fiioctl 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fiioctl-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ethan Barclay
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
fiioctl-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,384 @@
1
+ Metadata-Version: 2.4
2
+ Name: fiioctl
3
+ Version: 0.1.0
4
+ Summary: Minimal, extensible CLI for controlling FiiO USB DACs over HID
5
+ Author: Ethan Barclay
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/ethanbarclay/fiioctl
8
+ Project-URL: Repository, https://github.com/ethanbarclay/fiioctl
9
+ Project-URL: Issues, https://github.com/ethanbarclay/fiioctl/issues
10
+ Project-URL: Protocol, https://github.com/ethanbarclay/fiioctl/blob/main/docs/PROTOCOL.md
11
+ Keywords: fiio,usb-dac,hid,equalizer,parametric-eq,autoeq,audio
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Operating System :: MacOS :: MacOS X
17
+ Classifier: Operating System :: Microsoft :: Windows
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Multimedia :: Sound/Audio
24
+ Classifier: Topic :: System :: Hardware :: Universal Serial Bus (USB)
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: hidapi>=0.15
29
+ Provides-Extra: ui
30
+ Requires-Dist: textual>=8.0; extra == "ui"
31
+ Provides-Extra: cloud
32
+ Requires-Dist: cryptography>=44; extra == "cloud"
33
+ Provides-Extra: all
34
+ Requires-Dist: textual>=8.0; extra == "all"
35
+ Requires-Dist: cryptography>=44; extra == "all"
36
+ Provides-Extra: dev
37
+ Requires-Dist: pytest>=9; extra == "dev"
38
+ Requires-Dist: pytest-asyncio>=1.4; extra == "dev"
39
+ Requires-Dist: pytest-cov>=7; extra == "dev"
40
+ Requires-Dist: textual>=8.0; extra == "dev"
41
+ Requires-Dist: cryptography>=44; extra == "dev"
42
+ Dynamic: license-file
43
+
44
+ # fiioctl
45
+
46
+ [![CI](https://github.com/ethanbarclay/fiioctl/actions/workflows/ci.yml/badge.svg)](https://github.com/ethanbarclay/fiioctl/actions/workflows/ci.yml)
47
+
48
+ Control FiiO USB DACs from the command line on Linux, macOS and Windows. A small,
49
+ declarative reimplementation of what the FiiO Control web app does over WebHID,
50
+ built to be scripted.
51
+
52
+ The CLI core is ~1500 lines of Python with a single runtime dependency
53
+ (`hidapi`) and no daemon. The terminal UI and FiiO-catalogue support are optional
54
+ extras on top.
55
+
56
+ Session output below is illustrative; see
57
+ [Verification status](#verification-status) for what has actually been run.
58
+
59
+ ```console
60
+ $ fiioctl info
61
+ product FIIO KA15
62
+ matched_profile FIIO KA15
63
+ report_id 7
64
+ firmware 1.14
65
+ bands 10
66
+ capabilities eq, peq, preset-names, volume, filter, balance
67
+
68
+ $ fiioctl eq search "hd 650"
69
+ 0 Sennheiser HD 650 [oratory1990 / over-ear]
70
+ 1 Sennheiser HD 650 [crinacle / GRAS 43AG-7 over-ear]
71
+ 2 Sennheiser HD 650 [Kuulokenurkka / over-ear]
72
+
73
+ $ fiioctl eq apply "hd 650" --slot user1 --save
74
+ applied 'Sennheiser HD 650' (oratory1990): 10 bands, preamp -5.8 dB
75
+
76
+ $ fiioctl peq show
77
+ global gain -5.8 dB
78
+ 0 105 Hz +5.5 dB Q 0.70 low-shelf
79
+ 1 180 Hz -3.3 dB Q 0.57 peak
80
+ ...
81
+ ```
82
+
83
+ ## Install
84
+
85
+ ```bash
86
+ pipx install . # or: pip install -e .
87
+ ```
88
+
89
+ The one dependency is **`hidapi`** (cython-hidapi), whose wheels bundle the
90
+ hidapi library, so there is nothing to install by hand on any platform.
91
+
92
+ > **Watch out for the `hid` package.** A *different* PyPI distribution named
93
+ > `hid` also installs a top-level `hid` module, with an incompatible API and no
94
+ > bundled library. Its package directory shadows cython-hidapi's module, and you
95
+ > get `ImportError: Unable to load any of the following libraries`. If you hit
96
+ > that: `pip uninstall hid`.
97
+
98
+ If you use **pyenv-win**, console scripts land in the version's `Scripts`
99
+ directory, which is not on `PATH` — run `pyenv rehash` once after installing to
100
+ generate the shim, otherwise `fiioctl` is "not recognized".
101
+
102
+ On Linux, allow non-root access to the device:
103
+
104
+ ```bash
105
+ sudo install -m644 packaging/99-fiio.rules /etc/udev/rules.d/
106
+ sudo udevadm control --reload-rules && sudo udevadm trigger
107
+ ```
108
+
109
+ Then re-plug the DAC. On macOS and Windows no setup is needed.
110
+
111
+ ## What it does
112
+
113
+ | Area | Commands |
114
+ |---|---|
115
+ | Discovery | `fiioctl devices`, `fiioctl info`, `fiioctl state` |
116
+ | Any setting | `fiioctl get <name>`, `fiioctl set <name> <value>` |
117
+ | EQ presets | `fiioctl eq preset [name]`, `eq name <slot> [NAME]`, `eq save [slot]` |
118
+ | Headphone EQ | `fiioctl eq search <query>`, `fiioctl eq apply <query> [--slot] [--save]` |
119
+ | Parametric EQ | `fiioctl peq show`, `peq set <band> --freq --gain --q --filter`, `peq import/export`, `peq clear` |
120
+ | FiiO catalogue | `fiioctl cloud list`, `cloud show <n>`, `cloud apply <n>` |
121
+ | Terminal UI | `fiioctl ui` |
122
+ | Escape hatch | `fiioctl raw <cmd> [hex] [--read]`, global `--trace` |
123
+ | Machine use | `fiioctl schema`, `fiioctl batch`, `--json` on everything |
124
+
125
+ `fiioctl get`/`set` cover every decoded command — volume, volume limit, line-out
126
+ switch, channel balance, DAC filter, EQ master switch, EQ global gain, indicator
127
+ LED, screen orientation, and the microphone controls on devices that have one.
128
+ Run `fiioctl set --help` for the current list.
129
+
130
+ ## Terminal UI
131
+
132
+ ```bash
133
+ pip install 'fiioctl[all]'
134
+ fiioctl ui
135
+ ```
136
+
137
+ Four pages down the left — **Device**, **Equaliser**, **EQ slots**, **Library** —
138
+ switched with `1`–`4`. One rule everywhere: **arrow to a thing, press `+` or `-`
139
+ to change it.** No buttons, no forms to submit, and the hint line at the bottom
140
+ always names what is selected and what the step is, so there is nothing to
141
+ memorise.
142
+
143
+ ```
144
+ 1 Device │ # frequency gain Q filter global gain -7.0 dB
145
+ 2 Equaliser │ 0 32 Hz +3.0 dB 0.71 peak +6 ┤ ━━━
146
+ 3 EQ slots │ 1 64 Hz +3.0 dB 0.71 peak ┤ ━━━ ━━━
147
+ 4 Library │ 2 125 Hz +2.5 dB 0.71 peak ┤━━ ━ ━ ━━━
148
+ │ 3 250 Hz -0.5 dB 0.71 peak 0 ┼────────────━───━━─────━━━━━━━
149
+ │ 4 500 Hz -2.5 dB 0.71 peak ┤ ━━━━━
150
+ ─────────────────────────────────────────────────────────────────────────────────────────────
151
+ selected band 2 · gain = +2.5 dB
152
+ + / - change by 0.5 dB PgUp / PgDn by 5 dB Tab switch 1-4 pages ? keys
153
+ ```
154
+
155
+ - **Device** — every onboard setting the model exposes: volume, volume limit,
156
+ line-out switch, channel balance, DAC filter, EQ preset, global gain, and the
157
+ LED/screen/microphone controls on devices that have them.
158
+ - **Equaliser** — the band table with a live response curve, computed from the
159
+ same RBJ biquads the official app uses.
160
+ - **EQ slots** — every preset slot with its stored name and which one is active.
161
+ `Enter` loads one, `n` renames a user slot, `s` saves the current EQ into it.
162
+ - **Library** — search AutoEq *and* FiiO's catalogue together, with a curve
163
+ preview for the highlighted result before you commit it.
164
+
165
+ `u` undoes the last write, `r` re-reads from the device, `?` lists every key.
166
+
167
+ ## FiiO's own preset catalogue
168
+
169
+ `fiioctl cloud` reads the presets FiiO and other users publish for your exact
170
+ model — the same lists the official web app shows, anonymously and read-only:
171
+
172
+ ```console
173
+ $ fiioctl cloud list
174
+ 0 FH3 FOR KA15 [fiio-official] 10 bands, preamp +8.0 dB, 27 dl
175
+ 1 FF5 FOR KA15 [fiio-official] 10 bands, preamp +6.0 dB, 26 dl
176
+ 2 FT1 FOR KA15 [fiio-official] 10 bands, preamp -6.0 dB, 20 dl
177
+ ...
178
+ 9 Simgot EW300 to VDSF Target [fiio-community] by ... 10 bands, 10 dl
179
+ ```
180
+
181
+ `cloud show <n>` prints the bands and curve; `cloud apply <n>` writes it. Needs
182
+ `pip install 'fiioctl[cloud]'`. Protocol details — including the AES/RSA envelope
183
+ and FiiO's measurement service — are in [docs/CATALOGUE.md](docs/CATALOGUE.md).
184
+
185
+ ## Headphone EQ profiles
186
+
187
+ Profiles come from [AutoEq](https://github.com/jaakkopasanen/AutoEq) — the same
188
+ measurement data FiiO's own app credits as its source. The index (~850 KB) is
189
+ cached locally, so search is instant and works offline after the first run.
190
+ `--refresh` is not needed; the cache expires after 30 days.
191
+
192
+ `eq apply` maps a profile onto the device's bands, clamps gains to what the
193
+ hardware accepts, flattens any bands the profile does not use, and tells you if
194
+ the profile had more bands than the device has.
195
+
196
+ AutoEq does not carry everything — Drop collaborations such as the DT 177X Go are
197
+ absent from it. For those, use `fiioctl autoeq`, below.
198
+
199
+ ## FiiO's measurement database (~4500 headphones)
200
+
201
+ Bigger than AutoEq's public set, and it includes per-variant measurements. This
202
+ runs the same server-side AutoEq the official web app uses, so the result matches
203
+ what that UI would produce:
204
+
205
+ ```console
206
+ $ fiioctl autoeq refresh
207
+ refreshed: 4544 measurements, 4578 targets
208
+
209
+ $ fiioctl autoeq search "dt177x go"
210
+ 714 Beyerdynamic DT 177X Go [leather earpads] — oratory1990
211
+ 715 Beyerdynamic DT 177X Go [velour earpads] — oratory1990
212
+
213
+ $ fiioctl autoeq show 714
214
+ Beyerdynamic DT 177X Go (leather earpads) [fiio-autoeq/oratory1990]
215
+ target: Harman over-ear 2018 (id 6)
216
+ preamp -6.6 dB
217
+ 105 Hz -7.5 dB Q 0.70 low-shelf
218
+ 133 Hz -5.9 dB Q 1.15 peak
219
+ ...
220
+ 10000 Hz +2.4 dB Q 0.70 high-shelf
221
+
222
+ $ fiioctl autoeq apply 714 --save
223
+ ```
224
+
225
+ The target curve is chosen the way FiiO chooses it — matching the measurement's
226
+ rig and form factor against their own recommendation table — or pick one yourself
227
+ with `--target <id>` after browsing `fiioctl autoeq targets harman`.
228
+
229
+ The two indexes (~800 KB) are cached locally for 14 days; `autoeq refresh`
230
+ re-downloads them. Individual frequency-response curves are fetched lazily and
231
+ then cached, rather than mirroring ~90 MB of data nobody asked for.
232
+
233
+ ## Scripting and agents
234
+
235
+ Every command takes `--json` and returns a stable exit code: `0` ok, `2` usage,
236
+ `3` device error, `4` data error.
237
+
238
+ `fiioctl schema` dumps the entire model — framing, every command with its ID,
239
+ codec, range and capability, the per-device registry, and the batch protocol —
240
+ as one JSON document. That is the intended way for an agent to discover what it
241
+ can do, instead of parsing `--help`.
242
+
243
+ For many operations in a row, `fiioctl batch` reads one JSON object per line and
244
+ keeps the device open, which removes both interpreter startup and the
245
+ ~10–50 ms device-open cost from each step:
246
+
247
+ ```console
248
+ $ printf '%s\n' \
249
+ '{"op":"set","name":"eq-gain","value":-6}' \
250
+ '{"op":"band","index":0,"freq":105,"gain":5.5,"q":0.7,"filter":"low-shelf"}' \
251
+ '{"op":"get","name":"volume"}' | fiioctl batch
252
+ {"ok": true, "value": -6.0, "request": "..."}
253
+ {"ok": true, "request": "..."}
254
+ {"ok": true, "value": 42, "request": "..."}
255
+ ```
256
+
257
+ `--dry-run` builds and prints packets without writing, and works with no device
258
+ attached — useful for testing and for inspecting what a command would send:
259
+
260
+ ```console
261
+ $ fiioctl --dry-run peq set 2 --freq 9140 --gain 5.6 --q 3.46
262
+ TX 07 aa 0a 00 00 15 08 02 00 38 23 b4 01 5a 00 00 ee
263
+ band 2: 9140 Hz +5.6 dB Q 3.46 peak
264
+ ```
265
+
266
+ ## Extending it
267
+
268
+ Adding a decoded command is one line in `fiioctl/registry.py`:
269
+
270
+ ```python
271
+ "my-setting": Cmd(42, "u8", help="..."),
272
+ ```
273
+
274
+ Adding a device is one entry in the same file's `DEVICES` table — report ID,
275
+ command pacing, gain range, preset map, renameable slots. No code changes.
276
+
277
+ Undecoded commands are listed in `registry.UNMAPPED` and reachable right away:
278
+
279
+ ```console
280
+ $ fiioctl raw 27 # send cmd 27 with no payload
281
+ $ fiioctl raw 53 --read # GET cmd 53 and show the reply bytes
282
+ $ fiioctl --trace state # watch every packet
283
+ ```
284
+
285
+ ## Layout
286
+
287
+ ```
288
+ core (no optional dependencies)
289
+ fiioctl/proto.py framing + value codecs 77
290
+ fiioctl/curve.py RBJ biquads + text plot 102
291
+ fiioctl/registry.py command + device data 116
292
+ fiioctl/eqdb.py AutoEq search, PEQ files 117
293
+ fiioctl/device.py HID transport, pacing 174
294
+ fiioctl/cli.py argparse surface 499
295
+ optional
296
+ fiioctl/ui.py terminal UI [ui] 648
297
+ fiioctl/cloud.py FiiO catalogue [cloud] 124
298
+
299
+ docs/PROTOCOL.md the reverse-engineered HID protocol
300
+ docs/CATALOGUE.md FiiO's online services
301
+ packaging/ Linux udev rule
302
+ tests/ 61 tests, no hardware or network needed
303
+ ```
304
+
305
+ Line counts exclude blanks, comments and docstrings.
306
+
307
+ ## Verification status
308
+
309
+ Be aware of what has and has not been checked:
310
+
311
+ - **Verified on hardware** (KA15, firmware 1.1.6): device discovery, the reply
312
+ parser, `info`, `state`, `get`, `peq show`, preset-name reads, and the write
313
+ path.
314
+ - **Verified against FiiO's live services**: `cloud list` / `cloud show`
315
+ (39 presets for the KA15) and `autoeq refresh` / `search` / `show`
316
+ (4544 measurements, 4578 targets, server-side equalisation). Reading preset 6 returned a coherent octave-spaced hiphop curve and the
317
+ user slots decoded as real names (`FD15`, `FF5`, `FH3`), which together confirm
318
+ the band codec and the slot-offset-of-7 quirk.
319
+ - **Verified against the bundle**: every packet `fiioctl` builds was compared
320
+ byte-for-byte with the official app's own encoders, and the codecs round-trip
321
+ under `pytest` (`tests/test_proto.py`). AutoEq search and profile parsing run
322
+ against live data.
323
+ - **Write path verified on hardware**: `autoeq apply … --save` wrote all ten
324
+ bands and read back identically, `eq save` persisted to a user slot, and
325
+ `eq name` round-tripped. Round-tripping values through the device is also what
326
+ turned up two defects since fixed — the global gain's whole-decibel
327
+ quantization and the uncleared preset-name field (both in
328
+ [docs/PROTOCOL.md](docs/PROTOCOL.md)).
329
+ - **Not yet exercised**: `factory-reset`, and `peq clear` on a device with
330
+ settings worth keeping.
331
+ - **Best-effort**: the non-KA15 entries in `DEVICES`, transcribed from the app's
332
+ per-device tables rather than observed. `fiioctl info` reports
333
+ `verified_on_hardware` so you know which you are relying on.
334
+
335
+ ### Devices it cannot drive
336
+
337
+ FiiO's own app uses three different transports, and this project implements one
338
+ of them — the shared HID command set. Two groups are therefore out of reach, and
339
+ `fiioctl` says so rather than failing obscurely:
340
+
341
+ | | Devices |
342
+ |---|---|
343
+ | different protocol (adapter) | KG13, BTR15, JadeAudio Trustblu, FG3, JK13, SNOWSKY GAMDONG, JD1 Gaming |
344
+ | USB CDC serial, not HID | K15 (confirmed); K17/K19 possibly |
345
+
346
+ ```console
347
+ $ fiioctl info
348
+ error: FIIO BTR15: the official app drives this model through a different
349
+ protocol, which fiioctl does not implement. See docs/PROTOCOL.md.
350
+ Pass --force to talk to it anyway as if it were an HID device.
351
+ ```
352
+
353
+ Everything else the app supports uses the same command numbers, so volume, EQ,
354
+ presets, filter and balance should work — see
355
+ [docs/PROTOCOL.md](docs/PROTOCOL.md) for the evidence.
356
+
357
+ Use `--dry-run` to inspect packets without writing, and `--trace` to watch real
358
+ traffic. `fiioctl devices` and `fiioctl info` report which profile was matched.
359
+
360
+ ## TODO
361
+
362
+ - **Firmware flashing** (v2). The app's `update-firmware` / `kt1213-update`
363
+ chunks implement it. Left out of v1 deliberately: it is the only operation that
364
+ can brick the device, so it needs its own verification pass rather than being
365
+ bundled in with settings control.
366
+ - Bluetooth controls (codec selection, pairing) for the BTR/BT11 devices — the
367
+ commands live in the app's `bt11-cmd` and `tengtai-controller` chunks.
368
+ - The dynamics processor (compressor/limiter) commands, 53–57.
369
+
370
+ ## Credits
371
+
372
+ Protocol derived from the FiiO Control web app as vendored by
373
+ [Ircama/fiiocontrol](https://github.com/Ircama/fiiocontrol) and verified against
374
+ hardware. Headphone measurements by
375
+ [AutoEq](https://github.com/jaakkopasanen/AutoEq).
376
+
377
+ MIT licensed — see [LICENSE](LICENSE), and [ATTRIBUTION.md](ATTRIBUTION.md) for
378
+ full provenance and the licences of everything consulted. Not affiliated with or
379
+ endorsed by FiiO.
380
+
381
+ [CHANGELOG.md](CHANGELOG.md) covers what changed per release.
382
+ [SECURITY.md](SECURITY.md) covers how to report a vulnerability, what this tool
383
+ touches, and why the AES-ECB in the catalogue client is FiiO's choice rather
384
+ than ours.