← cyberloutre.fr 🦦 Loutre

🐰 Case study · Reverse engineering

Nabaztag Revival.

Un lapin connecté des années 2000, débranché de force en 2011 quand la société qui le faisait vivre a fermé. Je l'ai ramené à la vie chez moi, sans aucun service cloud, et je lui ai appris à dialoguer avec une IA — tout ça sans changer la moindre pièce dans l'objet.

Aujourd'hui : il se réveille, bouge, s'illumine, écoute, comprend le français et me répond à voix haute. Live terrier.cyberloutre.fr
Un Nabaztag, lapin connecté de 2006

🪦 Le problème

Un objet qui n'a plus de maison.

Le Nabaztag a été conçu comme un objet connecté avant l'heure : toute son « intelligence » vit dans un serveur quelque part sur Internet, pas dans le lapin lui-même. À chaque démarrage, il appelle ce serveur, qui lui dit quoi faire. Sans lui, le lapin allume une LED et s'arrête.

La société qui faisait tourner ce service — Violet — a mis la clé sous la porte vers 2011. Depuis, des milliers de lapins servent de presse-papiers ou dorment dans des cartons.

Mon objectif : en réanimer un chez moi, sans dépendre d'aucun service extérieur, et l'intégrer à mon installation domotique pour qu'il réagisse à la vie de la maison (quelqu'un arrive, la météo change, un rappel). Puis, plus tard, lui apprendre à m'écouter et à me répondre.

Une règle que je me suis imposée : ne pas toucher au matériel. La méthode populaire pour faire revivre un Nabaztag consiste à remplacer sa carte interne par un Raspberry Pi — trop facile, et ce n'est plus le même objet. On garde l'original, tel quel.

🛠️ L'approche

Lui construire un remplaçant qui parle sa langue.

Mon idée : écrire un petit serveur de remplacement, qui parle exactement la même langue que les serveurs disparus, et le faire tourner chez moi. Le lapin croit appeler Violet en 2008 ; en réalité, il discute avec mon logiciel sur le réseau local de la maison.

Concrètement, ce serveur fait quatre choses :

  • Il imite Violet. Quand le lapin appelle au démarrage, il accepte la conversation et lui envoie le « cerveau » dont il a besoin pour fonctionner, comme le faisait l'original.
  • Il pilote le lapin. Il bouge les oreilles à des positions précises, déclenche des jeux de lumière sur ses 5 LEDs, allume le nez, joue des sons et parle (synthèse vocale).
  • Il s'intègre à la maison connectée. Une petite interface permet à mon système domotique (Home Assistant) de demander au lapin de réagir — « annonce qu'il pleut », « clignote en bleu si Raphaël dort ».
  • Il garde le ton du lapin. Phrasé laconique d'origine — « Aujourd'hui… pluie ! » — c'est important.

Phase 2 : la voix. Quand je maintiens le bouton sur la tête du lapin, il enregistre ce que je dis et envoie l'audio au serveur. Mon serveur le transcrit, le passe à une IA (Claude), et fait reparler le lapin avec la réponse. L'IA peut même animer le lapin pendant qu'elle parle — oreilles, LEDs, nez — en glissant des instructions dans son texte.

Pour les ingés : c'est un serveur Python sans aucune dépendance (bibliothèque standard uniquement), packagé en add-on Home Assistant OS (Docker). Le détail du protocole est dans la section suivante.

🧩 Les défis (et comment je les ai résolus)

Là où ça devient intéressant.

Section pour les ingés et les curieux de mécanique. Si la technique te parle pas, scrolle direct jusqu'au résultat ↓, promis je ne le prends pas mal.

🔌 « Le boot réussit, mais toutes les commandes sont ignorées »

Le plus beau bug. Après reverse engineering de la machine à états du handshake XMPP, le lapin démarrait correctement… mais ignorait silencieusement chaque commande poussée. En instrumentant l'état interne de l'appareil (il a une commande XMPP cachée getrunningstate), j'ai découvert que le serveur DOIT répondre au <presence> du lapin pour qu'il atteigne l'état interne « free » où il agit sur les commandes. Sans cette réponse : boot OK, mais lapin sourd.

