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), the responses and the parameters, are defined by
WSDL files
, which (when visualized nicely) look like
this
.
Python OnVif
In Python, the main bottleneck was in finding a decent open source SOAP library that would do the trick. Recently, things have got better with the arrival of
Zeep
.
Before Zeep existed, people used
Suds
, which has become a bit obsolete by now. A library called
python-onvif
was based on Suds.
That python-onvif module has since then been forked and modded
to work with Zeep
.
However, we don’t need any of that, since it’s a better idea to
use Zeep directly
with minimum extra code bloat on top of it.
So, use Zeep as your SOAP client, give it the WSDL file and that’s about it.
OnVif with Zeep
Rather than giving you an obscure OnVif client implementation, you’ll learn to do this by yourself using Zeep. Let’s begin with:
pip3 install zeep
You also need this table to get started:
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.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)
The things you need for (1) 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 a local modified version of the wsdl file. This can be found in the third column, i.e. set wsdl_file
to devicemgmt.wsdl
(these files come included in libValkka).
Camera’s local http subaddress sub_xaddr
is device_service
(the second column of the table)
When you (2) instantiate the class into the device_service
object, you just give the camera’s local IP address and credentials
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, 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:
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()
Notes
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
In libValkka, cameras can be discovered like this:
from valkka.discovery import runWSDiscovery, runARPScan
ips = runWSDiscovery()
ips2 = runARPScan(exclude_list = ips) # run this if you want to use arp-scan
If you want arp-scan to work, you must permit normal users to run the executable, with:
sudo apt-get install arp-scan
sudo chmod u+s /usr/sbin/arp-scan