Control a Divoom Timebox Evo (16×16 RGB LED display) directly from macOS and iOS — send arbitrary images, solid colors, and brightness — with no Divoom app, no Node, no Electron/web server, and no ESP32/Raspberry Pi in the control path. Pure Swift over Apple's native Bluetooth stack.
It is primarily a Swift library (TimeboxClient) you can build apps on top of,
plus a command-line tool (timeboxctl) and a minimal SwiftUI shell.
The same image/command encoding drives two transports, selected automatically per platform:
- macOS → Bluetooth Classic SPP/RFCOMM (
IOBluetooth). The Evo's LED protocol runs over Classic SPP; the wire format and image encoding were reverse-engineered from the official Divoom Android app. - iOS → BLE (
CoreBluetooth). The device's BLE "Transparent UART" endpoint is usable — the trick is the JieLi RCSP wrapper (FE EF AA 55 | LEN | payload | sum16) and tunneling the same SPP command bytes through the device's reliable01command channel (01 <seq> 00 00 00 <SPP command>). Reverse-engineered from a BLE capture of the official iOS app.
TimeboxClient exposes one API (connect / setBrightness / setColor / send(image:))
over both. See DEVELOPMENT_NOTES.md for the protocol details.
- macOS (Apple Silicon or Intel), Swift 5.9+
- A Divoom Timebox Evo paired with your Mac in System Settings → Bluetooth
(one-time, manual — it shows up as e.g.
Timebox-Evo-audio). Pairing is the only step that can't be automated.
swift build
swift testList paired devices to find your Timebox's address:
.build/debug/timeboxctl listSend an image / color / brightness (the RFCOMM channel is auto-resolved from the device's SDP — no need to specify one):
.build/debug/timeboxctl image art.png --address <MAC>
.build/debug/timeboxctl color FF0000 --address <MAC>
.build/debug/timeboxctl brightness 40 --address <MAC>Interactive mode — one persistent connection, stream commands:
.build/debug/timeboxctl repl --address <MAC>
# then type: image art.png | color 00FF00 | brightness 25 | quitIf a command times out connecting to the SPP channel, macOS is probably holding it for the paired speaker — power-cycle the Timebox and retry.
Add this package as a SwiftPM dependency and import TimeboxBluetooth (+ TimeboxKit):
import TimeboxBluetooth
import TimeboxKit
let client = TimeboxClient()
// Find the user's Timebox among paired Bluetooth devices:
guard let timebox = TimeboxClient.discoverTimeboxes().first else {
print("No paired Timebox found — pair it in System Settings → Bluetooth first.")
return
}
try await client.connect(to: timebox) // SPP channel auto-resolved from SDP
try await client.send(imageAt: "art.png") // any PNG/JPG/BMP/GIF -> 16×16
try await client.setColor(PixelRGB(red: 255, green: 0, blue: 0))
try await client.setBrightness(60)
client.disconnect()Call the async methods from a context with a running run loop (any AppKit/SwiftUI app). The connection is persistent — connect once, then push images/colors at any time.
Same TimeboxClient, same send/setColor/setBrightness — only connection differs.
There is no manual pairing or SDP step; CoreBluetooth finds the device by name (or
attaches if iOS already holds it connected as a speaker):
let client = TimeboxClient()
try await client.connect() // iOS-only convenience: scans BLE for the Timebox
try await client.setBrightness(80)
try await client.send(image: frame)Add NSBluetoothAlwaysUsageDescription to the app's Info.plist. The transport is
CoreBluetoothRCSPTransport (RCSP over BLE); it is chosen automatically on iOS.
Yes — nothing is hardcoded to one device:
TimeboxClient.discoverTimeboxes()— paired devices whose name contains "Timebox".TimeboxClient.pairedDevices()— all paired devices, so you can present your own picker.- The Bluetooth address comes from discovery; the RFCOMM channel is resolved per
device from its SDP (Serial Port Profile, UUID
0x1101), exactly like the official app.
So any user's Timebox Evo works once they've paired it with their Mac.
TimeboxKit— protocol encoding (TimeboxPacketEncoder,TimeboxImageEncoder),PixelFrame/PixelRGB, andImageToPixelFrameConverter(PNG/JPG → 16×16, CoreGraphics).TimeboxBluetooth—TimeboxClient(high-level API),IOBluetoothTimeboxTransport(macOS Classic SPP),CoreBluetoothRCSPTransport(iOS/macOS BLE via RCSP), and paired-device discovery.
TimeboxStudio/
TimeboxKit/ protocol encoding + image conversion (platform-light)
Bluetooth/ TimeboxClient, IOBluetooth SPP transport, discovery, diagnostics
Persistence/ SavedDesign / DesignStore
Utilities/ hex parsing, logging
App/ UI/ minimal SwiftUI shell
timeboxctl/ command-line tool
TimeboxStudioTests/ packet + image encoding tests
- ✅ Images, solid color, brightness — working on real hardware, macOS (Classic SPP) and iOS (BLE/RCSP).
- ⬜ Not yet: GIF/animation, a full pixel-editor GUI (the SwiftUI shell is minimal), saved-design persistence wired into the UI.
Native macOS Swift only; no Divoom app, Node, Electron/web, or ESP32/Pi in the control path; protocol encoding kept separate from transport; a raw-hex escape hatch is retained.