mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-27 13:13:50 +00:00 
			
		
		
		
	Merge branch 'dev' into update_libsodium
This commit is contained in:
		| @@ -4,7 +4,7 @@ | ||||
| repos: | ||||
|   - repo: https://github.com/astral-sh/ruff-pre-commit | ||||
|     # Ruff version. | ||||
|     rev: v0.12.0 | ||||
|     rev: v0.12.1 | ||||
|     hooks: | ||||
|       # Run the linter. | ||||
|       - id: ruff | ||||
|   | ||||
| @@ -332,6 +332,7 @@ esphome/components/pca6416a/* @Mat931 | ||||
| esphome/components/pca9554/* @clydebarrow @hwstar | ||||
| esphome/components/pcf85063/* @brogon | ||||
| esphome/components/pcf8563/* @KoenBreeman | ||||
| esphome/components/pi4ioe5v6408/* @jesserockz | ||||
| esphome/components/pid/* @OttoWinter | ||||
| esphome/components/pipsolar/* @andreashergert1984 | ||||
| esphome/components/pm1006/* @habbie | ||||
|   | ||||
| @@ -28,19 +28,24 @@ static const adc_atten_t ADC_ATTEN_DB_12_COMPAT = ADC_ATTEN_DB_11; | ||||
| #endif | ||||
| #endif  // USE_ESP32 | ||||
|  | ||||
| enum class SamplingMode : uint8_t { AVG = 0, MIN = 1, MAX = 2 }; | ||||
| enum class SamplingMode : uint8_t { | ||||
|   AVG = 0, | ||||
|   MIN = 1, | ||||
|   MAX = 2, | ||||
| }; | ||||
|  | ||||
| const LogString *sampling_mode_to_str(SamplingMode mode); | ||||
|  | ||||
| class Aggregator { | ||||
|  public: | ||||
|   Aggregator(SamplingMode mode); | ||||
|   void add_sample(uint32_t value); | ||||
|   uint32_t aggregate(); | ||||
|   Aggregator(SamplingMode mode); | ||||
|  | ||||
|  protected: | ||||
|   SamplingMode mode_{SamplingMode::AVG}; | ||||
|   uint32_t aggr_{0}; | ||||
|   uint32_t samples_{0}; | ||||
|   SamplingMode mode_{SamplingMode::AVG}; | ||||
| }; | ||||
|  | ||||
| class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler { | ||||
| @@ -81,9 +86,9 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage | ||||
| #endif  // USE_RP2040 | ||||
|  | ||||
|  protected: | ||||
|   InternalGPIOPin *pin_; | ||||
|   bool output_raw_{false}; | ||||
|   uint8_t sample_count_{1}; | ||||
|   bool output_raw_{false}; | ||||
|   InternalGPIOPin *pin_; | ||||
|   SamplingMode sampling_mode_{SamplingMode::AVG}; | ||||
|  | ||||
| #ifdef USE_RP2040 | ||||
|   | ||||
| @@ -61,7 +61,7 @@ uint32_t Aggregator::aggregate() { | ||||
|  | ||||
| void ADCSensor::update() { | ||||
|   float value_v = this->sample(); | ||||
|   ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v); | ||||
|   ESP_LOGV(TAG, "'%s': Voltage=%.4fV", this->get_name().c_str(), value_v); | ||||
|   this->publish_state(value_v); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -55,32 +55,40 @@ void ADCSensor::setup() { | ||||
| } | ||||
|  | ||||
| void ADCSensor::dump_config() { | ||||
|   static const char *const ATTEN_AUTO_STR = "auto"; | ||||
|   static const char *const ATTEN_0DB_STR = "0 db"; | ||||
|   static const char *const ATTEN_2_5DB_STR = "2.5 db"; | ||||
|   static const char *const ATTEN_6DB_STR = "6 db"; | ||||
|   static const char *const ATTEN_12DB_STR = "12 db"; | ||||
|   const char *atten_str = ATTEN_AUTO_STR; | ||||
|  | ||||
|   LOG_SENSOR("", "ADC Sensor", this); | ||||
|   LOG_PIN("  Pin: ", this->pin_); | ||||
|   if (this->autorange_) { | ||||
|     ESP_LOGCONFIG(TAG, "  Attenuation: auto"); | ||||
|   } else { | ||||
|  | ||||
|   if (!this->autorange_) { | ||||
|     switch (this->attenuation_) { | ||||
|       case ADC_ATTEN_DB_0: | ||||
|         ESP_LOGCONFIG(TAG, "  Attenuation: 0db"); | ||||
|         atten_str = ATTEN_0DB_STR; | ||||
|         break; | ||||
|       case ADC_ATTEN_DB_2_5: | ||||
|         ESP_LOGCONFIG(TAG, "  Attenuation: 2.5db"); | ||||
|         atten_str = ATTEN_2_5DB_STR; | ||||
|         break; | ||||
|       case ADC_ATTEN_DB_6: | ||||
|         ESP_LOGCONFIG(TAG, "  Attenuation: 6db"); | ||||
|         atten_str = ATTEN_6DB_STR; | ||||
|         break; | ||||
|       case ADC_ATTEN_DB_12_COMPAT: | ||||
|         ESP_LOGCONFIG(TAG, "  Attenuation: 12db"); | ||||
|         atten_str = ATTEN_12DB_STR; | ||||
|         break; | ||||
|       default:  // This is to satisfy the unused ADC_ATTEN_MAX | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   ESP_LOGCONFIG(TAG, | ||||
|                 "  Attenuation: %s\n" | ||||
|                 "  Samples: %i\n" | ||||
|                 "  Sampling mode: %s", | ||||
|                 this->sample_count_, LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_))); | ||||
|                 atten_str, this->sample_count_, LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_))); | ||||
|   LOG_UPDATE_INTERVAL(this); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -85,8 +85,6 @@ class ADE7880 : public i2c::I2CDevice, public PollingComponent { | ||||
|  | ||||
|   void dump_config() override; | ||||
|  | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|  protected: | ||||
|   ADE7880Store store_{}; | ||||
|   InternalGPIOPin *irq0_pin_{nullptr}; | ||||
|   | ||||
| @@ -49,7 +49,6 @@ class ADS1115Component : public Component, public i2c::I2CDevice { | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   /// HARDWARE_LATE setup priority | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void set_continuous_mode(bool continuous_mode) { continuous_mode_ = continuous_mode; } | ||||
|  | ||||
|   /// Helper method to request a measurement from a sensor. | ||||
|   | ||||
| @@ -34,7 +34,6 @@ class ADS1118 : public Component, | ||||
|   ADS1118() = default; | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   /// Helper method to request a measurement from a sensor. | ||||
|   float request_measurement(ADS1118Multiplexer multiplexer, ADS1118Gain gain, bool temperature_mode); | ||||
|  | ||||
|   | ||||
| @@ -31,8 +31,6 @@ class AGS10Component : public PollingComponent, public i2c::I2CDevice { | ||||
|  | ||||
|   void dump_config() override; | ||||
|  | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   /** | ||||
|    * Modifies target address of AGS10. | ||||
|    * | ||||
|   | ||||
| @@ -66,7 +66,6 @@ class AIC3204 : public audio_dac::AudioDac, public Component, public i2c::I2CDev | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   bool set_mute_off() override; | ||||
|   bool set_mute_on() override; | ||||
|   | ||||
| @@ -41,7 +41,6 @@ class Alpha3 : public esphome::ble_client::BLEClientNode, public PollingComponen | ||||
|   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||
|                            esp_ble_gattc_cb_param_t *param) override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void set_flow_sensor(sensor::Sensor *sensor) { this->flow_sensor_ = sensor; } | ||||
|   void set_head_sensor(sensor::Sensor *sensor) { this->head_sensor_ = sensor; } | ||||
|   void set_power_sensor(sensor::Sensor *sensor) { this->power_sensor_ = sensor; } | ||||
|   | ||||
| @@ -22,7 +22,6 @@ class Am43Component : public cover::Cover, public esphome::ble_client::BLEClient | ||||
|   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||
|                            esp_ble_gattc_cb_param_t *param) override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   cover::CoverTraits get_traits() override; | ||||
|   void set_pin(uint16_t pin) { this->pin_ = pin; } | ||||
|   void set_invert_position(bool invert_position) { this->invert_position_ = invert_position; } | ||||
|   | ||||
| @@ -22,7 +22,6 @@ class Am43 : public esphome::ble_client::BLEClientNode, public PollingComponent | ||||
|   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||
|                            esp_ble_gattc_cb_param_t *param) override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void set_battery(sensor::Sensor *battery) { battery_ = battery; } | ||||
|   void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; } | ||||
|  | ||||
|   | ||||
| @@ -12,8 +12,6 @@ class AnalogThresholdBinarySensor : public Component, public binary_sensor::Bina | ||||
|   void dump_config() override; | ||||
|   void setup() override; | ||||
|  | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   void set_sensor(sensor::Sensor *analog_sensor); | ||||
|   template<typename T> void set_upper_threshold(T upper_threshold) { this->upper_threshold_ = upper_threshold; } | ||||
|   template<typename T> void set_lower_threshold(T lower_threshold) { this->lower_threshold_ = lower_threshold; } | ||||
|   | ||||
| @@ -26,7 +26,6 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode | ||||
|   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||
|                            esp_ble_gattc_cb_param_t *param) override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   climate::ClimateTraits traits() override { | ||||
|     auto traits = climate::ClimateTraits(); | ||||
|     traits.set_supports_current_temperature(true); | ||||
|   | ||||
| @@ -110,9 +110,10 @@ CONFIG_SCHEMA = cv.All( | ||||
|             ): ACTIONS_SCHEMA, | ||||
|             cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA, | ||||
|             cv.Optional(CONF_ENCRYPTION): _encryption_schema, | ||||
|             cv.Optional( | ||||
|                 CONF_BATCH_DELAY, default="100ms" | ||||
|             ): cv.positive_time_period_milliseconds, | ||||
|             cv.Optional(CONF_BATCH_DELAY, default="100ms"): cv.All( | ||||
|                 cv.positive_time_period_milliseconds, | ||||
|                 cv.Range(max=cv.TimePeriod(milliseconds=65535)), | ||||
|             ), | ||||
|             cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation( | ||||
|                 single=True | ||||
|             ), | ||||
| @@ -135,23 +136,26 @@ async def to_code(config): | ||||
|     cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) | ||||
|     cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY])) | ||||
|  | ||||
|     for conf in config.get(CONF_ACTIONS, []): | ||||
|         template_args = [] | ||||
|         func_args = [] | ||||
|         service_arg_names = [] | ||||
|         for name, var_ in conf[CONF_VARIABLES].items(): | ||||
|             native = SERVICE_ARG_NATIVE_TYPES[var_] | ||||
|             template_args.append(native) | ||||
|             func_args.append((native, name)) | ||||
|             service_arg_names.append(name) | ||||
|         templ = cg.TemplateArguments(*template_args) | ||||
|         trigger = cg.new_Pvariable( | ||||
|             conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names | ||||
|         ) | ||||
|         cg.add(var.register_user_service(trigger)) | ||||
|         await automation.build_automation(trigger, func_args, conf) | ||||
|     if actions := config.get(CONF_ACTIONS, []): | ||||
|         cg.add_define("USE_API_YAML_SERVICES") | ||||
|         for conf in actions: | ||||
|             template_args = [] | ||||
|             func_args = [] | ||||
|             service_arg_names = [] | ||||
|             for name, var_ in conf[CONF_VARIABLES].items(): | ||||
|                 native = SERVICE_ARG_NATIVE_TYPES[var_] | ||||
|                 template_args.append(native) | ||||
|                 func_args.append((native, name)) | ||||
|                 service_arg_names.append(name) | ||||
|             templ = cg.TemplateArguments(*template_args) | ||||
|             trigger = cg.new_Pvariable( | ||||
|                 conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names | ||||
|             ) | ||||
|             cg.add(var.register_user_service(trigger)) | ||||
|             await automation.build_automation(trigger, func_args, conf) | ||||
|  | ||||
|     if CONF_ON_CLIENT_CONNECTED in config: | ||||
|         cg.add_define("USE_API_CLIENT_CONNECTED_TRIGGER") | ||||
|         await automation.build_automation( | ||||
|             var.get_client_connected_trigger(), | ||||
|             [(cg.std_string, "client_info"), (cg.std_string, "client_address")], | ||||
| @@ -159,6 +163,7 @@ async def to_code(config): | ||||
|         ) | ||||
|  | ||||
|     if CONF_ON_CLIENT_DISCONNECTED in config: | ||||
|         cg.add_define("USE_API_CLIENT_DISCONNECTED_TRIGGER") | ||||
|         await automation.build_automation( | ||||
|             var.get_client_disconnected_trigger(), | ||||
|             [(cg.std_string, "client_info"), (cg.std_string, "client_address")], | ||||
|   | ||||
| @@ -65,10 +65,6 @@ uint32_t APIConnection::get_batch_delay_ms_() const { return this->parent_->get_ | ||||
| void APIConnection::start() { | ||||
|   this->last_traffic_ = App.get_loop_component_start_time(); | ||||
|  | ||||
|   // Set next_ping_retry_ to prevent immediate ping | ||||
|   // This ensures the first ping happens after the keepalive period | ||||
|   this->next_ping_retry_ = this->last_traffic_ + KEEPALIVE_TIMEOUT_MS; | ||||
|  | ||||
|   APIError err = this->helper_->init(); | ||||
|   if (err != APIError::OK) { | ||||
|     on_fatal_error(); | ||||
| @@ -94,11 +90,24 @@ APIConnection::~APIConnection() { | ||||
| #endif | ||||
| } | ||||
|  | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void APIConnection::log_batch_item_(const DeferredBatch::BatchItem &item) { | ||||
|   // Set log-only mode | ||||
|   this->flags_.log_only_mode = true; | ||||
|  | ||||
|   // Call the creator - it will create the message and log it via encode_message_to_buffer | ||||
|   item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true, item.message_type); | ||||
|  | ||||
|   // Clear log-only mode | ||||
|   this->flags_.log_only_mode = false; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| void APIConnection::loop() { | ||||
|   if (this->next_close_) { | ||||
|   if (this->flags_.next_close) { | ||||
|     // requested a disconnect | ||||
|     this->helper_->close(); | ||||
|     this->remove_ = true; | ||||
|     this->flags_.remove = true; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
| @@ -139,15 +148,14 @@ void APIConnection::loop() { | ||||
|         } else { | ||||
|           this->read_message(0, buffer.type, nullptr); | ||||
|         } | ||||
|         if (this->remove_) | ||||
|         if (this->flags_.remove) | ||||
|           return; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Process deferred batch if scheduled | ||||
|   if (this->deferred_batch_.batch_scheduled && | ||||
|       now - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) { | ||||
|   if (this->flags_.batch_scheduled && now - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) { | ||||
|     this->process_batch_(); | ||||
|   } | ||||
|  | ||||
| @@ -157,26 +165,21 @@ void APIConnection::loop() { | ||||
|     this->initial_state_iterator_.advance(); | ||||
|   } | ||||
|  | ||||
|   if (this->sent_ping_) { | ||||
|   if (this->flags_.sent_ping) { | ||||
|     // Disconnect if not responded within 2.5*keepalive | ||||
|     if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) { | ||||
|       on_fatal_error(); | ||||
|       ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str()); | ||||
|     } | ||||
|   } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && now > this->next_ping_retry_) { | ||||
|   } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS) { | ||||
|     ESP_LOGVV(TAG, "Sending keepalive PING"); | ||||
|     this->sent_ping_ = this->send_message(PingRequest()); | ||||
|     if (!this->sent_ping_) { | ||||
|       this->next_ping_retry_ = now + PING_RETRY_INTERVAL; | ||||
|       this->ping_retries_++; | ||||
|       if (this->ping_retries_ >= MAX_PING_RETRIES) { | ||||
|         on_fatal_error(); | ||||
|         ESP_LOGE(TAG, "%s: Ping failed %u times", this->get_client_combined_info().c_str(), this->ping_retries_); | ||||
|       } else if (this->ping_retries_ >= 10) { | ||||
|         ESP_LOGW(TAG, "%s: Ping retry %u", this->get_client_combined_info().c_str(), this->ping_retries_); | ||||
|       } else { | ||||
|         ESP_LOGD(TAG, "%s: Ping retry %u", this->get_client_combined_info().c_str(), this->ping_retries_); | ||||
|       } | ||||
|     this->flags_.sent_ping = this->send_message(PingRequest()); | ||||
|     if (!this->flags_.sent_ping) { | ||||
|       // If we can't send the ping request directly (tx_buffer full), | ||||
|       // schedule it at the front of the batch so it will be sent with priority | ||||
|       ESP_LOGW(TAG, "Buffer full, ping queued"); | ||||
|       this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE); | ||||
|       this->flags_.sent_ping = true;  // Mark as sent to avoid scheduling multiple pings | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -236,19 +239,27 @@ DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) { | ||||
|   // don't close yet, we still need to send the disconnect response | ||||
|   // close will happen on next loop | ||||
|   ESP_LOGD(TAG, "%s disconnected", this->get_client_combined_info().c_str()); | ||||
|   this->next_close_ = true; | ||||
|   this->flags_.next_close = true; | ||||
|   DisconnectResponse resp; | ||||
|   return resp; | ||||
| } | ||||
| void APIConnection::on_disconnect_response(const DisconnectResponse &value) { | ||||
|   this->helper_->close(); | ||||
|   this->remove_ = true; | ||||
|   this->flags_.remove = true; | ||||
| } | ||||
|  | ||||
| // Encodes a message to the buffer and returns the total number of bytes used, | ||||
| // including header and footer overhead. Returns 0 if the message doesn't fit. | ||||
| uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn, | ||||
|                                                  uint32_t remaining_size, bool is_single) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   // If in log-only mode, just log and return | ||||
|   if (conn->flags_.log_only_mode) { | ||||
|     conn->log_send_message_(msg.message_name(), msg.dump()); | ||||
|     return 1;  // Return non-zero to indicate "success" for logging | ||||
|   } | ||||
| #endif | ||||
|  | ||||
|   // Calculate size | ||||
|   uint32_t calculated_size = 0; | ||||
|   msg.calculate_size(calculated_size); | ||||
| @@ -276,11 +287,6 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t mes | ||||
|   // Encode directly into buffer | ||||
|   msg.encode(buffer); | ||||
|  | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   // Log the message for VV debugging | ||||
|   conn->log_send_message_(msg.message_name(), msg.dump()); | ||||
| #endif | ||||
|  | ||||
|   // Calculate actual encoded size (not including header that was already added) | ||||
|   size_t actual_payload_size = shared_buf.size() - size_before_encode; | ||||
|  | ||||
| @@ -297,10 +303,6 @@ bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary | ||||
|   return this->schedule_message_(binary_sensor, &APIConnection::try_send_binary_sensor_state, | ||||
|                                  BinarySensorStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor) { | ||||
|   this->schedule_message_(binary_sensor, &APIConnection::try_send_binary_sensor_info, | ||||
|                           ListEntitiesBinarySensorResponse::MESSAGE_TYPE); | ||||
| } | ||||
|  | ||||
| uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                                      bool is_single) { | ||||
| @@ -328,9 +330,6 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne | ||||
| bool APIConnection::send_cover_state(cover::Cover *cover) { | ||||
|   return this->schedule_message_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_cover_info(cover::Cover *cover) { | ||||
|   this->schedule_message_(cover, &APIConnection::try_send_cover_info, ListEntitiesCoverResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                              bool is_single) { | ||||
|   auto *cover = static_cast<cover::Cover *>(entity); | ||||
| @@ -392,9 +391,6 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) { | ||||
| bool APIConnection::send_fan_state(fan::Fan *fan) { | ||||
|   return this->schedule_message_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_fan_info(fan::Fan *fan) { | ||||
|   this->schedule_message_(fan, &APIConnection::try_send_fan_info, ListEntitiesFanResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                            bool is_single) { | ||||
|   auto *fan = static_cast<fan::Fan *>(entity); | ||||
| @@ -454,9 +450,6 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { | ||||
| bool APIConnection::send_light_state(light::LightState *light) { | ||||
|   return this->schedule_message_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_light_info(light::LightState *light) { | ||||
|   this->schedule_message_(light, &APIConnection::try_send_light_info, ListEntitiesLightResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                              bool is_single) { | ||||
|   auto *light = static_cast<light::LightState *>(entity); | ||||
| @@ -549,9 +542,6 @@ void APIConnection::light_command(const LightCommandRequest &msg) { | ||||
| bool APIConnection::send_sensor_state(sensor::Sensor *sensor) { | ||||
|   return this->schedule_message_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_sensor_info(sensor::Sensor *sensor) { | ||||
|   this->schedule_message_(sensor, &APIConnection::try_send_sensor_info, ListEntitiesSensorResponse::MESSAGE_TYPE); | ||||
| } | ||||
|  | ||||
| uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                               bool is_single) { | ||||
| @@ -584,9 +574,6 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection * | ||||
| bool APIConnection::send_switch_state(switch_::Switch *a_switch) { | ||||
|   return this->schedule_message_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_switch_info(switch_::Switch *a_switch) { | ||||
|   this->schedule_message_(a_switch, &APIConnection::try_send_switch_info, ListEntitiesSwitchResponse::MESSAGE_TYPE); | ||||
| } | ||||
|  | ||||
| uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                               bool is_single) { | ||||
| @@ -625,10 +612,6 @@ bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) | ||||
|   return this->schedule_message_(text_sensor, &APIConnection::try_send_text_sensor_state, | ||||
|                                  TextSensorStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) { | ||||
|   this->schedule_message_(text_sensor, &APIConnection::try_send_text_sensor_info, | ||||
|                           ListEntitiesTextSensorResponse::MESSAGE_TYPE); | ||||
| } | ||||
|  | ||||
| uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                                    bool is_single) { | ||||
| @@ -689,9 +672,6 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection | ||||
|     resp.target_humidity = climate->target_humidity; | ||||
|   return encode_message_to_buffer(resp, ClimateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||
| } | ||||
| void APIConnection::send_climate_info(climate::Climate *climate) { | ||||
|   this->schedule_message_(climate, &APIConnection::try_send_climate_info, ListEntitiesClimateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                               bool is_single) { | ||||
|   auto *climate = static_cast<climate::Climate *>(entity); | ||||
| @@ -759,9 +739,6 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) { | ||||
| bool APIConnection::send_number_state(number::Number *number) { | ||||
|   return this->schedule_message_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_number_info(number::Number *number) { | ||||
|   this->schedule_message_(number, &APIConnection::try_send_number_info, ListEntitiesNumberResponse::MESSAGE_TYPE); | ||||
| } | ||||
|  | ||||
| uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                               bool is_single) { | ||||
| @@ -813,9 +790,6 @@ uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *c | ||||
|   fill_entity_state_base(date, resp); | ||||
|   return encode_message_to_buffer(resp, DateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||
| } | ||||
| void APIConnection::send_date_info(datetime::DateEntity *date) { | ||||
|   this->schedule_message_(date, &APIConnection::try_send_date_info, ListEntitiesDateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_date_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                            bool is_single) { | ||||
|   auto *date = static_cast<datetime::DateEntity *>(entity); | ||||
| @@ -850,9 +824,6 @@ uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *c | ||||
|   fill_entity_state_base(time, resp); | ||||
|   return encode_message_to_buffer(resp, TimeStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||
| } | ||||
| void APIConnection::send_time_info(datetime::TimeEntity *time) { | ||||
|   this->schedule_message_(time, &APIConnection::try_send_time_info, ListEntitiesTimeResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_time_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                            bool is_single) { | ||||
|   auto *time = static_cast<datetime::TimeEntity *>(entity); | ||||
| @@ -889,9 +860,6 @@ uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnectio | ||||
|   fill_entity_state_base(datetime, resp); | ||||
|   return encode_message_to_buffer(resp, DateTimeStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||
| } | ||||
| void APIConnection::send_datetime_info(datetime::DateTimeEntity *datetime) { | ||||
|   this->schedule_message_(datetime, &APIConnection::try_send_datetime_info, ListEntitiesDateTimeResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_datetime_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                                bool is_single) { | ||||
|   auto *datetime = static_cast<datetime::DateTimeEntity *>(entity); | ||||
| @@ -915,9 +883,6 @@ void APIConnection::datetime_command(const DateTimeCommandRequest &msg) { | ||||
| bool APIConnection::send_text_state(text::Text *text) { | ||||
|   return this->schedule_message_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_text_info(text::Text *text) { | ||||
|   this->schedule_message_(text, &APIConnection::try_send_text_info, ListEntitiesTextResponse::MESSAGE_TYPE); | ||||
| } | ||||
|  | ||||
| uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                             bool is_single) { | ||||
| @@ -956,9 +921,6 @@ void APIConnection::text_command(const TextCommandRequest &msg) { | ||||
| bool APIConnection::send_select_state(select::Select *select) { | ||||
|   return this->schedule_message_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_select_info(select::Select *select) { | ||||
|   this->schedule_message_(select, &APIConnection::try_send_select_info, ListEntitiesSelectResponse::MESSAGE_TYPE); | ||||
| } | ||||
|  | ||||
| uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                               bool is_single) { | ||||
| @@ -992,9 +954,6 @@ void APIConnection::select_command(const SelectCommandRequest &msg) { | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_BUTTON | ||||
| void esphome::api::APIConnection::send_button_info(button::Button *button) { | ||||
|   this->schedule_message_(button, &APIConnection::try_send_button_info, ListEntitiesButtonResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                              bool is_single) { | ||||
|   auto *button = static_cast<button::Button *>(entity); | ||||
| @@ -1017,9 +976,6 @@ void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg | ||||
| bool APIConnection::send_lock_state(lock::Lock *a_lock) { | ||||
|   return this->schedule_message_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_lock_info(lock::Lock *a_lock) { | ||||
|   this->schedule_message_(a_lock, &APIConnection::try_send_lock_info, ListEntitiesLockResponse::MESSAGE_TYPE); | ||||
| } | ||||
|  | ||||
| uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                             bool is_single) { | ||||
| @@ -1073,9 +1029,6 @@ uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection * | ||||
|   fill_entity_state_base(valve, resp); | ||||
|   return encode_message_to_buffer(resp, ValveStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||
| } | ||||
| void APIConnection::send_valve_info(valve::Valve *valve) { | ||||
|   this->schedule_message_(valve, &APIConnection::try_send_valve_info, ListEntitiesValveResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                             bool is_single) { | ||||
|   auto *valve = static_cast<valve::Valve *>(entity); | ||||
| @@ -1121,10 +1074,6 @@ uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConne | ||||
|   fill_entity_state_base(media_player, resp); | ||||
|   return encode_message_to_buffer(resp, MediaPlayerStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||
| } | ||||
| void APIConnection::send_media_player_info(media_player::MediaPlayer *media_player) { | ||||
|   this->schedule_message_(media_player, &APIConnection::try_send_media_player_info, | ||||
|                           ListEntitiesMediaPlayerResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                                    bool is_single) { | ||||
|   auto *media_player = static_cast<media_player::MediaPlayer *>(entity); | ||||
| @@ -1168,7 +1117,7 @@ void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { | ||||
|  | ||||
| #ifdef USE_ESP32_CAMERA | ||||
| void APIConnection::set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) { | ||||
|   if (!this->state_subscription_) | ||||
|   if (!this->flags_.state_subscription) | ||||
|     return; | ||||
|   if (this->image_reader_.available()) | ||||
|     return; | ||||
| @@ -1176,9 +1125,6 @@ void APIConnection::set_camera_state(std::shared_ptr<esp32_camera::CameraImage> | ||||
|       image->was_requested_by(esphome::esp32_camera::IDLE)) | ||||
|     this->image_reader_.set_image(std::move(image)); | ||||
| } | ||||
| void APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { | ||||
|   this->schedule_message_(camera, &APIConnection::try_send_camera_info, ListEntitiesCameraResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                              bool is_single) { | ||||
|   auto *camera = static_cast<esp32_camera::ESP32Camera *>(entity); | ||||
| @@ -1385,10 +1331,6 @@ uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, A | ||||
|   fill_entity_state_base(a_alarm_control_panel, resp); | ||||
|   return encode_message_to_buffer(resp, AlarmControlPanelStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||
| } | ||||
| void APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { | ||||
|   this->schedule_message_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_info, | ||||
|                           ListEntitiesAlarmControlPanelResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_alarm_control_panel_info(EntityBase *entity, APIConnection *conn, | ||||
|                                                           uint32_t remaining_size, bool is_single) { | ||||
|   auto *a_alarm_control_panel = static_cast<alarm_control_panel::AlarmControlPanel *>(entity); | ||||
| @@ -1439,9 +1381,6 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe | ||||
| void APIConnection::send_event(event::Event *event, const std::string &event_type) { | ||||
|   this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_event_info(event::Event *event) { | ||||
|   this->schedule_message_(event, &APIConnection::try_send_event_info, ListEntitiesEventResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn, | ||||
|                                                 uint32_t remaining_size, bool is_single) { | ||||
|   EventResponse resp; | ||||
| @@ -1487,9 +1426,6 @@ uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection | ||||
|   fill_entity_state_base(update, resp); | ||||
|   return encode_message_to_buffer(resp, UpdateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||
| } | ||||
| void APIConnection::send_update_info(update::UpdateEntity *update) { | ||||
|   this->schedule_message_(update, &APIConnection::try_send_update_info, ListEntitiesUpdateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                              bool is_single) { | ||||
|   auto *update = static_cast<update::UpdateEntity *>(entity); | ||||
| @@ -1522,7 +1458,7 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) { | ||||
| #endif | ||||
|  | ||||
| bool APIConnection::try_send_log_message(int level, const char *tag, const char *line) { | ||||
|   if (this->log_subscription_ < level) | ||||
|   if (this->flags_.log_subscription < level) | ||||
|     return false; | ||||
|  | ||||
|   // Pre-calculate message size to avoid reallocations | ||||
| @@ -1563,7 +1499,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) { | ||||
|   resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; | ||||
|   resp.name = App.get_name(); | ||||
|  | ||||
|   this->connection_state_ = ConnectionState::CONNECTED; | ||||
|   this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::CONNECTED); | ||||
|   return resp; | ||||
| } | ||||
| ConnectResponse APIConnection::connect(const ConnectRequest &msg) { | ||||
| @@ -1574,8 +1510,10 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) { | ||||
|   resp.invalid_password = !correct; | ||||
|   if (correct) { | ||||
|     ESP_LOGD(TAG, "%s connected", this->get_client_combined_info().c_str()); | ||||
|     this->connection_state_ = ConnectionState::AUTHENTICATED; | ||||
|     this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED); | ||||
| #ifdef USE_API_CLIENT_CONNECTED_TRIGGER | ||||
|     this->parent_->get_client_connected_trigger()->trigger(this->client_info_, this->client_peername_); | ||||
| #endif | ||||
| #ifdef USE_HOMEASSISTANT_TIME | ||||
|     if (homeassistant::global_homeassistant_time != nullptr) { | ||||
|       this->send_time_request(); | ||||
| @@ -1688,7 +1626,7 @@ void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistant | ||||
|   state_subs_at_ = 0; | ||||
| } | ||||
| bool APIConnection::try_to_clear_buffer(bool log_out_of_space) { | ||||
|   if (this->remove_) | ||||
|   if (this->flags_.remove) | ||||
|     return false; | ||||
|   if (this->helper_->can_write_without_blocking()) | ||||
|     return true; | ||||
| @@ -1738,7 +1676,7 @@ void APIConnection::on_no_setup_connection() { | ||||
| } | ||||
| void APIConnection::on_fatal_error() { | ||||
|   this->helper_->close(); | ||||
|   this->remove_ = true; | ||||
|   this->flags_.remove = true; | ||||
| } | ||||
|  | ||||
| void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type) { | ||||
| @@ -1757,9 +1695,14 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c | ||||
|   items.emplace_back(entity, std::move(creator), message_type); | ||||
| } | ||||
|  | ||||
| void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type) { | ||||
|   // Insert at front for high priority messages (no deduplication check) | ||||
|   items.insert(items.begin(), BatchItem(entity, std::move(creator), message_type)); | ||||
| } | ||||
|  | ||||
| bool APIConnection::schedule_batch_() { | ||||
|   if (!this->deferred_batch_.batch_scheduled) { | ||||
|     this->deferred_batch_.batch_scheduled = true; | ||||
|   if (!this->flags_.batch_scheduled) { | ||||
|     this->flags_.batch_scheduled = true; | ||||
|     this->deferred_batch_.batch_start_time = App.get_loop_component_start_time(); | ||||
|   } | ||||
|   return true; | ||||
| @@ -1768,14 +1711,14 @@ bool APIConnection::schedule_batch_() { | ||||
| ProtoWriteBuffer APIConnection::allocate_single_message_buffer(uint16_t size) { return this->create_buffer(size); } | ||||
|  | ||||
| ProtoWriteBuffer APIConnection::allocate_batch_message_buffer(uint16_t size) { | ||||
|   ProtoWriteBuffer result = this->prepare_message_buffer(size, this->batch_first_message_); | ||||
|   this->batch_first_message_ = false; | ||||
|   ProtoWriteBuffer result = this->prepare_message_buffer(size, this->flags_.batch_first_message); | ||||
|   this->flags_.batch_first_message = false; | ||||
|   return result; | ||||
| } | ||||
|  | ||||
| void APIConnection::process_batch_() { | ||||
|   if (this->deferred_batch_.empty()) { | ||||
|     this->deferred_batch_.batch_scheduled = false; | ||||
|     this->flags_.batch_scheduled = false; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
| @@ -1828,7 +1771,7 @@ void APIConnection::process_batch_() { | ||||
|  | ||||
|   // Reserve based on estimated size (much more accurate than 24-byte worst-case) | ||||
|   this->parent_->get_shared_buffer_ref().reserve(total_estimated_size + total_overhead); | ||||
|   this->batch_first_message_ = true; | ||||
|   this->flags_.batch_first_message = true; | ||||
|  | ||||
|   size_t items_processed = 0; | ||||
|   uint16_t remaining_size = std::numeric_limits<uint16_t>::max(); | ||||
| @@ -1891,6 +1834,15 @@ void APIConnection::process_batch_() { | ||||
|     } | ||||
|   } | ||||
|  | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   // Log messages after send attempt for VV debugging | ||||
|   // It's safe to use the buffer for logging at this point regardless of send result | ||||
|   for (size_t i = 0; i < items_processed; i++) { | ||||
|     const auto &item = this->deferred_batch_.items[i]; | ||||
|     this->log_batch_item_(item); | ||||
|   } | ||||
| #endif | ||||
|  | ||||
|   // Handle remaining items more efficiently | ||||
|   if (items_processed < this->deferred_batch_.items.size()) { | ||||
|     // Remove processed items from the beginning | ||||
| @@ -1938,6 +1890,12 @@ uint16_t APIConnection::try_send_disconnect_request(EntityBase *entity, APIConne | ||||
|   return encode_message_to_buffer(req, DisconnectRequest::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||
| } | ||||
|  | ||||
| uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                               bool is_single) { | ||||
|   PingRequest req; | ||||
|   return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||
| } | ||||
|  | ||||
| uint16_t APIConnection::get_estimated_message_size(uint16_t message_type) { | ||||
|   // Use generated ESTIMATED_SIZE constants from each message type | ||||
|   switch (message_type) { | ||||
|   | ||||
| @@ -22,6 +22,7 @@ static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000; | ||||
| class APIConnection : public APIServerConnection { | ||||
|  public: | ||||
|   friend class APIServer; | ||||
|   friend class ListEntitiesIterator; | ||||
|   APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent); | ||||
|   virtual ~APIConnection(); | ||||
|  | ||||
| @@ -34,98 +35,79 @@ class APIConnection : public APIServerConnection { | ||||
|   } | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor); | ||||
|   void send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor); | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
|   bool send_cover_state(cover::Cover *cover); | ||||
|   void send_cover_info(cover::Cover *cover); | ||||
|   void cover_command(const CoverCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   bool send_fan_state(fan::Fan *fan); | ||||
|   void send_fan_info(fan::Fan *fan); | ||||
|   void fan_command(const FanCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   bool send_light_state(light::LightState *light); | ||||
|   void send_light_info(light::LightState *light); | ||||
|   void light_command(const LightCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|   bool send_sensor_state(sensor::Sensor *sensor); | ||||
|   void send_sensor_info(sensor::Sensor *sensor); | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|   bool send_switch_state(switch_::Switch *a_switch); | ||||
|   void send_switch_info(switch_::Switch *a_switch); | ||||
|   void switch_command(const SwitchCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   bool send_text_sensor_state(text_sensor::TextSensor *text_sensor); | ||||
|   void send_text_sensor_info(text_sensor::TextSensor *text_sensor); | ||||
| #endif | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|   void set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image); | ||||
|   void send_camera_info(esp32_camera::ESP32Camera *camera); | ||||
|   void camera_image(const CameraImageRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|   bool send_climate_state(climate::Climate *climate); | ||||
|   void send_climate_info(climate::Climate *climate); | ||||
|   void climate_command(const ClimateCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_NUMBER | ||||
|   bool send_number_state(number::Number *number); | ||||
|   void send_number_info(number::Number *number); | ||||
|   void number_command(const NumberCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATE | ||||
|   bool send_date_state(datetime::DateEntity *date); | ||||
|   void send_date_info(datetime::DateEntity *date); | ||||
|   void date_command(const DateCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   bool send_time_state(datetime::TimeEntity *time); | ||||
|   void send_time_info(datetime::TimeEntity *time); | ||||
|   void time_command(const TimeCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   bool send_datetime_state(datetime::DateTimeEntity *datetime); | ||||
|   void send_datetime_info(datetime::DateTimeEntity *datetime); | ||||
|   void datetime_command(const DateTimeCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
|   bool send_text_state(text::Text *text); | ||||
|   void send_text_info(text::Text *text); | ||||
|   void text_command(const TextCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_SELECT | ||||
|   bool send_select_state(select::Select *select); | ||||
|   void send_select_info(select::Select *select); | ||||
|   void select_command(const SelectCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
|   void send_button_info(button::Button *button); | ||||
|   void button_command(const ButtonCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_LOCK | ||||
|   bool send_lock_state(lock::Lock *a_lock); | ||||
|   void send_lock_info(lock::Lock *a_lock); | ||||
|   void lock_command(const LockCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
|   bool send_valve_state(valve::Valve *valve); | ||||
|   void send_valve_info(valve::Valve *valve); | ||||
|   void valve_command(const ValveCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
|   bool send_media_player_state(media_player::MediaPlayer *media_player); | ||||
|   void send_media_player_info(media_player::MediaPlayer *media_player); | ||||
|   void media_player_command(const MediaPlayerCommandRequest &msg) override; | ||||
| #endif | ||||
|   bool try_send_log_message(int level, const char *tag, const char *line); | ||||
|   void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { | ||||
|     if (!this->service_call_subscription_) | ||||
|     if (!this->flags_.service_call_subscription) | ||||
|       return; | ||||
|     this->send_message(call); | ||||
|   } | ||||
| @@ -167,26 +149,22 @@ class APIConnection : public APIServerConnection { | ||||
|  | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
|   bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); | ||||
|   void send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); | ||||
|   void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override; | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_EVENT | ||||
|   void send_event(event::Event *event, const std::string &event_type); | ||||
|   void send_event_info(event::Event *event); | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_UPDATE | ||||
|   bool send_update_state(update::UpdateEntity *update); | ||||
|   void send_update_info(update::UpdateEntity *update); | ||||
|   void update_command(const UpdateCommandRequest &msg) override; | ||||
| #endif | ||||
|  | ||||
|   void on_disconnect_response(const DisconnectResponse &value) override; | ||||
|   void on_ping_response(const PingResponse &value) override { | ||||
|     // we initiated ping | ||||
|     this->ping_retries_ = 0; | ||||
|     this->sent_ping_ = false; | ||||
|     this->flags_.sent_ping = false; | ||||
|   } | ||||
|   void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override; | ||||
| #ifdef USE_HOMEASSISTANT_TIME | ||||
| @@ -199,16 +177,16 @@ class APIConnection : public APIServerConnection { | ||||
|   DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override; | ||||
|   void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); } | ||||
|   void subscribe_states(const SubscribeStatesRequest &msg) override { | ||||
|     this->state_subscription_ = true; | ||||
|     this->flags_.state_subscription = true; | ||||
|     this->initial_state_iterator_.begin(); | ||||
|   } | ||||
|   void subscribe_logs(const SubscribeLogsRequest &msg) override { | ||||
|     this->log_subscription_ = msg.level; | ||||
|     this->flags_.log_subscription = msg.level; | ||||
|     if (msg.dump_config) | ||||
|       App.schedule_dump_config(); | ||||
|   } | ||||
|   void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) override { | ||||
|     this->service_call_subscription_ = true; | ||||
|     this->flags_.service_call_subscription = true; | ||||
|   } | ||||
|   void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override; | ||||
|   GetTimeResponse get_time(const GetTimeRequest &msg) override { | ||||
| @@ -220,9 +198,12 @@ class APIConnection : public APIServerConnection { | ||||
|   NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) override; | ||||
| #endif | ||||
|  | ||||
|   bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; } | ||||
|   bool is_authenticated() override { | ||||
|     return static_cast<ConnectionState>(this->flags_.connection_state) == ConnectionState::AUTHENTICATED; | ||||
|   } | ||||
|   bool is_connection_setup() override { | ||||
|     return this->connection_state_ == ConnectionState ::CONNECTED || this->is_authenticated(); | ||||
|     return static_cast<ConnectionState>(this->flags_.connection_state) == ConnectionState::CONNECTED || | ||||
|            this->is_authenticated(); | ||||
|   } | ||||
|   void on_fatal_error() override; | ||||
|   void on_unauthenticated_access() override; | ||||
| @@ -441,45 +422,32 @@ class APIConnection : public APIServerConnection { | ||||
|   // Helper function to get estimated message size for buffer pre-allocation | ||||
|   static uint16_t get_estimated_message_size(uint16_t message_type); | ||||
|  | ||||
|   // Pointers first (4 bytes each, naturally aligned) | ||||
|   // Batch message method for ping requests | ||||
|   static uint16_t try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                         bool is_single); | ||||
|  | ||||
|   // === Optimal member ordering for 32-bit systems === | ||||
|  | ||||
|   // Group 1: Pointers (4 bytes each on 32-bit) | ||||
|   std::unique_ptr<APIFrameHelper> helper_; | ||||
|   APIServer *parent_; | ||||
|  | ||||
|   // 4-byte aligned types | ||||
|   uint32_t last_traffic_; | ||||
|   uint32_t next_ping_retry_{0}; | ||||
|   int state_subs_at_ = -1; | ||||
|  | ||||
|   // Strings (12 bytes each on 32-bit) | ||||
|   std::string client_info_; | ||||
|   std::string client_peername_; | ||||
|  | ||||
|   // 2-byte aligned types | ||||
|   uint16_t client_api_version_major_{0}; | ||||
|   uint16_t client_api_version_minor_{0}; | ||||
|  | ||||
|   // Group all 1-byte types together to minimize padding | ||||
|   enum class ConnectionState : uint8_t { | ||||
|     WAITING_FOR_HELLO, | ||||
|     CONNECTED, | ||||
|     AUTHENTICATED, | ||||
|   } connection_state_{ConnectionState::WAITING_FOR_HELLO}; | ||||
|   uint8_t log_subscription_{ESPHOME_LOG_LEVEL_NONE}; | ||||
|   bool remove_{false}; | ||||
|   bool state_subscription_{false}; | ||||
|   bool sent_ping_{false}; | ||||
|   bool service_call_subscription_{false}; | ||||
|   bool next_close_ = false; | ||||
|   uint8_t ping_retries_{0}; | ||||
|   // 8 bytes used, no padding needed | ||||
|  | ||||
|   // Larger objects at the end | ||||
|   // Group 2: Larger objects (must be 4-byte aligned) | ||||
|   // These contain vectors/pointers internally, so putting them early ensures good alignment | ||||
|   InitialStateIterator initial_state_iterator_; | ||||
|   ListEntitiesIterator list_entities_iterator_; | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|   esp32_camera::CameraImageReader image_reader_; | ||||
| #endif | ||||
|  | ||||
|   // Group 3: Strings (12 bytes each on 32-bit, 4-byte aligned) | ||||
|   std::string client_info_; | ||||
|   std::string client_peername_; | ||||
|  | ||||
|   // Group 4: 4-byte types | ||||
|   uint32_t last_traffic_; | ||||
|   int state_subs_at_ = -1; | ||||
|  | ||||
|   // Function pointer type for message encoding | ||||
|   using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single); | ||||
|  | ||||
| @@ -589,7 +557,6 @@ class APIConnection : public APIServerConnection { | ||||
|  | ||||
|     std::vector<BatchItem> items; | ||||
|     uint32_t batch_start_time{0}; | ||||
|     bool batch_scheduled{false}; | ||||
|  | ||||
|     DeferredBatch() { | ||||
|       // Pre-allocate capacity for typical batch sizes to avoid reallocation | ||||
| @@ -598,15 +565,51 @@ class APIConnection : public APIServerConnection { | ||||
|  | ||||
|     // Add item to the batch | ||||
|     void add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type); | ||||
|     // Add item to the front of the batch (for high priority messages like ping) | ||||
|     void add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type); | ||||
|     void clear() { | ||||
|       items.clear(); | ||||
|       batch_scheduled = false; | ||||
|       batch_start_time = 0; | ||||
|     } | ||||
|     bool empty() const { return items.empty(); } | ||||
|   }; | ||||
|  | ||||
|   // DeferredBatch here (16 bytes, 4-byte aligned) | ||||
|   DeferredBatch deferred_batch_; | ||||
|  | ||||
|   // ConnectionState enum for type safety | ||||
|   enum class ConnectionState : uint8_t { | ||||
|     WAITING_FOR_HELLO = 0, | ||||
|     CONNECTED = 1, | ||||
|     AUTHENTICATED = 2, | ||||
|   }; | ||||
|  | ||||
|   // Group 5: Pack all small members together to minimize padding | ||||
|   // This group starts at a 4-byte boundary after DeferredBatch | ||||
|   struct APIFlags { | ||||
|     // Connection state only needs 2 bits (3 states) | ||||
|     uint8_t connection_state : 2; | ||||
|     // Log subscription needs 3 bits (log levels 0-7) | ||||
|     uint8_t log_subscription : 3; | ||||
|     // Boolean flags (1 bit each) | ||||
|     uint8_t remove : 1; | ||||
|     uint8_t state_subscription : 1; | ||||
|     uint8_t sent_ping : 1; | ||||
|  | ||||
|     uint8_t service_call_subscription : 1; | ||||
|     uint8_t next_close : 1; | ||||
|     uint8_t batch_scheduled : 1; | ||||
|     uint8_t batch_first_message : 1;  // For batch buffer allocation | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|     uint8_t log_only_mode : 1; | ||||
| #endif | ||||
|   } flags_{};  // 2 bytes total | ||||
|  | ||||
|   // 2-byte types immediately after flags_ (no padding between them) | ||||
|   uint16_t client_api_version_major_{0}; | ||||
|   uint16_t client_api_version_minor_{0}; | ||||
|   // Total: 2 (flags) + 2 + 2 = 6 bytes, then 2 bytes padding to next 4-byte boundary | ||||
|  | ||||
|   uint32_t get_batch_delay_ms_() const; | ||||
|   // Message will use 8 more bytes than the minimum size, and typical | ||||
|   // MTU is 1500. Sometimes users will see as low as 1460 MTU. | ||||
| @@ -624,8 +627,9 @@ class APIConnection : public APIServerConnection { | ||||
|   bool schedule_batch_(); | ||||
|   void process_batch_(); | ||||
|  | ||||
|   // State for batch buffer allocation | ||||
|   bool batch_first_message_{false}; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void log_batch_item_(const DeferredBatch::BatchItem &item); | ||||
| #endif | ||||
|  | ||||
|   // Helper function to schedule a deferred message with known message type | ||||
|   bool schedule_message_(EntityBase *entity, MessageCreator creator, uint16_t message_type) { | ||||
| @@ -637,6 +641,12 @@ class APIConnection : public APIServerConnection { | ||||
|   bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) { | ||||
|     return schedule_message_(entity, MessageCreator(function_ptr), message_type); | ||||
|   } | ||||
|  | ||||
|   // Helper function to schedule a high priority message at the front of the batch | ||||
|   bool schedule_message_front_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) { | ||||
|     this->deferred_batch_.add_item_front(entity, MessageCreator(function_ptr), message_type); | ||||
|     return this->schedule_batch_(); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
|   | ||||
| @@ -14,7 +14,7 @@ void APIServerConnectionBase::log_send_message_(const char *name, const std::str | ||||
| } | ||||
| #endif | ||||
|  | ||||
| bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { | ||||
| void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { | ||||
|   switch (msg_type) { | ||||
|     case 1: { | ||||
|       HelloRequest msg; | ||||
| @@ -106,50 +106,50 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, | ||||
|       this->on_subscribe_logs_request(msg); | ||||
|       break; | ||||
|     } | ||||
|     case 30: { | ||||
| #ifdef USE_COVER | ||||
|     case 30: { | ||||
|       CoverCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_cover_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_cover_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 31: { | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|     case 31: { | ||||
|       FanCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_fan_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_fan_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 32: { | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|     case 32: { | ||||
|       LightCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_light_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_light_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 33: { | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|     case 33: { | ||||
|       SwitchCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_switch_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_switch_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
| #endif | ||||
|     case 34: { | ||||
|       SubscribeHomeassistantServicesRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| @@ -204,395 +204,394 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, | ||||
|       this->on_execute_service_request(msg); | ||||
|       break; | ||||
|     } | ||||
|     case 45: { | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|     case 45: { | ||||
|       CameraImageRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_camera_image_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_camera_image_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 48: { | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|     case 48: { | ||||
|       ClimateCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_climate_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_climate_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 51: { | ||||
| #endif | ||||
| #ifdef USE_NUMBER | ||||
|     case 51: { | ||||
|       NumberCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_number_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_number_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 54: { | ||||
| #endif | ||||
| #ifdef USE_SELECT | ||||
|     case 54: { | ||||
|       SelectCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_select_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 57: { | ||||
| #endif | ||||
| #ifdef USE_SIREN | ||||
|     case 57: { | ||||
|       SirenCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_siren_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_siren_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 60: { | ||||
| #endif | ||||
| #ifdef USE_LOCK | ||||
|     case 60: { | ||||
|       LockCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_lock_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_lock_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 62: { | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
|     case 62: { | ||||
|       ButtonCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_button_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_button_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 65: { | ||||
| #endif | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
|     case 65: { | ||||
|       MediaPlayerCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_media_player_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_media_player_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 66: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 66: { | ||||
|       SubscribeBluetoothLEAdvertisementsRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_subscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_subscribe_bluetooth_le_advertisements_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 68: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 68: { | ||||
|       BluetoothDeviceRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_bluetooth_device_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_bluetooth_device_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 70: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 70: { | ||||
|       BluetoothGATTGetServicesRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_bluetooth_gatt_get_services_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_bluetooth_gatt_get_services_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 73: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 73: { | ||||
|       BluetoothGATTReadRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_bluetooth_gatt_read_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_bluetooth_gatt_read_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 75: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 75: { | ||||
|       BluetoothGATTWriteRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_bluetooth_gatt_write_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_bluetooth_gatt_write_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 76: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 76: { | ||||
|       BluetoothGATTReadDescriptorRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_bluetooth_gatt_read_descriptor_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_bluetooth_gatt_read_descriptor_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 77: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 77: { | ||||
|       BluetoothGATTWriteDescriptorRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_bluetooth_gatt_write_descriptor_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_bluetooth_gatt_write_descriptor_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 78: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 78: { | ||||
|       BluetoothGATTNotifyRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_bluetooth_gatt_notify_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_bluetooth_gatt_notify_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 80: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 80: { | ||||
|       SubscribeBluetoothConnectionsFreeRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_subscribe_bluetooth_connections_free_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_subscribe_bluetooth_connections_free_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 87: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 87: { | ||||
|       UnsubscribeBluetoothLEAdvertisementsRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_unsubscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_unsubscribe_bluetooth_le_advertisements_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 89: { | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|     case 89: { | ||||
|       SubscribeVoiceAssistantRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_subscribe_voice_assistant_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_subscribe_voice_assistant_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 91: { | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|     case 91: { | ||||
|       VoiceAssistantResponse msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_response(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 92: { | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|     case 92: { | ||||
|       VoiceAssistantEventResponse msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_event_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_event_response(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 96: { | ||||
| #endif | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
|     case 96: { | ||||
|       AlarmControlPanelCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_alarm_control_panel_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_alarm_control_panel_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 99: { | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
|     case 99: { | ||||
|       TextCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_text_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_text_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 102: { | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATE | ||||
|     case 102: { | ||||
|       DateCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_date_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_date_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 105: { | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
|     case 105: { | ||||
|       TimeCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_time_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_time_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 106: { | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|     case 106: { | ||||
|       VoiceAssistantAudio msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_audio: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_audio(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 111: { | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
|     case 111: { | ||||
|       ValveCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_valve_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_valve_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 114: { | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|     case 114: { | ||||
|       DateTimeCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_date_time_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_date_time_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 115: { | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|     case 115: { | ||||
|       VoiceAssistantTimerEventResponse msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_timer_event_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_timer_event_response(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 118: { | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
|     case 118: { | ||||
|       UpdateCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_update_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_update_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 119: { | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|     case 119: { | ||||
|       VoiceAssistantAnnounceRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_announce_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_announce_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 121: { | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|     case 121: { | ||||
|       VoiceAssistantConfigurationRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_configuration_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 123: { | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|     case 123: { | ||||
|       VoiceAssistantSetConfiguration msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_set_configuration: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_set_configuration(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 124: { | ||||
| #endif | ||||
| #ifdef USE_API_NOISE | ||||
|     case 124: { | ||||
|       NoiseEncryptionSetKeyRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_noise_encryption_set_key_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_noise_encryption_set_key_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 127: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 127: { | ||||
|       BluetoothScannerSetModeRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_bluetooth_scanner_set_mode_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_bluetooth_scanner_set_mode_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
| #endif | ||||
|     default: | ||||
|       return false; | ||||
|       break; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| void APIServerConnection::on_hello_request(const HelloRequest &msg) { | ||||
|   | ||||
| @@ -199,7 +199,7 @@ class APIServerConnectionBase : public ProtoService { | ||||
|   virtual void on_update_command_request(const UpdateCommandRequest &value){}; | ||||
| #endif | ||||
|  protected: | ||||
|   bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; | ||||
|   void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; | ||||
| }; | ||||
|  | ||||
| class APIServerConnection : public APIServerConnectionBase { | ||||
|   | ||||
| @@ -316,15 +316,13 @@ class ProtoSize { | ||||
|   /** | ||||
|    * @brief Calculates and adds the size of a nested message field to the total message size | ||||
|    * | ||||
|    * This templated version directly takes a message object, calculates its size internally, | ||||
|    * This version takes a ProtoMessage object, calculates its size internally, | ||||
|    * and updates the total_size reference. This eliminates the need for a temporary variable | ||||
|    * at the call site. | ||||
|    * | ||||
|    * @tparam MessageType The type of the nested message (inferred from parameter) | ||||
|    * @param message The nested message object | ||||
|    */ | ||||
|   template<typename MessageType> | ||||
|   static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const MessageType &message, | ||||
|   static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const ProtoMessage &message, | ||||
|                                         bool force = false) { | ||||
|     uint32_t nested_size = 0; | ||||
|     message.calculate_size(nested_size); | ||||
|   | ||||
| @@ -104,7 +104,7 @@ void APIServer::setup() { | ||||
|         return; | ||||
|       } | ||||
|       for (auto &c : this->clients_) { | ||||
|         if (!c->remove_) | ||||
|         if (!c->flags_.remove) | ||||
|           c->try_send_log_message(level, tag, message); | ||||
|       } | ||||
|     }); | ||||
| @@ -116,7 +116,7 @@ void APIServer::setup() { | ||||
|     esp32_camera::global_esp32_camera->add_image_callback( | ||||
|         [this](const std::shared_ptr<esp32_camera::CameraImage> &image) { | ||||
|           for (auto &c : this->clients_) { | ||||
|             if (!c->remove_) | ||||
|             if (!c->flags_.remove) | ||||
|               c->set_camera_state(image); | ||||
|           } | ||||
|         }); | ||||
| @@ -176,7 +176,7 @@ void APIServer::loop() { | ||||
|   while (client_index < this->clients_.size()) { | ||||
|     auto &client = this->clients_[client_index]; | ||||
|  | ||||
|     if (!client->remove_) { | ||||
|     if (!client->flags_.remove) { | ||||
|       // Common case: process active client | ||||
|       client->loop(); | ||||
|       client_index++; | ||||
| @@ -184,7 +184,9 @@ void APIServer::loop() { | ||||
|     } | ||||
|  | ||||
|     // Rare case: handle disconnection | ||||
| #ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER | ||||
|     this->client_disconnected_trigger_->trigger(client->client_info_, client->client_peername_); | ||||
| #endif | ||||
|     ESP_LOGV(TAG, "Remove connection %s", client->client_info_.c_str()); | ||||
|  | ||||
|     // Swap with the last element and pop (avoids expensive vector shifts) | ||||
| @@ -431,7 +433,7 @@ void APIServer::set_port(uint16_t port) { this->port_ = port; } | ||||
|  | ||||
| void APIServer::set_password(const std::string &password) { this->password_ = password; } | ||||
|  | ||||
| void APIServer::set_batch_delay(uint32_t batch_delay) { this->batch_delay_ = batch_delay; } | ||||
| void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; } | ||||
|  | ||||
| void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) { | ||||
|   for (auto &client : this->clients_) { | ||||
| @@ -502,7 +504,7 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) { | ||||
| #ifdef USE_HOMEASSISTANT_TIME | ||||
| void APIServer::request_time() { | ||||
|   for (auto &client : this->clients_) { | ||||
|     if (!client->remove_ && client->is_authenticated()) | ||||
|     if (!client->flags_.remove && client->is_authenticated()) | ||||
|       client->send_time_request(); | ||||
|   } | ||||
| } | ||||
| @@ -526,8 +528,8 @@ void APIServer::on_shutdown() { | ||||
|   for (auto &c : this->clients_) { | ||||
|     if (!c->send_message(DisconnectRequest())) { | ||||
|       // If we can't send the disconnect request directly (tx_buffer full), | ||||
|       // schedule it in the batch so it will be sent with the 5ms timer | ||||
|       c->schedule_message_(nullptr, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE); | ||||
|       // schedule it at the front of the batch so it will be sent with priority | ||||
|       c->schedule_message_front_(nullptr, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -40,8 +40,8 @@ class APIServer : public Component, public Controller { | ||||
|   void set_port(uint16_t port); | ||||
|   void set_password(const std::string &password); | ||||
|   void set_reboot_timeout(uint32_t reboot_timeout); | ||||
|   void set_batch_delay(uint32_t batch_delay); | ||||
|   uint32_t get_batch_delay() const { return batch_delay_; } | ||||
|   void set_batch_delay(uint16_t batch_delay); | ||||
|   uint16_t get_batch_delay() const { return batch_delay_; } | ||||
|  | ||||
|   // Get reference to shared buffer for API connections | ||||
|   std::vector<uint8_t> &get_shared_buffer_ref() { return shared_write_buffer_; } | ||||
| @@ -105,7 +105,18 @@ class APIServer : public Component, public Controller { | ||||
|   void on_media_player_update(media_player::MediaPlayer *obj) override; | ||||
| #endif | ||||
|   void send_homeassistant_service_call(const HomeassistantServiceResponse &call); | ||||
|   void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } | ||||
|   void register_user_service(UserServiceDescriptor *descriptor) { | ||||
| #ifdef USE_API_YAML_SERVICES | ||||
|     // Vector is pre-allocated when services are defined in YAML | ||||
|     this->user_services_.push_back(descriptor); | ||||
| #else | ||||
|     // Lazy allocate vector on first use for CustomAPIDevice | ||||
|     if (!this->user_services_) { | ||||
|       this->user_services_ = std::make_unique<std::vector<UserServiceDescriptor *>>(); | ||||
|     } | ||||
|     this->user_services_->push_back(descriptor); | ||||
| #endif | ||||
|   } | ||||
| #ifdef USE_HOMEASSISTANT_TIME | ||||
|   void request_time(); | ||||
| #endif | ||||
| @@ -134,35 +145,58 @@ class APIServer : public Component, public Controller { | ||||
|   void get_home_assistant_state(std::string entity_id, optional<std::string> attribute, | ||||
|                                 std::function<void(std::string)> f); | ||||
|   const std::vector<HomeAssistantStateSubscription> &get_state_subs() const; | ||||
|   const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; } | ||||
|   const std::vector<UserServiceDescriptor *> &get_user_services() const { | ||||
| #ifdef USE_API_YAML_SERVICES | ||||
|     return this->user_services_; | ||||
| #else | ||||
|     static const std::vector<UserServiceDescriptor *> EMPTY; | ||||
|     return this->user_services_ ? *this->user_services_ : EMPTY; | ||||
| #endif | ||||
|   } | ||||
|  | ||||
| #ifdef USE_API_CLIENT_CONNECTED_TRIGGER | ||||
|   Trigger<std::string, std::string> *get_client_connected_trigger() const { return this->client_connected_trigger_; } | ||||
| #endif | ||||
| #ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER | ||||
|   Trigger<std::string, std::string> *get_client_disconnected_trigger() const { | ||||
|     return this->client_disconnected_trigger_; | ||||
|   } | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   void schedule_reboot_timeout_(); | ||||
|   // Pointers and pointer-like types first (4 bytes each) | ||||
|   std::unique_ptr<socket::Socket> socket_ = nullptr; | ||||
| #ifdef USE_API_CLIENT_CONNECTED_TRIGGER | ||||
|   Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>(); | ||||
| #endif | ||||
| #ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER | ||||
|   Trigger<std::string, std::string> *client_disconnected_trigger_ = new Trigger<std::string, std::string>(); | ||||
| #endif | ||||
|  | ||||
|   // 4-byte aligned types | ||||
|   uint32_t reboot_timeout_{300000}; | ||||
|   uint32_t batch_delay_{100}; | ||||
|  | ||||
|   // Vectors and strings (12 bytes each on 32-bit) | ||||
|   std::vector<std::unique_ptr<APIConnection>> clients_; | ||||
|   std::string password_; | ||||
|   std::vector<uint8_t> shared_write_buffer_;  // Shared proto write buffer for all connections | ||||
|   std::vector<HomeAssistantStateSubscription> state_subs_; | ||||
| #ifdef USE_API_YAML_SERVICES | ||||
|   // When services are defined in YAML, we know at compile time that services will be registered | ||||
|   std::vector<UserServiceDescriptor *> user_services_; | ||||
| #else | ||||
|   // Services can still be registered at runtime by CustomAPIDevice components even when not | ||||
|   // defined in YAML. Using unique_ptr allows lazy allocation, saving 12 bytes in the common | ||||
|   // case where no services (YAML or custom) are used. | ||||
|   std::unique_ptr<std::vector<UserServiceDescriptor *>> user_services_; | ||||
| #endif | ||||
|  | ||||
|   // Group smaller types together | ||||
|   uint16_t port_{6053}; | ||||
|   uint16_t batch_delay_{100}; | ||||
|   bool shutting_down_ = false; | ||||
|   // 3 bytes used, 1 byte padding | ||||
|   // 5 bytes used, 3 bytes padding | ||||
|  | ||||
| #ifdef USE_API_NOISE | ||||
|   std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>(); | ||||
|   | ||||
| @@ -4,9 +4,15 @@ import asyncio | ||||
| from datetime import datetime | ||||
| import logging | ||||
| from typing import TYPE_CHECKING, Any | ||||
| import warnings | ||||
|  | ||||
| from aioesphomeapi import APIClient, parse_log_message | ||||
| from aioesphomeapi.log_runner import async_run | ||||
| # Suppress protobuf version warnings | ||||
| with warnings.catch_warnings(): | ||||
|     warnings.filterwarnings( | ||||
|         "ignore", category=UserWarning, message=".*Protobuf gencode version.*" | ||||
|     ) | ||||
|     from aioesphomeapi import APIClient, parse_log_message | ||||
|     from aioesphomeapi.log_runner import async_run | ||||
|  | ||||
| from esphome.const import CONF_KEY, CONF_PASSWORD, CONF_PORT, __version__ | ||||
| from esphome.core import CORE | ||||
| @@ -29,8 +35,8 @@ async def async_run_logs(config: dict[str, Any], address: str) -> None: | ||||
|     port: int = int(conf[CONF_PORT]) | ||||
|     password: str = conf[CONF_PASSWORD] | ||||
|     noise_psk: str | None = None | ||||
|     if CONF_ENCRYPTION in conf: | ||||
|         noise_psk = conf[CONF_ENCRYPTION][CONF_KEY] | ||||
|     if (encryption := conf.get(CONF_ENCRYPTION)) and (key := encryption.get(CONF_KEY)): | ||||
|         noise_psk = key | ||||
|     _LOGGER.info("Starting log output from %s using esphome API", address) | ||||
|     cli = APIClient( | ||||
|         address, | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| #include "list_entities.h" | ||||
| #ifdef USE_API | ||||
| #include "api_connection.h" | ||||
| #include "api_pb2.h" | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/util.h" | ||||
| @@ -8,155 +9,85 @@ | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| // Generate entity handler implementations using macros | ||||
| #ifdef USE_BINARY_SENSOR | ||||
| bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { | ||||
|   this->client_->send_binary_sensor_info(binary_sensor); | ||||
|   return true; | ||||
| } | ||||
| LIST_ENTITIES_HANDLER(binary_sensor, binary_sensor::BinarySensor, ListEntitiesBinarySensorResponse) | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
| bool ListEntitiesIterator::on_cover(cover::Cover *cover) { | ||||
|   this->client_->send_cover_info(cover); | ||||
|   return true; | ||||
| } | ||||
| LIST_ENTITIES_HANDLER(cover, cover::Cover, ListEntitiesCoverResponse) | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
| bool ListEntitiesIterator::on_fan(fan::Fan *fan) { | ||||
|   this->client_->send_fan_info(fan); | ||||
|   return true; | ||||
| } | ||||
| LIST_ENTITIES_HANDLER(fan, fan::Fan, ListEntitiesFanResponse) | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
| bool ListEntitiesIterator::on_light(light::LightState *light) { | ||||
|   this->client_->send_light_info(light); | ||||
|   return true; | ||||
| } | ||||
| LIST_ENTITIES_HANDLER(light, light::LightState, ListEntitiesLightResponse) | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
| bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { | ||||
|   this->client_->send_sensor_info(sensor); | ||||
|   return true; | ||||
| } | ||||
| LIST_ENTITIES_HANDLER(sensor, sensor::Sensor, ListEntitiesSensorResponse) | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
| bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { | ||||
|   this->client_->send_switch_info(a_switch); | ||||
|   return true; | ||||
| } | ||||
| LIST_ENTITIES_HANDLER(switch, switch_::Switch, ListEntitiesSwitchResponse) | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
| bool ListEntitiesIterator::on_button(button::Button *button) { | ||||
|   this->client_->send_button_info(button); | ||||
|   return true; | ||||
| } | ||||
| LIST_ENTITIES_HANDLER(button, button::Button, ListEntitiesButtonResponse) | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
| bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { | ||||
|   this->client_->send_text_sensor_info(text_sensor); | ||||
|   return true; | ||||
| } | ||||
| LIST_ENTITIES_HANDLER(text_sensor, text_sensor::TextSensor, ListEntitiesTextSensorResponse) | ||||
| #endif | ||||
| #ifdef USE_LOCK | ||||
| bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { | ||||
|   this->client_->send_lock_info(a_lock); | ||||
|   return true; | ||||
| } | ||||
| LIST_ENTITIES_HANDLER(lock, lock::Lock, ListEntitiesLockResponse) | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
| bool ListEntitiesIterator::on_valve(valve::Valve *valve) { | ||||
|   this->client_->send_valve_info(valve); | ||||
|   return true; | ||||
| } | ||||
| LIST_ENTITIES_HANDLER(valve, valve::Valve, ListEntitiesValveResponse) | ||||
| #endif | ||||
| #ifdef USE_ESP32_CAMERA | ||||
| LIST_ENTITIES_HANDLER(camera, esp32_camera::ESP32Camera, ListEntitiesCameraResponse) | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
| LIST_ENTITIES_HANDLER(climate, climate::Climate, ListEntitiesClimateResponse) | ||||
| #endif | ||||
| #ifdef USE_NUMBER | ||||
| LIST_ENTITIES_HANDLER(number, number::Number, ListEntitiesNumberResponse) | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATE | ||||
| LIST_ENTITIES_HANDLER(date, datetime::DateEntity, ListEntitiesDateResponse) | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
| LIST_ENTITIES_HANDLER(time, datetime::TimeEntity, ListEntitiesTimeResponse) | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
| LIST_ENTITIES_HANDLER(datetime, datetime::DateTimeEntity, ListEntitiesDateTimeResponse) | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
| LIST_ENTITIES_HANDLER(text, text::Text, ListEntitiesTextResponse) | ||||
| #endif | ||||
| #ifdef USE_SELECT | ||||
| LIST_ENTITIES_HANDLER(select, select::Select, ListEntitiesSelectResponse) | ||||
| #endif | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
| LIST_ENTITIES_HANDLER(media_player, media_player::MediaPlayer, ListEntitiesMediaPlayerResponse) | ||||
| #endif | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
| LIST_ENTITIES_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPanel, | ||||
|                       ListEntitiesAlarmControlPanelResponse) | ||||
| #endif | ||||
| #ifdef USE_EVENT | ||||
| LIST_ENTITIES_HANDLER(event, event::Event, ListEntitiesEventResponse) | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
| LIST_ENTITIES_HANDLER(update, update::UpdateEntity, ListEntitiesUpdateResponse) | ||||
| #endif | ||||
|  | ||||
| // Special cases that don't follow the pattern | ||||
| bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); } | ||||
|  | ||||
| ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {} | ||||
|  | ||||
| bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) { | ||||
|   auto resp = service->encode_list_service_response(); | ||||
|   return this->client_->send_message(resp); | ||||
| } | ||||
|  | ||||
| #ifdef USE_ESP32_CAMERA | ||||
| bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) { | ||||
|   this->client_->send_camera_info(camera); | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_CLIMATE | ||||
| bool ListEntitiesIterator::on_climate(climate::Climate *climate) { | ||||
|   this->client_->send_climate_info(climate); | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_NUMBER | ||||
| bool ListEntitiesIterator::on_number(number::Number *number) { | ||||
|   this->client_->send_number_info(number); | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_DATETIME_DATE | ||||
| bool ListEntitiesIterator::on_date(datetime::DateEntity *date) { | ||||
|   this->client_->send_date_info(date); | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_DATETIME_TIME | ||||
| bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) { | ||||
|   this->client_->send_time_info(time); | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
| bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *datetime) { | ||||
|   this->client_->send_datetime_info(datetime); | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_TEXT | ||||
| bool ListEntitiesIterator::on_text(text::Text *text) { | ||||
|   this->client_->send_text_info(text); | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_SELECT | ||||
| bool ListEntitiesIterator::on_select(select::Select *select) { | ||||
|   this->client_->send_select_info(select); | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
| bool ListEntitiesIterator::on_media_player(media_player::MediaPlayer *media_player) { | ||||
|   this->client_->send_media_player_info(media_player); | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
| bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { | ||||
|   this->client_->send_alarm_control_panel_info(a_alarm_control_panel); | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_EVENT | ||||
| bool ListEntitiesIterator::on_event(event::Event *event) { | ||||
|   this->client_->send_event_info(event); | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
| bool ListEntitiesIterator::on_update(update::UpdateEntity *update) { | ||||
|   this->client_->send_update_info(update); | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -9,75 +9,83 @@ namespace api { | ||||
|  | ||||
| class APIConnection; | ||||
|  | ||||
| // Macro for generating ListEntitiesIterator handlers | ||||
| // Calls schedule_message_ with try_send_*_info | ||||
| #define LIST_ENTITIES_HANDLER(entity_type, EntityClass, ResponseType) \ | ||||
|   bool ListEntitiesIterator::on_##entity_type(EntityClass *entity) { /* NOLINT(bugprone-macro-parentheses) */ \ | ||||
|     return this->client_->schedule_message_(entity, &APIConnection::try_send_##entity_type##_info, \ | ||||
|                                             ResponseType::MESSAGE_TYPE); \ | ||||
|   } | ||||
|  | ||||
| class ListEntitiesIterator : public ComponentIterator { | ||||
|  public: | ||||
|   ListEntitiesIterator(APIConnection *client); | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override; | ||||
|   bool on_binary_sensor(binary_sensor::BinarySensor *entity) override; | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
|   bool on_cover(cover::Cover *cover) override; | ||||
|   bool on_cover(cover::Cover *entity) override; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   bool on_fan(fan::Fan *fan) override; | ||||
|   bool on_fan(fan::Fan *entity) override; | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   bool on_light(light::LightState *light) override; | ||||
|   bool on_light(light::LightState *entity) override; | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|   bool on_sensor(sensor::Sensor *sensor) override; | ||||
|   bool on_sensor(sensor::Sensor *entity) override; | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|   bool on_switch(switch_::Switch *a_switch) override; | ||||
|   bool on_switch(switch_::Switch *entity) override; | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
|   bool on_button(button::Button *button) override; | ||||
|   bool on_button(button::Button *entity) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; | ||||
|   bool on_text_sensor(text_sensor::TextSensor *entity) override; | ||||
| #endif | ||||
|   bool on_service(UserServiceDescriptor *service) override; | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|   bool on_camera(esp32_camera::ESP32Camera *camera) override; | ||||
|   bool on_camera(esp32_camera::ESP32Camera *entity) override; | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|   bool on_climate(climate::Climate *climate) override; | ||||
|   bool on_climate(climate::Climate *entity) override; | ||||
| #endif | ||||
| #ifdef USE_NUMBER | ||||
|   bool on_number(number::Number *number) override; | ||||
|   bool on_number(number::Number *entity) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATE | ||||
|   bool on_date(datetime::DateEntity *date) override; | ||||
|   bool on_date(datetime::DateEntity *entity) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   bool on_time(datetime::TimeEntity *time) override; | ||||
|   bool on_time(datetime::TimeEntity *entity) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   bool on_datetime(datetime::DateTimeEntity *datetime) override; | ||||
|   bool on_datetime(datetime::DateTimeEntity *entity) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
|   bool on_text(text::Text *text) override; | ||||
|   bool on_text(text::Text *entity) override; | ||||
| #endif | ||||
| #ifdef USE_SELECT | ||||
|   bool on_select(select::Select *select) override; | ||||
|   bool on_select(select::Select *entity) override; | ||||
| #endif | ||||
| #ifdef USE_LOCK | ||||
|   bool on_lock(lock::Lock *a_lock) override; | ||||
|   bool on_lock(lock::Lock *entity) override; | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
|   bool on_valve(valve::Valve *valve) override; | ||||
|   bool on_valve(valve::Valve *entity) override; | ||||
| #endif | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
|   bool on_media_player(media_player::MediaPlayer *media_player) override; | ||||
|   bool on_media_player(media_player::MediaPlayer *entity) override; | ||||
| #endif | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
|   bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override; | ||||
|   bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *entity) override; | ||||
| #endif | ||||
| #ifdef USE_EVENT | ||||
|   bool on_event(event::Event *event) override; | ||||
|   bool on_event(event::Event *entity) override; | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
|   bool on_update(update::UpdateEntity *update) override; | ||||
|   bool on_update(update::UpdateEntity *entity) override; | ||||
| #endif | ||||
|   bool on_end() override; | ||||
|   bool completed() { return this->state_ == IteratorState::NONE; } | ||||
|   | ||||
| @@ -364,7 +364,7 @@ class ProtoService { | ||||
|    */ | ||||
|   virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 0; | ||||
|   virtual bool send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) = 0; | ||||
|   virtual bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0; | ||||
|   virtual void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0; | ||||
|  | ||||
|   // Optimized method that pre-allocates buffer based on message size | ||||
|   bool send_message_(const ProtoMessage &msg, uint16_t message_type) { | ||||
|   | ||||
| @@ -6,73 +6,67 @@ | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| // Generate entity handler implementations using macros | ||||
| #ifdef USE_BINARY_SENSOR | ||||
| bool InitialStateIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { | ||||
|   return this->client_->send_binary_sensor_state(binary_sensor); | ||||
| } | ||||
| INITIAL_STATE_HANDLER(binary_sensor, binary_sensor::BinarySensor) | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
| bool InitialStateIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_state(cover); } | ||||
| INITIAL_STATE_HANDLER(cover, cover::Cover) | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
| bool InitialStateIterator::on_fan(fan::Fan *fan) { return this->client_->send_fan_state(fan); } | ||||
| INITIAL_STATE_HANDLER(fan, fan::Fan) | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
| bool InitialStateIterator::on_light(light::LightState *light) { return this->client_->send_light_state(light); } | ||||
| INITIAL_STATE_HANDLER(light, light::LightState) | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
| bool InitialStateIterator::on_sensor(sensor::Sensor *sensor) { return this->client_->send_sensor_state(sensor); } | ||||
| INITIAL_STATE_HANDLER(sensor, sensor::Sensor) | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
| bool InitialStateIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_state(a_switch); } | ||||
| INITIAL_STATE_HANDLER(switch, switch_::Switch) | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
| bool InitialStateIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { | ||||
|   return this->client_->send_text_sensor_state(text_sensor); | ||||
| } | ||||
| INITIAL_STATE_HANDLER(text_sensor, text_sensor::TextSensor) | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
| bool InitialStateIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_state(climate); } | ||||
| INITIAL_STATE_HANDLER(climate, climate::Climate) | ||||
| #endif | ||||
| #ifdef USE_NUMBER | ||||
| bool InitialStateIterator::on_number(number::Number *number) { return this->client_->send_number_state(number); } | ||||
| INITIAL_STATE_HANDLER(number, number::Number) | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATE | ||||
| bool InitialStateIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_state(date); } | ||||
| INITIAL_STATE_HANDLER(date, datetime::DateEntity) | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
| bool InitialStateIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_state(time); } | ||||
| INITIAL_STATE_HANDLER(time, datetime::TimeEntity) | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
| bool InitialStateIterator::on_datetime(datetime::DateTimeEntity *datetime) { | ||||
|   return this->client_->send_datetime_state(datetime); | ||||
| } | ||||
| INITIAL_STATE_HANDLER(datetime, datetime::DateTimeEntity) | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
| bool InitialStateIterator::on_text(text::Text *text) { return this->client_->send_text_state(text); } | ||||
| INITIAL_STATE_HANDLER(text, text::Text) | ||||
| #endif | ||||
| #ifdef USE_SELECT | ||||
| bool InitialStateIterator::on_select(select::Select *select) { return this->client_->send_select_state(select); } | ||||
| INITIAL_STATE_HANDLER(select, select::Select) | ||||
| #endif | ||||
| #ifdef USE_LOCK | ||||
| bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock); } | ||||
| INITIAL_STATE_HANDLER(lock, lock::Lock) | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
| bool InitialStateIterator::on_valve(valve::Valve *valve) { return this->client_->send_valve_state(valve); } | ||||
| INITIAL_STATE_HANDLER(valve, valve::Valve) | ||||
| #endif | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
| bool InitialStateIterator::on_media_player(media_player::MediaPlayer *media_player) { | ||||
|   return this->client_->send_media_player_state(media_player); | ||||
| } | ||||
| INITIAL_STATE_HANDLER(media_player, media_player::MediaPlayer) | ||||
| #endif | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
| bool InitialStateIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { | ||||
|   return this->client_->send_alarm_control_panel_state(a_alarm_control_panel); | ||||
| } | ||||
| INITIAL_STATE_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPanel) | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
| bool InitialStateIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_state(update); } | ||||
| INITIAL_STATE_HANDLER(update, update::UpdateEntity) | ||||
| #endif | ||||
|  | ||||
| // Special cases (button and event) are already defined inline in subscribe_state.h | ||||
|  | ||||
| InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {} | ||||
|  | ||||
| }  // namespace api | ||||
|   | ||||
| @@ -10,71 +10,78 @@ namespace api { | ||||
|  | ||||
| class APIConnection; | ||||
|  | ||||
| // Macro for generating InitialStateIterator handlers | ||||
| // Calls send_*_state | ||||
| #define INITIAL_STATE_HANDLER(entity_type, EntityClass) \ | ||||
|   bool InitialStateIterator::on_##entity_type(EntityClass *entity) { /* NOLINT(bugprone-macro-parentheses) */ \ | ||||
|     return this->client_->send_##entity_type##_state(entity); \ | ||||
|   } | ||||
|  | ||||
| class InitialStateIterator : public ComponentIterator { | ||||
|  public: | ||||
|   InitialStateIterator(APIConnection *client); | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override; | ||||
|   bool on_binary_sensor(binary_sensor::BinarySensor *entity) override; | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
|   bool on_cover(cover::Cover *cover) override; | ||||
|   bool on_cover(cover::Cover *entity) override; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   bool on_fan(fan::Fan *fan) override; | ||||
|   bool on_fan(fan::Fan *entity) override; | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   bool on_light(light::LightState *light) override; | ||||
|   bool on_light(light::LightState *entity) override; | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|   bool on_sensor(sensor::Sensor *sensor) override; | ||||
|   bool on_sensor(sensor::Sensor *entity) override; | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|   bool on_switch(switch_::Switch *a_switch) override; | ||||
|   bool on_switch(switch_::Switch *entity) override; | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
|   bool on_button(button::Button *button) override { return true; }; | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; | ||||
|   bool on_text_sensor(text_sensor::TextSensor *entity) override; | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|   bool on_climate(climate::Climate *climate) override; | ||||
|   bool on_climate(climate::Climate *entity) override; | ||||
| #endif | ||||
| #ifdef USE_NUMBER | ||||
|   bool on_number(number::Number *number) override; | ||||
|   bool on_number(number::Number *entity) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATE | ||||
|   bool on_date(datetime::DateEntity *date) override; | ||||
|   bool on_date(datetime::DateEntity *entity) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   bool on_time(datetime::TimeEntity *time) override; | ||||
|   bool on_time(datetime::TimeEntity *entity) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   bool on_datetime(datetime::DateTimeEntity *datetime) override; | ||||
|   bool on_datetime(datetime::DateTimeEntity *entity) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
|   bool on_text(text::Text *text) override; | ||||
|   bool on_text(text::Text *entity) override; | ||||
| #endif | ||||
| #ifdef USE_SELECT | ||||
|   bool on_select(select::Select *select) override; | ||||
|   bool on_select(select::Select *entity) override; | ||||
| #endif | ||||
| #ifdef USE_LOCK | ||||
|   bool on_lock(lock::Lock *a_lock) override; | ||||
|   bool on_lock(lock::Lock *entity) override; | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
|   bool on_valve(valve::Valve *valve) override; | ||||
|   bool on_valve(valve::Valve *entity) override; | ||||
| #endif | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
|   bool on_media_player(media_player::MediaPlayer *media_player) override; | ||||
|   bool on_media_player(media_player::MediaPlayer *entity) override; | ||||
| #endif | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
|   bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override; | ||||
|   bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *entity) override; | ||||
| #endif | ||||
| #ifdef USE_EVENT | ||||
|   bool on_event(event::Event *event) override { return true; }; | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
|   bool on_update(update::UpdateEntity *update) override; | ||||
|   bool on_update(update::UpdateEntity *entity) override; | ||||
| #endif | ||||
|   bool completed() { return this->state_ == IteratorState::NONE; } | ||||
|  | ||||
|   | ||||
| @@ -50,7 +50,6 @@ class AS5600Component : public Component, public i2c::I2CDevice { | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   /// HARDWARE_LATE setup priority | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   // configuration setters | ||||
|   void set_dir_pin(InternalGPIOPin *pin) { this->dir_pin_ = pin; } | ||||
|   | ||||
| @@ -25,7 +25,6 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice | ||||
|  | ||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } | ||||
|   void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } | ||||
|   void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } | ||||
|   | ||||
| @@ -16,7 +16,6 @@ class BParasite : public Component, public esp32_ble_tracker::ESPBTDeviceListene | ||||
|  | ||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   void set_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; } | ||||
|   void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } | ||||
|   | ||||
| @@ -16,7 +16,6 @@ class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, publi | ||||
|  public: | ||||
|   void dump_config() override; | ||||
|   void loop() override {} | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } | ||||
|   void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } | ||||
|   void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } | ||||
|   | ||||
| @@ -18,7 +18,6 @@ class BLEClientRSSISensor : public sensor::Sensor, public PollingComponent, publ | ||||
|   void loop() override; | ||||
|   void update() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; | ||||
|  | ||||
|   | ||||
| @@ -24,7 +24,6 @@ class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClie | ||||
|   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||
|                            esp_ble_gattc_cb_param_t *param) override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } | ||||
|   void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } | ||||
|   void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } | ||||
|   | ||||
| @@ -19,7 +19,6 @@ class BLEClientSwitch : public switch_::Switch, public Component, public BLEClie | ||||
|   void loop() override {} | ||||
|   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||
|                            esp_ble_gattc_cb_param_t *param) override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|  protected: | ||||
|   void write_state(bool state) override; | ||||
|   | ||||
| @@ -20,7 +20,6 @@ class BLETextSensor : public text_sensor::TextSensor, public PollingComponent, p | ||||
|   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||
|                            esp_ble_gattc_cb_param_t *param) override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } | ||||
|   void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } | ||||
|   void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } | ||||
|   | ||||
| @@ -105,7 +105,6 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff, | ||||
|       this->set_found_(false); | ||||
|   } | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|  protected: | ||||
|   void set_found_(bool state) { | ||||
|   | ||||
| @@ -99,7 +99,6 @@ class BLERSSISensor : public sensor::Sensor, public esp32_ble_tracker::ESPBTDevi | ||||
|     return false; | ||||
|   } | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|  protected: | ||||
|   enum MatchType { MATCH_BY_MAC_ADDRESS, MATCH_BY_IRK, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID }; | ||||
|   | ||||
| @@ -29,7 +29,6 @@ class BLEScanner : public text_sensor::TextSensor, public esp32_ble_tracker::ESP | ||||
|     return true; | ||||
|   } | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
| }; | ||||
|  | ||||
| }  // namespace ble_scanner | ||||
|   | ||||
| @@ -61,8 +61,6 @@ enum IIRFilter { | ||||
|  | ||||
| class BMP581Component : public PollingComponent, public i2c::I2CDevice { | ||||
|  public: | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   void dump_config() override; | ||||
|  | ||||
|   void setup() override; | ||||
|   | ||||
| @@ -46,7 +46,6 @@ class CAP1188Component : public Component, public i2c::I2CDevice { | ||||
|   void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void loop() override; | ||||
|  | ||||
|  protected: | ||||
|   | ||||
| @@ -25,8 +25,6 @@ class CCS811Component : public PollingComponent, public i2c::I2CDevice { | ||||
|  | ||||
|   void dump_config() override; | ||||
|  | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|  protected: | ||||
|   optional<uint8_t> read_status_() { return this->read_byte(0x00); } | ||||
|   bool status_has_error_() { return this->read_status_().value_or(1) & 1; } | ||||
|   | ||||
| @@ -11,7 +11,6 @@ class CopyBinarySensor : public binary_sensor::BinarySensor, public Component { | ||||
|   void set_source(binary_sensor::BinarySensor *source) { source_ = source; } | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|  protected: | ||||
|   binary_sensor::BinarySensor *source_; | ||||
|   | ||||
| @@ -10,7 +10,6 @@ class CopyButton : public button::Button, public Component { | ||||
|  public: | ||||
|   void set_source(button::Button *source) { source_ = source; } | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|  protected: | ||||
|   void press_action() override; | ||||
|   | ||||
| @@ -11,7 +11,6 @@ class CopyCover : public cover::Cover, public Component { | ||||
|   void set_source(cover::Cover *source) { source_ = source; } | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   cover::CoverTraits get_traits() override; | ||||
|  | ||||
|   | ||||
| @@ -11,7 +11,6 @@ class CopyFan : public fan::Fan, public Component { | ||||
|   void set_source(fan::Fan *source) { source_ = source; } | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   fan::FanTraits get_traits() override; | ||||
|  | ||||
|   | ||||
| @@ -11,7 +11,6 @@ class CopyLock : public lock::Lock, public Component { | ||||
|   void set_source(lock::Lock *source) { source_ = source; } | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|  protected: | ||||
|   void control(const lock::LockCall &call) override; | ||||
|   | ||||
| @@ -11,7 +11,6 @@ class CopyNumber : public number::Number, public Component { | ||||
|   void set_source(number::Number *source) { source_ = source; } | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|  protected: | ||||
|   void control(float value) override; | ||||
|   | ||||
| @@ -11,7 +11,6 @@ class CopySelect : public select::Select, public Component { | ||||
|   void set_source(select::Select *source) { source_ = source; } | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|  protected: | ||||
|   void control(const std::string &value) override; | ||||
|   | ||||
| @@ -11,7 +11,6 @@ class CopySensor : public sensor::Sensor, public Component { | ||||
|   void set_source(sensor::Sensor *source) { source_ = source; } | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|  protected: | ||||
|   sensor::Sensor *source_; | ||||
|   | ||||
| @@ -11,7 +11,6 @@ class CopySwitch : public switch_::Switch, public Component { | ||||
|   void set_source(switch_::Switch *source) { source_ = source; } | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|  protected: | ||||
|   void write_state(bool state) override; | ||||
|   | ||||
| @@ -11,7 +11,6 @@ class CopyText : public text::Text, public Component { | ||||
|   void set_source(text::Text *source) { source_ = source; } | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|  protected: | ||||
|   void control(const std::string &value) override; | ||||
|   | ||||
| @@ -11,7 +11,6 @@ class CopyTextSensor : public text_sensor::TextSensor, public Component { | ||||
|   void set_source(text_sensor::TextSensor *source) { source_ = source; } | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|  protected: | ||||
|   text_sensor::TextSensor *source_; | ||||
|   | ||||
| @@ -77,7 +77,6 @@ class CS5460AComponent : public Component, | ||||
|  | ||||
|   void setup() override; | ||||
|   void loop() override {} | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void dump_config() override; | ||||
|  | ||||
|  protected: | ||||
|   | ||||
| @@ -19,7 +19,6 @@ class DutyTimeSensor : public sensor::Sensor, public PollingComponent { | ||||
|   void update() override; | ||||
|   void loop() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   void start(); | ||||
|   void stop(); | ||||
|   | ||||
| @@ -18,7 +18,6 @@ class ENS160Component : public PollingComponent, public sensor::Sensor { | ||||
|   void setup() override; | ||||
|   void update() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|  protected: | ||||
|   void send_env_data_(); | ||||
|   | ||||
| @@ -25,7 +25,6 @@ class ES7210 : public audio_adc::AudioAdc, public Component, public i2c::I2CDevi | ||||
|    */ | ||||
|  public: | ||||
|   void setup() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void dump_config() override; | ||||
|  | ||||
|   void set_bits_per_sample(ES7210BitsPerSample bits_per_sample) { this->bits_per_sample_ = bits_per_sample; } | ||||
|   | ||||
| @@ -14,7 +14,6 @@ class ES7243E : public audio_adc::AudioAdc, public Component, public i2c::I2CDev | ||||
|    */ | ||||
|  public: | ||||
|   void setup() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void dump_config() override; | ||||
|  | ||||
|   bool set_mic_gain(float mic_gain) override; | ||||
|   | ||||
| @@ -14,7 +14,6 @@ class ES8156 : public audio_dac::AudioDac, public Component, public i2c::I2CDevi | ||||
|   ///////////////////////// | ||||
|  | ||||
|   void setup() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void dump_config() override; | ||||
|  | ||||
|   //////////////////////// | ||||
|   | ||||
| @@ -50,7 +50,6 @@ class ES8311 : public audio_dac::AudioDac, public Component, public i2c::I2CDevi | ||||
|   ///////////////////////// | ||||
|  | ||||
|   void setup() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void dump_config() override; | ||||
|  | ||||
|   //////////////////////// | ||||
|   | ||||
| @@ -38,7 +38,6 @@ class ES8388 : public audio_dac::AudioDac, public Component, public i2c::I2CDevi | ||||
|   ///////////////////////// | ||||
|  | ||||
|   void setup() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void dump_config() override; | ||||
|  | ||||
|   //////////////////////// | ||||
|   | ||||
| @@ -341,6 +341,7 @@ SUPPORTED_PLATFORMIO_ESP_IDF_5X = [ | ||||
| # List based on https://github.com/pioarduino/esp-idf/releases | ||||
| SUPPORTED_PIOARDUINO_ESP_IDF_5X = [ | ||||
|     cv.Version(5, 5, 0), | ||||
|     cv.Version(5, 4, 2), | ||||
|     cv.Version(5, 4, 1), | ||||
|     cv.Version(5, 4, 0), | ||||
|     cv.Version(5, 3, 3), | ||||
| @@ -758,6 +759,9 @@ async def to_code(config): | ||||
|         add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0", False) | ||||
|         add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1", False) | ||||
|  | ||||
|         # Disable dynamic log level control to save memory | ||||
|         add_idf_sdkconfig_option("CONFIG_LOG_DYNAMIC_LEVEL_CONTROL", False) | ||||
|  | ||||
|         # Set default CPU frequency | ||||
|         add_idf_sdkconfig_option(f"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_{freq}", True) | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| #include "ble.h" | ||||
| #include "ble_event_pool.h" | ||||
|  | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/helpers.h" | ||||
|   | ||||
| @@ -12,8 +12,8 @@ | ||||
| #include "esphome/core/helpers.h" | ||||
|  | ||||
| #include "ble_event.h" | ||||
| #include "ble_event_pool.h" | ||||
| #include "queue.h" | ||||
| #include "esphome/core/lock_free_queue.h" | ||||
| #include "esphome/core/event_pool.h" | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| @@ -148,8 +148,8 @@ class ESP32BLE : public Component { | ||||
|   std::vector<BLEStatusEventHandler *> ble_status_event_handlers_; | ||||
|   BLEComponentState state_{BLE_COMPONENT_STATE_OFF}; | ||||
|  | ||||
|   LockFreeQueue<BLEEvent, MAX_BLE_QUEUE_SIZE> ble_events_; | ||||
|   BLEEventPool<MAX_BLE_QUEUE_SIZE> ble_event_pool_; | ||||
|   esphome::LockFreeQueue<BLEEvent, MAX_BLE_QUEUE_SIZE> ble_events_; | ||||
|   esphome::EventPool<BLEEvent, MAX_BLE_QUEUE_SIZE> ble_event_pool_; | ||||
|   BLEAdvertising *advertising_{}; | ||||
|   esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; | ||||
|   uint32_t advertising_cycle_time_{}; | ||||
|   | ||||
| @@ -134,13 +134,13 @@ class BLEEvent { | ||||
|   } | ||||
|  | ||||
|   // Destructor to clean up heap allocations | ||||
|   ~BLEEvent() { this->cleanup_heap_data(); } | ||||
|   ~BLEEvent() { this->release(); } | ||||
|  | ||||
|   // Default constructor for pre-allocation in pool | ||||
|   BLEEvent() : type_(GAP) {} | ||||
|  | ||||
|   // Clean up any heap-allocated data | ||||
|   void cleanup_heap_data() { | ||||
|   // Invoked on return to EventPool - clean up any heap-allocated data | ||||
|   void release() { | ||||
|     if (this->type_ == GAP) { | ||||
|       return; | ||||
|     } | ||||
| @@ -161,19 +161,19 @@ class BLEEvent { | ||||
|  | ||||
|   // Load new event data for reuse (replaces previous event data) | ||||
|   void load_gap_event(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) { | ||||
|     this->cleanup_heap_data(); | ||||
|     this->release(); | ||||
|     this->type_ = GAP; | ||||
|     this->init_gap_data_(e, p); | ||||
|   } | ||||
|  | ||||
|   void load_gattc_event(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) { | ||||
|     this->cleanup_heap_data(); | ||||
|     this->release(); | ||||
|     this->type_ = GATTC; | ||||
|     this->init_gattc_data_(e, i, p); | ||||
|   } | ||||
|  | ||||
|   void load_gatts_event(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) { | ||||
|     this->cleanup_heap_data(); | ||||
|     this->release(); | ||||
|     this->type_ = GATTS; | ||||
|     this->init_gatts_data_(e, i, p); | ||||
|   } | ||||
|   | ||||
| @@ -1,72 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| #include <atomic> | ||||
| #include <cstddef> | ||||
| #include "ble_event.h" | ||||
| #include "queue.h" | ||||
| #include "esphome/core/helpers.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace esp32_ble { | ||||
|  | ||||
| // BLE Event Pool - On-demand pool of BLEEvent objects to avoid heap fragmentation | ||||
| // Events are allocated on first use and reused thereafter, growing to peak usage | ||||
| template<uint8_t SIZE> class BLEEventPool { | ||||
|  public: | ||||
|   BLEEventPool() : total_created_(0) {} | ||||
|  | ||||
|   ~BLEEventPool() { | ||||
|     // Clean up any remaining events in the free list | ||||
|     BLEEvent *event; | ||||
|     while ((event = this->free_list_.pop()) != nullptr) { | ||||
|       delete event; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Allocate an event from the pool | ||||
|   // Returns nullptr if pool is full | ||||
|   BLEEvent *allocate() { | ||||
|     // Try to get from free list first | ||||
|     BLEEvent *event = this->free_list_.pop(); | ||||
|     if (event != nullptr) | ||||
|       return event; | ||||
|  | ||||
|     // Need to create a new event | ||||
|     if (this->total_created_ >= SIZE) { | ||||
|       // Pool is at capacity | ||||
|       return nullptr; | ||||
|     } | ||||
|  | ||||
|     // Use internal RAM for better performance | ||||
|     RAMAllocator<BLEEvent> allocator(RAMAllocator<BLEEvent>::ALLOC_INTERNAL); | ||||
|     event = allocator.allocate(1); | ||||
|  | ||||
|     if (event == nullptr) { | ||||
|       // Memory allocation failed | ||||
|       return nullptr; | ||||
|     } | ||||
|  | ||||
|     // Placement new to construct the object | ||||
|     new (event) BLEEvent(); | ||||
|     this->total_created_++; | ||||
|     return event; | ||||
|   } | ||||
|  | ||||
|   // Return an event to the pool for reuse | ||||
|   void release(BLEEvent *event) { | ||||
|     if (event != nullptr) { | ||||
|       this->free_list_.push(event); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  private: | ||||
|   LockFreeQueue<BLEEvent, SIZE> free_list_;  // Free events ready for reuse | ||||
|   uint8_t total_created_;                    // Total events created (high water mark) | ||||
| }; | ||||
|  | ||||
| }  // namespace esp32_ble | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
| @@ -1,85 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| #include <atomic> | ||||
| #include <cstddef> | ||||
|  | ||||
| /* | ||||
|  * BLE events come in from a separate Task (thread) in the ESP32 stack. Rather | ||||
|  * than using mutex-based locking, this lock-free queue allows the BLE | ||||
|  * task to enqueue events without blocking. The main loop() then processes | ||||
|  * these events at a safer time. | ||||
|  * | ||||
|  * This is a Single-Producer Single-Consumer (SPSC) lock-free ring buffer. | ||||
|  * The BLE task is the only producer, and the main loop() is the only consumer. | ||||
|  */ | ||||
|  | ||||
| namespace esphome { | ||||
| namespace esp32_ble { | ||||
|  | ||||
| template<class T, uint8_t SIZE> class LockFreeQueue { | ||||
|  public: | ||||
|   LockFreeQueue() : head_(0), tail_(0), dropped_count_(0) {} | ||||
|  | ||||
|   bool push(T *element) { | ||||
|     if (element == nullptr) | ||||
|       return false; | ||||
|  | ||||
|     uint8_t current_tail = tail_.load(std::memory_order_relaxed); | ||||
|     uint8_t next_tail = (current_tail + 1) % SIZE; | ||||
|  | ||||
|     if (next_tail == head_.load(std::memory_order_acquire)) { | ||||
|       // Buffer full | ||||
|       dropped_count_.fetch_add(1, std::memory_order_relaxed); | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     buffer_[current_tail] = element; | ||||
|     tail_.store(next_tail, std::memory_order_release); | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   T *pop() { | ||||
|     uint8_t current_head = head_.load(std::memory_order_relaxed); | ||||
|  | ||||
|     if (current_head == tail_.load(std::memory_order_acquire)) { | ||||
|       return nullptr;  // Empty | ||||
|     } | ||||
|  | ||||
|     T *element = buffer_[current_head]; | ||||
|     head_.store((current_head + 1) % SIZE, std::memory_order_release); | ||||
|     return element; | ||||
|   } | ||||
|  | ||||
|   size_t size() const { | ||||
|     uint8_t tail = tail_.load(std::memory_order_acquire); | ||||
|     uint8_t head = head_.load(std::memory_order_acquire); | ||||
|     return (tail - head + SIZE) % SIZE; | ||||
|   } | ||||
|  | ||||
|   uint16_t get_and_reset_dropped_count() { return dropped_count_.exchange(0, std::memory_order_relaxed); } | ||||
|  | ||||
|   void increment_dropped_count() { dropped_count_.fetch_add(1, std::memory_order_relaxed); } | ||||
|  | ||||
|   bool empty() const { return head_.load(std::memory_order_acquire) == tail_.load(std::memory_order_acquire); } | ||||
|  | ||||
|   bool full() const { | ||||
|     uint8_t next_tail = (tail_.load(std::memory_order_relaxed) + 1) % SIZE; | ||||
|     return next_tail == head_.load(std::memory_order_acquire); | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   T *buffer_[SIZE]; | ||||
|   // Atomic: written by producer (push/increment), read+reset by consumer (get_and_reset) | ||||
|   std::atomic<uint16_t> dropped_count_;  // 65535 max - more than enough for drop tracking | ||||
|   // Atomic: written by consumer (pop), read by producer (push) to check if full | ||||
|   std::atomic<uint8_t> head_; | ||||
|   // Atomic: written by producer (push), read by consumer (pop) to check if empty | ||||
|   std::atomic<uint8_t> tail_; | ||||
| }; | ||||
|  | ||||
| }  // namespace esp32_ble | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
| @@ -52,7 +52,6 @@ class ESP32TouchComponent : public Component { | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   void loop() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   void on_shutdown() override; | ||||
|  | ||||
|   | ||||
| @@ -38,7 +38,6 @@ class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2 | ||||
|   void loop() override; | ||||
|   void dump_config() override; | ||||
|   void update() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; }; | ||||
|  | ||||
|   // I2C | ||||
|   void set_address(uint8_t address); | ||||
|   | ||||
| @@ -23,7 +23,6 @@ namespace ezo_pmp { | ||||
| class EzoPMP : public PollingComponent, public i2c::I2CDevice { | ||||
|  public: | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; }; | ||||
|  | ||||
|   void loop() override; | ||||
|   void update() override; | ||||
|   | ||||
| @@ -16,7 +16,6 @@ class FeedbackCover : public cover::Cover, public Component { | ||||
|   void setup() override; | ||||
|   void loop() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; }; | ||||
|  | ||||
|   Trigger<> *get_open_trigger() const { return this->open_trigger_; } | ||||
|   Trigger<> *get_close_trigger() const { return this->close_trigger_; } | ||||
|   | ||||
| @@ -18,7 +18,6 @@ class FS3000Component : public PollingComponent, public i2c::I2CDevice, public s | ||||
|   void update() override; | ||||
|  | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   void set_model(FS3000Model model) { this->model_ = model; } | ||||
|  | ||||
|   | ||||
| @@ -12,7 +12,6 @@ class GCJA5Component : public Component, public uart::UARTDevice { | ||||
|  public: | ||||
|   void dump_config() override; | ||||
|   void loop() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   void set_pm_1_0_sensor(sensor::Sensor *pm_1_0) { pm_1_0_sensor_ = pm_1_0; } | ||||
|   void set_pm_2_5_sensor(sensor::Sensor *pm_2_5) { pm_2_5_sensor_ = pm_2_5; } | ||||
|   | ||||
| @@ -15,7 +15,6 @@ class GP8403 : public Component, public i2c::I2CDevice { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   void set_voltage(gp8403::GP8403Voltage voltage) { this->voltage_ = voltage; } | ||||
|  | ||||
|   | ||||
| @@ -10,11 +10,24 @@ GPIOBinarySensor = gpio_ns.class_( | ||||
|     "GPIOBinarySensor", binary_sensor.BinarySensor, cg.Component | ||||
| ) | ||||
|  | ||||
| CONF_USE_INTERRUPT = "use_interrupt" | ||||
| CONF_INTERRUPT_TYPE = "interrupt_type" | ||||
|  | ||||
| INTERRUPT_TYPES = { | ||||
|     "RISING": gpio_ns.INTERRUPT_RISING_EDGE, | ||||
|     "FALLING": gpio_ns.INTERRUPT_FALLING_EDGE, | ||||
|     "ANY": gpio_ns.INTERRUPT_ANY_EDGE, | ||||
| } | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     binary_sensor.binary_sensor_schema(GPIOBinarySensor) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.Required(CONF_PIN): pins.gpio_input_pin_schema, | ||||
|             cv.Optional(CONF_USE_INTERRUPT, default=True): cv.boolean, | ||||
|             cv.Optional(CONF_INTERRUPT_TYPE, default="ANY"): cv.enum( | ||||
|                 INTERRUPT_TYPES, upper=True | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
| @@ -27,3 +40,7 @@ async def to_code(config): | ||||
|  | ||||
|     pin = await cg.gpio_pin_expression(config[CONF_PIN]) | ||||
|     cg.add(var.set_pin(pin)) | ||||
|  | ||||
|     cg.add(var.set_use_interrupt(config[CONF_USE_INTERRUPT])) | ||||
|     if config[CONF_USE_INTERRUPT]: | ||||
|         cg.add(var.set_interrupt_type(config[CONF_INTERRUPT_TYPE])) | ||||
|   | ||||
| @@ -6,17 +6,91 @@ namespace gpio { | ||||
|  | ||||
| static const char *const TAG = "gpio.binary_sensor"; | ||||
|  | ||||
| void IRAM_ATTR GPIOBinarySensorStore::gpio_intr(GPIOBinarySensorStore *arg) { | ||||
|   bool new_state = arg->isr_pin_.digital_read(); | ||||
|   if (new_state != arg->last_state_) { | ||||
|     arg->state_ = new_state; | ||||
|     arg->last_state_ = new_state; | ||||
|     arg->changed_ = true; | ||||
|     // Wake up the component from its disabled loop state | ||||
|     if (arg->component_ != nullptr) { | ||||
|       arg->component_->enable_loop_soon_any_context(); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| void GPIOBinarySensorStore::setup(InternalGPIOPin *pin, gpio::InterruptType type, Component *component) { | ||||
|   pin->setup(); | ||||
|   this->isr_pin_ = pin->to_isr(); | ||||
|   this->component_ = component; | ||||
|  | ||||
|   // Read initial state | ||||
|   this->last_state_ = pin->digital_read(); | ||||
|   this->state_ = this->last_state_; | ||||
|  | ||||
|   // Attach interrupt - from this point on, any changes will be caught by the interrupt | ||||
|   pin->attach_interrupt(&GPIOBinarySensorStore::gpio_intr, this, type); | ||||
| } | ||||
|  | ||||
| void GPIOBinarySensor::setup() { | ||||
|   this->pin_->setup(); | ||||
|   this->publish_initial_state(this->pin_->digital_read()); | ||||
|   if (this->use_interrupt_ && !this->pin_->is_internal()) { | ||||
|     ESP_LOGD(TAG, "GPIO is not internal, falling back to polling mode"); | ||||
|     this->use_interrupt_ = false; | ||||
|   } | ||||
|  | ||||
|   if (this->use_interrupt_) { | ||||
|     auto *internal_pin = static_cast<InternalGPIOPin *>(this->pin_); | ||||
|     this->store_.setup(internal_pin, this->interrupt_type_, this); | ||||
|     this->publish_initial_state(this->store_.get_state()); | ||||
|   } else { | ||||
|     this->pin_->setup(); | ||||
|     this->publish_initial_state(this->pin_->digital_read()); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void GPIOBinarySensor::dump_config() { | ||||
|   LOG_BINARY_SENSOR("", "GPIO Binary Sensor", this); | ||||
|   LOG_PIN("  Pin: ", this->pin_); | ||||
|   const char *mode = this->use_interrupt_ ? "interrupt" : "polling"; | ||||
|   ESP_LOGCONFIG(TAG, "  Mode: %s", mode); | ||||
|   if (this->use_interrupt_) { | ||||
|     const char *interrupt_type; | ||||
|     switch (this->interrupt_type_) { | ||||
|       case gpio::INTERRUPT_RISING_EDGE: | ||||
|         interrupt_type = "RISING_EDGE"; | ||||
|         break; | ||||
|       case gpio::INTERRUPT_FALLING_EDGE: | ||||
|         interrupt_type = "FALLING_EDGE"; | ||||
|         break; | ||||
|       case gpio::INTERRUPT_ANY_EDGE: | ||||
|         interrupt_type = "ANY_EDGE"; | ||||
|         break; | ||||
|       default: | ||||
|         interrupt_type = "UNKNOWN"; | ||||
|         break; | ||||
|     } | ||||
|     ESP_LOGCONFIG(TAG, "  Interrupt Type: %s", interrupt_type); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void GPIOBinarySensor::loop() { this->publish_state(this->pin_->digital_read()); } | ||||
| void GPIOBinarySensor::loop() { | ||||
|   if (this->use_interrupt_) { | ||||
|     if (this->store_.is_changed()) { | ||||
|       // Clear the flag immediately to minimize the window where we might miss changes | ||||
|       this->store_.clear_changed(); | ||||
|       // Read the state and publish it | ||||
|       // Note: If the ISR fires between clear_changed() and get_state(), that's fine - | ||||
|       // we'll process the new change on the next loop iteration | ||||
|       bool state = this->store_.get_state(); | ||||
|       this->publish_state(state); | ||||
|     } else { | ||||
|       // No changes, disable the loop until the next interrupt | ||||
|       this->disable_loop(); | ||||
|     } | ||||
|   } else { | ||||
|     this->publish_state(this->pin_->digital_read()); | ||||
|   } | ||||
| } | ||||
|  | ||||
| float GPIOBinarySensor::get_setup_priority() const { return setup_priority::HARDWARE; } | ||||
|  | ||||
|   | ||||
| @@ -2,14 +2,51 @@ | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/components/binary_sensor/binary_sensor.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace gpio { | ||||
|  | ||||
| // Store class for ISR data (no vtables, ISR-safe) | ||||
| class GPIOBinarySensorStore { | ||||
|  public: | ||||
|   void setup(InternalGPIOPin *pin, gpio::InterruptType type, Component *component); | ||||
|  | ||||
|   static void gpio_intr(GPIOBinarySensorStore *arg); | ||||
|  | ||||
|   bool get_state() const { | ||||
|     // No lock needed: state_ is atomically updated by ISR | ||||
|     // Volatile ensures we read the latest value | ||||
|     return this->state_; | ||||
|   } | ||||
|  | ||||
|   bool is_changed() const { | ||||
|     // Simple read of volatile bool - no clearing here | ||||
|     return this->changed_; | ||||
|   } | ||||
|  | ||||
|   void clear_changed() { | ||||
|     // Separate method to clear the flag | ||||
|     this->changed_ = false; | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   ISRInternalGPIOPin isr_pin_; | ||||
|   volatile bool state_{false}; | ||||
|   volatile bool last_state_{false}; | ||||
|   volatile bool changed_{false}; | ||||
|   Component *component_{nullptr};  // Pointer to the component for enable_loop_soon_any_context() | ||||
| }; | ||||
|  | ||||
| class GPIOBinarySensor : public binary_sensor::BinarySensor, public Component { | ||||
|  public: | ||||
|   // No destructor needed: ESPHome components are created at boot and live forever. | ||||
|   // Interrupts are only detached on reboot when memory is cleared anyway. | ||||
|  | ||||
|   void set_pin(GPIOPin *pin) { pin_ = pin; } | ||||
|   void set_use_interrupt(bool use_interrupt) { use_interrupt_ = use_interrupt; } | ||||
|   void set_interrupt_type(gpio::InterruptType type) { interrupt_type_ = type; } | ||||
|   // ========== INTERNAL METHODS ========== | ||||
|   // (In most use cases you won't need these) | ||||
|   /// Setup pin | ||||
| @@ -22,6 +59,9 @@ class GPIOBinarySensor : public binary_sensor::BinarySensor, public Component { | ||||
|  | ||||
|  protected: | ||||
|   GPIOPin *pin_; | ||||
|   bool use_interrupt_{true}; | ||||
|   gpio::InterruptType interrupt_type_{gpio::INTERRUPT_ANY_EDGE}; | ||||
|   GPIOBinarySensorStore store_; | ||||
| }; | ||||
|  | ||||
| }  // namespace gpio | ||||
|   | ||||
| @@ -22,8 +22,6 @@ class GroveGasMultichannelV2Component : public PollingComponent, public i2c::I2C | ||||
|  | ||||
|   void dump_config() override; | ||||
|  | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|  protected: | ||||
|   enum ErrorCode { | ||||
|     UNKNOWN, | ||||
|   | ||||
| @@ -13,7 +13,6 @@ class HE60rCover : public cover::Cover, public Component, public uart::UARTDevic | ||||
|   void setup() override; | ||||
|   void loop() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; }; | ||||
|  | ||||
|   void set_open_duration(uint32_t duration) { this->open_duration_ = duration; } | ||||
|   void set_close_duration(uint32_t duration) { this->close_duration_ = duration; } | ||||
|   | ||||
| @@ -18,7 +18,6 @@ class HONEYWELLABP2Sensor : public PollingComponent, public i2c::I2CDevice { | ||||
|   void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }; | ||||
|   void loop() override; | ||||
|   void update() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; }; | ||||
|   void dump_config() override; | ||||
|  | ||||
|   void read_sensor_data(); | ||||
|   | ||||
| @@ -1,5 +1,8 @@ | ||||
| import logging | ||||
|  | ||||
| from esphome import pins | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import esp32 | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_ADDRESS, | ||||
| @@ -12,6 +15,8 @@ from esphome.const import ( | ||||
|     CONF_SCL, | ||||
|     CONF_SDA, | ||||
|     CONF_TIMEOUT, | ||||
|     KEY_CORE, | ||||
|     KEY_FRAMEWORK_VERSION, | ||||
|     PLATFORM_ESP32, | ||||
|     PLATFORM_ESP8266, | ||||
|     PLATFORM_RP2040, | ||||
| @@ -19,6 +24,7 @@ from esphome.const import ( | ||||
| from esphome.core import CORE, coroutine_with_priority | ||||
| import esphome.final_validate as fv | ||||
|  | ||||
| LOGGER = logging.getLogger(__name__) | ||||
| CODEOWNERS = ["@esphome/core"] | ||||
| i2c_ns = cg.esphome_ns.namespace("i2c") | ||||
| I2CBus = i2c_ns.class_("I2CBus") | ||||
| @@ -41,6 +47,32 @@ def _bus_declare_type(value): | ||||
|     raise NotImplementedError | ||||
|  | ||||
|  | ||||
| def validate_config(config): | ||||
|     if ( | ||||
|         config[CONF_SCAN] | ||||
|         and CORE.is_esp32 | ||||
|         and CORE.using_esp_idf | ||||
|         and esp32.get_esp32_variant() | ||||
|         in [ | ||||
|             esp32.const.VARIANT_ESP32C5, | ||||
|             esp32.const.VARIANT_ESP32C6, | ||||
|             esp32.const.VARIANT_ESP32P4, | ||||
|         ] | ||||
|     ): | ||||
|         version: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] | ||||
|         if version.major == 5 and ( | ||||
|             (version.minor == 3 and version.patch <= 3) | ||||
|             or (version.minor == 4 and version.patch <= 1) | ||||
|         ): | ||||
|             LOGGER.warning( | ||||
|                 "There is a bug in esp-idf version %s that breaks I2C scan, I2C scan " | ||||
|                 "has been disabled, see https://github.com/esphome/issues/issues/7128", | ||||
|                 str(version), | ||||
|             ) | ||||
|             config[CONF_SCAN] = False | ||||
|     return config | ||||
|  | ||||
|  | ||||
| pin_with_input_and_output_support = pins.internal_gpio_pin_number( | ||||
|     {CONF_OUTPUT: True, CONF_INPUT: True} | ||||
| ) | ||||
| @@ -66,6 +98,7 @@ CONFIG_SCHEMA = cv.All( | ||||
|         } | ||||
|     ).extend(cv.COMPONENT_SCHEMA), | ||||
|     cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]), | ||||
|     validate_config, | ||||
| ) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -9,7 +9,6 @@ namespace i2c_device { | ||||
| class I2CDeviceComponent : public Component, public i2c::I2CDevice { | ||||
|  public: | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|  protected: | ||||
| }; | ||||
|   | ||||
| @@ -16,8 +16,6 @@ class IAQCore : public PollingComponent, public i2c::I2CDevice { | ||||
|   void update() override; | ||||
|   void dump_config() override; | ||||
|  | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|  protected: | ||||
|   sensor::Sensor *co2_{nullptr}; | ||||
|   sensor::Sensor *tvoc_{nullptr}; | ||||
|   | ||||
| @@ -13,8 +13,6 @@ class INA260Component : public PollingComponent, public i2c::I2CDevice { | ||||
|   void dump_config() override; | ||||
|   void update() override; | ||||
|  | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   void set_bus_voltage_sensor(sensor::Sensor *bus_voltage_sensor) { this->bus_voltage_sensor_ = bus_voltage_sensor; } | ||||
|   void set_current_sensor(sensor::Sensor *current_sensor) { this->current_sensor_ = current_sensor; } | ||||
|   void set_power_sensor(sensor::Sensor *power_sensor) { this->power_sensor_ = power_sensor; } | ||||
|   | ||||
| @@ -16,7 +16,6 @@ class InkbirdIbstH1Mini : public Component, public esp32_ble_tracker::ESPBTDevic | ||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; | ||||
|  | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } | ||||
|   void set_external_temperature(sensor::Sensor *external_temperature) { external_temperature_ = external_temperature; } | ||||
|   void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } | ||||
|   | ||||
| @@ -27,7 +27,6 @@ class IntegrationSensor : public sensor::Sensor, public Component { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void set_sensor(Sensor *sensor) { sensor_ = sensor; } | ||||
|   void set_time(IntegrationSensorTime time) { time_ = time; } | ||||
|   void set_method(IntegrationMethod method) { method_ = method; } | ||||
|   | ||||
| @@ -23,8 +23,6 @@ class IntervalTrigger : public Trigger<>, public PollingComponent { | ||||
|  | ||||
|   void set_startup_delay(const uint32_t startup_delay) { this->startup_delay_ = startup_delay; } | ||||
|  | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|  protected: | ||||
|   uint32_t startup_delay_{0}; | ||||
|   bool started_{false}; | ||||
|   | ||||
| @@ -10,9 +10,11 @@ namespace libretiny { | ||||
| static const char *const TAG = "lt.component"; | ||||
|  | ||||
| void LTComponent::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "LibreTiny:"); | ||||
|   ESP_LOGCONFIG(TAG, "  Version: %s", LT_BANNER_STR + 10); | ||||
|   ESP_LOGCONFIG(TAG, "  Loglevel: %u", LT_LOGLEVEL); | ||||
|   ESP_LOGCONFIG(TAG, | ||||
|                 "LibreTiny:\n" | ||||
|                 "  Version: %s\n" | ||||
|                 "  Loglevel: %u", | ||||
|                 LT_BANNER_STR + 10, LT_LOGLEVEL); | ||||
|  | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   if (this->version_ != nullptr) { | ||||
|   | ||||
| @@ -69,8 +69,8 @@ class ESPColorCorrection { | ||||
|  protected: | ||||
|   uint8_t gamma_table_[256]; | ||||
|   uint8_t gamma_reverse_table_[256]; | ||||
|   Color max_brightness_; | ||||
|   uint8_t local_brightness_{255}; | ||||
|   Color max_brightness_; | ||||
| }; | ||||
|  | ||||
| }  // namespace light | ||||
|   | ||||
| @@ -136,7 +136,7 @@ LightColorValues LightCall::validate_() { | ||||
|  | ||||
|   // Color mode check | ||||
|   if (this->color_mode_.has_value() && !traits.supports_color_mode(this->color_mode_.value())) { | ||||
|     ESP_LOGW(TAG, "'%s' - This light does not support color mode %s!", name, | ||||
|     ESP_LOGW(TAG, "'%s' does not support color mode %s", name, | ||||
|              LOG_STR_ARG(color_mode_to_human(this->color_mode_.value()))); | ||||
|     this->color_mode_.reset(); | ||||
|   } | ||||
| @@ -152,20 +152,20 @@ LightColorValues LightCall::validate_() { | ||||
|  | ||||
|   // Brightness exists check | ||||
|   if (this->brightness_.has_value() && *this->brightness_ > 0.0f && !(color_mode & ColorCapability::BRIGHTNESS)) { | ||||
|     ESP_LOGW(TAG, "'%s' - This light does not support setting brightness!", name); | ||||
|     ESP_LOGW(TAG, "'%s': setting brightness not supported", name); | ||||
|     this->brightness_.reset(); | ||||
|   } | ||||
|  | ||||
|   // Transition length possible check | ||||
|   if (this->transition_length_.has_value() && *this->transition_length_ != 0 && | ||||
|       !(color_mode & ColorCapability::BRIGHTNESS)) { | ||||
|     ESP_LOGW(TAG, "'%s' - This light does not support transitions!", name); | ||||
|     ESP_LOGW(TAG, "'%s': transitions not supported", name); | ||||
|     this->transition_length_.reset(); | ||||
|   } | ||||
|  | ||||
|   // Color brightness exists check | ||||
|   if (this->color_brightness_.has_value() && *this->color_brightness_ > 0.0f && !(color_mode & ColorCapability::RGB)) { | ||||
|     ESP_LOGW(TAG, "'%s' - This color mode does not support setting RGB brightness!", name); | ||||
|     ESP_LOGW(TAG, "'%s': color mode does not support setting RGB brightness", name); | ||||
|     this->color_brightness_.reset(); | ||||
|   } | ||||
|  | ||||
| @@ -173,7 +173,7 @@ LightColorValues LightCall::validate_() { | ||||
|   if ((this->red_.has_value() && *this->red_ > 0.0f) || (this->green_.has_value() && *this->green_ > 0.0f) || | ||||
|       (this->blue_.has_value() && *this->blue_ > 0.0f)) { | ||||
|     if (!(color_mode & ColorCapability::RGB)) { | ||||
|       ESP_LOGW(TAG, "'%s' - This color mode does not support setting RGB color!", name); | ||||
|       ESP_LOGW(TAG, "'%s': color mode does not support setting RGB color", name); | ||||
|       this->red_.reset(); | ||||
|       this->green_.reset(); | ||||
|       this->blue_.reset(); | ||||
| @@ -183,14 +183,14 @@ LightColorValues LightCall::validate_() { | ||||
|   // White value exists check | ||||
|   if (this->white_.has_value() && *this->white_ > 0.0f && | ||||
|       !(color_mode & ColorCapability::WHITE || color_mode & ColorCapability::COLD_WARM_WHITE)) { | ||||
|     ESP_LOGW(TAG, "'%s' - This color mode does not support setting white value!", name); | ||||
|     ESP_LOGW(TAG, "'%s': color mode does not support setting white value", name); | ||||
|     this->white_.reset(); | ||||
|   } | ||||
|  | ||||
|   // Color temperature exists check | ||||
|   if (this->color_temperature_.has_value() && | ||||
|       !(color_mode & ColorCapability::COLOR_TEMPERATURE || color_mode & ColorCapability::COLD_WARM_WHITE)) { | ||||
|     ESP_LOGW(TAG, "'%s' - This color mode does not support setting color temperature!", name); | ||||
|     ESP_LOGW(TAG, "'%s': color mode does not support setting color temperature", name); | ||||
|     this->color_temperature_.reset(); | ||||
|   } | ||||
|  | ||||
| @@ -198,7 +198,7 @@ LightColorValues LightCall::validate_() { | ||||
|   if ((this->cold_white_.has_value() && *this->cold_white_ > 0.0f) || | ||||
|       (this->warm_white_.has_value() && *this->warm_white_ > 0.0f)) { | ||||
|     if (!(color_mode & ColorCapability::COLD_WARM_WHITE)) { | ||||
|       ESP_LOGW(TAG, "'%s' - This color mode does not support setting cold/warm white value!", name); | ||||
|       ESP_LOGW(TAG, "'%s': color mode does not support setting cold/warm white value", name); | ||||
|       this->cold_white_.reset(); | ||||
|       this->warm_white_.reset(); | ||||
|     } | ||||
| @@ -208,7 +208,7 @@ LightColorValues LightCall::validate_() { | ||||
|   if (name_##_.has_value()) { \ | ||||
|     auto val = *name_##_; \ | ||||
|     if (val < (min) || val > (max)) { \ | ||||
|       ESP_LOGW(TAG, "'%s' - %s value %.2f is out of range [%.1f - %.1f]!", name, LOG_STR_LITERAL(upper_name), val, \ | ||||
|       ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, LOG_STR_LITERAL(upper_name), val, \ | ||||
|                (min), (max)); \ | ||||
|       name_##_ = clamp(val, (min), (max)); \ | ||||
|     } \ | ||||
| @@ -270,7 +270,7 @@ LightColorValues LightCall::validate_() { | ||||
|  | ||||
|   // Flash length check | ||||
|   if (this->has_flash_() && *this->flash_length_ == 0) { | ||||
|     ESP_LOGW(TAG, "'%s' - Flash length must be greater than zero!", name); | ||||
|     ESP_LOGW(TAG, "'%s': flash length must be greater than zero", name); | ||||
|     this->flash_length_.reset(); | ||||
|   } | ||||
|  | ||||
| @@ -284,18 +284,18 @@ LightColorValues LightCall::validate_() { | ||||
|  | ||||
|   // validate effect index | ||||
|   if (this->has_effect_() && *this->effect_ > this->parent_->effects_.size()) { | ||||
|     ESP_LOGW(TAG, "'%s' - Invalid effect index %" PRIu32 "!", name, *this->effect_); | ||||
|     ESP_LOGW(TAG, "'%s': invalid effect index %" PRIu32, name, *this->effect_); | ||||
|     this->effect_.reset(); | ||||
|   } | ||||
|  | ||||
|   if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) { | ||||
|     ESP_LOGW(TAG, "'%s' - Effect cannot be used together with transition/flash!", name); | ||||
|     ESP_LOGW(TAG, "'%s': effect cannot be used with transition/flash", name); | ||||
|     this->transition_length_.reset(); | ||||
|     this->flash_length_.reset(); | ||||
|   } | ||||
|  | ||||
|   if (this->has_flash_() && this->has_transition_()) { | ||||
|     ESP_LOGW(TAG, "'%s' - Flash cannot be used together with transition!", name); | ||||
|     ESP_LOGW(TAG, "'%s': flash cannot be used with transition", name); | ||||
|     this->transition_length_.reset(); | ||||
|   } | ||||
|  | ||||
| @@ -311,7 +311,7 @@ LightColorValues LightCall::validate_() { | ||||
|   } | ||||
|  | ||||
|   if (this->has_transition_() && !supports_transition) { | ||||
|     ESP_LOGW(TAG, "'%s' - Light does not support transitions!", name); | ||||
|     ESP_LOGW(TAG, "'%s': transitions not supported", name); | ||||
|     this->transition_length_.reset(); | ||||
|   } | ||||
|  | ||||
| @@ -320,7 +320,7 @@ LightColorValues LightCall::validate_() { | ||||
|   // Reason: When user turns off the light in frontend, the effect should also stop | ||||
|   if (!this->has_flash_() && !this->state_.value_or(v.is_on())) { | ||||
|     if (this->has_effect_()) { | ||||
|       ESP_LOGW(TAG, "'%s' - Cannot start an effect when turning off!", name); | ||||
|       ESP_LOGW(TAG, "'%s': cannot start effect when turning off", name); | ||||
|       this->effect_.reset(); | ||||
|     } else if (this->parent_->active_effect_index_ != 0 && explicit_turn_off_request) { | ||||
|       // Auto turn off effect | ||||
| @@ -348,7 +348,7 @@ void LightCall::transform_parameters_() { | ||||
|       !(*this->color_mode_ & ColorCapability::WHITE) &&                                                // | ||||
|       !(*this->color_mode_ & ColorCapability::COLOR_TEMPERATURE) &&                                    // | ||||
|       traits.get_min_mireds() > 0.0f && traits.get_max_mireds() > 0.0f) { | ||||
|     ESP_LOGD(TAG, "'%s' - Setting cold/warm white channels using white/color temperature values.", | ||||
|     ESP_LOGD(TAG, "'%s': setting cold/warm white channels using white/color temperature values", | ||||
|              this->parent_->get_name().c_str()); | ||||
|     if (this->color_temperature_.has_value()) { | ||||
|       const float color_temp = clamp(*this->color_temperature_, traits.get_min_mireds(), traits.get_max_mireds()); | ||||
| @@ -388,8 +388,8 @@ ColorMode LightCall::compute_color_mode_() { | ||||
|  | ||||
|   // Don't change if the current mode is suitable. | ||||
|   if (suitable_modes.count(current_mode) > 0) { | ||||
|     ESP_LOGI(TAG, "'%s' - Keeping current color mode %s for call without color mode.", | ||||
|              this->parent_->get_name().c_str(), LOG_STR_ARG(color_mode_to_human(current_mode))); | ||||
|     ESP_LOGI(TAG, "'%s': color mode not specified; retaining %s", this->parent_->get_name().c_str(), | ||||
|              LOG_STR_ARG(color_mode_to_human(current_mode))); | ||||
|     return current_mode; | ||||
|   } | ||||
|  | ||||
| @@ -398,7 +398,7 @@ ColorMode LightCall::compute_color_mode_() { | ||||
|     if (supported_modes.count(mode) == 0) | ||||
|       continue; | ||||
|  | ||||
|     ESP_LOGI(TAG, "'%s' - Using color mode %s for call without color mode.", this->parent_->get_name().c_str(), | ||||
|     ESP_LOGI(TAG, "'%s': color mode not specified; using %s", this->parent_->get_name().c_str(), | ||||
|              LOG_STR_ARG(color_mode_to_human(mode))); | ||||
|     return mode; | ||||
|   } | ||||
| @@ -406,8 +406,8 @@ ColorMode LightCall::compute_color_mode_() { | ||||
|   // There's no supported mode for this call, so warn, use the current more or a mode at random and let validation strip | ||||
|   // out whatever we don't support. | ||||
|   auto color_mode = current_mode != ColorMode::UNKNOWN ? current_mode : *supported_modes.begin(); | ||||
|   ESP_LOGW(TAG, "'%s' - No color mode suitable for this call supported, defaulting to %s!", | ||||
|            this->parent_->get_name().c_str(), LOG_STR_ARG(color_mode_to_human(color_mode))); | ||||
|   ESP_LOGW(TAG, "'%s': no suitable color mode supported; defaulting to %s", this->parent_->get_name().c_str(), | ||||
|            LOG_STR_ARG(color_mode_to_human(color_mode))); | ||||
|   return color_mode; | ||||
| } | ||||
| std::set<ColorMode> LightCall::get_suitable_color_modes_() { | ||||
| @@ -472,7 +472,7 @@ LightCall &LightCall::set_effect(const std::string &effect) { | ||||
|     } | ||||
|   } | ||||
|   if (!found) { | ||||
|     ESP_LOGW(TAG, "'%s' - No such effect '%s'", this->parent_->get_name().c_str(), effect.c_str()); | ||||
|     ESP_LOGW(TAG, "'%s': no such effect '%s'", this->parent_->get_name().c_str(), effect.c_str()); | ||||
|   } | ||||
|   return *this; | ||||
| } | ||||
|   | ||||
| @@ -225,6 +225,11 @@ class LightState : public EntityBase, public Component { | ||||
|   /// Gamma correction factor for the light. | ||||
|   float gamma_correct_{}; | ||||
|  | ||||
|   /// Whether the light value should be written in the next cycle. | ||||
|   bool next_write_{true}; | ||||
|   // for effects, true if a transformer (transition) is active. | ||||
|   bool is_transformer_active_ = false; | ||||
|  | ||||
|   /// Object used to store the persisted values of the light. | ||||
|   ESPPreferenceObject rtc_; | ||||
|  | ||||
| @@ -247,10 +252,6 @@ class LightState : public EntityBase, public Component { | ||||
|  | ||||
|   /// Restore mode of the light. | ||||
|   LightRestoreMode restore_mode_; | ||||
|   /// Whether the light value should be written in the next cycle. | ||||
|   bool next_write_{true}; | ||||
|   // for effects, true if a transformer (transition) is active. | ||||
|   bool is_transformer_active_ = false; | ||||
| }; | ||||
|  | ||||
| }  // namespace light | ||||
|   | ||||
| @@ -44,7 +44,6 @@ enum LTR390RESOLUTION { | ||||
|  | ||||
| class LTR390Component : public PollingComponent, public i2c::I2CDevice { | ||||
|  public: | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   void update() override; | ||||
|   | ||||
| @@ -25,7 +25,6 @@ class LTRAlsPs501Component : public PollingComponent, public i2c::I2CDevice { | ||||
|   // | ||||
|   // EspHome framework functions | ||||
|   // | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   void update() override; | ||||
|   | ||||
| @@ -25,7 +25,6 @@ class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice { | ||||
|   // | ||||
|   // EspHome framework functions | ||||
|   // | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   void update() override; | ||||
|   | ||||
| @@ -38,7 +38,6 @@ class MAX9611Component : public PollingComponent, public i2c::I2CDevice { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void update() override; | ||||
|   void set_voltage_sensor(sensor::Sensor *vs) { voltage_sensor_ = vs; } | ||||
|   void set_current_sensor(sensor::Sensor *cs) { current_sensor_ = cs; } | ||||
|   | ||||
| @@ -6,7 +6,11 @@ namespace mcp23xxx_base { | ||||
|  | ||||
| float MCP23XXXBase::get_setup_priority() const { return setup_priority::IO; } | ||||
|  | ||||
| void MCP23XXXGPIOPin::setup() { pin_mode(flags_); } | ||||
| void MCP23XXXGPIOPin::setup() { | ||||
|   pin_mode(flags_); | ||||
|   this->parent_->pin_interrupt_mode(this->pin_, this->interrupt_mode_); | ||||
| } | ||||
|  | ||||
| void MCP23XXXGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } | ||||
| bool MCP23XXXGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } | ||||
| void MCP23XXXGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } | ||||
|   | ||||
| @@ -24,8 +24,6 @@ class MCP9600Component : public PollingComponent, public i2c::I2CDevice { | ||||
|   void dump_config() override; | ||||
|   void update() override; | ||||
|  | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   void set_hot_junction(sensor::Sensor *hot_junction) { this->hot_junction_sensor_ = hot_junction; } | ||||
|   void set_cold_junction(sensor::Sensor *cold_junction) { this->cold_junction_sensor_ = cold_junction; } | ||||
|   void set_thermocouple_type(MCP9600ThermocoupleType thermocouple_type) { | ||||
|   | ||||
| @@ -34,7 +34,6 @@ class MopekaProCheck : public Component, public esp32_ble_tracker::ESPBTDeviceLi | ||||
|  | ||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void set_min_signal_quality(SensorReadQuality min) { this->min_signal_quality_ = min; }; | ||||
|  | ||||
|   void set_level(sensor::Sensor *level) { level_ = level; }; | ||||
|   | ||||
| @@ -48,7 +48,6 @@ class MopekaStdCheck : public Component, public esp32_ble_tracker::ESPBTDeviceLi | ||||
|  | ||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   void set_level(sensor::Sensor *level) { this->level_ = level; }; | ||||
|   void set_temperature(sensor::Sensor *temperature) { this->temperature_ = temperature; }; | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user