Merge pull request #6 from memovai/deploy-skill
This commit is contained in:
186
skills/deploy/SKILL.md
Normal file
186
skills/deploy/SKILL.md
Normal file
@@ -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 |
|
||||||
89
skills/deploy/scripts/deploy.sh
Executable file
89
skills/deploy/scripts/deploy.sh
Executable file
@@ -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
|
||||||
106
skills/deploy/scripts/validate.sh
Executable file
106
skills/deploy/scripts/validate.sh
Executable file
@@ -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
|
||||||
Reference in New Issue
Block a user