PiPLC WebSocket Protocol¶
The PiPLC editor and engine communicate over WebSocket using two frame types: JSON text frames for control messages and binary frames for memory snapshots.
Implementation: src/protocol/PlcProtocol.h / PlcProtocol.cpp (built as the piplc-protocol static library).
JSON Control Messages¶
All request/reply messages carry a seq number for correlation.
| Category | Messages |
|---|---|
| Handshake | hello → hello.reply (protocol version, capabilities, memory layout) |
| Engine control | engine.start/stop/pause/resume/stepScan/stepRung/reset → .reply |
| Program | program.download (sends .plcproj XML as string), program.upload, program.clear |
| Memory force | memory.forceBit, memory.unforceBit, memory.clearAllForces |
| Memory stream | memory.subscribe (intervalMs) / memory.unsubscribe |
| Breakpoints | breakpoint.set/remove/clearAll |
| I/O config | ioconfig.load (sends IOProviderConfig JSON) |
| Queries | engine.state, engine.statistics, engine.info |
| Signal routing | route.sync (broadcasts full route table to server) |
| Context mgmt | context.create, context.remove, context.list |
| Events (server→client) | event.stateChanged, event.fault, event.breakpointHit, event.scanStats, event.ping |
Message Examples¶
Handshake¶
// Client -> Server
{
"type": "hello",
"seq": 1,
"protocolVersion": 1,
"clientName": "PiPLC Editor",
"clientVersion": "0.2.0"
}
// Server -> Client
{
"type": "hello.reply",
"seq": 1,
"protocolVersion": 1,
"serverName": "PiPLC Engine",
"serverVersion": "0.2.0",
"engineId": "uuid-string",
"capabilities": ["gpio", "analog"],
"memoryLayout": {
"inputWords": 8,
"outputWords": 8,
"bitWords": 64,
"integerWords": 256,
"timerCount": 64,
"counterCount": 64
}
}
Engine Control¶
{"type": "engine.start", "seq": 2}
// Reply:
{"type": "engine.start.reply", "seq": 2, "success": true, "state": "Running"}
Program Download¶
{
"type": "program.download",
"seq": 10,
"format": "plcproj-xml",
"data": "<?xml version=\"1.0\" ...>...</PLCProject>"
}
Memory Force¶
Memory Stream Subscription¶
Events (Server → Client, unsolicited)¶
{"type": "event.stateChanged", "state": "Running"}
{"type": "event.fault", "message": "Division by zero at rung 3"}
{"type": "event.breakpointHit", "rungIndex": 3}
Binary Memory Snapshot Format¶
Binary WebSocket frames for efficient memory monitoring (sent at the subscribed interval, default 20 Hz):
Offset Size Field
0 4 Magic: 0x504D454D ("PMEM")
4 4 Sequence (uint32 LE)
8 4 Timestamp ms (uint32 LE)
12 1 Engine state enum
13 3 Reserved
16 32 Input words (8 × uint32 LE)
48 32 Output words (8 × uint32 LE)
80 256 Bit words (64 × uint32 LE)
336 1024 Integers (256 × int32 LE)
1360 512 Timers (64 × 8 bytes: PRE:i16 ACC:i16 flags:u8 pad:3)
1872 512 Counters (64 × 8 bytes: PRE:i16 ACC:i16 flags:u8 pad:3)
2384 320 Force bitmap (80 words × uint32)
─────────────────
Total: 2704 bytes (~54 KB/s at 20Hz)
Timer Data Layout (8 bytes per timer)¶
| Offset | Size | Field |
|---|---|---|
| 0 | 2 | PRE (int16 LE) |
| 2 | 2 | ACC (int16 LE) |
| 4 | 1 | Flags: bit 0 = EN, bit 1 = TT, bit 2 = DN |
| 5 | 3 | Reserved |
Counter Data Layout (8 bytes per counter)¶
| Offset | Size | Field |
|---|---|---|
| 0 | 2 | PRE (int16 LE) |
| 2 | 2 | ACC (int16 LE) |
| 4 | 1 | Flags: bit 0 = DN, bit 1 = OV, bit 2 = UN, bit 3 = CU, bit 4 = CD |
| 5 | 3 | Reserved |
Force Bitmap¶
80 words (2560 bits) covering all addressable memory locations. A set bit indicates the corresponding address is forced. The bitmap layout mirrors the memory region order: inputs, outputs, bits, integers, timers, counters.
Connection Lifecycle¶
Client Server
| |
|--- WebSocket connect ------------->|
|--- hello ------------------------->|
|<-- hello.reply --------------------|
|--- memory.subscribe (50ms) ------->|
|<-- binary snapshot (periodic) -----|
|--- engine.start ------------------>|
|<-- engine.start.reply -------------|
|<-- event.stateChanged "Running" ---|
| |
| ... (runtime operations) ... |
| |
|<-- event.ping ---------------------| (every 30s)
|--- pong ------------------------->>|
| |
|--- WebSocket close --------------->|
Auto-Reconnect Behavior¶
The RemoteEngineContext handles disconnections automatically:
- On disconnect: starts a reconnect timer (3s interval)
- On reconnect: re-handshakes, re-subscribes to memory stream, re-downloads program if previously loaded
- Pending replies timeout after 5s
- Connection state changes are emitted for UI updates (green/yellow/red indicators)
See Also¶
- Architecture Overview — System architecture and deployment modes
- Threading Model — Thread safety and synchronization