Skip to content

Connection Lifecycle ​

This is the heart of blocksd: the state machine that takes a ROLI Block from "just plugged in via USB" to "fully alive with LED control, touch events, and keepalive." Getting this right required matching the exact sequencing and timing of ROLI's C++ implementation in roli_ConnectedDeviceGroup.cpp.

State Machine Overview ​

mermaid
stateDiagram-v2
    [*] --> Scanning: daemon starts
    Scanning --> SerialRequest: MIDI port matched
    SerialRequest --> SerialParsed: serial response received
    SerialRequest --> SerialRequest: retry every 300ms
    SerialParsed --> TopologyRequest: serial validated
    TopologyRequest --> TopologyParsed: topology response
    TopologyParsed --> APIActivation: devices enumerated
    APIActivation --> PingLoop: all devices in API mode
    PingLoop --> PingLoop: ping every 400ms/1666ms
    PingLoop --> TopologyRequest: topology change
    PingLoop --> [*]: device disconnected

Step by Step ​

1. Scan MIDI Ports ​

The topology manager polls MIDI ports every 1.5 seconds (currently a runtime constant). It looks for port names containing "BLOCK" or "Block". Inputs and outputs are paired by normalized name and occurrence.

2. Open MIDI Connection ​

When a matching port is found, blocksd opens the MIDI input and output pair. The MIDI connection uses python-rtmidi with a callback that marshals incoming messages to the asyncio event loop via loop.call_soon_threadsafe().

3. Request Serial Number ​

blocksd sends the serial dump request: F0 00 21 10 78 3F F7. This is retried every 300ms for up to 5 seconds. The response contains a MAC address prefix (48:B6:20:) followed by the 16-character serial number.

4. Parse Serial and Identify Device ​

The serial number's first 3 characters identify the device type (LPB = Lightpad, LKB = LUMI Keys, SBB = Seaboard, etc.). blocksd creates a DeviceGroup to manage this USB connection's lifecycle.

5. Request Topology ​

blocksd sends requestTopologyMessage (command 0x01) to device index 0 (the master). The device responds with a deviceTopology message containing all connected devices and their physical connections.

6. Parse Topology ​

The topology response is decoded to build a map of all devices in the mesh. Each device gets a topology index, serial number, battery level, and charging state.

7. Activate API Mode ​

For each device not yet in API mode, blocksd sends:

  1. endAPIMode (command 0x02): reset any stale API state
  2. beginAPIMode (command 0x00): enter rich protocol mode

This sequence is critical. Sending beginAPIMode without first sending endAPIMode can leave the device in an inconsistent state if it was already partially in API mode.

8. Start Ping Loop ​

Once all devices are in API mode, blocksd starts the keepalive ping loop:

  • Master block: ping every ~400ms
  • DNA-connected blocks: ping every ~1666ms

Each ping is a deviceCommandMessage with command 0x03. The device responds with a packetACK.

9. Monitor ACKs ​

blocksd tracks the last ACK time for each device. If a device hasn't responded within 6000ms, it's considered disconnected. The daemon removes the device from its internal state and emits a device_removed event.

10. Handle Topology Changes ​

If the topology changes (devices added or removed from the DNA mesh), blocksd:

  1. Re-requests topology from the master
  2. Activates API mode on any new devices
  3. Cleans up state for removed devices
  4. Emits appropriate events to API clients

Sequence Diagram ​

mermaid
sequenceDiagram
    participant H as blocksd
    participant M as Master Block
    participant D as DNA Block

    H->>M: Serial Dump Request (F0 00 21 10 78 3F F7)
    M-->>H: Serial Response (MAC + serial)
    H->>M: requestTopologyMessage (cmd 0x01)
    M-->>H: deviceTopology (2 devices, 1 connection)

    H->>M: endAPIMode (cmd 0x02)
    H->>M: beginAPIMode (cmd 0x00)
    M-->>H: packetACK

    H->>M: endAPIMode to DNA Block (cmd 0x02, index 1)
    H->>M: beginAPIMode to DNA Block (cmd 0x00, index 1)
    M-->>H: packetACK

    loop Keepalive
        H->>M: ping (cmd 0x03, index 0) every 400ms
        M-->>H: packetACK
        H->>M: ping (cmd 0x03, index 1) every 1666ms
        M-->>H: packetACK
    end

Error Recovery ​

The lifecycle is designed to be self-healing:

  • Serial timeout: retried every 300ms for up to 5 seconds, then topology discovery proceeds without the master serial
  • Topology timeout: re-requested using the runtime retry constants
  • Missed ACK: device is marked as timed out after 6000ms, then rediscovered
  • MIDI port disappears: the DeviceGroup is destroyed and all device state is cleaned up
  • Unexpected disconnect: the daemon continues scanning and will reconnect automatically when the device reappears

Threading Model ​

python-rtmidi callbacks arrive on a separate thread. blocksd marshals all incoming MIDI data to the asyncio event loop using loop.call_soon_threadsafe() and an asyncio.Queue. This ensures all protocol processing happens on the main event loop without locking.

mermaid
graph LR
    RT[rtmidi callback thread] -->|call_soon_threadsafe| Q[asyncio.Queue]
    Q --> EL[Event Loop]
    EL --> DG[DeviceGroup]
    DG --> API[API Server]

Released under the ISC License.