ExOxvif

Hex.pm CI

ExOxvif controls ONVIF IP cameras from Elixir. It wraps oxvif, an async Rust ONVIF client, in a Rustler NIF.

Service Functions
Discovery ExOxvif.discover/1 (WS-Discovery multicast)
Device info, clock, services, scopes
Media profiles, RTSP stream URI, snapshot URI
PTZ absolute, relative and continuous move, stop, status, presets, home, configurations, nodes and spaces
Imaging focus move, stop, status
Events pull-point create, pull, renew, unsubscribe

Authentication uses WS-Security UsernameToken (PasswordDigest) with HTTP Digest fallback. Clock offsets are synced from the device by default.

Installation

def deps do
  [
    {:ex_oxvif, "~> 0.1"}
  ]
end

Precompiled NIFs are downloaded for these targets:

To compile from source instead, add {:rustler, "~> 0.37"} to your deps and set EX_OXVIF_BUILD=1. This needs a Rust toolchain of 1.91 or later.

Usage

{:ok, [camera | _]} = ExOxvif.discover()

{:ok, session} =
  ExOxvif.connect(hd(camera.xaddrs), username: "admin", password: "secret")

{:ok, info} = ExOxvif.Device.info(session)
{:ok, [profile | _]} = ExOxvif.Media.profiles(session)
{:ok, %{uri: rtsp}} = ExOxvif.Media.stream_uri(session, profile.token)

# Pan, tilt and zoom in the device's generic spaces
:ok = ExOxvif.PTZ.absolute_move(session, profile.token, pan: 0.25, tilt: -0.1, zoom: 0.5)
{:ok, status} = ExOxvif.PTZ.status(session, profile.token)

# Presets
{:ok, gate} = ExOxvif.PTZ.set_preset(session, profile.token, name: "gate")
:ok = ExOxvif.PTZ.goto_preset(session, profile.token, gate)

# Focus (imaging takes the video source token)
:ok = ExOxvif.Imaging.move(session, profile.video_source_token, {:continuous, 0.3})
:ok = ExOxvif.Imaging.stop(session, profile.video_source_token)

# Events
{:ok, sub} = ExOxvif.Events.create_pull_point(session, termination: 60_000)
{:ok, messages} = ExOxvif.Events.pull_messages(session, sub, wait: 5_000)

Every call returns {:ok, value}, :ok, or {:error, %ExOxvif.Error{}}. %ExOxvif.Error{} is an exception, so you can raise it. Its :kind separates transport failures, HTTP status codes, SOAP faults and parse errors.

Degrees and PTZ spaces

ONVIF PTZ positions are normalised: pan and tilt run over -1.0..1.0, and zoom over 0.0..1.0. How those values map to degrees depends on the camera. ExOxvif.PTZ.nodes/1 lists the coordinate spaces a camera supports, and ExOxvif.PTZ.configurations/1 gives its configured limits. A pointing application usually also needs per-camera calibration: the mount heading and the pan and tilt range in degrees.

How the NIF behaves

Each function is a NIF that returns immediately. The ONVIF request runs on a two-thread Tokio runtime inside the native library. When it finishes, the result is sent to the calling process, which is waiting in a receive. Consequences:

Development

mix deps.get
mix test                       # builds the NIF with oxvif's mock camera
mix test --include multicast   # also runs the WS-Discovery test

The test suite runs against oxvif's in-process mock ONVIF camera. That code is compiled only into test builds (the mock-server cargo feature) and never ships in release artifacts.

Commits follow Conventional Commits. To install the hook:

cp scripts/hooks/commit-msg .git/hooks/commit-msg

Releasing is described in RELEASING.md.

License

MIT. oxvif is also MIT-licensed and copyright its authors.