onvif-python 0.1.5__tar.gz → 0.1.6__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.5/onvif_python.egg-info → onvif_python-0.1.6}/PKG-INFO +30 -9
  2. {onvif_python-0.1.5 → onvif_python-0.1.6}/README.md +29 -8
  3. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/__init__.py +8 -1
  4. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/cli/interactive.py +23 -32
  5. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/cli/main.py +9 -159
  6. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/utils/__init__.py +2 -0
  7. onvif_python-0.1.6/onvif/utils/discovery.py +297 -0
  8. {onvif_python-0.1.5 → onvif_python-0.1.6/onvif_python.egg-info}/PKG-INFO +30 -9
  9. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif_python.egg-info/SOURCES.txt +1 -0
  10. {onvif_python-0.1.5 → onvif_python-0.1.6}/pyproject.toml +1 -1
  11. {onvif_python-0.1.5 → onvif_python-0.1.6}/LICENSE.md +0 -0
  12. {onvif_python-0.1.5 → onvif_python-0.1.6}/MANIFEST.in +0 -0
  13. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/cli/__init__.py +0 -0
  14. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/cli/__main__.py +0 -0
  15. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/cli/utils.py +0 -0
  16. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/client.py +0 -0
  17. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/operator.py +0 -0
  18. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/__init__.py +0 -0
  19. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/accesscontrol.py +0 -0
  20. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/accessrules.py +0 -0
  21. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/actionengine.py +0 -0
  22. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/analytics/analytics.py +0 -0
  23. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/analytics/ruleengine.py +0 -0
  24. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/analyticsdevice.py +0 -0
  25. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/appmgmt.py +0 -0
  26. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/authenticationbehavior.py +0 -0
  27. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/credential.py +0 -0
  28. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/deviceio.py +0 -0
  29. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/devicemgmt.py +0 -0
  30. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/display.py +0 -0
  31. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/doorcontrol.py +0 -0
  32. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/events/events.py +0 -0
  33. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/events/notification.py +0 -0
  34. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/events/pullpoint.py +0 -0
  35. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/events/subscription.py +0 -0
  36. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/imaging.py +0 -0
  37. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/media.py +0 -0
  38. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/media2.py +0 -0
  39. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/provisioning.py +0 -0
  40. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/ptz.py +0 -0
  41. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/receiver.py +0 -0
  42. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/recording.py +0 -0
  43. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/replay.py +0 -0
  44. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/schedule.py +0 -0
  45. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/search.py +0 -0
  46. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/security/advancedsecurity.py +0 -0
  47. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/security/authorizationserver.py +0 -0
  48. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/security/dot1x.py +0 -0
  49. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/security/jwt.py +0 -0
  50. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/security/keystore.py +0 -0
  51. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/security/mediasigning.py +0 -0
  52. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/security/tlsserver.py +0 -0
  53. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/thermal.py +0 -0
  54. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/uplink.py +0 -0
  55. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/utils/error_handlers.py +0 -0
  56. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/utils/exceptions.py +0 -0
  57. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/utils/wsdl.py +0 -0
  58. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/utils/xml_capture.py +0 -0
  59. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/utils/zeep.py +0 -0
  60. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/accesscontrol/wsdl/index.html +0 -0
  61. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/accessrules/wsdl/accessrules.wsdl +0 -0
  62. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/accessrules/wsdl/index.html +0 -0
  63. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/actionengine.wsdl +0 -0
  64. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/advancedsecurity/wsdl/advancedsecurity.wsdl +0 -0
  65. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/advancedsecurity/wsdl/index.html +0 -0
  66. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/analyticsdevice.wsdl +0 -0
  67. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/appmgmt/wsdl/appmgmt.wsdl +0 -0
  68. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/appmgmt/wsdl/index.html +0 -0
  69. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/authenticationbehavior/wsdl/authenticationbehavior.wsdl +0 -0
  70. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/authenticationbehavior/wsdl/index.html +0 -0
  71. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/credential/wsdl/credential.wsdl +0 -0
  72. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/credential/wsdl/index.html +0 -0
  73. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/device/wsdl/devicemgmt.wsdl +0 -0
  74. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/deviceio.wsdl +0 -0
  75. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/display/display.wsdl +0 -0
  76. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/display/wsdl/index.htm +0 -0
  77. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/display.wsdl +0 -0
  78. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/doorcontrol/wsdl/index.html +0 -0
  79. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/events/wsdl/bw-2-vs-mod.wsdl +0 -0
  80. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/events/wsdl/event-vs.wsdl +0 -0
  81. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/events/wsdl/event.wsdl +0 -0
  82. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/events/wsdl/index.html +0 -0
  83. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/federatedsearch.wsdl +0 -0
  84. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/media/wsdl/media.wsdl +0 -0
  85. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/pacs/accesscontrol.wsdl +0 -0
  86. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/pacs/doorcontrol.wsdl +0 -0
  87. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/pacs/types.xsd +0 -0
  88. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/provisioning/wsdl/provisioning.wsdl +0 -0
  89. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/receiver.wsdl +0 -0
  90. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/recording.wsdl +0 -0
  91. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/replay.wsdl +0 -0
  92. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/schedule/wsdl/index.html +0 -0
  93. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/schedule/wsdl/schedule.wsdl +0 -0
  94. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/schema/common.xsd +0 -0
  95. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/schema/metadatastream.xsd +0 -0
  96. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/schema/onvif.xsd +0 -0
  97. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/search.wsdl +0 -0
  98. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/thermal/wsdl/index.html +0 -0
  99. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/thermal/wsdl/thermal.wsdl +0 -0
  100. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/topics/topicns.xml +0 -0
  101. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/uplink/wsdl/index.html +0 -0
  102. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/uplink/wsdl/uplink.wsdl +0 -0
  103. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/analytics/humanbody.xsd +0 -0
  104. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/analytics/humanface.xsd +0 -0
  105. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/analytics/index.html +0 -0
  106. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/analytics/radiometry.xsd +0 -0
  107. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/analytics/rules.xsd +0 -0
  108. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/analytics/wsdl/analytics.wsdl +0 -0
  109. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/analytics/wsdl/index.html +0 -0
  110. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/imaging/wsdl/imaging.wsdl +0 -0
  111. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/media/wsdl/index.html +0 -0
  112. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/media/wsdl/media.wsdl +0 -0
  113. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/ptz/wsdl/ptz.wsdl +0 -0
  114. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/util/federated_search_documentation.html +0 -0
  115. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/util/fedsearch-docu-viewer.xsl +0 -0
  116. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/util/onvif-wsdl-viewer.xsl +0 -0
  117. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/util/operationIndex.html +0 -0
  118. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif_python.egg-info/dependency_links.txt +0 -0
  119. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif_python.egg-info/entry_points.txt +0 -0
  120. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif_python.egg-info/requires.txt +0 -0
  121. {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif_python.egg-info/top_level.txt +0 -0
  122. {onvif_python-0.1.5 → onvif_python-0.1.6}/setup.cfg +0 -0
  123. {onvif_python-0.1.5 → onvif_python-0.1.6}/setup.py +0 -0
  124. {onvif_python-0.1.5 → onvif_python-0.1.6}/tests/test_client.py +0 -0
  125. {onvif_python-0.1.5 → onvif_python-0.1.6}/tests/test_core.py +0 -0
  126. {onvif_python-0.1.5 → onvif_python-0.1.6}/tests/test_error_handlers.py +0 -0
  127. {onvif_python-0.1.5 → onvif_python-0.1.6}/tests/test_exceptions.py +0 -0
  128. {onvif_python-0.1.5 → onvif_python-0.1.6}/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.5
3
+ Version: 0.1.6
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
@@ -40,7 +40,7 @@ Dynamic: license-file
40
40
 
41
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.5-orange?logo=archive)](https://pypi.org/project/onvif-python/)
43
+ [![PyPI](https://img.shields.io/badge/PyPI-0.1.6-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)
@@ -92,9 +92,29 @@ pip install .
92
92
  > [!TIP]
93
93
  > You can view the complete documentation automatically generated by DeepWiki via the [onvif-python AI Wiki](https://deepwiki.com/nirsimetri/onvif-python) link. We currently do not have an official documentation site. Help us create more examples and helpful documentation by [contributing](https://github.com/nirsimetri/onvif-python?tab=contributing-ov-file).
94
94
 
95
- Below are simple examples to help you get started with the ONVIF Python library. These demonstrate how to connect to an ONVIF-compliant device and retrieve basic device information.
95
+ Below are simple examples to help you get started with the ONVIF Python library. These demonstrate how to discover and connect to ONVIF-compliant devices and retrieve basic device information.
96
96
 
97
- **1. Initialize the ONVIFClient**
97
+ **1. Discover ONVIF Devices (Optional)**
98
+
99
+ Use `ONVIFDiscovery` (applied at [`>=v0.1.6`](https://github.com/nirsimetri/onvif-python/releases/tag/v0.1.6)) to automatically find ONVIF devices on your local network:
100
+
101
+ ```python
102
+ from onvif import ONVIFDiscovery
103
+
104
+ # Create discovery instance
105
+ discovery = ONVIFDiscovery(timeout=5)
106
+
107
+ # Discover devices
108
+ devices = discovery.discover()
109
+
110
+ # Display discovered devices
111
+ for device in devices:
112
+ print(f"Found device at {device['host']}:{device['port']}")
113
+ print(f" Scopes: {device.get('scopes', [])}")
114
+ print(f" XAddrs: {device['xaddrs']}")
115
+ ```
116
+
117
+ **2. Initialize the ONVIFClient**
98
118
 
99
119
  Create an instance of `ONVIFClient` by providing your device's IP address, port, username, and password:
100
120
 
@@ -111,7 +131,7 @@ client = ONVIFClient(
111
131
  )
112
132
  ```
113
133
 
114
- **2. Create Service Instance**
134
+ **3. Create Service Instance**
115
135
 
116
136
  `ONVIFClient` provides several main services that can be accessed via the following methods:
117
137
 
@@ -130,7 +150,7 @@ device = client.devicemgmt() # Device Management (Core)
130
150
  media = client.media() # Media
131
151
  ```
132
152
 
133
- **3. Get Device Information**
153
+ **4. Get Device Information**
134
154
 
135
155
  Retrieve basic information about the device, such as manufacturer, model, firmware version, and serial number using `devicemgmt()` service:
136
156
 
@@ -140,7 +160,7 @@ print(info)
140
160
  # Example output: {'Manufacturer': '..', 'Model': '..', 'FirmwareVersion': '..', 'SerialNumber': '..'}
141
161
  ```
142
162
 
143
- **4. Get RTSP URL**
163
+ **5. Get RTSP URL**
144
164
 
145
165
  Retrieve the RTSP stream URL for live video streaming from the device using `media()` service:
146
166
 
@@ -173,6 +193,7 @@ This library includes a powerful command-line interface (CLI) for interacting wi
173
193
 
174
194
  ### Features
175
195
 
196
+ - **Device Discovery:** Automatic ONVIF device discovery on local network using WS-Discovery protocol.
176
197
  - **Interactive Shell:** A user-friendly shell with tab completion, command history, and colorized output.
177
198
  - **Direct Command Execution:** Run ONVIF commands directly from the terminal for scripting and automation.
178
199
  - **Automatic Discovery:** Automatically detects available services on the device.
@@ -215,7 +236,7 @@ usage: onvif [-h] [--host HOST] [--port PORT] [--username USERNAME] [--password
215
236
  [--no-patch] [--interactive] [--debug] [--wsdl WSDL] [--cache {all,db,mem,none}] [--health-check-interval HEALTH_CHECK_INTERVAL] [--version]
216
237
  [service] [method] [params ...]
217
238
 
218
- ONVIF Terminal Client — v0.1.5
239
+ ONVIF Terminal Client — v0.1.6
219
240
  https://github.com/nirsimetri/onvif-python
220
241
 
221
242
  positional arguments:
@@ -272,7 +293,7 @@ Examples:
272
293
  <summary><b>2. Interactive Shell</b></summary>
273
294
 
274
295
  ```bash
275
- ONVIF Interactive Shell Commands - v0.1.5
296
+ ONVIF Interactive Shell Commands - v0.1.6
276
297
  https://github.com/nirsimetri/onvif-python
277
298
 
278
299
  Basic Commands:
@@ -2,7 +2,7 @@
2
2
 
3
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.5-orange?logo=archive)](https://pypi.org/project/onvif-python/)
5
+ [![PyPI](https://img.shields.io/badge/PyPI-0.1.6-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)
@@ -54,9 +54,29 @@ pip install .
54
54
  > [!TIP]
55
55
  > You can view the complete documentation automatically generated by DeepWiki via the [onvif-python AI Wiki](https://deepwiki.com/nirsimetri/onvif-python) link. We currently do not have an official documentation site. Help us create more examples and helpful documentation by [contributing](https://github.com/nirsimetri/onvif-python?tab=contributing-ov-file).
56
56
 
57
- Below are simple examples to help you get started with the ONVIF Python library. These demonstrate how to connect to an ONVIF-compliant device and retrieve basic device information.
57
+ Below are simple examples to help you get started with the ONVIF Python library. These demonstrate how to discover and connect to ONVIF-compliant devices and retrieve basic device information.
58
58
 
59
- **1. Initialize the ONVIFClient**
59
+ **1. Discover ONVIF Devices (Optional)**
60
+
61
+ Use `ONVIFDiscovery` (applied at [`>=v0.1.6`](https://github.com/nirsimetri/onvif-python/releases/tag/v0.1.6)) to automatically find ONVIF devices on your local network:
62
+
63
+ ```python
64
+ from onvif import ONVIFDiscovery
65
+
66
+ # Create discovery instance
67
+ discovery = ONVIFDiscovery(timeout=5)
68
+
69
+ # Discover devices
70
+ devices = discovery.discover()
71
+
72
+ # Display discovered devices
73
+ for device in devices:
74
+ print(f"Found device at {device['host']}:{device['port']}")
75
+ print(f" Scopes: {device.get('scopes', [])}")
76
+ print(f" XAddrs: {device['xaddrs']}")
77
+ ```
78
+
79
+ **2. Initialize the ONVIFClient**
60
80
 
61
81
  Create an instance of `ONVIFClient` by providing your device's IP address, port, username, and password:
62
82
 
@@ -73,7 +93,7 @@ client = ONVIFClient(
73
93
  )
74
94
  ```
75
95
 
76
- **2. Create Service Instance**
96
+ **3. Create Service Instance**
77
97
 
78
98
  `ONVIFClient` provides several main services that can be accessed via the following methods:
79
99
 
@@ -92,7 +112,7 @@ device = client.devicemgmt() # Device Management (Core)
92
112
  media = client.media() # Media
93
113
  ```
94
114
 
95
- **3. Get Device Information**
115
+ **4. Get Device Information**
96
116
 
97
117
  Retrieve basic information about the device, such as manufacturer, model, firmware version, and serial number using `devicemgmt()` service:
98
118
 
@@ -102,7 +122,7 @@ print(info)
102
122
  # Example output: {'Manufacturer': '..', 'Model': '..', 'FirmwareVersion': '..', 'SerialNumber': '..'}
103
123
  ```
104
124
 
105
- **4. Get RTSP URL**
125
+ **5. Get RTSP URL**
106
126
 
107
127
  Retrieve the RTSP stream URL for live video streaming from the device using `media()` service:
108
128
 
@@ -135,6 +155,7 @@ This library includes a powerful command-line interface (CLI) for interacting wi
135
155
 
136
156
  ### Features
137
157
 
158
+ - **Device Discovery:** Automatic ONVIF device discovery on local network using WS-Discovery protocol.
138
159
  - **Interactive Shell:** A user-friendly shell with tab completion, command history, and colorized output.
139
160
  - **Direct Command Execution:** Run ONVIF commands directly from the terminal for scripting and automation.
140
161
  - **Automatic Discovery:** Automatically detects available services on the device.
@@ -177,7 +198,7 @@ usage: onvif [-h] [--host HOST] [--port PORT] [--username USERNAME] [--password
177
198
  [--no-patch] [--interactive] [--debug] [--wsdl WSDL] [--cache {all,db,mem,none}] [--health-check-interval HEALTH_CHECK_INTERVAL] [--version]
178
199
  [service] [method] [params ...]
179
200
 
180
- ONVIF Terminal Client — v0.1.5
201
+ ONVIF Terminal Client — v0.1.6
181
202
  https://github.com/nirsimetri/onvif-python
182
203
 
183
204
  positional arguments:
@@ -234,7 +255,7 @@ Examples:
234
255
  <summary><b>2. Interactive Shell</b></summary>
235
256
 
236
257
  ```bash
237
- ONVIF Interactive Shell Commands - v0.1.5
258
+ ONVIF Interactive Shell Commands - v0.1.6
238
259
  https://github.com/nirsimetri/onvif-python
239
260
 
240
261
  Basic Commands:
@@ -2,7 +2,13 @@
2
2
 
3
3
  from .client import ONVIFClient
4
4
  from .operator import ONVIFOperator, CacheMode
5
- from .utils import ONVIFWSDL, ONVIFOperationException, ONVIFErrorHandler, ZeepPatcher
5
+ from .utils import (
6
+ ONVIFWSDL,
7
+ ONVIFOperationException,
8
+ ONVIFErrorHandler,
9
+ ZeepPatcher,
10
+ ONVIFDiscovery,
11
+ )
6
12
  from .cli import main as ONVIFCLI
7
13
 
8
14
  __all__ = [
@@ -14,4 +20,5 @@ __all__ = [
14
20
  "ONVIFErrorHandler",
15
21
  "ZeepPatcher",
16
22
  "ONVIFCLI",
23
+ "ONVIFDiscovery",
17
24
  ]
@@ -191,7 +191,7 @@ class InteractiveShell(cmd.Cmd):
191
191
  " / __ \\/ | / / | / / _/ ____/",
192
192
  " / / / / |/ /| | / // // /_ ",
193
193
  "/ /_/ / /| / | |/ // // __/ ",
194
- "\\____/_/ |_/ |___/___/_/ v0.1.5",
194
+ "\\____/_/ |_/ |___/___/_/ v0.1.6",
195
195
  " ",
196
196
  ]
197
197
 
@@ -240,25 +240,27 @@ class InteractiveShell(cmd.Cmd):
240
240
  while not self._stop_health_check.is_set():
241
241
  sock = None
242
242
  try:
243
- sock = socket.create_connection(
244
- (self.args.host, self.args.port), timeout=5.0
245
- )
246
-
247
- # If HTTPS, perform a short TLS handshake check
243
+ # For HTTPS, use ssl.create_connection for proper TLS handling
248
244
  if getattr(self.args, "https", False):
249
245
  context = ssl.create_default_context()
250
246
  context.minimum_version = ssl.TLSVersion.TLSv1_2
251
247
  context.check_hostname = False
252
248
  context.verify_mode = ssl.CERT_NONE
253
249
 
254
- with context.wrap_socket(
255
- sock, server_hostname=self.args.host
256
- ) as ssock:
257
- ssock.settimeout(5)
258
- # Try a minimal handshake
259
- ssock.do_handshake()
250
+ # Use ssl.create_connection instead of wrapping existing socket
251
+ sock = context.wrap_socket(
252
+ socket.socket(socket.AF_INET, socket.SOCK_STREAM),
253
+ server_hostname=self.args.host,
254
+ )
255
+ sock.settimeout(5.0)
256
+ sock.connect((self.args.host, self.args.port))
257
+ else:
258
+ # For HTTP, simple TCP connection check
259
+ sock = socket.create_connection(
260
+ (self.args.host, self.args.port), timeout=5.0
261
+ )
260
262
 
261
- # If we reach here, the connection (and TLS if applicable) succeeded
263
+ # Connection successful
262
264
  pass
263
265
 
264
266
  except (
@@ -368,7 +370,7 @@ class InteractiveShell(cmd.Cmd):
368
370
 
369
371
  return parts
370
372
 
371
- def _display_grid(self, items, force_recalc=False):
373
+ def _display_grid(self, items):
372
374
  """Display items in grid format matching TAB completion (vertical layout)"""
373
375
  if not items:
374
376
  return
@@ -393,7 +395,7 @@ class InteractiveShell(cmd.Cmd):
393
395
 
394
396
  # Column width calculation: use same algorithm as cmd.Cmd.columnize()
395
397
  # Python's columnize uses: maxlen + 2 if maxlen > 0 else 2
396
- col_width = longest_length + 1 if longest_length > 0 else 2
398
+ col_width = longest_length + 2 if longest_length > 0 else 2
397
399
 
398
400
  # Calculate number of columns that fit
399
401
  num_cols = max(1, term_width // col_width)
@@ -661,28 +663,17 @@ class InteractiveShell(cmd.Cmd):
661
663
  print("^C")
662
664
  line = ""
663
665
 
664
- # Handle TAB completion on empty line
665
- if line == "\t":
666
- print() # Newline before grid
667
- if self.current_service:
668
- self._display_grid(get_service_methods(self.current_service))
669
- else:
670
- services = get_device_available_services(self.client)
671
- commands = self.base_commands
672
- self._display_grid(services + commands)
673
- line = "" # Reset line to show prompt again
674
-
675
666
  line = self.precmd(line)
676
667
  stop = self.onecmd(line)
677
668
  stop = self.postcmd(stop, line)
678
669
 
679
- def columnize(self, list, displaywidth=80):
670
+ def columnize(self, items, displaywidth=80):
680
671
  """Override columnize to use grid format for TAB completion"""
681
- if not list:
672
+ if not items:
682
673
  return
683
674
 
684
675
  # Use our grid display for TAB completion with coloring enabled
685
- self._display_grid(list, force_recalc=True)
676
+ self._display_grid(items)
686
677
 
687
678
  def print_topics(self, header, cmds, cmdlen, maxcol):
688
679
  """Override print_topics to use grid format for TAB completion"""
@@ -694,7 +685,7 @@ class InteractiveShell(cmd.Cmd):
694
685
  self.stdout.write("%s\n" % str(header))
695
686
 
696
687
  # Use our grid display for TAB completion with coloring enabled
697
- self._display_grid(cmds, force_recalc=True)
688
+ self._display_grid(cmds)
698
689
 
699
690
  def do_capabilities(self, line):
700
691
  """Show device capabilities in service format"""
@@ -933,7 +924,7 @@ class InteractiveShell(cmd.Cmd):
933
924
  return
934
925
 
935
926
  if not operation_name:
936
- print(f"Usage: {colorize('type <operation_name>', 'yellow')}")
927
+ print("Usage: type <operation_name>")
937
928
  return
938
929
 
939
930
  if not hasattr(self.current_service, operation_name):
@@ -1394,7 +1385,7 @@ class InteractiveShell(cmd.Cmd):
1394
1385
  super().do_help(line)
1395
1386
  else:
1396
1387
  help_text = f"""
1397
- {colorize('ONVIF Interactive Shell Commands - v0.1.5', 'cyan')}\n{colorize('https://github.com/nirsimetri/onvif-python', 'white')}
1388
+ {colorize('ONVIF Interactive Shell Commands - v0.1.6', 'cyan')}\n{colorize('https://github.com/nirsimetri/onvif-python', 'white')}
1398
1389
 
1399
1390
  {colorize('Basic Commands:', 'yellow')}
1400
1391
  capabilities, caps - Show device capabilities
@@ -8,6 +8,7 @@ from typing import Any, Optional, Tuple
8
8
 
9
9
  from ..client import ONVIFClient
10
10
  from ..operator import CacheMode
11
+ from ..utils.discovery import ONVIFDiscovery
11
12
  from .interactive import InteractiveShell
12
13
  from .utils import parse_json_params, colorize
13
14
 
@@ -16,7 +17,7 @@ def create_parser():
16
17
  """Create argument parser for ONVIF CLI"""
17
18
  parser = argparse.ArgumentParser(
18
19
  prog="onvif",
19
- description=f"{colorize('ONVIF Terminal Client', 'yellow')} — v0.1.5\nhttps://github.com/nirsimetri/onvif-python",
20
+ description=f"{colorize('ONVIF Terminal Client', 'yellow')} — v0.1.6\nhttps://github.com/nirsimetri/onvif-python",
20
21
  formatter_class=argparse.RawDescriptionHelpFormatter,
21
22
  epilog=f"""
22
23
  Examples:
@@ -138,7 +139,7 @@ def main():
138
139
 
139
140
  # Show ONVIF CLI version
140
141
  if args.version:
141
- print(colorize("0.1.5", "yellow"))
142
+ print(colorize("0.1.6", "yellow"))
142
143
  sys.exit(0)
143
144
 
144
145
  # Validate arguments early (before discovery)
@@ -287,36 +288,10 @@ def discover_devices(timeout: int = 4, prefer_https: bool = False) -> list:
287
288
  Returns:
288
289
  List of discovered devices with connection info
289
290
  """
290
- import socket
291
- import uuid
292
- import xml.etree.ElementTree as ET
293
- import struct
294
-
295
- WS_DISCOVERY_PORT = 3702
296
- WS_DISCOVERY_ADDRESS_IPv4 = "239.255.255.250"
297
-
298
- # WS-Discovery Probe message
299
- WS_DISCOVERY_PROBE_MESSAGE = (
300
- '<?xml version="1.0" encoding="UTF-8"?>'
301
- '<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" '
302
- 'xmlns:tds="http://www.onvif.org/ver10/device/wsdl" '
303
- 'xmlns:tns="http://schemas.xmlsoap.org/ws/2005/04/discovery" '
304
- 'xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">'
305
- "<soap:Header>"
306
- "<wsa:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action>"
307
- "<wsa:MessageID>urn:uuid:{uuid}</wsa:MessageID>"
308
- "<wsa:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To>"
309
- "</soap:Header>"
310
- "<soap:Body>"
311
- "<tns:Probe>"
312
- "<tns:Types>tds:Device</tns:Types>"
313
- "</tns:Probe>"
314
- "</soap:Body>"
315
- "</soap:Envelope>"
316
- )
317
-
318
- # Get local network interface
291
+ # Get local network interface for display
319
292
  try:
293
+ import socket
294
+
320
295
  s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
321
296
  s.connect(("8.8.8.8", 80))
322
297
  local_ip = s.getsockname()[0]
@@ -328,134 +303,9 @@ def discover_devices(timeout: int = 4, prefer_https: bool = False) -> list:
328
303
  print(f"Network interface: {colorize(local_ip, 'white')}")
329
304
  print(f"Timeout: {timeout}s\n")
330
305
 
331
- probe_uuid = str(uuid.uuid4())
332
- probe = WS_DISCOVERY_PROBE_MESSAGE.format(uuid=probe_uuid)
333
-
334
- responses = []
335
-
336
- try:
337
- sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
338
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
339
- sock.bind((local_ip, 0))
340
- sock.settimeout(timeout)
341
-
342
- ttl = struct.pack("b", 1)
343
- sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
344
-
345
- sock.sendto(
346
- probe.encode("utf-8"), (WS_DISCOVERY_ADDRESS_IPv4, WS_DISCOVERY_PORT)
347
- )
348
-
349
- while True:
350
- try:
351
- data, addr = sock.recvfrom(8192)
352
- response = data.decode("utf-8", errors="ignore").strip()
353
-
354
- if response and len(response) > 10:
355
- if response.startswith("<?xml") or response.startswith("<"):
356
- responses.append({"xml": response, "address": addr[0]})
357
-
358
- except socket.timeout:
359
- break
360
- except Exception:
361
- break
362
-
363
- sock.close()
364
-
365
- except Exception as e:
366
- print(f"{colorize('Error during discovery:', 'red')} {e}")
367
- return []
368
-
369
- # Parse responses
370
- devices = []
371
- namespaces = {
372
- "soap": "http://www.w3.org/2003/05/soap-envelope",
373
- "wsa": "http://schemas.xmlsoap.org/ws/2004/08/addressing",
374
- "wsd": "http://schemas.xmlsoap.org/ws/2005/04/discovery",
375
- "d": "http://schemas.xmlsoap.org/ws/2005/04/discovery",
376
- }
377
-
378
- for resp in responses:
379
- try:
380
- root = ET.fromstring(resp["xml"])
381
- probe_match = root.find(".//d:ProbeMatch", namespaces) or root.find(
382
- ".//wsd:ProbeMatch", namespaces
383
- )
384
-
385
- if probe_match is None:
386
- continue
387
-
388
- device_info = {
389
- "epr": "",
390
- "types": [],
391
- "scopes": [],
392
- "xaddrs": [],
393
- "host": None,
394
- "port": 80,
395
- "use_https": False, # Auto-detect HTTPS from XAddrs
396
- }
397
-
398
- epr = probe_match.find(".//wsa:EndpointReference/wsa:Address", namespaces)
399
- if epr is not None:
400
- device_info["epr"] = epr.text
401
-
402
- types_elem = probe_match.find(".//d:Types", namespaces) or probe_match.find(
403
- ".//wsd:Types", namespaces
404
- )
405
- if types_elem is not None and types_elem.text:
406
- device_info["types"] = types_elem.text.split()
407
-
408
- scopes_elem = probe_match.find(
409
- ".//d:Scopes", namespaces
410
- ) or probe_match.find(".//wsd:Scopes", namespaces)
411
- if scopes_elem is not None and scopes_elem.text:
412
- device_info["scopes"] = scopes_elem.text.split()
413
-
414
- xaddrs_elem = probe_match.find(
415
- ".//d:XAddrs", namespaces
416
- ) or probe_match.find(".//wsd:XAddrs", namespaces)
417
- if xaddrs_elem is not None and xaddrs_elem.text:
418
- device_info["xaddrs"] = xaddrs_elem.text.split()
419
-
420
- # Extract host, port, and protocol from XAddrs
421
- # Use prefer_https parameter to decide which XAddr to use
422
- if device_info["xaddrs"]:
423
- # Select XAddr based on prefer_https flag
424
- if prefer_https:
425
- # Try to find HTTPS XAddr first if prefer_https=True
426
- https_xaddr = next(
427
- (
428
- x
429
- for x in device_info["xaddrs"]
430
- if x.startswith("https://")
431
- ),
432
- None,
433
- )
434
- xaddr = https_xaddr or device_info["xaddrs"][0]
435
- else:
436
- # Use first XAddr (usually HTTP) if prefer_https=False
437
- xaddr = device_info["xaddrs"][0]
438
-
439
- if "://" in xaddr:
440
- # Detect protocol
441
- protocol = xaddr.split("://")[0]
442
- device_info["use_https"] = protocol == "https"
443
-
444
- # Extract host and port
445
- parts = xaddr.split("://")[1].split("/")[0]
446
- if ":" in parts:
447
- device_info["host"] = parts.split(":")[0]
448
- device_info["port"] = int(parts.split(":")[1])
449
- else:
450
- device_info["host"] = parts
451
- # Set default port based on protocol
452
- device_info["port"] = 443 if protocol == "https" else 80
453
-
454
- if device_info["host"]:
455
- devices.append(device_info)
456
-
457
- except Exception:
458
- continue
306
+ # Use ONVIFDiscovery class
307
+ discovery = ONVIFDiscovery(timeout=timeout)
308
+ devices = discovery.discover(prefer_https=prefer_https)
459
309
 
460
310
  return devices
461
311
 
@@ -5,6 +5,7 @@ from .exceptions import ONVIFOperationException
5
5
  from .zeep import ZeepPatcher
6
6
  from .xml_capture import XMLCapturePlugin
7
7
  from .error_handlers import ONVIFErrorHandler
8
+ from .discovery import ONVIFDiscovery
8
9
 
9
10
 
10
11
  __all__ = [
@@ -13,4 +14,5 @@ __all__ = [
13
14
  "ZeepPatcher",
14
15
  "XMLCapturePlugin",
15
16
  "ONVIFErrorHandler",
17
+ "ONVIFDiscovery",
16
18
  ]