Skip to content

About

Universal camera control bridge: software RCP (paint control) and touch PTZ panel with one command bus for Sony, Canon, Panasonic, Blackmagic, Z CAM, JVC, BirdDog and VISCA cameras, plus Bitfocus Companion and USB panels.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

LZ Camera Bridge

A universal camera-control hub. One software interface — a broadcast-style RCP (paint controls) and a touch PTZ panel modelled on the Panasonic AW‑RP150 — plus a normalizing command bus that drives cameras from many vendors over their native protocols. The same commands also reach every camera from a Bitfocus Companion surface or a USB control panel.

LZ Camera Bridge — PTZ panel with the planned shots of a camera and its zoom curve

Status: the bridge, protocols and UI are real and build clean; a committed unit-test suite covers the protocol framing. Where a family was verified against public docs or an official reference it is marked verified below. Items marked tuning need a first on-camera test to confirm value encodings. Nothing ships as a fake/demo device.

Download

One installation is the whole tool. The installer for Windows and macOS carries the control surface and the bridge; the bridge starts with the application, on ws://localhost:9700, and stops with it.

https://github.com/larszu/lz-camera-bridge/releases

That was not always true, and the difference matters if you read an older note: until 2026-09-16 the installer shipped the panel alone. It came up looking complete and reconnected every three seconds against a bridge that nobody had started — you were expected to run a second process out of a clone of this repo, and nothing in the application said so. Now the panel finds its bridge because the application brought it.

A separate bridge is still perfectly legal, and for a control room it is the normal case: run the bridge where the cameras are and point tablets at it (see Other devices on the same network). If port 9700 is already taken the application says so and uses the bridge that is already running, instead of failing silently.

The web page

Every push to the default branch builds this repo's page from .github/workflows/pages.yml and publishes it:

https://larszu.github.io/lz-camera-bridge/

The workflow asks the Pages API before it configures anything. With no Pages site it still builds — that is a real check — and skips only the publishing step, with a warning and the one missing step in the run summary. A run that must stay red for a click nobody made teaches people to ignore red.

Measured 2026-09-16: published. The Pages site exists, and the deploy job now runs through — the switch that no workflow can flip (GITHUB_TOKEN may not create a site) has been thrown. Every push to main publishes by itself.

What the page carries is the README and docs/, rendered — not the UI itself. That is deliberate and not an omission: web-rcp is a client that needs the bridge, and on a Pages address it would look like the application while operating no camera. The running program comes from the installer below.


Supported cameras

Family Mode Transport Status
Sony CCU (HXC/HDC, BRC) tcp / serial 700PTP over TCP :7700 / RS‑422 paint verified; AWB/ABB deliberately disabled¹
Sony FX / Alpha (FX3, FX6, A7 …) sony-usb PTP vendor extension over USB (libusb) verified (codes from libgphoto2); handshake 3.00 and confirmed writes ported³
Sony FX / Alpha over Wi-Fi/LAN sony-ptpip PTP/IP TCP 15740, SSDP discovery; Access Authentication through SSH (port 22, aes128-ctr, host-key fingerprint) protocol **verified on an FX3 (fw 7.00)**³; this bridge path read-only on that FX3
Sony Monitor & Control (FX WiFi) sony-mnc HTTP :10000 + SSDP discovery unverified (undocumented app protocol) — for FX3/A7 prefer sony-ptpip
Canon EOS (R5/R6/R7/R8/R10 …) canon-ccapi CCAPI HTTP REST :8080 verified (official CCAPI)
Panasonic Lumix (S1/S5/GH5/GH6 …) lumix-http cam.cgi HTTP verified
Blackmagic (Pocket/Studio/URSA, FW 8.6+) blackmagic /control/api/v1 REST + WS verified
Z CAM (E2 family) zcam /ctrl/set HTTP verified
Panasonic AW PTZ (AW‑UE/HE) panasonic-ptz aw_ptz / aw_cam CGI verified (AW protocol)
VISCA over IP (PTZOptics, Marshall, AVer, Sony BRC/SRG) visca UDP :1259 (raw) / :52381 (Sony header) verified
HTTP-CGI PTZ (Vissonic/PTZOptics ptzctrl.cgi, Sony SRG/BRC /command/) http-cgi HTTP GET, Referer + Digest; power via VISCA TCP :5678 (Vissonic) or main.cgi (Sony) verified live on a Vissonic clone and a Sony SRG-A40
JVC ConnectedCam / KY‑PZ jvc Digest login + /cgi-bin/api.cgi verified vocabulary (some steps tuning)
BirdDog NDI PTZ birddog VISCA :52381 + REST :8080 verified endpoints (tuning)
VISCA on a serial line (same heads, RS‑232/422 wiring) visca-serial RS‑232 8N1, 9600 default, daisy‑chain address 1–7 framing unit‑tested; not yet on a camera
DJI Ronin RS 2 / RS 3 Pro dji-ronin DJI R SDK over CAN 1 Mbit/s via USB SLCAN adapter framing unit‑tested; not yet on a gimbal²
DJI Osmo Pocket 3 / 4 dji-osmo DUML over a serial (CDC) device framing unit‑tested; not yet on a gimbal³

