Skip to content

Commit dfb7d4e

Browse files
committed
Apply imported advanced EQ end-to-end (CLI --import-eq)
Confirmed activation chain on Ear (3): set bands (0xF041), select EQ mode 4 (0xF010), enable advanced EQ (0xF04F). NothingController gains apply_custom_eq(); CLI --import-eq decodes a Nothing X share code and applies the full 8-band profile. https://claude.ai/code/session_014KrpftzUgVFGFXVUU3DUTT
1 parent 77aa59f commit dfb7d4e

3 files changed

Lines changed: 53 additions & 1 deletion

File tree

src/nothing_ear/app.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ def _build_parser() -> argparse.ArgumentParser:
5252
choices=["anc", "eq", "custom-eq", "advanced-eq", "battery", "firmware", "serial"],
5353
help="Read a setting from the device and print the raw response (no GUI).",
5454
)
55+
parser.add_argument(
56+
"--import-eq",
57+
metavar="CODE",
58+
help="Apply a Nothing X advanced EQ share code and exit (no GUI).",
59+
)
5560
parser.add_argument(
5661
"--address",
5762
help="Device MAC address (auto-detected if omitted).",
@@ -110,6 +115,10 @@ def main() -> int:
110115
from .cli import cmd_read
111116

112117
return cmd_read(args.address, args.read, args.channel)
118+
if args.import_eq is not None:
119+
from .cli import cmd_import_eq
120+
121+
return cmd_import_eq(args.address, args.import_eq, args.channel)
113122

114123
return _run_gui(args.mock)
115124

src/nothing_ear/bluetooth/protocol.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
CMD_EQ = 0xF010
3232
CMD_CUSTOM_EQ = 0xF041
3333
CMD_CUSTOM_EQ_STATUS = 0xC044
34+
CMD_ADVANCED_EQ_ENABLED = 0xF04F
35+
36+
EQ_MODE_CUSTOM = 0x04
3437

3538
REQUEST_COMMANDS = {
3639
"anc": 0xC01E,
@@ -106,8 +109,16 @@ def anc_frame(mode: ANCMode, fsn: int = 0) -> bytes:
106109
return build_frame(CMD_ANC, bytes([0x01, _ANC_VALUE[mode], 0x00]), fsn)
107110

108111

112+
def eq_mode_frame(mode_value: int, fsn: int = 0) -> bytes:
113+
return build_frame(CMD_EQ, bytes([mode_value, 0x00]), fsn)
114+
115+
109116
def eq_preset_frame(preset: EQPreset, fsn: int = 0) -> bytes:
110-
return build_frame(CMD_EQ, bytes([_EQ_VALUE[preset], 0x00]), fsn)
117+
return eq_mode_frame(_EQ_VALUE[preset], fsn)
118+
119+
120+
def advanced_eq_enable_frame(enabled: bool, fsn: int = 0) -> bytes:
121+
return build_frame(CMD_ADVANCED_EQ_ENABLED, bytes([0x01 if enabled else 0x00]), fsn)
111122

112123

113124
def query_anc_frame(fsn: int = 0) -> bytes:
@@ -241,6 +252,11 @@ def query_raw(self, command: int, payload: bytes = b"") -> bytes:
241252
def set_custom_eq(self, bands) -> None:
242253
self.send_frame(custom_eq_frame(bands, self._next_fsn()))
243254

255+
def apply_custom_eq(self, bands) -> None:
256+
self.send_frame(custom_eq_frame(bands, self._next_fsn()))
257+
self.send_frame(eq_mode_frame(EQ_MODE_CUSTOM, self._next_fsn()))
258+
self.send_frame(advanced_eq_enable_frame(True, self._next_fsn()))
259+
244260
def get_custom_eq(self):
245261
resp = self.query_raw(CMD_CUSTOM_EQ_STATUS)
246262
return decode_custom_eq(frame_payload(resp))

src/nothing_ear/cli.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,33 @@ def cmd_eq(address: str | None, preset: str, channel: int) -> int:
108108
return 0
109109

110110

111+
def cmd_import_eq(address: str | None, code: str, channel: int) -> int:
112+
addr = _resolve_address(address)
113+
if addr is None:
114+
return 1
115+
116+
from .bluetooth.protocol import NothingController
117+
from .bluetooth.share import decode_profile
118+
119+
try:
120+
profile = decode_profile(code.strip())
121+
except Exception as exc:
122+
print(f"Could not decode EQ profile: {exc}")
123+
return 1
124+
125+
try:
126+
with NothingController(addr, channel=channel) as ctrl:
127+
ctrl.apply_custom_eq(profile.bands)
128+
except OSError as exc:
129+
print(f"RFCOMM connection on channel {channel} failed: {exc}")
130+
return 1
131+
132+
print(f"Applied '{profile.name}' ({len(profile.bands)} bands):")
133+
for band in profile.bands:
134+
print(f" {band.frequency:8.0f} Hz {band.gain:+5.1f} dB Q {band.q:.2f}")
135+
return 0
136+
137+
111138
def cmd_read(address: str | None, name: str, channel: int) -> int:
112139
addr = _resolve_address(address)
113140
if addr is None:

0 commit comments

Comments
 (0)