Bonus piège : le <unbind> du lapin partage un espace de noms XML avec <bind> — un handler naïf l'avalait et désynchronisait tout le suivi d'état.

🎙️ De la voix, sans aucun firmware custom

Plutôt que de flasher quoi que ce soit, j'ai découvert que le firmware d'origine cachait déjà un push-to-talk : le bouton enregistre le micro 8 kHz et envoie un WAV en IMA-ADPCM par HTTP POST. J'ai écrit un décodeur IMA-ADPCM → PCM en Python pur, rééchantillonné de 8 à 16 kHz, et lancé un modèle whisper.cpp embarqué pour la transcription. Aucun bidouillage matériel, aucun firmware remplacé.

Le lapin bootait, respirait, et restait muet — jusqu'à ce que le serveur réponde à son <presence>.

🔐 Trois formats binaires, dont un chiffré

En lisant le bytecode (écrit en « Metal », le langage de VM de Violet) et la réimplémentation C++ de référence, j'ai récupéré trois formats de paquets : l'AmbientPacket (icônes ventre / oreilles / nez / LED du bas), le SleepPacket, et les « programmes » MessagePacket — ces derniers obfusqués par un chiffrement à substitution roulante (table d'inversion). Je l'ai ré-implémenté et vérifié qu'il fait un aller-retour parfait contre la propre routine de dé-obfuscation de l'appareil.

🧱 Cross-compiler whisper.cpp pour Alpine/musl

L'add-on tourne sur Alpine (musl). J'ai cross-compilé whisper.cpp dans un build Docker multi-stage (avec OpenMP), intégré espeak-ng pour une TTS hors-ligne, et branché l'add-on Piper de Home Assistant (Wyoming) pour une voix de meilleure qualité, récupérée via le proxy de l'API Supervisor. Au passage, un bug retors : sous s6-overlay, le service n'héritait pas du token de l'API Supervisor depuis l'environnement — il fallait le lire depuis le fichier container-environment de s6.

Le push-to-talk était déjà là, planqué dans le bytecode d'usine. Il suffisait de décoder ce que le lapin envoyait.

✨ Le résultat

Un objet de 20 ans qui écoute et répond.

Un appareil arrêté depuis deux décennies qui, désormais : se réveille, respire, bouge, s'illumine, écoute, comprend le français, demande à une IA et me répond d'une voix naturelle. Tout ça entièrement chez moi, sur le réseau local de la maison — le seul morceau de cloud, optionnel, c'est l'IA elle-même (et encore, on peut la remplacer par une IA locale).

La boucle complète — j'appuie sur le bouton, je parle, le lapin transcrit, l'IA répond, le lapin parle — tourne sur la vraie machine. L'IA peut même animer le lapin pendant qu'elle parle : oreilles, LEDs, nez. Tout a été vérifié en direct sur l'appareil physique, pas dans un simulateur.

🔬 Plongée technique · Phase 3

Un firmware maison pour écouter sans les mains.

Section très technique — pour les ingés et les makers. Si tu lis pour l'histoire, file directement à la feuille de route ↓.

Après avoir réanimé le lapin puis lui avoir donné la parole, il restait une dernière frontière : l'écoute mains-libres — dire « Nabi » et qu'il réponde, sans rien toucher. Le hic : le programme interne du lapin n'enregistre le micro que quand on appuie physiquement sur le bouton. Le serveur ne peut pas déclencher un enregistrement tout seul.

Jusqu'ici, tout avait été fait sans le moindre firmware custom. Pour les mains-libres, il en fallait un : j'ai écrit un firmware maison qui diffuse le micro en continu, en reverse-engineerant puis en recompilant le propre bytecode de l'appareil — et fait tourner par-dessus une chaîne vocale 100 % locale (reconnaissance → LLM → synthèse). Toujours aucune modification matérielle : le firmware est livré au démarrage, rien n'est flashé en dur.

🧰 Remonter la toolchain « Metal » depuis les sources

Pour produire du bytecode, il faut d'abord le compilateur. J'ai remonté la toolchain Metal depuis ses sources : cross-compilation d'un compilateur C++ 32 bits dans un conteneur Linux (le build n'aboutit que sur x86, avec le multilib 32 bits). Preuve que la chaîne est correcte — j'ai recompilé le firmware d'origine depuis les sources, identique octet pour octet à celui d'usine.