³ Sony PTP, shared by sony-usb and sony-ptpip (SonyPtpClient), follows Sony's Camera Control PTP 3 Reference: handshake with protocol 3.00 and retry while the camera has no version yet; every write waits until the camera reports the value (a real FX3 shows it ~250 ms late); 500 ms after an exposure-mode change; the clock (0xD223) as an ISO 8601 string. The protocol code is the one lz-camera-sync runs against an FX3 (fw 7.00) since 2026-10-07 — reading 369 properties, writing and restoring values, setting the clock, over Wi-Fi with SSH. In this bridge the sony-ptpip path was checked read-only against that FX3 (SSDP found it and an A7 IV; SSH with fingerprint, handshake, 369 properties). The SSH password is never written to site.json; after a bridge restart it is entered again.

¹ Sony's 700 protocol is NDA-only; no public source documents the auto‑setup command codes, so those buttons stay disabled rather than guessing at a broadcast CCU. See packages/web-rcp/src/capabilities.ts.

² RS 2 and RS 3 Pro only. RS 3 and RSC 2 look the same and do not speak the SDK. Needs a USB SLCAN adapter (CANable, USBtin, Lawicel) on the focus‑wheel port — not SocketCAN, which is Linux‑only.

³ DJI publishes no control interface for the Pocket series, and its USB‑C port serves storage and UVC webcam — there is no USB control path. The one publicly reverse‑engineered route is Bluetooth LE; what ships here is DUML over a serial device, and the BLE seam is prepared but not filled. Both gimbals move the head only: a gimbal carries a camera, it is not one, so paint controls stay greyed out. The full picture, including which constants to turn first if a device stays silent, is in docs/dji-gimbal.md.

