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

ExportRecordedData #86

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from 21 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
20 changes: 20 additions & 0 deletions lib/device.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ defmodule Onvif.Device do
:ntp,
:media_ver10_service_path,
:media_ver20_service_path,
:recording_service_path,
:replay_service_path,
:auth_type,
:time_diff_from_system_secs,
:port,
Expand All @@ -41,6 +43,8 @@ defmodule Onvif.Device do
field(:ntp, :string)
field(:media_ver10_service_path, :string)
field(:media_ver20_service_path, :string)
field(:recording_service_path, :string)
field(:replay_service_path, :string)
embeds_one(:system_date_time, Onvif.Devices.SystemDateAndTime)
embeds_many(:services, Onvif.Device.Service)

Expand Down Expand Up @@ -288,6 +292,8 @@ defmodule Onvif.Device do
device
|> Map.put(:media_ver10_service_path, get_media_ver10_service_path(device.services))
|> Map.put(:media_ver20_service_path, get_media_ver20_service_path(device.services))
|> Map.put(:recording_service_path, get_recoding_service_path(device.services))
|> Map.put(:replay_service_path, get_replay_service_path(device.services))
end

defp get_media_ver20_service_path(services) do
Expand All @@ -303,4 +309,18 @@ defmodule Onvif.Device do
%Onvif.Device.Service{} = service -> service.xaddr |> URI.parse() |> Map.get(:path)
end
end

defp get_recoding_service_path(services) do
case Enum.find(services, &String.contains?(&1.namespace, "/recording")) do
nil -> nil
%Onvif.Device.Service{} = service -> service.xaddr |> URI.parse() |> Map.get(:path)
end
end

defp get_replay_service_path(services) do
case Enum.find(services, &String.contains?(&1.namespace, "/replay")) do
nil -> nil
%Onvif.Device.Service{} = service -> service.xaddr |> URI.parse() |> Map.get(:path)
end
end
end
2 changes: 2 additions & 0 deletions lib/factory.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ defmodule Onvif.Factory do
manufacturer: "General",
media_ver10_service_path: "/onvif/media_service",
media_ver20_service_path: "/onvif/media2_service",
replay_service_path: "/onvif/replay_service",
recording_service_path: "/onvif/recording_service",
model: "N864A6",
ntp: "NTP",
password: "admin",
Expand Down
56 changes: 56 additions & 0 deletions lib/recording.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
defmodule Onvif.Recording do
@moduledoc """
Interface for making requests to the Onvif recording service
http://www.onvif.org/onvif/ver10/recording.wsdl
"""
require Logger
alias Onvif.Device

