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 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.
@@ -0,0 +1,4 @@
1
+ # SPDX-FileCopyrightText: 2026 Stephan Strittmatter
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ include scripts/build-release.sh
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.