Control surfaces

  • Web RCP — Sony-style paint panel (iris, master black/gamma, R/G/B white & black balance, gain, ND, shutter, bars). Buttons are gated per backend so unsupported functions are disabled, not silently failing.

  • Touch PTZ panel — AW‑RP150-style joystick (pan/tilt with speed), zoom/focus rockers, push‑AF, iris and a paged preset grid with a store mode.

  • Soft presets — on heads that position absolutely and report their pose (VISCA IP/RS‑232, BirdDog, Panasonic AW, HTTP‑CGI, robotic heads) a stored preset keeps the pose in the bridge, and a recall glides there with ease‑in/out: all axes start and land together, move time per preset, Soft switch per preset. Other paths recall the camera's own preset, and the panel says so.

  • Bitfocus Companion — module in packages/companion-module-lz-camera-bridge (v0.2.0): paint, pan/tilt/zoom/focus and the elevation column, soft presets (move time, Soft per preset), studio shots (cut/fade, shot sets, seats, moves, move anyway, on-air lock per camera), panorama aim and the switcher, with dropdowns, feedbacks, variables and ready-made button pages built from the bridge's cameras and studio shots. It talks to the Companion API (9701/9702) and the panel's port (9700). See its HELP.md.

  • USB HID control panel — a data-driven adapter maps a hardware panel's axes/buttons onto the command bus, so one panel controls any brand.

  • Keyboard — on the PTZ panel the arrow keys drive while held, 1–9 recall presets, Home goes home; a window that loses focus stops the head.

  • Planned shots — the MultiCam Planner's camera list (v3) carries each camera's heading and its shots as pan/tilt/focal length in the room frame. The bridge turns them into head angles, drives them (ptzAbsolute on VISCA, Sony CGI, Panasonic AW and the Vissonic heads via VISCA TCP), stores them in the head's own preset memory, and takes a one-time offset on site: steer the head onto one known shot, click Head is here, and the mounting error is applied to every shot. From then on any panel or Companion recalls them by number. Zoom from a focal length is estimated between the ends of the lens until a zoom curve is measured: drive the zoom to 0/25/50/75/100 %, read the focal length off the lens or the camera menu, Record point. Details in docs/camera-plan.md.

  • Aim by frame — on the PTZ panel, drag a 16:9 frame in the camera's live picture: the bridge turns it into pan/tilt from the pose the head reports and into a zoom position from the field of view per zoom (datasheet wide/tele angles, sensor width + focal length, or measured by Measure: the head pans a little at five zoom positions and the bridge measures how far the picture moved). Heads that take a stream of positions glide there like a soft preset; the robotic head plays its own eased keyframes; the others drive their own absolute move. Needs a head that reports its pose and a stream address.

  • Panorama map — Scan panorama steps the head through a pan/tilt grid at wide zoom, takes a picture at every stop and stitches an equirectangular map from the known poses (no feature matching). It is stored in the config directory (panorama/cam-<n>.jpg) and served at /panorama/<n>.jpg. A frame drawn on the map drives the head there, also while it stands at full tele; the live picture is shown in its place on the map. The map is a still — scan again when the room has changed.

  • Studio shots (view Shots) — for every camera on the bridge:

    • Cut or Fade: cut recalls at the camera's own top speed; fade is a timed move with ease-in/out, pan, tilt and zoom arriving together — the same preset glide as above, with the move time of the recall. A planned shot glides from the pose the head reports; robotic heads play the time themselves. Where neither works the shot cuts, and the panel says so.
    • Shot sets — one button recalls a shot on several cameras with one common move time.
    • On-air lock — a camera whose switcher input is on programme does not move on a shot recall (sets, seats, moves, preset keys, Companion presets, planned shots); the banner offers Move anyway. Per camera, on by default.
    • Seats — a seating chart (rows × seats); each seat lists shots on one or more cameras, best first. A tap takes the first camera that is connected and not on air. Optionally a room plan instead of the grid: seats are dragged to where they are (a lectern, a sofa, a panel table), and Place grid lays out rows × seats as a quick start.
    • Moves — shots of one camera in a row, each with its own move time and an optional hold; any joystick touch stops the move.

    Shot sets, seats and moves live in the site file. Untested on real heads: so far all of it has only run against the demo camera.

Control room: switcher, video wall, site

