onvif-python 0.1.4__tar.gz → 0.1.5__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 (127) hide show
  1. {onvif_python-0.1.4/onvif_python.egg-info → onvif_python-0.1.5}/PKG-INFO +32 -7
  2. {onvif_python-0.1.4 → onvif_python-0.1.5}/README.md +31 -6
  3. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/cli/interactive.py +105 -10
  4. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/cli/main.py +13 -6
  5. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/cli/utils.py +205 -51
  6. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/client.py +4 -4
  7. {onvif_python-0.1.4 → onvif_python-0.1.5/onvif_python.egg-info}/PKG-INFO +32 -7
  8. {onvif_python-0.1.4 → onvif_python-0.1.5}/pyproject.toml +1 -1
  9. {onvif_python-0.1.4 → onvif_python-0.1.5}/LICENSE.md +0 -0
  10. {onvif_python-0.1.4 → onvif_python-0.1.5}/MANIFEST.in +0 -0
  11. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/__init__.py +0 -0
  12. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/cli/__init__.py +0 -0
  13. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/cli/__main__.py +0 -0
  14. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/operator.py +0 -0
  15. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/__init__.py +0 -0
  16. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/accesscontrol.py +0 -0
  17. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/accessrules.py +0 -0
  18. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/actionengine.py +0 -0
  19. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/analytics/analytics.py +0 -0
  20. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/analytics/ruleengine.py +0 -0
  21. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/analyticsdevice.py +0 -0
  22. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/appmgmt.py +0 -0
  23. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/authenticationbehavior.py +0 -0
  24. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/credential.py +0 -0
  25. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/deviceio.py +0 -0
  26. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/devicemgmt.py +0 -0
  27. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/display.py +0 -0
  28. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/doorcontrol.py +0 -0
  29. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/events/events.py +0 -0
  30. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/events/notification.py +0 -0
  31. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/events/pullpoint.py +0 -0
  32. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/events/subscription.py +0 -0
  33. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/imaging.py +0 -0
  34. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/media.py +0 -0
  35. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/media2.py +0 -0
  36. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/provisioning.py +0 -0
  37. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/ptz.py +0 -0
  38. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/receiver.py +0 -0
  39. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/recording.py +0 -0
  40. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/replay.py +0 -0
  41. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/schedule.py +0 -0
  42. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/search.py +0 -0
  43. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/security/advancedsecurity.py +0 -0
  44. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/security/authorizationserver.py +0 -0
  45. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/security/dot1x.py +0 -0
  46. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/security/jwt.py +0 -0
  47. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/security/keystore.py +0 -0
  48. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/security/mediasigning.py +0 -0
  49. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/security/tlsserver.py +0 -0
  50. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/thermal.py +0 -0
  51. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/services/uplink.py +0 -0
  52. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/utils/__init__.py +0 -0
  53. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/utils/error_handlers.py +0 -0
  54. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/utils/exceptions.py +0 -0
  55. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/utils/wsdl.py +0 -0
  56. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/utils/xml_capture.py +0 -0
  57. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/utils/zeep.py +0 -0
  58. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/accesscontrol/wsdl/index.html +0 -0
  59. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/accessrules/wsdl/accessrules.wsdl +0 -0
  60. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/accessrules/wsdl/index.html +0 -0
  61. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/actionengine.wsdl +0 -0
  62. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/advancedsecurity/wsdl/advancedsecurity.wsdl +0 -0
  63. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/advancedsecurity/wsdl/index.html +0 -0
  64. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/analyticsdevice.wsdl +0 -0
  65. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/appmgmt/wsdl/appmgmt.wsdl +0 -0
  66. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/appmgmt/wsdl/index.html +0 -0
  67. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/authenticationbehavior/wsdl/authenticationbehavior.wsdl +0 -0
  68. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/authenticationbehavior/wsdl/index.html +0 -0
  69. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/credential/wsdl/credential.wsdl +0 -0
  70. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/credential/wsdl/index.html +0 -0
  71. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/device/wsdl/devicemgmt.wsdl +0 -0
  72. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/deviceio.wsdl +0 -0
  73. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/display/display.wsdl +0 -0
  74. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/display/wsdl/index.htm +0 -0
  75. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/display.wsdl +0 -0
  76. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/doorcontrol/wsdl/index.html +0 -0
  77. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/events/wsdl/bw-2-vs-mod.wsdl +0 -0
  78. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/events/wsdl/event-vs.wsdl +0 -0
  79. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/events/wsdl/event.wsdl +0 -0
  80. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/events/wsdl/index.html +0 -0
  81. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/federatedsearch.wsdl +0 -0
  82. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/media/wsdl/media.wsdl +0 -0
  83. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/pacs/accesscontrol.wsdl +0 -0
  84. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/pacs/doorcontrol.wsdl +0 -0
  85. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/pacs/types.xsd +0 -0
  86. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/provisioning/wsdl/provisioning.wsdl +0 -0
  87. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/receiver.wsdl +0 -0
  88. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/recording.wsdl +0 -0
  89. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/replay.wsdl +0 -0
  90. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/schedule/wsdl/index.html +0 -0
  91. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/schedule/wsdl/schedule.wsdl +0 -0
  92. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/schema/common.xsd +0 -0
  93. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/schema/metadatastream.xsd +0 -0
  94. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/schema/onvif.xsd +0 -0
  95. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/search.wsdl +0 -0
  96. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/thermal/wsdl/index.html +0 -0
  97. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/thermal/wsdl/thermal.wsdl +0 -0
  98. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/topics/topicns.xml +0 -0
  99. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/uplink/wsdl/index.html +0 -0
  100. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver10/uplink/wsdl/uplink.wsdl +0 -0
  101. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver20/analytics/humanbody.xsd +0 -0
  102. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver20/analytics/humanface.xsd +0 -0
  103. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver20/analytics/index.html +0 -0
  104. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver20/analytics/radiometry.xsd +0 -0
  105. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver20/analytics/rules.xsd +0 -0
  106. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver20/analytics/wsdl/analytics.wsdl +0 -0
  107. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver20/analytics/wsdl/index.html +0 -0
  108. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver20/imaging/wsdl/imaging.wsdl +0 -0
  109. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver20/media/wsdl/index.html +0 -0
  110. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver20/media/wsdl/media.wsdl +0 -0
  111. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver20/ptz/wsdl/ptz.wsdl +0 -0
  112. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver20/util/federated_search_documentation.html +0 -0
  113. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver20/util/fedsearch-docu-viewer.xsl +0 -0
  114. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver20/util/onvif-wsdl-viewer.xsl +0 -0
  115. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif/wsdl/ver20/util/operationIndex.html +0 -0
  116. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif_python.egg-info/SOURCES.txt +0 -0
  117. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif_python.egg-info/dependency_links.txt +0 -0
  118. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif_python.egg-info/entry_points.txt +0 -0
  119. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif_python.egg-info/requires.txt +0 -0
  120. {onvif_python-0.1.4 → onvif_python-0.1.5}/onvif_python.egg-info/top_level.txt +0 -0
  121. {onvif_python-0.1.4 → onvif_python-0.1.5}/setup.cfg +0 -0
  122. {onvif_python-0.1.4 → onvif_python-0.1.5}/setup.py +0 -0
  123. {onvif_python-0.1.4 → onvif_python-0.1.5}/tests/test_client.py +0 -0
  124. {onvif_python-0.1.4 → onvif_python-0.1.5}/tests/test_core.py +0 -0
  125. {onvif_python-0.1.4 → onvif_python-0.1.5}/tests/test_error_handlers.py +0 -0
  126. {onvif_python-0.1.4 → onvif_python-0.1.5}/tests/test_exceptions.py +0 -0
  127. {onvif_python-0.1.4 → onvif_python-0.1.5}/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.4
