libonvif 4.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- libonvif/__init__.py +0 -0
- libonvif/datastructures/__init__.py +0 -0
- libonvif/datastructures/capabilities.py +302 -0
- libonvif/datastructures/datetime.py +126 -0
- libonvif/datastructures/device_io.py +109 -0
- libonvif/datastructures/event.py +270 -0
- libonvif/datastructures/imaging.py +397 -0
- libonvif/datastructures/network.py +270 -0
- libonvif/datastructures/profiles.py +622 -0
- libonvif/datastructures/ptz.py +179 -0
- libonvif/devices/__init__.py +0 -0
- libonvif/devices/camera.py +1180 -0
- libonvif/utils/__init__.py +0 -0
- libonvif/utils/adapters.py +23 -0
- libonvif/utils/server.py +79 -0
- libonvif/utils/soap.py +113 -0
- libonvif/utils/subscriber.py +156 -0
- libonvif/utils/xml.py +87 -0
- libonvif-4.0.1.dist-info/METADATA +20 -0
- libonvif-4.0.1.dist-info/RECORD +21 -0
- libonvif-4.0.1.dist-info/WHEEL +4 -0
libonvif/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from lxml import etree
|
|
6
|
+
from libonvif.utils.xml import text, int_text, bool_text, NS
|
|
7
|
+
from .ptz import PTZPreset, PresetTour, PresetTourOptions
|
|
8
|
+
from .event import EventServiceCapabilities, EventProperties
|
|
9
|
+
from .device_io import DeviceIOServiceCapabilities, RelayOutput
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class OnvifVersion:
|
|
13
|
+
major: int
|
|
14
|
+
minor: int
|
|
15
|
+
|
|
16
|
+
def __str__(self) -> str:
|
|
17
|
+
return f"{self.major}.{self.minor}"
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class AnalyticsCapabilities:
|
|
21
|
+
xaddr: Optional[str] = None
|
|
22
|
+
rule_support: Optional[bool] = None
|
|
23
|
+
analytics_module_support: Optional[bool] = None
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class NetworkCapabilities:
|
|
27
|
+
ip_filter: Optional[bool] = None
|
|
28
|
+
zero_configuration: Optional[bool] = None
|
|
29
|
+
ip_version6: Optional[bool] = None
|
|
30
|
+
dyn_dns: Optional[bool] = None
|
|
31
|
+
dot11_configuration: Optional[bool] = None
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class SystemCapabilities:
|
|
35
|
+
discovery_resolve: Optional[bool] = None
|
|
36
|
+
discovery_bye: Optional[bool] = None
|
|
37
|
+
remote_discovery: Optional[bool] = None
|
|
38
|
+
system_backup: Optional[bool] = None
|
|
39
|
+
system_logging: Optional[bool] = None
|
|
40
|
+
firmware_upgrade: Optional[bool] = None
|
|
41
|
+
supported_versions: list[OnvifVersion] = field(default_factory=list)
|
|
42
|
+
http_firmware_upgrade: Optional[bool] = None
|
|
43
|
+
http_system_backup: Optional[bool] = None
|
|
44
|
+
http_system_logging: Optional[bool] = None
|
|
45
|
+
http_support_information: Optional[bool] = None
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class IOCapabilities:
|
|
49
|
+
input_connectors: Optional[int] = None
|
|
50
|
+
relay_outputs: Optional[int] = None
|
|
51
|
+
auxiliary: Optional[bool] = None
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class SecurityCapabilities:
|
|
55
|
+
tls_1_0: Optional[bool] = None
|
|
56
|
+
tls_1_1: Optional[bool] = None
|
|
57
|
+
tls_1_2: Optional[bool] = None
|
|
58
|
+
onboard_key_generation: Optional[bool] = None
|
|
59
|
+
access_policy_config: Optional[bool] = None
|
|
60
|
+
x509_token: Optional[bool] = None
|
|
61
|
+
saml_token: Optional[bool] = None
|
|
62
|
+
kerberos_token: Optional[bool] = None
|
|
63
|
+
rel_token: Optional[bool] = None
|
|
64
|
+
dot1x: Optional[bool] = None
|
|
65
|
+
supported_eap_method: Optional[int] = None
|
|
66
|
+
remote_user_handling: Optional[bool] = None
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class DeviceCapabilities:
|
|
70
|
+
xaddr: Optional[str] = None
|
|
71
|
+
network: NetworkCapabilities = field(default_factory=NetworkCapabilities)
|
|
72
|
+
system: SystemCapabilities = field(default_factory=SystemCapabilities)
|
|
73
|
+
io: IOCapabilities = field(default_factory=IOCapabilities)
|
|
74
|
+
security: SecurityCapabilities = field(default_factory=SecurityCapabilities)
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class EventsCapabilities:
|
|
78
|
+
xaddr: Optional[str] = None
|
|
79
|
+
ws_subscription_policy_support: Optional[bool] = None
|
|
80
|
+
ws_pull_point_support: Optional[bool] = None
|
|
81
|
+
ws_pausable_subscription_manager_interface_support: Optional[bool] = None
|
|
82
|
+
service_capabilities: EventServiceCapabilities = field(default_factory=EventServiceCapabilities)
|
|
83
|
+
|
|
84
|
+
@dataclass
|
|
85
|
+
class ImagingCapabilities:
|
|
86
|
+
xaddr: Optional[str] = None
|
|
87
|
+
|
|
88
|
+
@dataclass
|
|
89
|
+
class StreamingCapabilities:
|
|
90
|
+
rtp_multicast: Optional[bool] = None
|
|
91
|
+
rtp_tcp: Optional[bool] = None
|
|
92
|
+
rtp_rtsp_tcp: Optional[bool] = None
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class MediaCapabilities:
|
|
96
|
+
xaddr: Optional[str] = None
|
|
97
|
+
streaming: StreamingCapabilities = field(default_factory=StreamingCapabilities)
|
|
98
|
+
maximum_number_of_profiles: Optional[int] = None
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class PTZCapabilities:
|
|
102
|
+
xaddr: Optional[str] = None
|
|
103
|
+
|
|
104
|
+
@dataclass
|
|
105
|
+
class DeviceIOCapabilities:
|
|
106
|
+
xaddr: Optional[str] = None
|
|
107
|
+
video_sources: Optional[int] = None
|
|
108
|
+
video_outputs: Optional[int] = None
|
|
109
|
+
audio_sources: Optional[int] = None
|
|
110
|
+
audio_outputs: Optional[int] = None
|
|
111
|
+
relay_outputs: Optional[int] = None
|
|
112
|
+
service_capabilities: DeviceIOServiceCapabilities = field(default_factory=DeviceIOServiceCapabilities)
|
|
113
|
+
|
|
114
|
+
@dataclass
|
|
115
|
+
class TelexCapabilities:
|
|
116
|
+
xaddr: Optional[str] = None
|
|
117
|
+
time_osd_support: Optional[bool] = None
|
|
118
|
+
title_osd_support: Optional[bool] = None
|
|
119
|
+
ptz_3d_zoom_support: Optional[bool] = None
|
|
120
|
+
ptz_aux_switch_support: Optional[bool] = None
|
|
121
|
+
motion_detector_support: Optional[bool] = None
|
|
122
|
+
tamper_detector_support: Optional[bool] = None
|
|
123
|
+
|
|
124
|
+
@dataclass
|
|
125
|
+
class Capabilities:
|
|
126
|
+
analytics: Optional[AnalyticsCapabilities] = None
|
|
127
|
+
device: Optional[DeviceCapabilities] = None
|
|
128
|
+
events: Optional[EventsCapabilities] = None
|
|
129
|
+
imaging: Optional[ImagingCapabilities] = None
|
|
130
|
+
media: Optional[MediaCapabilities] = None
|
|
131
|
+
ptz: Optional[PTZCapabilities] = None
|
|
132
|
+
device_io: Optional[DeviceIOCapabilities] = None
|
|
133
|
+
telex: Optional[TelexCapabilities] = None
|
|
134
|
+
|
|
135
|
+
def parse_capabilities_response(xml: str) -> Capabilities:
|
|
136
|
+
if not xml: return
|
|
137
|
+
root = etree.fromstring(xml.encode('utf-8'))
|
|
138
|
+
|
|
139
|
+
caps = root.find(".//tds:GetCapabilitiesResponse/tds:Capabilities", NS)
|
|
140
|
+
if caps is None:
|
|
141
|
+
raise ValueError("Could not find tds:GetCapabilitiesResponse/tds:Capabilities")
|
|
142
|
+
|
|
143
|
+
result = Capabilities()
|
|
144
|
+
|
|
145
|
+
analytics = caps.find("tt:Analytics", NS)
|
|
146
|
+
if analytics is not None:
|
|
147
|
+
result.analytics = AnalyticsCapabilities(
|
|
148
|
+
xaddr=text(analytics, "tt:XAddr"),
|
|
149
|
+
rule_support=bool_text(analytics, "tt:RuleSupport"),
|
|
150
|
+
analytics_module_support=bool_text(analytics, "tt:AnalyticsModuleSupport"),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
device = caps.find("tt:Device", NS)
|
|
154
|
+
if device is not None:
|
|
155
|
+
d = DeviceCapabilities(
|
|
156
|
+
xaddr=text(device, "tt:XAddr"),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
net = device.find("tt:Network", NS)
|
|
160
|
+
if net is not None:
|
|
161
|
+
d.network = NetworkCapabilities(
|
|
162
|
+
ip_filter=bool_text(net, "tt:IPFilter"),
|
|
163
|
+
zero_configuration=bool_text(net, "tt:ZeroConfiguration"),
|
|
164
|
+
ip_version6=bool_text(net, "tt:IPVersion6"),
|
|
165
|
+
dyn_dns=bool_text(net, "tt:DynDNS"),
|
|
166
|
+
dot11_configuration=bool_text(
|
|
167
|
+
net, "tt:Extension/tt:Dot11Configuration"
|
|
168
|
+
),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
system = device.find("tt:System", NS)
|
|
172
|
+
if system is not None:
|
|
173
|
+
versions: list[OnvifVersion] = []
|
|
174
|
+
for ver in system.findall("tt:SupportedVersions", NS):
|
|
175
|
+
major = int_text(ver, "tt:Major")
|
|
176
|
+
minor = int_text(ver, "tt:Minor")
|
|
177
|
+
if major is not None and minor is not None:
|
|
178
|
+
versions.append(OnvifVersion(major, minor))
|
|
179
|
+
|
|
180
|
+
d.system = SystemCapabilities(
|
|
181
|
+
discovery_resolve=bool_text(system, "tt:DiscoveryResolve"),
|
|
182
|
+
discovery_bye=bool_text(system, "tt:DiscoveryBye"),
|
|
183
|
+
remote_discovery=bool_text(system, "tt:RemoteDiscovery"),
|
|
184
|
+
system_backup=bool_text(system, "tt:SystemBackup"),
|
|
185
|
+
system_logging=bool_text(system, "tt:SystemLogging"),
|
|
186
|
+
firmware_upgrade=bool_text(system, "tt:FirmwareUpgrade"),
|
|
187
|
+
supported_versions=versions,
|
|
188
|
+
http_firmware_upgrade=bool_text(
|
|
189
|
+
system, "tt:Extension/tt:HttpFirmwareUpgrade"
|
|
190
|
+
),
|
|
191
|
+
http_system_backup=bool_text(
|
|
192
|
+
system, "tt:Extension/tt:HttpSystemBackup"
|
|
193
|
+
),
|
|
194
|
+
http_system_logging=bool_text(
|
|
195
|
+
system, "tt:Extension/tt:HttpSystemLogging"
|
|
196
|
+
),
|
|
197
|
+
http_support_information=bool_text(
|
|
198
|
+
system, "tt:Extension/tt:HttpSupportInformation"
|
|
199
|
+
),
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
io = device.find("tt:IO", NS)
|
|
203
|
+
if io is not None:
|
|
204
|
+
d.io = IOCapabilities(
|
|
205
|
+
input_connectors=int_text(io, "tt:InputConnectors"),
|
|
206
|
+
relay_outputs=int_text(io, "tt:RelayOutputs"),
|
|
207
|
+
auxiliary=bool_text(io, "tt:Extension/tt:Auxiliary"),
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
sec = device.find("tt:Security", NS)
|
|
211
|
+
if sec is not None:
|
|
212
|
+
d.security = SecurityCapabilities(
|
|
213
|
+
tls_1_1=bool_text(sec, "tt:TLS1.1"),
|
|
214
|
+
tls_1_2=bool_text(sec, "tt:TLS1.2"),
|
|
215
|
+
onboard_key_generation=bool_text(sec, "tt:OnboardKeyGeneration"),
|
|
216
|
+
access_policy_config=bool_text(sec, "tt:AccessPolicyConfig"),
|
|
217
|
+
x509_token=bool_text(sec, "tt:X.509Token"),
|
|
218
|
+
saml_token=bool_text(sec, "tt:SAMLToken"),
|
|
219
|
+
kerberos_token=bool_text(sec, "tt:KerberosToken"),
|
|
220
|
+
rel_token=bool_text(sec, "tt:RELToken"),
|
|
221
|
+
tls_1_0=bool_text(sec, "tt:Extension/tt:TLS1.0"),
|
|
222
|
+
dot1x=bool_text(sec, "tt:Extension/tt:Extension/tt:Dot1X"),
|
|
223
|
+
supported_eap_method=int_text(
|
|
224
|
+
sec, "tt:Extension/tt:Extension/tt:SupportedEAPMethod"
|
|
225
|
+
),
|
|
226
|
+
remote_user_handling=bool_text(
|
|
227
|
+
sec, "tt:Extension/tt:Extension/tt:RemoteUserHandling"
|
|
228
|
+
),
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
result.device = d
|
|
232
|
+
|
|
233
|
+
events = caps.find("tt:Events", NS)
|
|
234
|
+
if events is not None:
|
|
235
|
+
result.events = EventsCapabilities(
|
|
236
|
+
xaddr=text(events, "tt:XAddr"),
|
|
237
|
+
ws_subscription_policy_support=bool_text(
|
|
238
|
+
events, "tt:WSSubscriptionPolicySupport"
|
|
239
|
+
),
|
|
240
|
+
ws_pull_point_support=bool_text(events, "tt:WSPullPointSupport"),
|
|
241
|
+
ws_pausable_subscription_manager_interface_support=bool_text(
|
|
242
|
+
events, "tt:WSPausableSubscriptionManagerInterfaceSupport"
|
|
243
|
+
),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
imaging = caps.find("tt:Imaging", NS)
|
|
247
|
+
if imaging is not None:
|
|
248
|
+
result.imaging = ImagingCapabilities(
|
|
249
|
+
xaddr=text(imaging, "tt:XAddr"),
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
media = caps.find("tt:Media", NS)
|
|
253
|
+
if media is not None:
|
|
254
|
+
result.media = MediaCapabilities(
|
|
255
|
+
xaddr=text(media, "tt:XAddr"),
|
|
256
|
+
streaming=StreamingCapabilities(
|
|
257
|
+
rtp_multicast=bool_text(
|
|
258
|
+
media, "tt:StreamingCapabilities/tt:RTPMulticast"
|
|
259
|
+
),
|
|
260
|
+
rtp_tcp=bool_text(
|
|
261
|
+
media, "tt:StreamingCapabilities/tt:RTP_TCP"
|
|
262
|
+
),
|
|
263
|
+
rtp_rtsp_tcp=bool_text(
|
|
264
|
+
media, "tt:StreamingCapabilities/tt:RTP_RTSP_TCP"
|
|
265
|
+
),
|
|
266
|
+
),
|
|
267
|
+
maximum_number_of_profiles=int_text(
|
|
268
|
+
media,
|
|
269
|
+
"tt:Extension/tt:ProfileCapabilities/tt:MaximumNumberOfProfiles",
|
|
270
|
+
),
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
ptz = caps.find("tt:PTZ", NS)
|
|
274
|
+
if ptz is not None:
|
|
275
|
+
result.ptz = PTZCapabilities(
|
|
276
|
+
xaddr=text(ptz, "tt:XAddr"),
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
device_io = caps.find("tt:Extension/tt:DeviceIO", NS)
|
|
280
|
+
if device_io is not None:
|
|
281
|
+
result.device_io = DeviceIOCapabilities(
|
|
282
|
+
xaddr=text(device_io, "tt:XAddr"),
|
|
283
|
+
video_sources=int_text(device_io, "tt:VideoSources"),
|
|
284
|
+
video_outputs=int_text(device_io, "tt:VideoOutputs"),
|
|
285
|
+
audio_sources=int_text(device_io, "tt:AudioSources"),
|
|
286
|
+
audio_outputs=int_text(device_io, "tt:AudioOutputs"),
|
|
287
|
+
relay_outputs=int_text(device_io, "tt:RelayOutputs"),
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
telex = caps.find("tt:Extension/tt:Extensions/tt:TelexCapabilities", NS)
|
|
291
|
+
if telex is not None:
|
|
292
|
+
result.telex = TelexCapabilities(
|
|
293
|
+
xaddr=text(telex, "tt:XAddr"),
|
|
294
|
+
time_osd_support=bool_text(telex, "tt:TimeOSDSupport"),
|
|
295
|
+
title_osd_support=bool_text(telex, "tt:TitleOSDSupport"),
|
|
296
|
+
ptz_3d_zoom_support=bool_text(telex, "tt:PTZ3DZoomSupport"),
|
|
297
|
+
ptz_aux_switch_support=bool_text(telex, "tt:PTZAuxSwitchSupport"),
|
|
298
|
+
motion_detector_support=bool_text(telex, "tt:MotionDetectorSupport"),
|
|
299
|
+
tamper_detector_support=bool_text(telex, "tt:TamperDetectorSupport"),
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
return result
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from lxml import etree
|
|
6
|
+
from libonvif.utils.xml import text, int_text, bool_text, NS
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class NTPInformation:
|
|
10
|
+
from_dhcp: Optional[bool] = None
|
|
11
|
+
ntp_from_dhcp: list[str] = field(default_factory=list)
|
|
12
|
+
ntp_manual: list[str] = field(default_factory=list)
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class Time:
|
|
16
|
+
hour: Optional[int] = None
|
|
17
|
+
minute: Optional[int] = None
|
|
18
|
+
second: Optional[int] = None
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class Date:
|
|
22
|
+
year: Optional[int] = None
|
|
23
|
+
month: Optional[int] = None
|
|
24
|
+
day: Optional[int] = None
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class DateTime:
|
|
28
|
+
time: Time = field(default_factory=Time)
|
|
29
|
+
date: Date = field(default_factory=Date)
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class TimeZone:
|
|
33
|
+
tz: Optional[str] = None
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class SystemDateAndTime:
|
|
37
|
+
date_time_type: Optional[str] = None
|
|
38
|
+
daylight_savings: Optional[bool] = None
|
|
39
|
+
time_zone: Optional[TimeZone] = None
|
|
40
|
+
utc_date_time: Optional[DateTime] = None
|
|
41
|
+
local_date_time: Optional[DateTime] = None
|
|
42
|
+
|
|
43
|
+
def parse_time(elem: Optional[etree._Element]) -> Time:
|
|
44
|
+
if elem is None:
|
|
45
|
+
return Time()
|
|
46
|
+
|
|
47
|
+
return Time(
|
|
48
|
+
hour=int_text(elem, "tt:Hour"),
|
|
49
|
+
minute=int_text(elem, "tt:Minute"),
|
|
50
|
+
second=int_text(elem, "tt:Second"),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def parse_date(elem: Optional[etree._Element]) -> Date:
|
|
54
|
+
if elem is None:
|
|
55
|
+
return Date()
|
|
56
|
+
|
|
57
|
+
return Date(
|
|
58
|
+
year=int_text(elem, "tt:Year"),
|
|
59
|
+
month=int_text(elem, "tt:Month"),
|
|
60
|
+
day=int_text(elem, "tt:Day"),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def parse_datetime(elem: Optional[etree._Element]) -> Optional[DateTime]:
|
|
64
|
+
if elem is None:
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
return DateTime(
|
|
68
|
+
time=parse_time(elem.find("tt:Time", NS)),
|
|
69
|
+
date=parse_date(elem.find("tt:Date", NS)),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def parse_timezone(elem: Optional[etree._Element]) -> Optional[TimeZone]:
|
|
73
|
+
if elem is None:
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
return TimeZone(
|
|
77
|
+
tz=text(elem, "tt:TZ"),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def parse_system_date_and_time_response(xml: str) -> SystemDateAndTime:
|
|
81
|
+
if not xml: return
|
|
82
|
+
root = etree.fromstring(xml.encode('utf-8'))
|
|
83
|
+
|
|
84
|
+
elem = root.find(
|
|
85
|
+
".//tds:GetSystemDateAndTimeResponse/tds:SystemDateAndTime",
|
|
86
|
+
NS,
|
|
87
|
+
)
|
|
88
|
+
if elem is None:
|
|
89
|
+
raise ValueError(
|
|
90
|
+
"Could not find tds:GetSystemDateAndTimeResponse/tds:SystemDateAndTime"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return SystemDateAndTime(
|
|
94
|
+
date_time_type=text(elem, "tt:DateTimeType"),
|
|
95
|
+
daylight_savings=bool_text(elem, "tt:DaylightSavings"),
|
|
96
|
+
time_zone=parse_timezone(elem.find("tt:TimeZone", NS)),
|
|
97
|
+
utc_date_time=parse_datetime(elem.find("tt:UTCDateTime", NS)),
|
|
98
|
+
local_date_time=parse_datetime(elem.find("tt:LocalDateTime", NS)),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def parse_ip_address(elem):
|
|
102
|
+
return (
|
|
103
|
+
text(elem, "tt:IPv4Address")
|
|
104
|
+
or text(elem, "tt:IPv6Address")
|
|
105
|
+
or text(elem, "tt:DNSname")
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def parse_ntp_response(xml: str) -> NTPInformation:
|
|
109
|
+
if not xml: return
|
|
110
|
+
root = etree.fromstring(xml.encode('utf-8'))
|
|
111
|
+
|
|
112
|
+
ntp_elem = root.find(".//tds:NTPInformation", NS)
|
|
113
|
+
if ntp_elem is None:
|
|
114
|
+
raise ValueError("Missing NTPInformation")
|
|
115
|
+
|
|
116
|
+
return NTPInformation(
|
|
117
|
+
from_dhcp=bool_text(ntp_elem, "tt:FromDHCP"),
|
|
118
|
+
ntp_from_dhcp=[
|
|
119
|
+
parse_ip_address(e)
|
|
120
|
+
for e in ntp_elem.findall("tt:NTPFromDHCP", NS)
|
|
121
|
+
],
|
|
122
|
+
ntp_manual=[
|
|
123
|
+
parse_ip_address(e)
|
|
124
|
+
for e in ntp_elem.findall("tt:NTPManual", NS)
|
|
125
|
+
],
|
|
126
|
+
)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from lxml import etree
|
|
6
|
+
from libonvif.utils.xml import int_attr, text, text_list, bool_text, NS
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class RelayOutputOptions:
|
|
10
|
+
token: Optional[str] = None
|
|
11
|
+
modes: list[str] = field(default_factory=list)
|
|
12
|
+
delay_times: list[str] = field(default_factory=list)
|
|
13
|
+
discrete: Optional[bool] = None
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class RelayOutputProperties:
|
|
17
|
+
mode: Optional[str] = None
|
|
18
|
+
delay_time: Optional[str] = None
|
|
19
|
+
idle_state: Optional[str] = None
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class RelayOutput:
|
|
23
|
+
token: Optional[str] = None
|
|
24
|
+
properties: RelayOutputProperties = field(default_factory=RelayOutputProperties)
|
|
25
|
+
options: RelayOutputOptions = field(default_factory=RelayOutputOptions)
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class DeviceIOServiceCapabilities:
|
|
29
|
+
video_sources: Optional[int] = None
|
|
30
|
+
video_outputs: Optional[int] = None
|
|
31
|
+
audio_sources: Optional[int] = None
|
|
32
|
+
audio_outputs: Optional[int] = None
|
|
33
|
+
relay_outputs: Optional[int] = None
|
|
34
|
+
serial_ports: Optional[int] = None
|
|
35
|
+
digital_inputs: Optional[int] = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def parse_deviceio_service_capabilities_response(xml: str) -> Optional[DeviceIOServiceCapabilities]:
|
|
39
|
+
if not xml: return
|
|
40
|
+
root = etree.fromstring(xml.encode("utf-8"))
|
|
41
|
+
|
|
42
|
+
result = root.xpath(
|
|
43
|
+
".//tmd:GetServiceCapabilitiesResponse/tmd:Capabilities",
|
|
44
|
+
namespaces=NS,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if not result: return
|
|
48
|
+
|
|
49
|
+
cap = result[0]
|
|
50
|
+
|
|
51
|
+
return DeviceIOServiceCapabilities(
|
|
52
|
+
video_sources=int_attr(cap, "VideoSources"),
|
|
53
|
+
video_outputs=int_attr(cap, "VideoOutputs"),
|
|
54
|
+
audio_sources=int_attr(cap, "AudioSources"),
|
|
55
|
+
audio_outputs=int_attr(cap, "AudioOutputs"),
|
|
56
|
+
relay_outputs=int_attr(cap, "RelayOutputs"),
|
|
57
|
+
serial_ports=int_attr(cap, "SerialPorts"),
|
|
58
|
+
digital_inputs=int_attr(cap, "DigitalInputs"),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def parse_get_relay_outputs_response(xml: str) -> list[RelayOutput]:
|
|
62
|
+
if not xml:
|
|
63
|
+
return []
|
|
64
|
+
|
|
65
|
+
root = etree.fromstring(xml.encode("utf-8"))
|
|
66
|
+
|
|
67
|
+
outputs: list[RelayOutput] = []
|
|
68
|
+
|
|
69
|
+
for output_el in root.xpath(
|
|
70
|
+
".//*[local-name()='GetRelayOutputsResponse']/*[local-name()='RelayOutputs']"
|
|
71
|
+
):
|
|
72
|
+
outputs.append(
|
|
73
|
+
RelayOutput(
|
|
74
|
+
token=output_el.get("token"),
|
|
75
|
+
properties=RelayOutputProperties(
|
|
76
|
+
mode=text(output_el, "./tt:Properties/tt:Mode"),
|
|
77
|
+
delay_time=text(output_el, "./tt:Properties/tt:DelayTime"),
|
|
78
|
+
idle_state=text(output_el, "./tt:Properties/tt:IdleState"),
|
|
79
|
+
),
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return outputs
|
|
84
|
+
|
|
85
|
+
def parse_get_relay_output_options_response(xml: str) -> RelayOutputOptions:
|
|
86
|
+
if not xml:
|
|
87
|
+
return RelayOutputOptions()
|
|
88
|
+
|
|
89
|
+
root = etree.fromstring(xml.encode("utf-8"))
|
|
90
|
+
|
|
91
|
+
result = root.xpath(
|
|
92
|
+
".//tmd:GetRelayOutputOptionsResponse/tmd:RelayOutputOptions",
|
|
93
|
+
namespaces=NS,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if not result:
|
|
97
|
+
return RelayOutputOptions()
|
|
98
|
+
|
|
99
|
+
options_el = result[0]
|
|
100
|
+
|
|
101
|
+
delay_time_text = text(options_el, "./tmd:DelayTimes")
|
|
102
|
+
|
|
103
|
+
return RelayOutputOptions(
|
|
104
|
+
token=options_el.get("token"),
|
|
105
|
+
modes=text_list(options_el, "./tmd:Mode"),
|
|
106
|
+
delay_times=delay_time_text.split() if delay_time_text else [],
|
|
107
|
+
discrete=bool_text(options_el, "./tmd:Discrete"),
|
|
108
|
+
)
|
|
109
|
+
|