mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 09:01:49 +00:00 
			
		
		
		
	Merge remote-tracking branch 'upstream/has_state_' into integration
This commit is contained in:
		@@ -11,25 +11,25 @@ static const char *const TAG = "datetime.date_entity";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void DateEntity::publish_state() {
 | 
					void DateEntity::publish_state() {
 | 
				
			||||||
  if (this->year_ == 0 || this->month_ == 0 || this->day_ == 0) {
 | 
					  if (this->year_ == 0 || this->month_ == 0 || this->day_ == 0) {
 | 
				
			||||||
    this->has_state_ = false;
 | 
					    this->set_has_state(false);
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (this->year_ < 1970 || this->year_ > 3000) {
 | 
					  if (this->year_ < 1970 || this->year_ > 3000) {
 | 
				
			||||||
    this->has_state_ = false;
 | 
					    this->set_has_state(false);
 | 
				
			||||||
    ESP_LOGE(TAG, "Year must be between 1970 and 3000");
 | 
					    ESP_LOGE(TAG, "Year must be between 1970 and 3000");
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (this->month_ < 1 || this->month_ > 12) {
 | 
					  if (this->month_ < 1 || this->month_ > 12) {
 | 
				
			||||||
    this->has_state_ = false;
 | 
					    this->set_has_state(false);
 | 
				
			||||||
    ESP_LOGE(TAG, "Month must be between 1 and 12");
 | 
					    ESP_LOGE(TAG, "Month must be between 1 and 12");
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (this->day_ > days_in_month(this->month_, this->year_)) {
 | 
					  if (this->day_ > days_in_month(this->month_, this->year_)) {
 | 
				
			||||||
    this->has_state_ = false;
 | 
					    this->set_has_state(false);
 | 
				
			||||||
    ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(this->month_, this->year_), this->month_);
 | 
					    ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(this->month_, this->year_), this->month_);
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  this->has_state_ = true;
 | 
					  this->set_has_state(true);
 | 
				
			||||||
  ESP_LOGD(TAG, "'%s': Sending date %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_);
 | 
					  ESP_LOGD(TAG, "'%s': Sending date %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_);
 | 
				
			||||||
  this->state_callback_.call();
 | 
					  this->state_callback_.call();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,9 +13,6 @@ namespace datetime {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class DateTimeBase : public EntityBase {
 | 
					class DateTimeBase : public EntityBase {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  /// Return whether this Datetime has gotten a full state yet.
 | 
					 | 
				
			||||||
  bool has_state() const { return this->has_state_; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  virtual ESPTime state_as_esptime() const = 0;
 | 
					  virtual ESPTime state_as_esptime() const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
 | 
					  void add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
 | 
				
			||||||
@@ -31,8 +28,6 @@ class DateTimeBase : public EntityBase {
 | 
				
			|||||||
#ifdef USE_TIME
 | 
					#ifdef USE_TIME
 | 
				
			||||||
  time::RealTimeClock *rtc_;
 | 
					  time::RealTimeClock *rtc_;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					 | 
				
			||||||
  bool has_state_{false};
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_TIME
 | 
					#ifdef USE_TIME
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,40 +11,40 @@ static const char *const TAG = "datetime.datetime_entity";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void DateTimeEntity::publish_state() {
 | 
					void DateTimeEntity::publish_state() {
 | 
				
			||||||
  if (this->year_ == 0 || this->month_ == 0 || this->day_ == 0) {
 | 
					  if (this->year_ == 0 || this->month_ == 0 || this->day_ == 0) {
 | 
				
			||||||
    this->has_state_ = false;
 | 
					    this->set_has_state(false);
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (this->year_ < 1970 || this->year_ > 3000) {
 | 
					  if (this->year_ < 1970 || this->year_ > 3000) {
 | 
				
			||||||
    this->has_state_ = false;
 | 
					    this->set_has_state(false);
 | 
				
			||||||
    ESP_LOGE(TAG, "Year must be between 1970 and 3000");
 | 
					    ESP_LOGE(TAG, "Year must be between 1970 and 3000");
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (this->month_ < 1 || this->month_ > 12) {
 | 
					  if (this->month_ < 1 || this->month_ > 12) {
 | 
				
			||||||
    this->has_state_ = false;
 | 
					    this->set_has_state(false);
 | 
				
			||||||
    ESP_LOGE(TAG, "Month must be between 1 and 12");
 | 
					    ESP_LOGE(TAG, "Month must be between 1 and 12");
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (this->day_ > days_in_month(this->month_, this->year_)) {
 | 
					  if (this->day_ > days_in_month(this->month_, this->year_)) {
 | 
				
			||||||
    this->has_state_ = false;
 | 
					    this->set_has_state(false);
 | 
				
			||||||
    ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(this->month_, this->year_), this->month_);
 | 
					    ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(this->month_, this->year_), this->month_);
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (this->hour_ > 23) {
 | 
					  if (this->hour_ > 23) {
 | 
				
			||||||
    this->has_state_ = false;
 | 
					    this->set_has_state(false);
 | 
				
			||||||
    ESP_LOGE(TAG, "Hour must be between 0 and 23");
 | 
					    ESP_LOGE(TAG, "Hour must be between 0 and 23");
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (this->minute_ > 59) {
 | 
					  if (this->minute_ > 59) {
 | 
				
			||||||
    this->has_state_ = false;
 | 
					    this->set_has_state(false);
 | 
				
			||||||
    ESP_LOGE(TAG, "Minute must be between 0 and 59");
 | 
					    ESP_LOGE(TAG, "Minute must be between 0 and 59");
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (this->second_ > 59) {
 | 
					  if (this->second_ > 59) {
 | 
				
			||||||
    this->has_state_ = false;
 | 
					    this->set_has_state(false);
 | 
				
			||||||
    ESP_LOGE(TAG, "Second must be between 0 and 59");
 | 
					    ESP_LOGE(TAG, "Second must be between 0 and 59");
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  this->has_state_ = true;
 | 
					  this->set_has_state(true);
 | 
				
			||||||
  ESP_LOGD(TAG, "'%s': Sending datetime %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_,
 | 
					  ESP_LOGD(TAG, "'%s': Sending datetime %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_,
 | 
				
			||||||
           this->month_, this->day_, this->hour_, this->minute_, this->second_);
 | 
					           this->month_, this->day_, this->hour_, this->minute_, this->second_);
 | 
				
			||||||
  this->state_callback_.call();
 | 
					  this->state_callback_.call();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,21 +11,21 @@ static const char *const TAG = "datetime.time_entity";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void TimeEntity::publish_state() {
 | 
					void TimeEntity::publish_state() {
 | 
				
			||||||
  if (this->hour_ > 23) {
 | 
					  if (this->hour_ > 23) {
 | 
				
			||||||
    this->has_state_ = false;
 | 
					    this->set_has_state(false);
 | 
				
			||||||
    ESP_LOGE(TAG, "Hour must be between 0 and 23");
 | 
					    ESP_LOGE(TAG, "Hour must be between 0 and 23");
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (this->minute_ > 59) {
 | 
					  if (this->minute_ > 59) {
 | 
				
			||||||
    this->has_state_ = false;
 | 
					    this->set_has_state(false);
 | 
				
			||||||
    ESP_LOGE(TAG, "Minute must be between 0 and 59");
 | 
					    ESP_LOGE(TAG, "Minute must be between 0 and 59");
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (this->second_ > 59) {
 | 
					  if (this->second_ > 59) {
 | 
				
			||||||
    this->has_state_ = false;
 | 
					    this->set_has_state(false);
 | 
				
			||||||
    ESP_LOGE(TAG, "Second must be between 0 and 59");
 | 
					    ESP_LOGE(TAG, "Second must be between 0 and 59");
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  this->has_state_ = true;
 | 
					  this->set_has_state(true);
 | 
				
			||||||
  ESP_LOGD(TAG, "'%s': Sending time %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_,
 | 
					  ESP_LOGD(TAG, "'%s': Sending time %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_,
 | 
				
			||||||
           this->second_);
 | 
					           this->second_);
 | 
				
			||||||
  this->state_callback_.call();
 | 
					  this->state_callback_.call();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -57,7 +57,7 @@ void ESP32Camera::dump_config() {
 | 
				
			|||||||
                "  External Clock: Pin:%d Frequency:%u\n"
 | 
					                "  External Clock: Pin:%d Frequency:%u\n"
 | 
				
			||||||
                "  I2C Pins: SDA:%d SCL:%d\n"
 | 
					                "  I2C Pins: SDA:%d SCL:%d\n"
 | 
				
			||||||
                "  Reset Pin: %d",
 | 
					                "  Reset Pin: %d",
 | 
				
			||||||
                this->name_.c_str(), YESNO(this->internal_), conf.pin_d0, conf.pin_d1, conf.pin_d2, conf.pin_d3,
 | 
					                this->name_.c_str(), YESNO(this->is_internal()), conf.pin_d0, conf.pin_d1, conf.pin_d2, conf.pin_d3,
 | 
				
			||||||
                conf.pin_d4, conf.pin_d5, conf.pin_d6, conf.pin_d7, conf.pin_vsync, conf.pin_href, conf.pin_pclk,
 | 
					                conf.pin_d4, conf.pin_d5, conf.pin_d6, conf.pin_d7, conf.pin_vsync, conf.pin_href, conf.pin_pclk,
 | 
				
			||||||
                conf.pin_xclk, conf.xclk_freq_hz, conf.pin_sccb_sda, conf.pin_sccb_scl, conf.pin_reset);
 | 
					                conf.pin_xclk, conf.xclk_freq_hz, conf.pin_sccb_sda, conf.pin_sccb_scl, conf.pin_reset);
 | 
				
			||||||
  switch (this->config_.frame_size) {
 | 
					  switch (this->config_.frame_size) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -56,7 +56,7 @@ void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nexti
 | 
				
			|||||||
    this->publish_state(state);
 | 
					    this->publish_state(state);
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    this->state = state;
 | 
					    this->state = state;
 | 
				
			||||||
    this->has_state_ = true;
 | 
					    this->set_has_state(true);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  this->update_component_settings();
 | 
					  this->update_component_settings();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -88,7 +88,7 @@ void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) {
 | 
				
			|||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      this->raw_state = state;
 | 
					      this->raw_state = state;
 | 
				
			||||||
      this->state = state;
 | 
					      this->state = state;
 | 
				
			||||||
      this->has_state_ = true;
 | 
					      this->set_has_state(true);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  this->update_component_settings();
 | 
					  this->update_component_settings();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,7 +37,7 @@ void NextionTextSensor::set_state(const std::string &state, bool publish, bool s
 | 
				
			|||||||
    this->publish_state(state);
 | 
					    this->publish_state(state);
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    this->state = state;
 | 
					    this->state = state;
 | 
				
			||||||
    this->has_state_ = true;
 | 
					    this->set_has_state(true);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  this->update_component_settings();
 | 
					  this->update_component_settings();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ namespace number {
 | 
				
			|||||||
static const char *const TAG = "number";
 | 
					static const char *const TAG = "number";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Number::publish_state(float state) {
 | 
					void Number::publish_state(float state) {
 | 
				
			||||||
  this->has_state_ = true;
 | 
					  this->set_has_state(true);
 | 
				
			||||||
  this->state = state;
 | 
					  this->state = state;
 | 
				
			||||||
  ESP_LOGD(TAG, "'%s': Sending state %f", this->get_name().c_str(), state);
 | 
					  ESP_LOGD(TAG, "'%s': Sending state %f", this->get_name().c_str(), state);
 | 
				
			||||||
  this->state_callback_.call(state);
 | 
					  this->state_callback_.call(state);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,9 +48,6 @@ class Number : public EntityBase {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  NumberTraits traits;
 | 
					  NumberTraits traits;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Return whether this number has gotten a full state yet.
 | 
					 | 
				
			||||||
  bool has_state() const { return has_state_; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  friend class NumberCall;
 | 
					  friend class NumberCall;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -63,7 +60,6 @@ class Number : public EntityBase {
 | 
				
			|||||||
  virtual void control(float value) = 0;
 | 
					  virtual void control(float value) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  CallbackManager<void(float)> state_callback_;
 | 
					  CallbackManager<void(float)> state_callback_;
 | 
				
			||||||
  bool has_state_{false};
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace number
 | 
					}  // namespace number
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,7 @@ void Select::publish_state(const std::string &state) {
 | 
				
			|||||||
  auto index = this->index_of(state);
 | 
					  auto index = this->index_of(state);
 | 
				
			||||||
  const auto *name = this->get_name().c_str();
 | 
					  const auto *name = this->get_name().c_str();
 | 
				
			||||||
  if (index.has_value()) {
 | 
					  if (index.has_value()) {
 | 
				
			||||||
    this->has_state_ = true;
 | 
					    this->set_has_state(true);
 | 
				
			||||||
    this->state = state;
 | 
					    this->state = state;
 | 
				
			||||||
    ESP_LOGD(TAG, "'%s': Sending state %s (index %zu)", name, state.c_str(), index.value());
 | 
					    ESP_LOGD(TAG, "'%s': Sending state %s (index %zu)", name, state.c_str(), index.value());
 | 
				
			||||||
    this->state_callback_.call(state, index.value());
 | 
					    this->state_callback_.call(state, index.value());
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,9 +35,6 @@ class Select : public EntityBase {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  void publish_state(const std::string &state);
 | 
					  void publish_state(const std::string &state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Return whether this select component has gotten a full state yet.
 | 
					 | 
				
			||||||
  bool has_state() const { return has_state_; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// Instantiate a SelectCall object to modify this select component's state.
 | 
					  /// Instantiate a SelectCall object to modify this select component's state.
 | 
				
			||||||
  SelectCall make_call() { return SelectCall(this); }
 | 
					  SelectCall make_call() { return SelectCall(this); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -73,7 +70,6 @@ class Select : public EntityBase {
 | 
				
			|||||||
  virtual void control(const std::string &value) = 0;
 | 
					  virtual void control(const std::string &value) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  CallbackManager<void(std::string, size_t)> state_callback_;
 | 
					  CallbackManager<void(std::string, size_t)> state_callback_;
 | 
				
			||||||
  bool has_state_{false};
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace select
 | 
					}  // namespace select
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -93,13 +93,12 @@ float Sensor::get_raw_state() const { return this->raw_state; }
 | 
				
			|||||||
std::string Sensor::unique_id() { return ""; }
 | 
					std::string Sensor::unique_id() { return ""; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Sensor::internal_send_state_to_frontend(float state) {
 | 
					void Sensor::internal_send_state_to_frontend(float state) {
 | 
				
			||||||
  this->has_state_ = true;
 | 
					  this->set_has_state(true);
 | 
				
			||||||
  this->state = state;
 | 
					  this->state = state;
 | 
				
			||||||
  ESP_LOGD(TAG, "'%s': Sending state %.5f %s with %d decimals of accuracy", this->get_name().c_str(), state,
 | 
					  ESP_LOGD(TAG, "'%s': Sending state %.5f %s with %d decimals of accuracy", this->get_name().c_str(), state,
 | 
				
			||||||
           this->get_unit_of_measurement().c_str(), this->get_accuracy_decimals());
 | 
					           this->get_unit_of_measurement().c_str(), this->get_accuracy_decimals());
 | 
				
			||||||
  this->callback_.call(state);
 | 
					  this->callback_.call(state);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
bool Sensor::has_state() const { return this->has_state_; }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace sensor
 | 
					}  // namespace sensor
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -141,9 +141,6 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  float raw_state;
 | 
					  float raw_state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Return whether this sensor has gotten a full state (that passed through all filters) yet.
 | 
					 | 
				
			||||||
  bool has_state() const;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /** Override this method to set the unique ID of this sensor.
 | 
					  /** Override this method to set the unique ID of this sensor.
 | 
				
			||||||
   *
 | 
					   *
 | 
				
			||||||
   * @deprecated Do not use for new sensors, a suitable unique ID is automatically generated (2023.4).
 | 
					   * @deprecated Do not use for new sensors, a suitable unique ID is automatically generated (2023.4).
 | 
				
			||||||
@@ -161,7 +158,6 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
 | 
				
			|||||||
  optional<int8_t> accuracy_decimals_;                  ///< Accuracy in decimals override
 | 
					  optional<int8_t> accuracy_decimals_;                  ///< Accuracy in decimals override
 | 
				
			||||||
  optional<StateClass> state_class_{STATE_CLASS_NONE};  ///< State class override
 | 
					  optional<StateClass> state_class_{STATE_CLASS_NONE};  ///< State class override
 | 
				
			||||||
  bool force_update_{false};                            ///< Force update mode
 | 
					  bool force_update_{false};                            ///< Force update mode
 | 
				
			||||||
  bool has_state_{false};
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace sensor
 | 
					}  // namespace sensor
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ namespace text {
 | 
				
			|||||||
static const char *const TAG = "text";
 | 
					static const char *const TAG = "text";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Text::publish_state(const std::string &state) {
 | 
					void Text::publish_state(const std::string &state) {
 | 
				
			||||||
  this->has_state_ = true;
 | 
					  this->set_has_state(true);
 | 
				
			||||||
  this->state = state;
 | 
					  this->state = state;
 | 
				
			||||||
  if (this->traits.get_mode() == TEXT_MODE_PASSWORD) {
 | 
					  if (this->traits.get_mode() == TEXT_MODE_PASSWORD) {
 | 
				
			||||||
    ESP_LOGD(TAG, "'%s': Sending state " LOG_SECRET("'%s'"), this->get_name().c_str(), state.c_str());
 | 
					    ESP_LOGD(TAG, "'%s': Sending state " LOG_SECRET("'%s'"), this->get_name().c_str(), state.c_str());
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,9 +28,6 @@ class Text : public EntityBase {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  void publish_state(const std::string &state);
 | 
					  void publish_state(const std::string &state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Return whether this text input has gotten a full state yet.
 | 
					 | 
				
			||||||
  bool has_state() const { return has_state_; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// Instantiate a TextCall object to modify this text component's state.
 | 
					  /// Instantiate a TextCall object to modify this text component's state.
 | 
				
			||||||
  TextCall make_call() { return TextCall(this); }
 | 
					  TextCall make_call() { return TextCall(this); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -48,7 +45,6 @@ class Text : public EntityBase {
 | 
				
			|||||||
  virtual void control(const std::string &value) = 0;
 | 
					  virtual void control(const std::string &value) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  CallbackManager<void(std::string)> state_callback_;
 | 
					  CallbackManager<void(std::string)> state_callback_;
 | 
				
			||||||
  bool has_state_{false};
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace text
 | 
					}  // namespace text
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -65,13 +65,12 @@ std::string TextSensor::get_state() const { return this->state; }
 | 
				
			|||||||
std::string TextSensor::get_raw_state() const { return this->raw_state; }
 | 
					std::string TextSensor::get_raw_state() const { return this->raw_state; }
 | 
				
			||||||
void TextSensor::internal_send_state_to_frontend(const std::string &state) {
 | 
					void TextSensor::internal_send_state_to_frontend(const std::string &state) {
 | 
				
			||||||
  this->state = state;
 | 
					  this->state = state;
 | 
				
			||||||
  this->has_state_ = true;
 | 
					  this->set_has_state(true);
 | 
				
			||||||
  ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), state.c_str());
 | 
					  ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), state.c_str());
 | 
				
			||||||
  this->callback_.call(state);
 | 
					  this->callback_.call(state);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
std::string TextSensor::unique_id() { return ""; }
 | 
					std::string TextSensor::unique_id() { return ""; }
 | 
				
			||||||
bool TextSensor::has_state() { return this->has_state_; }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace text_sensor
 | 
					}  // namespace text_sensor
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -70,8 +70,6 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  virtual std::string unique_id();
 | 
					  virtual std::string unique_id();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool has_state();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void internal_send_state_to_frontend(const std::string &state);
 | 
					  void internal_send_state_to_frontend(const std::string &state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
@@ -80,8 +78,6 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass {
 | 
				
			|||||||
  CallbackManager<void(std::string)> callback_;  ///< Storage for filtered state callbacks.
 | 
					  CallbackManager<void(std::string)> callback_;  ///< Storage for filtered state callbacks.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Filter *filter_list_{nullptr};  ///< Store all active filters.
 | 
					  Filter *filter_list_{nullptr};  ///< Store all active filters.
 | 
				
			||||||
 | 
					 | 
				
			||||||
  bool has_state_{false};
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace text_sensor
 | 
					}  // namespace text_sensor
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,7 +30,7 @@ void UpdateEntity::publish_state() {
 | 
				
			|||||||
    ESP_LOGD(TAG, "  Progress: %.0f%%", this->update_info_.progress);
 | 
					    ESP_LOGD(TAG, "  Progress: %.0f%%", this->update_info_.progress);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  this->has_state_ = true;
 | 
					  this->set_has_state(true);
 | 
				
			||||||
  this->state_callback_.call();
 | 
					  this->state_callback_.call();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,8 +28,6 @@ enum UpdateState : uint8_t {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class UpdateEntity : public EntityBase, public EntityBase_DeviceClass {
 | 
					class UpdateEntity : public EntityBase, public EntityBase_DeviceClass {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  bool has_state() const { return this->has_state_; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void publish_state();
 | 
					  void publish_state();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void perform() { this->perform(false); }
 | 
					  void perform() { this->perform(false); }
 | 
				
			||||||
@@ -44,7 +42,6 @@ class UpdateEntity : public EntityBase, public EntityBase_DeviceClass {
 | 
				
			|||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  UpdateState state_{UPDATE_STATE_UNKNOWN};
 | 
					  UpdateState state_{UPDATE_STATE_UNKNOWN};
 | 
				
			||||||
  UpdateInfo update_info_;
 | 
					  UpdateInfo update_info_;
 | 
				
			||||||
  bool has_state_{false};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  CallbackManager<void()> state_callback_{};
 | 
					  CallbackManager<void()> state_callback_{};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,7 @@ static const char *const TAG = "uptime.sensor";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void UptimeTimestampSensor::setup() {
 | 
					void UptimeTimestampSensor::setup() {
 | 
				
			||||||
  this->time_->add_on_time_sync_callback([this]() {
 | 
					  this->time_->add_on_time_sync_callback([this]() {
 | 
				
			||||||
    if (this->has_state_)
 | 
					    if (this->has_state())
 | 
				
			||||||
      return;  // No need to update the timestamp if it's already set
 | 
					      return;  // No need to update the timestamp if it's already set
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    auto now = this->time_->now();
 | 
					    auto now = this->time_->now();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,20 +12,12 @@ void EntityBase::set_name(const char *name) {
 | 
				
			|||||||
  this->name_ = StringRef(name);
 | 
					  this->name_ = StringRef(name);
 | 
				
			||||||
  if (this->name_.empty()) {
 | 
					  if (this->name_.empty()) {
 | 
				
			||||||
    this->name_ = StringRef(App.get_friendly_name());
 | 
					    this->name_ = StringRef(App.get_friendly_name());
 | 
				
			||||||
    this->has_own_name_ = false;
 | 
					    this->flags_.has_own_name = false;
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    this->has_own_name_ = true;
 | 
					    this->flags_.has_own_name = true;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Entity Internal
 | 
					 | 
				
			||||||
bool EntityBase::is_internal() const { return this->internal_; }
 | 
					 | 
				
			||||||
void EntityBase::set_internal(bool internal) { this->internal_ = internal; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Entity Disabled by Default
 | 
					 | 
				
			||||||
bool EntityBase::is_disabled_by_default() const { return this->disabled_by_default_; }
 | 
					 | 
				
			||||||
void EntityBase::set_disabled_by_default(bool disabled_by_default) { this->disabled_by_default_ = disabled_by_default; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Entity Icon
 | 
					// Entity Icon
 | 
				
			||||||
std::string EntityBase::get_icon() const {
 | 
					std::string EntityBase::get_icon() const {
 | 
				
			||||||
  if (this->icon_c_str_ == nullptr) {
 | 
					  if (this->icon_c_str_ == nullptr) {
 | 
				
			||||||
@@ -35,14 +27,10 @@ std::string EntityBase::get_icon() const {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
void EntityBase::set_icon(const char *icon) { this->icon_c_str_ = icon; }
 | 
					void EntityBase::set_icon(const char *icon) { this->icon_c_str_ = icon; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Entity Category
 | 
					 | 
				
			||||||
EntityCategory EntityBase::get_entity_category() const { return this->entity_category_; }
 | 
					 | 
				
			||||||
void EntityBase::set_entity_category(EntityCategory entity_category) { this->entity_category_ = entity_category; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Entity Object ID
 | 
					// Entity Object ID
 | 
				
			||||||
std::string EntityBase::get_object_id() const {
 | 
					std::string EntityBase::get_object_id() const {
 | 
				
			||||||
  // Check if `App.get_friendly_name()` is constant or dynamic.
 | 
					  // Check if `App.get_friendly_name()` is constant or dynamic.
 | 
				
			||||||
  if (!this->has_own_name_ && App.is_name_add_mac_suffix_enabled()) {
 | 
					  if (!this->flags_.has_own_name && App.is_name_add_mac_suffix_enabled()) {
 | 
				
			||||||
    // `App.get_friendly_name()` is dynamic.
 | 
					    // `App.get_friendly_name()` is dynamic.
 | 
				
			||||||
    return str_sanitize(str_snake_case(App.get_friendly_name()));
 | 
					    return str_sanitize(str_snake_case(App.get_friendly_name()));
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
@@ -61,7 +49,7 @@ void EntityBase::set_object_id(const char *object_id) {
 | 
				
			|||||||
// Calculate Object ID Hash from Entity Name
 | 
					// Calculate Object ID Hash from Entity Name
 | 
				
			||||||
void EntityBase::calc_object_id_() {
 | 
					void EntityBase::calc_object_id_() {
 | 
				
			||||||
  // Check if `App.get_friendly_name()` is constant or dynamic.
 | 
					  // Check if `App.get_friendly_name()` is constant or dynamic.
 | 
				
			||||||
  if (!this->has_own_name_ && App.is_name_add_mac_suffix_enabled()) {
 | 
					  if (!this->flags_.has_own_name && App.is_name_add_mac_suffix_enabled()) {
 | 
				
			||||||
    // `App.get_friendly_name()` is dynamic.
 | 
					    // `App.get_friendly_name()` is dynamic.
 | 
				
			||||||
    const auto object_id = str_sanitize(str_snake_case(App.get_friendly_name()));
 | 
					    const auto object_id = str_sanitize(str_snake_case(App.get_friendly_name()));
 | 
				
			||||||
    // FNV-1 hash
 | 
					    // FNV-1 hash
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,7 +22,7 @@ class EntityBase {
 | 
				
			|||||||
  void set_name(const char *name);
 | 
					  void set_name(const char *name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Get whether this Entity has its own name or it should use the device friendly_name.
 | 
					  // Get whether this Entity has its own name or it should use the device friendly_name.
 | 
				
			||||||
  bool has_own_name() const { return this->has_own_name_; }
 | 
					  bool has_own_name() const { return this->flags_.has_own_name; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Get the sanitized name of this Entity as an ID.
 | 
					  // Get the sanitized name of this Entity as an ID.
 | 
				
			||||||
  std::string get_object_id() const;
 | 
					  std::string get_object_id() const;
 | 
				
			||||||
@@ -32,23 +32,31 @@ class EntityBase {
 | 
				
			|||||||
  uint32_t get_object_id_hash();
 | 
					  uint32_t get_object_id_hash();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Get/set whether this Entity should be hidden outside ESPHome
 | 
					  // Get/set whether this Entity should be hidden outside ESPHome
 | 
				
			||||||
  bool is_internal() const;
 | 
					  bool is_internal() const { return this->flags_.internal; }
 | 
				
			||||||
  void set_internal(bool internal);
 | 
					  void set_internal(bool internal) { this->flags_.internal = internal; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Check if this object is declared to be disabled by default.
 | 
					  // Check if this object is declared to be disabled by default.
 | 
				
			||||||
  // That means that when the device gets added to Home Assistant (or other clients) it should
 | 
					  // That means that when the device gets added to Home Assistant (or other clients) it should
 | 
				
			||||||
  // not be added to the default view by default, and a user action is necessary to manually add it.
 | 
					  // not be added to the default view by default, and a user action is necessary to manually add it.
 | 
				
			||||||
  bool is_disabled_by_default() const;
 | 
					  bool is_disabled_by_default() const { return this->flags_.disabled_by_default; }
 | 
				
			||||||
  void set_disabled_by_default(bool disabled_by_default);
 | 
					  void set_disabled_by_default(bool disabled_by_default) { this->flags_.disabled_by_default = disabled_by_default; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Get/set the entity category.
 | 
					  // Get/set the entity category.
 | 
				
			||||||
  EntityCategory get_entity_category() const;
 | 
					  EntityCategory get_entity_category() const { return static_cast<EntityCategory>(this->flags_.entity_category); }
 | 
				
			||||||
  void set_entity_category(EntityCategory entity_category);
 | 
					  void set_entity_category(EntityCategory entity_category) {
 | 
				
			||||||
 | 
					    this->flags_.entity_category = static_cast<uint8_t>(entity_category);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Get/set this entity's icon
 | 
					  // Get/set this entity's icon
 | 
				
			||||||
  std::string get_icon() const;
 | 
					  std::string get_icon() const;
 | 
				
			||||||
  void set_icon(const char *icon);
 | 
					  void set_icon(const char *icon);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Check if this entity has state
 | 
				
			||||||
 | 
					  bool has_state() const { return this->flags_.has_state; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Set has_state - for components that need to manually set this
 | 
				
			||||||
 | 
					  void set_has_state(bool state) { this->flags_.has_state = state; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  /// The hash_base() function has been deprecated. It is kept in this
 | 
					  /// The hash_base() function has been deprecated. It is kept in this
 | 
				
			||||||
  /// class for now, to prevent external components from not compiling.
 | 
					  /// class for now, to prevent external components from not compiling.
 | 
				
			||||||
@@ -59,11 +67,16 @@ class EntityBase {
 | 
				
			|||||||
  const char *object_id_c_str_{nullptr};
 | 
					  const char *object_id_c_str_{nullptr};
 | 
				
			||||||
  const char *icon_c_str_{nullptr};
 | 
					  const char *icon_c_str_{nullptr};
 | 
				
			||||||
  uint32_t object_id_hash_{};
 | 
					  uint32_t object_id_hash_{};
 | 
				
			||||||
  bool has_own_name_{false};
 | 
					
 | 
				
			||||||
  bool internal_{false};
 | 
					  // Bit-packed flags to save memory (1 byte instead of 5)
 | 
				
			||||||
  bool disabled_by_default_{false};
 | 
					  struct EntityFlags {
 | 
				
			||||||
  EntityCategory entity_category_{ENTITY_CATEGORY_NONE};
 | 
					    uint8_t has_own_name : 1;
 | 
				
			||||||
  bool has_state_{};
 | 
					    uint8_t internal : 1;
 | 
				
			||||||
 | 
					    uint8_t disabled_by_default : 1;
 | 
				
			||||||
 | 
					    uint8_t has_state : 1;
 | 
				
			||||||
 | 
					    uint8_t entity_category : 2;  // Supports up to 4 categories
 | 
				
			||||||
 | 
					    uint8_t reserved : 2;         // Reserved for future use
 | 
				
			||||||
 | 
					  } flags_{};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EntityBase_DeviceClass {  // NOLINT(readability-identifier-naming)
 | 
					class EntityBase_DeviceClass {  // NOLINT(readability-identifier-naming)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										108
									
								
								tests/integration/fixtures/host_mode_entity_fields.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								tests/integration/fixtures/host_mode_entity_fields.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,108 @@
 | 
				
			|||||||
 | 
					esphome:
 | 
				
			||||||
 | 
					  name: host-test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					host:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					api:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logger:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Test various entity types with different flag combinations
 | 
				
			||||||
 | 
					sensor:
 | 
				
			||||||
 | 
					  - platform: template
 | 
				
			||||||
 | 
					    name: "Test Normal Sensor"
 | 
				
			||||||
 | 
					    id: normal_sensor
 | 
				
			||||||
 | 
					    update_interval: 1s
 | 
				
			||||||
 | 
					    lambda: |-
 | 
				
			||||||
 | 
					      return 42.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - platform: template
 | 
				
			||||||
 | 
					    name: "Test Internal Sensor"
 | 
				
			||||||
 | 
					    id: internal_sensor
 | 
				
			||||||
 | 
					    internal: true
 | 
				
			||||||
 | 
					    update_interval: 1s
 | 
				
			||||||
 | 
					    lambda: |-
 | 
				
			||||||
 | 
					      return 43.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - platform: template
 | 
				
			||||||
 | 
					    name: "Test Disabled Sensor"
 | 
				
			||||||
 | 
					    id: disabled_sensor
 | 
				
			||||||
 | 
					    disabled_by_default: true
 | 
				
			||||||
 | 
					    update_interval: 1s
 | 
				
			||||||
 | 
					    lambda: |-
 | 
				
			||||||
 | 
					      return 44.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - platform: template
 | 
				
			||||||
 | 
					    name: "Test Mixed Flags Sensor"
 | 
				
			||||||
 | 
					    id: mixed_flags_sensor
 | 
				
			||||||
 | 
					    internal: true
 | 
				
			||||||
 | 
					    entity_category: diagnostic
 | 
				
			||||||
 | 
					    update_interval: 1s
 | 
				
			||||||
 | 
					    lambda: |-
 | 
				
			||||||
 | 
					      return 45.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - platform: template
 | 
				
			||||||
 | 
					    name: "Test Diagnostic Sensor"
 | 
				
			||||||
 | 
					    id: diagnostic_sensor
 | 
				
			||||||
 | 
					    entity_category: diagnostic
 | 
				
			||||||
 | 
					    update_interval: 1s
 | 
				
			||||||
 | 
					    lambda: |-
 | 
				
			||||||
 | 
					      return 46.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - platform: template
 | 
				
			||||||
 | 
					    name: "Test All Flags Sensor"
 | 
				
			||||||
 | 
					    id: all_flags_sensor
 | 
				
			||||||
 | 
					    internal: true
 | 
				
			||||||
 | 
					    disabled_by_default: true
 | 
				
			||||||
 | 
					    entity_category: diagnostic
 | 
				
			||||||
 | 
					    update_interval: 1s
 | 
				
			||||||
 | 
					    lambda: |-
 | 
				
			||||||
 | 
					      return 47.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Also test other entity types to ensure bit-packing works across all
 | 
				
			||||||
 | 
					binary_sensor:
 | 
				
			||||||
 | 
					  - platform: template
 | 
				
			||||||
 | 
					    name: "Test Binary Sensor"
 | 
				
			||||||
 | 
					    entity_category: config
 | 
				
			||||||
 | 
					    lambda: |-
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					text_sensor:
 | 
				
			||||||
 | 
					  - platform: template
 | 
				
			||||||
 | 
					    name: "Test Text Sensor"
 | 
				
			||||||
 | 
					    disabled_by_default: true
 | 
				
			||||||
 | 
					    lambda: |-
 | 
				
			||||||
 | 
					      return {"Hello"};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					number:
 | 
				
			||||||
 | 
					  - platform: template
 | 
				
			||||||
 | 
					    name: "Test Number"
 | 
				
			||||||
 | 
					    initial_value: 50
 | 
				
			||||||
 | 
					    min_value: 0
 | 
				
			||||||
 | 
					    max_value: 100
 | 
				
			||||||
 | 
					    step: 1
 | 
				
			||||||
 | 
					    optimistic: true
 | 
				
			||||||
 | 
					    entity_category: diagnostic
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					select:
 | 
				
			||||||
 | 
					  - platform: template
 | 
				
			||||||
 | 
					    name: "Test Select"
 | 
				
			||||||
 | 
					    options:
 | 
				
			||||||
 | 
					      - "Option 1"
 | 
				
			||||||
 | 
					      - "Option 2"
 | 
				
			||||||
 | 
					    initial_option: "Option 1"
 | 
				
			||||||
 | 
					    optimistic: true
 | 
				
			||||||
 | 
					    internal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					switch:
 | 
				
			||||||
 | 
					  - platform: template
 | 
				
			||||||
 | 
					    name: "Test Switch"
 | 
				
			||||||
 | 
					    optimistic: true
 | 
				
			||||||
 | 
					    disabled_by_default: true
 | 
				
			||||||
 | 
					    entity_category: config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					button:
 | 
				
			||||||
 | 
					  - platform: template
 | 
				
			||||||
 | 
					    name: "Test Button"
 | 
				
			||||||
 | 
					    on_press:
 | 
				
			||||||
 | 
					      - logger.log: "Button pressed"
 | 
				
			||||||
							
								
								
									
										93
									
								
								tests/integration/test_host_mode_entity_fields.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								tests/integration/test_host_mode_entity_fields.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
				
			|||||||
 | 
					"""Integration test for entity bit-packed fields."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from __future__ import annotations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import asyncio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aioesphomeapi import EntityCategory, EntityState
 | 
				
			||||||
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .types import APIClientConnectedFactory, RunCompiledFunction
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.mark.asyncio
 | 
				
			||||||
 | 
					async def test_host_mode_entity_fields(
 | 
				
			||||||
 | 
					    yaml_config: str,
 | 
				
			||||||
 | 
					    run_compiled: RunCompiledFunction,
 | 
				
			||||||
 | 
					    api_client_connected: APIClientConnectedFactory,
 | 
				
			||||||
 | 
					) -> None:
 | 
				
			||||||
 | 
					    """Test entity bit-packed fields work correctly with all possible values."""
 | 
				
			||||||
 | 
					    # Write, compile and run the ESPHome device, then connect to API
 | 
				
			||||||
 | 
					    async with run_compiled(yaml_config), api_client_connected() as client:
 | 
				
			||||||
 | 
					        # Get all entities
 | 
				
			||||||
 | 
					        entities = await client.list_entities_services()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Create a map of entity names to entity info
 | 
				
			||||||
 | 
					        entity_map = {}
 | 
				
			||||||
 | 
					        for entity in entities[0]:
 | 
				
			||||||
 | 
					            if hasattr(entity, "name"):
 | 
				
			||||||
 | 
					                entity_map[entity.name] = entity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Test entities that should be visible via API (non-internal)
 | 
				
			||||||
 | 
					        visible_test_cases = [
 | 
				
			||||||
 | 
					            # (entity_name, expected_disabled_by_default, expected_entity_category)
 | 
				
			||||||
 | 
					            ("Test Normal Sensor", False, EntityCategory.NONE),
 | 
				
			||||||
 | 
					            ("Test Disabled Sensor", True, EntityCategory.NONE),
 | 
				
			||||||
 | 
					            ("Test Diagnostic Sensor", False, EntityCategory.DIAGNOSTIC),
 | 
				
			||||||
 | 
					            ("Test Switch", True, EntityCategory.CONFIG),
 | 
				
			||||||
 | 
					            ("Test Binary Sensor", False, EntityCategory.CONFIG),
 | 
				
			||||||
 | 
					            ("Test Number", False, EntityCategory.DIAGNOSTIC),
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Test entities that should NOT be visible via API (internal)
 | 
				
			||||||
 | 
					        internal_entities = [
 | 
				
			||||||
 | 
					            "Test Internal Sensor",
 | 
				
			||||||
 | 
					            "Test Mixed Flags Sensor",
 | 
				
			||||||
 | 
					            "Test All Flags Sensor",
 | 
				
			||||||
 | 
					            "Test Select",
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Verify visible entities
 | 
				
			||||||
 | 
					        for entity_name, expected_disabled, expected_category in visible_test_cases:
 | 
				
			||||||
 | 
					            assert entity_name in entity_map, (
 | 
				
			||||||
 | 
					                f"Entity '{entity_name}' not found - it should be visible via API"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            entity = entity_map[entity_name]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Check disabled_by_default flag
 | 
				
			||||||
 | 
					            assert entity.disabled_by_default == expected_disabled, (
 | 
				
			||||||
 | 
					                f"{entity_name}: disabled_by_default flag mismatch - "
 | 
				
			||||||
 | 
					                f"expected {expected_disabled}, got {entity.disabled_by_default}"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Check entity_category
 | 
				
			||||||
 | 
					            assert entity.entity_category == expected_category, (
 | 
				
			||||||
 | 
					                f"{entity_name}: entity_category mismatch - "
 | 
				
			||||||
 | 
					                f"expected {expected_category}, got {entity.entity_category}"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Verify internal entities are NOT visible
 | 
				
			||||||
 | 
					        for entity_name in internal_entities:
 | 
				
			||||||
 | 
					            assert entity_name not in entity_map, (
 | 
				
			||||||
 | 
					                f"Entity '{entity_name}' found in API response - "
 | 
				
			||||||
 | 
					                f"internal entities should not be exposed via API"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Subscribe to states to verify has_state flag works
 | 
				
			||||||
 | 
					        states: dict[int, EntityState] = {}
 | 
				
			||||||
 | 
					        state_received = asyncio.Event()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def on_state(state: EntityState) -> None:
 | 
				
			||||||
 | 
					            states[state.key] = state
 | 
				
			||||||
 | 
					            state_received.set()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        client.subscribe_states(on_state)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Wait for at least one state
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            await asyncio.wait_for(state_received.wait(), timeout=5.0)
 | 
				
			||||||
 | 
					        except asyncio.TimeoutError:
 | 
				
			||||||
 | 
					            pytest.fail("No states received within 5 seconds")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Verify we received states (which means has_state flag is working)
 | 
				
			||||||
 | 
					        assert len(states) > 0, "No states received - has_state flag may not be working"
 | 
				
			||||||
		Reference in New Issue
	
	Block a user