One Control Surface, Many Cranes
One Control Surface, Many Cranes
The first version of the control page assumed there was one rig. One set of lights, one status feed, one set of buttons. As soon as that stopped being true, the assumption was everywhere.
What had to change
Every piece of state became crane-scoped: the status stream, the command path, the cluster definitions, the labels, the last-known levels. The UI got a crane picker, and the picker was the easy part.
The part that wasn’t easy was making sure nothing silently fell back to “the current rig” when a crane identifier was missing. A command that doesn’t know which crane it belongs to is a command that might land on the wrong one, and on a live rig that’s not a cosmetic problem.
Clusters
Alongside individual lights and native groups, I added clusters — UI-level groupings of addresses that a human thought of as “the boom” or “the deck”. They’re persisted separately from the hardware’s own group concept, which matters: one is a fact about the gear, the other is a convenience in the software.
Keeping those two ideas apart was the single most useful decision in this round. Conflating them produces a system where editing a convenience grouping changes the physical behaviour, or where a physical group can’t be represented because the software’s model doesn’t fit it.
What I got wrong first
I built the crane picker as a dropdown that filtered the view, and left the command path crane-agnostic. It looked fine until I ran two rigs at once and started seeing commands land on whichever one happened to be current. A filter is not a scope. The moment the view can show more than one thing, the identity has to travel with the command, not live in the UI.
The shape of it now
Crane identity is part of every command and every subscription. Clusters are per crane. Labels are per crane. Nothing in the control layer assumes a default.
It’s unglamorous work, and it’s most of what made the difference between a demo and something that could be pointed at a real port.