Add English localization and MIT-licensed public release documentation
Tests / unit (de, 3.10) (push) Has been cancelled
Tests / unit (de, 3.14) (push) Has been cancelled
Tests / unit (en, 3.14) (push) Has been cancelled
Tests / unit (en, 3.10) (push) Has been cancelled

This commit is contained in:
Justin
2026-09-05 21:02:14 +02:00
parent 3579667c6e
commit dd0809e877
31 changed files with 1261 additions and 377 deletions
+112 -109
View File
@@ -1,142 +1,145 @@
# Rekonstruiertes BTD-700-Steuerprotokoll
# Reconstructed BTD 700 control protocol
Stand der Untersuchung: 5. September 2026. Zweck: unabhängige Linux-Steuerung des
eigenen USB-Dongles. Die Windows-Anwendung wurde statisch untersucht, nicht ausgeführt.
Investigation date: 5 September 2026. Purpose: independently control a user's
USB dongle on Linux. The official Windows application was examined statically;
it was not executed. Research and implementation were carried out with AI
(OpenAI Codex). This is a working protocol reconstruction, not a vendor specification.
## Herkunft und Reproduzierbarkeit
## Provenance and reproducibility
- [Offizielle Produktseite](https://uk.sennheiser-hearing.com/products/btd-700)
- [Offizieller Dongle-Control-Download](https://uk.sennheiser-hearing.com/pages/sennheiser-dongle-control)
- [Dort verlinktes Windows-ZIP](https://eu-central-1-akqa.graphassets.com/AGz66yvUcQ42Ggm7CrXdgz/cmgrvi8excrci07uu3ivz166x)
- Archiv: `windows-signed-v1.0.5/Sennheiser Dongle Control.exe`, Version 1.0.5.0,
- [Official product page](https://uk.sennheiser-hearing.com/products/btd-700)
- [Official Dongle Control download page](https://uk.sennheiser-hearing.com/pages/sennheiser-dongle-control)
- [Windows ZIP linked from that page](https://eu-central-1-akqa.graphassets.com/AGz66yvUcQ42Ggm7CrXdgz/cmgrvi8excrci07uu3ivz166x)
- Archive entry: `windows-signed-v1.0.5/Sennheiser Dongle Control.exe`, version 1.0.5.0,
ProductVersion `1.0.5+eac62d73c43c8572999e7e68cbaec6e4536a1318`.
- SHA256 ZIP: `1d1057b7eb64ab08e41d76723c343c691196f0affe1d5fc9168f01f9908a8cc7`
- SHA256 EXE: `e176f1ab7d4aae40308c152b0bd95227ac5a16fedb99efef7e9229345d8014c0`
- SHA256 eingebettete App-Assembly:
- ZIP SHA256: `1d1057b7eb64ab08e41d76723c343c691196f0affe1d5fc9168f01f9908a8cc7`
- EXE SHA256: `e176f1ab7d4aae40308c152b0bd95227ac5a16fedb99efef7e9229345d8014c0`
- Embedded application assembly SHA256:
`2e8c89ea0333b0a9dd5bb0e851cd685810edaa6e32caee7645511d646ee970b4`
Die EXE ist ein .NET-Single-File-Bundle, Manifestversion 6 mit 451 Einträgen.
Die benötigte Assembly kann mit `tools/extract_control_assembly.py` aus einer
lokalen Original-EXE extrahiert werden. Das Bundleformat wurde mit den
[Manifest](https://github.com/dotnet/runtime/blob/main/src/installer/managed/Microsoft.NET.HostModel/Bundle/Manifest.cs)-
und [FileEntry](https://github.com/dotnet/runtime/blob/main/src/installer/managed/Microsoft.NET.HostModel/Bundle/FileEntry.cs)-
Definitionen des .NET-Runtimes abgeglichen.
The executable is a .NET single-file bundle, manifest version 6, with 451 entries.
`tools/extract_control_assembly.py` can extract only the application assembly from
a locally supplied original EXE. Its format was checked against the .NET runtime's
[Manifest](https://github.com/dotnet/runtime/blob/main/src/installer/managed/Microsoft.NET.HostModel/Bundle/Manifest.cs)
and [FileEntry](https://github.com/dotnet/runtime/blob/main/src/installer/managed/Microsoft.NET.HostModel/Bundle/FileEntry.cs)
definitions.
Analysewerkzeug: ILSpy CLI 11.0.0.9375. Relevante Typen:
`BTDTool.BTD700Tool`, `_BTD700_HOSTCMD`, `_BTD700_DONGLECMD`, `_BTD700_*`-Enums,
Analysis tool: ILSpy CLI 11.0.0.9375. Relevant original types:
`BTDTool.BTD700Tool`, `_BTD700_HOSTCMD`, `_BTD700_DONGLECMD`, the `_BTD700_*` enums,
`BTD700Context`, `HidDeviceExt.sendGenericCommand`,
`ViewModels.MainAppWindowViewModel`, `Views.AppBtd700Features`.
`ViewModels.MainAppWindowViewModel` and `Views.AppBtd700Features`.
Die neue Implementierung enthält nur rekonstruierte Protokollfakten und eigenen
Code. Original-Binaries, dekompilierte Originalquellen und Ressourcen gehören
nicht zum Projekt. Die Herstellerlizenz der Originalsoftware bleibt davon getrennt.
This repository contains protocol facts and an independent implementation.
Original executables, decompiled original sources and vendor resources are not
included. The original software retains its own license. Extracted assemblies
are not required to run this app and should not be added to the repository.
## USB und Framing
## USB interface and framing
VID `0x3542`, PID `0x3001`, USB-HID-Interface 0. Die Steuersammlung verwendet
Vendor Usage Page `0xFFA2`, Report ID **0x34**. Auf dem Testsystem liegt sie unter
`/dev/hidraw6`; die Nummer wird dynamisch ermittelt. Interface 1 ist für diese
Steuer-App nicht erforderlich und wird nicht geöffnet.
VID `0x3542`, PID `0x3001`, USB HID interface 0. The control collection uses vendor
usage page `0xFFA2` and report ID **0x34**. It appeared as `/dev/hidraw6` on the test
system; the application discovers the path dynamically from its descriptor.
Interface 1 is not opened by this control app.
64-Byte-Output-Report, ungenutzte Bytes mit Null gefüllt:
Output reports are 64 bytes, with unused bytes padded with zero:
| Byte | Bedeutung |
| Byte | Meaning |
|---|---|
| 0 | Report-ID `34` |
| 1 | `FE` Host-Befehl; `FF` Dongle-Antwort; `FC` Dongle-Ereignis; `FD` Ereignisbestätigung |
| 2 | Befehls- oder Ereignisnummer |
| 3 | Nutzdatenlänge, maximal 60 |
| 4… | Nutzdaten |
| 0 | Report ID `34` |
| 1 | `FE`: host command; `FF`: dongle response; `FC`: dongle event; `FD`: event acknowledgement |
| 2 | Command or event number |
| 3 | Payload length, at most 60 |
| 4… | Payload |
Host-Abfrage Beispiel: `34 FE 06 00` + 60 Nullbytes.
Antwort Beispiel: `34 FF 06 01 03` (Audio läuft).
Bestätigung des Ereignisses 15: `34 FD 0F 00` + 60 Nullbytes.
Example query: `34 FE 06 00` followed by 60 zero bytes.
Example reply: `34 FF 06 01 03` (audio is playing).
Acknowledgement for event 15: `34 FD 0F 00` followed by 60 zero bytes.
Ereignisse 2/3/4/15/16/17/22/23 werden bestätigt. Andere Reports können von
Medientasten auf derselben Schnittstelle stammen und werden ignoriert. Antworten
werden nach Richtung und Befehlsnummer zugeordnet; Längen werden geprüft.
Nur eine Anfrage ist gleichzeitig aktiv. Schreibbefehle werden nicht blind
wiederholt; für Einstellungen wird anschließend der Wert erneut abgefragt.
Es gibt keine Transaktions-ID und keine garantierte Atomizität mehrerer Einstellungen.
Events 2/3/4/15/16/17/22/23 are acknowledged. Other report IDs can carry media keys
on the same interface and are ignored. Replies are matched by direction and
command number, with length validation. Only one request is outstanding at a time.
Setters are not blindly retried; settings are queried again for confirmation.
There is no transaction ID or guarantee of atomic multi-setting updates.
## Befehle
## Commands
Alle Nummern hexadezimal. Bei Lesezugriffen ist die Anfrage-Nutzlast leer.
Die nachfolgende Nutzlast beschreibt bei `GET` die Antwort, bei `SET` die Anfrage.
IDs below are hexadecimal. GET requests have an empty payload. The payload column
means the response for GET operations and the request for SET operations.
| ID | Operation | Nutzlast |
| ID | Operation | Payload |
|---|---|---|
| 01 | GET Modus/Transport | Modus, konfigurierter Transport, optional aktuell verbundener Transport |
| 02 | SET Modus/Transport | Modus, Transport |
| 03 | GET verfügbare Codecs | Codec-Bitmaske |
| 04 | SET Codec | Einzelnes Codec-Bit, nicht Bitindex |
| 05 | GET aktiver Codec | Codec-Bitmaske |
| 06 | GET Dongle-Zustand | Zustand |
| 07 | GET LE-Audio-Zustand | LE-Zustand |
| 08 | GET Audioqualität | Auflösung, Frequenz |
| 09 | GET Auracast-Konfiguration | öffentlich, Qualität, Verschlüsselung |
| 0A | SET Auracast-Konfiguration | öffentlich, Qualität, Verschlüsselung |
| 0B | GET Auracast-Schlüssel | Zeichenbytes; nur bei expliziter Passwort-Operation gelesen |
| 0C | SET Auracast-Schlüssel | 016 Bytes; UI beschränkt auf leer oder 416 ASCII-Zeichen |
| 0D | GET Auracast-Name | Zeichenbytes; am Gerät 32 Bytes, mit Null aufgefüllt |
| 0E | SET Auracast-Name | bis 16 Zeichenbytes; leer setzt Gerätenamen zurück |
| 12 | GET Firmwareversion | drei Versionsbytes, nur Anzeige |
| 13 | Werksreset | leer; nur nach Nutzerbestätigung |
| 14 | Bluetooth verbinden/trennen | 1 / 0 |
| 15 | GET Kopfhörer-Transportmöglichkeiten | Bitmaske |
| 17 | GET Gaming-Verfügbarkeit | im Original definiert; Firmware 3.11 antwortet nicht, daher nicht regelmäßig abgefragt |
| 01 | GET mode/transport | Mode, configured transport, optional connected transport |
| 02 | SET mode/transport | Mode, transport |
| 03 | GET available codecs | Codec bitmask |
| 04 | SET codec | One codec bit, not its bit index |
| 05 | GET active codec | Codec bitmask |
| 06 | GET dongle state | State |
| 07 | GET LE Audio state | LE state |
| 08 | GET audio quality | Resolution, frequency |
| 09 | GET Auracast configuration | Public discovery, quality, encryption |
| 0A | SET Auracast configuration | Public discovery, quality, encryption |
| 0B | GET Auracast key | Character bytes; only read by explicit password operations |
| 0C | SET Auracast key | 016 bytes; UI accepts empty or 416 ASCII characters |
| 0D | GET Auracast name | Character bytes; 32 bytes with zero padding on the tested device |
| 0E | SET Auracast name | Up to 16 character bytes; empty restores the device default |
| 12 | GET firmware version | Three version bytes, display only |
| 13 | Factory reset | Empty; requires user confirmation |
| 14 | Bluetooth connect/disconnect | 1 / 0 |
| 15 | GET supported headphone transports | Bitmask |
| 17 | GET Gaming availability | Defined by the original app; firmware 3.11 did not reply, so not polled |
Modus: 0 Standard, 1 Gaming, 2 Auracast.
Transport: 0 getrennt, 1 BR/EDR, 2 LE Audio, 3 Dual/automatisch. Setzen von 0 wird
in der neuen App nicht angeboten; Trennen hat einen eigenen Befehl.
Mode: 0 Standard, 1 Gaming, 2 Auracast.
Transport: 0 disconnected, 1 BR/EDR, 2 LE Audio, 3 dual/automatic. The app does not
set transport 0; disconnection has a separate command.
Codec-Bits: `01` SBC, `02` aptX Classic, `04` aptX Adaptive/Low Latency,
`08` aptX Lossless, `10` aptX Lite/QMAP, `20` LC3. Nur die vom Dongle angebotenen
Bits werden im Menü angezeigt. Die Firmware kann die Liste abhängig vom Modus ändern.
Codec bits: `01` SBC, `02` aptX Classic, `04` aptX Adaptive/Low Latency,
`08` aptX Lossless, `10` aptX Lite/QMAP, `20` LC3. Only offered bits appear as
choices. Firmware can change the available list depending on the current mode.
Dongle-Zustand: 0 keiner/bereit, 1 getrennt, 2 verbunden, 3 Audio, 4 Sprache.
LE-Zustand: 0 keiner, 1 getrennt, 2 verbunden, 3 Unicast, 4 Broadcast.
Auflösung: 1 = 16 Bit, 2 = 24 Bit.
Frequenz: 1 = 44,1 kHz, 2 = 48 kHz, 3 = 96 kHz.
Auracast: öffentlich 0/1; Qualität 0 = SQ 16 kHz, 1 = SQ 24 kHz, 2 = HQ;
Verschlüsselung 0/1. „Öffentlich“ bezeichnet die Ankündigung/Auffindbarkeit,
nicht das Aktivieren des Audiomodus.
Dongle state: 0 none/ready, 1 disconnected, 2 connected, 3 audio, 4 voice.
LE state: 0 none, 1 disconnected, 2 connected, 3 unicast, 4 broadcast.
Resolution: 1 = 16-bit, 2 = 24-bit.
Frequency: 1 = 44.1 kHz, 2 = 48 kHz, 3 = 96 kHz.
Auracast: public discovery 0/1; quality 0 = SQ 16 kHz, 1 = SQ 24 kHz, 2 = HQ;
encryption 0/1. Public discovery advertises the broadcast; it does not select
the audio mode or start/stop audio by itself.
Gaming-Verfügbarkeit fällt wie in der Original-App auf den Verbindungsstatus,
aptX-Adaptive-Bit und LE-Transport zurück, solange kein Ereignis 23 eingetroffen ist.
Gaming availability falls back to the current connection, aptX Adaptive bit and
LE transport, following the original app, unless event 23 has supplied a value.
Display strings may be translated; command IDs and numeric values never are.
## Tatsächlich gelesene Antworten (Firmware 3.11.0)
## Observed hardware responses: firmware 3.11.0
| Anfrage | Antwort ohne Null-Padding | Interpretation |
| Query | Response without padding | Interpretation |
|---|---|---|
| 06 | `34 FF 06 01 03` | Audio läuft |
| 01 | `34 FF 01 03 01 03 01` | Gaming, automatisch, verbunden per Classic |
| 03 | `34 FF 03 01 04` | aktuell aptX Adaptive angeboten |
| 05 | `34 FF 05 01 04` | aptX Adaptive aktiv |
| 07 | `34 FF 07 01 01` | LE getrennt |
| 08 | `34 FF 08 02 02 02` | 24 Bit / 48 kHz |
| 09 | `34 FF 09 03 01 02 00` | öffentlich, HQ, unverschlüsselt konfiguriert |
| 06 | `34 FF 06 01 03` | Audio playing |
| 01 | `34 FF 01 03 01 03 01` | Gaming, automatic, connected using Classic |
| 03 | `34 FF 03 01 04` | aptX Adaptive currently offered |
| 05 | `34 FF 05 01 04` | aptX Adaptive active |
| 07 | `34 FF 07 01 01` | LE disconnected |
| 08 | `34 FF 08 02 02 02` | 24-bit / 48 kHz |
| 09 | `34 FF 09 03 01 02 00` | Public discovery, HQ, no encryption configured |
| 12 | `34 FF 12 03 03 0B 00` | Firmware 3.11.0 |
| 15 | `34 FF 15 01 01` | Kopfhörer unterstützt Classic |
| 15 | `34 FF 15 01 01` | Headphones support Classic |
Auch der Auracast-Name wurde erfolgreich gelesen. Private Kennungen/Schlüssel
werden hier nicht dokumentiert. Die verfügbare Audiokonfiguration belegt nicht,
dass Audio mit diesem Profil bitgenau übertragen wird.
The broadcast name was also read successfully. Private device identifiers and keys
are omitted. The reported audio configuration does not prove bit-perfect playback.
## Getrennte Update-Funktion und Grenzen
## Update exclusion and remaining limitations
Die App implementiert ausschließlich Report `0x34` auf der Kontrollschnittstelle.
Sie enthält keine DFU-/Upgrade-Kommandos, Firmwaredateien, Firmwareparser,
Firmware-Download-URLs oder Umschaltung in einen Update-Modus.
Die Firmwareversion wird nur über den Kontrollbefehl `0x12` gelesen.
Only control report `0x34` is implemented. The runtime has no DFU/upgrade commands,
firmware files, firmware parser, firmware-download endpoints or update-mode switch.
Firmware version is read using control command `0x12` solely for display.
Der Nutzer hat die Funktion der App am eigenen Dongle bestätigt. Der automatisierte
Hardware-Umschalttest wurde noch nicht ausgeführt; die Rückmeldung belegt keine
vollständige Prüfung jedes Schreibbefehls. Insbesondere Wiederverbindung, echte
Auracast-Empfänger und Firmwareunterschiede benötigen weitere systematische Tests.
The device owner reported that the app works. The automated hardware mutation test
has not been run, and that feedback is not a complete verification of every setter.
Reconnection, real Auracast receivers and differing firmware require more systematic
testing. The original app and simulator are not substitutes for device validation.
Infobereich: `org.kde.StatusNotifierItem` plus `com.canonical.dbusmenu` über
libdbusmenu. Grundlage ist die
[StatusNotifier-Spezifikation](https://specifications.freedesktop.org/status-notifier-item/latest-single/).
GNOME-Registrierung und D-Bus-Menüaktionen wurden lokal geprüft. Name/Passwort
verwenden fokussierte GTK-Eingaben, da D-Bus-Menüs keine Texteingabefelder vorsehen.
## Desktop tray
`org.kde.StatusNotifierItem` with `com.canonical.dbusmenu`, exported using libdbusmenu,
following the [StatusNotifier specification](https://specifications.freedesktop.org/status-notifier-item/latest-single/).
Registration with GNOME and menu actions over D-Bus were tested locally in English
and German. Name/password actions focus the GTK editor because the menu protocol
does not provide text-entry fields.
+49 -33
View File
@@ -1,37 +1,53 @@
# Validierung am 5. September 2026
# Validation — 5 September 2026
Getestetes System: Bazzite, GNOME, Python 3.14, GTK 4.22, libadwaita 1.9.
Angeschlossener BTD 700: USB `3542:3001`, Firmware 3.11.0.
This document separates observed hardware behavior from simulated tests.
The project was developed using AI (OpenAI Codex); these results are not a warranty.
- 21 Tests mit `python3 -m unittest discover -s tests -q`: bestanden.
- Fenster-/Infobereich-Test `python3 tools/check_gui.py`: bestanden, ausschließlich
mit einem simulierten Dongle. Aktionen wurden über `com.canonical.dbusmenu.Event`
ausgelöst, nicht nur direkt gegen Controller-Methoden.
- Geprüfte Demo-Abläufe: Standard/Gaming/Auracast, Codec, Transport, Trennen/Verbinden,
öffentliche Auffindbarkeit, Broadcastqualität, Name/Passwort speichern,
Passwortschutz ausschalten, Fenster schließen/aus Menü wieder öffnen,
ungespeicherte Eingaben bei Statusabfragen erhalten und verwerfen.
- Eigene Fensterbilder bei 620 × 800 und 420 × 600 Pixeln gerendert und visuell
geprüft. Oberfläche scrollbar, Bedienelemente erreichbar. Die zuvor überlange
Passwortbeschriftung wurde gekürzt und um einen sichtbaren Hinweis ergänzt.
- Echte Hardwareabfragen: erfolgreich, siehe PROTOCOL.md. Anzeige:
Gaming / aptX Adaptive / 24 Bit / 48 kHz / laufende Musikwiedergabe.
- Live-Symbol exportiert StatusNotifierItem mit aktuellem Tooltip und einem
D-Bus-Menü. GNOME-StatusNotifierWatcher nimmt die Registrierung an.
- App über persönlichen Desktop-Eintrag installierbar; Desktop-Datei validiert.
Dauerhafter Start in der Benutzersitzung mit transienter Unit
`btd700-control.service`; kein Systemdienst und kein aktivierter Autostart.
- Zweiter USB-Zugriff während laufender App: korrekt mit verständlicher Meldung
abgelehnt, ohne die laufende Instanz zu beeinträchtigen.
- Extraktionsskript gegen die offizielle Windows-EXE geprüft; SHA256 der
extrahierten Assembly entspricht der bei der Analyse verwendeten Datei.
## Environment
Der Nutzer hat anschließend bestätigt, dass die App am eigenen Dongle funktioniert.
Welche einzelnen Funktionen dabei getestet wurden, wurde nicht näher aufgeschlüsselt.
Bazzite / GNOME, Python 3.14, GTK 4.22, libadwaita 1.9.
Connected BTD 700: USB `3542:3001`, firmware 3.11.0.
**Noch offen:** automatisierte Hardware-Schreibtests sowie eine systematische
Prüfung mit Audio-/Auracast-Empfängern. `tools/check_hardware.py --run` wurde bisher
nicht ausgeführt. Dieser Test unterbricht kurz Audio und verändert vorübergehend
Auracast-Einstellungen einschließlich des Passworts; anschließend versucht er,
die ursprünglichen Werte wiederherzustellen. Ein Werksreset wird dabei nicht
ausgeführt; Firmware-Updates sind nicht implementiert.
## Observed on the real device
- Correct control-interface discovery and opening without detaching audio drivers.
- Successful state, mode, codec, quality, transport, Auracast configuration/name
and firmware-version reads. See [PROTOCOL.md](PROTOCOL.md) for response bytes.
- Reported live state: Gaming, aptX Adaptive, 24-bit / 48 kHz, audio playing.
- Native GTK window and registered GNOME tray item with a live status tooltip.
- A second USB client is rejected with a clear message while the app owns the device.
- The device owner subsequently confirmed that the app works. The individual
functions exercised by the owner were not enumerated.
## Automated verification
- Protocol/controller/transport tests: command allowlist, malformed frames, response
correlation, event acknowledgements, readback, capability checks, input validation,
reset confirmation and password handling using a simulated device.
- Localization tests: English/German detection, explicit CLI override, message
placeholders, localized status/errors and unchanged numeric protocol data.
- Desktop integration tests: isolated installation/uninstallation and autostart
behavior without changing the real user's launchers.
- `tools/check_gui.py` exercised English and German windows and actual D-Bus menu
events against a demo device: modes, codec, transport, connection, broadcast
discovery/quality, name/password saving, encryption, closing/reopening and
preserving/discarding unsaved edits.
- Window layouts rendered and inspected at 620 × 800 and 420 × 600 pixels.
- README screenshots captured from the real English GTK window at 620 × 880 with
fictional demo data. No real USB access or generated mockups were used.
- Extraction helper output matched the SHA256 of the original assembly used for
protocol research. No vendor binaries were added to the distribution.
The CI workflow runs the unit tests on Python 3.10 and 3.14, in both languages.
It does not run real hardware tests or claim that a particular receiver works.
## Not yet systematically verified
- Hardware writes across different firmware revisions and all individual commands.
- Real Auracast receiver compatibility, audio quality and reconnect behavior.
- Other distributions/desktops beyond the tested Bazzite/GNOME setup.
`tools/check_hardware.py --run` has not been executed. It deliberately interrupts
audio, changes settings including the broadcast password, and attempts to restore
the original values. It does not perform a factory reset and cannot perform
firmware updates. Unplugging midway can prevent restoration.
Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB