Skip to content

Add Pan/Tilt (PTZ) support for Arlo pan-tilt cameras#105

Open
dw5304 wants to merge 1 commit into
scryptedapp:mainfrom
dw5304:feat/pan-tilt-ptz
Open

Add Pan/Tilt (PTZ) support for Arlo pan-tilt cameras#105
dw5304 wants to merge 1 commit into
scryptedapp:mainfrom
dw5304:feat/pan-tilt-ptz

Conversation

@dw5304

@dw5304 dw5304 commented Jun 17, 2026

Copy link
Copy Markdown

Summary

Adds PanTiltZoom support to ArloCamera for Arlo's pan/tilt models (e.g. VMC2083A – Essential Indoor 2nd-gen Pan & Tilt). Previously the plugin exposed no movement controls for these cameras.

The Scrypted PanTiltZoom interface is added capability-gated (only cameras whose Arlo capabilities advertise PanTiltZoom.pan), so no other models are affected.

How Arlo PTZ actually works (the non-obvious part)

Arlo pan/tilt is velocity-based continuous motion, not absolute positioning. I captured the official web client (my.arlo.com) driving the camera and found it sends, on the device notify channel:

// while the joystick is held:
{ "from": "<userId>_web", "to": "<deviceId>", "action": "continuousMove",
  "resource": "cameras/<deviceId>/ptz", "publishResponse": true,
  "properties": { "panTiltSpaces": { "velocityGenericSpace": { "x": <pan -1..1>, "y": <tilt -1..1> } } } }

// on release:
{ "from": "<userId>_web", "to": "<deviceId>", "action": "stop",
  "resource": "cameras/<deviceId>/ptz", "publishResponse": true }

Key findings:

  • The action is continuousMove (move until stop), not set. An action:"set" with positionGenericSpace is accepted by the cloud (success:true) but the camera never moves.
  • Properties use velocityGenericSpace (x=pan velocity, y=tilt velocity, each −1..1), not positionGenericSpace.
  • The web app happens to send this over the SIP-WebSocket instantMessage proxy during a live stream, but the existing HTTP /notify channel works identically — no in-band/SIP changes needed. The camera reports live position back on cameras/<id>/ptz (action:"is") as it moves.
  • Read-only capability/preset metadata is available at cameras/<id>/ptz (position, homePosition, defaultPTZSpeed, presets[]).

Changes

  • arlo/arlo_async.py: PTZContinuous(basestation, camera, x, y) (continuousMove) and PTZStop(basestation, camera) (stop).
  • camera.py:
    • ArloCamera now mixes in PanTiltZoom.
    • has_ptz property gated on Capabilities.PanTiltZoom.pan.
    • arlo_presets helper (exposes Arlo presets as Scrypted PTZ presets metadata).
    • get_applicable_interfaces advertises PanTiltZoom when has_ptz.
    • ptzCapabilities is published in delayed_init (needs to run after the device is registered — setting it during get_applicable_interfaces is too early and the state write is lost).
    • ptzCommand maps Scrypted PTZ → Arlo: nonzero pan/tilt velocity → continuousMove; zero → stop. This lines up exactly with ONVIF/clients that drive PTZ via ContinuousMove + Stop.

Testing

  • VMC2083A, firmware er1.4, interfaceVersion i007.
  • Verified the camera physically pans/tilts full range and reports matching live positions back over the event stream.
  • Verified end-to-end via UniFi Protect (adopted as an ONVIF camera through a rebroadcast/ONVIF bridge): joystick press → continuousMove, release → stop.

Notes / follow-ups

  • Presets and absolute moves aren't wired into ptzCommand yet (clients using ONVIF ContinuousMove/Stop — incl. UniFi — only need continuous + stop, which this covers). Preset metadata is exposed via ptzCapabilities.presets; honoring a goto-preset command could be a follow-up (Arlo has a presetToken-based move).
  • Latency is inherent (cloud round-trip); the velocity+stop model handles it gracefully.

🤖 Generated with Claude Code

Adds the Scrypted PanTiltZoom interface to ArloCamera, capability-gated on
Capabilities.PanTiltZoom.pan (e.g. VMC2083A). Arlo PTZ is velocity-based
continuous motion: action 'continuousMove' with velocityGenericSpace, then
'stop' — over the existing device notify channel. ptzCommand maps nonzero
pan/tilt velocity to continuousMove and zero to stop, matching how ONVIF
clients (UniFi Protect) drive PTZ via ContinuousMove + Stop.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant