Skip to content

Commit 4a11e13

Browse files
committed
Add shortened common onvif namespace prefixes.
This fixes some Tapo cameras that respond with a 500 error when receiving the ns0/ns1 namespace prefix with embedded xmlns definitions inside each element. A similar change was done upstream here: FalkTannhaeuser/python-onvif-zeep#46 for mvantellingen/python-zeep#870. I also updated the events example that was broken.
1 parent 040cdef commit 4a11e13

File tree

3 files changed

+101
-41
lines changed

3 files changed

+101
-41
lines changed

examples/events.py

Lines changed: 91 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,107 @@
11
"""Example to fetch pullpoint events."""
2+
3+
from aiohttp import web
4+
import argparse
25
import asyncio
36
import datetime as dt
47
import logging
8+
import onvif
9+
import os.path
10+
import pprint
11+
import sys
12+
13+
SUBSCRIPTION_TIME = dt.timedelta(minutes=1)
14+
WAIT_TIME = dt.timedelta(seconds=30)
15+
516

6-
from pytz import UTC
7-
from zeep import xsd
17+
def subscription_lost():
18+
print("subscription lost")
819

9-
from onvif import ONVIFCamera
1020

11-
logging.getLogger("zeep").setLevel(logging.DEBUG)
21+
async def post_handler(request):
22+
print(request)
23+
print(request.url)
24+
for k, v in request.headers.items():
25+
print(f"{k}: {v}")
26+
body = await request.content.read()
27+
print(body)
28+
return web.Response()
1229

1330

14-
async def run():
15-
mycam = ONVIFCamera(
16-
"192.168.3.10",
17-
80,
18-
"hass",
19-
"peek4boo",
20-
wsdl_dir="/home/jason/python-onvif-zeep-async/onvif/wsdl",
31+
async def run(args):
32+
mycam = onvif.ONVIFCamera(
33+
args.host,
34+
args.port,
35+
args.username,
36+
args.password,
37+
wsdl_dir=f"{os.path.dirname(onvif.__file__)}/wsdl/",
2138
)
2239
await mycam.update_xaddrs()
2340

24-
if not await mycam.create_pullpoint_subscription():
25-
print("PullPoint not supported")
26-
return
27-
28-
event_service = mycam.create_events_service()
29-
properties = await event_service.GetEventProperties()
30-
print(properties)
31-
capabilities = await event_service.GetServiceCapabilities()
32-
print(capabilities)
33-
34-
pullpoint = mycam.create_pullpoint_service()
35-
await pullpoint.SetSynchronizationPoint()
36-
req = pullpoint.create_type("PullMessages")
37-
req.MessageLimit = 100
38-
req.Timeout = dt.timedelta(seconds=30)
39-
messages = await pullpoint.PullMessages(req)
40-
print(messages)
41-
42-
subscription = mycam.create_subscription_service("PullPointSubscription")
43-
termination_time = (
44-
(dt.datetime.utcnow() + dt.timedelta(days=1))
45-
.isoformat(timespec="seconds")
46-
.replace("+00:00", "Z")
47-
)
48-
await subscription.Renew(termination_time)
49-
await subscription.Unsubscribe()
41+
capabilities = await mycam.get_capabilities()
42+
pprint.pprint(capabilities)
43+
44+
if args.notification:
45+
app = web.Application()
46+
app.add_routes([web.post("/", post_handler)])
47+
runner = web.AppRunner(app)
48+
await runner.setup()
49+
site = web.TCPSite(runner, args.notification_address, args.notification_port)
50+
await site.start()
51+
52+
receive_url = f"http://{args.notification_address}:{args.notification_port}/"
53+
manager = await mycam.create_notification_manager(
54+
receive_url,
55+
SUBSCRIPTION_TIME,
56+
subscription_lost,
57+
)
58+
await manager.set_synchronization_point()
59+
60+
print(f"waiting for messages at {receive_url}...")
61+
await asyncio.sleep(WAIT_TIME.total_seconds())
62+
63+
await manager.shutdown()
64+
await runner.cleanup()
65+
else:
66+
manager = await mycam.create_pullpoint_manager(
67+
SUBSCRIPTION_TIME, subscription_lost
68+
)
69+
await manager.set_synchronization_point()
70+
71+
pullpoint = manager.get_service()
72+
print("waiting for messages...")
73+
messages = await pullpoint.PullMessages(
74+
{
75+
"MessageLimit": 100,
76+
"Timeout": WAIT_TIME,
77+
}
78+
)
79+
print(messages)
80+
81+
await manager.shutdown()
82+
5083
await mycam.close()
5184

5285

53-
if __name__ == "__main__":
86+
def main():
87+
logging.getLogger("zeep").setLevel(logging.DEBUG)
88+
89+
parser = argparse.ArgumentParser(prog="EventTester")
90+
parser.add_argument("--host", default="192.168.3.10")
91+
parser.add_argument("--port", type=int, default=80)
92+
parser.add_argument("--username", default="hass")
93+
parser.add_argument("--password", default="peek4boo")
94+
parser.add_argument("--notification", action=argparse.BooleanOptionalAction)
95+
parser.add_argument("--notification_address")
96+
parser.add_argument("--notification_port", type=int, default=8976)
97+
98+
args = parser.parse_args(sys.argv[1:])
99+
if args.notification and args.notification_address is None:
100+
parser.error("--notification requires --notification_address")
101+
54102
loop = asyncio.get_event_loop()
55-
loop.run_until_complete(run())
103+
loop.run_until_complete(run(args))
104+
105+
106+
if __name__ == "__main__":
107+
main()

onvif/client.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""ONVIF Client."""
2+
23
from __future__ import annotations
34

45
import asyncio
@@ -125,6 +126,11 @@ def _load_document() -> DocumentWithDeferredLoad:
125126
class ZeepAsyncClient(BaseZeepAsyncClient):
126127
"""Overwrite create_service method to be async."""
127128

129+
def __init__(self, *args, **kwargs):
130+
super().__init__(*args, **kwargs)
131+
self.set_ns_prefix('wsnt', 'http://docs.oasis-open.org/wsn/b-2')
132+
self.set_ns_prefix('wsa', 'http://www.w3.org/2005/08/addressing')
133+
128134
def create_service(self, binding_name, address):
129135
"""Create a new ServiceProxy for the given binding name and address.
130136
:param binding_name: The QName of the binding

requirements.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# Package
2-
httpx==0.19.0
3-
zeep[async]==4.1.0
2+
ciso8601>=2.1.3
3+
httpx>=0.19.0,<1.0.0
4+
zeep[async]>=4.2.1,<5.0.0
45

56
# Dev
7+
aiohttp
68
pytest
79
pytest-cov
810
pylint

0 commit comments

Comments
 (0)