Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implements SystemDateAndTime schema as well as Get and Set operations with tests #71

Merged
merged 20 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions lib/device.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ defmodule Onvif.Device do
:auth_type,
:time_diff_from_system_secs,
:port,
:device_service_path
:device_service_path,
:system_date_time
]

@type t :: %__MODULE__{}
Expand All @@ -41,6 +42,7 @@ defmodule Onvif.Device do
field(:ntp, :string)
field(:media_ver10_service_path, :string)
field(:media_ver20_service_path, :string)
embeds_one(:system_date_time, Onvif.Devices.SystemDateAndTime)
embeds_many(:services, Onvif.Device.Service)

field(:auth_type, Ecto.Enum,
Expand Down Expand Up @@ -84,6 +86,7 @@ defmodule Onvif.Device do
def changeset(%__MODULE__{} = device, attrs \\ %{}) do
device
|> cast(attrs, @required ++ @optional)
|> cast_embed(:system_date_time, with: &Onvif.Devices.SystemDateAndTime.changeset/2)
|> cast_embed(:services, with: &Onvif.Device.Service.changeset/2)
|> validate_required(@required)
end
Expand Down Expand Up @@ -235,7 +238,13 @@ defmodule Onvif.Device do

defp get_date_time(device) do
with {:ok, res} <- Onvif.Devices.GetSystemDateAndTime.request(device) do
updated_device = %{device | time_diff_from_system_secs: res.current_diff, ntp: res.ntp}
updated_device = %{
device
| time_diff_from_system_secs: res.current_diff,
ntp: res.date_time_type,
system_date_time: res
}

{:ok, updated_device}
end
end
Expand Down
15 changes: 12 additions & 3 deletions lib/devices/devices.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,34 @@ defmodule Onvif.Devices do
alias Onvif.Device

@namespaces [
"xmlns:tds": "http://www.onvif.org/ver10/device/wsdl"
"xmlns:tds": "http://www.onvif.org/ver10/device/wsdl",
"xmlns:tt": "http://www.onvif.org/ver10/schema"
]

@spec request(Device.t(), module()) :: {:ok, any} | {:error, map()}
def request(%Device{} = device, operation) do
content = generate_content(operation)
soap_action = operation.soap_action()
do_request(device, operation, content)
end

def request(%Device{} = device, args, operation) do
content = generate_content(operation, args)
do_request(device, operation, content)
end

defp do_request(device, operation, content) do
device
|> Onvif.API.client()
|> Tesla.request(
method: :post,
headers: [{"Content-Type", "application/soap+xml"}, {"SOAPAction", soap_action}],
headers: [{"Content-Type", "application/soap+xml"}, {"SOAPAction", operation.soap_action()}],
body: %Onvif.Request{content: content, namespaces: @namespaces}
)
|> parse_response(operation)
end

defp generate_content(operation), do: operation.request_body()
defp generate_content(operation, args), do: operation.request_body(args)

defp parse_response({:ok, %{status: 200, body: body}}, operation) do
operation.response(body)
Expand Down
56 changes: 10 additions & 46 deletions lib/devices/get_system_date_and_time.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,51 +18,15 @@ defmodule Onvif.Devices.GetSystemDateAndTime do
end

def response(xml_response_body) do
doc = parse(xml_response_body, namespace_conformant: true, quiet: true)

parsed_result =
xpath(
doc,
~x"//s:Envelope/s:Body/tds:GetSystemDateAndTimeResponse/tds:SystemDateAndTime"
|> add_namespace("s", "http://www.w3.org/2003/05/soap-envelope")
|> add_namespace("tds", "http://www.onvif.org/ver10/device/wsdl"),
hour:
~x"./tt:UTCDateTime/tt:Time/tt:Hour/text()"i
|> add_namespace("tt", "http://www.onvif.org/ver10/schema"),
minute:
~x"./tt:UTCDateTime/tt:Time/tt:Minute/text()"i
|> add_namespace("tt", "http://www.onvif.org/ver10/schema"),
second:
~x"./tt:UTCDateTime/tt:Time/tt:Second/text()"i
|> add_namespace("tt", "http://www.onvif.org/ver10/schema"),
year:
~x"./tt:UTCDateTime/tt:Date/tt:Year/text()"i
|> add_namespace("tt", "http://www.onvif.org/ver10/schema"),
month:
~x"./tt:UTCDateTime/tt:Date/tt:Month/text()"i
|> add_namespace("tt", "http://www.onvif.org/ver10/schema"),
day:
~x"./tt:UTCDateTime/tt:Date/tt:Day/text()"i
|> add_namespace("tt", "http://www.onvif.org/ver10/schema"),
timezone:
~x"./tt:Timezone/tt:TZ/text()"s
|> add_namespace("tt", "http://www.onvif.org/ver10/schema"),
ntp:
~x"./tt:DateTimeType/text()"s
|> add_namespace("tt", "http://www.onvif.org/ver10/schema")
)

current = DateTime.utc_now()
{:ok, date} = Date.new(parsed_result.year, parsed_result.month, parsed_result.day)
{:ok, time} = Time.new(parsed_result.hour, parsed_result.minute, parsed_result.second)
{:ok, datetime} = DateTime.new(date, time)
diff_between_device = DateTime.diff(datetime, current)

{:ok,
%Onvif.Devices.SystemDateAndTime{
datetime: datetime,
ntp: parsed_result.ntp,
current_diff: diff_between_device
}}
xml_response_body
|> parse(namespace_conformant: true, quiet: true)
|> xpath(
~x"//s:Envelope/s:Body/tds:GetSystemDateAndTimeResponse/tds:SystemDateAndTime"e
|> add_namespace("s", "http://www.w3.org/2003/05/soap-envelope")
|> add_namespace("tds", "http://www.onvif.org/ver10/device/wsdl")
|> add_namespace("tt", "http://www.onvif.org/ver10/schema")
)
|> Onvif.Devices.SystemDateAndTime.parse()
|> Onvif.Devices.SystemDateAndTime.to_struct()
end
end
83 changes: 83 additions & 0 deletions lib/devices/set_system_date_and_time.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
defmodule Onvif.Devices.SetSystemDateAndTime do
@moduledoc """
Module used to set the device system date and time as well as their configuration such as daylight saving and NTP or Manual update type on an ONVIF device.
If system time and date are set manually, the request shall include UTCDateTime.
A TimeZone token which is not formed according to the rules of IEEE 1003.1 section 8.3 is considered as invalid timezone.
The DayLightSavings flag should be set to true to activate any DST settings of the TimeZone string. Clear the DayLightSavings flag if the DST portion of the TimeZone settings should be ignored.
The configuration requires a %Onvif.Devices.SystemDateAndTime{} config and a bolean set_time? to manually change the time. It will be ignored if using NTP.

Ref: https://www.onvif.org/ver10/device/wsdl/devicemgmt.wsdl#op.SetSystemDateAndTime
"""
import SweetXml
import XmlBuilder

alias Onvif.Device
alias Onvif.Devices.SystemDateAndTime

def soap_action, do: "http://www.onvif.org/ver10/device/wsdl/SetSystemDateAndTime"

@spec request(Device.t(), list) :: {:ok, any} | {:error, map()}
def request(device, args) do
Onvif.Devices.request(device, args, __MODULE__)
end

def request_body(config: %SystemDateAndTime{} = system_date_time) do
request_body(config: system_date_time, set_time?: false)
end

def request_body(config: %SystemDateAndTime{} = system_date_time, set_time?: set_time?) do
element(:"s:Body", [
element(:"tds:SetSystemDateAndTime", [
element(
:"tds:DateTimeType",
Keyword.fetch!(
Ecto.Enum.mappings(system_date_time.__struct__, :date_time_type),
system_date_time.date_time_type
)
),
element(:"tds:DaylightSavings", system_date_time.daylight_savings),
element(
:"tds:TimeZone",
[
element(:"tt:TZ", system_date_time.time_zone.tz)
]
),
List.flatten([utc_date_time_element(system_date_time, set_time?)])
])
])
end

def utc_date_time_element(_system_date_time, false), do: []

def utc_date_time_element(system_date_time, true) do
element(
:"tds:UTCDateTime",
[
element(:"tt:Time", [
element(:"tt:Hour", system_date_time.utc_date_time.time.hour),
element(:"tt:Minute", system_date_time.utc_date_time.time.minute),
element(:"tt:Second", system_date_time.utc_date_time.time.second)
]),
element(:"tt:Date", [
element(:"tt:Year", system_date_time.utc_date_time.date.year),
element(:"tt:Month", system_date_time.utc_date_time.date.month),
element(:"tt:Day", system_date_time.utc_date_time.date.day)
])
]
)
end

def response(xml_response_body) do
res =
xml_response_body
|> parse(namespace_conformant: true, quiet: true)
|> xpath(
~x"//s:Envelope/s:Body/tds:SetSystemDateAndTimeResponse/text()"s
|> add_namespace("s", "http://www.w3.org/2003/05/soap-envelope")
|> add_namespace("tds", "http://www.onvif.org/ver10/device/wsdl")
|> add_namespace("tt", "http://www.onvif.org/ver10/schema")
)

{:ok, res}
end
end
Loading