FalkTannhaeuser / python-onvif-zeep

ONVIF Client Implementation in Python 2+3 (using https://github.com/mvantellingen/python-zeep instead of suds as SOAP client)
MIT License
420 stars 137 forks source link

onvif.exceptions.ONVIFError: Unknown error: ('Connection aborted.', BadStatusLine('POST... *HTTP/1.1 500 Internal Server Error\r\n')) #118

Open kim1037 opened 8 months ago

kim1037 commented 8 months ago

Hi, I was trying to connect TP-Link tapo C220 ip camera with python, but I 'm stuck on a problem. Here is my code :

import time
from onvif import ONVIFCamera
import zeep
import requests
from requests.auth import HTTPDigestAuth
wsdl_path = r'C:/Users/user/Desktop/python-onvif/python-onvif-zeep/wsdl'
camera_ip = '192.168.X.XXX'
username = 'user'
password = '123456'

def zeep_pythonvalue(self, xmlvalue):
    return xmlvalue

def absolute_move():
    pan = 0
    pan_speed = 1
    tilt = 0
    tilt_speed = 1

    mycam = ONVIFCamera(camera_ip, 80, username, password, wsdl_path)

    # Create media service object
    media = mycam.create_media_service()
    # Create ptz service object
    ptz = mycam.create_ptz_service()

    # Get target profile
    zeep.xsd.simple.AnySimpleType.pythonvalue = zeep_pythonvalue
    media_profile = media.GetProfiles()[0]

    request = ptz.create_type('AbsoluteMove')
    request.ProfileToken = media_profile.token
    ptz.Stop({'ProfileToken': media_profile.token})

    if request.Position is None:
        request.Position = ptz.GetStatus({'ProfileToken': media_profile.token}).Position
    if request.Speed is None:
        request.Speed = ptz.GetStatus({'ProfileToken': media_profile.token}).Position

    request.Position.PanTilt.x = pan
    request.Speed.PanTilt.x = pan_speed

    request.Position.PanTilt.y = tilt
    request.Speed.PanTilt.y = tilt_speed

    ptz.AbsoluteMove(request)
    print('finish')

def snap():
    # Get target profile
    zeep.xsd.simple.AnySimpleType.pythonvalue = zeep_pythonvalue
    mycam = ONVIFCamera(camera_ip, 80, username, password, wsdl_path)
    media = mycam.create_media_service()  
    media_profile = media.GetProfiles()[0] 
    res = media.GetSnapshotUri({'ProfileToken': media_profile.token})
    response = requests.get(res.Uri, auth=HTTPDigestAuth("admin", "pass"))
    res = "{_time}.png".format(_time=time.strftime('%Y_%m_%d_%H_%M_%S', time.localtime(time.time())))
    with open(res, 'wb') as f:
        f.write(response.content)

def gotoPreset():
    zeep.xsd.simple.AnySimpleType.pythonvalue = zeep_pythonvalue
    mycam = ONVIFCamera(camera_ip, 80, username, password, wsdl_path)
    media = mycam.create_media_service()
    ptz = mycam.create_ptz_service()
    params = ptz.create_type('GotoPreset')
    media_profile = media.GetProfiles()[0] 
    print(media_profile.token)
    params.ProfileToken = media_profile.token
    params.PresetToken = 1
    re = ptz.GotoPreset(params)
    print(re)

def getStatus():
    zeep.xsd.simple.AnySimpleType.pythonvalue = zeep_pythonvalue
    mycam = ONVIFCamera(camera_ip, 80, username, password, wsdl_path)
    media = mycam.create_media_service()
    ptz = mycam.create_ptz_service()
    params = ptz.create_type('GetStatus')
    media_profile = media.GetProfiles()[0]
    print(media_profile.token)
    params.ProfileToken = media_profile.token
    res = ptz.GetStatus(params)
    print(res)

if __name__ == '__main__':
    absolute_move()
    snap()
    gotoPreset()
    getStatus()

When I start this code, it is the error message:

Traceback (most recent call last): File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\urllib3\connectionpool.py", line 790, in urlopen response = self._make_request( ^^^^^^^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\urllib3\connectionpool.py", line 536, in _make_request response = conn.getresponse() ^^^^^^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\urllib3\connection.py", line 461, in getresponse httplib_response = super().getresponse() ^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\http\client.py", line 1378, in getresponse response.begin() File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\http\client.py", line 318, in begin version, status, reason = self._read_status() ^^^^^^^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\http\client.py", line 300, in _read_status raise BadStatusLine(line) http.client.BadStatusLine: POST /onvif/serviceHTTP/1.1Host 192.168.1.xxx:xxxUser-Agent Zeep/4.2.1 (www.python-zeep.org)Accept-Encoding gzip, deflateAccept HTTP/1.1 500 Internal Server Error During handling of the above exception, another exception occurred: Traceback (most recent call last): File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\requests\adapters.py", line 486, in send resp = conn.urlopen( ^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\urllib3\connectionpool.py", line 844, in urlopen retries = retries.increment( ^^^^^^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\urllib3\util\retry.py", line 470, in increment raise reraise(type(error), error, _stacktrace) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\urllib3\util\util.py", line 38, in reraise raise value.with_traceback(tb) File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\urllib3\connectionpool.py", line 790, in urlopen response = self._make_request( ^^^^^^^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\urllib3\connectionpool.py", line 536, in _make_request response = conn.getresponse() ^^^^^^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\urllib3\connection.py", line 461, in getresponse httplib_response = super().getresponse() ^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\http\client.py", line 1378, in getresponse response.begin() File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\http\client.py", line 318, in begin version, status, reason = self._read_status() ^^^^^^^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\http\client.py", line 300, in _read_status raise BadStatusLine(line) urllib3.exceptions.ProtocolError: ('Connection aborted.', BadStatusLine('POST /onvif/service\x00HTTP/1.1\x00\x00Host\x00 192.168.1.xxx:xxx\x00\x00User-Agent\x00 Zeep/4.2.1 (www.python-zeep.org)\x00\x00Accept-Encoding\x00 gzip, deflate\x00\x00Accept\x00 HTTP/1.1 500 Internal Server Error\r\n')) During handling of the above exception, another exception occurred: Traceback (most recent call last): File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\onvif_zeep-0.2.12-py3.11.egg\onvif\client.py", line 25, in wrapped return func(*args, kwargs) ^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\onvif_zeep-0.2.12-py3.11.egg\onvif\client.py", line 150, in wrapped return call(params, callback) ^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\onvif_zeep-0.2.12-py3.11.egg\onvif\client.py", line 138, in call ret = func(params) ^^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\zeep-4.2.1-py3.11.egg\zeep\proxy.py", line 46, in call return self._proxy._binding.send( ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\zeep-4.2.1-py3.11.egg\zeep\wsdl\bindings\soap.py", line 127, in send response = client.transport.post_xml(options["address"], envelope, http_headers) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\zeep-4.2.1-py3.11.egg\zeep\transports.py", line 108, in post_xml return self.post(address, message, headers) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\zeep-4.2.1-py3.11.egg\zeep\transports.py", line 74, in post response = self.session.post( ^^^^^^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\requests\sessions.py", line 637, in post return self.request("POST", url, data=data, json=json, kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\requests\sessions.py", line 589, in request resp = self.send(prep, send_kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\requests\sessions.py", line 703, in send r = adapter.send(request, *kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\requests\adapters.py", line 501, in send raise ConnectionError(err, request=request) requests.exceptions.ConnectionError: ('Connection aborted.', BadStatusLine('POST /onvif/service\x00HTTP/1.1\x00\x00Host\x00 192.168.1.xxx:xxx\x00\x00User-Agent\x00 Zeep/4.2.1 (www.python-zeep.org)\x00\x00Accept-Encoding\x00 gzip, deflate\x00\x00Accept\x00 HTTP/1.1 500 Internal Server Error\r\n')) During handling of the above exception, another exception occurred: Traceback (most recent call last): File "c:\Users\user\Desktop\vision\vision\onvif_control.py", line 102, in absolute_move() File "c:\Users\user\Desktop\vision\vision\onvif_control.py", line 44, in absolute_move request.Position = ptz.GetStatus({'ProfileToken': media_profile.token}).Position ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\onvif_zeep-0.2.12-py3.11.egg\onvif\client.py", line 27, in wrapped raise ONVIFError(err) onvif.exceptions.ONVIFError: Unknown error: ('Connection aborted.', BadStatusLine('POST /onvif/service\x00HTTP/1.1\x00\x00Host\x00 192.168.1.xxx:xxx\x00\x00User-Agent\x00 Zeep/4.2.1 (www.python-zeep.org)\x00\x00Accept-Encoding\x00 gzip, deflate\x00\x00Accept\x00 *HTTP/1.1 500 Internal Server Error\r\n'))`

What's wrong with my code? Thanks

schauveau commented 5 months ago

I have a similar problem with a Tapo 500. Basically, nothing works so I captured the traffic and I figured out that the Tapo does not like the GetCapabilities request.

request.xml:

    <soap-env:Envelope
        xmlns:soap-env="http://www.w3.org/2003/05/soap-envelope">
        <soap-env:Header>
            <wsse:Security
                xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
                <wsse:UsernameToken>
                    <wsse:Username>
                        xxxxxxxxx
                        </wsse:Username>
                    <wsse:Password
                        Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">
                        xxxxxxxxxxx
                        </wsse:Password>
                    </wsse:UsernameToken>
                </wsse:Security>
            </soap-env:Header>
        <soap-env:Body>
            <ns0:GetCapabilities xmlns:ns0="http://www.onvif.org/ver10/device/wsdl"/>
        </soap-env:Body>
   </soap-env:Envelope>

When I use curl to do the POST request, it returns an error. I believe that there was not even a reply. The connection is simply closed.

# curl -k -X POST --header 'Content-Type: text/xml; charset=utf-8' -d @request.xml "http://192.168.1.26:2020/onvif/device_service"
curl: (1) Received HTTP/0.9 when not allowed
-:1: parser error : Document is empty

I eventually found what seems to be the problem: The Tapo does not like the self-closing tag <ns0:GetCapabilities ... />.

For example, the following GetCapabilities request works fine

<?xml version=1.0' encoding='utf-8'>
<soap-env:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
    <soap-env:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <GetCapabilities xmlns="http://www.onvif.org/ver10/device/wsdl"></GetCapabilities>
    </soap-env:Body>
</soap-env:Envelope>

but that one does not

<?xml version=1.0' encoding='utf-8'>
<soap-env:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
    <soap-env:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <GetCapabilities xmlns="http://www.onvif.org/ver10/device/wsdl"/>
    </soap-env:Body>
</soap-env:Envelope>

This is quite a problem because there are probably a lot of onvif clients that do not work on the tapo for the same reason.

I will try to fill a ticket at Tp-Link about.

Is there a way to prevent onvif-zeep to produce closing tags?

schauveau commented 5 months ago

I figured out how to prevent self-closing tags. The XML is produced in package zeep by the function etree_to_string in wsdl/utils.py
The trick is to insert an empty text in all empty elements so

 def etree_to_string(node):
    for elem in node.iter():
        if elem.text == None:
            elem.text = ''
    return etree.tostring(
        node, pretty_print=False, xml_declaration=True, encoding="utf-8"
    )

So I can now successfully do the GetCapabilities and a few other requests.

I still get a disconnection later in my test script but this is probably because I am trying to use an feature that is not supported by my Tapo 500 (i.e. PTZ ContinuousMove)

schauveau commented 5 months ago

I noticed some self-closing tag in a network trace from another onvif tool that appears to be working fine so my initial diagnostic is probably incorrect. Something odd is happening here!!!