编辑 | blame | 历史 | 原始文档

Offline Command Word Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Make the board keep AFE offline wake word recognition while also enabling the offline command word 切换模式 in non-power-off states.

Architecture: Keep AfeWakeWord as the primary ESP32-S3 wake-word implementation whenever a WakeNet model exists. Attach MultiNet command detection inside AfeWakeWord, and enable a Chinese MultiNet model in configuration so the command recognizer can initialize. The board command callback remains log-only per user confirmation.

Tech Stack: ESP-IDF, ESP-SR WakeNet/MultiNet, C++ audio service, C board support code, Python static validation.

Global Constraints

  • Do not change the offline command behavior beyond log-only callback.
  • Preserve existing AFE wake-word behavior.
  • User will perform final firmware compilation.
  • Avoid broad refactors.

Task 1: Static Validation for Offline Command Wiring

Files:
- Create: tools/validate_offline_command_wiring.py

Interfaces:
- Consumes: main/audio/audio_service.cc, sdkconfig, sdkconfig.defaults.esp32s3
- Produces: a command-line validator that exits non-zero if AFE wake word is not preferred or Chinese MultiNet is not enabled.

  • [ ] Step 1: Write the failing validation script
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]


def read_text(path: str) -> str:
    return (ROOT / path).read_text(encoding="utf-8")


def require(condition: bool, message: str) -> None:
    if not condition:
        raise SystemExit(message)


def main() -> None:
    audio_service = read_text("main/audio/audio_service.cc")
    afe_pos = audio_service.find("wake_word_ = std::make_unique<AfeWakeWord>();")
    custom_pos = audio_service.find("wake_word_ = std::make_unique<CustomWakeWord>();")
    require(afe_pos != -1, "AfeWakeWord selection is missing")
    require(custom_pos != -1, "CustomWakeWord fallback is missing")
    require(afe_pos < custom_pos, "AfeWakeWord must be preferred before CustomWakeWord when WakeNet exists")

    sdkconfig = read_text("sdkconfig")
    defaults = read_text("sdkconfig.defaults.esp32s3")
    multinet_enabled = "CONFIG_SR_MN_CN_MULTINET" in sdkconfig and "CONFIG_SR_MN_CN_NONE=y" not in sdkconfig
    defaults_enabled = "CONFIG_SR_MN_CN_MULTINET" in defaults
    require(multinet_enabled, "sdkconfig must enable a Chinese MultiNet model")
    require(defaults_enabled, "sdkconfig.defaults.esp32s3 must enable a Chinese MultiNet model")


if __name__ == "__main__":
    main()
  • [ ] Step 2: Run validation to verify it fails

Run: python tools/validate_offline_command_wiring.py
Expected: FAIL with either AfeWakeWord must be preferred... or sdkconfig must enable....

  • [ ] Step 3: Implement minimal wiring/config changes

Modify main/audio/audio_service.cc so SetModelsList() chooses AfeWakeWord first when WakeNet exists, then falls back to CustomWakeWord when only MultiNet exists.

Modify sdkconfig and sdkconfig.defaults.esp32s3 to enable a Chinese MultiNet model:

CONFIG_SR_MN_CN_MULTINET7_QUANT=y
# CONFIG_SR_MN_CN_NONE is not set
  • [ ] Step 4: Run validation to verify it passes

Run: python tools/validate_offline_command_wiring.py
Expected: no output and exit code 0.

  • [ ] Step 5: Optional compile check

User will run ESP-IDF build locally and report any errors.