Bring the 2006 Wi-Fi rabbit back to life — and let it talk to Claude.
100 % local · no cloud · no account. The Violet servers died in 2011; this is its new home.
every capability above verified live on a real Nabaztag:tag v2 🐰
Bring an original Nabaztag (the 2005–2006 Wi-Fi rabbit by Violet/Mindscape) back to life and drive it from Home Assistant, fully locally — no cloud.
This repository is a Home Assistant OS add-on repository. Its primary add-on is a small, dependency-free server that speaks the original Violet protocol directly: the rabbit phones home to it (the real Violet servers died around 2011), and Home Assistant drives the rabbit — ears, belly weather icons, nose — through a simple control API.
- 🐰 Revives a 2006 rabbit with zero hardware mods and nothing flashed by default — a stock rabbit just downloads its bytecode from the add-on on boot.
- 🗣️ Talk to Claude through the rabbit — hold the head button, ask, and it answers in its own voice while moving its ears + LEDs. Hands-free "hey Nabi" wake word is in beta — opt-in and off by default (see the privacy note below).
- 🔒 100 % local — bundled whisper.cpp STT + Piper / espeak-ng TTS. No cloud, no account, no telemetry.
- 🧩 Driven from Home Assistant — speak text, play audio, original Violet jingles, ear choreographies, RGB light shows, weather / mail / air belly icons; button / RFID / ear-move events come back as HA events for your automations.
- 🔧 Optional custom firmware (Naboot) — Ed25519-signed OTA, verified end-to-end on hardware (flashed over the air, no JTAG), plus a modern config UI.
- 📡 Survives a hostile network — a tiny firmware mDNS resolver lets the rabbit find its server over multicast even when the gateway refuses its unicast DNS.
- 🌍 Le Terrier — a public "warren" so anyone can adopt a rabbit without running their own server.
✅ It works end-to-end — verified live on a real Nabaztag:tag v2. A stock rabbit is a "dumb" Wi-Fi client that downloads its bytecode on every boot (nothing flashed by default) and talks plain HTTP + XMPP. The rabbit boots, breathes, speaks, plays jingles, moves its ears, lights up, listens hands-free for "hey Nabi", asks Claude and answers. The custom Naboot firmware adds a signed OTA path (proven by actually flashing over the air) and a modern config UI; Le Terrier is the public warren. See the Roadmap below for the status of every phase.
Nabaztag (<rabbit-ip>) ──HTTP /vl/bc.jsp,/vl/locate.jsp :80──▶ ┌──────────────────────────┐
▲ │ Add-on @ <haos-ip> │
│ ◀──── XMPP push (commands) :5222 ─────── │ nabaztag-violet (Python) │
│ ───── XMPP events (button/RFID) ───────▶ │ HTTP boot + XMPP + API │
Home Assistant ──── HTTP control API :8099 (/api/...) ─────────▶ └──────────────────────────┘
On boot the rabbit fetches its bytecode (/vl/bc.jsp) and a locate file
(/vl/locate.jsp) that points it at our server, then opens an XMPP stream.
Our server completes the handshake using the documented SASL success-bypass
(no Violet password needed), keeps the rabbit idle, pushes violet:packet
commands, and logs everything the rabbit sends. No OpenJabNab, no reverse
proxy — one clean process.
Commands use the two violet:packet channels reverse-engineered from the
bytecode — programs (MessagePacket) for the rich stuff, ambient
(AmbientPacket) for the at-a-glance indicators:
- 🗣️ make it speak any text — built-in local TTS (
/api/say, espeak-ng; no cloud, nothing to install) - 🔊 play audio — stream any MP3 or WAV (e.g. a Piper TTS media URL); the rabbit fetches it over HTTP from the add-on
- 🎵 original Nabaztag jingles — the iconic Violet sounds (extracted from the
firmware as MIDI, synthesized on the fly):
acquired,ack,rfid_ok, … - 👂 move the ears to precise positions (independent left / right) via a choreography
- 💡 RGB light shows on the 5 LEDs (bottom / left / middle / right / top)
- 🌦️ belly icons — weather (sun/cloudy/smog/rain/snow/storm), stock, e-mail, air quality 👃 nose blink 💤 sleep / wake
- 🎤 talk to it — hold the head button, ask a question; it transcribes (bundled whisper.cpp), asks a conversation agent (e.g. Claude), and speaks the reply — which can itself move the ears/LEDs
- 🐇 give it standing orders — "préviens-moi quand la batterie de l'aspirateur est pleine", "rappelle-moi de sortir le linge dans 40 minutes". Nabi remembers, watches the condition (or the clock) and pipes up on its own — then forgets the task. See Give Nabi standing orders.
- 📟 (v2) react to button presses, RFID/Ztamp tags and ears moved by
hand — re-emitted as
nabaztag_eventHome Assistant events to trigger automations (Nabi as an input device)
Beyond one-off questions, you can hand Nabi a deferred task in plain French and let it watch for you:
"Nabi, préviens-moi quand la batterie de l'aspirateur est pleine." "Rappelle-moi de sortir le linge dans 40 minutes." "Oublie tout."
Nabi remembers the order, Home Assistant watches the condition (a state threshold, an entity state, or the clock), and the rabbit announces it on its own in Violet's voice once it's met — then forgets the task (single shot, up to 5 at a time).
It works by exposing three scripts as tools to your Claude conversation agent
(no add-on change): the spoken request already flows to that agent, which calls
nabaztag_remember_task / nabaztag_remind / nabaztag_forget. A 1-minute
engine in home-assistant/nabaztag_tasks.yaml evaluates the stored tasks and
routes the announcement through script.nabaztag_notify (so quiet hours still
apply unless the reminder is marked urgent).
Setup (once):
- Drop
home-assistant/nabaztag_tasks.yamlinto/config/packages/(it reusesproactive.yaml'sscript.nabaztag_notify— install that too). - Anthropic integration → enable "Control Home Assistant" (the LLM API).
- Settings → Voice assistants → Expose the three
nabaztag_*scripts and the entities Nabi should watch (vacuum, washer, weather…) so Claude knows theirentity_id. - Reload YAML. Try it by voice, or test from Developer Tools → Actions.
.
├── repository.yaml # declares this as an HA add-on repository
├── nabaztag-violet/ # ⭐ primary add-on — our own Violet-protocol server
│ ├── server.py # dependency-free Python (HTTP boot + XMPP + control API)
│ ├── config.yaml # options (server_address, log_level), ports, arch
│ ├── Dockerfile # installs python3, fetches the bytecode at build
│ ├── build.yaml
│ └── DOCS.md # install / pairing / API / status
├── nabaztag-server/ # OpenJabNab add-on (superseded — kept for reference)
│ └── … # its HTTP listener is non-functional in the upstream image
│ # (Le Terrier — the public warren at
│ # terrier.cyberloutre.fr — lives in a separate
│ # private repo; the live site is the showcase.)
├── firmware-arm/ # Naboot — custom ARM7 firmware (Phase 4/5). Forks
│ ├── apply-mods.py # RedoXyde/nabgcc wpa2 + Ed25519-gated httpflash +
│ ├── boot-mods/mdns.mtl # modernized config UI + boot-side mDNS announcer.
│ ├── crypto/ # Build via ./build.sh --remote HOST --runtime
│ ├── pages/ # {docker,podman} --mode {minimal|full|max|lean|…}.
│ ├── BRICK_FORENSICS.md # Recovery plan + bisection ladder.
│ └── RECOVERY.md
├── firmware/ # Runtime-side bytecode add-ons (mic stream, mDNS,
│ ├── micstream.mtl # OTA helper) compiled into the hybrid bootcode
│ ├── mdns.mtl # served by the add-on.
│ └── patch_main.py
├── home-assistant/
│ ├── nabaztag.yaml # ready-to-paste HA package (rest_commands + scripts)
│ ├── ambient.yaml # ambient automations (belly/ears/nose) over the REST API
│ ├── entities.yaml # optional dashboard controls (sleep/nose/ears/belly) over REST
│ ├── proactive.yaml # Nabi "alive" — proactive companion (quips, briefing, notify)
│ ├── nabaztag_tasks.yaml # standing orders ("préviens-moi quand…") via Claude tools
│ └── rfid.yaml # RFID tag → action (remembers last tag; dispatches known ones)
├── README.md
└── LICENSE
- In Home Assistant: Settings → Add-ons → Add-on Store → ⋮ → Repositories,
add
https://github.com/ClaraVnk/nabaztag. - Install Nabaztag Violet Server. In Configuration, set
server_addressto a hostname such asnabaztag.lan— not a raw IP: the bytecode resolves the XMPP server by DNS, so an IP literal fails. (bootcodeoption:ojnis the default and works.) Start the add-on. - Add a DNS record so the rabbit resolves that hostname to the HAOS host:
nabaztag.lan → <haos-ip>(UniFi "Local DNS Records", AdGuard/Pi-hole rewrite…). Verify from any device:nslookup nabaztag.lan→<haos-ip>. - Point the rabbit at the server: hold its head while powering it (LEDs go
blue), join its
NabaztagXXWi-Fi, open192.168.0.1, set Violet Platform tohttp://<haos-ip>/vl(the IP is fine here — boot/locate don't need DNS), then update and start. - Watch the add-on log:
serving bootcode → … → bound and idle — ready for commands. Optionally drophome-assistant/nabaztag.yamlinto/config/packages/.
Rabbit on its own VLAN/subnet? Allow it through the firewall to the HAOS host on TCP 80 and 5222, and give it a DHCP reservation (so its IP — and the firewall rule — stay valid; a changed IP silently breaks everything). On a flat home network there's nothing to do.
Full guide (API, pairing, troubleshooting) is in
nabaztag-violet/DOCS.md.
Don't want to run a server at home? Put your rabbit on the public warren — Le Terrier — in four steps:
- Download Naboot — the signed firmware lives at
firmware-arm/releases/naboot-stable.signed.sim(hardware-verified, reproducible). The illustrated 5-minute flashing guide is at https://terrier.cyberloutre.fr/naboot. - Flash it from your phone via the rabbit's config-mode page: unplug, hold
the head button while plugging the power back in (belly turns blue), join
the open
Nabaztag-XXXXWi-Fi, openhttp://192.168.0.1, tap firmware upgrade and upload the file. Don't unplug during the ~30 s flash. This is the only manual flash — from here on, updates are signed OTA, over the air. - Point it at Le Terrier. After it reboots, re-enter config mode (head
button while plugging in → belly blue again) and reopen
http://192.168.0.1— it now shows Naboot's setup page. Enter your home Wi-Fi, then under Advanced → Server set Boot URL host toterrier.cyberloutre.frand tap Connect rabbit. (Running your own warren? Put your server's hostname here instead.) - Adopt it. The rabbit dials Le Terrier and says its adoption code out
loud (e.g. “BRAMBLE 4 2”). Sign up on the Terrier, type that code on the
/pairpage — the rabbit is yours, and any update reaches it automatically. Missed the code? Unplug/replug — it repeats it on every connect until it's claimed.
The custom firmware is optional: a stock rabbit works fine on the Home Assistant add-on above (nothing flashed). Naboot is what unlocks signed OTA
- the modern config UI + dialing a remote warren like Le Terrier.
-
Phase 1 — working (verified live): the rabbit boots our bytecode, completes the full XMPP handshake (incl. answering its
violet:iq:sourcesquery with an init packet), becomes operational (idle) and breathes. It receives binary AmbientPacket commands (ears, belly weather/stock/mail/air-quality icons, nose) and sleep/wake. Connection + command pipeline confirmed on the real device. -
Phase 1.5 — working (verified live on the device): the rich command channel runs end-to-end. The rabbit speaks (built-in espeak-ng TTS), plays audio (MP3 and WAV), moves its ears to precise positions and runs RGB light shows — pushed as
MessagePacketprograms whose audio/choreography resources the rabbit fetches back over HTTP from the add-on. Getting here needed two protocol fixes: the server must answer the rabbit's presence (so it reaches thessFreestate where it executes pushed commands) and must not let<unbind>be mistaken for a bind. A ready-to-use Home Assistant package (incl. a speak Claude's reply on the rabbit script) ships inhome-assistant/. -
Phase 2 — voice → Claude — working (verified live), no firmware hacking: the stock bytecode already does push-to-talk — hold the head button, speak, and it records the mic and POSTs the audio to
/vl/record.jsp. The add-on decodes it, runs bundled whisper.cpp for speech-to-text, sends the text to a Home Assistant conversation agent (e.g. the Anthropic/Claude integration), and speaks the reply back. The agent can also drive the rabbit by embedding[ears …]/[led …]/[nose …]tags in its reply. TTS is bundled espeak-ng or, for a much nicer voice, Piper via the HA Piper add-on. Enable it with thevoice_pipeline/conversation_agent/tts_engineoptions. -
Phase 3 — wake word — beta (opt-in, off by default): push-to-talk is stable and needs no firmware change; the hands-free wake word ("hey Nabi") is beta — always-listening is opt-in and off at source by default (the mic does not stream until you turn it on; turning it off stops capture at the rabbit, not just server-side). It works end-to-end but the wake-word accuracy and barge-in handling are still being tuned. The stock firmware only records the mic on a physical button press — the server can't start a recording. So passive listening required a custom mic-streaming bytecode. Solution: a hybrid bytecode that adds a server-triggered UDP microphone stream (
firmware/micstream.mtl, à laopenab) to the stock Violet bytecode, compiled with RedoXyde'smtl_linuxin a Linux x86 container. Server-sidewake_loopswaps the audio buffer every few seconds, runs bundled whisper.cpp, and if "Nabi" is heard, the rest of the utterance is sent to the conversation agent and the reply spoken back. Confirmed live: "Nabi, raconte une blague" → Claude reply spoken full-voice in the Piper voice with action tags driving ears + LEDs simultaneously. -
Phase 4 — firmware upgrades — done (Naboot, hardware-verified 2026-06-20): with the toolchain stood up, built Naboot, a fork of
RedoXyde/nabgcc(wpa2 branch) compiled as a.simflashable via the config-mode upload page (no JTAG / no opening). Adds an Ed25519-gated OTA path (verifySigopcode 152 +flashFirmwaregated on signature againstfirmware-arm/keys/signing_pubkey.h), strips the upstream debugprintfglue, and wraps the linker script's vector / startup / bytecode sections inKEEP()so--gc-sectionsis safe to enable. Build is reproducible from the pinned upstream commits (nabgcc2c05b53f,mtl_linux7e557a15). Status (2026-06-20): brick root-caused and fixed. It was a linker bug, not a Naboot mod —-fdata-sectionssplits.bssinto per-variable.bss.*sections that the nabgcc linker (sys/ml67q4051.ld) left outside__bss_start__..__bss_end__, so the startup zero-loop never cleared them → garbage globals → Data Abort at boot. This hit every mode (evenminimal/vanilla), which exoneratesmodernize_pages(the original suspect). The first fix (97e05d1) bracketed*(.bss .bss.*)so the zero-loop clears all of it — but on the 16 KB SRAM that pushes__heap_start__past the end of.bss, leaving zero VM heap, so the bytecode loader stalls right afterbc.jsp. The final fix (3cd45d8) reverts the bracket (keeping the ~9 KB heap the working firmware relies on) and instead forces the one global that actually faulted — the audio FIFO indicesplay_w/play_r— into.data(init 0), soaudioPlayFetchByte()can't read a garbage index beforeaudioPlayStart()runs. Recovered with a Raspberry Pi as a bit-bang JTAG adapter (no probe, F/F Dupont to the 8-pin header) + a TCL reimplementation of the OKI ML67Q4051 flash sequence on stock OpenOCD; flash read/erase/program verified byte-exact. Bothminimaland the fullleanbuild (modernized pages + sig-gated OTA- mDNS) now boot and run on the rabbit — JTAG confirms it executes in User
mode with a live main loop, no longer the Abort dead-loop. See
firmware-arm/RECOVERY.mdandBRICK_FORENSICS.md. Six pre-built variants:minimal(rescue, vanilla bootloader + verifySig only),signed-stock/pages-only/mdns-only(isolation bisects),full(sig-gated OTA + modernized UI),max(full+ boot-side mDNS),lean(max+--gc-sections).
- mDNS) now boot and run on the rabbit — JTAG confirms it executes in User
mode with a live main loop, no longer the Abort dead-loop. See
-
Phase 5 — setup UX — done (hardware-verified 2026-06-21): rewrote the four config-mode HTML pages (
page_a / page_done / page_u / page_error) in modern semantic HTML with a dark theme, mobile viewport, and the same form field names + template markers the firmware backend expects, so the existingcbhttp/httpindex/httpflashpaths inboot.0.0.0.13.mtlkeep working unchanged. Saved ~5 KB of ROM in the process. Source HTML lives underfirmware-arm/pages/; injection into the boot bytecode is done byfirmware-arm/apply-mods.py:modernize_pages. The modernized pages, rendered on a real rabbit (theleanbuild) from a phone at192.168.0.1: -
Phase 6 — Le Terrier — DONE (live at https://terrier.cyberloutre.fr/): the public warren every Naboot rabbit dials back to. Dependency-free Python (stdlib + Flask for the owner UI), deployed on Loutre's VPS via a rootless Podman quadlet behind Caddy 2 (auto-Let's-Encrypt). Reachable from any network:
/vl/locate.jsp→ ping/broad/xmpp_domain,/vl/bc.jsp→ signed.hybridbaseline bytecode (103 542 B), XMPP:5222published direct (the rabbit hardware has no TLS, so Caddy stays on the HTTP/HTTPS layer). The owner UI is FR/EN, has Open Graph + Twitter card metadata (so the link previews cleanly in iMessage), and walks an owner through signup → claim → pair → drive in three steps. Multi-tenant per-MAC state in SQLite; per-owner / per-rabbit accounts; ProxyFix so HTTPS canonical URLs resolve cleanly behind Caddy. Adoption is wired up: an unclaimed rabbit that dials in mints a pair code and says it out loud ("BRAMBLE 4 2") so its human can claim it on/pair; the on-device download + flash + adopt guide is at/naboot. Still on the list: per-rabbit bytecode minting (every paired rabbit gets the same baseline today). The Terrier source now lives in a private repo (it carries the warren's state); the live site is the showcase. Naboot is the firmware on the rabbit; Le Terrier is the home they all dial back to. -
Phase 7 — modernize the Metal toolchain in Python (in progress, working v1 shipped): Toolchain landed (all in
firmware-arm/tools/):mtl_dis.py— disassembler. Decodes all 153 opcodes, reconstructs globals, resolves jump targets, annotatesOPexeccallsites with names (via--src boot.0.0.0.13.mtl), auto-strips theamber<hex>...MindHTTP wire wrapper. Outputs text, JSON, or--format masmfor re-assembling.mtl_dis.py --check— structural validator. Refuses bins with unknown opcodes, out-of-range jump targets, OPexec to out-of-range fun indices, or funtable entries outside the code section. Should gate every freshly-built.simbefore flash.mtl_asm.py— bytecode encoder. Library API + line-oriented.masmtext format. Validated byte-for-byte against the C++mtl_compileron real production bytecode: fullbin → .masm → binround-trip onboot.0.0.0.13.bin(31 437 B) andbootcode_hybrid.bin(103 525 B, 459 functions).mtl_comp.py—.mtlsource compiler. Recursive-descent parser + codegen that emits byte-identical.binto the C++mtl_compilerfor the supported subset (proto / var / const / fun, integers, strings, builtins, user-fun calls, arithmetic and comparison, if/then/else, let-in, set, sequencing). Tested byte-for-byte on five progressively richer programs up to a 182-byte real program with 4-level nested let, recursion, and chained ifs. The C++ toolchain is no longer the only way to compile Metal source on this stack.
- Phase 7a — Python MTL compiler (pending): rewrite the existing
C++
mtl_compilerin Python. Same input (.mtlsource), same output (the rabbit's bytecode), zero behavioral change for the device. Win: no more aging C++ toolchain (g++-multilib, build dance), one fewer barrier for contributors, easier to add new opcodes / sanity checks / linting. Grammar is small (seeDT_metal_03_01_13_grammaire.pdf). Estimated 2-4 weeks of focused work. - Phase 7b — "view as Python" layer (pending): on top of 7a, add a bidirectional MTL ↔ Python source mapper so people can read, write and review bytecode logic in familiar Python syntax. The rabbit still executes MTL bytecode; the Python is purely a contributor-facing surface. Doable mechanically, not research.
-
Phase 8 — shrink the firmware by moving boot-bytecode logic to C (prototype landed): the rabbit's bootloader is ~3000 lines of Metal bytecode (
mtl/boot/boot.0.0.0.13.mtl) running on top of a tiny stack VM. Native ARM is roughly 3× denser than Metal bytecode, so rewriting boot-side hot paths in C — and stripping the corresponding VM opcodes — would free 10–20 KB of ROM (the flash budget is 124 KB andmaxuses ~92%). Scope clarification: Phase 8 only touches the boot bytecode, which is already inside the.simand already requires a flash to change. It does NOT touch the runtime bytecode (firmware/*.mtl), which is downloaded fresh from the server on every boot and stays OTA-instant.phase8-rombuild mode stacks 8 source-level slices on top oflean: (1) the 4 config-portal HTML pages move from Metal globals into a C-sideconst char[]and render through a newOPpageRenderopcode; (2)pagefill/listreplacestrand 4 stillborn helpers pruned as orphans after rom_pages obsoleted their callers; (3)mkwav 8000 1 16precomputed at build time and inlined as a 46-byte literal; (4)dump/dumpscanreduced to identity, callsites inlined, defs dropped; (5) all 86Secho{ln} "literal"calls replaced withnil; (6) user echo helpersMACecho/SEQecho/IPechocollapsed to identity (return-value contract preserved); (7) every dynamic-argSecho/Iecho/{ln}opcode keyword deleted — verified as pure stack passthroughs in vinterp.c; (8) every prior slice composable + idempotent. The resulting.simis 220 330 B — 17.9 KB smaller than minimal/rescue (238 706 B), 6.2 KB smaller than the previous best (leanat 226 658 B) — and ~10 KB of vmem RAM is freed at runtime. The remaining big bytecode is concentrated in stateful paths (loop,cbnettcp,tcpwrite,tcpevent) where C rewrites would require hardware-tested validation; this stack stays at the safe envelope. -
Phase 9 — gateway-independent resolution (mDNS) — done, Claude live on the recovered rabbit (2026-06-21): after the un-brick the rabbit booted and reached XMPP, but the home gateway silently dropped its unicast DNS — a per-client ACL / DNS-flood guard, proven with a control test: the same
A? <name>query is answered for other hosts and refused only when the source IP is the rabbit's. So it could never resolve its server and stayed all-orange. Rather than touch the firewall, taught the rabbit to resolve over multicast mDNS:firmware/mdnsresolve.mtl+patch_dns.pymake the runtime bytecode'sdnsreqroute.localnames to224.0.0.251:5353instead of the gateway — no flash (runtime bytecode, served fresh bybc.jsp). The rabbit queries from port 1597, so an RFC 6762 §6.7 responder answers it as a legacy unicast query and the existing response path parses it unchanged.locatereturnsnabaztag.local; a standard mDNS responder (nabaztag-violet/tools/nabmdns.py, no spoofing) answers it. Confirmed live: fresh boot → multicast resolve → full XMPP bind → the whole Phase 2 voice loop runs on the recovered rabbit (button → whisper → Claude → Piper TTS, spoken full-voice with ears/LED action tags). No gateway DNS, no reply-spoofing. The unicast fallback (nabdns.py, for stock un-patched bytecode) and the deploy steps live innabaztag-violet/tools/DNS-HELPER.md.
- Tested on the Nabaztag:tag (v2, 2006 — mic + RFID). The v1 (2005) likely works (same Violet protocol) but is untested. The Violet add-on drives a stock rabbit with no hardware mod and no flashing required — but the optional Naboot custom firmware (Phase 4) is flashed (over the air or the config-mode upload page) for signed OTA + a modern config UI.
- Host architecture: amd64.
No — don't panic. Orange (blinking or solid) means the rabbit is awake and looking for its Wi-Fi / server, not dead. A bricked rabbit shows no boot animation at all. These 2006 radios are simply flaky at re-associating Wi-Fi on boot — after a power-cycle, a firmware OTA, an add-on restart, or any reboot, the rabbit often comes up orange and just sits there with no network traffic for a minute or more.
The fix is almost always a clean power-cycle: unplug the power, wait ~10
seconds (so it fully discharges), plug it back in. It then boots → fetches its
bytecode → locate → connects → breathes. If the first try doesn't catch,
do it once more — it's normal, not a fault.
Tips: it's the Wi-Fi association that's flaky, so a strong signal / 2.4 GHz / a
less crowded channel helps. Don't restart the add-on repeatedly in a short window
(each restart drops the rabbit's connection and makes it re-seek). A ping from
HA failing is not a sign of trouble if the rabbit is on a different VLAN
(cross-VLAN ICMP is usually blocked) — check the add-on's status/logs instead.
| Symptom | Meaning | Fix |
|---|---|---|
| Belly LEDs blink/glow orange, boot animation plays | Booted, seeking Wi-Fi/server | Power-cycle (unplug ~10 s) |
| No LEDs / no boot animation at all, frozen | Possible bad flash | Config-mode upload (hold head button on power-up → upload a signed .sim); JTAG as last resort |
The rabbit's original TTS relied on Acapela's now-dead web service. The add-on
ships its own fully-local TTS (espeak-ng, robotic but offline) and can
speak through any Home Assistant TTS engine — so the voice is your choice,
from robotic-and-local to Siri/Alexa-natural. Set it in the add-on options:
-
tts_engine—espeak(bundled, robotic, 100% local) orpiper/ha(speak through the HA TTS entity below). -
tts_entity— when the engine ispiper/ha, which HA voice to use. Point it at anytts.*entity in your Home Assistant:tts_entityVoice Local? Naturalness tts.piperPiper (neural, on-device) ✅ local, free good tts.home_assistant_cloudHA Cloud / Nabu Casa ☁️ (privacy-friendly) very natural tts.elevenlabsElevenLabs ☁️ paid the most natural tts.google_cloud/tts.azure/tts.openaiGoogle / Azure / OpenAI ☁️ paid very natural (Wyoming) Kokoro / XTTS newer neural, self-hosted ✅ local very natural, more setup Whatever you install in HA appears as a
tts.*entity — just name it here. -
voice_pitch— pitch-shift up for a cuter timbre.0is the most natural;~12–18is the playful "mignonne" range;>30goes chipmunk.
Privacy note:
espeakandpiperstay on your network; the cloud engines send the spoken text to a third party. Pick the row that matches your trade-off.
You can also bypass TTS entirely: generate audio elsewhere and hand the rabbit a
media URL via GET /api/play?url=… (it decodes MP3 and 22 kHz/16-bit mono WAV),
or POST the audio bytes directly and the add-on serves them.
- The Violet documentation at nabaztag.com/doc — official API, the Metal language grammar, and the Télécom SudParis report that reverse-engineered the v2 boot + XMPP protocol (the SASL success-bypass).
- OpenJabNab — reference PHP/C++ Violet
reimplementation (and the bundled
bootcode.violet). - RedoXyde (
mtl_linux,nabgcc) and Pixel166 (nabAsm/nabDasm) — the open Metal firmware toolchain that makes Phase 2 possible.
Images: Home Assistant logo © the Home Assistant project. Nabaztag photo by Catalarem, via Wikimedia Commons, licensed CC BY-SA 2.5.
Apache License 2.0 — the same license as Home Assistant.


