Stoatworks Labs

User guide

Companion — BlackMatrix user guide

v1.0.1Updated 25 August 2026

This module drives a BlackMatrix crosspoint server from a Stream Deck or any other Bitfocus Companion surface: a router panel for a fleet of Blackmagic ATEM switchers, where every bus that takes one source at a time is a destination.

The README covers installing the module. This is how to build a panel with it, and where a press can mislead an operator.

Before you rely on this: BlackMatrix has no authentication and binds every interface. Anyone who can reach its port can re-route program on a live switcher. Private production network only, and note that a press here re-points a live feed with no confirmation step.

This module has never been loaded into Companion. It is verified by a harness driving its real source against a fake crosspoint server, which is a different claim from “it works on a rack”.

This module was built with AI assistance, directed and reviewed by a human author.


Connecting

Two fields: the BlackMatrix server’s host, and its port — 8533 by default. This is the crosspoint server’s port, not an ATEM’s.

The module holds a WebSocket for state and uses REST for commands, so a connection that is green but never updates is a different fault from one that will not take. Watch $(blackmatrix:connection_status) for the first and the module’s log for the second.


Build an X-Y panel, not a crosspoint matrix

This is the thing to get right before you lay out a page.

A fleet of switchers has thousands of crosspoints. One button per crosspoint is not a page, it is a spreadsheet. The generated presets are a router panel instead:

  1. Press a destination. It turns blue. Nothing has been sent to any switcher yet.
  2. Press a source. That crosspoint is made.

Two hundred buttons cover what tens of thousands would, and it is the interaction every router operator already has in their hands.

The selection lives in this Companion, not in the server. Two Companions on the same fleet each have their own armed destination and never fight over a half-finished take. The other side of that: a destination armed on the rack Companion is invisible on the one in the gallery.

Drop Fleet → X-Y: what is selected somewhere on the page. An armed destination that scrolled off the visible page is how a source press lands somewhere nobody expected.

The panel reads as a monitor wall between takes

Destination buttons carry a live label of what they are currently carrying, so the panel is worth looking at even when nothing is armed. Source buttons light amber when the armed destination is already taking them — the route you are about to make is shown before you make it.


Addressing, when you build buttons by hand

Destinations and sources are deviceId:id:

Reference What it is
stage:aux.0 Aux 1 on the switcher called stage
stage:me.0.usk.1.fill ME 1 upstream keyer 2’s fill
stage:1 Input 1 on stage

Both dropdowns accept a custom value, so a variable can drive either end.

A crosspoint lives on one switcher. Routing studio’s source to stage’s aux is rejected by the module before a request is sent, because an ATEM cannot take another ATEM’s source. If a button does nothing at all, check both halves name the same device.


Salvos fire across the fleet, and partially

A salvo sets crosspoints on every switcher in it at once. Salvos are defined in BlackMatrix, not here — this module only fires them.

If part of a salvo is refused — a locked destination, or a source that switcher will not accept on that bus — the rest still happens, and the refusals are logged by name. That is the right behaviour for a live show and the wrong assumption for a button: a salvo button that lit up is not proof the whole salvo landed. Where a salvo has to be all-or-nothing, the check belongs in front of the button, not on it.

The server’s refusals are worth reading rather than dismissing. “Aux 1 is not available on ME 1 Program” is a switcher doing its job, not a broken module.


Locks are per IP address

BlackMatrix holds locks the way a Videohub does: by the address of whoever took them, not by a user or a session.

  • A lock taken in BlackMatrix’s browser UI on the machine running Companion is the same owner as one taken from these buttons. It will unlock without complaint.
  • A lock taken from any other machine is somebody else’s, and only force takes it.

Lock presets are generated for the Outputs (aux) section only, because that is the case worth a button. The lock action works on any destination, so build one by hand if a keyer needs protecting.


Feedbacks, and which button they belong on

Feedback Put it on Says
crosspoint a direct-route button this destination is taking this source
sourceOnSelected source buttons the armed destination already has this
destinationSelected destination buttons this one is armed
locked destination buttons locked, by anyone
deviceOnline a per-switcher tile that ATEM is connected to BlackMatrix
panelAttached a per-switcher tile a hardware Videohub panel is on that switcher
serviceUp one tile on the page BlackMatrix itself is answering

serviceUp and deviceOnline answer different questions and fail separately. BlackMatrix can be perfectly healthy while the switcher it fronts has dropped off the network — the panel keeps taking presses and nothing reaches the ATEM. One tile of each, near each other, is the layout that sends people to the right fault.

panelAttached is the one people leave out and then want. A hardware Videohub panel on the same switcher routes the same destinations this page does, with no idea that this page exists.


Variables worth knowing

Variable Value
<switcher>_<destination>_source live label of what that bus is carrying
selected_destination_label the armed destination’s name
selected_destination_source what the armed destination is currently taking
<switcher>_videohub_port that switcher’s Videohub port, off when it has none
<switcher>_videohub_panels how many Videohub clients are attached to it
locked_count, device_count, online_count, salvo_count fleet counts

$(blackmatrix:stage_aux_0_source) on a button is how a monitor-wall button labels itself.

Destination ids are flattened for variable names — stage:aux.0 becomes stage_aux_0.


When a button does nothing

Symptom Where to look
Every button dead, no variables Wrong host or port, or BlackMatrix is not running. serviceUp is the tile that says so.
Labels present but a take does nothing The destination is locked, or the source is not legal on that bus. The module logs the server’s reason.
Source press goes somewhere unexpected A destination was still armed from earlier. Put the X-Y selection tile on the page.
Route silently refused Source and destination name different switchers. A crosspoint cannot cross devices.
Panel disagrees with the rack A hardware Videohub panel is routing the same destinations. panelAttached shows when one is on.

This guide lives in the project's own repository and is rebuilt from it — corrections are welcome as a pull request againstdocs/USER-GUIDE.md. Last updated 25 August 2026.