onvif-python 0.1.2__tar.gz → 0.1.3__tar.gz
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.
- {onvif_python-0.1.2/onvif_python.egg-info → onvif_python-0.1.3}/PKG-INFO +14 -7
- {onvif_python-0.1.2 → onvif_python-0.1.3}/README.md +13 -6
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/cli/interactive.py +202 -6
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/cli/main.py +77 -14
- onvif_python-0.1.3/onvif/cli/utils.py +1278 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/operator.py +6 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3/onvif_python.egg-info}/PKG-INFO +14 -7
- {onvif_python-0.1.2 → onvif_python-0.1.3}/pyproject.toml +1 -1
- onvif_python-0.1.2/onvif/cli/utils.py +0 -475
- {onvif_python-0.1.2 → onvif_python-0.1.3}/LICENSE.md +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/MANIFEST.in +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/__init__.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/cli/__init__.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/cli/__main__.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/client.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/__init__.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/accesscontrol.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/accessrules.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/actionengine.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/analytics/analytics.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/analytics/ruleengine.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/analyticsdevice.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/appmgmt.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/authenticationbehavior.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/credential.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/deviceio.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/devicemgmt.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/display.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/doorcontrol.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/events/events.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/events/notification.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/events/pullpoint.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/events/subscription.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/imaging.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/media.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/media2.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/provisioning.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/ptz.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/receiver.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/recording.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/replay.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/schedule.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/search.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/security/advancedsecurity.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/security/authorizationserver.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/security/dot1x.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/security/jwt.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/security/keystore.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/security/mediasigning.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/security/tlsserver.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/thermal.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/uplink.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/utils/__init__.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/utils/error_handlers.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/utils/exceptions.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/utils/wsdl.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/utils/xml_capture.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/utils/zeep.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/accesscontrol/wsdl/index.html +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/accessrules/wsdl/accessrules.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/accessrules/wsdl/index.html +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/actionengine.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/advancedsecurity/wsdl/advancedsecurity.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/advancedsecurity/wsdl/index.html +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/analyticsdevice.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/appmgmt/wsdl/appmgmt.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/appmgmt/wsdl/index.html +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/authenticationbehavior/wsdl/authenticationbehavior.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/authenticationbehavior/wsdl/index.html +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/credential/wsdl/credential.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/credential/wsdl/index.html +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/device/wsdl/devicemgmt.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/deviceio.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/display/display.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/display/wsdl/index.htm +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/display.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/doorcontrol/wsdl/index.html +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/events/wsdl/bw-2-vs-mod.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/events/wsdl/event-vs.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/events/wsdl/event.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/events/wsdl/index.html +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/federatedsearch.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/media/wsdl/media.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/pacs/accesscontrol.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/pacs/doorcontrol.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/pacs/types.xsd +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/provisioning/wsdl/provisioning.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/receiver.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/recording.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/replay.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/schedule/wsdl/index.html +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/schedule/wsdl/schedule.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/schema/common.xsd +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/schema/metadatastream.xsd +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/schema/onvif.xsd +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/search.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/thermal/wsdl/index.html +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/thermal/wsdl/thermal.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/topics/topicns.xml +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/uplink/wsdl/index.html +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/uplink/wsdl/uplink.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/analytics/humanbody.xsd +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/analytics/humanface.xsd +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/analytics/index.html +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/analytics/radiometry.xsd +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/analytics/rules.xsd +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/analytics/wsdl/analytics.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/analytics/wsdl/index.html +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/imaging/wsdl/imaging.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/media/wsdl/index.html +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/media/wsdl/media.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/ptz/wsdl/ptz.wsdl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/util/federated_search_documentation.html +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/util/fedsearch-docu-viewer.xsl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/util/onvif-wsdl-viewer.xsl +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/util/operationIndex.html +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif_python.egg-info/SOURCES.txt +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif_python.egg-info/dependency_links.txt +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif_python.egg-info/entry_points.txt +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif_python.egg-info/requires.txt +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif_python.egg-info/top_level.txt +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/setup.cfg +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/setup.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/tests/test_client.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/tests/test_core.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/tests/test_error_handlers.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/tests/test_exceptions.py +0 -0
- {onvif_python-0.1.2 → onvif_python-0.1.3}/tests/test_wsdl_map.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: onvif-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: A comprehensive and developer-friendly Python library for working with ONVIF-compliant devices
|
|
5
5
|
Author-email: Nirsimetri Technologies® <open@nirsimetri.com>
|
|
6
6
|
Maintainer: Kaburagi
|
|
@@ -39,9 +39,9 @@ Dynamic: license-file
|
|
|
39
39
|
# ONVIF Python
|
|
40
40
|
|
|
41
41
|
[](https://github.com/nirsimetri/onvif-python?tab=MIT-1-ov-file)
|
|
42
|
-
[](https://deepwiki.com/nirsimetri/onvif-python)
|
|
43
|
+
[](https://pypi.org/project/onvif-python/)
|
|
44
|
+
[](https://clickpy.clickhouse.com/dashboard/onvif-python)
|
|
45
45
|
<br>
|
|
46
46
|
[](https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml)
|
|
47
47
|
[](https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml)
|
|
@@ -186,12 +186,12 @@ This library includes a powerful command-line interface (CLI) for interacting wi
|
|
|
186
186
|
<tr>
|
|
187
187
|
<td width="34.2%">
|
|
188
188
|
<a href="https://github.com/nirsimetri/onvif-python">
|
|
189
|
-
<img src="
|
|
189
|
+
<img src="https://raw.githubusercontent.com/nirsimetri/onvif-python/refs/heads/main/assets/onvif_cli.png" />
|
|
190
190
|
</a>
|
|
191
191
|
</td>
|
|
192
192
|
<td width="65.8%">
|
|
193
193
|
<a href="https://github.com/nirsimetri/onvif-python">
|
|
194
|
-
<img src="
|
|
194
|
+
<img src="https://raw.githubusercontent.com/nirsimetri/onvif-python/refs/heads/main/assets/onvif_operations.png" />
|
|
195
195
|
</a>
|
|
196
196
|
</td>
|
|
197
197
|
</tr>
|
|
@@ -278,6 +278,7 @@ Basic Commands:
|
|
|
278
278
|
info - Show connection and device information
|
|
279
279
|
exit, quit - Exit the shell
|
|
280
280
|
shortcuts - Show available shortcuts
|
|
281
|
+
help <command> - Show help for a specific command
|
|
281
282
|
|
|
282
283
|
Navigation Commands:
|
|
283
284
|
<service> - Enter service mode (e.g., devicemgmt, media)
|
|
@@ -286,7 +287,10 @@ Navigation Commands:
|
|
|
286
287
|
up - Exit current service mode (go up one level)
|
|
287
288
|
pwd - Show current service context
|
|
288
289
|
clear - Clear terminal screen
|
|
289
|
-
|
|
290
|
+
|
|
291
|
+
Service Mode Commands:
|
|
292
|
+
desc <method> - Show method documentation
|
|
293
|
+
type <method> - Show input/output types from WSDL
|
|
290
294
|
|
|
291
295
|
Method Execution:
|
|
292
296
|
<method> - Execute method without parameters
|
|
@@ -374,6 +378,9 @@ onvif --discover --username admin --password admin123 --interactive
|
|
|
374
378
|
|
|
375
379
|
# Short form
|
|
376
380
|
onvif -d -u admin -p admin123 -i
|
|
381
|
+
|
|
382
|
+
# Discover and interactive (will prompt for credentials)
|
|
383
|
+
onvif -d -i
|
|
377
384
|
```
|
|
378
385
|
|
|
379
386
|
**Discover and Execute Command:**
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# ONVIF Python
|
|
2
2
|
|
|
3
3
|
[](https://github.com/nirsimetri/onvif-python?tab=MIT-1-ov-file)
|
|
4
|
-
[](https://deepwiki.com/nirsimetri/onvif-python)
|
|
5
|
+
[](https://pypi.org/project/onvif-python/)
|
|
6
|
+
[](https://clickpy.clickhouse.com/dashboard/onvif-python)
|
|
7
7
|
<br>
|
|
8
8
|
[](https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml)
|
|
9
9
|
[](https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml)
|
|
@@ -148,12 +148,12 @@ This library includes a powerful command-line interface (CLI) for interacting wi
|
|
|
148
148
|
<tr>
|
|
149
149
|
<td width="34.2%">
|
|
150
150
|
<a href="https://github.com/nirsimetri/onvif-python">
|
|
151
|
-
<img src="
|
|
151
|
+
<img src="https://raw.githubusercontent.com/nirsimetri/onvif-python/refs/heads/main/assets/onvif_cli.png" />
|
|
152
152
|
</a>
|
|
153
153
|
</td>
|
|
154
154
|
<td width="65.8%">
|
|
155
155
|
<a href="https://github.com/nirsimetri/onvif-python">
|
|
156
|
-
<img src="
|
|
156
|
+
<img src="https://raw.githubusercontent.com/nirsimetri/onvif-python/refs/heads/main/assets/onvif_operations.png" />
|
|
157
157
|
</a>
|
|
158
158
|
</td>
|
|
159
159
|
</tr>
|
|
@@ -240,6 +240,7 @@ Basic Commands:
|
|
|
240
240
|
info - Show connection and device information
|
|
241
241
|
exit, quit - Exit the shell
|
|
242
242
|
shortcuts - Show available shortcuts
|
|
243
|
+
help <command> - Show help for a specific command
|
|
243
244
|
|
|
244
245
|
Navigation Commands:
|
|
245
246
|
<service> - Enter service mode (e.g., devicemgmt, media)
|
|
@@ -248,7 +249,10 @@ Navigation Commands:
|
|
|
248
249
|
up - Exit current service mode (go up one level)
|
|
249
250
|
pwd - Show current service context
|
|
250
251
|
clear - Clear terminal screen
|
|
251
|
-
|
|
252
|
+
|
|
253
|
+
Service Mode Commands:
|
|
254
|
+
desc <method> - Show method documentation
|
|
255
|
+
type <method> - Show input/output types from WSDL
|
|
252
256
|
|
|
253
257
|
Method Execution:
|
|
254
258
|
<method> - Execute method without parameters
|
|
@@ -336,6 +340,9 @@ onvif --discover --username admin --password admin123 --interactive
|
|
|
336
340
|
|
|
337
341
|
# Short form
|
|
338
342
|
onvif -d -u admin -p admin123 -i
|
|
343
|
+
|
|
344
|
+
# Discover and interactive (will prompt for credentials)
|
|
345
|
+
onvif -d -i
|
|
339
346
|
```
|
|
340
347
|
|
|
341
348
|
**Discover and Execute Command:**
|
|
@@ -21,6 +21,7 @@ from .utils import (
|
|
|
21
21
|
format_capabilities_as_services,
|
|
22
22
|
format_services_list,
|
|
23
23
|
get_method_documentation,
|
|
24
|
+
get_operation_type_info,
|
|
24
25
|
)
|
|
25
26
|
|
|
26
27
|
|
|
@@ -61,6 +62,7 @@ class InteractiveShell(cmd.Cmd):
|
|
|
61
62
|
"pwd",
|
|
62
63
|
"shortcuts",
|
|
63
64
|
"desc",
|
|
65
|
+
"type",
|
|
64
66
|
]
|
|
65
67
|
|
|
66
68
|
# For background health check
|
|
@@ -179,7 +181,7 @@ class InteractiveShell(cmd.Cmd):
|
|
|
179
181
|
" / __ \\/ | / / | / / _/ ____/",
|
|
180
182
|
" / / / / |/ /| | / // // /_ ",
|
|
181
183
|
"/ /_/ / /| / | |/ // // __/ ",
|
|
182
|
-
"\\____/_/ |_/ |___/___/_/ v0.1.
|
|
184
|
+
"\\____/_/ |_/ |___/___/_/ v0.1.3",
|
|
183
185
|
" ",
|
|
184
186
|
]
|
|
185
187
|
|
|
@@ -766,8 +768,9 @@ class InteractiveShell(cmd.Cmd):
|
|
|
766
768
|
|
|
767
769
|
if not self.current_service:
|
|
768
770
|
print(
|
|
769
|
-
f"{colorize('Error:', 'red')}
|
|
771
|
+
f"{colorize('Error:', 'red')} You must be in a service mode to use 'desc'."
|
|
770
772
|
)
|
|
773
|
+
print(f"Enter a service first (e.g., {colorize('devicemgmt', 'yellow')})")
|
|
771
774
|
return
|
|
772
775
|
|
|
773
776
|
if not method_name:
|
|
@@ -808,8 +811,175 @@ class InteractiveShell(cmd.Cmd):
|
|
|
808
811
|
f"No documentation or parameter info found for method '{method_name}'."
|
|
809
812
|
)
|
|
810
813
|
|
|
811
|
-
def
|
|
812
|
-
|
|
814
|
+
def complete_desc(self, text, line, begidx, endidx):
|
|
815
|
+
"""Autocomplete method names for desc command"""
|
|
816
|
+
if not self.current_service:
|
|
817
|
+
return []
|
|
818
|
+
methods = get_service_methods(self.current_service)
|
|
819
|
+
return [m for m in methods if m.lower().startswith(text.lower())]
|
|
820
|
+
|
|
821
|
+
def do_type(self, line):
|
|
822
|
+
"""Show input and output types for an operation. Usage: type <operation_name>"""
|
|
823
|
+
operation_name = line.strip()
|
|
824
|
+
|
|
825
|
+
if not self.current_service:
|
|
826
|
+
print(
|
|
827
|
+
f"{colorize('Error:', 'red')} You must be in a service mode to use 'type'."
|
|
828
|
+
)
|
|
829
|
+
print(f"Enter a service first (e.g., {colorize('d', 'yellow')})")
|
|
830
|
+
return
|
|
831
|
+
|
|
832
|
+
if not operation_name:
|
|
833
|
+
print(f"Usage: {colorize('type <operation_name>', 'yellow')}")
|
|
834
|
+
return
|
|
835
|
+
|
|
836
|
+
if not hasattr(self.current_service, operation_name):
|
|
837
|
+
print(
|
|
838
|
+
f"{colorize('Error:', 'red')} Operation '{operation_name}' not found in service '{self.current_service_name}'."
|
|
839
|
+
)
|
|
840
|
+
print(f"Use {colorize('ls', 'yellow')} to see available operations.")
|
|
841
|
+
return
|
|
842
|
+
|
|
843
|
+
type_info = get_operation_type_info(self.current_service, operation_name)
|
|
844
|
+
|
|
845
|
+
if type_info:
|
|
846
|
+
# Helper function to display parameters recursively with tree-style indentation
|
|
847
|
+
def display_params(params, prefix_lines=None, is_root_level=False):
|
|
848
|
+
"""
|
|
849
|
+
Display parameters with tree-style formatting.
|
|
850
|
+
|
|
851
|
+
Args:
|
|
852
|
+
params: List of parameter dictionaries
|
|
853
|
+
prefix_lines: List of strings representing the tree prefix for each line
|
|
854
|
+
is_root_level: If True, don't show tree characters at this level
|
|
855
|
+
"""
|
|
856
|
+
if prefix_lines is None:
|
|
857
|
+
prefix_lines = []
|
|
858
|
+
|
|
859
|
+
for idx, param in enumerate(params):
|
|
860
|
+
is_last = idx == len(params) - 1
|
|
861
|
+
|
|
862
|
+
# Determine tree characters (only if not root level)
|
|
863
|
+
if is_root_level:
|
|
864
|
+
tree_branch = ""
|
|
865
|
+
tree_continue = ""
|
|
866
|
+
else:
|
|
867
|
+
if is_last:
|
|
868
|
+
tree_branch = "└── "
|
|
869
|
+
tree_continue = " "
|
|
870
|
+
else:
|
|
871
|
+
tree_branch = "├── "
|
|
872
|
+
tree_continue = "│ "
|
|
873
|
+
|
|
874
|
+
# Determine prefix symbol: + for attributes, - for elements
|
|
875
|
+
prefix_symbol = "+" if param.get("is_attribute", False) else "-"
|
|
876
|
+
|
|
877
|
+
# Format occurrences based on type
|
|
878
|
+
occurs = ""
|
|
879
|
+
if param.get("is_attribute", False):
|
|
880
|
+
# For attributes: only show "required" if use="required"
|
|
881
|
+
# Default for attributes is optional (no label needed)
|
|
882
|
+
if param["minOccurs"] == "1": # This means use="required"
|
|
883
|
+
occurs = f" - {colorize('required', 'yellow')}"
|
|
884
|
+
# If minOccurs == "0", it's optional (default), so no label
|
|
885
|
+
else:
|
|
886
|
+
# For elements: show unbounded, optional, or required
|
|
887
|
+
if param["maxOccurs"] == "unbounded":
|
|
888
|
+
occurs = f" - {colorize('unbounded', 'magenta')}"
|
|
889
|
+
elif param["minOccurs"] == "0":
|
|
890
|
+
occurs = f" - {colorize('optional', 'green')}"
|
|
891
|
+
else:
|
|
892
|
+
occurs = f" - {colorize('required', 'yellow')}"
|
|
893
|
+
|
|
894
|
+
# Format type
|
|
895
|
+
type_str = f"[{param['type']}]" if param["type"] else ""
|
|
896
|
+
|
|
897
|
+
# Build the current line prefix from all previous levels
|
|
898
|
+
current_prefix = "".join(prefix_lines)
|
|
899
|
+
|
|
900
|
+
# Add spacing for root level items
|
|
901
|
+
if is_root_level:
|
|
902
|
+
current_prefix = " "
|
|
903
|
+
|
|
904
|
+
# Display parameter name with tree structure, prefix, occurrence, and type
|
|
905
|
+
# Only add semicolon separator if there's an occurrence label
|
|
906
|
+
separator = ";" if occurs else ""
|
|
907
|
+
param_line = f"{current_prefix}{tree_branch}{prefix_symbol}{colorize(param['name'], 'white')}{occurs}{separator} {colorize(type_str, 'cyan')}"
|
|
908
|
+
print(param_line)
|
|
909
|
+
|
|
910
|
+
# Display documentation if available
|
|
911
|
+
if param.get("documentation"):
|
|
912
|
+
doc_lines = param["documentation"].split("\n")
|
|
913
|
+
for doc_line in doc_lines:
|
|
914
|
+
if doc_line.strip():
|
|
915
|
+
# Add tree continuation for documentation
|
|
916
|
+
if is_root_level:
|
|
917
|
+
doc_prefix = " "
|
|
918
|
+
else:
|
|
919
|
+
doc_prefix = current_prefix + tree_continue
|
|
920
|
+
wrapped_doc = textwrap.fill(
|
|
921
|
+
doc_line.strip(),
|
|
922
|
+
width=96, # Slightly less to account for tree chars
|
|
923
|
+
initial_indent=doc_prefix + " ",
|
|
924
|
+
subsequent_indent=doc_prefix + " ",
|
|
925
|
+
)
|
|
926
|
+
print(colorize(wrapped_doc, "reset"))
|
|
927
|
+
|
|
928
|
+
# Display children recursively with updated prefix
|
|
929
|
+
if param.get("children"):
|
|
930
|
+
if is_root_level:
|
|
931
|
+
# Start tree structure from children
|
|
932
|
+
new_prefix_lines = [" "]
|
|
933
|
+
else:
|
|
934
|
+
new_prefix_lines = prefix_lines + [tree_continue]
|
|
935
|
+
display_params(
|
|
936
|
+
param["children"], new_prefix_lines, is_root_level=False
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
# Display Input
|
|
940
|
+
if type_info["input"]:
|
|
941
|
+
input_msg = type_info["input"]
|
|
942
|
+
print(f"\n{colorize('Input:', 'cyan')}")
|
|
943
|
+
msg_name = f"[{input_msg['name']}]"
|
|
944
|
+
print(f"{colorize(msg_name, 'yellow')}")
|
|
945
|
+
|
|
946
|
+
if input_msg["parameters"]:
|
|
947
|
+
display_params(input_msg["parameters"], is_root_level=True)
|
|
948
|
+
else:
|
|
949
|
+
print(f" {colorize('(no parameters)', 'reset')}")
|
|
950
|
+
else:
|
|
951
|
+
print(
|
|
952
|
+
f"\n{colorize('Input:', 'cyan')} {colorize('(not defined)', 'reset')}"
|
|
953
|
+
)
|
|
954
|
+
|
|
955
|
+
# Display Output
|
|
956
|
+
if type_info["output"]:
|
|
957
|
+
output_msg = type_info["output"]
|
|
958
|
+
print(f"\n{colorize('Output:', 'cyan')}")
|
|
959
|
+
msg_name = f"[{output_msg['name']}]"
|
|
960
|
+
print(f"{colorize(msg_name, 'yellow')}")
|
|
961
|
+
|
|
962
|
+
if output_msg["parameters"]:
|
|
963
|
+
display_params(output_msg["parameters"], is_root_level=True)
|
|
964
|
+
else:
|
|
965
|
+
print(f" {colorize('(no parameters)', 'reset')}")
|
|
966
|
+
else:
|
|
967
|
+
print(
|
|
968
|
+
f"\n{colorize('Output:', 'cyan')} {colorize('(not defined)', 'reset')}"
|
|
969
|
+
)
|
|
970
|
+
|
|
971
|
+
print() # Add newline for spacing
|
|
972
|
+
else:
|
|
973
|
+
print(
|
|
974
|
+
f"{colorize('Error:', 'red')} Could not retrieve type information for '{operation_name}'."
|
|
975
|
+
)
|
|
976
|
+
|
|
977
|
+
def complete_type(self, text, line, begidx, endidx):
|
|
978
|
+
"""Autocomplete method names for type command"""
|
|
979
|
+
if not self.current_service:
|
|
980
|
+
return []
|
|
981
|
+
methods = get_service_methods(self.current_service)
|
|
982
|
+
return [m for m in methods if m.lower().startswith(text.lower())]
|
|
813
983
|
|
|
814
984
|
def do_cd(self, line):
|
|
815
985
|
"""Change to service directory (alias for entering service)"""
|
|
@@ -821,6 +991,11 @@ class InteractiveShell(cmd.Cmd):
|
|
|
821
991
|
return
|
|
822
992
|
return self.do_enter_service(line)
|
|
823
993
|
|
|
994
|
+
def complete_cd(self, text, line, begidx, endidx):
|
|
995
|
+
"""Autocomplete service names for cd command"""
|
|
996
|
+
services = get_device_available_services(self.client)
|
|
997
|
+
return [s for s in services if s.lower().startswith(text.lower())]
|
|
998
|
+
|
|
824
999
|
def do_pwd(self, line):
|
|
825
1000
|
"""Show current service context"""
|
|
826
1001
|
if self.current_service_name:
|
|
@@ -844,7 +1019,11 @@ class InteractiveShell(cmd.Cmd):
|
|
|
844
1019
|
up - Go up one level
|
|
845
1020
|
pwd - Show current context
|
|
846
1021
|
clear - Clear terminal screen
|
|
847
|
-
|
|
1022
|
+
help <command> - Show help for a command
|
|
1023
|
+
|
|
1024
|
+
{colorize('Service Mode Commands:', 'yellow')}
|
|
1025
|
+
desc <method> - Show method documentation
|
|
1026
|
+
type <method> - Show input/output types from WSDL
|
|
848
1027
|
|
|
849
1028
|
{colorize('Quick Access:', 'yellow')}
|
|
850
1029
|
caps - Show capabilities (same as 'capabilities')
|
|
@@ -925,6 +1104,10 @@ class InteractiveShell(cmd.Cmd):
|
|
|
925
1104
|
else:
|
|
926
1105
|
print(f"{colorize('Error:', 'red')} No stored data named '{line}'")
|
|
927
1106
|
|
|
1107
|
+
def complete_rm(self, text, line, begidx, endidx):
|
|
1108
|
+
"""Autocomplete stored variable names for rm command"""
|
|
1109
|
+
return [name for name in self.stored_data.keys() if name.startswith(text)]
|
|
1110
|
+
|
|
928
1111
|
def do_show(self, line):
|
|
929
1112
|
"""Show stored data: show <name> or show <name>.<attribute> or show <name>[index]"""
|
|
930
1113
|
if not line:
|
|
@@ -947,6 +1130,15 @@ class InteractiveShell(cmd.Cmd):
|
|
|
947
1130
|
else:
|
|
948
1131
|
print(f"{colorize('Error:', 'red')} Cannot resolve '{line}'")
|
|
949
1132
|
|
|
1133
|
+
def complete_show(self, text, line, begidx, endidx):
|
|
1134
|
+
"""Autocomplete stored variable names for show command"""
|
|
1135
|
+
# Get the part being completed
|
|
1136
|
+
parts = line.split()
|
|
1137
|
+
if len(parts) <= 1 or (len(parts) == 2 and not line.endswith(" ")):
|
|
1138
|
+
# Completing the variable name
|
|
1139
|
+
return [name for name in self.stored_data.keys() if name.startswith(text)]
|
|
1140
|
+
return []
|
|
1141
|
+
|
|
950
1142
|
def do_clear(self, line):
|
|
951
1143
|
"""Clear terminal screen"""
|
|
952
1144
|
import os
|
|
@@ -1109,7 +1301,11 @@ class InteractiveShell(cmd.Cmd):
|
|
|
1109
1301
|
up - Exit current service mode (go up one level)
|
|
1110
1302
|
pwd - Show current service context
|
|
1111
1303
|
clear - Clear terminal screen
|
|
1112
|
-
|
|
1304
|
+
help <command> - Show help for a specific command
|
|
1305
|
+
|
|
1306
|
+
{colorize('Service Mode Commands:', 'yellow')}
|
|
1307
|
+
desc <method> - Show method documentation
|
|
1308
|
+
type <method> - Show input/output types from WSDL
|
|
1113
1309
|
|
|
1114
1310
|
{colorize('Method Execution:', 'yellow')}
|
|
1115
1311
|
<method> - Execute method without parameters
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import sys
|
|
5
|
+
import warnings
|
|
5
6
|
import getpass
|
|
6
7
|
from typing import Any, Optional, Tuple
|
|
7
8
|
|
|
@@ -111,6 +112,9 @@ Examples:
|
|
|
111
112
|
|
|
112
113
|
def main():
|
|
113
114
|
"""Main CLI entry point"""
|
|
115
|
+
# Setup custom warning format for cleaner output
|
|
116
|
+
setup_warning_format()
|
|
117
|
+
|
|
114
118
|
parser = create_parser()
|
|
115
119
|
|
|
116
120
|
# Check if no arguments provided at all
|
|
@@ -127,8 +131,8 @@ def main():
|
|
|
127
131
|
f"{colorize('--discover', 'white')} cannot be used with {colorize('--host', 'white')}"
|
|
128
132
|
)
|
|
129
133
|
|
|
130
|
-
# Discover devices
|
|
131
|
-
devices = discover_devices(timeout=4)
|
|
134
|
+
# Discover devices (pass --https flag to prioritize HTTPS XAddrs)
|
|
135
|
+
devices = discover_devices(timeout=4, prefer_https=args.https)
|
|
132
136
|
|
|
133
137
|
if not devices:
|
|
134
138
|
print(f"\n{colorize('No ONVIF devices discovered. Exiting.', 'red')}")
|
|
@@ -141,8 +145,11 @@ def main():
|
|
|
141
145
|
print(f"{colorize('Device selection cancelled.', 'cyan')}")
|
|
142
146
|
sys.exit(0)
|
|
143
147
|
|
|
144
|
-
# Set host and
|
|
145
|
-
args.host, args.port = selected
|
|
148
|
+
# Set host, port, and HTTPS from selected device
|
|
149
|
+
args.host, args.port, device_use_https = selected
|
|
150
|
+
|
|
151
|
+
# Use device's detected protocol (already filtered by prefer_https in discover_devices)
|
|
152
|
+
# No need to override - device info already has correct protocol based on --https flag
|
|
146
153
|
|
|
147
154
|
# Validate that host is provided (either via --host or --discover)
|
|
148
155
|
if not args.host:
|
|
@@ -253,8 +260,16 @@ def execute_command(
|
|
|
253
260
|
return method(**params)
|
|
254
261
|
|
|
255
262
|
|
|
256
|
-
def discover_devices(timeout: int = 4) -> list:
|
|
257
|
-
"""Discover ONVIF devices on the network using WS-Discovery.
|
|
263
|
+
def discover_devices(timeout: int = 4, prefer_https: bool = False) -> list:
|
|
264
|
+
"""Discover ONVIF devices on the network using WS-Discovery.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
timeout: Discovery timeout in seconds
|
|
268
|
+
prefer_https: If True, prioritize HTTPS XAddrs when available
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
List of discovered devices with connection info
|
|
272
|
+
"""
|
|
258
273
|
import socket
|
|
259
274
|
import uuid
|
|
260
275
|
import xml.etree.ElementTree as ET
|
|
@@ -360,6 +375,7 @@ def discover_devices(timeout: int = 4) -> list:
|
|
|
360
375
|
"xaddrs": [],
|
|
361
376
|
"host": None,
|
|
362
377
|
"port": 80,
|
|
378
|
+
"use_https": False, # Auto-detect HTTPS from XAddrs
|
|
363
379
|
}
|
|
364
380
|
|
|
365
381
|
epr = probe_match.find(".//wsa:EndpointReference/wsa:Address", namespaces)
|
|
@@ -384,17 +400,39 @@ def discover_devices(timeout: int = 4) -> list:
|
|
|
384
400
|
if xaddrs_elem is not None and xaddrs_elem.text:
|
|
385
401
|
device_info["xaddrs"] = xaddrs_elem.text.split()
|
|
386
402
|
|
|
387
|
-
# Extract host and
|
|
403
|
+
# Extract host, port, and protocol from XAddrs
|
|
404
|
+
# Use prefer_https parameter to decide which XAddr to use
|
|
388
405
|
if device_info["xaddrs"]:
|
|
389
|
-
|
|
406
|
+
# Select XAddr based on prefer_https flag
|
|
407
|
+
if prefer_https:
|
|
408
|
+
# Try to find HTTPS XAddr first if prefer_https=True
|
|
409
|
+
https_xaddr = next(
|
|
410
|
+
(
|
|
411
|
+
x
|
|
412
|
+
for x in device_info["xaddrs"]
|
|
413
|
+
if x.startswith("https://")
|
|
414
|
+
),
|
|
415
|
+
None,
|
|
416
|
+
)
|
|
417
|
+
xaddr = https_xaddr or device_info["xaddrs"][0]
|
|
418
|
+
else:
|
|
419
|
+
# Use first XAddr (usually HTTP) if prefer_https=False
|
|
420
|
+
xaddr = device_info["xaddrs"][0]
|
|
421
|
+
|
|
390
422
|
if "://" in xaddr:
|
|
423
|
+
# Detect protocol
|
|
424
|
+
protocol = xaddr.split("://")[0]
|
|
425
|
+
device_info["use_https"] = protocol == "https"
|
|
426
|
+
|
|
427
|
+
# Extract host and port
|
|
391
428
|
parts = xaddr.split("://")[1].split("/")[0]
|
|
392
429
|
if ":" in parts:
|
|
393
430
|
device_info["host"] = parts.split(":")[0]
|
|
394
431
|
device_info["port"] = int(parts.split(":")[1])
|
|
395
432
|
else:
|
|
396
433
|
device_info["host"] = parts
|
|
397
|
-
|
|
434
|
+
# Set default port based on protocol
|
|
435
|
+
device_info["port"] = 443 if protocol == "https" else 80
|
|
398
436
|
|
|
399
437
|
if device_info["host"]:
|
|
400
438
|
devices.append(device_info)
|
|
@@ -405,8 +443,12 @@ def discover_devices(timeout: int = 4) -> list:
|
|
|
405
443
|
return devices
|
|
406
444
|
|
|
407
445
|
|
|
408
|
-
def select_device_interactive(devices: list) -> Optional[Tuple[str, int]]:
|
|
409
|
-
"""Display devices and allow user to select one interactively.
|
|
446
|
+
def select_device_interactive(devices: list) -> Optional[Tuple[str, int, bool]]:
|
|
447
|
+
"""Display devices and allow user to select one interactively.
|
|
448
|
+
|
|
449
|
+
Returns:
|
|
450
|
+
Tuple of (host, port, use_https) or None if cancelled
|
|
451
|
+
"""
|
|
410
452
|
if not devices:
|
|
411
453
|
print(f"\n{colorize('No ONVIF devices found.', 'red')}")
|
|
412
454
|
return None
|
|
@@ -415,8 +457,14 @@ def select_device_interactive(devices: list) -> Optional[Tuple[str, int]]:
|
|
|
415
457
|
|
|
416
458
|
for idx, device in enumerate(devices, 1):
|
|
417
459
|
idx_str = colorize(f"[{idx}]", "yellow")
|
|
460
|
+
protocol = "https" if device.get("use_https", False) else "http"
|
|
418
461
|
host_port = f"{device['host']}:{device['port']}"
|
|
419
|
-
|
|
462
|
+
protocol_indicator = (
|
|
463
|
+
colorize("🔒 HTTPS", "green")
|
|
464
|
+
if device.get("use_https", False)
|
|
465
|
+
else colorize("HTTP", "white")
|
|
466
|
+
)
|
|
467
|
+
print(f"\n{idx_str} {colorize(host_port, 'yellow')} ({protocol_indicator})")
|
|
420
468
|
print(f" [id] {device['epr']}")
|
|
421
469
|
|
|
422
470
|
if device["xaddrs"]:
|
|
@@ -454,11 +502,16 @@ def select_device_interactive(devices: list) -> Optional[Tuple[str, int]]:
|
|
|
454
502
|
idx = int(selection)
|
|
455
503
|
if 1 <= idx <= len(devices):
|
|
456
504
|
selected = devices[idx - 1]
|
|
505
|
+
protocol = "https" if selected.get("use_https", False) else "http"
|
|
457
506
|
host_port = f"{selected['host']}:{selected['port']}"
|
|
458
507
|
print(
|
|
459
|
-
f"\n{colorize('Selected:', 'green')} {colorize(host_port, 'yellow')}"
|
|
508
|
+
f"\n{colorize('Selected:', 'green')} {colorize(protocol, 'cyan')}://{colorize(host_port, 'yellow')}"
|
|
509
|
+
)
|
|
510
|
+
return (
|
|
511
|
+
selected["host"],
|
|
512
|
+
selected["port"],
|
|
513
|
+
selected.get("use_https", False),
|
|
460
514
|
)
|
|
461
|
-
return (selected["host"], selected["port"])
|
|
462
515
|
else:
|
|
463
516
|
print(f"{colorize('Invalid selection. Please try again.', 'red')}")
|
|
464
517
|
|
|
@@ -468,5 +521,15 @@ def select_device_interactive(devices: list) -> Optional[Tuple[str, int]]:
|
|
|
468
521
|
return None
|
|
469
522
|
|
|
470
523
|
|
|
524
|
+
def setup_warning_format():
|
|
525
|
+
"""Setup custom warning format to show clean, concise warnings"""
|
|
526
|
+
|
|
527
|
+
def custom_warning_format(message, category, filename, lineno, line=None):
|
|
528
|
+
# Show only the warning message without file path and line number
|
|
529
|
+
return f"{category.__name__}: {message}\n"
|
|
530
|
+
|
|
531
|
+
warnings.formatwarning = custom_warning_format
|
|
532
|
+
|
|
533
|
+
|
|
471
534
|
if __name__ == "__main__":
|
|
472
535
|
main()
|