3
+ Version: 0.1.5
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
@@ -38,9 +38,9 @@ Dynamic: license-file
38
38
 
39
39
  # ONVIF Python
40
40
 
41
- [![License](https://img.shields.io/badge/License-MIT-blue)](https://github.com/nirsimetri/onvif-python?tab=MIT-1-ov-file)
41
+ [![Codacy grade](https://img.shields.io/codacy/grade/bff08a94e4d447b690cea49c6594826d?label=Project%20Quality&logo=codacy)](https://app.codacy.com/gh/nirsimetri/onvif-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
42
42
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/nirsimetri/onvif-python)
43
- [![PyPI](https://img.shields.io/badge/PyPI-0.1.4-orange?logo=archive)](https://pypi.org/project/onvif-python/)
43
+ [![PyPI](https://img.shields.io/badge/PyPI-0.1.5-orange?logo=archive)](https://pypi.org/project/onvif-python/)
44
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)
@@ -212,10 +212,10 @@ This library includes a powerful command-line interface (CLI) for interacting wi
212
212
 
213
213
  ```bash
214
214
  usage: onvif [-h] [--host HOST] [--port PORT] [--username USERNAME] [--password PASSWORD] [--discover] [--timeout TIMEOUT] [--https] [--no-verify]
215
- [--no-patch] [--interactive] [--debug] [--wsdl WSDL] [--cache {all,db,mem,none}] [--version]
215
+ [--no-patch] [--interactive] [--debug] [--wsdl WSDL] [--cache {all,db,mem,none}] [--health-check-interval HEALTH_CHECK_INTERVAL] [--version]
216
216
  [service] [method] [params ...]
217
217
 
218
- ONVIF Terminal Client — v0.1.4
218
+ ONVIF Terminal Client — v0.1.5
219
219
  https://github.com/nirsimetri/onvif-python
220
220
 
221
221
  positional arguments:
@@ -241,6 +241,8 @@ options:
241
241
  --wsdl WSDL Custom WSDL directory path
242
242
  --cache {all,db,mem,none}
243
243
  Caching mode for ONVIFClient (default: all). 'all': memory+disk, 'db': disk-only, 'mem': memory-only, 'none': disabled.
244
+ --health-check-interval HEALTH_CHECK_INTERVAL, -hci HEALTH_CHECK_INTERVAL
245
+ Health check interval in seconds for interactive mode (default: 10)
244
246
  --version, -v Show ONVIF CLI version and exit
245
247
 
246
248
  Examples:
@@ -270,7 +272,7 @@ Examples:
270
272
  <summary><b>2. Interactive Shell</b></summary>
271
273
 
272
274
  ```bash
273
- ONVIF Interactive Shell Commands - v0.1.4
275
+ ONVIF Interactive Shell Commands - v0.1.5
274
276
  https://github.com/nirsimetri/onvif-python
275
277
 
276
278
  Basic Commands:
@@ -282,6 +284,7 @@ Basic Commands:
282
284
 
283
285
  Navigation Commands:
284
286
  <service> - Enter service mode (e.g., devicemgmt, media)
287
+ <service> <argument> - Enter service mode with argument (e.g. pullpoint SubscriptionRef=<value>)
285
288
  cd <service> - Enter service mode (alias)
286
289
  ls - List commands/services/methods in grid format
287
290
  up - Exit current service mode (go up one level)
@@ -301,6 +304,7 @@ Method Execution:
301
304
  Data Management:
302
305
  store <name> - Store last result with a name
303
306
  show <name> - Show stored data
307
+ show <name>[0] - Show element at index (for lists)
304
308
  show <name>.attribute - Show specific attribute
305
309
  show - List all stored data
306
310
  rm <name> - Remove stored data by name
@@ -368,6 +372,27 @@ If you omit the username or password, you will be prompted to enter them securel
368
372
  > [!IMPORTANT]
369
373
  > You can see all the other commands available in the interactive shell by trying it out directly. The interactive shell runs periodic background health checks to detect connection loss. It uses silent TCP pings to avoid interrupting your work and will automatically exit if the device is unreachable, similar to an SSH session.
370
374
 
375
+ **Command Chaining with `&&`:**
376
+
377
+ The CLI supports chaining multiple commands in a single line using the `&&` operator, allowing you to execute sequential operations efficiently:
378
+
379
+ ```bash
380
+ # Enter service and execute method in one line
381
+ 192.168.1.17:8000 > media && GetProfiles && store profiles
382
+
383
+ # Chain multiple method calls
384
+ 192.168.1.17:8000 > devicemgmt && GetDeviceInformation && store device_info
385
+
386
+ # Complex workflow
387
+ 192.168.1.17:8000 > media && GetProfiles && store profiles && up && imaging && GetImagingSettings VideoSourceToken=$profiles[0].VideoSourceConfiguration.SourceToken
388
+ ```
389
+
390
+ This feature is particularly useful for:
391
+ - Quick operations without entering service mode
392
+ - Scripting repetitive tasks
393
+ - Testing workflows
394
+ - Automating multi-step procedures
395
+
371
396
  **2. Device Discovery (WS-Discovery)**
372
397
 
373
398
  The CLI includes automatic ONVIF device discovery using the WS-Discovery protocol. This feature allows you to find all ONVIF-compliant devices on your local network without knowing their IP addresses beforehand.
@@ -491,7 +516,7 @@ The `ONVIFClient` class provides various configuration options to customize the
491
516
 
492
517
  | Parameter | Type | Required | Default | Description |
493
518
  |-----------|------|----------|---------|-------------|
494
- | `cache` | `CacheMode` | ❌ No | `CacheMode.ALL` | WSDL caching strategy (see [Cache Modes](#cache-modes) below) |
519
+ | `cache` | `CacheMode` | ❌ No | `CacheMode.ALL` | WSDL caching strategy (see **Cache Modes** below) |
495
520
 
496
521
  </details>
497
522
 
@@ -1,8 +1,8 @@
1
1
  # ONVIF Python
2
2
 
3
- [![License](https://img.shields.io/badge/License-MIT-blue)](https://github.com/nirsimetri/onvif-python?tab=MIT-1-ov-file)
3
+ [![Codacy grade](https://img.shields.io/codacy/grade/bff08a94e4d447b690cea49c6594826d?label=Project%20Quality&logo=codacy)](https://app.codacy.com/gh/nirsimetri/onvif-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
4
4
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/nirsimetri/onvif-python)
5
- [![PyPI](https://img.shields.io/badge/PyPI-0.1.4-orange?logo=archive)](https://pypi.org/project/onvif-python/)
5
+ [![PyPI](https://img.shields.io/badge/PyPI-0.1.5-orange?logo=archive)](https://pypi.org/project/onvif-python/)
6
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)
@@ -174,10 +174,10 @@ This library includes a powerful command-line interface (CLI) for interacting wi
174
174
 
175
175
  ```bash
176
176
  usage: onvif [-h] [--host HOST] [--port PORT] [--username USERNAME] [--password PASSWORD] [--discover] [--timeout TIMEOUT] [--https] [--no-verify]
177
- [--no-patch] [--interactive] [--debug] [--wsdl WSDL] [--cache {all,db,mem,none}] [--version]
177
+ [--no-patch] [--interactive] [--debug] [--wsdl WSDL] [--cache {all,db,mem,none}] [--health-check-interval HEALTH_CHECK_INTERVAL] [--version]
178
178
  [service] [method] [params ...]
179
179
 
180
- ONVIF Terminal Client — v0.1.4
180
+ ONVIF Terminal Client — v0.1.5
181
181
  https://github.com/nirsimetri/onvif-python
182
182
 
183
183
  positional arguments:
@@ -203,6 +203,8 @@ options:
203
203
  --wsdl WSDL Custom WSDL directory path
204
204
  --cache {all,db,mem,none}
205
205
  Caching mode for ONVIFClient (default: all). 'all': memory+disk, 'db': disk-only, 'mem': memory-only, 'none': disabled.
206
+ --health-check-interval HEALTH_CHECK_INTERVAL, -hci HEALTH_CHECK_INTERVAL
207
+ Health check interval in seconds for interactive mode (default: 10)
206
208
  --version, -v Show ONVIF CLI version and exit
207
209
 
208
210
  Examples:
@@ -232,7 +234,7 @@ Examples:
232
234
  <summary><b>2. Interactive Shell</b></summary>
233
235
 
234
236
  ```bash
235
- ONVIF Interactive Shell Commands - v0.1.4
237
+ ONVIF Interactive Shell Commands - v0.1.5
236
238
  https://github.com/nirsimetri/onvif-python
237
239
 
238
240
  Basic Commands:
@@ -244,6 +246,7 @@ Basic Commands:
244
246
 
245
247
  Navigation Commands:
246
248
  <service> - Enter service mode (e.g., devicemgmt, media)
249
+ <service> <argument> - Enter service mode with argument (e.g. pullpoint SubscriptionRef=<value>)
247
250
  cd <service> - Enter service mode (alias)
248
251
  ls - List commands/services/methods in grid format
249
252
  up - Exit current service mode (go up one level)
@@ -263,6 +266,7 @@ Method Execution:
263
266
  Data Management:
264
267
  store <name> - Store last result with a name
265
268
  show <name> - Show stored data
269
+ show <name>[0] - Show element at index (for lists)
266
270
  show <name>.attribute - Show specific attribute
267
271
  show - List all stored data
268
272
  rm <name> - Remove stored data by name
@@ -330,6 +334,27 @@ If you omit the username or password, you will be prompted to enter them securel
330
334
  > [!IMPORTANT]
331
335
  > You can see all the other commands available in the interactive shell by trying it out directly. The interactive shell runs periodic background health checks to detect connection loss. It uses silent TCP pings to avoid interrupting your work and will automatically exit if the device is unreachable, similar to an SSH session.
332
336
 
337
+ **Command Chaining with `&&`:**
338
+
339
+ The CLI supports chaining multiple commands in a single line using the `&&` operator, allowing you to execute sequential operations efficiently:
340
+
341
+ ```bash
342
+ # Enter service and execute method in one line
343
+ 192.168.1.17:8000 > media && GetProfiles && store profiles
344
+
345
+ # Chain multiple method calls
346
+ 192.168.1.17:8000 > devicemgmt && GetDeviceInformation && store device_info
347
+
348
+ # Complex workflow
349
+ 192.168.1.17:8000 > media && GetProfiles && store profiles && up && imaging && GetImagingSettings VideoSourceToken=$profiles[0].VideoSourceConfiguration.SourceToken
350
+ ```
351
+
352
+ This feature is particularly useful for:
353
+ - Quick operations without entering service mode
354
+ - Scripting repetitive tasks
355
+ - Testing workflows
356
+ - Automating multi-step procedures
357
+
333
358
  **2. Device Discovery (WS-Discovery)**
334
359
 
335
360
  The CLI includes automatic ONVIF device discovery using the WS-Discovery protocol. This feature allows you to find all ONVIF-compliant devices on your local network without knowing their IP addresses beforehand.
@@ -453,7 +478,7 @@ The `ONVIFClient` class provides various configuration options to customize the
453
478
 
454
479
  | Parameter | Type | Required | Default | Description |
455
480
  |-----------|------|----------|---------|-------------|
456
- | `cache` | `CacheMode` | ❌ No | `CacheMode.ALL` | WSDL caching strategy (see [Cache Modes](#cache-modes) below) |
481
+ | `cache` | `CacheMode` | ❌ No | `CacheMode.ALL` | WSDL caching strategy (see **Cache Modes** below) |
457
482
 
458
483
  </details>
459
484
 
@@ -18,6 +18,7 @@ from ..utils.exceptions import ONVIFOperationException
18
18
  from .utils import (
19
19
  parse_json_params,
20
20
  get_service_methods,
21
+ get_service_required_args,
21
22
  get_device_available_services,
22
23
  colorize,
23
24
  format_capabilities_as_services,
@@ -172,6 +173,13 @@ class InteractiveShell(cmd.Cmd):
172
173
  if hasattr(args, "no_patch") and args.no_patch:
173
174
  options_info.append(f" ZeepPatcher : {colorize('Disabled', 'red')}")
174
175
 
176
+ if (
177
+ hasattr(args, "health_check_interval") and args.health_check_interval != 10
178
+ ): # 10 is default
179
+ options_info.append(
180
+ f" Health Check : every {colorize(f'{args.health_check_interval}s', 'yellow')}"
181
+ )
182
+
175
183
  # Format options info
176
184
  options_display = ""
177
185
  if options_info:
@@ -183,7 +191,7 @@ class InteractiveShell(cmd.Cmd):
183
191
  " / __ \\/ | / / | / / _/ ____/",
184
192
  " / / / / |/ /| | / // // /_ ",
185
193
  "/ /_/ / /| / | |/ // // __/ ",
186
- "\\____/_/ |_/ |___/___/_/ v0.1.4",
194
+ "\\____/_/ |_/ |___/___/_/ v0.1.5",
187
195
  " ",
188
196
  ]
189
197
 
@@ -223,8 +231,11 @@ class InteractiveShell(cmd.Cmd):
223
231
 
224
232
  def _periodic_health_check(self):
225
233
  """Periodically checks device connection using TCP or TLS depending on mode."""
234
+ # Get health check interval from args, default to 10 seconds
235
+ health_check_interval = getattr(self.args, "health_check_interval", 10)
236
+
226
237
  # Wait before first check to allow intro to finish
227
- self._stop_health_check.wait(10.0)
238
+ self._stop_health_check.wait(health_check_interval)
228
239
 
229
240
  while not self._stop_health_check.is_set():
230
241
  sock = None
@@ -236,6 +247,7 @@ class InteractiveShell(cmd.Cmd):
236
247
  # If HTTPS, perform a short TLS handshake check
237
248
  if getattr(self.args, "https", False):
238
249
  context = ssl.create_default_context()
250
+ context.minimum_version = ssl.TLSVersion.TLSv1_2
239
251
  context.check_hostname = False
240
252
  context.verify_mode = ssl.CERT_NONE
241
253
 
@@ -279,7 +291,7 @@ class InteractiveShell(cmd.Cmd):
279
291
  sock.close()
280
292
 
281
293
  # Wait before next check or stop signal
282
- self._stop_health_check.wait(5.0) # Check every 5 seconds
294
+ self._stop_health_check.wait(health_check_interval)
283
295
 
284
296
  def _handle_connection_error(self, e):
285
297
  """Handle connection errors by notifying the user and exiting."""
@@ -549,8 +561,17 @@ class InteractiveShell(cmd.Cmd):
549
561
 
550
562
  def default(self, line):
551
563
  """Handle unknown commands"""
552
- # Check if it's a service call using device-specific services
553
- if line in get_device_available_services(self.client):
564
+ available_services = get_device_available_services(self.client)
565
+
566
+ # Check if it's a service call (with or without arguments)
567
+ # Extract first word to check if it's a service name
568
+ first_word = line.split()[0] if line.split() else line
569
+
570
+ if first_word in available_services:
571
+ return self.do_enter_service(line)
572
+
573
+ # Check if the whole line (without arguments) is a service
574
+ if line in available_services:
554
575
  return self.do_enter_service(line)
555
576
 
556
577
  # Check if it's a method call in service context
@@ -721,8 +742,13 @@ class InteractiveShell(cmd.Cmd):
721
742
  else:
722
743
  print(f"{colorize('Error:', 'red')} {e}")
723
744
 
724
- def do_enter_service(self, service_name):
725
- """Enter service mode"""
745
+ def do_enter_service(self, line):
746
+ """Enter service mode with optional arguments for services that require them"""
747
+ # Parse service name and arguments
748
+ parts = line.split(None, 1)
749
+ service_name = parts[0] if parts else ""
750
+ args_str = parts[1] if len(parts) > 1 else ""
751
+
726
752
  available_services = get_device_available_services(self.client)
727
753
  if service_name not in available_services:
728
754
  print(f"{colorize('Error:', 'red')} Unknown service '{service_name}'")
@@ -731,7 +757,63 @@ class InteractiveShell(cmd.Cmd):
731
757
  return
732
758
 
733
759
  try:
734
- service = getattr(self.client, service_name)()
760
+ # Check if this service requires arguments
761
+ required_args = get_service_required_args(service_name)
762
+
763
+ if required_args:
764
+ # Service requires arguments
765
+ if not args_str:
766
+ # No arguments provided, show help
767
+ print(
768
+ f"{colorize('Error:', 'red')} Service '{service_name}' requires arguments"
769
+ )
770
+ print(
771
+ f"{colorize('Usage:', 'yellow')} {service_name} {' '.join([f'{arg}=<value>' for arg in required_args])}"
772
+ )
773
+ print(
774
+ f"{colorize('Example:', 'yellow')} {service_name} {required_args[0]}=$subscription"
775
+ )
776
+ return
777
+
778
+ # Parse arguments
779
+ try:
780
+ parsed_args = parse_json_params(args_str)
781
+ except Exception as e:
782
+ print(f"{colorize('Error parsing arguments:', 'red')} {e}")
783
+ return
784
+
785
+ # Check if all required arguments are provided
786
+ missing_args = [arg for arg in required_args if arg not in parsed_args]
787
+ if missing_args:
788
+ print(
789
+ f"{colorize('Error:', 'red')} Missing required arguments: {', '.join(missing_args)}"
790
+ )
791
+ print(
792
+ f"{colorize('Usage:', 'yellow')} {service_name} {' '.join([f'{arg}=<value>' for arg in required_args])}"
793
+ )
794
+ return
795
+
796
+ # Resolve stored references in arguments
797
+ for arg_name in required_args:
798
+ arg_value = parsed_args[arg_name]
799
+ if isinstance(arg_value, str) and arg_value.startswith("$"):
800
+ # Resolve stored reference
801
+ reference = arg_value[1:] # Remove $ prefix
802
+ resolved_value = self._resolve_stored_reference(reference)
803
+ if resolved_value is None:
804
+ print(
805
+ f"{colorize('Error:', 'red')} Could not resolve reference '{arg_value}'"
806
+ )
807
+ return
808
+ parsed_args[arg_name] = resolved_value
809
+
810
+ # Call service method with arguments
811
+ service_method = getattr(self.client, service_name)
812
+ service = service_method(**parsed_args)
813
+ else:
814
+ # Service doesn't require arguments
815
+ service = getattr(self.client, service_name)()
816
+
735
817
  self.current_service = service
736
818
  self.current_service_name = service_name
737
819
  self.update_prompt()
@@ -756,6 +838,10 @@ class InteractiveShell(cmd.Cmd):
756
838
 
757
839
  except Exception as e:
758
840
  print(f"{colorize('Error entering service:', 'red')} {e}")
841
+ if self.args.debug:
842
+ import traceback
843
+
844
+ traceback.print_exc()
759
845
 
760
846
  def do_ls(self, line):
761
847
  """List available commands/services like TAB completion"""
@@ -843,7 +929,7 @@ class InteractiveShell(cmd.Cmd):
843
929
  print(
844
930
  f"{colorize('Error:', 'red')} You must be in a service mode to use 'type'."
845
931
  )
846
- print(f"Enter a service first (e.g., {colorize('d', 'yellow')})")
932
+ print(f"Enter a service first (e.g., {colorize('devicemgmt', 'yellow')})")
847
933
  return
848
934
 
849
935
  if not operation_name:
@@ -1191,6 +1277,14 @@ class InteractiveShell(cmd.Cmd):
1191
1277
  if hasattr(self.args, "no_patch") and self.args.no_patch:
1192
1278
  options_info.append(f" ZeepPatcher : {colorize('Disabled', 'red')}")
1193
1279
 
1280
+ if (
1281
+ hasattr(self.args, "health_check_interval")
1282
+ and self.args.health_check_interval != 10
1283
+ ): # 10 is default
1284
+ options_info.append(
1285
+ f" Health Check : every {colorize(f'{self.args.health_check_interval}s', 'yellow')}"
1286
+ )
1287
+
1194
1288
  # Format options info
1195
1289
  options_display = ""
1196
1290
  if options_info:
@@ -1300,7 +1394,7 @@ class InteractiveShell(cmd.Cmd):
1300
1394
  super().do_help(line)
1301
1395
  else:
1302
1396
  help_text = f"""
1303
- {colorize('ONVIF Interactive Shell Commands - v0.1.4', 'cyan')}\n{colorize('https://github.com/nirsimetri/onvif-python', 'white')}
1397
+ {colorize('ONVIF Interactive Shell Commands - v0.1.5', 'cyan')}\n{colorize('https://github.com/nirsimetri/onvif-python', 'white')}
1304
1398
 
1305
1399
  {colorize('Basic Commands:', 'yellow')}
1306
1400
  capabilities, caps - Show device capabilities
@@ -1311,6 +1405,7 @@ class InteractiveShell(cmd.Cmd):
1311
1405
 
1312
1406
  {colorize('Navigation Commands:', 'yellow')}
1313
1407
  <service> - Enter service mode (e.g., devicemgmt, media)
1408
+ <service> <argument> - Enter service mode with argument (e.g. pullpoint SubscriptionRef=<value>)
1314
1409
  cd <service> - Enter service mode (alias)
1315
1410
  ls - List commands/services/methods in grid format
1316
1411
  up - Exit current service mode (go up one level)
@@ -16,7 +16,7 @@ def create_parser():
16
16
  """Create argument parser for ONVIF CLI"""
17
17
  parser = argparse.ArgumentParser(
18
18
  prog="onvif",
19
- description=f"{colorize('ONVIF Terminal Client', 'yellow')} — v0.1.4\nhttps://github.com/nirsimetri/onvif-python",
19
+ description=f"{colorize('ONVIF Terminal Client', 'yellow')} — v0.1.5\nhttps://github.com/nirsimetri/onvif-python",
20
20
  formatter_class=argparse.RawDescriptionHelpFormatter,
21
21
  epilog=f"""
22
22
  Examples:
@@ -93,6 +93,13 @@ Examples:
93
93
  help="Caching mode for ONVIFClient (default: all). "
94
94
  "'all': memory+disk, 'db': disk-only, 'mem': memory-only, 'none': disabled.",
95
95
  )
96
+ parser.add_argument(
97
+ "--health-check-interval",
98
+ "-hci",
99
+ type=int,
100
+ default=10,
101
+ help="Health check interval in seconds for interactive mode (default: 10)",
102
+ )
96
103
 
97
104
  # Service and method (for direct command execution)
98
105
  parser.add_argument(
@@ -131,7 +138,7 @@ def main():
131
138
 
132
139
  # Show ONVIF CLI version
133
140
  if args.version:
134
- print(colorize("0.1.4", "yellow"))
141
+ print(colorize("0.1.5", "yellow"))
135
142
  sys.exit(0)
136
143
 
137
144
  # Validate arguments early (before discovery)
@@ -151,14 +158,14 @@ def main():
151
158
  devices = discover_devices(timeout=4, prefer_https=args.https)
152
159
 
153
160
  if not devices:
154
- print(f"\n{colorize('No ONVIF devices discovered. Exiting.', 'red')}")
161
+ print(colorize("No ONVIF devices discovered. Exiting.", "red"))
155
162
  sys.exit(1)
156
163
 
157
164
  # Let user select a device
158
165
  selected = select_device_interactive(devices)
159
166
 
160
167
  if selected is None:
161
- print(f"{colorize('Device selection cancelled.', 'cyan')}")
168
+ print(colorize("Device selection cancelled.", "cyan"))
162
169
  sys.exit(0)
163
170
 
164
171
  # Set host, port, and HTTPS from selected device
@@ -523,10 +530,10 @@ def select_device_interactive(devices: list) -> Optional[Tuple[str, int, bool]]:
523
530
  selected.get("use_https", False),
524
531
  )
525
532
  else:
526
- print(f"{colorize('Invalid selection. Please try again.', 'red')}")
533
+ print(colorize("Invalid selection. Please try again.", "red"))
527
534
 
528
535
  except ValueError:
529
- print(f"{colorize('Invalid input. Please enter a number.', 'red')}")
536
+ print(colorize("Invalid input. Please enter a number.", "red"))
530
537
  except (EOFError, KeyboardInterrupt):
531
538
  return None
532
539