Skip to content

Message Model

Root Message

All protocol characteristic traffic is a Reaction protobuf message inside a SLIP frame.

message Reaction {
  enum MESSAGE_TYPE {
    Query   = 0;
    Command = 1;
    Update  = 2;
  }

  enum QUERY_TYPE {
    Unset      = 0;
    DeviceInfo = 1;
  }

  optional MESSAGE_TYPE type      = 1;
  optional QUERY_TYPE queryType   = 2;
  uint32 sequenceNumber           = 3;
  uint32 timestamp                = 4;

  oneof message {
    QuizboxMessage quizbox        = 10;
    DeviceInfo device_info        = 11;
    FirmwareUpdate firmware_update = 12;
    ConfigMessage config          = 13;
    BleAccessMessage ble_access   = 14;
  }
}

sequenceNumber is present in the schema but the current firmware does not use it for request correlation.

Device notifications are encoded with type=Update. If firmware is building an outbound message that is not Query or Command, the encoder forces type=Update.

SLIP Framing

SLIP constants:

Name Byte
END 0xC0
ESC 0xDB
escaped END 0xDB 0xDC
escaped ESC 0xDB 0xDD

The firmware and Python helper encode frames with an END byte at both the start and end of each frame:

0xC0 + escaped protobuf bytes + 0xC0

Clients should tolerate empty frames and keep buffering until a complete non-empty frame is available.

Request Routing

The firmware routes incoming decoded Reaction messages like this:

Incoming message Firmware behavior
type=Query, queryType=DeviceInfo Sends a device_info update
firmware_update oneof set Processes OTA command and sends a response for all commands except protobuf DATA
config oneof set Processes GET, SET, RESET, or NOTIFY and sends a config update
anything else with quizbox.event Converts to an internal event and publishes it as source=External

External events are intentionally not echoed directly by BLE notifications. The state changes they cause may publish new internal events, which are then notified to clients.

Event Payload Selection

EventMessage has one event type plus a oneof payload:

Event type Payload
QUIZZER_ACTION quizzer_event
BUTTON_ACTION button_event
TIMER_COUNTDOWN timer_event
QUIZZER_DISPLAYED active_quizzers
QUIZZER_ACTIVE active_quizzers
QUIZZERS_LIT_UP active_quizzers
TIMER_EXPIRED, TIMER_STARTED, TIMER_CLEARED, QUIZZER_CLEARED, PAIRING_ACTIVE, PAIRING_INACTIVE, END no additional payload

The firmware sets ActiveQuizzers.is_active to true when the active quizzer list has at least one member.