Skip to content

Release v0.4.0

Latest

Choose a tag to compare

@github-actions github-actions released this 05 Apr 16:37
· 1 commit to main since this release

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_SERIALREQUESTING_TOPOLOGYRUNNINGFAILED
  • TopologyManager in src/blocksd/topology/manager.py polls MIDI ports every 1.5s, creates per-USB DeviceGroup instances
  • MidiConnection in src/blocksd/device/connection.py bridges python-rtmidi threads into asyncio via call_soon_threadsafe queue marshalling
  • RemoteHeap in src/blocksd/protocol/remote_heap.py tracks ACK-based acknowledgments, handles retransmission, and supports multi-packet data-change encoding with budget-aware chunking
  • DataChangeEncoder implements skip/set/sequence/repeated commands with last-value optimization and run-length encoding
  • Deterministic device UIDs via blake2b hash (stable across sessions, replacing Python's non-deterministic hash())
  • Registry-based capability queries: bitmap_grid_dimensions(), supports_bitmap_led_program(), heap_size_for_block() in src/blocksd/device/registry.py

🐛 Protocol Stability Fixes

  • Fixed DNA-connected Lightpad LED flickering by sending endAPIMode exactly 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_sent in on_topology_end (limits to 4 total)
  • Fixed RemoteHeap to sync full heap on unknown device state by mapping unknown bytes to target ^ 0xFF so 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_BYTES from 200 to 194 to account for SysEx framing overhead in multi-packet transfers

👆 Touch, Button, & Config Events

  • TouchEvent with normalized x/y (0.0–1.0 from 12-bit), pressure z, and signed velocity components via from_raw() factory
  • ButtonEvent with press/release tracking and timestamps
  • ConfigValue with min/max range tracking; 21 known BlockConfigId items (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

  • BytecodeAssembler in src/blocksd/littlefoot/assembler.py (300 lines) with label resolution, function table generation, and program checksum calculation matching roli_LittleFootRunner.h
  • BitmapLEDProgram renders RGB565 heap data to the 15×15 grid using fillPixel/makeARGB native calls
  • Opcode compatibility validated on hardware: documented unsafe opcodes (0x11 dupOffset_01, 0x40 getHeapBits, 0x1C/0x1D globals) in docs/FIRMWARE_NOTES.md
  • Fast-path dupOffset_0107 optimization disabled in assembler; always emits the general dupOffset(int8) form
  • LED patterns in src/blocksd/led/patterns.py: solid, gradient (horizontal/vertical), rainbow, checkerboard
  • LEDGrid in src/blocksd/led/bitmap.py with RGB565 encoding, brightness scaling, and Color dataclass 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, config categories
  • 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=True in DaemonConfig)

🔌 Unix Socket API

  • Hybrid wire protocol: NDJSON for control messages, binary 685-byte frames (0xBD magic) 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
  • EventBroadcaster shared between Unix socket (ApiServer) and WebSocket (WebServer)

⚙️ CLI & Service Management

  • blocksd run — start daemon (foreground or --daemon mode, optional --verbose, --config)
  • blocksd ui — launch web dashboard with optional --port, --host, --no-browser
  • blocksd status — quick MIDI port scan; --probe for full connection with serial, battery, type, topology
  • blocksd led solid '#ff00ff' / rainbow / gradient / checkerboard / off
  • blocksd 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, ty type checking, libasound2-dev system 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
  • justfile with 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.typed marker for downstream type checking
  • Dependency groups instead of optional-dependencies so uv sync includes dev deps by default

📝 Documentation

  • README.md with centered header, feature table, install methods (curl/PyPI/AUR/source), full CLI usage, architecture diagram, protocol pipeline, and device support matrix
  • docs/API.md with complete external API reference: NDJSON commands, binary frame protocol, event subscriptions, and Hypercolor integration guidance
  • docs/FIRMWARE_NOTES.md documenting Lightpad Block M firmware v1.1.0 VM testing results and opcode compatibility
  • VISION.md with use cases beyond music: dev dashboards, creative coding, accessibility, smart home, gaming
  • CONTRIBUTING.md with 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