Обзор кода прошивки: структура каталогов, конечный автомат и правила разработки
Прошивка XiaoZhi — это C/C++ проект на ESP-IDF, который поддерживает множество чипов, плат, экранов, аудиоустройств и сетевых каналов. Одна сборка выбирает ровно одну реализацию платы. Страница составлена по AGENTS.md, main/audio/README.md и исходному коду upstream v2.5.2, чтобы вы нашли нужное место до того, как менять код.
1. Карта исходного кода
| Расположение | Назначение |
|---|---|
main/main.cc | Инициализирует NVS, создаёт Application и входит в главный цикл Run() |
main/application.* | Главный цикл событий, жизненный цикл протокола, высокоуровневое поведение (пробуждение, диалог, обновление, уведомления) |
main/device_state_machine.*, device_state.h | Допустимые рабочие состояния и переходы между ними |
main/boards/common/ | Интерфейс платы и повторно используемые вспомогательные классы для оборудования и сети |
main/boards/<производитель>/<плата>/ | Выводы, инициализация и варианты сборки каждой платы (config.h, config.json) |
main/audio/ | Кодеки, аудиозадачи, входные движки, слова пробуждения и очереди |
main/protocols/ | Не зависящий от транспорта интерфейс Protocol и две реализации: WebSocket и MQTT/UDP |
main/display/, main/led/ | Общие реализации LCD, OLED, отображения эмодзи и светодиодов |
main/mcp_server.* | Инструменты MCP на стороне устройства и их диспетчеризация |
main/ota.*, main/assets.*, main/settings.* | Проверка версии OTA и активация, обновление прошивки, раздел assets, настройки NVS |
main/Kconfig.projbuild, main/CMakeLists.txt | Тип платы и конфигурация функций; выбор исходников, плат, языков, шрифтов и ресурсов |
scripts/build.py | Официально рекомендуемая точка входа для сборки платы или варианта |
2. Ход выполнения
После инициализации NVS main.cc вызывает Application::GetInstance().Initialize() и Run(); Run() — главный цикл событий, который никогда не возвращается. Рабочим состоянием управляет DeviceStateMachine, а DeviceState принимает следующие значения:
| Состояние | Значение |
|---|---|
kDeviceStateStarting | Запуск |
kDeviceStateWifiConfiguring | Режим настройки сети, см. Первый запуск |
kDeviceStateIdle | Ожидание слова пробуждения или кнопки |
kDeviceStateConnecting | Установка соединения с сервером |
kDeviceStateListening | Запись и отправка звука на сервер |
kDeviceStateSpeaking | Воспроизведение ответа |
kDeviceStateNotifying | Показ уведомления |
kDeviceStateUpgrading | Обновление прошивки или ресурсов |
kDeviceStateActivating | Процесс активации |
kDeviceStateAudioTesting | Проверка аудио |
kDeviceStateFatalError | Критическая ошибка |
Application предоставляет точки входа ToggleChatState(), StartListening(), StopListening(), WakeWordInvoke(), Alert(), Schedule(), SendMcpMessage() и другие. Код платы (например, обработчики кнопок) запускает поведение через них.
3. Абстракция платы
Board (main/boards/common/board.h) — интерфейс, от которого зависит основной код. Главные чисто виртуальные методы: GetBoardType(), GetAudioCodec(), GetNetwork(), StartNetwork(), GetNetworkStateIcon(), SetPowerSaveLevel(), GetBoardJson(), GetDeviceStatusJson(); подсветка, светодиод, дисплей, камера и батарея — необязательные возможности.
В boards/common/ есть базовые классы для разных типов сети: WifiBoard, Ml307Board, DualNetworkBoard, EthernetBoard, RndisBoard, Nt26Board, а также вспомогательные классы для кнопок, управления питанием и таймеров сна. Каждая конкретная плата наследует один из базовых классов и экспортируется через DECLARE_BOARD(ИмяКласса); в сборке должен быть ровно один такой экспорт.
Полную процедуру добавления платы см. в руководстве по своей плате.
4. Аудио-путь
Аудио управляется AudioService: ввод-вывод кодека, кодирование и декодирование Opus и сетевые очереди отделены от обработки речи, зависящей от чипа.
- Отправка: микрофон →
AudioCodec→AudioInputTask→ один изAudioEngine(AfeAudioEngineна S3 / P4 / S31,LiteAudioEngineна остальных чипах) → моно PCM 16 кГц → кодирование вOpusCodecTask→ очередь отправки → сеть. - Приём: сеть → очередь декодирования → декодирование в
OpusCodecTask→ очередь воспроизведения →AudioOutputTask→ кодек → динамик. - Длительность кадра Opus — 60 мс; очереди используют хранилище, ёмкость которого фиксируется при компиляции.
Соответствие движков и чипов см. в разделе Поддержка чипов и спецификации хранилища.
5. Протокольный слой
Protocol абстрагирует действия: открытие и закрытие аудиоканала, отправку аудио, отправку слова пробуждения, начало и остановку прослушивания, прерывание воспроизведения и сообщения MCP. Конкретные транспорты — WebsocketProtocol и MqttProtocol. Подробности протоколов см. в разделах Протокол связи WebSocket, Гибридный протокол MQTT + UDP и Протокол MCP.
6. Правила разработки
Правила взяты из upstream AGENTS.md. Сверьтесь с ними перед изменением кода:
- Держите изменения сфокусированными и не добавляйте постороннее форматирование. Форматируйте по
.clang-formatрепозитория только изменённые файлы C/C++, см. руководство по стилю кода. - В сборке должен экспортироваться ровно один
DECLARE_BOARD(...). - Не меняйте выводы существующей платы ради другого оборудования. Вместо этого добавьте новую плату или вариант релиза с уникальным именем, потому что идентификатор типа платы влияет на совместимость OTA.
- Основной код зависит только от интерфейса
Board, а не от конкретного класса платы илиconfig.hотдельной платы. - Камера, подсветка, дисплей, светодиод и батарея считаются необязательными возможностями.
- Рабочее состояние меняйте только через
Application::SetDeviceState()и конечный автомат. - Обратные вызовы могут выполняться не в главной задаче: перед изменением состояния приложения вернитесь в главную задачу через
Application::Schedule()или биты событий. - Не блокируйте главный цикл событий и аудиозадачи; в аудио-пути избегайте неограниченных очередей и повторных выделений больших блоков.
- Общую семантику сообщений размещайте в
Protocol; при изменении контракта протокола проверяйте и WebSocket, и MQTT/UDP. - Проверяйте сетевой ввод и соблюдайте соглашения о владении
cJSON; ключи NVS — это постоянный API, их изменение требует миграции. - Функции, зависящие от цели, ограничивайте через Kconfig или правила компонентов; не считайте, что у каждого чипа есть PSRAM или ресурсы S3/P4.
- Не правьте вручную сгенерированные файлы и вывод сторонних компонентов:
build/,releases/,managed_components/,components/,sdkconfig*,main/assets/lang_config.hи т. п.
7. Сборка и проверка
Сначала загрузите среду ESP-IDF (source /path/to/esp-idf/export.sh, проверьте командой idf.py --version), затем используйте команды, рекомендованные upstream:
# Discover exact board and variant names
python3 scripts/build.py --list-boards
# Canonical variant build
python3 scripts/build.py <board-directory> --name <variant-name>
# Host-side build tests
python3 -m unittest discover -s scripts/tests -vscripts/build.py перезаписывает локальный sdkconfig и состояние сборки. После смены платы не считайте, что старый каталог сборки всё ещё соответствует прежней цели.
Upstream требует следующей проверки:
- Изменения только в коде платы: соберите затронутые варианты и проведите дымовое тестирование на реальном оборудовании.
- Изменения в ядре, общем коде плат, аудио, протоколе, дисплее, зависимостях, Kconfig или CMake: запустите тесты на хосте и соберите репрезентативные варианты по чипам и сетям.
- Изменения аудио: проверьте захват, воспроизведение, пробуждение/VAD, перебивание, повторное подключение и применимые режимы AEC.
- Изменения UI или assets: проверьте варианты без экрана, с OLED и с LVGL, а также размеры разделов.
- Успешная сборка — это не проверка на оборудовании. В отчёте укажите, что именно проверено и что ещё требует реального оборудования.
Связанные материалы
- Настройка среды ESP-IDF: IDF v6.0.1 или новее
- Руководство по своей плате: полные шаги по добавлению платы
- Точки расширения: типы сетей, слова пробуждения, MCP, уведомления и настройка ресурсов
- Устранение неполадок: проблемы сборки, прошивки и работы
Источники: AGENTS.md, main/audio/README.md, main/application.h, main/device_state.h, main/boards/common/board.h и main/protocols/protocol.h в 78/xiaozhi-esp32 v2.5.2.