Released: 2026-04-05
The first public release of blocksd, a Linux daemon that implements the complete ROLI Blocks protocol. ROLI Blocks devices require a continuous host-side handshake over MIDI SysEx to enter "API mode"; without it, they display a searching animation and power off. blocksd keeps your devices alive, gives you full LED control, touch/button events, device configuration, and a real-time web dashboard, all on Linux where no official support exists.
🌟 Highlights
✨ Full ROLI Blocks Protocol Implementation
Complete protocol stack for device discovery, topology management, API mode activation, and keepalive pings. The daemon handles the full device lifecycle: serial number request, topology parsing, API mode entry with endAPIMode/beginAPIMode sequencing, and per-device ping scheduling (400ms for master, 1666ms for DNA-connected blocks).
💡 LED Control Pipeline with LittleFoot VM
End-to-end LED control for Lightpad and Lightpad M: a BytecodeAssembler in src/blocksd/littlefoot/ compiles LittleFoot programs that read RGB565 pixel data from the device heap and paint the 15×15 LED grid at ~25 Hz. CLI patterns include solid, gradient, rainbow, checkerboard, and off. Hardware testing against Lightpad Block M firmware v1.1.0 uncovered incompatible opcodes (dupOffset_01, getHeapBits, dupFromGlobal), and the program was rewritten to use only verified-safe opcodes.
🖥️ Real-Time Web Dashboard
Self-contained web UI served by a pure-asyncio HTTP + WebSocket server (zero additional Python dependencies). Built with React 19, TypeScript, Tailwind v4, and a SilkCircuit dark theme. Shows connected devices with battery gauges, topology visualization, and interactive config sliders. Launch with blocksd ui and it opens your browser automatically.
🔌 Dual External APIs
Unix socket ($XDG_RUNTIME_DIR/blocksd/blocksd.sock) for low-latency local IPC and WebSocket (ws://localhost:9010/ws) for browser clients. Both share the same NDJSON + binary frame protocol: discover, subscribe, topology, config_get/config_set, brightness, and 685-byte binary LED frame writes with RGB888→RGB565 conversion.
🛡️ systemd Integration
Zero-dependency sd_notify implementation in src/blocksd/sdnotify.py sends READY=1, WATCHDOG=1 heartbeats, and STATUS= updates. The service template uses Type=notify with WatchdogSec=30, security hardening (ProtectSystem=strict, NoNewPrivileges, PrivateTmp), and Restart=on-failure.
📦 Multi-Platform Packaging
One-line curl installer (install.sh), PyPI package via uv tool install blocksd, and AUR packages (blocksd stable + blocksd-git development). CI/CD pipelines handle linting, type checking, PyPI trusted publishing (OIDC), GitHub Releases, and automatic AUR updates on tag push.
🤖 Protocol & Device Management
- Implemented 4-stage device lifecycle state machine:
REQUESTING_SERIAL→REQUESTING_TOPOLOGY→RUNNING→FAILED TopologyManagerinsrc/blocksd/topology/manager.pypolls MIDI ports every 1.5s, creates per-USBDeviceGroupinstancesMidiConnectioninsrc/blocksd/device/connection.pybridges python-rtmidi threads into asyncio viacall_soon_threadsafequeue marshallingRemoteHeapinsrc/blocksd/protocol/remote_heap.pytracks ACK-based acknowledgments, handles retransmission, and supports multi-packet data-change encoding with budget-aware chunkingDataChangeEncoderimplements skip/set/sequence/repeated commands with last-value optimization and run-length encoding- Deterministic device UIDs via
blake2bhash (stable across sessions, replacing Python's non-deterministichash()) - Registry-based capability queries:
bitmap_grid_dimensions(),supports_bitmap_led_program(),heap_size_for_block()insrc/blocksd/device/registry.py
🐛 Protocol Stability Fixes
- Fixed DNA-connected Lightpad LED flickering by sending
endAPIModeexactly once per session instead of on every topology glitch - Ping timing measured from last send time (not ACK time) for steady cadence matching the C++ reference
- Stopped infinite topology re-request loop by not resetting
_topology_requests_sentinon_topology_end(limits to 4 total) - Fixed
RemoteHeapto sync full heap on unknown device state by mapping unknown bytes totarget ^ 0xFFso they always diff - ALSA sequencer resource leak fixed: rtmidi objects now explicitly deleted via
.delete()in both scan and close paths - API mode activation limited to 10 attempts per device to prevent 200ms hammering
- Reduced
MAX_PACKET_BYTESfrom 200 to 194 to account for SysEx framing overhead in multi-packet transfers
👆 Touch, Button, & Config Events
TouchEventwith normalized x/y (0.0–1.0 from 12-bit), pressure z, and signed velocity components viafrom_raw()factoryButtonEventwith press/release tracking and timestampsConfigValuewith min/max range tracking; 21 knownBlockConfigIditems (velocity sensitivity, MIDI channel, scale, etc.)- CLI:
blocksd config list,blocksd config get <id>,blocksd config set <id> <value>with Rich table display
💡 LittleFoot VM & LED Pipeline
BytecodeAssemblerinsrc/blocksd/littlefoot/assembler.py(300 lines) with label resolution, function table generation, and program checksum calculation matchingroli_LittleFootRunner.hBitmapLEDProgramrenders RGB565 heap data to the 15×15 grid usingfillPixel/makeARGBnative calls- Opcode compatibility validated on hardware: documented unsafe opcodes (
0x11 dupOffset_01,0x40 getHeapBits,0x1C/0x1Dglobals) indocs/FIRMWARE_NOTES.md - Fast-path
dupOffset_01–07optimization disabled in assembler; always emits the generaldupOffset(int8)form - LED patterns in
src/blocksd/led/patterns.py:solid,gradient(horizontal/vertical),rainbow,checkerboard LEDGridinsrc/blocksd/led/bitmap.pywith RGB565 encoding, brightness scaling, andColordataclass with hex conversions
🖥️ Web Dashboard
- HTTP/1.1 static file server with SPA fallback routing and path traversal guards in
src/blocksd/api/http.py - RFC 6455 WebSocket frame codec in
src/blocksd/api/websocket.py(~70 lines) with masking, TEXT/BINARY/CLOSE/PING/PONG opcodes - React 19 + TypeScript SPA in
web/:useBlocksd(WebSocket auto-reconnect),useDevices(5s polling),useTopology,useConfig(debounced writes) - Dashboard with device cards, battery gauges, topology SVG, and device detail view with grouped config sliders
- Event subscription system with per-client queues (1024-message buffer, drops slow clients):
device,touch,button,topology,configcategories - Fixed WebSocket magic GUID to RFC 6455 (
258EAFA5-E914-47DA-95CA-C5AB0DC85B11) - Fixed startup race: API/web servers start before topology manager so event callbacks are wired before device groups are created
- Web UI enabled by default (
web_enabled=TrueinDaemonConfig)
🔌 Unix Socket API
- Hybrid wire protocol: NDJSON for control messages, binary 685-byte frames (
0xBDmagic) for high-speed RGB888 pixel writes - Frame format:
magic(1) + type(1) + uid(8 LE u64) + pixels(675)with automatic RGB565 conversion and per-device brightness scaling - Fixed UID overflow: changed from
<BBL(u32) to<BBQ(u64) for 64-bit topology UIDs - Event broadcaster with per-client subscription queues and backpressure via queue overflow eviction
EventBroadcastershared between Unix socket (ApiServer) and WebSocket (WebServer)
⚙️ CLI & Service Management
blocksd run— start daemon (foreground or--daemonmode, optional--verbose,--config)blocksd ui— launch web dashboard with optional--port,--host,--no-browserblocksd status— quick MIDI port scan;--probefor full connection with serial, battery, type, topologyblocksd led solid '#ff00ff'/rainbow/gradient/checkerboard/offblocksd install— writes systemd user service + udev rules (embedded in Python for PyPI compatibility)blocksd uninstall— stops service, removes service file and udev rules
🔧 CI/CD & Developer Tooling
- CI pipeline with shared workflows: ruff lint, pytest,
tytype checking,libasound2-devsystem dependency - Release pipeline with manual dispatch: version bump (patch/minor/major), dry-run mode, auto-tag + publish trigger
- Publish pipeline: PyPI OIDC trusted publishing (inlined for issuer compatibility), GitHub Release, AUR auto-update
justfilewith 18 recipes:install,run,test,test-mod,lint,lint-fix,fmt,fmt-docs,typecheck,check,fix,build,clean,aur-build- Pre-commit hooks: ruff lint + format, trailing whitespace, YAML/TOML validation, secret detection
- Expanded ruff configuration with 18+ rule groups (C4, ASYNC, PT, S, PL, TRY, T20, ARG, RET, PIE, etc.)
- pytest strict mode with
asyncio_mode: auto, custom markers (slow,integration), coverage config - PEP 561
py.typedmarker for downstream type checking - Dependency groups instead of optional-dependencies so
uv syncincludes dev deps by default
📝 Documentation
README.mdwith centered header, feature table, install methods (curl/PyPI/AUR/source), full CLI usage, architecture diagram, protocol pipeline, and device support matrixdocs/API.mdwith complete external API reference: NDJSON commands, binary frame protocol, event subscriptions, and Hypercolor integration guidancedocs/FIRMWARE_NOTES.mddocumenting Lightpad Block M firmware v1.1.0 VM testing results and opcode compatibilityVISION.mdwith use cases beyond music: dev dashboards, creative coding, accessibility, smart home, gamingCONTRIBUTING.mdwith setup instructions, justfile workflow, and module guide- ISC license
📊 By the Numbers
- 107 files changed across this release
- 12,047 lines added
- 30+ source modules in
src/blocksd/ - 20+ test modules with comprehensive coverage of protocol, LED, assembler, API, and WebSocket layers