mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Merge branch 'prefs_device_id' into integration
This commit is contained in:
		| @@ -327,7 +327,7 @@ void Climate::add_on_control_callback(std::function<void(ClimateCall &)> &&callb | |||||||
| static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL; | static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL; | ||||||
|  |  | ||||||
| optional<ClimateDeviceRestoreState> Climate::restore_state_() { | optional<ClimateDeviceRestoreState> Climate::restore_state_() { | ||||||
|   this->rtc_ = global_preferences->make_preference<ClimateDeviceRestoreState>(this->get_object_id_hash() ^ |   this->rtc_ = global_preferences->make_preference<ClimateDeviceRestoreState>(this->get_preference_hash() ^ | ||||||
|                                                                               RESTORE_STATE_VERSION); |                                                                               RESTORE_STATE_VERSION); | ||||||
|   ClimateDeviceRestoreState recovered{}; |   ClimateDeviceRestoreState recovered{}; | ||||||
|   if (!this->rtc_.load(&recovered)) |   if (!this->rtc_.load(&recovered)) | ||||||
|   | |||||||
| @@ -194,7 +194,7 @@ void Cover::publish_state(bool save) { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| optional<CoverRestoreState> Cover::restore_state_() { | optional<CoverRestoreState> Cover::restore_state_() { | ||||||
|   this->rtc_ = global_preferences->make_preference<CoverRestoreState>(this->get_object_id_hash()); |   this->rtc_ = global_preferences->make_preference<CoverRestoreState>(this->get_preference_hash()); | ||||||
|   CoverRestoreState recovered{}; |   CoverRestoreState recovered{}; | ||||||
|   if (!this->rtc_.load(&recovered)) |   if (!this->rtc_.load(&recovered)) | ||||||
|     return {}; |     return {}; | ||||||
|   | |||||||
| @@ -41,7 +41,7 @@ void DutyTimeSensor::setup() { | |||||||
|   uint32_t seconds = 0; |   uint32_t seconds = 0; | ||||||
|  |  | ||||||
|   if (this->restore_) { |   if (this->restore_) { | ||||||
|     this->pref_ = global_preferences->make_preference<uint32_t>(this->get_object_id_hash()); |     this->pref_ = global_preferences->make_preference<uint32_t>(this->get_preference_hash()); | ||||||
|     this->pref_.load(&seconds); |     this->pref_.load(&seconds); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -148,7 +148,8 @@ void Fan::publish_state() { | |||||||
| constexpr uint32_t RESTORE_STATE_VERSION = 0x71700ABA; | constexpr uint32_t RESTORE_STATE_VERSION = 0x71700ABA; | ||||||
| optional<FanRestoreState> Fan::restore_state_() { | optional<FanRestoreState> Fan::restore_state_() { | ||||||
|   FanRestoreState recovered{}; |   FanRestoreState recovered{}; | ||||||
|   this->rtc_ = global_preferences->make_preference<FanRestoreState>(this->get_object_id_hash() ^ RESTORE_STATE_VERSION); |   this->rtc_ = | ||||||
|  |       global_preferences->make_preference<FanRestoreState>(this->get_preference_hash() ^ RESTORE_STATE_VERSION); | ||||||
|   bool restored = this->rtc_.load(&recovered); |   bool restored = this->rtc_.load(&recovered); | ||||||
|  |  | ||||||
|   switch (this->restore_mode_) { |   switch (this->restore_mode_) { | ||||||
|   | |||||||
| @@ -351,7 +351,7 @@ ClimateTraits HaierClimateBase::traits() { return traits_; } | |||||||
| void HaierClimateBase::initialization() { | void HaierClimateBase::initialization() { | ||||||
|   constexpr uint32_t restore_settings_version = 0xA77D21EF; |   constexpr uint32_t restore_settings_version = 0xA77D21EF; | ||||||
|   this->base_rtc_ = |   this->base_rtc_ = | ||||||
|       global_preferences->make_preference<HaierBaseSettings>(this->get_object_id_hash() ^ restore_settings_version); |       global_preferences->make_preference<HaierBaseSettings>(this->get_preference_hash() ^ restore_settings_version); | ||||||
|   HaierBaseSettings recovered; |   HaierBaseSettings recovered; | ||||||
|   if (!this->base_rtc_.load(&recovered)) { |   if (!this->base_rtc_.load(&recovered)) { | ||||||
|     recovered = {false, true}; |     recovered = {false, true}; | ||||||
|   | |||||||
| @@ -516,7 +516,7 @@ void HonClimate::initialization() { | |||||||
|   HaierClimateBase::initialization(); |   HaierClimateBase::initialization(); | ||||||
|   constexpr uint32_t restore_settings_version = 0x57EB59DDUL; |   constexpr uint32_t restore_settings_version = 0x57EB59DDUL; | ||||||
|   this->hon_rtc_ = |   this->hon_rtc_ = | ||||||
|       global_preferences->make_preference<HonSettings>(this->get_object_id_hash() ^ restore_settings_version); |       global_preferences->make_preference<HonSettings>(this->get_preference_hash() ^ restore_settings_version); | ||||||
|   HonSettings recovered; |   HonSettings recovered; | ||||||
|   if (this->hon_rtc_.load(&recovered)) { |   if (this->hon_rtc_.load(&recovered)) { | ||||||
|     this->settings_ = recovered; |     this->settings_ = recovered; | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ static const char *const TAG = "integration"; | |||||||
|  |  | ||||||
| void IntegrationSensor::setup() { | void IntegrationSensor::setup() { | ||||||
|   if (this->restore_) { |   if (this->restore_) { | ||||||
|     this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash()); |     this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash()); | ||||||
|     float preference_value = 0; |     float preference_value = 0; | ||||||
|     this->pref_.load(&preference_value); |     this->pref_.load(&preference_value); | ||||||
|     this->result_ = preference_value; |     this->result_ = preference_value; | ||||||
|   | |||||||
| @@ -184,7 +184,7 @@ static inline bool validate_header_footer(const uint8_t *header_footer, const ui | |||||||
| void LD2450Component::setup() { | void LD2450Component::setup() { | ||||||
| #ifdef USE_NUMBER | #ifdef USE_NUMBER | ||||||
|   if (this->presence_timeout_number_ != nullptr) { |   if (this->presence_timeout_number_ != nullptr) { | ||||||
|     this->pref_ = global_preferences->make_preference<float>(this->presence_timeout_number_->get_object_id_hash()); |     this->pref_ = global_preferences->make_preference<float>(this->presence_timeout_number_->get_preference_hash()); | ||||||
|     this->set_presence_timeout(); |     this->set_presence_timeout(); | ||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -41,7 +41,7 @@ void LightState::setup() { | |||||||
|     case LIGHT_RESTORE_DEFAULT_ON: |     case LIGHT_RESTORE_DEFAULT_ON: | ||||||
|     case LIGHT_RESTORE_INVERTED_DEFAULT_OFF: |     case LIGHT_RESTORE_INVERTED_DEFAULT_OFF: | ||||||
|     case LIGHT_RESTORE_INVERTED_DEFAULT_ON: |     case LIGHT_RESTORE_INVERTED_DEFAULT_ON: | ||||||
|       this->rtc_ = global_preferences->make_preference<LightStateRTCState>(this->get_object_id_hash()); |       this->rtc_ = global_preferences->make_preference<LightStateRTCState>(this->get_preference_hash()); | ||||||
|       // Attempt to load from preferences, else fall back to default values |       // Attempt to load from preferences, else fall back to default values | ||||||
|       if (!this->rtc_.load(&recovered)) { |       if (!this->rtc_.load(&recovered)) { | ||||||
|         recovered.state = (this->restore_mode_ == LIGHT_RESTORE_DEFAULT_ON || |         recovered.state = (this->restore_mode_ == LIGHT_RESTORE_DEFAULT_ON || | ||||||
| @@ -54,7 +54,7 @@ void LightState::setup() { | |||||||
|       break; |       break; | ||||||
|     case LIGHT_RESTORE_AND_OFF: |     case LIGHT_RESTORE_AND_OFF: | ||||||
|     case LIGHT_RESTORE_AND_ON: |     case LIGHT_RESTORE_AND_ON: | ||||||
|       this->rtc_ = global_preferences->make_preference<LightStateRTCState>(this->get_object_id_hash()); |       this->rtc_ = global_preferences->make_preference<LightStateRTCState>(this->get_preference_hash()); | ||||||
|       this->rtc_.load(&recovered); |       this->rtc_.load(&recovered); | ||||||
|       recovered.state = (this->restore_mode_ == LIGHT_RESTORE_AND_ON); |       recovered.state = (this->restore_mode_ == LIGHT_RESTORE_AND_ON); | ||||||
|       break; |       break; | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ class LVGLNumber : public number::Number, public Component { | |||||||
|   void setup() override { |   void setup() override { | ||||||
|     float value = this->value_lambda_(); |     float value = this->value_lambda_(); | ||||||
|     if (this->restore_) { |     if (this->restore_) { | ||||||
|       this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash()); |       this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash()); | ||||||
|       if (this->pref_.load(&value)) { |       if (this->pref_.load(&value)) { | ||||||
|         this->control_lambda_(value); |         this->control_lambda_(value); | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ class LVGLSelect : public select::Select, public Component { | |||||||
|     this->set_options_(); |     this->set_options_(); | ||||||
|     if (this->restore_) { |     if (this->restore_) { | ||||||
|       size_t index; |       size_t index; | ||||||
|       this->pref_ = global_preferences->make_preference<size_t>(this->get_object_id_hash()); |       this->pref_ = global_preferences->make_preference<size_t>(this->get_preference_hash()); | ||||||
|       if (this->pref_.load(&index)) |       if (this->pref_.load(&index)) | ||||||
|         this->widget_->set_selected_index(index, LV_ANIM_OFF); |         this->widget_->set_selected_index(index, LV_ANIM_OFF); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ void ValueRangeTrigger::setup() { | |||||||
|   float local_min = this->min_.value(0.0); |   float local_min = this->min_.value(0.0); | ||||||
|   float local_max = this->max_.value(0.0); |   float local_max = this->max_.value(0.0); | ||||||
|   convert hash = {.from = (local_max - local_min)}; |   convert hash = {.from = (local_max - local_min)}; | ||||||
|   uint32_t myhash = hash.to ^ this->parent_->get_object_id_hash(); |   uint32_t myhash = hash.to ^ this->parent_->get_preference_hash(); | ||||||
|   this->rtc_ = global_preferences->make_preference<bool>(myhash); |   this->rtc_ = global_preferences->make_preference<bool>(myhash); | ||||||
|   bool initial_state; |   bool initial_state; | ||||||
|   if (this->rtc_.load(&initial_state)) { |   if (this->rtc_.load(&initial_state)) { | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ void OpenthermNumber::setup() { | |||||||
|   if (!this->restore_value_) { |   if (!this->restore_value_) { | ||||||
|     value = this->initial_value_; |     value = this->initial_value_; | ||||||
|   } else { |   } else { | ||||||
|     this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash()); |     this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash()); | ||||||
|     if (!this->pref_.load(&value)) { |     if (!this->pref_.load(&value)) { | ||||||
|       if (!std::isnan(this->initial_value_)) { |       if (!std::isnan(this->initial_value_)) { | ||||||
|         value = this->initial_value_; |         value = this->initial_value_; | ||||||
|   | |||||||
| @@ -132,7 +132,7 @@ void RotaryEncoderSensor::setup() { | |||||||
|   int32_t initial_value = 0; |   int32_t initial_value = 0; | ||||||
|   switch (this->restore_mode_) { |   switch (this->restore_mode_) { | ||||||
|     case ROTARY_ENCODER_RESTORE_DEFAULT_ZERO: |     case ROTARY_ENCODER_RESTORE_DEFAULT_ZERO: | ||||||
|       this->rtc_ = global_preferences->make_preference<int32_t>(this->get_object_id_hash()); |       this->rtc_ = global_preferences->make_preference<int32_t>(this->get_preference_hash()); | ||||||
|       if (!this->rtc_.load(&initial_value)) { |       if (!this->rtc_.load(&initial_value)) { | ||||||
|         initial_value = 0; |         initial_value = 0; | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -40,7 +40,7 @@ class ValueRangeTrigger : public Trigger<float>, public Component { | |||||||
|   template<typename V> void set_max(V max) { this->max_ = max; } |   template<typename V> void set_max(V max) { this->max_ = max; } | ||||||
|  |  | ||||||
|   void setup() override { |   void setup() override { | ||||||
|     this->rtc_ = global_preferences->make_preference<bool>(this->parent_->get_object_id_hash()); |     this->rtc_ = global_preferences->make_preference<bool>(this->parent_->get_preference_hash()); | ||||||
|     bool initial_state; |     bool initial_state; | ||||||
|     if (this->rtc_.load(&initial_state)) { |     if (this->rtc_.load(&initial_state)) { | ||||||
|       this->previous_in_range_ = initial_state; |       this->previous_in_range_ = initial_state; | ||||||
|   | |||||||
| @@ -55,7 +55,7 @@ void SpeakerMediaPlayer::setup() { | |||||||
|  |  | ||||||
|   this->media_control_command_queue_ = xQueueCreate(MEDIA_CONTROLS_QUEUE_LENGTH, sizeof(MediaCallCommand)); |   this->media_control_command_queue_ = xQueueCreate(MEDIA_CONTROLS_QUEUE_LENGTH, sizeof(MediaCallCommand)); | ||||||
|  |  | ||||||
|   this->pref_ = global_preferences->make_preference<VolumeRestoreState>(this->get_object_id_hash()); |   this->pref_ = global_preferences->make_preference<VolumeRestoreState>(this->get_preference_hash()); | ||||||
|  |  | ||||||
|   VolumeRestoreState volume_restore_state; |   VolumeRestoreState volume_restore_state; | ||||||
|   if (this->pref_.load(&volume_restore_state)) { |   if (this->pref_.load(&volume_restore_state)) { | ||||||
|   | |||||||
| @@ -81,7 +81,7 @@ void SprinklerControllerNumber::setup() { | |||||||
|   if (!this->restore_value_) { |   if (!this->restore_value_) { | ||||||
|     value = this->initial_value_; |     value = this->initial_value_; | ||||||
|   } else { |   } else { | ||||||
|     this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash()); |     this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash()); | ||||||
|     if (!this->pref_.load(&value)) { |     if (!this->pref_.load(&value)) { | ||||||
|       if (!std::isnan(this->initial_value_)) { |       if (!std::isnan(this->initial_value_)) { | ||||||
|         value = this->initial_value_; |         value = this->initial_value_; | ||||||
|   | |||||||
| @@ -32,7 +32,7 @@ optional<bool> Switch::get_initial_state() { | |||||||
|   if (!(restore_mode & RESTORE_MODE_PERSISTENT_MASK)) |   if (!(restore_mode & RESTORE_MODE_PERSISTENT_MASK)) | ||||||
|     return {}; |     return {}; | ||||||
|  |  | ||||||
|   this->rtc_ = global_preferences->make_preference<bool>(this->get_object_id_hash()); |   this->rtc_ = global_preferences->make_preference<bool>(this->get_preference_hash()); | ||||||
|   bool initial_state; |   bool initial_state; | ||||||
|   if (!this->rtc_.load(&initial_state)) |   if (!this->rtc_.load(&initial_state)) | ||||||
|     return {}; |     return {}; | ||||||
|   | |||||||
| @@ -86,7 +86,7 @@ void TemplateAlarmControlPanel::setup() { | |||||||
|       break; |       break; | ||||||
|     case ALARM_CONTROL_PANEL_RESTORE_DEFAULT_DISARMED: { |     case ALARM_CONTROL_PANEL_RESTORE_DEFAULT_DISARMED: { | ||||||
|       uint8_t value; |       uint8_t value; | ||||||
|       this->pref_ = global_preferences->make_preference<uint8_t>(this->get_object_id_hash()); |       this->pref_ = global_preferences->make_preference<uint8_t>(this->get_preference_hash()); | ||||||
|       if (this->pref_.load(&value)) { |       if (this->pref_.load(&value)) { | ||||||
|         this->current_state_ = static_cast<alarm_control_panel::AlarmControlPanelState>(value); |         this->current_state_ = static_cast<alarm_control_panel::AlarmControlPanelState>(value); | ||||||
|       } else { |       } else { | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ void TemplateDate::setup() { | |||||||
|   } else { |   } else { | ||||||
|     datetime::DateEntityRestoreState temp; |     datetime::DateEntityRestoreState temp; | ||||||
|     this->pref_ = |     this->pref_ = | ||||||
|         global_preferences->make_preference<datetime::DateEntityRestoreState>(194434030U ^ this->get_object_id_hash()); |         global_preferences->make_preference<datetime::DateEntityRestoreState>(194434030U ^ this->get_preference_hash()); | ||||||
|     if (this->pref_.load(&temp)) { |     if (this->pref_.load(&temp)) { | ||||||
|       temp.apply(this); |       temp.apply(this); | ||||||
|       return; |       return; | ||||||
|   | |||||||
| @@ -19,8 +19,8 @@ void TemplateDateTime::setup() { | |||||||
|     state = this->initial_value_; |     state = this->initial_value_; | ||||||
|   } else { |   } else { | ||||||
|     datetime::DateTimeEntityRestoreState temp; |     datetime::DateTimeEntityRestoreState temp; | ||||||
|     this->pref_ = global_preferences->make_preference<datetime::DateTimeEntityRestoreState>(194434090U ^ |     this->pref_ = global_preferences->make_preference<datetime::DateTimeEntityRestoreState>( | ||||||
|                                                                                             this->get_object_id_hash()); |         194434090U ^ this->get_preference_hash()); | ||||||
|     if (this->pref_.load(&temp)) { |     if (this->pref_.load(&temp)) { | ||||||
|       temp.apply(this); |       temp.apply(this); | ||||||
|       return; |       return; | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ void TemplateTime::setup() { | |||||||
|   } else { |   } else { | ||||||
|     datetime::TimeEntityRestoreState temp; |     datetime::TimeEntityRestoreState temp; | ||||||
|     this->pref_ = |     this->pref_ = | ||||||
|         global_preferences->make_preference<datetime::TimeEntityRestoreState>(194434060U ^ this->get_object_id_hash()); |         global_preferences->make_preference<datetime::TimeEntityRestoreState>(194434060U ^ this->get_preference_hash()); | ||||||
|     if (this->pref_.load(&temp)) { |     if (this->pref_.load(&temp)) { | ||||||
|       temp.apply(this); |       temp.apply(this); | ||||||
|       return; |       return; | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ void TemplateNumber::setup() { | |||||||
|   if (!this->restore_value_) { |   if (!this->restore_value_) { | ||||||
|     value = this->initial_value_; |     value = this->initial_value_; | ||||||
|   } else { |   } else { | ||||||
|     this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash()); |     this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash()); | ||||||
|     if (!this->pref_.load(&value)) { |     if (!this->pref_.load(&value)) { | ||||||
|       if (!std::isnan(this->initial_value_)) { |       if (!std::isnan(this->initial_value_)) { | ||||||
|         value = this->initial_value_; |         value = this->initial_value_; | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ void TemplateSelect::setup() { | |||||||
|     ESP_LOGD(TAG, "State from initial: %s", value.c_str()); |     ESP_LOGD(TAG, "State from initial: %s", value.c_str()); | ||||||
|   } else { |   } else { | ||||||
|     size_t index; |     size_t index; | ||||||
|     this->pref_ = global_preferences->make_preference<size_t>(this->get_object_id_hash()); |     this->pref_ = global_preferences->make_preference<size_t>(this->get_preference_hash()); | ||||||
|     if (!this->pref_.load(&index)) { |     if (!this->pref_.load(&index)) { | ||||||
|       value = this->initial_option_; |       value = this->initial_option_; | ||||||
|       ESP_LOGD(TAG, "State from initial (could not load stored index): %s", value.c_str()); |       ESP_LOGD(TAG, "State from initial (could not load stored index): %s", value.c_str()); | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ void TemplateText::setup() { | |||||||
|   if (!this->pref_) { |   if (!this->pref_) { | ||||||
|     ESP_LOGD(TAG, "State from initial: %s", value.c_str()); |     ESP_LOGD(TAG, "State from initial: %s", value.c_str()); | ||||||
|   } else { |   } else { | ||||||
|     uint32_t key = this->get_object_id_hash(); |     uint32_t key = this->get_preference_hash(); | ||||||
|     key += this->traits.get_min_length() << 2; |     key += this->traits.get_min_length() << 2; | ||||||
|     key += this->traits.get_max_length() << 4; |     key += this->traits.get_max_length() << 4; | ||||||
|     key += fnv1_hash(this->traits.get_pattern()) << 6; |     key += fnv1_hash(this->traits.get_pattern()) << 6; | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ void TotalDailyEnergy::setup() { | |||||||
|   float initial_value = 0; |   float initial_value = 0; | ||||||
|  |  | ||||||
|   if (this->restore_) { |   if (this->restore_) { | ||||||
|     this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash()); |     this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash()); | ||||||
|     this->pref_.load(&initial_value); |     this->pref_.load(&initial_value); | ||||||
|   } |   } | ||||||
|   this->publish_state_and_save(initial_value); |   this->publish_state_and_save(initial_value); | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ static const char *const TAG = "tuya.number"; | |||||||
|  |  | ||||||
| void TuyaNumber::setup() { | void TuyaNumber::setup() { | ||||||
|   if (this->restore_value_) { |   if (this->restore_value_) { | ||||||
|     this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash()); |     this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash()); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   this->parent_->register_listener(this->number_id_, [this](const TuyaDatapoint &datapoint) { |   this->parent_->register_listener(this->number_id_, [this](const TuyaDatapoint &datapoint) { | ||||||
|   | |||||||
| @@ -155,7 +155,7 @@ void Valve::publish_state(bool save) { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| optional<ValveRestoreState> Valve::restore_state_() { | optional<ValveRestoreState> Valve::restore_state_() { | ||||||
|   this->rtc_ = global_preferences->make_preference<ValveRestoreState>(this->get_object_id_hash()); |   this->rtc_ = global_preferences->make_preference<ValveRestoreState>(this->get_preference_hash()); | ||||||
|   ValveRestoreState recovered{}; |   ValveRestoreState recovered{}; | ||||||
|   if (!this->rtc_.load(&recovered)) |   if (!this->rtc_.load(&recovered)) | ||||||
|     return {}; |     return {}; | ||||||
|   | |||||||
| @@ -85,6 +85,19 @@ class EntityBase { | |||||||
|   // Set has_state - for components that need to manually set this |   // Set has_state - for components that need to manually set this | ||||||
|   void set_has_state(bool state) { this->flags_.has_state = state; } |   void set_has_state(bool state) { this->flags_.has_state = state; } | ||||||
|  |  | ||||||
|  |   // Get a unique hash for preferences that includes device_id | ||||||
|  |   uint32_t get_preference_hash() { | ||||||
|  | #ifdef USE_DEVICES | ||||||
|  |     // Combine object_id_hash with device_id to ensure uniqueness across devices | ||||||
|  |     // Note: device_id is 0 for the main device, so XORing with 0 preserves the original hash | ||||||
|  |     // This ensures backward compatibility for existing single-device configurations | ||||||
|  |     return this->get_object_id_hash() ^ this->get_device_id(); | ||||||
|  | #else | ||||||
|  |     // Without devices, just use object_id_hash as before | ||||||
|  |     return this->get_object_id_hash(); | ||||||
|  | #endif | ||||||
|  |   } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   friend class api::APIConnection; |   friend class api::APIConnection; | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										165
									
								
								tests/integration/fixtures/multi_device_preferences.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								tests/integration/fixtures/multi_device_preferences.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,165 @@ | |||||||
|  | esphome: | ||||||
|  |   name: multi-device-preferences-test | ||||||
|  |   # Define multiple devices for testing preference storage | ||||||
|  |   devices: | ||||||
|  |     - id: device_a | ||||||
|  |       name: Device A | ||||||
|  |     - id: device_b | ||||||
|  |       name: Device B | ||||||
|  |  | ||||||
|  | host: | ||||||
|  | api:  # Port will be automatically injected | ||||||
|  | logger: | ||||||
|  |   level: DEBUG | ||||||
|  |  | ||||||
|  | # Test entities with restore modes to verify preference storage | ||||||
|  |  | ||||||
|  | # Switches with same name on different devices - test restore mode | ||||||
|  | switch: | ||||||
|  |   - platform: template | ||||||
|  |     name: Light | ||||||
|  |     id: light_device_a | ||||||
|  |     device_id: device_a | ||||||
|  |     restore_mode: RESTORE_DEFAULT_OFF | ||||||
|  |     turn_on_action: | ||||||
|  |       - lambda: |- | ||||||
|  |           ESP_LOGI("test", "Device A Light turned ON"); | ||||||
|  |     turn_off_action: | ||||||
|  |       - lambda: |- | ||||||
|  |           ESP_LOGI("test", "Device A Light turned OFF"); | ||||||
|  |  | ||||||
|  |   - platform: template | ||||||
|  |     name: Light | ||||||
|  |     id: light_device_b | ||||||
|  |     device_id: device_b | ||||||
|  |     restore_mode: RESTORE_DEFAULT_ON  # Different default to test uniqueness | ||||||
|  |     turn_on_action: | ||||||
|  |       - lambda: |- | ||||||
|  |           ESP_LOGI("test", "Device B Light turned ON"); | ||||||
|  |     turn_off_action: | ||||||
|  |       - lambda: |- | ||||||
|  |           ESP_LOGI("test", "Device B Light turned OFF"); | ||||||
|  |  | ||||||
|  |   - platform: template | ||||||
|  |     name: Light | ||||||
|  |     id: light_main | ||||||
|  |     restore_mode: RESTORE_DEFAULT_OFF | ||||||
|  |     turn_on_action: | ||||||
|  |       - lambda: |- | ||||||
|  |           ESP_LOGI("test", "Main Light turned ON"); | ||||||
|  |     turn_off_action: | ||||||
|  |       - lambda: |- | ||||||
|  |           ESP_LOGI("test", "Main Light turned OFF"); | ||||||
|  |  | ||||||
|  | # Numbers with restore to test preference storage | ||||||
|  | number: | ||||||
|  |   - platform: template | ||||||
|  |     name: Setpoint | ||||||
|  |     id: setpoint_device_a | ||||||
|  |     device_id: device_a | ||||||
|  |     min_value: 10.0 | ||||||
|  |     max_value: 30.0 | ||||||
|  |     step: 0.5 | ||||||
|  |     restore_value: true | ||||||
|  |     initial_value: 20.0 | ||||||
|  |     set_action: | ||||||
|  |       - lambda: |- | ||||||
|  |           ESP_LOGI("test", "Device A Setpoint set to %.1f", x); | ||||||
|  |           id(setpoint_device_a).state = x; | ||||||
|  |  | ||||||
|  |   - platform: template | ||||||
|  |     name: Setpoint | ||||||
|  |     id: setpoint_device_b | ||||||
|  |     device_id: device_b | ||||||
|  |     min_value: 10.0 | ||||||
|  |     max_value: 30.0 | ||||||
|  |     step: 0.5 | ||||||
|  |     restore_value: true | ||||||
|  |     initial_value: 25.0  # Different initial to test uniqueness | ||||||
|  |     set_action: | ||||||
|  |       - lambda: |- | ||||||
|  |           ESP_LOGI("test", "Device B Setpoint set to %.1f", x); | ||||||
|  |           id(setpoint_device_b).state = x; | ||||||
|  |  | ||||||
|  |   - platform: template | ||||||
|  |     name: Setpoint | ||||||
|  |     id: setpoint_main | ||||||
|  |     min_value: 10.0 | ||||||
|  |     max_value: 30.0 | ||||||
|  |     step: 0.5 | ||||||
|  |     restore_value: true | ||||||
|  |     initial_value: 22.0 | ||||||
|  |     set_action: | ||||||
|  |       - lambda: |- | ||||||
|  |           ESP_LOGI("test", "Main Setpoint set to %.1f", x); | ||||||
|  |           id(setpoint_main).state = x; | ||||||
|  |  | ||||||
|  | # Selects with restore to test preference storage | ||||||
|  | select: | ||||||
|  |   - platform: template | ||||||
|  |     name: Mode | ||||||
|  |     id: mode_device_a | ||||||
|  |     device_id: device_a | ||||||
|  |     options: | ||||||
|  |       - "Auto" | ||||||
|  |       - "Manual" | ||||||
|  |       - "Off" | ||||||
|  |     restore_value: true | ||||||
|  |     initial_option: "Auto" | ||||||
|  |     set_action: | ||||||
|  |       - lambda: |- | ||||||
|  |           ESP_LOGI("test", "Device A Mode set to %s", x.c_str()); | ||||||
|  |           id(mode_device_a).state = x; | ||||||
|  |  | ||||||
|  |   - platform: template | ||||||
|  |     name: Mode | ||||||
|  |     id: mode_device_b | ||||||
|  |     device_id: device_b | ||||||
|  |     options: | ||||||
|  |       - "Auto" | ||||||
|  |       - "Manual" | ||||||
|  |       - "Off" | ||||||
|  |     restore_value: true | ||||||
|  |     initial_option: "Manual"  # Different initial to test uniqueness | ||||||
|  |     set_action: | ||||||
|  |       - lambda: |- | ||||||
|  |           ESP_LOGI("test", "Device B Mode set to %s", x.c_str()); | ||||||
|  |           id(mode_device_b).state = x; | ||||||
|  |  | ||||||
|  |   - platform: template | ||||||
|  |     name: Mode | ||||||
|  |     id: mode_main | ||||||
|  |     options: | ||||||
|  |       - "Auto" | ||||||
|  |       - "Manual" | ||||||
|  |       - "Off" | ||||||
|  |     restore_value: true | ||||||
|  |     initial_option: "Off" | ||||||
|  |     set_action: | ||||||
|  |       - lambda: |- | ||||||
|  |           ESP_LOGI("test", "Main Mode set to %s", x.c_str()); | ||||||
|  |           id(mode_main).state = x; | ||||||
|  |  | ||||||
|  | # Button to trigger preference logging test | ||||||
|  | button: | ||||||
|  |   - platform: template | ||||||
|  |     name: Test Preferences | ||||||
|  |     on_press: | ||||||
|  |       - lambda: |- | ||||||
|  |           ESP_LOGI("test", "Testing preference storage uniqueness:"); | ||||||
|  |           ESP_LOGI("test", "Device A Light state: %s", id(light_device_a).state ? "ON" : "OFF"); | ||||||
|  |           ESP_LOGI("test", "Device B Light state: %s", id(light_device_b).state ? "ON" : "OFF"); | ||||||
|  |           ESP_LOGI("test", "Main Light state: %s", id(light_main).state ? "ON" : "OFF"); | ||||||
|  |           ESP_LOGI("test", "Device A Setpoint: %.1f", id(setpoint_device_a).state); | ||||||
|  |           ESP_LOGI("test", "Device B Setpoint: %.1f", id(setpoint_device_b).state); | ||||||
|  |           ESP_LOGI("test", "Main Setpoint: %.1f", id(setpoint_main).state); | ||||||
|  |           ESP_LOGI("test", "Device A Mode: %s", id(mode_device_a).state.c_str()); | ||||||
|  |           ESP_LOGI("test", "Device B Mode: %s", id(mode_device_b).state.c_str()); | ||||||
|  |           ESP_LOGI("test", "Main Mode: %s", id(mode_main).state.c_str()); | ||||||
|  |           // Log preference hashes for entities that actually store preferences | ||||||
|  |           ESP_LOGI("test", "Device A Switch Pref Hash: %u", id(light_device_a).get_preference_hash()); | ||||||
|  |           ESP_LOGI("test", "Device B Switch Pref Hash: %u", id(light_device_b).get_preference_hash()); | ||||||
|  |           ESP_LOGI("test", "Main Switch Pref Hash: %u", id(light_main).get_preference_hash()); | ||||||
|  |           ESP_LOGI("test", "Device A Number Pref Hash: %u", id(setpoint_device_a).get_preference_hash()); | ||||||
|  |           ESP_LOGI("test", "Device B Number Pref Hash: %u", id(setpoint_device_b).get_preference_hash()); | ||||||
|  |           ESP_LOGI("test", "Main Number Pref Hash: %u", id(setpoint_main).get_preference_hash()); | ||||||
							
								
								
									
										144
									
								
								tests/integration/test_multi_device_preferences.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								tests/integration/test_multi_device_preferences.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,144 @@ | |||||||
|  | """Test multi-device preference storage functionality.""" | ||||||
|  |  | ||||||
|  | from __future__ import annotations | ||||||
|  |  | ||||||
|  | import asyncio | ||||||
|  | import re | ||||||
|  |  | ||||||
|  | from aioesphomeapi import ButtonInfo, NumberInfo, SelectInfo, SwitchInfo | ||||||
|  | import pytest | ||||||
|  |  | ||||||
|  | from .types import APIClientConnectedFactory, RunCompiledFunction | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_multi_device_preferences( | ||||||
|  |     yaml_config: str, | ||||||
|  |     run_compiled: RunCompiledFunction, | ||||||
|  |     api_client_connected: APIClientConnectedFactory, | ||||||
|  | ) -> None: | ||||||
|  |     """Test that entities with same names on different devices have unique preference storage.""" | ||||||
|  |     loop = asyncio.get_running_loop() | ||||||
|  |     log_lines: list[str] = [] | ||||||
|  |     preferences_logged = loop.create_future() | ||||||
|  |  | ||||||
|  |     # Patterns to match preference hash logs | ||||||
|  |     switch_hash_pattern_device = re.compile(r"Device ([AB]) Switch Pref Hash: (\d+)") | ||||||
|  |     switch_hash_pattern_main = re.compile(r"Main Switch Pref Hash: (\d+)") | ||||||
|  |     number_hash_pattern_device = re.compile(r"Device ([AB]) Number Pref Hash: (\d+)") | ||||||
|  |     number_hash_pattern_main = re.compile(r"Main Number Pref Hash: (\d+)") | ||||||
|  |     switch_hashes: dict[str, int] = {} | ||||||
|  |     number_hashes: dict[str, int] = {} | ||||||
|  |  | ||||||
|  |     def check_output(line: str) -> None: | ||||||
|  |         """Check log output for preference hash information.""" | ||||||
|  |         log_lines.append(line) | ||||||
|  |  | ||||||
|  |         # Look for device switch preference hash logs | ||||||
|  |         match = switch_hash_pattern_device.search(line) | ||||||
|  |         if match: | ||||||
|  |             device = match.group(1) | ||||||
|  |             hash_value = int(match.group(2)) | ||||||
|  |             switch_hashes[device] = hash_value | ||||||
|  |  | ||||||
|  |         # Look for main switch preference hash | ||||||
|  |         match = switch_hash_pattern_main.search(line) | ||||||
|  |         if match: | ||||||
|  |             hash_value = int(match.group(1)) | ||||||
|  |             switch_hashes["Main"] = hash_value | ||||||
|  |  | ||||||
|  |         # Look for device number preference hash logs | ||||||
|  |         match = number_hash_pattern_device.search(line) | ||||||
|  |         if match: | ||||||
|  |             device = match.group(1) | ||||||
|  |             hash_value = int(match.group(2)) | ||||||
|  |             number_hashes[device] = hash_value | ||||||
|  |  | ||||||
|  |         # Look for main number preference hash | ||||||
|  |         match = number_hash_pattern_main.search(line) | ||||||
|  |         if match: | ||||||
|  |             hash_value = int(match.group(1)) | ||||||
|  |             number_hashes["Main"] = hash_value | ||||||
|  |  | ||||||
|  |         # If we have all hashes, complete the future | ||||||
|  |         if ( | ||||||
|  |             len(switch_hashes) == 3 | ||||||
|  |             and len(number_hashes) == 3 | ||||||
|  |             and not preferences_logged.done() | ||||||
|  |         ): | ||||||
|  |             preferences_logged.set_result(True) | ||||||
|  |  | ||||||
|  |     async with ( | ||||||
|  |         run_compiled(yaml_config, line_callback=check_output), | ||||||
|  |         api_client_connected() as client, | ||||||
|  |     ): | ||||||
|  |         # Get entity list | ||||||
|  |         entities, _ = await client.list_entities_services() | ||||||
|  |  | ||||||
|  |         # Verify we have the expected entities with duplicate names on different devices | ||||||
|  |  | ||||||
|  |         # Check switches (3 with name "Light") | ||||||
|  |         switches = [ | ||||||
|  |             e for e in entities if isinstance(e, SwitchInfo) and e.name == "Light" | ||||||
|  |         ] | ||||||
|  |         assert len(switches) == 3, f"Expected 3 'Light' switches, got {len(switches)}" | ||||||
|  |  | ||||||
|  |         # Check numbers (3 with name "Setpoint") | ||||||
|  |         numbers = [ | ||||||
|  |             e for e in entities if isinstance(e, NumberInfo) and e.name == "Setpoint" | ||||||
|  |         ] | ||||||
|  |         assert len(numbers) == 3, f"Expected 3 'Setpoint' numbers, got {len(numbers)}" | ||||||
|  |  | ||||||
|  |         # Check selects (3 with name "Mode") | ||||||
|  |         selects = [ | ||||||
|  |             e for e in entities if isinstance(e, SelectInfo) and e.name == "Mode" | ||||||
|  |         ] | ||||||
|  |         assert len(selects) == 3, f"Expected 3 'Mode' selects, got {len(selects)}" | ||||||
|  |  | ||||||
|  |         # Find the test button entity to trigger preference logging | ||||||
|  |         buttons = [e for e in entities if isinstance(e, ButtonInfo)] | ||||||
|  |         test_button = next((b for b in buttons if b.name == "Test Preferences"), None) | ||||||
|  |         assert test_button is not None, "Test Preferences button not found" | ||||||
|  |  | ||||||
|  |         # Press the button to trigger logging | ||||||
|  |         client.button_command(test_button.key) | ||||||
|  |  | ||||||
|  |         # Wait for preference hashes to be logged | ||||||
|  |         try: | ||||||
|  |             await asyncio.wait_for(preferences_logged, timeout=5.0) | ||||||
|  |         except TimeoutError: | ||||||
|  |             pytest.fail("Preference hashes not logged within timeout") | ||||||
|  |  | ||||||
|  |         # Verify all switch preference hashes are unique | ||||||
|  |         assert len(switch_hashes) == 3, ( | ||||||
|  |             f"Expected 3 devices with switches, got {switch_hashes}" | ||||||
|  |         ) | ||||||
|  |         switch_hash_values = list(switch_hashes.values()) | ||||||
|  |         assert len(switch_hash_values) == len(set(switch_hash_values)), ( | ||||||
|  |             f"Switch preference hashes are not unique: {switch_hashes}" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Verify all number preference hashes are unique | ||||||
|  |         assert len(number_hashes) == 3, ( | ||||||
|  |             f"Expected 3 devices with numbers, got {number_hashes}" | ||||||
|  |         ) | ||||||
|  |         number_hash_values = list(number_hashes.values()) | ||||||
|  |         assert len(number_hash_values) == len(set(number_hash_values)), ( | ||||||
|  |             f"Number preference hashes are not unique: {number_hashes}" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Verify Device A and Device B have different hashes (they have device_id set) | ||||||
|  |         assert switch_hashes["A"] != switch_hashes["B"], ( | ||||||
|  |             f"Device A and B switches should have different hashes: A={switch_hashes['A']}, B={switch_hashes['B']}" | ||||||
|  |         ) | ||||||
|  |         assert number_hashes["A"] != number_hashes["B"], ( | ||||||
|  |             f"Device A and B numbers should have different hashes: A={number_hashes['A']}, B={number_hashes['B']}" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Verify Main device hash is different from both A and B | ||||||
|  |         assert switch_hashes["Main"] != switch_hashes["A"], ( | ||||||
|  |             f"Main and Device A switches should have different hashes: Main={switch_hashes['Main']}, A={switch_hashes['A']}" | ||||||
|  |         ) | ||||||
|  |         assert switch_hashes["Main"] != switch_hashes["B"], ( | ||||||
|  |             f"Main and Device B switches should have different hashes: Main={switch_hashes['Main']}, B={switch_hashes['B']}" | ||||||
|  |         ) | ||||||
		Reference in New Issue
	
	Block a user