Skip to content

Latest commit

 

History

History
295 lines (215 loc) · 11.4 KB

SETUP.md

File metadata and controls

295 lines (215 loc) · 11.4 KB

First-Boot Setup — Bringing Up Your StackChan

Step-by-step for taking a fresh M5Stack StackChan from the box to a working, fully-self-hosted voice robot. The backend (all four Docker containers on a single Docker host) is assumed to already be deployed — if you're starting fresh, skim README.md first.

This guide assumes you've already substituted the placeholders from README.md's "Configuring for your environment" section with the real values for your setup.


0. Pre-flight checks (do these with the robot still in the box)

Run these from any LAN-connected machine — should all succeed within a second or two:

curl -s http://<XIAOZHI_HOST>:8003/xiaozhi/ota/
# Expect:  OTA接口运行正常,向设备发送的websocket地址是:ws://<XIAOZHI_HOST>:8000/xiaozhi/v1/

curl -s http://<XIAOZHI_HOST>:8081/health
# Expect:  {"status":"ok", ...}

curl -s http://<XIAOZHI_HOST>:8090/health
# Expect:  {"status":"ok", ...}

If any fails, fix the backend before dealing with the robot:

  • OTA down → ssh <XIAOZHI_USER>@<XIAOZHI_HOST> 'docker logs --tail 40 xiaozhi-esp32-server'
  • Dashboard/bridge down → ssh <XIAOZHI_USER>@<XIAOZHI_HOST> 'docker logs --tail 40 bridge'
  • dotty-behaviour down → ssh <XIAOZHI_USER>@<XIAOZHI_HOST> 'docker logs --tail 40 dotty-behaviour'
  • dotty-pi down → ssh <XIAOZHI_USER>@<XIAOZHI_HOST> 'docker logs --tail 40 dotty-pi'

1. About the firmware situation

The StackChan ships from M5Stack with stock firmware that:

  • Shows a pairing-code screen on first boot
  • Uses BLE-based WiFi provisioning via M5Stack's StackChanWorld phone app
  • Registers the device to M5Stack's cloud for device management

There is no SoftAP captive portal in stock firmware (some older third-party xiaozhi builds had one; the shipped firmware does not).

To run the StackChan fully self-hosted (no phone-app account, no vendor cloud, your own xiaozhi-server as the endpoint), reflash it with Dotty's pinned BrettKinny/StackChan@dotty firmware fork. The official m5stack/StackChan tree is its upstream, but it does not contain Dotty's state, motion, LED, and perception changes.

The StackChan's on-device Settings app has no Advanced Options or OTA URL editor. The similarly named Advanced tab documented by Xiaozhi belongs to its browser-based SoftAP captive portal and appears only when that provisioning mode is active. The server address is compiled into the firmware. Earlier fw-v* GitHub releases carried binaries locked to the maintainer's LAN server; those binaries have been removed and the releases are now source-only. A portable prebuilt build is tracked in #190. Build from source with your own CONFIG_OTA_URL as shown below.

The exact firmware source is the firmware/ submodule of this repo. It pins BrettKinny/StackChan@dotty, whose firmware/fetch_repos.py pulls 78/xiaozhi-esp32 v2.2.4 and applies the StackChan integration patch.


2. Build and flash open firmware

Requires ESP-IDF v5.5.4. Easiest path: the official espressif/idf:v5.5.4 Docker image.

2a. Clone and configure

git clone --recursive https://github.com/BrettKinny/dotty-stackchan.git
cd dotty-stackchan/firmware/firmware

Point the firmware at your xiaozhi-server for OTA. Edit sdkconfig.defaults and add (or modify) the line:

CONFIG_OTA_URL="http://<XIAOZHI_HOST>:8003/xiaozhi/ota/"

Trailing slash matters — that's the path the server exposes. The build stops with an error while the <XIAOZHI_HOST> placeholder is still there.