The bridge also drives the room around the cameras. This came over from the former av-control-center application (2026-09-28) and replaces it.

  • Switcher — a Vissonic VIS-CATC (one output, twelve multi-image layouts) operated the way a panel works: a programme row over a preview row and one CUT. The preview bus lives in the bridge, so every screen shows the same pre-selection. Keys: 1–6 preview, Shift+digit take, Enter or Space cut — by key code, so it works on every keyboard layout. Two routes to the device, chosen per site: its network page (POST /) or the RS-232 verb list from the manual, through a TCP-serial gateway or a port on the bridge computer. Layouts, audio, freeze (serial only), a crosspoint list for multi-window layouts. Protocol in docs/switcher.md.
  • Tally per camera — each camera names its switcher input; the bridge derives programme/preview from the switcher and shows it on the camera list, the wall, the PTZ panel and the video tiles. Companion gets switcherPreview, switcherTake, switcherCut.
  • Video — live tiles from each camera's stream address (RTSP, SRT or RTMP). The bridge runs one ffmpeg per stream and serves GET /video/<n>.mjpeg on its own port; the page shows it in an <img>, no player. Click a tile to preview its input, double-click to take. Own window opens the tiles alone for a second screen. The stream address never leaves the bridge. ffmpeg is looked for in LZ_BRIDGE_FFMPEG, ffmpeg-portable/, then PATH; the desktop app brings its own. SRT needs an ffmpeg built with libsrt — the bundled one and Homebrew's default formula have none (RTSP and RTMP work with both); the tile says so instead of staying black.
  • Scopes — Scopes on a video tile opens waveform (luma) and vectorscope under it; each panel's header switches to RGB parade, histogram, CIE and more, 4 scopes fills the window with waveform, vectorscope, parade and histogram. They measure raw R'G'B' frames from WS /scope/<n> (LZ Scopes frame protocol), not the JPEG of the tile: the bridge starts its own ffmpeg per open scope and stops it when the panel closes. Details in docs/live-video.md.
  • Site — cameras and switcher as one file (lz-site v1). The desktop app and npm run bridge keep it in LZ_BRIDGE_CONFIG_DIR/site.json (default ~/.lz-camera-bridge) and restore it on start, connecting what was connected. Export downloads it; import replaces the room — also from the settings export of the old av-control-center, so an existing room moves with one file. The file carries the camera logins; keep it out of repositories.
  • FreeD tracking — every camera that reports its pose (VISCA, Panasonic AW, HTTP-CGI, robotic heads incl. elevation column) streams pan, tilt, zoom and focus as FreeD D1 over UDP to one or more receivers, e.g. Unreal Live Link: one switch per camera, targets and rate under Site, interpolated between pose reads, silent when a camera stops answering. Not yet tested against Unreal or real heads. Details in docs/freed-tracking.md.

Screens

Switcher view: programme row over preview row, CUT, the twelve layouts Video view: live tiles with programme and preview tally
Switcher — PGM over PVW, CUT, layouts, audio Video — live tiles from RTSP/RTMP/SRT, tally on the top edge
Wall view: every camera's panel at once Site view: cameras and switcher of the room, export and import
Wall — every camera's panel at once Site — the room as one file

Taken 2026-09-29 against demo cameras, a switcher stand-in and ffmpeg test patterns streamed over RTMP — no real room, so nothing to blank out.

Architecture

Input surfaces                 Bridge :9700 (command bus)          Devices
────────────────               ──────────────────────────          ───────
Web panel   ─┐                                                     ┌─ Sony CCU (700PTP) · Sony USB (PTP)
Cameras/Wall ┼─ WebSocket ────▶  BridgeServer                      ├─ Canon CCAPI · Lumix · Z CAM · Blackmagic
Switcher     ┤                   camera slots ──(RCP verbs)───────▶├─ VISCA (IP / RS-232) · Panasonic AW · JVC · BirdDog
Video tiles ─┼─ GET /video/n ─▶  RtspHub (ffmpeg → MJPEG) ◀────────┤  RTSP streams
Scopes       ┼─ WS /scope/n ──▶  ScopeStream (ffmpeg → raw RGBA) ◀─┤
Site        ─┘  GET /site.json   site file (persisted)             ├─ HTTP-CGI PTZ (Vissonic/PTZOptics, Sony SRG)
Companion   ── HTTP/WS :9701-2   switcher slots ──(route/cut)─────▶└─ Vissonic VIS-CATC (HTTP or RS-232)
HID panel   ── USB                 └─ tally per camera ──▶ every client

Every camera backend exposes the same handleRcpCommand(cmd, params) contract, so a single command vocabulary (setIris, setMasterGain, ptz, recallPreset, home, setCameraPower, …) fans out to all of them. A switcher is not a camera and has its own slot family and verbs (preview, cut, take, route, setLayout); the bridge derives the tally per camera from both. A visual walk-through lives in docs/architecture.html.

