Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Repository files navigation

MiniToo Control

Open source macOS tools for controlling a Divoom MiniToo over Bluetooth Classic RFCOMM. The repository includes animated status screens, direct keyboard control, a persistent Swift transport, protocol tests, and a read only RuView sensing bridge.

It does not modify MiniToo or ESP32 firmware. It uses the stock Bluetooth service exposed by a normally paired MiniToo.

What you can build

  1. Animated presence, alert, empty, and degraded screens.
  2. Live RuView occupancy, motion, heart rate, and sensor health displays.
  3. Keyboard controlled physical status terminals.
  4. Room occupancy and meeting room indicators.
  5. Custom renderers using the documented packet and animation pipeline.

Quick start

Requirements are macOS, Node.js 20 or newer, Xcode Command Line Tools, zstd, and a paired MiniToo.

git clone https://github.com/ruvnet/minitoo-control.git
cd minitoo-control
npm ci
npm run build
system_profiler SPBluetoothDataType
MINITOO_ADDRESS=AA:BB:CC:DD:EE:FF npm start -- --offline

Use the physical Mac keyboard while the terminal is focused:

1              EMPTY
2              PRESENCE
3 or A         ALERT
4 or H         DEGRADED
Arrow keys     Cycle screens
Space          Acknowledge local alert
Q              Quit

For a single screen without the long running bridge:

MINITOO_ADDRESS=AA:BB:CC:DD:EE:FF npm run example:state -- presence
MINITOO_ADDRESS=AA:BB:CC:DD:EE:FF npm run example:state -- alert

The MiniToo must be paired first. The default RFCOMM channel is 1.

Boundary

RuView read-only WebSocket       Mac keyboard
             \                    /
              allowlisted state
                     |
             local Node bridge
                     |
            persistent stdin pipe
                     |
          macOS IOBluetooth RFCOMM
                     |
                  MiniToo

The bridge recognizes only presence, occupancy, alert, degraded sensor-health, and derived heart-rate fields. Heart rate appears only during presence when it is between 35 and 220 BPM and both heartbeat confidence and signal quality initially reach 0.4. A 0.3 retention threshold, exponential smoothing, ten-sample loss window, and five-second detail refresh prevent threshold flicker and Bluetooth write churn. Presence, alert, zone, and control-source transitions remain immediate. It is an experimental RF estimate, not a medical measurement. The bridge ignores unrelated entities, caps inbound messages at 256 KiB, defaults the WebSocket origin to loopback, and never writes commands back to RuView. The Swift transport accepts only bounded, checksum-valid Divoom packet files. It exposes no TCP listener.

Requirements

  1. macOS with Xcode Command Line Tools.
  2. Node.js 20 or newer.
  3. zstd on PATH.
  4. A paired MiniToo with its Bluetooth address from system_profiler SPBluetoothDataType.

Build and run

cd minitoo-control
npm ci
npm run build
MINITOO_ADDRESS=AA:BB:CC:DD:EE:FF npm start

The default event source is the local RuView sensing server at ws://127.0.0.1:3001/ws/sensing. Keyboard control remains available while the bridge reconnects if that stream is absent. Full sensing messages are projected immediately to display fields and are never logged or persisted. Display updates are deduplicated by mode, zone, low or medium or high confidence band, motion, people, 10 BPM heart-rate bucket, and local versus live source.

To test the complete bridge without RuView hardware, start the synthetic local stream in one terminal:

npm run example:stream

Then connect the bridge from another terminal:

MINITOO_ADDRESS=AA:BB:CC:DD:EE:FF \
npm start -- --ws-url ws://127.0.0.1:8765

Run without a WebSocket:

MINITOO_ADDRESS=AA:BB:CC:DD:EE:FF npm start -- --offline

Connect to an authenticated local RuView events endpoint:

RUVIEW_MINITOO_WS_TOKEN='<runtime token>' \
MINITOO_ADDRESS=AA:BB:CC:DD:EE:FF \
npm start -- --ws-url ws://127.0.0.1:8000/api/v1/stream/events?event_types=motion,presence

Connect to Homecore state changes:

RUVIEW_MINITOO_WS_TOKEN='<runtime token>' \
MINITOO_ADDRESS=AA:BB:CC:DD:EE:FF \
npm start -- --ws-url ws://127.0.0.1:3000/api/websocket

Tokens come from the environment and are never printed. Non-loopback event URLs fail closed unless --allow-remote is explicit. Use wss:// for any remote connection.

Keyboard controls

1              EMPTY
2              PRESENCE
3 or A         ALERT
4 or H         DEGRADED
Arrow keys     Cycle screens
Space          Acknowledge alert
Q              Quit

Keyboard changes affect only the MiniToo display. They do not acknowledge or mutate RuView alerts.

Display protocol

The renderer produces eight 128 by 128 RGB frames at 125 ms each, compresses them with Zstandard window log 17, and wraps them in checksum-validated Divoom 0x8b packets. The Swift child keeps RFCOMM channel 1 open and requires both the device chunk request and final acknowledgement for every update.

The MiniToo protocol is undocumented by the manufacturer. This implementation follows independently published reverse-engineering evidence from divoom-minitoo-osx and the MiniToo SPP reference.

See docs/protocol.md for framing, compression, acknowledgements, known limits, and safe extension points.

Modify the screens

The useful extension points are deliberately small:

  1. Edit PALETTES and the state labels in src/render.mjs.
  2. Add a bounded state projection in src/state.mjs.
  3. Add the state to DISPLAY_MODES and write a renderer test.
  4. Keep packet length, frame count, checksum, and compressed size checks intact.

examples/send-state.mjs demonstrates direct rendering and transfer. examples/synthetic-ruview-server.mjs demonstrates a local event source. Neither example opens a listener beyond loopback.

Safety boundary

The project writes display frames only. It does not flash firmware, change pairing, write to RuView, acknowledge production alerts, or persist raw sensing data. Remote WebSocket sources require explicit authorization with --allow-remote. Credentials in URLs are rejected.

Heart rate and breathing values derived from RF sensing are experimental indicators, not medical measurements.

Validation

npm test
npm run build
node bridge.mjs --dry-run --offline --no-keyboard

[MEASURED] Unit tests cover event allowlisting, keyboard mapping, loopback enforcement, URL credential rejection, Bluetooth argument validation, pixel-frame bounds, packet checksums, and full Zstandard round-trip reconstruction.

[MEASURED transport, MiniToo firmware 2.4.0] Persistent-channel keyboard transitions completed in 141 to 230 ms across PRESENCE, ALERT, acknowledgement, and DEGRADED. A synthetic loopback RuView sensing event completed event projection, first-time rendering, Bluetooth transfer, device chunk request, and final acknowledgement in 221 ms. Cold startup took 924 ms. These measurements prove the transport path, not human-visible rendering latency.

Physical display rendering is a separate hardware gate. A successful RFCOMM write is insufficient without the expected request, final acknowledgement, and human-visible screen transition.

License

MIT. See LICENSE.

Acceptance test

  1. Start the bridge with the MiniToo address.
  2. Press 2 and verify the display changes to PRESENCE.
  3. Press 3 and verify the red alert animation appears.
  4. Press Space and verify the prior state returns.
  5. Inject a presence event into the configured RuView stream.
  6. Verify the display changes within the [CLAIMED target] of 500 ms and an unrelated entity produces no update.

About

Open source macOS Bluetooth control, animated displays, and RuView sensing examples for the Divoom MiniToo

Topics

Resources

Contributing

Security policy

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages