Files
xiaozhi-esp32/main/device_state_machine.cc
2026-04-26 21:35:04 +08:00

162 lines
5.0 KiB
C++

#include "device_state_machine.h"
#include <algorithm>
#include <esp_log.h>
static const char* TAG = "StateMachine";
// State name strings for logging
static const char* const STATE_STRINGS[] = {
"unknown",
"starting",
"wifi_configuring",
"idle",
"connecting",
"listening",
"speaking",
"upgrading",
"activating",
"audio_testing",
"fatal_error",
"invalid_state"
};
DeviceStateMachine::DeviceStateMachine() {
}
const char* DeviceStateMachine::GetStateName(DeviceState state) {
if (state >= 0 && state <= kDeviceStateFatalError) {
return STATE_STRINGS[state];
}
return STATE_STRINGS[kDeviceStateFatalError + 1];
}
bool DeviceStateMachine::IsValidTransition(DeviceState from, DeviceState to) const {
// Allow transition to the same state (no-op)
if (from == to) {
return true;
}
// Define valid state transitions based on the state diagram
switch (from) {
case kDeviceStateUnknown:
// Can only go to starting
return to == kDeviceStateStarting;
case kDeviceStateStarting:
// Can go to wifi configuring or activating
return to == kDeviceStateWifiConfiguring ||
to == kDeviceStateActivating;
case kDeviceStateWifiConfiguring:
// Can go to activating (after wifi connected) or audio testing
return to == kDeviceStateActivating ||
to == kDeviceStateAudioTesting;
case kDeviceStateAudioTesting:
// Can go back to wifi configuring
return to == kDeviceStateWifiConfiguring;
case kDeviceStateActivating:
// Can go to upgrading, idle, or back to wifi configuring (on error)
return to == kDeviceStateUpgrading ||
to == kDeviceStateIdle ||
to == kDeviceStateWifiConfiguring;
case kDeviceStateUpgrading:
// Can go to idle (upgrade failed) or activating
return to == kDeviceStateIdle ||
to == kDeviceStateActivating;
case kDeviceStateIdle:
// Can go to connecting, listening (manual mode), speaking, activating, upgrading, or wifi configuring
return to == kDeviceStateConnecting ||
to == kDeviceStateListening ||
to == kDeviceStateSpeaking ||
to == kDeviceStateActivating ||
to == kDeviceStateUpgrading ||
to == kDeviceStateWifiConfiguring;
case kDeviceStateConnecting:
// Can go to idle (failed) or listening (success)
return to == kDeviceStateIdle ||
to == kDeviceStateListening;
case kDeviceStateListening:
// Can go to speaking or idle
return to == kDeviceStateSpeaking ||
to == kDeviceStateIdle;
case kDeviceStateSpeaking:
// Can go to listening or idle
return to == kDeviceStateListening ||
to == kDeviceStateIdle;
case kDeviceStateFatalError:
// Cannot transition out of fatal error
return false;
default:
return false;
}
}
bool DeviceStateMachine::CanTransitionTo(DeviceState target) const {
return IsValidTransition(current_state_.load(), target);
}
bool DeviceStateMachine::TransitionTo(DeviceState new_state) {
DeviceState old_state = current_state_.load();
// No-op if already in the target state
if (old_state == new_state) {
return true;
}
// Validate transition
if (!IsValidTransition(old_state, new_state)) {
ESP_LOGW(TAG, "Invalid state transition: %s -> %s",
GetStateName(old_state), GetStateName(new_state));
return false;
}
// Perform transition
current_state_.store(new_state);
ESP_LOGI(TAG, "State: %s -> %s",
GetStateName(old_state), GetStateName(new_state));
// Notify callback
NotifyStateChange(old_state, new_state);
return true;
}
int DeviceStateMachine::AddStateChangeListener(StateCallback callback) {
std::lock_guard<std::mutex> lock(mutex_);
int id = next_listener_id_++;
listeners_.emplace_back(id, std::move(callback));
return id;
}
void DeviceStateMachine::RemoveStateChangeListener(int listener_id) {
std::lock_guard<std::mutex> lock(mutex_);
listeners_.erase(
std::remove_if(listeners_.begin(), listeners_.end(),
[listener_id](const auto& p) { return p.first == listener_id; }),
listeners_.end());
}
void DeviceStateMachine::NotifyStateChange(DeviceState old_state, DeviceState new_state) {
std::vector<StateCallback> callbacks_copy;
{
std::lock_guard<std::mutex> lock(mutex_);
callbacks_copy.reserve(listeners_.size());
for (const auto& [id, cb] : listeners_) {
callbacks_copy.push_back(cb);
}
}
for (const auto& cb : callbacks_copy) {
cb(old_state, new_state);
}
}