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.
Files changed (128) hide show
  1. {onvif_python-0.1.2/onvif_python.egg-info → onvif_python-0.1.3}/PKG-INFO +14 -7
  2. {onvif_python-0.1.2 → onvif_python-0.1.3}/README.md +13 -6
  3. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/cli/interactive.py +202 -6
  4. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/cli/main.py +77 -14
  5. onvif_python-0.1.3/onvif/cli/utils.py +1278 -0
  6. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/operator.py +6 -0
  7. {onvif_python-0.1.2 → onvif_python-0.1.3/onvif_python.egg-info}/PKG-INFO +14 -7
  8. {onvif_python-0.1.2 → onvif_python-0.1.3}/pyproject.toml +1 -1
  9. onvif_python-0.1.2/onvif/cli/utils.py +0 -475
  10. {onvif_python-0.1.2 → onvif_python-0.1.3}/LICENSE.md +0 -0
  11. {onvif_python-0.1.2 → onvif_python-0.1.3}/MANIFEST.in +0 -0
  12. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/__init__.py +0 -0
  13. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/cli/__init__.py +0 -0
  14. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/cli/__main__.py +0 -0
  15. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/client.py +0 -0
  16. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/__init__.py +0 -0
  17. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/accesscontrol.py +0 -0
  18. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/accessrules.py +0 -0
  19. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/actionengine.py +0 -0
  20. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/analytics/analytics.py +0 -0
  21. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/analytics/ruleengine.py +0 -0
  22. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/analyticsdevice.py +0 -0
  23. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/appmgmt.py +0 -0
  24. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/authenticationbehavior.py +0 -0
  25. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/credential.py +0 -0
  26. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/deviceio.py +0 -0
  27. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/devicemgmt.py +0 -0
  28. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/display.py +0 -0
  29. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/doorcontrol.py +0 -0
  30. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/events/events.py +0 -0
  31. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/events/notification.py +0 -0
  32. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/events/pullpoint.py +0 -0
  33. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/events/subscription.py +0 -0
  34. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/imaging.py +0 -0
  35. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/media.py +0 -0
  36. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/media2.py +0 -0
  37. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/provisioning.py +0 -0
  38. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/ptz.py +0 -0
  39. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/receiver.py +0 -0
  40. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/recording.py +0 -0
  41. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/replay.py +0 -0
  42. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/schedule.py +0 -0
  43. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/search.py +0 -0
  44. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/security/advancedsecurity.py +0 -0
  45. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/security/authorizationserver.py +0 -0
  46. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/security/dot1x.py +0 -0
  47. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/security/jwt.py +0 -0
  48. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/security/keystore.py +0 -0
  49. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/security/mediasigning.py +0 -0
  50. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/security/tlsserver.py +0 -0
  51. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/thermal.py +0 -0
  52. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/services/uplink.py +0 -0
  53. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/utils/__init__.py +0 -0
  54. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/utils/error_handlers.py +0 -0
  55. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/utils/exceptions.py +0 -0
  56. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/utils/wsdl.py +0 -0
  57. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/utils/xml_capture.py +0 -0
  58. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/utils/zeep.py +0 -0
  59. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/accesscontrol/wsdl/index.html +0 -0
  60. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/accessrules/wsdl/accessrules.wsdl +0 -0
  61. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/accessrules/wsdl/index.html +0 -0
  62. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/actionengine.wsdl +0 -0
  63. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/advancedsecurity/wsdl/advancedsecurity.wsdl +0 -0
  64. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/advancedsecurity/wsdl/index.html +0 -0
  65. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/analyticsdevice.wsdl +0 -0
  66. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/appmgmt/wsdl/appmgmt.wsdl +0 -0
  67. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/appmgmt/wsdl/index.html +0 -0
  68. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/authenticationbehavior/wsdl/authenticationbehavior.wsdl +0 -0
  69. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/authenticationbehavior/wsdl/index.html +0 -0
  70. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/credential/wsdl/credential.wsdl +0 -0
  71. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/credential/wsdl/index.html +0 -0
  72. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/device/wsdl/devicemgmt.wsdl +0 -0
  73. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/deviceio.wsdl +0 -0
  74. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/display/display.wsdl +0 -0
  75. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/display/wsdl/index.htm +0 -0
  76. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/display.wsdl +0 -0
  77. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/doorcontrol/wsdl/index.html +0 -0
  78. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/events/wsdl/bw-2-vs-mod.wsdl +0 -0
  79. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/events/wsdl/event-vs.wsdl +0 -0
  80. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/events/wsdl/event.wsdl +0 -0
  81. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/events/wsdl/index.html +0 -0
  82. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/federatedsearch.wsdl +0 -0
  83. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/media/wsdl/media.wsdl +0 -0
  84. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/pacs/accesscontrol.wsdl +0 -0
  85. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/pacs/doorcontrol.wsdl +0 -0
  86. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/pacs/types.xsd +0 -0
  87. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/provisioning/wsdl/provisioning.wsdl +0 -0
  88. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/receiver.wsdl +0 -0
  89. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/recording.wsdl +0 -0
  90. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/replay.wsdl +0 -0
  91. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/schedule/wsdl/index.html +0 -0
  92. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/schedule/wsdl/schedule.wsdl +0 -0
  93. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/schema/common.xsd +0 -0
  94. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/schema/metadatastream.xsd +0 -0
  95. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/schema/onvif.xsd +0 -0
  96. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/search.wsdl +0 -0
  97. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/thermal/wsdl/index.html +0 -0
  98. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/thermal/wsdl/thermal.wsdl +0 -0
  99. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/topics/topicns.xml +0 -0
  100. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/uplink/wsdl/index.html +0 -0
  101. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver10/uplink/wsdl/uplink.wsdl +0 -0
  102. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/analytics/humanbody.xsd +0 -0
  103. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/analytics/humanface.xsd +0 -0
  104. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/analytics/index.html +0 -0
  105. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/analytics/radiometry.xsd +0 -0
  106. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/analytics/rules.xsd +0 -0
  107. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/analytics/wsdl/analytics.wsdl +0 -0
  108. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/analytics/wsdl/index.html +0 -0
  109. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/imaging/wsdl/imaging.wsdl +0 -0
  110. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/media/wsdl/index.html +0 -0
  111. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/media/wsdl/media.wsdl +0 -0
  112. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/ptz/wsdl/ptz.wsdl +0 -0
  113. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/util/federated_search_documentation.html +0 -0
  114. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/util/fedsearch-docu-viewer.xsl +0 -0
  115. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/util/onvif-wsdl-viewer.xsl +0 -0
  116. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif/wsdl/ver20/util/operationIndex.html +0 -0
  117. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif_python.egg-info/SOURCES.txt +0 -0
  118. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif_python.egg-info/dependency_links.txt +0 -0
  119. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif_python.egg-info/entry_points.txt +0 -0
  120. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif_python.egg-info/requires.txt +0 -0
  121. {onvif_python-0.1.2 → onvif_python-0.1.3}/onvif_python.egg-info/top_level.txt +0 -0
  122. {onvif_python-0.1.2 → onvif_python-0.1.3}/setup.cfg +0 -0
  123. {onvif_python-0.1.2 → onvif_python-0.1.3}/setup.py +0 -0
  124. {onvif_python-0.1.2 → onvif_python-0.1.3}/tests/test_client.py +0 -0
  125. {onvif_python-0.1.2 → onvif_python-0.1.3}/tests/test_core.py +0 -0
  126. {onvif_python-0.1.2 → onvif_python-0.1.3}/tests/test_error_handlers.py +0 -0
  127. {onvif_python-0.1.2 → onvif_python-0.1.3}/tests/test_exceptions.py +0 -0
  128. {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.2
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
  [![License](https://img.shields.io/badge/License-MIT-blue)](https://github.com/nirsimetri/onvif-python?tab=MIT-1-ov-file)
42
- [![DeepWiki](https://img.shields.io/badge/DeepWiki-AI%20Wiki-orange)](https://deepwiki.com/nirsimetri/onvif-python)
43
- [![PyPI](https://img.shields.io/badge/PyPI-0.1.2-yellow?logo=archive)](https://pypi.org/project/onvif-python/)
44
- [![Downloads](https://img.shields.io/pypi/dm/onvif-python?label=PyPI%20Downloads)](https://clickpy.clickhouse.com/dashboard/onvif-python)
42
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/nirsimetri/onvif-python)
43
+ [![PyPI](https://img.shields.io/badge/PyPI-0.1.3-orange?logo=archive)](https://pypi.org/project/onvif-python/)
44
+ [![Downloads](https://img.shields.io/pypi/dm/onvif-python?label=Downloads&color=red)](https://clickpy.clickhouse.com/dashboard/onvif-python)
45
45
  <br>
46
46
  [![Build](https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml/badge.svg?branch=main)](https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml)
47
47
  [![Upload Python Package](https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml/badge.svg)](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="./assets/onvif_cli.png" />
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="./assets/onvif_operations.png" />
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
- desc <method> - Show method documentation in service mode
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
  [![License](https://img.shields.io/badge/License-MIT-blue)](https://github.com/nirsimetri/onvif-python?tab=MIT-1-ov-file)
4
- [![DeepWiki](https://img.shields.io/badge/DeepWiki-AI%20Wiki-orange)](https://deepwiki.com/nirsimetri/onvif-python)
5
- [![PyPI](https://img.shields.io/badge/PyPI-0.1.2-yellow?logo=archive)](https://pypi.org/project/onvif-python/)
6
- [![Downloads](https://img.shields.io/pypi/dm/onvif-python?label=PyPI%20Downloads)](https://clickpy.clickhouse.com/dashboard/onvif-python)
4
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/nirsimetri/onvif-python)
5
+ [![PyPI](https://img.shields.io/badge/PyPI-0.1.3-orange?logo=archive)](https://pypi.org/project/onvif-python/)
6
+ [![Downloads](https://img.shields.io/pypi/dm/onvif-python?label=Downloads&color=red)](https://clickpy.clickhouse.com/dashboard/onvif-python)
7
7
  <br>
8
8
  [![Build](https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml/badge.svg?branch=main)](https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml)
9
9
  [![Upload Python Package](https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml/badge.svg)](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="./assets/onvif_cli.png" />
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="./assets/onvif_operations.png" />
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
- desc <method> - Show method documentation in service mode
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.2",
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')} This command can only be used within a service context."
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 help_desc(self):
812
- print(self.do_desc.__doc__)
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
- desc <method> - Show method documentation in service mode
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
- desc <method> - Show method documentation in service mode
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 port from selected device
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 port from first XAddr
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
- xaddr = device_info["xaddrs"][0]
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
- device_info["port"] = 80
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
- print(f"\n{idx_str} {colorize(host_port, 'yellow')}")
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()