Skip to content

Data Flow

Data Flow

This document traces how a button press travels from the physical input to the gaming device.

Button Press Sequence

┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ User │───►│ ESP32 │───►│ RPi │───►│ HID │───►│ Gaming │
│ Press │ │ Module │ │ Server │ │ Worker │ │ Device │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │ │
│ Physical │ BLE GATT │ Aggregate │ USB HID │
│ GPIO │ Notify │ + Map │ Report │
│ │ (4 bytes) │ │ (8 bytes) │

Data Transformation Stages

StageComponentDataFormat
1ESP32 GPIOButton statesDigital HIGH/LOW
2Debounce filterFiltered statesBoolean per button
3AggregateCombined state32-bit bitmask (4 bytes)
4BLE NotifyGATT notification4 bytes @ 100Hz max
5RPi DeviceSessionParsed statePython dict
6StateReducerAggregated (N devices)Keycodes list
7ReportBuilderHID report8 bytes (modifier + 6 keys)
8HID WorkerUSB packetWritten to /dev/hidg0
9Host PCKeyboard inputOS keyboard events

Button State Packet Format

The ESP32 sends a 4-byte packet over BLE:

┌─────────────┬─────────────┬─────────────────────┬───────────┐
│ Byte 0 │ Byte 1 │ Byte 2 │ Byte 3 │
├─────────────┼─────────────┼─────────────────────┼───────────┤
│ b1-b8 │ joy_l,r, │ select,start,pair │ reserved │
│ (buttons) │ u,d │ (system) │ │
└─────────────┴─────────────┴─────────────────────┴───────────┘

Each bit represents a button state: 1 = pressed, 0 = released.

HID Report Format

The RPi builds an 8-byte USB HID keyboard report:

┌─────────┬──────────┬─────────────────────────────────────────┐
│ Byte 0 │ Byte 1 │ Bytes 2-7 │
├─────────┼──────────┼─────────────────────────────────────────┤
│Modifier │ Reserved │ Up to 6 simultaneous keycodes │
│ flags │ (0) │ │
└─────────┴──────────┴─────────────────────────────────────────┘

Configuration Update Flow

When the user updates button mappings in the config app:

┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Config │───►│WebSerial │───►│ Serial │───►│ Config │───►│ Runtime │
│ App │ │ API │ │ Device │ │ Store │ │ Server │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │ │
│ JSON cmd │ USB data │ Persist │ IPC │
│ │ │ config.json │ update │
  1. User changes button mapping in React app
  2. App sends JSON command via WebSerial
  3. Serial config service receives and validates
  4. Config persisted to /var/lib/openarcade/config.json
  5. IPC event sent to runtime via Unix socket
  6. StateReducer refreshes mappings (no restart needed)

Latency Considerations

HopTypical Latency
GPIO read + debounce10ms loop
BLE notification7.5-15ms interval
Async processing< 1ms
HID queue + write< 1ms
USB polling1-8ms (host dependent)
Total~20-35ms

Multi-Device Aggregation

When multiple modules are connected, the StateReducer merges their states:

┌─────────────┐
│ Module A │──┐
│ State │ │
└─────────────┘ │ ┌─────────────┐ ┌─────────────┐
├───►│ State │───►│ Report │
┌─────────────┐ │ │ Reducer │ │ Builder │
│ Module B │──┤ └─────────────┘ └─────────────┘
│ State │ │
└─────────────┘ │
┌─────────────┐ │
│ Module N │──┘
│ State │
└─────────────┘

Each module’s buttons are mapped according to their configured roles in config.json.