devgo / labo retour d'expérience

Rétro-ingénierie · domotique

J'ai fait parler une enceinte VULKKANO A4 Arc MKII — et la piloter depuis Home Assistant

Un ESP32 dans l'enceinte, deux fils : lire son état réel (volume, source, Bluetooth) par le port de debug, et la commander en infrarouge — sans casser la télécommande d'origine. Le guide complet, du pad UART jusqu'au YAML ESPHome, avec le piège d'encodage NEC qui bloque tous ceux qui s'y essaient.

Puce Mvsilicon BP1048B1 Bus UART 460800 + IR NEC Outils FTDI · ESPHome · un multimètre
IR · NEC · démodulé · actif-bas addr 0xA129 · cmd 0x619E (vol+)

00 — l'essentiel

Ce qu'on obtient au final

L'enceinte est vendue avec une entrée Bluetooth, une optique et une HDMI ARC, mais aucune API. Après reverse, un petit ESP32-C3 caché à l'intérieur expose tout dans Home Assistant : le contrôle complet et — c'est la vraie valeur ajoutée — le retour d'état réel, chose qu'un simple émetteur infrarouge « à l'aveugle » ne donne jamais.

Contrôle
power · sources · vol · mute · lecture
État lu
source · volume · mute · appareil BT
Câblage
2 fils + masse + alim
Télécommande
toujours fonctionnelle

01 — ouvrir

Trouver par où parler à l'enceinte

À l'ouverture, la carte principale porte la référence TX600BJ-MBS06789 et un SoC audio Bluetooth Mvsilicon BP1048B1. Sur le cuivre, la sérigraphie ne ment pas : un pad TXD traîne près du contrôleur, avec des masses partout (AGND, LGND, blindage optique).

Un coup de multimètre confirme la piste : TXD repose à 3,30 V au repos — la signature d'une ligne UART émettrice en logique 3,3 V. On soude un fil, masse commune, et on écoute. Écouter ne casse rien : c'est une entrée sur notre adaptateur, une rue à sens unique.

02 — écouter

L'enceinte se raconte à voix haute

Un scan de débits trouve la bonne vitesse : 460800 bauds, 8N1. Et là, jackpot — ce n'est pas un bus muet, c'est un journal de debug bavard. Le firmware commente chacun de ses gestes en clair :

capture UART · 460800 8N1 Bt Name:VULKKANO A4 ARC MKII
A2dp Connected : bt address = 7d:d7:3d:xx:xx:xx
name = iPhone du salon
SysCurrentMode = OpticalPlay
APP_SOURCE_NUM vol = 12
Mute Digital  /  Demute Digital

En parsant ces lignes, l'ESP reconstruit tout l'état en temps réel : la source active (SysCurrentMode), le volume numérique exact, le mute, l'appareil Bluetooth connecté et son nom. Voilà l'atout qu'aucun blaster IR ne peut offrir : savoir où en est vraiment l'enceinte.

03 — décoder

La télécommande, entièrement cartographiée

