diff --git a/lib/device.ex b/lib/device.ex index 612e912..640dc15 100644 --- a/lib/device.ex +++ b/lib/device.ex @@ -333,6 +333,4 @@ defmodule Onvif.Device do %Onvif.Device.Service{} = service -> service.xaddr |> URI.parse() |> Map.get(:path) end end - - end diff --git a/lib/recording/create_recording.ex b/lib/recording/create_recording.ex index 3416e23..d866f64 100644 --- a/lib/recording/create_recording.ex +++ b/lib/recording/create_recording.ex @@ -53,8 +53,9 @@ defmodule Onvif.Recording.CreateRecording do def gen_maximum_retention_time(nil), do: [] def gen_maximum_retention_time(""), do: [] - def gen_maximum_retention_time(maximum_retention_time), do: element(:"tt:MaximumRetentionTime", maximum_retention_time) + def gen_maximum_retention_time(maximum_retention_time), + do: element(:"tt:MaximumRetentionTime", maximum_retention_time) def response(xml_response_body) do recording_token = diff --git a/lib/recording/create_recording_job.ex b/lib/recording/create_recording_job.ex index da1ca52..9614d8b 100644 --- a/lib/recording/create_recording_job.ex +++ b/lib/recording/create_recording_job.ex @@ -31,6 +31,7 @@ defmodule Onvif.Recording.CreateRecordingJob do |> add_namespace("trc", "http://www.onvif.org/ver10/recording/wsdl") |> add_namespace("tt", "http://www.onvif.org/ver10/schema") ) + {:ok, parsed_result} end end diff --git a/lib/search/find_events.ex b/lib/search/find_events.ex new file mode 100644 index 0000000..93ff136 --- /dev/null +++ b/lib/search/find_events.ex @@ -0,0 +1,39 @@ +defmodule Onvif.Search.FindEvents do + import SweetXml + import XmlBuilder + require Logger + + def soap_action, do: "http://www.onvif.org/ver10/search/wsdl/FindEvents" + + def request(device, args) do + Onvif.Search.request(device, args, __MODULE__) + end + + def request_body([included_recordings, start_point, end_point, search_filter, keep_alive_time]) do + element(:"s:Body", [ + element(:"tse:FindEvents", [ + element(:"tse:StartPoint", start_point), + element(:"tse:EndPoint", end_point), + element(:"tse:scope", [ + Enum.map(included_recordings, fn ir -> element(:"tt:IncludedRecordings", [ir]) end) + ]), + element(:"tt:SearchFilter", [search_filter]), + element(:"tse:IncludeStartState", false), + element(:"tse:KeepAliveTime", keep_alive_time) + ]) + ]) + end + + def response(xml_response_body) do + parsed_result = + xml_response_body + |> parse(namespace_conformant: true, quiet: true) + |> xpath( + ~x"//tse:SearchToken/text()"s + |> add_namespace("s", "http://www.w3.org/2003/05/soap-envelope") + |> add_namespace("tse", "http://www.onvif.org/ver10/search/wsdl") + ) + + {:ok, parsed_result} + end +end diff --git a/lib/search/get_recording_summary.ex b/lib/search/get_recording_summary.ex new file mode 100644 index 0000000..e818814 --- /dev/null +++ b/lib/search/get_recording_summary.ex @@ -0,0 +1,35 @@ +defmodule Onvif.Search.GetRecordingSummary do + import SweetXml + import XmlBuilder + require Logger + + def soap_action, do: "http://www.onvif.org/ver10/search/wsdl/GetRecordingSummary" + + def request(device) do + Onvif.Search.request(device, __MODULE__) + end + + def request_body() do + element(:"s:Body", [ + element(:"tse:GetRecordingSummary") + ]) + 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/tse:GetRecordingSummaryResponse/tse:Summary" + |> add_namespace("s", "http://www.w3.org/2003/05/soap-envelope") + |> add_namespace("tse", "http://www.onvif.org/ver10/search/wsdl") + |> add_namespace("tt", "http://www.onvif.org/ver10/schema"), + data_from: ~x"//tt:DataFrom/text()"so, + data_until: ~x"//tt:DataUntil/text()"so, + number_recordings: ~x"//tt:NumberRecordings/text()"so + ) + + {:ok, parsed_result} + end +end diff --git a/test/recording/create_recording_job_test.exs b/test/recording/create_recording_job_test.exs index 2437cb9..465bd32 100644 --- a/test/recording/create_recording_job_test.exs +++ b/test/recording/create_recording_job_test.exs @@ -13,7 +13,12 @@ defmodule Onvif.Recording.CreateRecordingJobTest do {:ok, %{status: 200, body: xml_response}} end) - {:ok, response} = Onvif.Recording.CreateRecordingJob.request(device, ["SD_DISK_20241120_211729_9C896594", "9", "Active"]) + {:ok, response} = + Onvif.Recording.CreateRecordingJob.request(device, [ + "SD_DISK_20241120_211729_9C896594", + "9", + "Active" + ]) assert response == "SD_DISK_20241120_211729_9C896594" end diff --git a/test/recording/create_recording_test.exs b/test/recording/create_recording_test.exs index 65042cd..5dc4f83 100644 --- a/test/recording/create_recording_test.exs +++ b/test/recording/create_recording_test.exs @@ -13,15 +13,17 @@ defmodule Onvif.Recording.CreateRecordingTest do {:ok, %{status: 200, body: xml_response}} end) - {:ok, response_uri} = Onvif.Recording.CreateRecording.request( - device, - config: %Onvif.Recording.Recording.Configuration{ + {:ok, response_uri} = + Onvif.Recording.CreateRecording.request( + device, + config: %Onvif.Recording.Recording.Configuration{ content: "test", maximum_retention_time: "PT1H", source: %Onvif.Recording.Recording.Configuration.Source{ - name: "test", + name: "test" } - }) + } + ) assert response_uri == "SD_DISK_20200422_123501_A2388AB3" end diff --git a/test/search/find_events_test.exs b/test/search/find_events_test.exs new file mode 100644 index 0000000..a0d5da0 --- /dev/null +++ b/test/search/find_events_test.exs @@ -0,0 +1,28 @@ +defmodule Onvif.Search.FindEventsTest do + use ExUnit.Case, async: true + + @moduletag capture_log: true + + describe "FindEvents/2" do + test "get an event search token" do + xml_response = File.read!("test/search/fixtures/find_events__success.xml") + + device = Onvif.Factory.device() + + Mimic.expect(Tesla, :request, fn _client, _opts -> + {:ok, %{status: 200, body: xml_response}} + end) + + {:ok, response} = + Onvif.Search.FindEvents.request(device, [ + ["Record_004"], + "2024-12-06T19:00:00.0Z", + "2024-12-06T19:02:00.0Z", + "tns1:RecordingHistory/Track/State", + "PT1M" + ]) + + assert response == "SearchToken[1]" + end + end +end diff --git a/test/search/fixtures/find_events__success.xml b/test/search/fixtures/find_events__success.xml new file mode 100644 index 0000000..6023b58 --- /dev/null +++ b/test/search/fixtures/find_events__success.xml @@ -0,0 +1,53 @@ + + + + + + SearchToken[1] + + + \ No newline at end of file diff --git a/test/search/fixtures/get_recording_summary__success.xml b/test/search/fixtures/get_recording_summary__success.xml new file mode 100644 index 0000000..8130e42 --- /dev/null +++ b/test/search/fixtures/get_recording_summary__success.xml @@ -0,0 +1,57 @@ + + + + + + + 1970-01-01T00:00:00Z + 2024-12-13T08:08:49Z + 8 + + + + diff --git a/test/search/get_recording_summary_test.exs b/test/search/get_recording_summary_test.exs new file mode 100644 index 0000000..30f466d --- /dev/null +++ b/test/search/get_recording_summary_test.exs @@ -0,0 +1,25 @@ +defmodule Onvif.Search.GetRecordingSummaryTest do + use ExUnit.Case, async: true + + @moduletag capture_log: true + + describe "GetRecordingSummary/0" do + test "get an event search token" do + xml_response = File.read!("test/search/fixtures/get_recording_summary__success.xml") + + device = Onvif.Factory.device() + + Mimic.expect(Tesla, :request, fn _client, _opts -> + {:ok, %{status: 200, body: xml_response}} + end) + + {:ok, response} = + Onvif.Search.GetRecordingSummary.request(device) + + assert response == %{ + data_from: "1970-01-01T00:00:00Z", + data_until: "2024-12-13T08:08:49Z", + number_recordings: "8"} + end + end +end