@namespaces [
"xmlns:trc": "http://www.onvif.org/ver10/recording/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)
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(service_path: :recording_service_path)
|> Tesla.request(
method: :post,
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)
end

defp parse_response({:ok, %{status: status_code, body: body}}, operation)
when status_code >= 400,
do:
{:error,
%{
status: status_code,
reason: "Received #{status_code} from #{operation}",
response: body
}}

defp parse_response({:error, response}, operation) do
{:error, %{status: nil, reason: "Error performing #{operation}", response: response}}
end
end
42 changes: 42 additions & 0 deletions lib/recording/create_recording.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
defmodule Onvif.Recording.CreateRecording do
import SweetXml
import XmlBuilder
require Logger

def soap_action, do: "http://www.onvif.org/ver10/recording/wsdl/CreateRecording"

def request(device, args) do
Onvif.Recording.request(device, args, __MODULE__)
end

def request_body(%{name: name, content: content, max_retention: max_retention}) do
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should accept recording configuration as input like we do here. So that we can set all the input attrs. Right now I see we are defaulting to "" for some attrs. With the current approach of map as input we need to start adding new keys if we want to support setting all the attrs from the spec.

image

element(:"s:Body", [
element(:"trc:CreateRecording", [
element(:"trc:RecordingConfiguration", [
element(:"tt:Source", [
element(:"tt:SourceId", ""),
element(:"tt:Name", name),
element(:"tt:Location", ""),
element(:"tt:Description", ""),
element(:"tt:Address", "")
]),
element(:"tt:Content", content),
element(:"tt:MaximumRetentionTime", max_retention)
])
])
])
end

def response(xml_response_body) do
response_uri =
xml_response_body
|> parse(namespace_conformant: true, quiet: true)
|> xpath(
~x"//s:Envelope/s:Body/trc:CreateRecordingResponse/trc:RecordingToken/text()"s
|> add_namespace("s", "http://www.w3.org/2003/05/soap-envelope")
|> add_namespace("trc", "http://www.onvif.org/ver10/recording/wsdl")
)

{:ok, response_uri}
paoloo marked this conversation as resolved.
Show resolved Hide resolved
end
end
40 changes: 40 additions & 0 deletions lib/recording/create_recording_job.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
defmodule Onvif.Recording.CreateRecordingJob do
import SweetXml
import XmlBuilder
require Logger

def soap_action, do: "http://www.onvif.org/ver10/recording/wsdl/CreateRecordingJob"

def request(device, args) do
Onvif.Recording.request(device, args, __MODULE__)
end

def request_body(recording_token, priority \\ "0", mode \\ "Active") do
element(:"s:Body", [
element(:"trc:CreateRecordingJob", [
element(:"trc:JobConfiguration", [
element(:"tt:RecordingToken", recording_token),
element(:"tt:Mode", mode),
element(:"tt:Priority", priority)
])
])
])
end

def response(xml_response_body) do
parsed_result =
xml_response_body
|> parse(namespace_conformant: true, quiet: true)
|> xpath(
~x"//s:Envelope/s:Body/trc:CreateRecordingJobResponse"
|> add_namespace("s", "http://www.w3.org/2003/05/soap-envelope")
|> add_namespace("trc", "http://www.onvif.org/ver10/recording/wsdl"),
job_token: ~x"//trc:JobToken/text()"so,
recording_token: ~x"//trc:JobConfiguration/tt:RecordingToken/text()"so,
mode: ~x"//trc:JobConfiguration/tt:Mode/text()"so,
priority: ~x"//trc:JobConfiguration/tt:Priority/text()"so
)

{:ok, parsed_result}
end
end
44 changes: 44 additions & 0 deletions lib/recording/get_recording_jobs.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
defmodule Onvif.Recording.GetRecordingJobs do
import SweetXml
import XmlBuilder
require Logger

alias Onvif.Recording.RecordingJobs

def soap_action, do: "http://www.onvif.org/ver10/recording/wsdl/GetRecordingJobs"

def request(device) do
Onvif.Recording.request(device, __MODULE__)
end

def request_body() do
element(:"s:Body", [
element(:"trc:GetRecordingJobs")
])
end

def response(xml_response_body) do
response =
xml_response_body
|> parse(namespace_conformant: true, quiet: true)
|> xpath(
~x"//s:Envelope/s:Body/trc:GetRecordingJobsResponse/trc:JobItem"el
|> add_namespace("s", "http://www.w3.org/2003/05/soap-envelope")
|> add_namespace("trc", "http://www.onvif.org/ver10/recording/wsdl")
|> add_namespace("tt", "http://www.onvif.org/ver10/schema")
)
|> Enum.map(&RecordingJobs.parse/1)
|> Enum.reduce([], fn raw_job, acc ->
case RecordingJobs.to_struct(raw_job) do
{:ok, job} ->
[job | acc]

{:error, changeset} ->
Logger.error("Discarding invalid recording: #{inspect(changeset)}")
acc
end
end)

{:ok, response}
end
end
44 changes: 44 additions & 0 deletions lib/recording/get_recordings.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
defmodule Onvif.Recording.GetRecordings do
import SweetXml
import XmlBuilder
require Logger

alias Onvif.Recording.Recordings

def soap_action, do: "http://www.onvif.org/ver10/recording/wsdl/GetRecordings"

def request(device) do
Onvif.Recording.request(device, __MODULE__)
end

def request_body() do
element(:"s:Body", [
element(:"trc:GetRecordings")
])
end

def response(xml_response_body) do
response =
xml_response_body
|> parse(namespace_conformant: true, quiet: true)
|> xpath(
~x"//s:Envelope/s:Body/trc:GetRecordingsResponse/trc:RecordingItem"el
|> add_namespace("s", "http://www.w3.org/2003/05/soap-envelope")
|> add_namespace("trc", "http://www.onvif.org/ver10/recording/wsdl")
|> add_namespace("tt", "http://www.onvif.org/ver10/schema")
)
|> Enum.map(&Recordings.parse/1)
|> Enum.reduce([], fn raw_recording, acc ->
case Recordings.to_struct(raw_recording) do
{:ok, recording} ->
[recording | acc]

{:error, changeset} ->
Logger.error("Discarding invalid recording: #{inspect(changeset)}")
acc
end
end)

{:ok, response}
end
end
Loading
Loading