diff --git a/skills/deploy/SKILL.md b/skills/deploy/SKILL.md new file mode 100644 index 0000000..5b67ba4 --- /dev/null +++ b/skills/deploy/SKILL.md @@ -0,0 +1,186 @@ +--- +name: deploy +description: Deploy MimiClaw firmware to an ESP32-S3 board. Covers prerequisites, configuration, build, flash, verification, and troubleshooting. +--- + +# Deploy MimiClaw + +End-to-end guide for deploying MimiClaw to an ESP32-S3 dev board. + +## Prerequisites + +### Hardware +- ESP32-S3 dev board with **16 MB flash + 8 MB PSRAM** (e.g. Xiaozhi AI board, ~$5-10) +- USB Type-C data cable (not charge-only) + +### Software +- **ESP-IDF v5.5+** installed and working + ```bash + # Install: https://docs.espressif.com/projects/esp-idf/en/v5.5.2/esp32s3/get-started/ + # Verify: + idf.py --version # should show >= 5.5 + ``` + +### Credentials (get these first) +- **WiFi SSID + password** — the network the ESP32 will connect to +- **Telegram Bot Token** — create via [@BotFather](https://t.me/BotFather) on Telegram +- **Anthropic API Key** — from [console.anthropic.com](https://console.anthropic.com) +- *(Optional)* Brave Search API key — from [brave.com/search/api](https://brave.com/search/api/) +- *(Optional)* HTTP proxy host:port — if in China or restricted network + +## Step 1: Clone and Set Target + +```bash +git clone https://github.com/memovai/mimiclaw.git +cd mimiclaw +idf.py set-target esp32s3 +``` + +## Step 2: Configure Secrets + +```bash +cp main/mimi_secrets.h.example main/mimi_secrets.h +``` + +Edit `main/mimi_secrets.h` — fill in ALL required fields: + +```c +#define MIMI_SECRET_WIFI_SSID "YourWiFiName" // REQUIRED +#define MIMI_SECRET_WIFI_PASS "YourWiFiPassword" // REQUIRED +#define MIMI_SECRET_TG_TOKEN "123456:ABC-DEF..." // REQUIRED +#define MIMI_SECRET_API_KEY "sk-ant-api03-..." // REQUIRED +#define MIMI_SECRET_MODEL "" // optional, defaults to claude-opus-4-5 +#define MIMI_SECRET_SEARCH_KEY "" // optional: Brave Search API key +#define MIMI_SECRET_PROXY_HOST "" // optional: e.g. "192.168.1.83" +#define MIMI_SECRET_PROXY_PORT "" // optional: e.g. "7897" +``` + +**Proxy setup (China users):** +If you need a proxy to reach Telegram/Anthropic APIs, set both `PROXY_HOST` and `PROXY_PORT`. The proxy machine must: +- Be on the same LAN as the ESP32 +- Support HTTP CONNECT method (Clash, V2Ray, etc.) +- Have "Allow LAN connections" enabled + +## Step 3: Build + +```bash +idf.py fullclean && idf.py build +``` + +**IMPORTANT:** Always `fullclean` after changing `mimi_secrets.h` — the secrets are compiled into the binary. + +Expected output: `Project build complete. To flash, run: idf.py flash` + +### Build Troubleshooting + +| Error | Fix | +|-------|-----| +| `mimi_secrets.h: No such file` | Run `cp main/mimi_secrets.h.example main/mimi_secrets.h` | +| `esp_websocket_client not found` | Run `idf.py fullclean` then `idf.py build` (managed component auto-downloads) | +| `Toolchain not found` | Re-run ESP-IDF `install.sh` and `source export.sh` | +| Build runs out of memory | Close other apps, ESP-IDF build needs ~2GB RAM | + +## Step 4: Find Serial Port + +```bash +# macOS +ls /dev/cu.usb* + +# Linux +ls /dev/ttyACM* /dev/ttyUSB* +``` + +Common ports: +- macOS USB-OTG: `/dev/cu.usbmodem1101` or `/dev/cu.usbmodem11401` +- Linux: `/dev/ttyACM0` + +**If no port shows up:** +- Try a different USB cable (must be data cable, not charge-only) +- Try a different USB port +- Check if board has a power LED lit + +## Step 5: Flash + +```bash +idf.py -p PORT flash monitor +``` + +Replace `PORT` with your actual port. Example: +```bash +idf.py -p /dev/cu.usbmodem1101 flash monitor +``` + +The monitor shows boot logs. Look for: +``` +I (xxx) mimi: MimiClaw - ESP32-S3 AI Agent +I (xxx) mimi: PSRAM free: ~8000000 bytes +I (xxx) wifi: WiFi connected: 192.168.x.x +I (xxx) telegram: Telegram bot token loaded +I (xxx) mimi: All services started! +``` + +**Exit monitor:** `Ctrl+]` + +## Step 6: Verify + +1. Open Telegram, find your bot (the one you created with BotFather) +2. Send: `Hello` +3. You should see "mimi is working..." followed by a response +4. Send: `What time is it?` — tests the get_current_time tool +5. Send: `Search for latest news about ESP32` — tests web_search (if Brave key set) + +## Post-Deploy: Runtime Configuration + +Connect via serial (`idf.py -p PORT monitor`) and use CLI commands: + +``` +mimi> config_show # see current config +mimi> wifi_set NewSSID NewPass # change WiFi +mimi> set_tg_token 123456:ABC... # change Telegram token +mimi> set_api_key sk-ant-... # change API key +mimi> set_model claude-sonnet-4-5 # change model +mimi> set_proxy 192.168.1.83 7897 # set proxy +mimi> clear_proxy # remove proxy +mimi> heap_info # check memory +mimi> restart # reboot +``` + +CLI settings are stored in NVS flash and take priority over build-time values. + +## OTA Update (over WiFi) + +After initial USB flash, future updates can be done over WiFi: + +1. Build new firmware: `idf.py build` +2. Host the `.bin` file on a local HTTP server: + ```bash + cd build && python3 -m http.server 8080 + ``` +3. Send to your bot on Telegram or use the OTA CLI command with the URL: + ``` + http://YOUR_PC_IP:8080/mimiclaw.bin + ``` + +## Flash Layout + +``` +16 MB Flash: +├── 0x009000 NVS (24 KB) — runtime config +├── 0x020000 OTA_0 (2 MB) — active firmware +├── 0x220000 OTA_1 (2 MB) — update slot +├── 0x420000 SPIFFS (12 MB) — memory, sessions, config +└── 0xFF0000 Coredump (64 KB) +``` + +## Common Issues + +| Symptom | Cause | Fix | +|---------|-------|-----| +| No WiFi connection | Wrong SSID/password | Check `mimi_secrets.h`, `idf.py fullclean && build && flash` | +| "No bot token" | Empty TG token | Set via `mimi_secrets.h` or CLI `set_tg_token` | +| Bot doesn't respond | API key invalid | Check key at console.anthropic.com, set via CLI | +| "Markdown send failed" | Normal with Markdown mode | Non-critical, falls back to plain text | +| Proxy timeout | Proxy not reachable | Ensure same LAN, proxy allows LAN connections | +| SPIFFS mount failed | First boot or corruption | Normal on first boot (auto-formats) | +| Port busy/not found | Wrong port or cable | Try different USB port/cable, check `ls /dev/cu.usb*` | +| Boot loop | Firmware crash | Flash via USB again, check serial logs for crash info | diff --git a/skills/deploy/scripts/deploy.sh b/skills/deploy/scripts/deploy.sh new file mode 100755 index 0000000..603fdc0 --- /dev/null +++ b/skills/deploy/scripts/deploy.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# MimiClaw Quick Deploy Script +# Usage: ./skills/deploy/scripts/deploy.sh [port] +# +# This script handles the full build-flash cycle: +# 1. Checks prerequisites +# 2. Ensures mimi_secrets.h exists +# 3. Builds the firmware +# 4. Auto-detects or uses specified serial port +# 5. Flashes and opens monitor + +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +info() { echo -e "${GREEN}[+]${NC} $*"; } +warn() { echo -e "${YELLOW}[!]${NC} $*"; } +error() { echo -e "${RED}[x]${NC} $*"; exit 1; } + +# Find project root (where this script lives: skills/deploy/scripts/) +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" +cd "$PROJECT_ROOT" + +info "MimiClaw Deploy — project: $PROJECT_ROOT" + +# Check ESP-IDF +if ! command -v idf.py &>/dev/null; then + error "ESP-IDF not found. Source export.sh first:\n source \$IDF_PATH/export.sh" +fi + +IDF_VER=$(idf.py --version 2>&1 | head -1) +info "ESP-IDF: $IDF_VER" + +# Check secrets +if [ ! -f main/mimi_secrets.h ]; then + warn "main/mimi_secrets.h not found — creating from example" + cp main/mimi_secrets.h.example main/mimi_secrets.h + warn "Edit main/mimi_secrets.h with your credentials, then re-run this script" + exit 1 +fi + +# Check if secrets are configured (WiFi SSID not empty) +if grep -q 'MIMI_SECRET_WIFI_SSID.*""' main/mimi_secrets.h; then + error "WiFi SSID is empty in main/mimi_secrets.h — edit it first" +fi + +# Build +info "Building firmware (fullclean)..." +idf.py fullclean >/dev/null 2>&1 || true +idf.py build 2>&1 | tail -5 + +if [ ! -f build/mimiclaw.bin ]; then + error "Build failed — check errors above" +fi + +BIN_SIZE=$(stat -f%z build/mimiclaw.bin 2>/dev/null || stat -c%s build/mimiclaw.bin 2>/dev/null) +info "Firmware built: build/mimiclaw.bin ($(( BIN_SIZE / 1024 )) KB)" + +# Detect serial port +PORT="${1:-}" +if [ -z "$PORT" ]; then + # Auto-detect + if [ "$(uname)" = "Darwin" ]; then + PORT=$(ls /dev/cu.usbmodem* 2>/dev/null | head -1) + else + PORT=$(ls /dev/ttyACM* /dev/ttyUSB* 2>/dev/null | head -1) + fi +fi + +if [ -z "$PORT" ]; then + error "No serial port found. Plug in your ESP32-S3 or specify port:\n $0 /dev/cu.usbmodem1101" +fi + +info "Serial port: $PORT" + +# Flash +info "Flashing..." +idf.py -p "$PORT" flash 2>&1 | tail -10 + +info "Flash complete!" +echo "" +info "Opening serial monitor (Ctrl+] to exit)..." +echo "" + +idf.py -p "$PORT" monitor diff --git a/skills/deploy/scripts/validate.sh b/skills/deploy/scripts/validate.sh new file mode 100755 index 0000000..2679b20 --- /dev/null +++ b/skills/deploy/scripts/validate.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +# MimiClaw Deployment Validator +# Usage: ./skills/deploy/scripts/validate.sh +# +# Checks that all prerequisites are met before building. + +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +pass() { echo -e " ${GREEN}✓${NC} $*"; } +fail() { echo -e " ${RED}✗${NC} $*"; ERRORS=$((ERRORS + 1)); } +warn() { echo -e " ${YELLOW}!${NC} $*"; } + +ERRORS=0 + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" +cd "$PROJECT_ROOT" + +echo "MimiClaw Deployment Validator" +echo "=============================" +echo "" + +# 1. ESP-IDF +echo "ESP-IDF:" +if command -v idf.py &>/dev/null; then + VER=$(idf.py --version 2>&1 | head -1) + pass "idf.py found: $VER" +else + fail "idf.py not found — source \$IDF_PATH/export.sh" +fi + +# 2. Project files +echo "Project:" +if [ -f main/mimi_config.h ]; then + pass "main/mimi_config.h exists" +else + fail "main/mimi_config.h missing — wrong directory?" +fi + +if [ -f partitions.csv ]; then + pass "partitions.csv exists" +else + fail "partitions.csv missing" +fi + +# 3. Secrets +echo "Secrets:" +if [ -f main/mimi_secrets.h ]; then + pass "main/mimi_secrets.h exists" + + # Check individual fields + if grep -q 'MIMI_SECRET_WIFI_SSID.*""' main/mimi_secrets.h; then + fail "WiFi SSID is empty" + else + pass "WiFi SSID configured" + fi + + if grep -q 'MIMI_SECRET_TG_TOKEN.*""' main/mimi_secrets.h; then + fail "Telegram token is empty" + else + pass "Telegram token configured" + fi + + if grep -q 'MIMI_SECRET_API_KEY.*""' main/mimi_secrets.h; then + fail "Anthropic API key is empty" + else + pass "Anthropic API key configured" + fi + + if grep -q 'MIMI_SECRET_SEARCH_KEY.*""' main/mimi_secrets.h; then + warn "Brave Search key not set (web_search will be unavailable)" + else + pass "Brave Search key configured" + fi +else + fail "main/mimi_secrets.h missing — run: cp main/mimi_secrets.h.example main/mimi_secrets.h" +fi + +# 4. Serial port +echo "Hardware:" +PORTS="" +if [ "$(uname)" = "Darwin" ]; then + PORTS=$(ls /dev/cu.usbmodem* 2>/dev/null || true) +else + PORTS=$(ls /dev/ttyACM* /dev/ttyUSB* 2>/dev/null || true) +fi + +if [ -n "$PORTS" ]; then + pass "Serial port found: $(echo "$PORTS" | head -1)" +else + warn "No ESP32 serial port detected (plug in the board to flash)" +fi + +# Summary +echo "" +if [ $ERRORS -eq 0 ]; then + echo -e "${GREEN}All checks passed!${NC} Ready to build and flash." + echo " Run: ./skills/deploy/scripts/deploy.sh" +else + echo -e "${RED}$ERRORS issue(s) found.${NC} Fix them before deploying." +fi