BLE tooling and protocol / reverse-engineering notes for DG01-class LCD pins (SuperBand / FitPro-style OEM apps). Primary reference: PROTOCOL.md.
| Path | Purpose |
|---|---|
dg01-ble/ |
Rust CLI on Linux (BlueZ via bluer): scan, find, sync-time, query, device-info, battery-watch, dial-dims, dial-status, file-send-status, upload-dial — see PROTOCOL.md; APK ↔ tool parity notes: dg01-ble/APK_PARITY.md |
PROTOCOL.md |
GATT map, framing, command IDs from APK analysis and local captures |
ebadge_inspect.py, superband_find_device.py |
Python helpers (Bleak path; flaky vs BlueZ in practice) |
capture_le_passive.sh, apk-get |
Shell helpers |
Not tracked in git: APKs, JADX output tree (superband_jadx_src/), and the whole tools/ tree (local JADX install / zip) — download or regenerate locally.
- dg01-ble: Rust toolchain, Bluetooth adapter managed by BlueZ (typical Linux desktop / Pi).
The connect and disconnect subcommands use the same D-Bus methods as the Ubuntu Bluetooth settings toggle: org.bluez.Device1.Connect and Disconnect (via bluer, same path as bluetoothctl). Before connect, the tool sets Trusted=true when BlueZ allows it (helps unpaired LE peripherals — the panel shows Paired: No for some devices).
- Default: no extra LE scan before connect (
--warm-scan-secsdefaults to 0). Use--warm-scan-secs Nonly if the device has never been seen by BlueZ and the D-Bus device object is missing. - If generic
Connecthangs on LE-only gear, try--nus-profile-connect(ConnectProfileon the NUS service UUID).
cd dg01-ble && cargo run --release -- connect --addr 0A:93:79:0C:DD:20
cd dg01-ble && cargo run --release -- disconnect --addr 0A:93:79:0C:DD:20dg01-ble find opens the link with Device1.Connect, then writes the find payload to the NUS TX characteristic. BlueZ returns quickly if the ACL is already up. --connect-timeout-secs, --nus-profile-connect, and --reconnect cover flaky links. See PROTOCOL.md.
cd dg01-ble && cargo run --release -- find --addr 0A:93:79:0C:DD:20Optional --warm-scan-secs N ( N > 0 ): run LE discovery only when the address is not yet in BlueZ’s cache and you need to populate the device before Connect.
If the phone app holds the only LE link, disconnect it or turn phone Bluetooth off so Linux can connect.
Quick BlueZ check (no Connect): cargo run --release -- is-connected --addr 0A:93:79:0C:DD:20. Exit status 1 if Connected is false (prints ServicesResolved for info only).
Subcommand device-info connects and reads:
- Device Information (0x180A): manufacturer, model, serial, firmware, hardware, software, system ID, IEEE regulatory, PnP ID — decoded like nRF Connect (UTF-8 strings; PnP fields broken out).
- Battery Service (0x180F): Battery Level (0x2A19) — SIG defines one octet 0–100 as %; some OEMs return extra octets; the decoded Value uses the first octet only;
device-infostill prints a Raw hex line for the full read.
If 0x180F is absent, a one-line notice is printed; 0x180A is still required for the command to succeed.
cd dg01-ble && cargo run --release -- device-info --addr 0A:93:79:0C:DD:20
cd dg01-ble && cargo run --release -- device-info --addr 0A:93:79:0C:DD:20 --disconnectVendor UART query (cmd 26) is separate from SIG GATT — use both if you want APK-style keys and standard DIS/battery reads.
Subcommand battery-watch connects, subscribes to Battery Level (0x2A19) NOTIFY, and prints a line only when the device pushes a new value (no periodic GATT reads). If NOTIFY is missing or subscribe fails, it falls back to polling with --interval-secs (default 10).
Use --duration-secs N to stop after N seconds, or 0 to run until Ctrl+C. --disconnect ends the BLE session when the command exits so the adapter does not leave the link up (same idea as device-info --disconnect).
cd dg01-ble && cargo run --release -- battery-watch --addr 0A:93:79:0C:DD:20 --duration-secs 300 --disconnect
cd dg01-ble && cargo run --release -- battery-watch --addr 0A:93:79:0C:DD:20 --duration-secs 0 --disconnectdial-dims sends the same getDialClockInfo frame as the Android app (cmd 32 sub 2), reassembles 0xCD notifications if split, and prints width, height, and expected RGB565 payload size. Use this before upload-dial or pass --use-device-dial-dims on upload so image size is not guessed.
cd dg01-ble && cargo run --release -- dial-dims --addr 0A:93:79:0C:DD:20 --disconnectThere is no CLI command to list the phone-style catalogue of installed watch faces — that UI is driven by the app’s HTTP API; see PROTOCOL.md (watchface section).
Uploading splash on the badge: the stock app waits for a start ACK (status 1000) after cmd 31/2 before sending chunks; --skip-start-ack skips that wait and may mean the device never shows the uploading screen even if data still transfers — see PROTOCOL.md.
The DG01 exposes Nordic UART Service with UUIDs 7e400001 / 7e400002 / 7e400003 (see PROTOCOL.md). The SuperBand / FitPro Android build uses the same 128-bit layout but with 6e40… instead of 7e40….
- Talking to a real DG01 from Linux: use defaults — do not pass
--apk-uartonquery,upload-dial,dial-status, etc. Wrong UUIDs produce “characteristic not found” even when the link is up. --apk-uart: only when emulating APK wire captures or a peripheral that actually exposes6e400002.
FitPro uses periodic readStatus()-style UART polls during long transfers. dg01-ble exposes the same no-payload frames for debugging stalls:
| Subcommand | Frame (APK) | Use |
|---|---|---|
dial-status |
getNoValueProtocol(32, 1) — getDialUpdateStatus() |
Dial (cmd 31) path |
file-send-status |
getNoValueProtocol(35, 1) — getFileSendStatus() |
File (cmd 34) path |
# DG01 on Linux — omit --apk-uart (defaults use 7e400002/7e400003)
cd dg01-ble && cargo run --release -- dial-status --addr 0A:93:79:0C:DD:20 --disconnect
cd dg01-ble && cargo run --release -- file-send-status --addr 0A:93:79:0C:DD:20 --disconnectWatchface upload (DG01, solid test pattern) — phone Bluetooth off; if Connect() hangs, disconnect the badge once (bluetoothctl or dg01-ble disconnect) and retry:
cd dg01-ble && cargo run --release -- upload-dial \
--addr 0A:93:79:0C:DD:20 \
--solid --use-device-dial-dims \
--preflight-upload2 \
--apk-parity \
--disconnectOmit --reconnect if the link is already stable; add --reconnect if GATT was stale. Use --skip-start-ack only for debugging (see PROTOCOL.md).
Automated matrix (iPhone / APK order, uploading UI checks): run dg01-ble/scripts/run_upload_like_app_tests.sh from the dg01-ble directory after cargo build --release. It runs is-connected, device-info, dial-dims, several dial-start-probe sequences with --preflight-upload2, then optional full solid uploads. Set SKIP_FULL_UPLOAD=1 to skip long transfers; DG01_ADDR=… to override the MAC.
Extra probes (no capture needed): dg01-ble/scripts/run_extra_ble_probes.sh runs sync-time, query, dial-status, small 64×64 upload attempts (often fail start ACK if firmware expects 360×360 — see script header), and optional RUN_DIM_MATCH_UPLOAD=1 for a full–screen solid with --skip-start-ack (debug only; long run).
upload-dial defaults are tuned toward phone captures: --gatt-fragment-gap-ms 3, --step-timeout-ms 10000. --apk-parity enforces at least 10 s step timeout and 3 ms fragment gap. Other useful flags:
--min-battery-percent N— read BAS (same service asdevice-info) before sending payload; abort if level is below N (0 = off).--chunk-write-retries— optional resend of the same chunk after an ACK timeout (default 1 extra attempt).--dial-read-status-after-start/--file-read-status-after-start— after start ACK 1000, send cmd 32/1 or cmd 35/1 and drain notifies (matches APK status-poll style).
Full behaviour and backlog: dg01-ble/APK_PARITY.md.
Tooling and documentation in this repository are provided as-is for interoperability research. Third-party apps and firmware remain under their respective terms.