OnVif & Discovery
(And 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
Some of the discovery tools in that package employ arp-scan, so do also this:
sudo apt-get install arp-scan
sudo chmod u+s /usr/sbin/arp-scan
Discovery Quickstart
After installing, you can start discovering cameras with
valkka-camsearch
which combines several discovery strategies (wsdiscovery, rtsp scan, arp-scan) and tries to connect to the cameras using onvif.
Use the –help argument for more options
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 |
---|---|---|---|
device_service |
devicemgmt-10.wsdl |
DeviceManagement |
|
Media |
media-10.wsdl |
Media |
|
Events |
events-10.wsdl |
Events |
|
PTZ |
ptz-20.wsdl |
PTZ |
|
Imaging |
imaging-20.wsdl |
Imaging |
|
DeviceIO |
deviceio-10.wsdl |
DeviceIO |
|
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
tohttp://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
todevicemgmt-10.wsdl
(these files are included in valkka-onvif).Camera’s local http subaddress
sub_xaddr
isdevice_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