pythonip-cameraonvifzeeppython-onvif

Send VISCA commands to IPcamera using python-onvif-zeep or valkka and sendreceiveserialcommand service from DeviceIO


The problem

I have recently acquired an Active Silicon IP-camera and I have been trying to control it using python-onvif-zeep or valkka. The camera implements the ONVIF Profile S standard. I would like to send zoom in / our, focus and other basic VISCA commands to the camera using the SendReceiveSerialData service as described in the DeviceIO wsdl, however I can not seem to get it to work. My knowledge in ONVIF, SOAP, and zeep is quite limited so please forgive me if its something too obvious!

Minimal reproducible example and needed changes

MRE

Here is the python code I have (I have replaced the original python-onvif-zeep code I had with valkka because it makes everything a bit neater):

#! /usr/bin/env python3

from valkka.onvif import OnVif, DeviceManagement, Media, DeviceIO, PTZ, getWSDLPath
from zeep import Client
import zeep.helpers
from zeep.wsse.username import UsernameToken
import time

try:
    device_service = DeviceManagement(
        ip="10.0.0.250",
        port=8000,
        user="",
        password=""
    )
except Exception as e:
    print(e)

cap = device_service.ws_client.GetCapabilities()
print("CAPABILITIES: \n{}".format(cap))
srv = device_service.ws_client.GetServices(True)
print("SERVICES: \n{}".format(srv))

try:
    deviceIO_service = DeviceIO(
        ip="10.0.0.250",
        port=8000,
        user="",
        password=""
    )
except Exception as e:
    print(e)

# element = deviceIO_service.zeep_client.get_element('ns10:SendReceiveSerialCommand')
# print(element)

ports = deviceIO_service.ws_client.GetSerialPorts()
# print (ports)
serial_token = ports[0].token
# print(serial_token)

zoomin = bytes.fromhex('81 01 04 07 02 FF')
#zoomout = bytes.fromhex('81 01 04 07 03 FF')
#zoomin = b'\x81\x01\x04\x07\x02\xff'

data = {
    'SerialData': zoomin
}

ack = deviceIO_service.ws_client.SendReceiveSerialCommand(serial_token, data)
print(ack)
time.sleep(3)

The first issue I encountered was that the SendReceiveSerialCommand service was not working and the following error occurred:

raise ConnectionError(err, request=request)
requests.exceptions.ConnectionError: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))
Needed changes

The reason behind that is the deviceIO.wsdl file of the library. It does not contain the "Token" element, unlike DeviceIO wsdl,therefore I had to manually add it under the SendReceiveSerialCommand element:

    <xs:element name="SendReceiveSerialCommand">
        <xs:annotation>
            <xs:documentation>Transmitting arbitrary data to the connected serial device and then receiving its response data.</xs:documentation>
        </xs:annotation>
        <xs:complexType>
            <xs:sequence>
                <xs:element name="Token" type="tt:ReferenceToken" minOccurs="0">
                    <xs:annotation>
                                <xs:documentation>The physical serial port reference to be used when this request is invoked.</xs:documentation>
                        </xs:annotation>
                    </xs:element>
                <xs:element name="SerialData" type="tmd:SerialData" minOccurs="0">
                    <xs:annotation>
                        <xs:documentation>The serial port data.</xs:documentation>
                    </xs:annotation>
                </xs:element>
                <xs:element name="TimeOut" type="xs:duration" minOccurs="0">
                    <xs:annotation>
                        <xs:documentation>Indicates that the command should be responded back within the specified period of time.</xs:documentation>
                    </xs:annotation>
                </xs:element>
                <xs:element name="DataLength" type="xs:integer" minOccurs="0">
                    <xs:annotation>
                        <xs:documentation>This element may be put in the case that data length returned from the connected serial device is already determined as some fixed bytes length. It indicates the length of received data which can be regarded as available.</xs:documentation>
                    </xs:annotation>
                </xs:element>
                <xs:element name="Delimiter" type="xs:string" minOccurs="0">
                    <xs:annotation>
                        <xs:documentation>This element may be put in the case that the delimiter codes returned from the connected serial device is already known. It indicates the termination data sequence of the responded data. In case the string has more than one character a device shall interpret the whole string as a single delimiter. Furthermore a device shall return the delimiter character(s) to the client.</xs:documentation>
                    </xs:annotation>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="SendReceiveSerialCommandResponse">
        <xs:annotation>
            <xs:documentation>Receiving the response data.</xs:documentation>
        </xs:annotation>
        <xs:complexType>
            <xs:sequence>
                <xs:element name="SerialData" type="tmd:SerialData" minOccurs="0"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

After this part was fixed the errors go away, but the camera does not zoom in/out ( depending on which VISCA command is being passed). Additionally, the acknowledgement comes back as "None".

Nothing changes if I fill the dictionary with all the fields from the SendReceiveSerialCommand service like so:

serial_data = {
    'SerialData': zoomin,
    'TimeOut': "PT0M0.1S",
    'DataLength': "100",
    'Delimiter': "",
}

The setup

I am running this python script on Ubuntu 20.04 with python 3.8. The setup is a network between the camera and the laptop with static IP addresses assigned.

Please note that the camera is working as I can successfully send commands to the camera over the web interface and when running the C# example (can be found under downloads->software) it comes with, on a Windows machine.

Thank you in advance for your time and effort to help me out!


Solution

  • I managed to get your example to work using the following code. The data was not passed in the correct format to the function, so the SOAP message was incomplete.

    Check the zeep documentation for details about passing SOAP datastructures.

    deviceio_type_factory = deviceIO_service.zeep_client.type_factory("http://www.onvif.org/ver10/deviceIO/wsdl")
    serial_data = deviceio_type_factory.SerialData(Binary=zoomin)
    
    ack = deviceIO_service.ws_client.SendReceiveSerialCommand(Token= ports[0].token, SerialData=serial_data, TimeOut='PT0M6S', DataLength='100', Delimiter='')
    
    visca_ack_comp = ack['Binary'].hex()
    print(visca_ack_comp )
    

    Here is what I get from the camera: 9041ff9051ff (ACK + COMP).

    Note, you could use the WDSL file from ONVIF instead of creating your own, as that one is correct.

    class MyDeviceIO(OnVif):
        wsdl_file = "https://www.onvif.org/ver10/deviceio.wsdl"
        namespace = "http://www.onvif.org/ver10/deviceIO/wsdl"
        sub_xaddr = "DeviceIO"
        port      = "DeviceIOBinding"