🔎 Reverse du dispatch, de la pile réseau et du chemin d'enregistrement

En reverse-engineerant le dispatch de commandes, la pile réseau et le chemin d'enregistrement, j'ai isolé exactement pourquoi le serveur ne pouvait pas capter d'audio : le micro est verrouillé derrière le bouton. J'ai alors écrit un petit module firmware qui ajoute deux commandes pilotées par le serveur et diffuse l'audio 8 kHz en datagrammes UDP.

🌐 Des problèmes systèmes, sur un appareil de 20 ans

Routage inter-sous-réseaux : l'ARP du firmware résout déjà la passerelle pour les IP non locales, donc le flux traverse les VLAN sans bidouille. Et une contrainte half-duplex bien réelle — l'appareil ne peut pas enregistrer et jouer en même temps : le serveur coupe le micro avant de parler, puis le réarme ensuite.

🎧 La boucle vocale, côté serveur

Décoder le flux IMA-ADPCM du lapin en PCM, faire tourner un modèle de reconnaissance vocale local sur des fenêtres glissantes, détecter le mot d'éveil, envoyer la commande à un agent conversationnel (LLM), puis reparler la réponse d'une voix naturelle locale — l'agent pouvant même bouger les oreilles et les LED dans sa réponse.

« Nabi, raconte-moi une blague » — et le lapin a transcrit la phrase, interrogé le LLM, puis raconté la blague à voix haute. Mains-libres, sur la vraie machine.

Bilan : un gadget arrêté depuis 2006 fait désormais tourner un firmware maison qui diffuse son micro, écoute son nom, comprend le français, interroge un LLM et répond à voix haute — entièrement sur le réseau local, le LLM étant le seul composant cloud, et optionnel. (Sa toute première blague mains-libres : « pourquoi la tomate a-t-elle traversé la route ? Pour prouver qu'elle n'était pas une banane. »)

Honnêteté de rigueur : c'est une preuve de concept qui fonctionne, pas un produit fini. L'écoute permanente est optionnelle et gourmande en CPU — on l'active quand on en a envie.

Ce que cette phase a mobilisé

🗺️ La feuille de route

Ce qui arrive ensuite.

Les phases 1 à 7 sont faites. Au programme : réveiller le lapin, le piloter, lui donner la parole avec une IA, lui apprendre à écouter sans les mains, renforcer la sécurité de son logiciel interne (en cours de tests), soigner sa mise en route, faire tourner un serveur public sur terrier.cyberloutre.fr où n'importe quel Nabaztag du monde peut se connecter, et un traducteur automatique qui rend le « cerveau » du lapin (langage Metal de 2006) lisible en Python par tout le monde. Et la phase 8 est en cours — première tranche livrée :

Phase 8 · en cours

🪓 Alléger le cerveau en passant à un langage natif

Première tranche livrée. Le cerveau du lapin tient en environ 3000 lignes du langage Metal, qui tournent sur une petite machine virtuelle. Le langage natif de la puce (« C ») est environ 3× plus compact : réécrire les morceaux les plus utilisés directement en C libérerait 10 à 20 Ko de mémoire pour de nouvelles fonctions. Le compromis : Metal est petit, lisible, et modifiable sans tout reflasher ; le C est plus dense mais chaque modification exige un reflash complet. Le jeu en vaut la chandelle pour des parties stables et bien comprises du logiciel.

🧱 Stack & compétences

Ce que ce projet mobilise.

Voir le code sur GitHub →