Krolyxon
Commit 9f993c9

Branches

Refs →

budctlCLAUDE.md

127 lines · 8.5 KB · Markdown

1# budctl
2
3budctl is a Linux CLI (and later a GUI) for controlling wireless earbuds without the vendor app. It has no telemetry, needs no account and never makes a network call.
4
5The first supported device is the **realme Buds T310**. More devices are added as plugins when the owner gets new earbuds.
6
7## Goals
8
9- Control earbud features from Linux: ANC / transparency / off, EQ presets, game mode, touch controls, battery readout, and anything else the buds offer.
10- Two front ends, one core: a `budctl` CLI and later a `budctl-gui`, both thin layers over the same library (`budctl-core`). The GUI is undecided. It will be either a Quickshell/Aurora integration (which can use `budctl --json`) or a standalone Rust GUI.
11- One small, readable codebase that the owner fully understands.
12- Device support is pluggable. Adding a new pair of buds means adding one module under `crates/budctl-core/src/devices/` and registering it in `devices/mod.rs`, without touching transport, CLI or GUI code.
13- Coexist with the phone app. Settings live on the buds, so budctl never stores device state locally beyond user config.
14
15## Non-goals
16
17- No firmware dumping, flashing or modification. We only speak the buds' existing control protocol.
18- No network access of any kind: no update checks, crash reports or analytics.
19- No support for models the owner doesn't have. Don't speculatively add devices.
20- No Windows or macOS support.
21
22## Hard rules
23
241. **No network code.** Nothing in this workspace may open a socket except a Bluetooth socket (RFCOMM for the control link, L2CAP for SDP).
25 - The one exception, approved by the owner: the local system D-Bus Unix socket, used through `zbus` only to talk to BlueZ (`org.bluez`). budctl opens that socket itself at the fixed path, so an environment variable can never redirect it to TCP.
26 - No HTTP clients (reqwest, hyper, ureq, surf, etc.) may appear in the dependency tree, direct or transitive. This is enforced with `cargo deny` (`deny.toml`), clippy `disallowed-types`, and `crates/budctl-core/tests/guards.rs`.
272. **Clean-room protocol work.** Protocol facts (UUIDs, channel numbers, packet framing, command IDs, init sequences) may come from public references, but no other project's source code is ever translated or copied. Write findings into `docs/protocol/<device>.md` in our own words, then implement from that spec.
28 - Committed files do not name or cite those references. Each fact carries an evidence level instead (rule 3).
29 - Only the owner can change this rule, by editing it here.
303. **Never guess command bytes.** Every command in a device module must trace back to an anchor in its protocol spec, and the spec must give the fact's evidence level (`realme_t310.md#sources`): seen on a T310, vendor behavior, another model of the family, derived, or verified by us on hardware (`ours-YYYY-MM-DD`).
31 - Put the spec reference in a doc comment on the constant, as `/// Spec: realme_t310.md#anchor`. `tests/guards.rs` checks that each anchor exists.
324. **Write commands are the risky part.** Read-only commands (battery, current ANC mode, firmware version) get implemented and tested first. Write commands are added one at a time and tested on real hardware before the next one.
335. **No `unwrap()`/`expect()`/`panic!`/indexing outside tests.** Bluetooth fails in many ways, and every failure becomes a typed `budctl_core::Error`. Workspace clippy lints enforce this. `unsafe` is allowed only in `crates/budctl-core/src/transport/socket.rs`.
34
35## Stack
36
37- Rust (edition 2024) in a cargo workspace:
38 - `crates/budctl-core`: library with transport, BlueZ and device plugins.
39 - `crates/budctl-cli`: package and binary `budctl`.
40 - `crates/budctl-gui` will come later.
41- Bluetooth goes through BlueZ:
42 - Raw `AF_BLUETOOTH` sockets via `libc`: RFCOMM for the control link, L2CAP to read the buds' SDP record.
43 - `zbus` (blocking API) only to list paired devices.
44 - Supporting BLE GATT would need a new transport kind in core.
45- CLI: `clap` (derive). It is parsed with `try_parse`, so usage errors exit 1, not clap's default 2. `serde_json` provides `--json`.
46- Tests: `cargo test --workspace`. Lints: `cargo clippy --workspace --all-targets -- -D warnings`. Dependency policy: `cargo deny check bans licenses sources`.
47- Keep dependencies minimal. Every new dependency needs a reason in the commit message. The current direct dependencies:
48 - core: `libc`, `zbus`, `serde`
49 - cli: `clap`, `serde_json`
50
51## Device plugin contract
52
53Each module in `crates/budctl-core/src/devices/` defines one `Device` implementation and is listed in `devices::REGISTRY`. A device declares:
54
55- `name()`, plus `matches()`: how to recognize it from BlueZ data (Bluetooth name and/or service UUID).
56- `rfcomm_uuids()`: the transport. SDP turns the UUID into an RFCOMM channel.
57- `open()`: runs any init sequence the buds need before they accept commands, and returns a `Session`.
58- The features the model supports. The CLI and GUI only show features the connected device declares.
59- Pure encode/decode functions (value → bytes, bytes → value) with no I/O, so they can be unit-tested without hardware.
60
61Many realme / OPPO / OnePlus buds share one protocol family. `devices/realme_t310/frame.rs` and `link.rs` hold the shared framing and request/reply logic, and contain no T310 tables. If a second device from that family is added, move them to `devices/oppo_family/` then, not before.
62
63## Connection behavior
64
65- The control channel is often single-client. If an app on the phone holds it, connecting from Linux may fail. Report that plainly ("control channel busy — is another app connected?") instead of retrying in a loop.
66- CLI commands connect, act and disconnect. The GUI must also release the channel when idle rather than hold it open.
67- Timeouts on every read. Buds that don't answer should produce a clear error, never a hang.
68- While a session waits for anything, it keeps answering the buds' own requests (e.g. the time request). Never `thread::sleep` in core; clippy bans it.
69
70## CLI shape
71
72```
73budctl list # paired devices budctl recognizes
74budctl status # battery, ANC mode, EQ, game mode
75budctl touch # touch controls (gesture table) per bud
76budctl anc off|on|transparency
77budctl eq <preset>
78budctl game on|off
79budctl --device <MAC> ... # when more than one supported device is paired
80budctl --json status # machine-readable output for scripts/status bars ("schema": 1)
81budctl --debug ... # hex-dump every frame sent/received (stderr)
82```
83
84Write commands (`anc`, `eq`, `game`, `set`, `bass`, `touch <side> …`, `find`, `fit-test`, custom EQ) are added one at a time, following rule 4. The plan is in `docs/protocol/realme_t310.md#roadmap`.
85
86Exit codes:
87
88| Code | Meaning |
89|---|---|
90| 0 | success |
91| 1 | usage error |
92| 2 | device not found / not connected |
93| 3 | channel busy |
94| 4 | device rejected command or timed out |
95
96## Roadmap
97
981. **Spec**: `docs/protocol/realme_t310.md` and `docs/protocol/transport.md`. Done when they cover transport, framing, handshake and the command table with sources.
992. **Read-only CLI**: connect, handshake, `list`, `status`, `touch`. Verify on real buds (checkpoint HW1).
1003. **Write commands**, one at a time, each verified on hardware before the next:
101 1. ANC
102 2. ANC levels
103 3. game mode
104 4. EQ preset
105 5. switches (bass boost, 3D sound, wind; dual connection last)
106 6. bass level
107 7. find
108 8. touch controls
109 9. hold cycle
110 10. fit test
111 11. custom EQ
1124. **GUI**: Quickshell/Aurora via `--json`, or a Rust GUI on top of the same core.
1135. **More devices**: as acquired.
114
115## Working with Claude
116
117- Claude has no access to the buds. Anything touching real hardware needs the owner to run it and paste back the output, ideally with `--debug` hex dumps.
118- Each hardware checkpoint goes into `docs/protocol/realme_t310.md#verification-log`, with the date and the evidence level ("ours-YYYY-MM-DD").
119- When the spec is unclear, ask for a capture rather than guessing:
120 - `sudo btmon -w file.btsnoop` on Linux shows budctl's own traffic.
121 - An Android "Bluetooth HCI snoop log" shows what realme Link sends.
122- Keep the encode/decode logic pure and covered by tests, so most changes can be verified without hardware.
123- Before adding a feature, check the spec says the T310 supports it. The protocol family covers many models, and not every command applies to this one. The T310's command bitmap (spec §5) is the authority: never send a command whose bit is off.
124
125## License
126
127GPL-3.0-or-later (`LICENSE`). Rule 2 forbids copying other projects' code.