一键导入
xiaozhi-application-patterns
Core application patterns for Xiaozhi ESP32 firmware. Use when working with application.cc, state machine, callbacks, or scheduling.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Core application patterns for Xiaozhi ESP32 firmware. Use when working with application.cc, state machine, callbacks, or scheduling.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Skill for audio processing on ESP32. Covers I2S, audio codecs (ES8311, ES8388), Opus encoding/decoding, VAD, wake word detection, and audio streaming. Use when working with microphone, speaker, or audio processing.
Core skill for ESP32/ESP-IDF firmware development. Covers ESP-IDF framework, FreeRTOS, memory management, peripherals, WiFi/BLE, OTA updates, and embedded C++ patterns. Use when writing, debugging, or reviewing ESP32 firmware code.
MQTT protocol implementation for ESP32 IoT devices. Covers MQTT client, subscriptions, publish, QoS, and push notifications. Use when working with MQTT communication, server connections, or push notifications.
Skill for LVGL (Light and Versatile Graphics Library) UI development on ESP32. Covers widgets, styles, layouts, animations, and display drivers. Use when working with LCD displays, UI components, or graphics.
| name | Xiaozhi Application Patterns |
| description | Core application patterns for Xiaozhi ESP32 firmware. Use when working with application.cc, state machine, callbacks, or scheduling. |
| triggers | ["application.cc","state machine","SetDeviceState","Schedule","OnMqtt","callback"] |
enum DeviceState {
kDeviceStateUnknown,
kDeviceStateIdle, // Waiting for wake word
kDeviceStateConnecting, // Opening audio channel
kDeviceStateListening, // Recording user speech
kDeviceStateSpeaking // Playing AI response
};
void Application::SetDeviceState(DeviceState state) {
device_state_ = state;
auto display = Board::GetInstance().GetDisplay();
switch (state) {
case kDeviceStateIdle:
display->SetStatus(Lang::Strings::STANDBY);
display->SetEmotion("neutral");
display->SetChatMessage("system", ""); // Clear messages
audio_service_.EnableVoiceProcessing(false);
audio_service_.EnableWakeWordDetection(true);
break;
case kDeviceStateConnecting:
display->SetStatus(Lang::Strings::CONNECTING);
display->SetChatMessage("system", "");
break;
case kDeviceStateListening:
display->SetStatus(Lang::Strings::LISTENING);
audio_service_.EnableVoiceProcessing(true);
audio_service_.EnableWakeWordDetection(false);
break;
case kDeviceStateSpeaking:
display->SetStatus(Lang::Strings::SPEAKING);
break;
}
}
Callbacks from protocols/audio run on different threads. UI and state must be updated on main thread.
// From any callback
protocol_->SetOnIncomingJsonCallback([this](const cJSON* json) {
// DON'T access display/state directly here!
// DO schedule to main thread
std::string message = extract_message(json);
Schedule([this, message]() {
auto display = Board::GetInstance().GetDisplay();
display->SetChatMessage("assistant", message.c_str());
});
});
void Application::Schedule(std::function<void()> callback) {
std::lock_guard<std::mutex> lock(mutex_);
main_tasks_.push_back(std::move(callback));
xEventGroupSetBits(event_group_, SCHEDULE_BIT);
}
// In main loop
void Application::MainLoop() {
while (running_) {
// Wait for events
xEventGroupWaitBits(event_group_, ALL_BITS, pdTRUE, pdFALSE, portMAX_DELAY);
// Process scheduled tasks
std::vector<std::function<void()>> tasks;
{
std::lock_guard<std::mutex> lock(mutex_);
tasks.swap(main_tasks_);
}
for (auto& task : tasks) {
task();
}
}
}
void Application::OnMqttNotification(const MqttNotificationData ¬ification) {
auto display = Board::GetInstance().GetDisplay();
// Display notification
if (!notification.title.empty()) {
display->ShowNotification(notification.title.c_str());
}
if (!notification.content.empty()) {
display->SetChatMessage("assistant", notification.content.c_str());
}
// Handle TTS
if (notification.useLLM && !notification.content.empty()) {
if (protocol_->IsAudioChannelOpened()) {
// Already in conversation - send directly
protocol_->SendMcpMessage("notification_speak", notification.content);
} else {
// Need to open channel first
pending_notification_ = notification.content;
protocol_->OpenAudioChannel();
}
} else {
// Just play notification sound
audio_service_.PlaySound(Lang::Sounds::OGG_NOTIFY);
}
}
void Application::SetupProtocolCallbacks() {
// JSON messages from server
protocol_->SetOnIncomingJsonCallback([this](const cJSON* json) {
HandleIncomingJson(json);
});
// Audio from server (TTS)
protocol_->SetOnIncomingAudioCallback([this](std::vector<uint8_t>&& data) {
audio_service_.PlayOpusData(std::move(data));
});
// Connection state
protocol_->SetOnAudioChannelOpenedCallback([this]() {
SetDeviceState(kDeviceStateListening);
});
protocol_->SetOnAudioChannelClosedCallback([this]() {
SetDeviceState(kDeviceStateIdle);
});
}
void Application::HandleError(const std::string& error) {
ESP_LOGE(TAG, "Error: %s", error.c_str());
// Show error on display
auto display = Board::GetInstance().GetDisplay();
display->SetChatMessage("system", error.c_str());
// Play error sound
audio_service_.PlaySound(Lang::Sounds::OGG_ERROR);
// Reset to idle state
if (protocol_->IsAudioChannelOpened()) {
protocol_->CloseAudioChannel();
}
SetDeviceState(kDeviceStateIdle);
}
Cause: Updating UI from non-main thread
Fix: Use Schedule() wrapper
Cause: Missing state transition on error Fix: Always reset to idle on errors
Cause: Callbacks capturing this without care
Fix: Ensure objects outlive callbacks or use weak references