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 5 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
7 changes: 5 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,7 @@ 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: 15 additions & 0 deletions lib/devices/devices.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,22 @@ defmodule Onvif.Devices do
|> parse_response(operation)
end

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

device
|> Onvif.API.client()
|> Tesla.request(
method: :post,
headers: [{"Content-Type", "application/soap+xml"}, {"SOAPAction", 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: apply(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
69 changes: 69 additions & 0 deletions lib/devices/set_system_date_and_time.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
defmodule Onvif.Devices.SetSystemDateAndTime do
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(%SystemDateAndTime{} = system_date_time) do
request_body([config: system_date_time, set_time?: false])
end

#def request_body(%SystemDateAndTime{} = system_date_time, opts) do
def request_body([config: %SystemDateAndTime{} = system_date_time] = opts) do
paoloo marked this conversation as resolved.
Show resolved Hide resolved
IO.inspect(opts)
set_time? = Keyword.get(opts, :set_time?, false)
element(:"s:Body", [
element(:"tds:SetSystemDateAndTime", [
element(:"tds:DateAndTime", 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 [] end

Check warning on line 38 in lib/devices/set_system_date_and_time.ex

View workflow job for this annotation

GitHub Actions / Build and test

variable "system_date_time" is unused (if the variable is not meant to be used, prefix it with an underscore)
paoloo marked this conversation as resolved.
Show resolved Hide resolved
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
178 changes: 177 additions & 1 deletion lib/devices/system_date_and_time.ex
Original file line number Diff line number Diff line change
@@ -1,3 +1,179 @@
defmodule Onvif.Devices.SystemDateAndTime do
defstruct [:datetime, :ntp, :current_diff]
use Ecto.Schema
import Ecto.Changeset
import SweetXml

@primary_key false
@derive Jason.Encoder
@required [:date_time_type, :daylight_savings]
@optional []

embedded_schema do
field :date_time_type, Ecto.Enum, values: [manual: "Manual", ntp: "NTP"]
field :daylight_savings, :boolean, default: true
field :datetime, :utc_datetime
field :current_diff, :integer

embeds_one :time_zone, TimeZone, primary_key: false, on_replace: :update do
yuriploc marked this conversation as resolved.
Show resolved Hide resolved
@derive Jason.Encoder
field(:tz, :string)
end

embeds_one :utc_date_time, UTCDateTime, primary_key: false, on_replace: :update do
@derive Jason.Encoder

embeds_one :time, Time, primary_key: false, on_replace: :update do
@derive Jason.Encoder
field(:hour, :integer)
field(:minute, :integer)
field(:second, :integer)
end

embeds_one :date, Date, primary_key: false, on_replace: :update do
@derive Jason.Encoder
field(:year, :integer)
field(:month, :integer)
field(:day, :integer)
end
end

embeds_one :local_date_time, UTCDateTime, primary_key: false, on_replace: :update do

Check warning on line 40 in lib/devices/system_date_and_time.ex

View workflow job for this annotation

GitHub Actions / Build and test

redefining module Onvif.Devices.SystemDateAndTime.UTCDateTime (current version loaded from _build/test/lib/onvif/ebin/Elixir.Onvif.Devices.SystemDateAndTime.UTCDateTime.beam)

Check warning on line 40 in lib/devices/system_date_and_time.ex

View workflow job for this annotation

GitHub Actions / Build and test

redefining module Jason.Encoder.Onvif.Devices.SystemDateAndTime.UTCDateTime (current version loaded from _build/test/lib/onvif/ebin/Elixir.Jason.Encoder.Onvif.Devices.SystemDateAndTime.UTCDateTime.beam)
paoloo marked this conversation as resolved.
Show resolved Hide resolved
@derive Jason.Encoder

embeds_one :time, Time, primary_key: false, on_replace: :update do

Check warning on line 43 in lib/devices/system_date_and_time.ex

View workflow job for this annotation

GitHub Actions / Build and test

redefining module Onvif.Devices.SystemDateAndTime.UTCDateTime.Time (current version loaded from _build/test/lib/onvif/ebin/Elixir.Onvif.Devices.SystemDateAndTime.UTCDateTime.Time.beam)

Check warning on line 43 in lib/devices/system_date_and_time.ex

View workflow job for this annotation

GitHub Actions / Build and test

redefining module Jason.Encoder.Onvif.Devices.SystemDateAndTime.UTCDateTime.Time (current version loaded from _build/test/lib/onvif/ebin/Elixir.Jason.Encoder.Onvif.Devices.SystemDateAndTime.UTCDateTime.Time.beam)
@derive Jason.Encoder
field(:hour, :integer)
field(:minute, :integer)
field(:second, :integer)
end

embeds_one :date, Date, primary_key: false, on_replace: :update do

Check warning on line 50 in lib/devices/system_date_and_time.ex

View workflow job for this annotation

GitHub Actions / Build and test

redefining module Onvif.Devices.SystemDateAndTime.UTCDateTime.Date (current version loaded from _build/test/lib/onvif/ebin/Elixir.Onvif.Devices.SystemDateAndTime.UTCDateTime.Date.beam)

Check warning on line 50 in lib/devices/system_date_and_time.ex

View workflow job for this annotation

GitHub Actions / Build and test

redefining module Jason.Encoder.Onvif.Devices.SystemDateAndTime.UTCDateTime.Date (current version loaded from _build/test/lib/onvif/ebin/Elixir.Jason.Encoder.Onvif.Devices.SystemDateAndTime.UTCDateTime.Date.beam)
@derive Jason.Encoder
field(:year, :integer)
field(:month, :integer)
field(:day, :integer)
end
end
end

def parse(nil), do: nil
def parse([]), do: nil

def parse(doc) do
xmap(
doc,
date_time_type: ~x"./tt:DateTimeType/text()"so,
daylight_savings: ~x"./tt:DaylightSavings/text()"so,
time_zone: ~x"./tt:TimeZone"eo |> transform_by(&parse_time_zone/1),
utc_date_time: ~x"./tt:UTCDateTime"eo |> transform_by(&parse_date_time/1),
local_date_time: ~x"./tt:LocalDateTime"eo |> transform_by(&parse_date_time/1)
)
end

defp parse_time_zone([]), do: nil
defp parse_time_zone(nil), do: nil
defp parse_time_zone(doc) do
xmap(
doc,
tz: ~x"./tt:TZ/text()"s
)
end

defp parse_date_time([]), do: nil
defp parse_date_time(nil), do: nil
defp parse_date_time(doc) do
xmap(
doc,
time: ~x"./tt:Time"eo |> transform_by(&parse_time/1),
date: ~x"./tt:Date"eo |> transform_by(&parse_date/1),
)
end

defp parse_time([]), do: nil
defp parse_time(nil), do: nil
defp parse_time(doc) do
xmap(
doc,
hour: ~x"./tt:Hour/text()"i,
minute: ~x"./tt:Minute/text()"i,
second: ~x"./tt:Second/text()"i
)
end
defp parse_date([]), do: nil
defp parse_date(nil), do: nil
defp parse_date(doc) do
xmap(
doc,
year: ~x"./tt:Year/text()"i,
month: ~x"./tt:Month/text()"i,
day: ~x"./tt:Day/text()"i
)
end

def to_struct(parsed) do
%__MODULE__{}
|> changeset(parsed)
|> apply_action(:validate)
end

@spec to_json(%__MODULE__{}) ::
{:error,
%{
:__exception__ => any,
:__struct__ => Jason.EncodeError | Protocol.UndefinedError,
optional(atom) => any
}}
| {:ok, binary}
def to_json(%__MODULE__{} = schema) do
Jason.encode(schema)
end


def changeset(module, attrs) do
module
|> cast(attrs, @required ++ @optional)
|> validate_required(@required)
|> cast_embed(:time_zone, with: &time_zone_changeset/2)
|> cast_embed(:utc_date_time, with: &date_time_changeset/2)
|> cast_embed(:local_date_time, with: &date_time_changeset/2)
|> put_datetime
|> put_current_diff
end

defp put_datetime(changeset) do
case get_field(changeset, :utc_date_time) do
nil -> changeset
utc_date_time ->
{:ok, date} = Date.new(utc_date_time.date.year, utc_date_time.date.month, utc_date_time.date.day)
{:ok, time} = Time.new(utc_date_time.time.hour, utc_date_time.time.minute, utc_date_time.time.second)
{:ok, datetime} = DateTime.new(date, time)
put_change(changeset, :datetime, datetime)
end
end

defp put_current_diff(changeset) do
case get_field(changeset, :datetime) do
nil -> changeset
datetime ->
current = DateTime.utc_now()
diff = DateTime.diff(datetime, current)
put_change(changeset, :current_diff, diff)
end
end

defp time_zone_changeset(module, attrs) do
cast(module, attrs, [:tz])
end
defp date_time_changeset(module, attrs) do
cast(module, attrs, [])
|> cast_embed(:date, with: &date_changeset/2)
|> cast_embed(:time, with: &time_changeset/2)
end
defp date_changeset(module, attrs) do
cast(module, attrs, [:year, :month, :day])
end
defp time_changeset(module, attrs) do
cast(module, attrs, [:hour, :minute, :second])
end

end
Loading