3.0 KiB
3.0 KiB
WebSocket Compact Update Format Proposal
1. Problem Statement
The current WebSocket update mechanism uses verbose JSON payloads for frequent events. Example Payload:
{"type":"register_update","data":{"slaveId":13,"address":1235,"fc":3,"count":1,"id":633,"value":166}}
Issues:
- High network overhead (~100 bytes/update).
- JSON parsing/serializing CPU cost on ESP32.
2. Proposed Solution: Hybrid Binary Protocol
Use WebSocket Binary Frames for high-frequency updates while maintaining Text Frames for JSON (backward compatibility).
Key Features
- Efficiency: ~10-12 bytes per update (vs ~100 bytes).
- Performance: Direct memory replication (C++ struct) -> DataView (JS).
- Compatibility: Legacy clients ignore binary frames; new clients handle both.
3. Binary Specification (Little Endian)
Packet Structure
[Type (1B)] [Payload (Variable)]
| Hex | Type | Description |
|---|---|---|
0x01 |
COIL_UPDATE |
Single Coil Update |
0x02 |
REGISTER_UPDATE |
Single Register Update |
3.1 Packet 0x01: COIL_UPDATE (10 Bytes)
| Offset | Field | Type | Description |
|---|---|---|---|
| 0 | Type |
UInt8 |
0x01 |
| 1 | SlaveId |
UInt8 |
Modbus Slave ID |
| 2 | Fc |
UInt8 |
Function Code (e.g. 5) |
| 3 | Value |
UInt8 |
0 or 1 |
| 4 | ComponentId |
UInt16 |
Internal ID |
| 6 | Address |
UInt16 |
Modbus Address |
| 8 | Count |
UInt16 |
Count (usually 1) |
3.2 Packet 0x02: REGISTER_UPDATE (12 Bytes)
| Offset | Field | Type | Description |
|---|---|---|---|
| 0 | Type |
UInt8 |
0x02 |
| 1 | SlaveId |
UInt8 |
Modbus Slave ID |
| 2 | Fc |
UInt8 |
Function Code (e.g. 3) |
| 3 | Reserved |
UInt8 |
Padding |
| 4 | ComponentId |
UInt16 |
Internal ID |
| 6 | Address |
UInt16 |
Modbus Address |
| 8 | Count |
UInt16 |
Count (usually 1) |
| 10 | Value |
UInt16 |
Register Value |
4. Implementation
Server (C++)
Define packed structs:
#pragma pack(push, 1)
struct BinaryRegisterUpdate {
uint8_t type = 0x02;
uint8_t slaveId;
uint8_t fc;
uint8_t reserved;
uint16_t componentId;
uint16_t address;
uint16_t count;
uint16_t value;
};
#pragma pack(pop)
Send using ws.binaryAll((uint8_t*)&update, sizeof(update));.
Client (TypeScript)
Handle binary frames:
ws.binaryType = 'arraybuffer';
ws.onmessage = (event) => {
if (event.data instanceof ArrayBuffer) {
handleBinary(new DataView(event.data));
} else {
// legacy JSON
}
};
function handleBinary(view: DataView) {
const type = view.getUint8(0);
if (type === 0x02) {
const value = view.getUint16(10, true); // Little Endian
// Dispatch update
}
}
5. Alternative: Compact Text
If binary is not feasible, use delimiter-separated strings:
RU|13|3|633|1235|1|166
- Pros: Human readable.
- Cons: Larger, string parsing overhead.