Paint values can be trimmed relatively (cmd: 'nudge') instead of only jumped to: one resolution for every path — WebSocket, Companion buttons, the RCP's up/down selectors — against the value the bridge has actually read. Where it has read none, the trim is refused with a reason rather than started from a guessed 128. The bus scale of every value, and what each backend makes of it, is documented in docs/paint-nudge.md.

Every paint value on the panel says where it comes from: read back from the device, or merely the last thing the bridge sent. Which is which is decided per field and per connection mode by a table that was read out of this repository's own backends — five of the twelve paths read nothing back at all, and the panel says so once at the top instead of marking twenty knobs. See docs/value-origin.md.

Live-video feasibility per camera family — which cameras can show a live picture in the panel, at what cost — is documented in docs/live-video.md.

A USB control surface drives the bridge instead of the other way round: a gamepad, or a panel you build yourself. A PlayStation controller is recognised by vendor/product id and gets a drive mapping (left stick pan/tilt, analog triggers zoom, face buttons presets); anything unknown keeps the paint mapping it always had. That split is a safety boundary and not a convenience — setIris is a 9-byte VISCA packet whose bytes 6/7 the DigitalBird DB3 decoder reads as a pan/tilt direction, so a stick on iris makes that head drive off. The parts list for a panel of your own, and the compatibility check against the DB3 head, are in docs/ptz-pult-hardware.md; the two things that still need a real device are in docs/ptz-pult-messprotokoll.md.

The bridge reads the camera-list the AV Planner Suite's MultiCam Planner exports and holds it against the cameras on the bus, so the control wall can label a slot the way the show calls it ("CAM 3 — Bühne links") instead of by number. Every match carries evidence — a measured model, a mere convention, or a human — and where there is none, there is no match. See docs/camera-plan.md.

A B4 lens reports its name, open f-number, focal lengths and MOD over the serial protocol. Those are readings from one piece of glass, not capabilities of this repo, so they stay out of the caps:parity contract — the reasoning, and where they live instead, is in docs/b4/lens-facts-vs-capabilities.md.

A zoom or focus demand wired to the B4 interface becomes an input on the same bus: its wiper drives setZoom/setFocus on a chosen camera, through a table measured on that demand, and an unplugged demand stops the axis instead of holding its last value. Without the bridge the same firmware can appear as a USB gamepad. See docs/b4/demand.md.

Packages

Path What it is
packages/bridge Node/TypeScript bridge server, camera clients, protocols, HID input, switcher (src/switcher), video hub (src/multiview), site file (src/site)
packages/web-rcp React UI: Cameras (RCP + PTZ panel, wizard), Wall, Switcher, Video, Site
packages/companion-module-lz-camera-bridge Bitfocus Companion module
packages/electron-app Desktop application: the web UI and the bridge, bundled into one installer
packages/firmware WIZ108SR serial↔TCP bridge firmware (C)

Source language: en. New user-facing text goes in English. That is not a preference but a measurement: the UI already carries roughly 140 English spots against 32 German ones, and its vocabulary is the technical one anyway — Iris, Gain, ND, Paint. Rewriting the English into German to then put „Blende" next to WB costs work and makes the result worse. Decided 2026-09-08 (E-17); multicam-planner is English-source as well, while cable-planner and light-planner are German-source. The machine-readable copy of this declaration sits in package.json under avplan.sourceLanguage.

The German spots that are still there are a mixed-language product, not a translation backlog — the same dialog shows both. npm run lang:check counts them and holds the count at the level measured when the check was introduced: while that cleanup is open, the existing ones may stay, but the mix must not grow. Translating some means lowering the limit in the same commit.

Getting started

npm install            # workspaces install
./dev.sh               # Linux / macOS — bridge + web UI, demo camera preselected
npm run dev            # the same via npm (bridge backgrounded with `&`)
npm test               # protocol framing unit tests

Try it without a camera

Pick the Demo (no camera) tab in the connection panel and connect. The panel then talks to a state held in the bridge: move a control and the value follows, so the surface — RCP, joystick, layout — can be tried on a laptop.

