Skip to main content
Glama
README.md
# nabaztag-ai 🐰

[![CI](https://github.com/maubau/nabaztag-ai/actions/workflows/ci.yml/badge.svg)](https://github.com/maubau/nabaztag-ai/actions/workflows/ci.yml)

> AI brain for the original Nabaztag rabbit β€” LLM tool-use embodiment, voice pipeline, MCP server. Physical AI, 2006 edition.

## What & why

In 2006 the Nabaztag:tag was the first consumer connected device β€” a Wi-Fi rabbit that read the news and wiggled its ears. Twenty years later, this project revives a **stock, unopened** Nabaztag:tag as the body of a modern AI assistant: wake word β†’ speech-to-text β†’ an LLM with tool use β†’ text-to-speech, played through the rabbit's own speaker, with the LLM deciding ear positions and LED moods itself.

There is also a **full-duplex mode**: the wake word opens a continuous conversation and you can talk over the rabbit mid-sentence. That needs playback which can be cancelled β€” the 2006 decoder cannot be β€” so it routes audio through the reSpeaker instead, whose on-chip echo cancellation was measured before being relied on. The rabbit keeps the ears and the lights.

The rabbit stays 100% original. It talks to a self-hosted [OpenJabNab](https://github.com/OpenJabNab/OpenJabNab) server; modern audio input comes from an external reSpeaker 4-mic array; the brain runs on a UDOO Bolt (any Linux PC or SBC works) β€” closing a 20-year loop between the first consumer connected device and modern edge AI.

Full spec: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).

## Hardware BOM

| Item | Role | ~Price |
| :--- | :--- | :--- |
| Nabaztag:tag (V2), stock | Body: speaker, ears, LEDs, RFID, button | ~€70 (second-hand) |
| reSpeaker Flex XVF3800 Circular-4 | Ears-in: 4-mic array, beamforming, DoA | ~€50 |
| UDOO Bolt (or any Linux PC/SBC) | Brain: OJN server, rabbit-brain, MCP | you probably have one |
| *(usually nothing)* β€” the Bolt's own M.2 Wi-Fi module runs the AP | Dedicated legacy WPA-TKIP AP (Β§4.1) | β€” |

## Quickstart (no hardware needed)

```bash
git clone https://github.com/<you>/nabaztag-ai && cd nabaztag-ai
python -m venv .venv && source .venv/bin/activate
pip install -e "brain[dev]"
pytest                      # runs against the mock-OJN simulator
python -m rabbit_brain.body.demo --mock-ojn   # drive a simulated rabbit
```

With real hardware, start from **Gate S0** (the rabbit's legacy Wi-Fi segment β€” see [docs/ARCHITECTURE.md Β§4.1](docs/ARCHITECTURE.md) and [ojn/](ojn/)).

On the Bolt, the audio-in pipeline (reSpeaker capture, openWakeWord, silero-vad, DoA) needs the extras and the XVF3800 udev rule:

```bash
sudo apt install libportaudio2       # sounddevice's runtime library
brain/scripts/install-audio.sh       # add --stt-local for stt_profile: local
sudo install -m 644 brain/udev/70-respeaker-flex.rules /etc/udev/rules.d/
sudo udevadm control --reload && sudo udevadm trigger
python -m rabbit_brain.audio.demo --config config.yaml --mock-ojn   # mic smoke test
```

`config.yaml` is gitignored, so `git pull` never updates it β€” a config created from an older example can silently keep stale settings (e.g. a missing `deepgram.endpointing`). Check it against the current shape after pulling:

```bash
brain/scripts/config-doctor.py config.yaml         # report drift
brain/scripts/config-doctor.py config.yaml --fix   # rewrite present-but-stale keys in place
```

(The script exists because openWakeWord's Linux dependency on tflite-runtime has no Python 3.12 wheel; we only use its ONNX backend, so it is installed `--no-deps` on top of the `brain[audio]` extra. CI runs the same script on Ubuntu 24.04/3.12.)

### Full voice runtime

One persistent process owns the whole conversation loop (one BodyController, one Mp3Server on :8090, one EventListener on :8091):

```bash
python -m rabbit_brain.runtime --config config.yaml
```

Pipeline: reSpeaker β†’ openWakeWord β†’ **Deepgram Flux** (recognition *and* end-of-turn detection in one pass, `stt_profile: flux`) β†’ **OpenAI Responses** (`gpt-5.4-mini`) β†’ **Piper** (self-hosted, `it_IT-paola-medium` / `en_GB-alba-medium`, voice routed by the STT-detected language; Deepgram Aura as automatic fallback) β†’ local MP3 β†’ OpenJabNab β†’ Nabaztag speaker. Set `conversation.mode: realtime` (with `audio_out.backend: local`) for the full-duplex path instead. `stt_profile: cloud` switches back to nova-3 + a local silero VAD window, which is what Flux replaces. The runtime loads `.env` automatically (shell environment wins; `--env-file ''` disables). Needs `OPENAI_API_KEY`, `DEEPGRAM_API_KEY` and the OJN vars in `.env` (values never logged). **Do not run the runtime and the MCP server at the same time** β€” both bind :8090/:8091 and each would create its own BodyController. Pick one per session.

Rabbit-facing MP3 delivery goes through **Apache**, not the built-in server: the MTL decoder ignores aiohttp-served audio (hardware finding). Install the alias once (`ojn/apache/brain-audio.conf.example`) and set `NABAZTAG_MP3_SERVE_HTTP=0` + `NABAZTAG_MP3_BASE_URL=http://192.168.66.1/brain-audio` in `.env`. A plain `chmod` on the audio dir is **not** enough (403: www-data can't traverse your home directory) β€” the conf example includes the `setfacl` commands that hardware-confirmed fix it.

## Architecture

```
Nabaztag:tag ◄─Violet proto─► OpenJabNab (self-hosted) ◄─REST─► rabbit-brain
   stock, WPA-TKIP segment         on the Bolt            wake→VAD→STT→LLM(tools)→TTS
                                                                    β”‚
reSpeaker XVF3800 ──USB audio + DoA──────────────────────► BodyController (arbiter)
                                                                    β–²
                                                     nabaztag-mcp β”€β”€β”˜ β†’ Claude Desktop/Code
```

All body output funnels through the `BodyController` (single owner of the body, priority queue); bodies are swappable behind the `BodyAdapter` protocol.

## Roadmap

- **v1** β€” Nabaztag:tag via OpenJabNab (this repo, phases S0 β†’ 4)
- **P1** β€” `ReachyMiniAdapter`: same brain, different body (comparison protocol)
- **P2** β€” `VentunoQLocalProfile`: fully local STT/TTS/LLM on Arduino VENTUNO Q

## License

Apache-2.0 (root, covers `brain/`, `mcp/`, `demos/`, docs, configs) β€” with one exception:
[ojn/plugin_events/](ojn/plugin_events/) is an OpenJabNab plugin (webhook egress for
button/RFID events) and, as a derivative work of OpenJabNab, is licensed under **OpenJabNab's
GPL v2** (its LICENSE is OJN's COPYING, copied verbatim). It is cleanly separated: no code is
shared with `brain/` or `mcp/` β€” the brain talks to OJN only over HTTP. The choreography
plugin foreseen in the architecture turned out to be unnecessary (Gate G0).

*Nabaztag is a trademark of its respective owner; this is an independent community project, not affiliated with Violet/Aldebaran.*