Le même journal logge chaque touche infrarouge reçue : IrKeyVal = 619ea129. C'est de l'infrarouge NEC, adresse constante 0xA129. En pressant chaque bouton, on relève la table complète — power, volume, mute, les trois sources en codes directs (un par entrée, pas de bascule à l'aveugle), la lecture, et cinq presets d'égaliseur.

FonctionIrKeyVal loggéaddresscommand
Power807fa1290xA1290x807F
Volume +619ea1290xA1290x619E
Volume −609fa1290xA1290x609F
Mute649ba1290xA1290x649B
Source Bluetooth32cda1290xA1290x32CD
Source Optique57a8a1290xA1290x57A8
Source HDMI ARC56a9a1290xA1290x56A9
Play / Pause659aa1290xA1290x659A
Suivant / Préc.629d· / 639c·0xA1290x629D / 0x639C

Détail qui a son importance pour la suite : la règle du command = la moitié haute de l'IrKeyVal imprimé, l'adresse étant la moitié basse. On y revient — c'est là que tout s'est joué.


04 — le piège

Le piège d'encodage qui bloque tout le monde

Émettre les codes semble trivial — et pourtant, avec l'encodage « intuitif », l'enceinte ignore chaque trame. Deux fausses pistes très convaincantes détournent l'attention du vrai problème ; autant les baliser pour t'épargner le détour.

Fausse piste électrique. La sortie du récepteur IR est push-pull (~30 Ω) et « tient » la ligne : on croit alors devoir isoler le récepteur au prix de la télécommande. Fausse piste matérielle. Récepteur débranché, l'ESP « seul maître » ne fait toujours rien, et une capture au vol — un « blob » de 3 ms au lieu d'un leader NEC de 9 ms — fait accuser le périphérique RMT de l'ESP32-C3. Les deux sont des impasses : ni l'électronique ni le silicium ne sont en cause.

Ce qu'on écarte à tort avant de trouver

  • ✗Débit / codes — l'état se lit parfaitement, les codes correspondent à la télécommande.
  • ✗Porteuse 38 kHz résiduelle — écartée (carrier_duty 100%).
  • ✗Niveau logique / câblage — 0 Ω vers un signal propre, le FTDI décode la télécommande.
  • ✗Repos collé en bas — non, le GPIO au repos est à 3,28 V.

Toutes ces pistes sont vraies mais hors sujet. Le vrai bug tient en trois mots : l'ordre des octets.

05 — l'oracle

Faire dire à l'enceinte ce qu'elle a compris

La méthode fiable : le firmware imprime IrKeyVal = … pour chaque trame NEC qu'il décode — la sienne comme la nôtre. Or on lit déjà cet UART ! Il suffit de republier cette ligne dans un capteur. L'enceinte devient alors un oracle : elle dit exactement ce qu'elle reçoit, et transforme chaque essai en verdict.

Une trame NEC transporte [adresse][commande][~commande]. ESPHome prend address et command en 16 bits — mais on ne savait pas dans quel sens le SDK Mvsilicon assemble ses 32 bits. Piège diabolique : un mauvais ordre reste une trame NEC parfaitement valide (le contrôle cmd ^ ~cmd passe), mais avec la commande au mauvais endroit → rejetée en silence. Quatre combinaisons possibles, une seule testée. On les a toutes tirées, l'oracle en surveillance :

H0 — l'encodage intuitif

command: 0x9E61

l'enceinte décode ↓

IrKeyVal = 9e61a129

Reçue — mais commande inversée, donc ignorée. Le signal partait bien ; c'est la sémantique qui clochait.

H1 — octets dans l'autre sens

command: 0x619E

l'enceinte décode ↓

IrKeyVal = 619ea129

Code exact de la télécommande — et le volume monte 9 → 10 → 11 → 12. ✔

La règle, une fois pour toutes

address = moitié basse de l'IrKeyVal (0xA129), command = moitié haute (vol+ → 0x619E). Le RMT de l'ESP32-C3 n'a jamais été en cause ; l'injection passe même récepteur branché, sans diode — donc la télécommande d'origine reste vivante.

06 — ça marche

Le montage final

Un ESP32-C3 alimenté par le rail 3,3 V de l'enceinte (maintenu même en veille, donc l'ESP peut la réveiller). Deux signaux, une masse, quelques lignes de YAML :

esphome · vulkkano-a4.yaml # TXD enceinte → GPIO1 · ligne IR → GPIO4 · GND commun

uart:
  rx_pin: GPIO1
  baud_rate: 460800

remote_transmitter:
  pin: { number: GPIO4, inverted: true, mode: { output: true } }
  carrier_duty_percent: 100%

# Volume +
transmit_nec: { address: 0xA129, command: 0x619E }

Résultat : contrôle complet et retour d'état dans Home Assistant, avec un bonus — le capteur oracle remonte aussi les appuis de la vraie télécommande, de quoi bâtir des automatisations (« touche power pressée → allumer l'ampli »).


07 — la config

La configuration ESPHome complète

À copier-coller dans ESPHome — ajoute simplement tes secrets (api_key, wifi_ssid, wifi_password), flashe une première fois en USB puis en OTA. Tout est là : lecture d'état, dix boutons de contrôle, et le capteur oracle qui remonte aussi les appuis de la vraie télécommande.

vulkkano-a4.yaml

  

08 — à emporter

Les leçons qui resservent

01
Cherche l'oracle de l'appareil.Beaucoup de firmwares logguent ce qu'ils reçoivent. Boucler ce retour transforme chaque essai en verdict — au lieu de deviner à l'aveugle.
02
Un signal « valide » peut être faux.Une trame NEC bien formée avec les octets dans le mauvais ordre est acceptée par le protocole… et jetée par l'application. La validité formelle ne dit rien de la sémantique.
03
Épuise le logiciel avant d'accuser le silicium.« C'est le matériel » est une conclusion séduisante et souvent fausse. Ici le RMT était hors de cause : la vraie faute était logicielle et triviale.
04
Le port de debug est un trésor.Un UART bavard donne l'état gratuitement — la moitié la plus utile du projet, celle qu'aucun émetteur infrarouge ne réplique.