earctl
Earctl is a Rust HTTP API and CLI for controlling Nothing and CMF earbuds through the BlueZ Serial Port Profile. Run it on any Linux box to expose ANC, EQ, latency, and gesture controls to scripts or other devices on your network.
Key Features
- HTTP API and CLI for Nothing/CMF earbuds via BlueZ RFCOMM.
- Auto-detects connected devices, RFCOMM channel, and model metadata (SKU/serial overrides supported).
- Controls ANC, EQ (including custom EQ), latency, gestures, enhanced bass, case LEDs, and Find My Buds.
Quick Stats
- Default API bind:
127.0.0.1:8787(Axum HTTP server). - Targets Nothing Ear (1), Ear (stick), Ear (2), Ear, Ear (a), Ear (open) and CMF Buds, Buds Pro, Buds Pro 2, Neckband Pro.
- Rust 2024 edition on Tokio; depends on BlueZ utilities (
bluetoothctlandsdptool).
Install
Earctl targets Linux with BlueZ. Pair your earbuds first and ensure bluez, bluez-utils (bluetoothctl), and bluez-deprecated-tools (sdptool) are installed. You need Rust (1.75+ recommended) to build.
git clone https://github.com/DaanHessen/earctl.git
cd earctl
cargo build --release
./target/release/earctl server --addr 0.0.0.0:8787
Install the binary locally from the repo:
cargo install --path .
The Arch package ships a user service. After installing the package, enable it:
systemctl --user enable --now earctl.service
Usage
Start the API server, connect to your buds, then query or set states from the CLI or HTTP.
earctl server --addr 0.0.0.0:8787
earctl auto-connect --name "Nothing Ear"
earctl battery
earctl anc set transparency
earctl custom-eq set --bass 2 --mid 0 --treble -1
earctl ring --enable true --side left
HTTP example:
curl -X POST http://127.0.0.1:8787/api/session/auto-connect \
-H "Content-Type: application/json" \
-d '{ "name": "Nothing Ear" }'
auto-connect reuses the active connection and falls back to channel 1 if SDP discovery fails. You can optionally specify "channel": <number> to override the detected channel.Configuration
Configure earctl through CLI flags. There is no separate config file.
Global Options
| Option | Default | Description |
|---|---|---|
--endpoint | http://127.0.0.1:8787 | Base URL used by client commands to reach the API. |
Server Options
| Option | Default | Description |
|---|---|---|
--addr | 127.0.0.1:8787 | Listen address for earctl server. |
Command Options
auto-connect
| Option | Description |
|---|---|
--bluetooth-address | Explicit MAC address; skips discovery. |
--name | Filter connected devices by a substring of their name. |
--channel | RFCOMM channel fallback when SDP detection fails (default: 1). |
--sku | Override detected model metadata (SKU). |
connect
| Option | Description |
|---|---|
--address | Required. Bluetooth device address. |
--channel | RFCOMM channel (default: 1). |
--model-id | Override model ID. |
--sku | Override SKU. |
--base | Override model base type. |
API Reference
/api/session*
POST /api/session/auto-connect: Detect the connected buds, discover RFCOMM, and open a session.POST /api/session/connect: Connect with an explicit MAC and channel.POST /api/session/detect/POST /api/session/model: Read or override SKU/model metadata.GET /api/session/DELETE /api/session: Show or close the active session.
/api/anc / /api/eq
/api/anc,/api/eq,/api/eq/custom: Read or set ANC and EQ (including custom bands)./api/enhanced-bass: Read or set enhanced bass state (enabled + level)./api/personalized-anc,/api/in-ear,/api/latency: Toggle feature switches per model support./api/gestures: Read or update gesture mappings./api/led-case: Read or set case LED pixels (Ear (1) only).
/api/battery & tools
/api/battery,/api/firmware: Battery readings and firmware string./api/ear-fit: Start an ear-fit test (POST) and read the result (GET)./api/ring: Trigger the Find My Buds tone (loud)./api/session: Returns session ID, port path, and detected model summary.
Contributing
Issues and pull requests are welcome. Please check existing issues, keep changes scoped, and run cargo fmt and cargo test before submitting.
- Fork the repository.
- Create a feature branch.
- Submit a pull request.
License
earctl is licensed under the GNU Affero General Public License v3.0. See LICENSE for details.