OnVif & Discovery

(Your short primer to SOAP and OnVif)

Installing

Onvif and discovery come in a separate python package which you need to install with:

pip install -U valkka-onvif

Intro

OnVif is a remote-control protocol for manipulating IP cameras, developed by Axis.

You can use it to PTZ (pan-tilt-zoom) the camera, for setting camera’s credentials and resolution, and for almost anything else you can imagine.

OnVif is based on SOAP, i.e. on sending rather complex XML messages between your client computer and the IP camera. The messages (remote protocol calls), their responses and the parameters, are defined by WSDL files, which (when visualized nicely) look like this.

For a list of the up-to-date official WSDL files, visit here.

Python OnVif with Zeep

We use the Zeep opensource SOAP library to communicate with the cameras, with minimum extra code on top of Zeep.

You also need this table to get started:

WSDL Declaration

Camera http sub address

Wsdl file

Subclass

http://www.onvif.org/ver10/device/wsdl

device_service

devicemgmt-10.wsdl

DeviceManagement

http://www.onvif.org/ver10/media/wsdl

Media

media-10.wsdl

Media

http://www.onvif.org/ver10/events/wsdl

Events

events-10.wsdl

Events

http://www.onvif.org/ver20/ptz/wsdl

PTZ

ptz-20.wsdl

PTZ

http://www.onvif.org/ver20/imaging/wsdl

Imaging

imaging-20.wsdl

Imaging

http://www.onvif.org/ver10/deviceIO/wsdl

DeviceIO

deviceio-10.wsdl

DeviceIO

http://www.onvif.org/ver20/analytics/wsdl

Analytics

analytics-20.wsdl

Analytics

Here is an example on how to create your own class for an OnVif device service, based on the class OnVif:

from valkka.onvif import OnVif, getWSDLPath

# (1) create your own class:

class DeviceManagement(OnVif):
    namespace = "http://www.onvif.org/ver10/device/wsdl"
    wsdl_file = getWSDLPath("devicemgmt-10.wsdl")
    sub_xaddr = "device_service"
    port      = "DeviceBinding"


# (2) instantiate your class:

device_service = DeviceManagement(
    ip          = "192.168.0.24",
    port        = 80,
    user        = "admin",
    password    = "12345"
    )

The implementation of the base class OnVif is only a few lines long (take a look here) and it is based on Zeep.

The things you need for subclassing an OnVif service are:

  • The remote control protocol is declared / visualized in the link at the first column. Go to http://www.onvif.org/ver10/device/wsdl to see the detailed specifications.

  • In that specification, we see that the WSDL “port” is DeviceBinding.

  • Each SOAP remote control protocol comes with a certain namespace. This is the same as that address in the first column, so we set namespace to http://www.onvif.org/ver10/device/wsdl.

  • We use local modified versions of the wsdl files. This can be found in the third column, i.e. set wsdl_file to devicemgmt-10.wsdl (these files are included in valkka-onvif).

  • Camera’s local http subaddress sub_xaddr is device_service (the second column of the table)

Check out for an example subclass in here.

Service classes

You can create your own OnVif subclass as described above.

However, we have done some of the work for you. Take a look at the column “Subclass” in the table above, and you’ll find them:

from valkka.onvif import Media

media_service = Media(
    ip          = "192.168.0.24",
    port        = 80,
    user        = "admin",
    password    = "12345"
    )

Example call

Let’s try a remote protocol call.

If you look at that specification in http://www.onvif.org/ver10/device/wsdl, there is a remote protocol call name GetCapabilities. Let’s call it:

from valkka.onvif import DeviceManagement

device_service = DeviceManagement(
        ip          = "192.168.0.24",
        port        = 80,
        user        = "admin",
        password    = "12345"
        )

cap = device_service.ws_client.GetCapabilities()
print(cap)

We can also pass a variable to that GetCapabilities call - variables are nested objects, that must be constructed separately. Like this:

factory = device_service.zeep_client.type_factory("http://www.onvif.org/ver10/schema")
category = factory.CapabilityCategory("Device")
cap = device_service.ws_client.GetCapabilities(category)
print(cap)

The namespace http://www.onvif.org/ver10/schema declares all basic variables used by the wsdl files. We also provide a short-cut command for that:

category = device_service.getVariable("Device")

That’s about it. Now you are able to remote control your camera.

One extra bonus: to open the specifications directly with Firefox, try this

device_service.openSpecs()

Onvif Pitfalls

Axis cameras

Although Axis is the brand that created OnVif, their cameras are extremely picky on the onvif calls: if your client machine’s walltime differs even slightly on the camera’s time, axis cameras (at least the model I have), reject the call (you’ll get “Sender not Authorized”).

You need to set (manually) your camera’s time equal to your client linux machine’s time - up to a second. One option is to establish an NTP server on your linux machine and then tell the camera to use that NTP server to synchronize it’s time.

Time durations

When specifying durations with Zeep, you must use the isodate module, like this:

import isodate
timeout = isodate.Duration(seconds = 2)

Now that variable timeout can be used with OnVif calls.

Discovery

Discovery module uses arp-scan and/or the WSDiscovery protocol.

The API is subject to change and is explained in detail at the valkka-onvif main readme

Most importantly, you need to give normal users the ability to perform arp-scans, so before using discovery tools, do this:

sudo apt-get install arp-scan
sudo chmod u+s /usr/sbin/arp-scan