sdkconfig.defaults seeds a newly generated sdkconfig; it does not overwrite an existing one. On a repeat build, either make the same change through idf.py menuconfig (Xiaozhi Assistant → Default OTA URL) or remove the generated sdkconfig before rebuilding. Verify the effective value after configuration with:

grep '^CONFIG_OTA_URL=' sdkconfig

2b. Build inside the IDF container

docker run --rm -it -v "$PWD":/project -w /project \
  espressif/idf:v5.5.4 \
  bash -c "python3 fetch_repos.py && idf.py build"

fetch_repos.py pulls the xiaozhi-esp32 dependency and applies the StackChan patches; idf.py build produces build/*.bin.

2c. Flash with device passthrough

Plug the device in via USB-C. It typically appears as /dev/ttyACM0 on Linux (/dev/cu.usbmodem* on macOS — adapt the --device flag).

docker run --rm -it --device=/dev/ttyACM0 \
  -v "$PWD":/project -w /project \
  espressif/idf:v5.5.4 \
  idf.py -p /dev/ttyACM0 flash

If the build reported a non-standard flash command (e.g. a merged-bin flow), use that instead.

2d. First boot after flash

Seeing “Welcome! Let's get started.” is expected on a fresh flash. It is the StackChan app-configuration wizard, controlled by an NVS flag; it does not mean the compiled CONFIG_OTA_URL was ignored.

For a self-hosted setup:

  1. Tap Skip on the welcome screen. This bypasses the M5Stack account/BLE wizard for the current boot.
  2. If the launcher remains visible, open SETUP, then use its home control to exit. Closing SETUP starts the Xiaozhi voice application.
  3. With no saved SSID, the pinned build enters its default Xiaozhi hotspot provisioner. Join the temporary Xiaozhi-* Wi-Fi network and open the URL shown on the robot (normally http://192.168.4.1).
  4. Select the robot's 2.4 GHz Wi-Fi network. The browser portal also has an Advanced tab where you can confirm or override ota_url.
  5. After Wi-Fi connects, the device POSTs to the compiled/persisted OTA URL, receives the WebSocket endpoint, and connects.

Tail the server logs while the device boots so you can watch the handshake happen (see step 4 below).


3. WiFi credentials

The pinned firmware does not define CONFIG_WIFI_SSID or CONFIG_WIFI_PASSWORD; adding those names to sdkconfig.defaults has no effect. Wi-Fi credentials live in NVS and are supplied through the Xiaozhi hotspot portal described above (or retained from an earlier compatible provisioning).

The device must land on a 2.4 GHz network. ESP32-S3 does not do 5 GHz.


4. Watch the handshake

While the freshly flashed device boots, tail the server logs in one terminal:

ssh <XIAOZHI_USER>@<XIAOZHI_HOST> 'docker logs -f xiaozhi-esp32-server'

Within ~30s of reboot you should see (in order):

  1. A POST /xiaozhi/ota/ line — device asking for config
  2. A WebSocket connect line with the device's MAC
  3. A vad / asr init line when the device first starts listening

If the device isn't on the list after 60s:

  • Confirm it actually joined your LAN: check your router's DHCP table for a new ESP32-looking MAC
  • If it's on the LAN but not reaching OTA, try the OTA URL from a phone on the same WiFi — it should return plain text, not a connection error
  • Power-cycle the device (hold power 3s, unplug, replug)

5. First voice test

  1. Wait for the face to change from "connecting" to a neutral/idle expression. That means the WebSocket is up.
  2. Say a wake phrase and then a short message (the default wake phrase depends on your xiaozhi-esp32 build — consult its README). Some builds also support a press-to-talk button on the side.
  3. Watch the logs — you should see:
    • An ASR line with transcribed text
    • A PiVoiceLLM call (routes to the dotty-pi brain container)
    • A TTS line with the response text
    • Face animation changes to match the leading emoji
  4. The robot speaks. If you hear audio but no face change, check that the response starts with one of: 😊 😆 😢 😮 🤔 😠 😐 😍 😴.

Expected first-audio latency: ~5–8s after you stop speaking (the pi agent cold-start on first turn). Subsequent turns are faster once the model is warm.


6. Tune if needed

All of these are edits to data/.config.yaml on the Docker host followed by docker compose restart.

Complaint Edit File
"It cuts me off mid-sentence" raise min_silence_duration_ms from 700 to e.g. 1000 data/.config.yaml → VAD.SileroVAD
"It waits forever after I stop talking" lower min_silence_duration_ms to 400 same
"I don't like the voice" change voice: to any Edge Neural voice data/.config.yaml → TTS.EdgeTTS / StreamingEdgeTTS
"Responses are too long" add "Keep replies under 20 words." to the persona data/.config.yaml → prompt: block
"Too slow to reply" switch LLM model see dotty-pi/README.md for model selection rules
"No facial expression change" check response actually starts with a supported emoji (tail logs) —

7. If you brick it

Hold the power button for 5s to force reboot. Hold for 10s+ to enter download mode on some boards (used for firmware reflashing).

To recover, re-flash from step 2c. If the device won't enter download mode via the button, hold the BOOT button (if present) while pressing RESET, then release RESET, then release BOOT.


8. Legacy SoftAP flow (older firmware only)

This section may match older xiaozhi-esp32 builds that ship a SoftAP captive portal. Stock M5Stack firmware does NOT work this way — if you're on current stock, see sections 1–2 above.

Some older xiaozhi builds expose a SoftAP captive portal on first boot:

  1. Device creates an open SSID like Xiaozhi-XXXX, ML307R-XXXX, or Device-XXXX.
  2. Join it from your phone; a captive portal typically auto-pops. If not, browse to http://192.168.4.1.
  3. Pick your 2.4 GHz LAN SSID, enter the password.
  4. Open the portal's Advanced tab and paste the OTA URL: http://<XIAOZHI_HOST>:8003/xiaozhi/ota/ (trailing slash required).
  5. Save. Device reboots and connects.

If you have a build where this works, it's the fastest provisioning flow. It just isn't what M5Stack ships today.

This Advanced control is a tab in the browser portal at 192.168.4.1, not an item in the StackChan's on-device Settings app.


9. When it's working: bookmark these

  • Tail voice pipeline: ssh <XIAOZHI_USER>@<XIAOZHI_HOST> 'docker logs -f xiaozhi-esp32-server'
  • Tail brain container: ssh <XIAOZHI_USER>@<XIAOZHI_HOST> 'docker logs -f dotty-pi'
  • Tail perception/behaviour: ssh <XIAOZHI_USER>@<XIAOZHI_HOST> 'docker logs -f dotty-behaviour'
  • Admin dashboard: open http://<XIAOZHI_HOST>:8081/ui in a browser.
  • Dashboard health: curl http://<XIAOZHI_HOST>:8081/health

10. Lock down the admin API (recommended)

The /xiaozhi/admin/* routes (inject-text, say, set-state, play-asset, …) let anyone who can reach port 8003 make the robot speak, move, and take photos. make setup generates a shared secret, DOTTY_ADMIN_TOKEN, into the repo-root .env — once set, xiaozhi-server rejects admin calls that don't carry it as an X-Admin-Token header.

All callers read the same variable: copy the generated value into the deploy-dir .env of each sibling service —

# on the Docker host, same value in each:
/mnt/user/appdata/dotty-bridge-src/.env       # bridge dashboard
/mnt/user/appdata/dotty-behaviour-src/.env    # behaviour consumers
<dotty-pi deploy dir>/.env                    # voice tools (adminFetch)

— then restart the four containers in the same window. Leaving the variable unset everywhere keeps the legacy permissive mode (admin routes open on the LAN); setting it on only some services 401s the ones missing it. See the "Admin API auth" section of .env.example.