ptzsim 0.2.0__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.
- ptzsim-0.2.0/LICENSE +21 -0
- ptzsim-0.2.0/MANIFEST.in +4 -0
- ptzsim-0.2.0/PKG-INFO +420 -0
- ptzsim-0.2.0/README.md +404 -0
- ptzsim-0.2.0/ptzsim/__init__.py +6 -0
- ptzsim-0.2.0/ptzsim/__main__.py +8 -0
- ptzsim-0.2.0/ptzsim/cli.py +221 -0
- ptzsim-0.2.0/ptzsim/display.py +102 -0
- ptzsim-0.2.0/ptzsim/ndi.py +285 -0
- ptzsim-0.2.0/ptzsim/onvif.py +1410 -0
- ptzsim-0.2.0/ptzsim/state.py +250 -0
- ptzsim-0.2.0/ptzsim/transports.py +223 -0
- ptzsim-0.2.0/ptzsim/tui.py +315 -0
- ptzsim-0.2.0/ptzsim/viewfinder.py +96 -0
- ptzsim-0.2.0/ptzsim/visca.py +499 -0
- ptzsim-0.2.0/ptzsim.egg-info/PKG-INFO +420 -0
- ptzsim-0.2.0/ptzsim.egg-info/SOURCES.txt +32 -0
- ptzsim-0.2.0/ptzsim.egg-info/dependency_links.txt +1 -0
- ptzsim-0.2.0/ptzsim.egg-info/entry_points.txt +2 -0
- ptzsim-0.2.0/ptzsim.egg-info/requires.txt +4 -0
- ptzsim-0.2.0/ptzsim.egg-info/top_level.txt +1 -0
- ptzsim-0.2.0/pyproject.toml +38 -0
- ptzsim-0.2.0/scripts/build-release.sh +22 -0
- ptzsim-0.2.0/setup.cfg +7 -0
- ptzsim-0.2.0/tests/test_conventional_commits.py +48 -0
- ptzsim-0.2.0/tests/test_ndi.py +179 -0
- ptzsim-0.2.0/tests/test_onvif.py +347 -0
- ptzsim-0.2.0/tests/test_onvif_standard.py +445 -0
- ptzsim-0.2.0/tests/test_release.py +45 -0
- ptzsim-0.2.0/tests/test_transports.py +181 -0
- ptzsim-0.2.0/tests/test_tui.py +116 -0
- ptzsim-0.2.0/tests/test_viewfinder.py +74 -0
- ptzsim-0.2.0/tests/test_visca.py +247 -0
ptzsim-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stephan Strittmatter
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
ptzsim-0.2.0/MANIFEST.in
ADDED
ptzsim-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ptzsim
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: CLI simulator for testing ONVIF, VISCA and NDI PTZ clients
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/stritti/ptz-simulator
|
|
7
|
+
Project-URL: Documentation, https://stritti.github.io/ptz-simulator/
|
|
8
|
+
Project-URL: Changelog, https://github.com/stritti/ptz-simulator/blob/main/CHANGELOG.md
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Provides-Extra: ndi
|
|
13
|
+
Requires-Dist: ndi-python; extra == "ndi"
|
|
14
|
+
Requires-Dist: numpy; extra == "ndi"
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
<!--
|
|
18
|
+
SPDX-FileCopyrightText: 2026 Stephan Strittmatter
|
|
19
|
+
SPDX-License-Identifier: MIT
|
|
20
|
+
-->
|
|
21
|
+
|
|
22
|
+
# ptzsim – command-line PTZ camera simulator
|
|
23
|
+
|
|
24
|
+
Simulates a PTZ camera controlled via **ONVIF**, **VISCA** and **NDI**, so PTZ
|
|
25
|
+
clients can be tested without a real camera. All protocols control the same
|
|
26
|
+
camera. The simulator keeps the camera state (pan, tilt, zoom, focus, presets,
|
|
27
|
+
power) and shows it live in the terminal.
|
|
28
|
+
|
|
29
|
+
ONVIF and VISCA need nothing but Python ≥ 3.8 (standard library only). NDI
|
|
30
|
+
additionally needs the `ndi` extra (see [NDI](#ndi)). The project is managed
|
|
31
|
+
with [uv](https://docs.astral.sh/uv/): `uv sync` creates `.venv` with the dev
|
|
32
|
+
dependencies.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
ptzsim needs only Python ≥ 3.8. Pick one:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Single file, nothing to install: download and run with any Python 3
|
|
40
|
+
curl -LO https://github.com/stritti/ptz-simulator/releases/latest/download/ptzsim.pyz
|
|
41
|
+
python3 ptzsim.pyz serve --onvif # Windows: py ptzsim.pyz serve --onvif
|
|
42
|
+
|
|
43
|
+
# As a command on the PATH (with uv or pipx)
|
|
44
|
+
uv tool install git+https://github.com/stritti/ptz-simulator
|
|
45
|
+
pipx install git+https://github.com/stritti/ptz-simulator
|
|
46
|
+
ptzsim serve --onvif
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Wheel, sdist and `ptzsim.pyz` of every version are attached to the
|
|
50
|
+
[GitHub releases](https://github.com/stritti/ptz-simulator/releases). For NDI,
|
|
51
|
+
install the extra: `uv tool install "ptzsim[ndi] @ git+https://github.com/stritti/ptz-simulator"`.
|
|
52
|
+
|
|
53
|
+
## Supported transports
|
|
54
|
+
|
|
55
|
+
| Option | Protocol | Default port |
|
|
56
|
+
|------------------|------------------------------------------------------|--------------|
|
|
57
|
+
| `--onvif [PORT]` | ONVIF (SOAP over HTTP) + WS-Discovery on UDP 3702 | 8080 |
|
|
58
|
+
| `--sony [PORT]` | Sony VISCA over IP (UDP, 8-byte header) | 52381 |
|
|
59
|
+
| `--tcp [PORT]` | raw VISCA over TCP (e.g. PTZOptics) | 5678 |
|
|
60
|
+
| `--udp [PORT]` | raw VISCA over UDP | 1259 |
|
|
61
|
+
| `--pty [LINK]` | serial VISCA via a pseudo terminal (RS-232/422 stand-in) | – |
|
|
62
|
+
| `--ndi` | NDI source with PTZ control and a test picture | NDI default (mDNS, TCP 5960+) |
|
|
63
|
+
|
|
64
|
+
Transports can be combined; they all control the same camera. Without any option
|
|
65
|
+
`--sony` is started.
|
|
66
|
+
|
|
67
|
+
## Running
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
cd ptz-simulator
|
|
71
|
+
uv run ptzsim serve --onvif --onvif-user admin --onvif-password secret # ONVIF with authentication
|
|
72
|
+
uv run ptzsim serve --onvif --sony # ONVIF and VISCA at the same time
|
|
73
|
+
uv run ptzsim serve # Sony VISCA/IP on UDP 52381
|
|
74
|
+
uv run ptzsim serve --tcp --sony # TCP 5678 and UDP 52381
|
|
75
|
+
uv run ptzsim serve --pty /tmp/ttyPTZ # serial port at /tmp/ttyPTZ
|
|
76
|
+
uv run --extra ndi ptzsim serve --ndi --name "Camera 1" # NDI source "HOSTNAME (Camera 1)"
|
|
77
|
+
uv run ptzsim serve --view-only # dashboard without keyboard control
|
|
78
|
+
uv run ptzsim serve --no-tui -v # line log instead of the dashboard
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Without uv, `python3 -m ptzsim serve ...` works as well (standard library only).
|
|
82
|
+
|
|
83
|
+
More options: `--onvif-auth both|digest|wsse` (HTTP digest and/or WS-UsernameToken,
|
|
84
|
+
default both), `--onvif-no-absolute` (camera without `AbsoluteMove`, presets only via
|
|
85
|
+
`SetPreset`/`GotoPreset`), `--address 1..7` (VISCA address), `--instant` (jump to target
|
|
86
|
+
positions instead of moving at motor speed), `--host` (bind address). In
|
|
87
|
+
dashboard mode, log messages go to `ptzsim.log`.
|
|
88
|
+
|
|
89
|
+
## Keyboard control (TUI)
|
|
90
|
+
|
|
91
|
+
In a terminal, `serve` shows the live dashboard and lets you drive the camera
|
|
92
|
+
from the keyboard. Your actions show up in the command log as `TUI`, next to the
|
|
93
|
+
commands from ONVIF, VISCA or NDI clients, and they change the same camera, so the
|
|
94
|
+
camera state is shared with every connected client. ONVIF clients can read the
|
|
95
|
+
current position using `GetStatus`.
|
|
96
|
+
|
|
97
|
+
| Key | Action |
|
|
98
|
+
|-----|--------|
|
|
99
|
+
| arrows or `W` `A` `S` `D` | pan/tilt while the key is held (stops ~0.35 s after the last key repeat) |
|
|
100
|
+
| `+` / `-` | zoom tele / wide while the key is held |
|
|
101
|
+
| space | stop all movement |
|
|
102
|
+
| `[` / `]` | jog speed down / up (1–8) |
|
|
103
|
+
| `h` | home position |
|
|
104
|
+
| `1`–`9` | recall preset |
|
|
105
|
+
| `P` then `1`–`9` | save the current position as preset |
|
|
106
|
+
| `C` then `1`–`9` | clear preset |
|
|
107
|
+
| `f` | toggle autofocus |
|
|
108
|
+
| `b` | one-push white balance (sets gains to neutral, locks manual) |
|
|
109
|
+
| `o` | power on / standby |
|
|
110
|
+
| `q` | quit |
|
|
111
|
+
|
|
112
|
+
Next to the dashboard (or above it in narrow terminals) the TUI shows the
|
|
113
|
+
**camera picture** at the current position: the same virtual scene as the NDI test
|
|
114
|
+
picture, drawn with text characters. The horizon is at 0° tilt, the ground has a
|
|
115
|
+
5° checker pattern, a post with its angle stands every 45° of pan, and a 1° grid
|
|
116
|
+
appears when zoomed in. The field of view is 60° at 1× and narrows with the zoom
|
|
117
|
+
(3° at 20×); the caption shows pan, tilt and zoom. Moves from any client show up
|
|
118
|
+
in the picture as they happen.
|
|
119
|
+
|
|
120
|
+
The keyboard UI uses `curses` and needs a terminal, so it is not available on
|
|
121
|
+
native Windows Python (use WSL) or when stdin/stdout are redirected; `serve` then
|
|
122
|
+
falls back to the read-only dashboard.
|
|
123
|
+
|
|
124
|
+
## Testing ONVIF clients
|
|
125
|
+
|
|
126
|
+
1. Start the simulator in the same network as the client:
|
|
127
|
+
`uv run ptzsim serve --onvif --onvif-user admin --onvif-password secret --name "Camera 1"`.
|
|
128
|
+
For several cameras, start several instances with their own port and name
|
|
129
|
+
(`--onvif 8081 --name "Camera 2"`); all of them answer WS-Discovery.
|
|
130
|
+
2. Search for cameras with an ONVIF client. The simulator answers the
|
|
131
|
+
WS-Discovery probe (`dn:NetworkVideoTransmitter`) with the XAddr
|
|
132
|
+
`http://<ip>:8080/onvif/device_service` and the name in the scope
|
|
133
|
+
`onvif://www.onvif.org/name/<name>`. Alternatively enter the simulator's IP
|
|
134
|
+
and port directly. Use the user and password from above.
|
|
135
|
+
3. Use the client to pan, tilt, zoom, save and recall presets. The dashboard shows
|
|
136
|
+
position, motion, presets and the latest commands.
|
|
137
|
+
|
|
138
|
+
If a client runs in Docker/WSL and multicast discovery fails, enter the
|
|
139
|
+
simulator's address directly. Set `--advertise-host <IP>` if the address
|
|
140
|
+
advertised by discovery is not reachable by the client. Port 80 (the ONVIF
|
|
141
|
+
default) needs root privileges, so 8080 is the default.
|
|
142
|
+
|
|
143
|
+
## ONVIF scope
|
|
144
|
+
|
|
145
|
+
- **Device**: `GetCapabilities`, `GetServices`, `GetSystemDateAndTime`, `GetDeviceInformation`,
|
|
146
|
+
`GetWsdlUrl`, `GetEndpointReference`, `GetUsers` (the `--onvif-user` as Administrator).
|
|
147
|
+
Scopes: `GetScopes`, `SetScopes`, `AddScopes`, `RemoveScopes` (type, profile and hardware
|
|
148
|
+
are fixed, name and location configurable; WS-Discovery announces the current list),
|
|
149
|
+
`GetDiscoveryMode`/`SetDiscoveryMode` (`NonDiscoverable` stops Hello and ProbeMatches).
|
|
150
|
+
Network: `GetHostname`/`SetHostname`, `GetNetworkInterfaces` (`eth0` with the address the
|
|
151
|
+
client connected to), `GetDNS`, `GetNTP`, `GetNetworkProtocols` (HTTP), `GetNetworkDefaultGateway`.
|
|
152
|
+
`SetNetworkInterfaces`, `SetDNS`, `SetNTP`, `SetNetworkProtocols`, `SetNetworkDefaultGateway`,
|
|
153
|
+
`SetSystemDateAndTime`, `SetSystemFactoryDefault` and `SystemReboot` are accepted but change
|
|
154
|
+
nothing. User management (`CreateUsers`, `SetUser`, `DeleteUsers`) is not supported.
|
|
155
|
+
- **Media**: one fixed profile `Profile_1` with a video source configuration
|
|
156
|
+
(`VideoSourceConfig_1`, 640×360), an H.264 encoder configuration (`VideoEncoderConfig_1`) and
|
|
157
|
+
the PTZ configuration (including `DefaultContinuousZoomVelocitySpace`). `GetProfiles`/`GetProfile`,
|
|
158
|
+
`Get(Compatible)VideoSourceConfiguration(s)`, `GetVideoSourceConfigurationOptions`,
|
|
159
|
+
`SetVideoSourceConfiguration`, `Get(Compatible)VideoEncoderConfiguration(s)`,
|
|
160
|
+
`GetVideoEncoderConfigurationOptions`, `SetVideoEncoderConfiguration` (stores resolution
|
|
161
|
+
640×360/320×180, quality 0…10, frame rate 1…25, bitrate, GOV length, H.264 profile; other
|
|
162
|
+
values give `ter:ConfigModify`), `GetGuaranteedNumberOfVideoEncoderInstances`,
|
|
163
|
+
`Add`/`Remove` `VideoSource`/`VideoEncoder`/`PTZ` `Configuration`. `GetStreamUri` returns
|
|
164
|
+
`--rtsp-uri` (default `rtsp://<host>:554/stream`); ptzsim does not stream video itself, and
|
|
165
|
+
without a source and encoder in the profile it fails with `ter:IncompleteConfiguration`.
|
|
166
|
+
After `RemovePTZConfiguration`, PTZ moves, status and presets fail with `ter:NoPTZProfile`.
|
|
167
|
+
`GetSnapshotUri` is not supported (`SnapshotUri="false"`).
|
|
168
|
+
- **PTZ**: `ContinuousMove` (with `Timeout`, default 5 s; every new call extends it),
|
|
169
|
+
`Stop`, `AbsoluteMove`, `RelativeMove`, `GetStatus`, `GotoHomePosition`, `SetHomePosition`,
|
|
170
|
+
`GetPresets`, `SetPreset`, `GotoPreset`, `RemovePreset`, `GetNodes`, `GetConfigurations`,
|
|
171
|
+
`GetConfigurationOptions`, `GetCompatibleConfigurations`
|
|
172
|
+
- **Authentication**: HTTP digest and/or WS-Security UsernameToken with `PasswordDigest`
|
|
173
|
+
(or `PasswordText`), only when `--onvif-user` is set. Missing or wrong digest credentials
|
|
174
|
+
get HTTP 401 with a new challenge. `GetCapabilities`, `GetServices`, `GetServiceCapabilities`,
|
|
175
|
+
`GetWsdlUrl`, `GetHostname`, `GetEndpointReference` and `GetSystemDateAndTime` work without
|
|
176
|
+
login, as on real cameras.
|
|
177
|
+
- **Imaging** (white balance only): `GetImagingSettings`, `SetImagingSettings` and `GetOptions`
|
|
178
|
+
for video source `VideoSource_1`, mode `AUTO`/`MANUAL`, gains 0…255/128 (1.0 = VISCA 0x80). Media
|
|
179
|
+
`GetVideoSources` lists `VideoSource_1` (640×360, 25 fps). `GetMoveOptions` and `GetStatus`
|
|
180
|
+
answer empty (no remote focus); imaging `Move`/`Stop` return `ter:ActionNotSupported` and
|
|
181
|
+
never touch the PTZ unit. ONVIF has no
|
|
182
|
+
one-push trigger, so one push is only available via VISCA, NDI and the `b` key.
|
|
183
|
+
- Not supported: Events, Media2. These calls return a SOAP fault
|
|
184
|
+
(`ter:ActionNotSupported`); Events are also missing from `GetCapabilities`.
|
|
185
|
+
|
|
186
|
+
Coordinates use the generic ONVIF spaces: pan −1…1 (≈ ±170°), tilt −1…1
|
|
187
|
+
(−1 = −30°, 0 = level, 1 = +90°), zoom 0…1 (1×…20×), velocities −1…1.
|
|
188
|
+
VISCA and ONVIF presets are the same. The ONVIF token and the NDI index are the VISCA wire value (0–63); the dashboard and the keys 1–9 show it as 1–64. Recall uses the preset speed setting; ONVIF `GotoPreset` with a `Speed` element overrides it.
|
|
189
|
+
|
|
190
|
+
## Testing VISCA clients
|
|
191
|
+
|
|
192
|
+
Start the simulator with `--sony`, `--tcp`, `--udp` or `--pty /tmp/ttyPTZ` and
|
|
193
|
+
enter the IP and port in the client (52381 for Sony VISCA/IP, 5678 for
|
|
194
|
+
VISCA/TCP), or `/tmp/ttyPTZ` as the serial port.
|
|
195
|
+
|
|
196
|
+
## NDI
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
uv run --extra ndi ptzsim serve --ndi --name "Camera 1"
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The simulator shows up as the NDI source `HOSTNAME (Camera 1)` and sends a test
|
|
203
|
+
picture (640×360, `--ndi-fps`, default 25): a grid with sky, ground, horizon and
|
|
204
|
+
red posts every 45° that moves with pan, tilt and zoom. It announces PTZ support
|
|
205
|
+
(`<ndi_capabilities ntk_ptz="true" .../>` with pan/tilt, zoom, focus, autofocus, white balance and preset speed; NDI preset indexes 0–99, of which the camera has 64), so NDI Studio Monitor, vMix, OBS
|
|
206
|
+
(DistroAV) etc. offer PTZ control.
|
|
207
|
+
|
|
208
|
+
It executes the NDI PTZ commands that receivers send as metadata:
|
|
209
|
+
|
|
210
|
+
| Metadata | Effect | Value range (NDI SDK) |
|
|
211
|
+
|---|---|---|
|
|
212
|
+
| `ntk_ptz_pan_tilt pan tilt` | absolute pan/tilt | −1 left … +1 right, −1 bottom … +1 top |
|
|
213
|
+
| `ntk_ptz_pan_tilt_speed pan_speed tilt_speed` | move pan/tilt | +1 = moving left, +1 = moving up |
|
|
214
|
+
| `ntk_ptz_zoom zoom` | absolute zoom | 0 = zoomed in … 1 = zoomed out |
|
|
215
|
+
| `ntk_ptz_zoom_speed zoom_speed` | move zoom | +1 = inwards, −1 = outwards |
|
|
216
|
+
| `ntk_ptz_focus mode="auto"` / `mode="manual" distance` | focus | 0 = infinity … 1 = close |
|
|
217
|
+
| `ntk_ptz_focus_speed distance` | move focus | −1 … +1 |
|
|
218
|
+
| `ntk_ptz_store_preset index` / `ntk_ptz_recall_preset index speed` | presets | 0…99, speed 0…1 |
|
|
219
|
+
| `ntk_ptz_white_balance mode="auto"` / `mode="one_shot"` / `mode="manual" red blue` | white balance | 0…1 for red/blue, 0.5 neutral; `indoor`/`outdoor` fixed modes |
|
|
220
|
+
| `ntk_ptz_exposure`, `ntk_ptz_iris` | accepted and logged, not simulated | – |
|
|
221
|
+
|
|
222
|
+
NDI presets are the same as for VISCA and ONVIF. `ndi-python` ships the NDI
|
|
223
|
+
runtime, which is subject to the NDI license terms (NDI® is a trademark of Vizrt
|
|
224
|
+
NDI AB). On Linux, NDI discovery uses mDNS. If a receiver does not find the
|
|
225
|
+
source, add the simulator's IP as an extra source or in NDI Access Manager.
|
|
226
|
+
|
|
227
|
+
## Sending commands by hand
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
uv run ptzsim send "81 01 06 04 FF" # home (Sony VISCA/IP)
|
|
231
|
+
uv run ptzsim send --proto tcp "81 01 04 3F 01 05 FF" # store preset 5
|
|
232
|
+
uv run ptzsim send "81 09 06 12 FF" # query position
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Supported VISCA commands
|
|
236
|
+
|
|
237
|
+
VISCA commands follow `docs/visca-interface-api-description-en-US-266656.pdf` (AXIS example). The Sony list in `docs/VISCA-Command-List-Version-2.00.pdf` uses other white balance codes (`04` = ATW).
|
|
238
|
+
|
|
239
|
+
Commands (reply: ACK `90 41 FF` + completion `90 51 FF`; for absolute moves the
|
|
240
|
+
completion is sent once the position has been reached):
|
|
241
|
+
|
|
242
|
+
| Command | Bytes |
|
|
243
|
+
|---|---|
|
|
244
|
+
| Power on / standby | `8x 01 04 00 02/03 FF` |
|
|
245
|
+
| Zoom stop / tele / wide / variable | `8x 01 04 07 00/02/03/2p/3p FF` |
|
|
246
|
+
| Zoom direct | `8x 01 04 47 0p 0q 0r 0s FF` |
|
|
247
|
+
| Focus stop / far / near / variable | `8x 01 04 08 00/02/03/2p/3p FF` |
|
|
248
|
+
| Focus direct | `8x 01 04 48 0p 0q 0r 0s FF` |
|
|
249
|
+
| Auto focus / manual / toggle | `8x 01 04 38 02/03/10 FF` |
|
|
250
|
+
| One push AF | `8x 01 04 18 01 FF` |
|
|
251
|
+
| White balance mode (00 auto, 01 indoor, 02 outdoor, 04 auto outdoor, 05 manual) | `8x 01 04 35 0p FF` |
|
|
252
|
+
| Red / blue gain, relative and absolute | `8x 01 04 03/04 0p FF`, `8x 01 04 43/44 00 00 0p 0q FF` |
|
|
253
|
+
| One push white balance | `8x 01 04 10 05 FF` |
|
|
254
|
+
| Preset reset / set / recall (pp 00–63, shown as 1–64) | `8x 01 04 3F 00/01/02 pp FF` |
|
|
255
|
+
| Preset speed select (1 separate, 2 common) | `8x 01 7E 04 1B 0p FF` |
|
|
256
|
+
| Common preset speed (01–32 hex) | `8x 01 7E 04 1C 0p 0p FF` |
|
|
257
|
+
| Separate preset speed (01–32 hex) | `8x 01 7E 01 0B pp qq FF` |
|
|
258
|
+
| Pan/tilt drive (up, down, left, right, diagonal, stop) | `8x 01 06 01 VV WW XX YY FF` |
|
|
259
|
+
| Pan/tilt absolute / relative | `8x 01 06 02/03 VV WW 0Y0Y0Y0Y 0Z0Z0Z0Z FF` |
|
|
260
|
+
| Home / reset | `8x 01 06 04 FF` / `8x 01 06 05 FF` |
|
|
261
|
+
| Command cancel | `8x 2z FF` (z = socket) |
|
|
262
|
+
| IF_Clear | `8x 01 00 01 FF`, `88 01 00 01 FF` |
|
|
263
|
+
| Address set | `88 30 01 FF` |
|
|
264
|
+
|
|
265
|
+
Inquiries (reply `90 50 ... FF`): power `09 04 00`, zoom `09 04 47`,
|
|
266
|
+
focus `09 04 48`, focus mode `09 04 38`, white balance mode `09 04 35`, preset speed `09 7E 04 1B`, `09 7E 04 1C`, `09 7E 01 0B`,
|
|
267
|
+
pan/tilt position `09 06 12`, max speed `09 06 11`,
|
|
268
|
+
pan/tilt status `09 06 10`, version `09 00 02`.
|
|
269
|
+
|
|
270
|
+
Errors: syntax error `90 60 02 FF`, not executable `90 61 41 FF` (e.g. standby
|
|
271
|
+
or preset not set). `IF_Clear` answers `90 50 FF` without ACK. Pan/tilt status
|
|
272
|
+
reports "initialization completes" (`20 00`) plus "operating" (`04 00`) or
|
|
273
|
+
"operations complete" (`08 00`) and the end-stop bits. In absolute/relative moves
|
|
274
|
+
tilt speed `00` means "same as pan", and both 4- and 5-digit coordinates are
|
|
275
|
+
accepted (classic BRC/EVI/Axis and newer Sony models).
|
|
276
|
+
|
|
277
|
+
Sony VISCA/IP (per the Sony VISCA command list, chapter VISCA over IP): the control
|
|
278
|
+
command `RESET` (`02 00`) is acknowledged with `02 01 ... 01` and resets the
|
|
279
|
+
sequence number; replies carry the sequence number of the request. A repeated
|
|
280
|
+
sequence number is not executed again and answered with control reply `0F 01`, a
|
|
281
|
+
payload type that does not match the packet (inquiry sent as command or vice versa)
|
|
282
|
+
with `0F 02`. The camera address is fixed to 1 over IP: any address is accepted and
|
|
283
|
+
replies always come from `90`.
|
|
284
|
+
|
|
285
|
+
**VISCA is not one standard.** The transport (VISCA over IP, replies, error codes,
|
|
286
|
+
sockets) is common to Sony cameras, the command set differs per model. The simulator
|
|
287
|
+
follows the classic 4-digit Sony BRC/EVI and Axis command set; check the command
|
|
288
|
+
list of the target camera before relying on model-specific commands (e.g. white
|
|
289
|
+
balance codes, speed ranges, 5-digit positions on newer Sony models). Simplification:
|
|
290
|
+
only one command socket, so "command buffer full" never occurs.
|
|
291
|
+
|
|
292
|
+
Value ranges: pan −2448…2448 (≈ ±170°), tilt −432…1296 (≈ −30…+90°),
|
|
293
|
+
zoom 0x0000…0x4000 (1×…20×), focus 0x1000…0xF000, pan speed 1…0x18,
|
|
294
|
+
tilt speed 1…0x17, presets 0…63 (shown as 1…64).
|
|
295
|
+
|
|
296
|
+
## Tests
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
uv run pytest
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
The tests check VISCA, ONVIF and NDI directly and run end to end over real
|
|
303
|
+
TCP/UDP sockets, Sony VISCA/IP, HTTP, WS-Discovery and a pseudo terminal. With
|
|
304
|
+
`uv run --extra ndi pytest`, a real NDI round trip runs as well (a receiver calls
|
|
305
|
+
`NDIlib_recv_ptz_*`, the simulator executes the commands).
|
|
306
|
+
|
|
307
|
+
There is also a test against the real ONVIF client library
|
|
308
|
+
(`onvif-zeep-async`, same WSDLs as `onvif-zeep`):
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
PTZSIM_ONVIF_CLIENT=1 uv run --group onvif-client pytest
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## ONVIF conformance
|
|
315
|
+
|
|
316
|
+
Checked against the ONVIF Core Specification 26.06 (`docs/ONVIF-Core-Specification.pdf`).
|
|
317
|
+
The PTZ Service Specification is not in `docs/`, so the PTZ fault subcodes are not
|
|
318
|
+
verified against it. Responses are not validated against the XSD schemas automatically.
|
|
319
|
+
|
|
320
|
+
- PTZ service: all mandatory operations (GetNodes, GetNode, GetConfigurations,
|
|
321
|
+
GetConfiguration, GetConfigurationOptions, SetConfiguration, ContinuousMove, Stop,
|
|
322
|
+
GetStatus, GetServiceCapabilities), presets, home position, AbsoluteMove and
|
|
323
|
+
RelativeMove. Faults use the spec's chains, e.g. `env:Sender / ter:InvalidArgVal /
|
|
324
|
+
ter:NoProfile`; out-of-range velocity, timeout or position is rejected, SetPreset
|
|
325
|
+
fails while the camera moves (`ter:MovingPTZ`). An omitted axis in ContinuousMove
|
|
326
|
+
keeps moving, a zero velocity stops it.
|
|
327
|
+
- Authentication: HTTP digest (RFC 2617, answers 401 with a challenge, also for wrong
|
|
328
|
+
credentials as in Core spec figure 4) and
|
|
329
|
+
WS-UsernameToken with nonce and timestamp; `--onvif-auth` limits it to one of them.
|
|
330
|
+
GetSystemDateAndTime, GetCapabilities, GetServices, GetServiceCapabilities, GetWsdlUrl,
|
|
331
|
+
GetHostname and GetEndpointReference work without credentials (PRE_AUTH). HTTP methods other than GET and POST get 405.
|
|
332
|
+
- WS-Discovery: Types `dn:NetworkVideoTransmitter tds:Device`, AppSequence, Hello on
|
|
333
|
+
start and Bye on exit, Probe matching by type and scope (rfc3986/strcmp0), Resolve.
|
|
334
|
+
|
|
335
|
+
SetConfiguration returns success for the known configuration token but changes nothing.
|
|
336
|
+
|
|
337
|
+
Not simulated: device management beyond what PTZ clients need (network, users,
|
|
338
|
+
scopes, firmware), events, imaging beyond white balance, Media2, preset tours, auxiliary commands, HTTPS.
|
|
339
|
+
|
|
340
|
+
## Unsupported protocols
|
|
341
|
+
|
|
342
|
+
These PTZ protocols are common but **not supported** by the simulator:
|
|
343
|
+
|
|
344
|
+
| Protocol | Used for | Note |
|
|
345
|
+
|---|---|---|
|
|
346
|
+
| Pelco-D / Pelco-P | serial control over RS-485/RS-422, mostly older surveillance cameras | byte protocol with checksum, about as simple as VISCA |
|
|
347
|
+
| Panasonic AW protocol (HTTP CGI) | Panasonic PTZ (AW-UE/AW-HE), supported by many switchers | HTTP GET on `/cgi-bin/aw_ptz` and `/cgi-bin/aw_cam` |
|
|
348
|
+
| PTZOptics HTTP-CGI | PTZOptics cameras via browser URL | `/cgi-bin/ptzctrl.cgi`; VISCA over TCP/UDP is supported |
|
|
349
|
+
| Sony CGI | Sony BRC/SRG via HTTP | Sony VISCA over IP is supported |
|
|
350
|
+
| Axis VAPIX | Axis cameras via HTTP | `/axis-cgi/com/ptz.cgi` |
|
|
351
|
+
| Canon XC Protocol | Canon CR-N/CR-X PTZ | no public specification found |
|
|
352
|
+
| ONVIF Events, Media2 | ONVIF services besides PTZ | return `ter:ActionNotSupported` |
|
|
353
|
+
|
|
354
|
+
## References
|
|
355
|
+
|
|
356
|
+
**ONVIF**
|
|
357
|
+
- ONVIF PTZ Service Specification 25.12: https://www.onvif.org/specs/2512/ONVIF-PTZ-Service-Spec-v2512.pdf
|
|
358
|
+
- ONVIF Core Specification (device service, WS-Discovery on 239.255.255.250:3702): https://www.onvif.org/specs/2606/ONVIF-Core-Spec-v2606.pdf
|
|
359
|
+
- OASIS WS-Security UsernameToken Profile (PasswordDigest = Base64(SHA-1(nonce + created + password))): https://docs.oasis-open.org/wss/v1.1/wss-v1.1-spec-pr-UsernameTokenProfile-01.htm
|
|
360
|
+
- ONVIF Imaging Service Specification 18.12 (`docs/ONVIF-Imaging-Service-Spec-v1812.pdf`)
|
|
361
|
+
- Client library used for the interop test, onvif-zeep-async: https://pypi.org/project/onvif-zeep-async/
|
|
362
|
+
|
|
363
|
+
**VISCA**
|
|
364
|
+
- Sony BRC-X400/SRG-X400 Technical Manual (VISCA over IP, command list): https://sony.net/Products/CameraSystem/CA/BRC_X400_SRG_X400/Technical_Document/E042100011.pdf
|
|
365
|
+
- Sony BRC-X1000/BRC-H800 Command List: https://sony.net/Products/CameraSystem/CA/BRC_X1000_BRC_H800/Technical_Document/C456100111.pdf
|
|
366
|
+
- PTZOptics Developer Portal (VISCA over TCP 5678 / UDP 1259, HTTP-CGI): https://ptzoptics.com/developer-portal/
|
|
367
|
+
- PTZOptics: introduction to IP control: https://docs.ptzoptics.com/articles/miscellaneous/misc-cameras/an-introduction-to-ip-control-&-scripting-for-ptzoptics-cameras
|
|
368
|
+
|
|
369
|
+
**NDI**
|
|
370
|
+
- NDI docs, "PTZ and Control Messages": https://docs.ndi.video/all/developing-with-ndi/ndi-metadata/ptz-and-control-messages
|
|
371
|
+
- NDI SDK, header `Processing.NDI.Recv.ex.h` (value ranges of the `NDIlib_recv_ptz_*` functions), included in the SDK: https://ndi.video/
|
|
372
|
+
- ndi-python (Python bindings including the NDI runtime): https://pypi.org/project/ndi-python/
|
|
373
|
+
|
|
374
|
+
**Unsupported protocols**
|
|
375
|
+
- Pelco-D specification (Fujifilm SX400): https://asset.fujifilm.com/www/tw/files/2026-04/f5bc920c501c1c6a7f321ad8aa5dcb65/pelco-d_protocol_specification_for_sx400_v1.10.0_en.pdf
|
|
376
|
+
- Pelco-P specification (Fujifilm SX1600): https://asset.fujifilm.com/www/au/files/2023-06/84c9b575b79de420436e8c0d0fa147f8/pelco-p_protocol_specification_for_sx1600_v.1.00.0_en.pdf
|
|
377
|
+
- Panasonic AW-UE150/AW-HE145 Interface Specification: https://eww.pass.panasonic.co.jp/pro-av/support/content/guide/DEF/UE150/AW-UE150HE145_InterfaceSpecification_E.pdf
|
|
378
|
+
- Sony BRC-X400 CGI Command List: https://shop.ccisolutions.com/StoreFront/jsp/pdf/SON-BRCX400W_cgiCommandList.pdf
|
|
379
|
+
- Axis VAPIX Pan/Tilt/Zoom API: https://developer.axis.com/vapix/network-video/pantiltzoom-api/
|
|
380
|
+
- Canon CR-N300 specifications (lists the XC Protocol): https://www.canon.co.uk/ptz-cameras/cr-n300/specifications/
|
|
381
|
+
|
|
382
|
+
## Documentation website
|
|
383
|
+
|
|
384
|
+
The VitePress site in `website/` uses this README as its guide. Before publishing
|
|
385
|
+
it, review the examples for machine-specific hostnames and network addresses.
|
|
386
|
+
|
|
387
|
+
Node.js 20.19+ (20.x) or 22.12+ is required. From the repository directory:
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
cd website
|
|
391
|
+
npm ci
|
|
392
|
+
npm run docs:dev # local development server
|
|
393
|
+
npm test # check root and GitHub Pages subpath builds
|
|
394
|
+
npm run docs:build # production build
|
|
395
|
+
npm run docs:preview # preview the production build
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
For a local preview under a project subpath, set the same base path when building
|
|
399
|
+
and previewing:
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
BASE_PATH=/ptz-simulator npm run docs:build
|
|
403
|
+
BASE_PATH=/ptz-simulator npm run docs:preview
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
To publish on GitHub Pages, select **GitHub Actions** under **Settings > Pages >
|
|
407
|
+
Build and deployment > Source** in the repository. The `Deploy documentation`
|
|
408
|
+
workflow runs on pushes to `main` and can also be started manually. It gets the
|
|
409
|
+
site's base path from GitHub Pages, so project sites and root domains both work.
|
|
410
|
+
|
|
411
|
+
VitePress 1.x depends on an older Vite with development-server security
|
|
412
|
+
advisories. The website overrides it with a patched Vite 6 release; VitePress
|
|
413
|
+
data and path loaders are not used because they are not fully compatible with
|
|
414
|
+
that override. See the [upstream compatibility guidance](https://github.com/vuejs/vitepress/issues/4494).
|
|
415
|
+
|
|
416
|
+
## License
|
|
417
|
+
|
|
418
|
+
ptzsim is licensed under the [MIT License](https://github.com/stritti/ptz-simulator/blob/main/LICENSE).
|
|
419
|
+
The optional NDI runtime and third-party reference documents are subject to
|
|
420
|
+
their respective terms.
|