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.
- {onvif_python-0.1.5/onvif_python.egg-info → onvif_python-0.1.6}/PKG-INFO +30 -9
- {onvif_python-0.1.5 → onvif_python-0.1.6}/README.md +29 -8
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/__init__.py +8 -1
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/cli/interactive.py +23 -32
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/cli/main.py +9 -159
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/utils/__init__.py +2 -0
- onvif_python-0.1.6/onvif/utils/discovery.py +297 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6/onvif_python.egg-info}/PKG-INFO +30 -9
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif_python.egg-info/SOURCES.txt +1 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/pyproject.toml +1 -1
- {onvif_python-0.1.5 → onvif_python-0.1.6}/LICENSE.md +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/MANIFEST.in +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/cli/__init__.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/cli/__main__.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/cli/utils.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/client.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/operator.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/__init__.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/accesscontrol.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/accessrules.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/actionengine.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/analytics/analytics.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/analytics/ruleengine.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/analyticsdevice.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/appmgmt.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/authenticationbehavior.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/credential.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/deviceio.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/devicemgmt.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/display.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/doorcontrol.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/events/events.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/events/notification.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/events/pullpoint.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/events/subscription.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/imaging.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/media.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/media2.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/provisioning.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/ptz.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/receiver.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/recording.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/replay.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/schedule.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/search.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/security/advancedsecurity.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/security/authorizationserver.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/security/dot1x.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/security/jwt.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/security/keystore.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/security/mediasigning.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/security/tlsserver.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/thermal.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/services/uplink.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/utils/error_handlers.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/utils/exceptions.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/utils/wsdl.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/utils/xml_capture.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/utils/zeep.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/accesscontrol/wsdl/index.html +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/accessrules/wsdl/accessrules.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/accessrules/wsdl/index.html +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/actionengine.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/advancedsecurity/wsdl/advancedsecurity.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/advancedsecurity/wsdl/index.html +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/analyticsdevice.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/appmgmt/wsdl/appmgmt.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/appmgmt/wsdl/index.html +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/authenticationbehavior/wsdl/authenticationbehavior.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/authenticationbehavior/wsdl/index.html +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/credential/wsdl/credential.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/credential/wsdl/index.html +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/device/wsdl/devicemgmt.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/deviceio.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/display/display.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/display/wsdl/index.htm +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/display.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/doorcontrol/wsdl/index.html +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/events/wsdl/bw-2-vs-mod.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/events/wsdl/event-vs.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/events/wsdl/event.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/events/wsdl/index.html +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/federatedsearch.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/media/wsdl/media.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/pacs/accesscontrol.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/pacs/doorcontrol.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/pacs/types.xsd +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/provisioning/wsdl/provisioning.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/receiver.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/recording.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/replay.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/schedule/wsdl/index.html +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/schedule/wsdl/schedule.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/schema/common.xsd +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/schema/metadatastream.xsd +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/schema/onvif.xsd +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/search.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/thermal/wsdl/index.html +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/thermal/wsdl/thermal.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/topics/topicns.xml +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/uplink/wsdl/index.html +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver10/uplink/wsdl/uplink.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/analytics/humanbody.xsd +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/analytics/humanface.xsd +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/analytics/index.html +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/analytics/radiometry.xsd +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/analytics/rules.xsd +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/analytics/wsdl/analytics.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/analytics/wsdl/index.html +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/imaging/wsdl/imaging.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/media/wsdl/index.html +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/media/wsdl/media.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/ptz/wsdl/ptz.wsdl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/util/federated_search_documentation.html +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/util/fedsearch-docu-viewer.xsl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/util/onvif-wsdl-viewer.xsl +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif/wsdl/ver20/util/operationIndex.html +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif_python.egg-info/dependency_links.txt +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif_python.egg-info/entry_points.txt +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif_python.egg-info/requires.txt +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/onvif_python.egg-info/top_level.txt +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/setup.cfg +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/setup.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/tests/test_client.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/tests/test_core.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/tests/test_error_handlers.py +0 -0
- {onvif_python-0.1.5 → onvif_python-0.1.6}/tests/test_exceptions.py +0 -0
- {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.
|
|
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
|
[](https://app.codacy.com/gh/nirsimetri/onvif-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
|
42
42
|
[](https://deepwiki.com/nirsimetri/onvif-python)
|
|
43
|
-
[](https://pypi.org/project/onvif-python/)
|
|
44
44
|
[](https://clickpy.clickhouse.com/dashboard/onvif-python)
|
|
45
45
|
<br>
|
|
46
46
|
[](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
|
|
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.
|
|
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
|
-
**
|
|
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
|
-
**
|
|
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
|
-
**
|
|
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.
|
|
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.
|
|
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
|
[](https://app.codacy.com/gh/nirsimetri/onvif-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
|
4
4
|
[](https://deepwiki.com/nirsimetri/onvif-python)
|
|
5
|
-
[](https://pypi.org/project/onvif-python/)
|
|
6
6
|
[](https://clickpy.clickhouse.com/dashboard/onvif-python)
|
|
7
7
|
<br>
|
|
8
8
|
[](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
|
|
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.
|
|
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
|
-
**
|
|
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
|
-
**
|
|
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
|
-
**
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
#
|
|
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
|
|
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 +
|
|
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,
|
|
670
|
+
def columnize(self, items, displaywidth=80):
|
|
680
671
|
"""Override columnize to use grid format for TAB completion"""
|
|
681
|
-
if not
|
|
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(
|
|
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
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
332
|
-
|
|
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
|
]
|