Before this, npm run dev always started but could not show anything: every backend in backendFactory needs a real address (a CCU on TCP, a VISCA port, a USB device in PC-Remote mode). Without one the panel came up empty and every control was inert.

It is not a camera simulator. Nothing drifts, nothing is measured, and every value moved because a command moved it. The state carries an isDemo flag all the way to the dashboard, so a demo value can never be mistaken for a reading from a real device — that is the same line valueOrigin.ts draws between commanded and confirmed, and test/demoKamera.test.ts holds it: one case asserts that nothing changes over 600 ms with no command, another that isDemo survives mapState.

dev.sh also cleans up after itself. npm run dev appends the bridge with & and leaves it: a Ctrl-C ends only the web UI, the bridge stays on port 9700, and the next start fails on it.

Other devices on the same network

The bridge binds every interface and now prints the addresses you can hand out, not just localhost:

[BridgeServer] WebSocket listening on ws://localhost:9700
[BridgeServer]                     ws://192.168.1.42:9700  (same network)

All detected addresses are listed rather than one being guessed: on a machine with a Docker or VPN bridge the first one is often the wrong one.

Build individually:

npm run build --workspace=packages/bridge
npm run build --workspace=packages/web-rcp

Optional native modules (loaded lazily, not required to build/run):

npm install usb      --workspace=packages/bridge   # Sony USB (PTP) discovery/control
npm install node-hid --workspace=packages/bridge   # USB control-panel input

First run

Open the web UI and follow the setup wizard: pick a camera family, enter its address (the wizard shows the right defaults per protocol), and connect. PTZ cameras open the joystick panel automatically; paint cameras open the RCP.

The header switches between five views: Cameras (one camera, its connection and panel), Wall (every camera's panel at once), Switcher, Video and Site. Under Cameras every camera also takes a name, its RTSP stream address and its switcher input — that is all the Video and Switcher views need. A room that already exists as a site file goes in under Site → Import; the bridge then connects everything by itself.

  • Sony BRC/SRG PTZ: choose the VISCA over IP tab and set port 52381 — the Sony transport header is applied automatically. Where the room blocks the VISCA ports, the HTTP-CGI PTZ tab drives the same head over its web CGI (family Sony, login required; power is read back). Over this CGI the Sony also takes manual white balance: R and B gain (imaging.cgi WhiteBalanceCrGain/CbGain, read back from inquiry.cgi?inq=imaging). It has no G gain and, over the CGI, no hue, saturation, black or gamma.
  • Vissonic / PTZOptics PTZ: HTTP-CGI PTZ, family Vissonic. No login for control; power on/standby goes over VISCA TCP 5678 and is read back. Stream address rtsp://<ip>:554/1.
  • VIS-CATC switcher: the Switcher view, + Switcher, address of the device page. Give each camera its switcher input in the connection panel.
  • Sony FX/Alpha USB: put the camera in PC Remote mode; install the usb module on the host.
  • Canon: activate CCAPI once via Canon's tool, then enter the shown IP/port.
  • VISCA on a cable: the VISCA RS‑232 tab. Most heads ship at 9600 baud, address 1. Not the same as the 8‑Pin RS‑422 tab next to it — that is Sony's 9‑pin protocol on a different cable with different framing (odd parity).
  • DJI gimbals: the DJI Ronin and DJI Osmo Pocket tabs move the head only. Read docs/dji-gimbal.md first — it says what is proven and what is not, and there is no USB control path for the Pocket.

License

Proprietär — © 2026 Lars Zumpe, alle Rechte vorbehalten. Nutzung der veröffentlichten Builds ist kostenlos; Weiterverbreitung und abgeleitete Werke sind es nicht. Siehe LICENSE.

About

Universal camera control bridge: software RCP (paint control) and touch PTZ panel with one command bus for Sony, Canon, Panasonic, Blackmagic, Z CAM, JVC, BirdDog and VISCA cameras, plus Bitfocus Companion and USB panels.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages