diff --git a/esphome/components/micro_wake_word/streaming_model.cpp b/esphome/components/micro_wake_word/streaming_model.cpp index 72984f04fb..faf5a6fe9a 100644 --- a/esphome/components/micro_wake_word/streaming_model.cpp +++ b/esphome/components/micro_wake_word/streaming_model.cpp @@ -270,7 +270,7 @@ WakeWordModel::WakeWordModel(const std::string &id, const uint8_t *model_start, this->current_stride_step_ = 0; this->internal_only_ = internal_only; - this->pref_ = global_preferences->make_preference(fnv1_hash(id)); + this->pref_ = global_preferences->make_preference(WakeWordModel::enabled_preference_key(id)); bool enabled; if (this->pref_.load(&enabled)) { // Use the enabled state loaded from flash @@ -305,7 +305,7 @@ WakeWordModel::WakeWordModel(const std::string &id, std::shared_ptr m this->current_stride_step_ = 0; this->internal_only_ = false; // Runtime models are always exposed to Home Assistant - this->pref_ = global_preferences->make_preference(fnv1_hash(id)); + this->pref_ = global_preferences->make_preference(WakeWordModel::enabled_preference_key(id)); bool enabled; if (this->pref_.load(&enabled)) { // Use the enabled state loaded from flash diff --git a/esphome/components/micro_wake_word/streaming_model.h b/esphome/components/micro_wake_word/streaming_model.h index 1cb9d6eba5..de918f0994 100644 --- a/esphome/components/micro_wake_word/streaming_model.h +++ b/esphome/components/micro_wake_word/streaming_model.h @@ -157,6 +157,13 @@ class WakeWordModel final : public StreamingModel { bool get_internal_only() { return this->internal_only_; } + /// @brief Derives the preference key holding a model's enabled state. This component owns the persistence + /// format, so anything reading or writing that state (voice_assistant restores downloaded models from it) + /// must derive the key through here rather than repeating the hash. + /// @param id (std::string) identifier for the model + /// @return The preference key for the model's enabled state + static uint32_t enabled_preference_key(const std::string &id) { return fnv1_hash(id); } + protected: // Kept for runtime-downloaded models so the model buffer stays alive for the model's lifetime. // Null for compiled-in models (their data lives in flash). diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 6052d64190..3541e0697e 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -1309,8 +1309,8 @@ void VoiceAssistant::restore_runtime_models_() { // Only re-download models the user had enabled before the reboot. Read the key directly: make_preference // allocates a backend that is never freed, and this runs for every advertised model on every request. bool enabled = false; - if (global_preferences->load_from_key(fnv1_hash(cached_ww.id), reinterpret_cast(&enabled), - sizeof(enabled)) && + if (global_preferences->load_from_key(micro_wake_word::WakeWordModel::enabled_preference_key(cached_ww.id), + reinterpret_cast(&enabled), sizeof(enabled)) && enabled) { ESP_LOGD(TAG, "Restoring runtime model %s", cached_ww.id.c_str()); this->model_download_queue_.push_back(cached_ww); @@ -1345,7 +1345,7 @@ void VoiceAssistant::erase_pending_wake_word_(const std::string &id) { void VoiceAssistant::mark_model_load_failed_(const std::string &id) { // Persist disabled so a broken model isn't retried on every boot, and drop the optimistic active entry so // HA sees the real (inactive) state. - auto pref = global_preferences->make_preference(fnv1_hash(id)); + auto pref = global_preferences->make_preference(micro_wake_word::WakeWordModel::enabled_preference_key(id)); bool enabled = false; pref.save(&enabled); this->erase_pending_wake_word_(id);