plate-state gives a Plate's browser UI access to hardware over serial. It builds a state model from serial traffic and serves it to the Plate Console tab via WebSocket. It can also send commands.
stm32-mcp is completely independent. It works on its own, as it always has. plate-state does not sit between Claude and hardware. It comes along for the ride.
plate-state has two modes for connecting to a serial port:
When stm32-mcp is running, it starts a serial bridge (TCP server on localhost:8765) that multiplexes serial access. The stm32-serial CLI already connects to this bridge as an interactive terminal. plate-state connects the same way: as another client of the existing bridge. It sees all traffic and can send commands through the shared, lock-protected connection.
graph TD
MCP["stm32-mcp"] -->|starts| Bridge["Serial Bridge\nlocalhost:8765"]
Bridge <-->|pyserial| Port["Serial Port\n/dev/tty*"]
Bridge <--> CLI["stm32-serial\n(terminal)"]
Bridge <--> PS["plate-state"]
PS <-->|WebSocket :9147| Console["Plate Console\n(browser)"]
When stm32-mcp is not running, plate-state opens the serial port itself. No bridge needed. You're just using the Plate Console to talk to hardware on your own.
Either way, the Plate Console gets the same interface. The connection mode is an implementation detail.
plate-state watches serial traffic and builds a model of what it knows about the hardware. When traffic comes through (from any source), plate-state parses responses and updates the model. The Plate Console renders the current model. Widgets stay in sync with reality.
| Event | What happens |
|---|---|
| Slider dragged in Plate Console | plate-state sends command via bridge (or directly). Hardware responds. Model updates. Widget confirms. |
| Claude sends a command via stm32-mcp | Traffic flows through bridge. plate-state observes it. Model updates. Plate Console reflects the change. |
| Hardware sends unsolicited data | plate-state sees it on the wire, parses it, updates model, notifies the Console. |
| Plate Console connects | plate-state sends current state snapshot. No stale UI. |
Parsing responses into the state model is project-specific. Each firmware speaks its own protocol. plate-state needs a parser per project, or a generic line-based approach that the Plate Console interprets.
plate-state serves the Plate Console over WebSocket. JSON messages, three types:
| Direction | Type | Shape |
|---|---|---|
| Console → plate-state | command | { type: "command", cmd: "REG_READ 0x04" } |
| plate-state → Console | traffic | { type: "traffic", dir: "tx"|"rx", data: "...", ts: ... } |
| plate-state → Console | state | { type: "state", model: {...}, connected: true } |
The Console is a Plate tab that connects to plate-state. Two panes: traffic on the left, widgets on the right.
| Class | Prefix | Color | Use |
|---|---|---|---|
tx | → | Blue | Commands sent to hardware |
rx | ← | Green | Responses from hardware |
sys | italic | Dim | Connection events |
err | ! | Red | Errors, faults, timeouts |
| Type | Controls | Use |
|---|---|---|
| Slider | Range + number input + Send | Analog parameters (brightness, current set) |
| Buttons | Labeled button group | Discrete commands (enable, disable, reset) |
| Status | Read-only display + action buttons | Register readback, fault status |
Widgets are defined in HTML or a static JSON file, not a database. They change when the firmware interface changes.
| State | Indicator | Behavior |
|---|---|---|
| plate-state not running | Gray dot | Widgets disabled, command input disabled |
| Connected, no serial port | Yellow dot | plate-state is up but no port open |
| Connected, port open | Green dot | Full operation |
| Step | What |
|---|---|
| 1 | plate-state process. Connects to serial bridge (or opens port directly). WebSocket server on :9147. |
| 2 | Traffic forwarding. Serial data flows to Console via WebSocket. Console commands flow to serial. |
| 3 | State model. Parse responses, build model, broadcast to Console on change. |
| 4 | Console tab in Plate. Traffic pane, command input, connection status. |
| 5 | Widget panel. Render widget definitions, wire controls to commands, update from state. |
How should plate-state decide between bridge mode and direct mode? Auto-detect (try bridge first, fall back to direct)? A flag? TBD.
The state model parser is project-specific. Is there a generic approach (line-based, key:value) that covers most firmware protocols, or does each project need its own parser?