Skip to content

Module Structure ​

Each module in blocksd has a clear responsibility and minimal coupling to its neighbors. This page documents what lives where and why.

protocol/ ​

The protocol package is the foundation. It contains pure encoding and decoding functions plus stateful packing buffers and remote heap tracking, with no I/O.

constants.py ​

All protocol enums, SysEx headers, USB product IDs, message type definitions, device commands, config commands, and bit size declarations. This is the single source of truth for protocol constants.

checksum.py ​

The SysEx checksum algorithm: seed with data length, iterate with checksum = checksum * 3 + byte, mask to 7 bits.

packing.py ​

The 7-bit packing engine. Provides Packed7BitWriter (for building outbound messages) and Packed7BitReader (for parsing inbound messages). Values are packed LSB-first, spanning byte boundaries as needed.

This is the highest-risk module in the codebase. Incorrect packing produces packets that the device silently ignores or misinterprets. It has extensive property-based tests using Hypothesis.

builder.py ​

Constructs host-to-device packets. Takes message type, device index, and payload fields; returns a complete SysEx byte sequence including header, packed payload, checksum, and framing.

decoder.py ​

Parses device-to-host packets. Strips SysEx framing, validates checksum, and decodes the 7-bit packed payload into structured message objects. Handles all device-to-host message types including topology, touch events, button events, ACKs, and config messages.

serial.py ​

Serial number request/response handling. Builds the serial dump request packet and parses the response to extract the 16-character serial number.

data_change.py ​

SharedDataChange encoder. Computes the diff between current and target heap state, then encodes it as a compact sequence of skip, set, and RLE commands. This is how LED pixel data and LittleFoot programs are uploaded to the device.

remote_heap.py ​

ACK-tracked heap manager. Maintains the daemon-side view of what the device's heap contains, tracks in-flight data change packets, handles retransmission on timeout, and coalesces rapid updates into the latest target state.

device/ ​

models.py ​

Device state models: the BlockType enum and dataclasses including DeviceInfo, TouchEvent, ButtonEvent, and DeviceConnection.

connection.py ​

The python-rtmidi to asyncio bridge. Wraps MIDI input/output with a callback that marshals incoming SysEx messages to the event loop. Provides synchronous sends and asynchronous receives for the rest of the codebase.

registry.py ​

Maps serial number prefixes to BlockType values. Used during device identification to determine what kind of block we're talking to.

config_ids.py ​

SDK system configuration IDs and their names, displayed by blocksd config list. Config synchronization and API requests use numeric IDs directly.

topology/ ​

detector.py ​

MIDI port scanning. Polls the system's MIDI ports looking for names that match ROLI's naming convention. Pairs inputs and outputs by normalized name and occurrence; it does not inspect USB IDs.

device_group.py ​

The big state machine. Manages the full lifecycle of a USB-connected device group: serial request, topology parsing, API mode activation, keepalive ping loop, touch/button event handling, and cleanup on disconnect. This is where most of the protocol complexity lives.

manager.py ​

The TopologyManager orchestrates DeviceGroups. It runs the 1.5-second scan loop, creates DeviceGroups for new connections, and destroys them when connections are lost. It's the entry point that daemon.py calls.

api/ ​

commands.py ​

Shared JSON and binary command handling for both transports. Each server has its own subscription and brightness state; validation and dispatch use one implementation.

server.py ​

The API server entry point. Creates and manages the Unix socket server and optionally the WebSocket/HTTP server. Handles client connections, request routing, and LED frame dispatch.

protocol.py ​

NDJSON and binary frame parsing/serialization. Distinguishes between binary frames (first byte 0xBD) and JSON messages. Handles request/response mapping.

events.py ​

EventBroadcaster. Pushes device, touch, and button events to subscribed clients. Manages per-client bounded queues and drops slow consumers to prevent server blocking.

websocket.py ​

RFC 6455 WebSocket frame codec. Handles frame encoding/decoding, ping/pong, and close frames; the HTTP module handles the upgrade handshake. No external WebSocket library dependency.

http.py ​

Minimal HTTP parser and static file server. Serves the web dashboard's built static files and handles WebSocket upgrade requests.

led/ ​

bitmap.py ​

The RGB565 LED grid. Provides a 15x15 pixel buffer with color conversion (RGB888 to RGB565), individual pixel access, and full-frame operations. Includes a Color type for hex color parsing.

patterns.py ​

Built-in LED pattern generators: solid fill, horizontal/vertical gradient, rainbow, and checkerboard. Each generator modifies the supplied grid.

littlefoot/ ​

opcodes.py ​

LittleFoot VM opcode values and operand widths from the ROLI SDK. Renderer execution has been checked against the upstream VM; older illegal-instruction reports were reproduced as incorrect host-side jump addresses.

assembler.py ​

Bytecode assembler with program-relative label resolution and the SDK native signature hash. Converts assembly instructions into LittleFoot program binaries with a header, function table, and checksum.

programs.py ​

The 145-byte Lightpad renderer reads a 450-byte RGB565 heap and paints each grid pixel. The 130-byte LUMI renderer in keys.py reads a 48-byte heap and paints each key while preserving the default musical handler. Both initialise the display and answer host execution challenges before the host publishes pixel data.

cli/ ​

app.py ​

The main Typer application. Defines top-level commands (run, ui, status) and registers subcommand groups (led, config, install, uninstall).

led.py, config.py ​

LED pattern and device configuration CLI commands. These create their own TopologyManager and access MIDI directly; they are not socket clients.

install.py ​

systemd service and udev rule installation/uninstallation. Generates the service file and udev rules from templates and writes them to the appropriate system locations.

Other Modules ​

daemon.py ​

The asyncio main loop. Creates the TopologyManager, starts the API server, handles signals (SIGINT, SIGTERM), and manages the sd_notify lifecycle. Owns and joins the manager, shutdown waiter and watchdog tasks, and unwinds partially initialized servers when startup fails.

config/schema.py and config/loader.py ​

Pydantic-based configuration schema and TOML file loader. Parses settings into DaemonConfig. API and web settings are applied; timing fields are currently not forwarded to the topology runtime.

sdnotify.py ​

Lightweight systemd notification client. Sends READY=1, WATCHDOG=1, and status updates over the sd_notify socket without any external dependencies.

Released under the ISC License.