Custom firmware and server for the M5Stack StackChan (CoreS3, ESP32-S3) that turns it into Tarquin: a Victorian butler with a dry wit, a British male voice, a camera that follows you, and a bridge to your Grok bots.
StackChan (firmware/) ── WiFi/WebSocket ──► Tarquin server (server/, Go) ──► Muse Spark 1.3 (Meta Model API)
mic → VAD → PCM ├─ parakeet-server (STT, ggml) ◄── Grok bots (REST, bearer)
speaker ◄ TTS PCM └─ otter-vox (Audio8 TTS, ggml)
camera → motion + face assist
servos, LEDs, face on screen
No Python at runtime. Speech runs on CPU via ggml (parakeet.cpp for STT, otter-vox for TTS).
/api/sleep.| path | what |
|---|---|
firmware/ | PlatformIO (pioarduino, Arduino core 3.x) firmware for CoreS3 + StackChan-BSP |
server/ | Go server: WebSocket device link, STT/LLM/TTS pipeline, bot REST API |
docker-compose.yml, server/Dockerfile | Coolify deployment (tarquin + parakeet-server) |
deploy/voices/ | extra otter-vox voices (tarquin.codes + tarquin.txt) |
tools/voice/ | one-off voice encoder (Python, offline) |
docs/ | protocol, bot API, voice |
docker-compose.yml.OTTER_TOKENS=device:<tok>,grok-alpha:<tok>,admin:<tok> and META_MODEL_API_KEY=<key>
(the Muse Code plan key from ~/.config/muse/auth.json → providers.meta.api_key works). Optional:
OTTER_VOX_ARGS="--backend vulkan" on a GPU host (uncomment devices in the compose file).tarquin service domain to https://bot.ferreo.dev:8480. Coolify's proxy terminates TLS and
the robot connects to wss://bot.ferreo.dev/ws/device (its default URL). The firmware pins
Let's Encrypt's ISRG Root X1, so plain ws:// is only used for LAN testing.docs/bot-api.md.First boot of the stt container downloads tdt_ctc-110m (~255 MB) into a volume. The tarquin
image includes otter-vox and the Audio8 weights from the PikaOS PPA (Debian sid base for glibc 2.43).
Local run: cp .env.example .env, fill it, docker compose up --build.
cd firmware
cp secrets.ini.example secrets.ini # optional compile-time WiFi/server defaults
pio run -t upload # /dev/ttyACM1 by default; ~/.local/bin/pio if installed via uv
pio device monitor
Without secrets.ini the robot boots into setup: join WiFi OtterChan-XXXX, open http://192.168.4.1/,
enter WiFi, server URL (default wss://bot.ferreo.dev/ws/device; ws://host:8480/ws/device for LAN), and the device token. Or send
{"ssid":"…","pass":"…","url":"wss://…/ws/device","token":"…"} as one line on the USB serial console.
Hold the side button while powering on to re-enter setup. status / reset on serial also work.
Factory firmware can be restored any time with M5Burner.
OTTER_LLM_REASONING=minimal keeps replies snappy (~2 s, ~120 hidden tokens; low roughly doubles that).--serve --no-play -o - patch (in this repo's sibling otter-vox tree);
without it the server falls back to spawning otter-vox per sentence, which reloads the model each time.C++
51.6%
Go
44.2%
C
1.6%
Python
1.5%
Dockerfile
1.1%
Custom firmware and server for the M5Stack StackChan (CoreS3, ESP32-S3) that turns it into Tarquin: a Victorian butler with a dry wit, a British male voice, a camera that follows you, and a bridge to your Grok bots.
StackChan (firmware/) ── WiFi/WebSocket ──► Tarquin server (server/, Go) ──► Muse Spark 1.3 (Meta Model API)
mic → VAD → PCM ├─ parakeet-server (STT, ggml) ◄── Grok bots (REST, bearer)
speaker ◄ TTS PCM └─ otter-vox (Audio8 TTS, ggml)
camera → motion + face assist
servos, LEDs, face on screen
No Python at runtime. Speech runs on CPU via ggml (parakeet.cpp for STT, otter-vox for TTS).
/api/sleep.| path | what |
|---|---|
firmware/ | PlatformIO (pioarduino, Arduino core 3.x) firmware for CoreS3 + StackChan-BSP |
server/ | Go server: WebSocket device link, STT/LLM/TTS pipeline, bot REST API |
docker-compose.yml, server/Dockerfile | Coolify deployment (tarquin + parakeet-server) |
deploy/voices/ | extra otter-vox voices (tarquin.codes + tarquin.txt) |
tools/voice/ | one-off voice encoder (Python, offline) |
docs/ | protocol, bot API, voice |
docker-compose.yml.OTTER_TOKENS=device:<tok>,grok-alpha:<tok>,admin:<tok> and META_MODEL_API_KEY=<key>
(the Muse Code plan key from ~/.config/muse/auth.json → providers.meta.api_key works). Optional:
OTTER_VOX_ARGS="--backend vulkan" on a GPU host (uncomment devices in the compose file).tarquin service domain to https://bot.ferreo.dev:8480. Coolify's proxy terminates TLS and
the robot connects to wss://bot.ferreo.dev/ws/device (its default URL). The firmware pins
Let's Encrypt's ISRG Root X1, so plain ws:// is only used for LAN testing.docs/bot-api.md.First boot of the stt container downloads tdt_ctc-110m (~255 MB) into a volume. The tarquin
image includes otter-vox and the Audio8 weights from the PikaOS PPA (Debian sid base for glibc 2.43).
Local run: cp .env.example .env, fill it, docker compose up --build.
cd firmware
cp secrets.ini.example secrets.ini # optional compile-time WiFi/server defaults
pio run -t upload # /dev/ttyACM1 by default; ~/.local/bin/pio if installed via uv
pio device monitor
Without secrets.ini the robot boots into setup: join WiFi OtterChan-XXXX, open http://192.168.4.1/,
enter WiFi, server URL (default wss://bot.ferreo.dev/ws/device; ws://host:8480/ws/device for LAN), and the device token. Or send
{"ssid":"…","pass":"…","url":"wss://…/ws/device","token":"…"} as one line on the USB serial console.
Hold the side button while powering on to re-enter setup. status / reset on serial also work.
Factory firmware can be restored any time with M5Burner.
OTTER_LLM_REASONING=minimal keeps replies snappy (~2 s, ~120 hidden tokens; low roughly doubles that).--serve --no-play -o - patch (in this repo's sibling otter-vox tree);
without it the server falls back to spawning otter-vox per sentence, which reloads the model each time.C++
51.6%
Go
44.2%
C
1.6%
Python
1.5%
Dockerfile
1.1%