feat: ESP32-S3-BOX-3 room satellite — ESPHome config, OTA deploy, placeholder faces
Living room unit fully working: on-device wake word (hey_jarvis), voice pipeline via HA (Wyoming STT → OpenClaw → Wyoming TTS), static PNG display states, OTA updates. Includes deploy.sh for quick OTA with custom image support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,76 +1,177 @@
|
||||
#!/usr/bin/env bash
|
||||
# homeai-esp32/setup.sh — P6: ESPHome firmware for ESP32-S3-BOX-3
|
||||
#
|
||||
# Components:
|
||||
# - ESPHome — firmware build + flash tool
|
||||
# - base.yaml — shared device config
|
||||
# - voice.yaml — Wyoming Satellite + microWakeWord
|
||||
# - display.yaml — LVGL animated face
|
||||
# - Per-room configs — s3-box-living-room.yaml, etc.
|
||||
# Usage:
|
||||
# ./setup.sh — check environment + validate config
|
||||
# ./setup.sh flash — compile + flash via USB (first time)
|
||||
# ./setup.sh ota — compile + flash via OTA (wireless)
|
||||
# ./setup.sh logs — stream device logs
|
||||
# ./setup.sh validate — validate YAML without compiling
|
||||
#
|
||||
# Prerequisites:
|
||||
# - P1 (homeai-infra) — Home Assistant running
|
||||
# - P3 (homeai-voice) — Wyoming STT/TTS running (ports 10300/10301)
|
||||
# - Python 3.10+
|
||||
# - USB-C cable for first flash (subsequent updates via OTA)
|
||||
# - On Linux: ensure user is in the dialout group for USB access
|
||||
# - ~/homeai-esphome-env — Python 3.12 venv with ESPHome
|
||||
# - Home Assistant running on 10.0.0.199
|
||||
# - Wyoming STT/TTS running on Mac Mini (ports 10300/10301)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
source "${REPO_DIR}/scripts/common.sh"
|
||||
ESPHOME_VENV="${HOME}/homeai-esphome-env"
|
||||
ESPHOME="${ESPHOME_VENV}/bin/esphome"
|
||||
ESPHOME_DIR="${SCRIPT_DIR}/esphome"
|
||||
DEFAULT_CONFIG="${ESPHOME_DIR}/homeai-living-room.yaml"
|
||||
|
||||
log_section "P6: ESP32 Firmware (ESPHome)"
|
||||
detect_platform
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# ─── Prerequisite check ────────────────────────────────────────────────────────
|
||||
log_info "Checking prerequisites..."
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
||||
log_ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
|
||||
|
||||
if ! command_exists python3; then
|
||||
log_warn "python3 not found — required for ESPHome"
|
||||
fi
|
||||
# ─── Environment checks ──────────────────────────────────────────────────────
|
||||
|
||||
if ! command_exists esphome; then
|
||||
log_info "ESPHome not installed. To install: pip install esphome"
|
||||
fi
|
||||
check_env() {
|
||||
local ok=true
|
||||
|
||||
if [[ "$OS_TYPE" == "linux" ]]; then
|
||||
if ! groups "$USER" | grep -q dialout; then
|
||||
log_warn "User '$USER' not in 'dialout' group — USB flashing may fail."
|
||||
log_warn "Fix: sudo usermod -aG dialout $USER (then log out and back in)"
|
||||
log_info "Checking environment..."
|
||||
|
||||
# ESPHome venv
|
||||
if [[ -x "${ESPHOME}" ]]; then
|
||||
local version
|
||||
version=$("${ESPHOME}" version 2>/dev/null)
|
||||
log_ok "ESPHome: ${version}"
|
||||
else
|
||||
log_error "ESPHome not found at ${ESPHOME}"
|
||||
echo " Install: /opt/homebrew/opt/python@3.12/bin/python3.12 -m venv ${ESPHOME_VENV}"
|
||||
echo " ${ESPHOME_VENV}/bin/pip install 'esphome>=2025.5.0'"
|
||||
ok=false
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check P3 dependency
|
||||
if ! curl -sf http://localhost:8123 -o /dev/null 2>/dev/null; then
|
||||
log_warn "Home Assistant (P1) not reachable — ESP32 units won't auto-discover"
|
||||
fi
|
||||
# secrets.yaml
|
||||
if [[ -f "${ESPHOME_DIR}/secrets.yaml" ]]; then
|
||||
if grep -q "YOUR_" "${ESPHOME_DIR}/secrets.yaml" 2>/dev/null; then
|
||||
log_warn "secrets.yaml contains placeholder values — edit before flashing"
|
||||
ok=false
|
||||
else
|
||||
log_ok "secrets.yaml configured"
|
||||
fi
|
||||
else
|
||||
log_error "secrets.yaml not found at ${ESPHOME_DIR}/secrets.yaml"
|
||||
ok=false
|
||||
fi
|
||||
|
||||
# ─── TODO: Implementation ──────────────────────────────────────────────────────
|
||||
cat <<'EOF'
|
||||
# Config file
|
||||
if [[ -f "${DEFAULT_CONFIG}" ]]; then
|
||||
log_ok "Config: $(basename "${DEFAULT_CONFIG}")"
|
||||
else
|
||||
log_error "Config not found: ${DEFAULT_CONFIG}"
|
||||
ok=false
|
||||
fi
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ P6: homeai-esp32 — NOT YET IMPLEMENTED │
|
||||
│ │
|
||||
│ Implementation steps: │
|
||||
│ 1. pip install esphome │
|
||||
│ 2. Create esphome/secrets.yaml (gitignored) │
|
||||
│ 3. Create esphome/base.yaml (WiFi, API, OTA) │
|
||||
│ 4. Create esphome/voice.yaml (Wyoming Satellite, wakeword) │
|
||||
│ 5. Create esphome/display.yaml (LVGL face, 5 states) │
|
||||
│ 6. Create esphome/animations.yaml (face state scripts) │
|
||||
│ 7. Create per-room configs (s3-box-living-room.yaml, etc.) │
|
||||
│ 8. First flash via USB: esphome run esphome/<room>.yaml │
|
||||
│ 9. Subsequent OTA: esphome upload esphome/<room>.yaml │
|
||||
│ 10. Add to Home Assistant → assign Wyoming voice pipeline │
|
||||
│ │
|
||||
│ Quick flash (once esphome/ is ready): │
|
||||
│ esphome run esphome/s3-box-living-room.yaml │
|
||||
│ esphome logs esphome/s3-box-living-room.yaml │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
# Illustrations
|
||||
local illust_dir="${ESPHOME_DIR}/illustrations"
|
||||
local illust_count
|
||||
illust_count=$(find "${illust_dir}" -name "*.png" 2>/dev/null | wc -l | tr -d ' ')
|
||||
if [[ "${illust_count}" -ge 7 ]]; then
|
||||
log_ok "Illustrations: ${illust_count} PNGs in illustrations/"
|
||||
else
|
||||
log_warn "Missing illustrations (found ${illust_count}, need 7)"
|
||||
fi
|
||||
|
||||
EOF
|
||||
# Wyoming services on Mac Mini
|
||||
if curl -sf "http://localhost:10300" -o /dev/null 2>/dev/null || nc -z localhost 10300 2>/dev/null; then
|
||||
log_ok "Wyoming STT (port 10300) reachable"
|
||||
else
|
||||
log_warn "Wyoming STT (port 10300) not reachable"
|
||||
fi
|
||||
|
||||
log_info "P6 is not yet implemented. See homeai-esp32/PLAN.md for details."
|
||||
exit 0
|
||||
if curl -sf "http://localhost:10301" -o /dev/null 2>/dev/null || nc -z localhost 10301 2>/dev/null; then
|
||||
log_ok "Wyoming TTS (port 10301) reachable"
|
||||
else
|
||||
log_warn "Wyoming TTS (port 10301) not reachable"
|
||||
fi
|
||||
|
||||
# Home Assistant
|
||||
if curl -sk "https://10.0.0.199:8123" -o /dev/null 2>/dev/null; then
|
||||
log_ok "Home Assistant (10.0.0.199:8123) reachable"
|
||||
else
|
||||
log_warn "Home Assistant not reachable — ESP32 won't be able to connect"
|
||||
fi
|
||||
|
||||
if $ok; then
|
||||
log_ok "Environment ready"
|
||||
else
|
||||
log_warn "Some issues found — fix before flashing"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Commands ─────────────────────────────────────────────────────────────────
|
||||
|
||||
cmd_flash() {
|
||||
local config="${1:-${DEFAULT_CONFIG}}"
|
||||
log_info "Compiling + flashing via USB: $(basename "${config}")"
|
||||
log_info "First compile downloads ESP-IDF toolchain (~500MB), takes 5-10 min..."
|
||||
cd "${ESPHOME_DIR}"
|
||||
"${ESPHOME}" run "$(basename "${config}")"
|
||||
}
|
||||
|
||||
cmd_ota() {
|
||||
local config="${1:-${DEFAULT_CONFIG}}"
|
||||
log_info "Compiling + OTA upload: $(basename "${config}")"
|
||||
cd "${ESPHOME_DIR}"
|
||||
"${ESPHOME}" run "$(basename "${config}")"
|
||||
}
|
||||
|
||||
cmd_logs() {
|
||||
local config="${1:-${DEFAULT_CONFIG}}"
|
||||
log_info "Streaming logs for: $(basename "${config}")"
|
||||
cd "${ESPHOME_DIR}"
|
||||
"${ESPHOME}" logs "$(basename "${config}")"
|
||||
}
|
||||
|
||||
cmd_validate() {
|
||||
local config="${1:-${DEFAULT_CONFIG}}"
|
||||
log_info "Validating: $(basename "${config}")"
|
||||
cd "${ESPHOME_DIR}"
|
||||
"${ESPHOME}" config "$(basename "${config}")"
|
||||
log_ok "Config valid"
|
||||
}
|
||||
|
||||
# ─── Main ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
case "${1:-}" in
|
||||
flash)
|
||||
check_env
|
||||
echo ""
|
||||
cmd_flash "${2:-}"
|
||||
;;
|
||||
ota)
|
||||
check_env
|
||||
echo ""
|
||||
cmd_ota "${2:-}"
|
||||
;;
|
||||
logs)
|
||||
cmd_logs "${2:-}"
|
||||
;;
|
||||
validate)
|
||||
cmd_validate "${2:-}"
|
||||
;;
|
||||
*)
|
||||
check_env
|
||||
echo ""
|
||||
echo "Usage: $0 {flash|ota|logs|validate} [config.yaml]"
|
||||
echo ""
|
||||
echo " flash Compile + flash via USB (first time)"
|
||||
echo " ota Compile + flash via OTA (wireless, after first flash)"
|
||||
echo " logs Stream device logs"
|
||||
echo " validate Validate YAML config without compiling"
|
||||
echo ""
|
||||
echo "Default config: $(basename "${DEFAULT_CONFIG}")"
|
||||
;;
|
||||
esac
|
||||
|
||||
Reference in New Issue
Block a user