mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Merge branch 'dev' from upstream
This commit is contained in:
		| @@ -4,6 +4,7 @@ | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include <cmath> | ||||
| #include <numbers> | ||||
|  | ||||
| #ifdef USE_ESP8266 | ||||
| #include <core_esp8266_waveform.h> | ||||
| @@ -203,7 +204,7 @@ void AcDimmer::setup() { | ||||
| #endif | ||||
| } | ||||
| void AcDimmer::write_state(float state) { | ||||
|   state = std::acos(1 - (2 * state)) / 3.14159;  // RMS power compensation | ||||
|   state = std::acos(1 - (2 * state)) / std::numbers::pi;  // RMS power compensation | ||||
|   auto new_value = static_cast<uint16_t>(roundf(state * 65535)); | ||||
|   if (new_value != 0 && this->store_.value == 0) | ||||
|     this->store_.init_cycle = this->init_with_half_cycle_; | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -14,8 +14,8 @@ from esphome.const import ( | ||||
|     CONF_WEB_SERVER, | ||||
| ) | ||||
| from esphome.core import CORE, coroutine_with_priority | ||||
| from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||
| from esphome.cpp_generator import MockObjClass | ||||
| from esphome.cpp_helpers import setup_entity | ||||
|  | ||||
| CODEOWNERS = ["@grahambrown11", "@hwstar"] | ||||
| IS_PLATFORM_COMPONENT = True | ||||
| @@ -149,6 +149,9 @@ _ALARM_CONTROL_PANEL_SCHEMA = ( | ||||
| ) | ||||
|  | ||||
|  | ||||
| _ALARM_CONTROL_PANEL_SCHEMA.add_extra(entity_duplicate_validator("alarm_control_panel")) | ||||
|  | ||||
|  | ||||
| def alarm_control_panel_schema( | ||||
|     class_: MockObjClass, | ||||
|     *, | ||||
| @@ -190,7 +193,7 @@ ALARM_CONTROL_PANEL_CONDITION_SCHEMA = maybe_simple_id( | ||||
|  | ||||
|  | ||||
| async def setup_alarm_control_panel_core_(var, config): | ||||
|     await setup_entity(var, config) | ||||
|     await setup_entity(var, config, "alarm_control_panel") | ||||
|     for conf in config.get(CONF_ON_STATE, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         await automation.build_automation(trigger, [], conf) | ||||
|   | ||||
| @@ -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")], | ||||
|   | ||||
| @@ -188,6 +188,17 @@ message DeviceInfoRequest { | ||||
|   // Empty | ||||
| } | ||||
|  | ||||
| message AreaInfo { | ||||
|   uint32 area_id = 1; | ||||
|   string name = 2; | ||||
| } | ||||
|  | ||||
| message DeviceInfo { | ||||
|   uint32 device_id = 1; | ||||
|   string name = 2; | ||||
|   uint32 area_id = 3; | ||||
| } | ||||
|  | ||||
| message DeviceInfoResponse { | ||||
|   option (id) = 10; | ||||
|   option (source) = SOURCE_SERVER; | ||||
| @@ -236,6 +247,12 @@ message DeviceInfoResponse { | ||||
|  | ||||
|   // Supports receiving and saving api encryption key | ||||
|   bool api_encryption_supported = 19; | ||||
|  | ||||
|   repeated DeviceInfo devices = 20; | ||||
|   repeated AreaInfo areas = 21; | ||||
|  | ||||
|   // Top-level area info to phase out suggested_area | ||||
|   AreaInfo area = 22; | ||||
| } | ||||
|  | ||||
| message ListEntitiesRequest { | ||||
| @@ -280,6 +297,7 @@ message ListEntitiesBinarySensorResponse { | ||||
|   bool disabled_by_default = 7; | ||||
|   string icon = 8; | ||||
|   EntityCategory entity_category = 9; | ||||
|   uint32 device_id = 10; | ||||
| } | ||||
| message BinarySensorStateResponse { | ||||
|   option (id) = 21; | ||||
| @@ -315,6 +333,7 @@ message ListEntitiesCoverResponse { | ||||
|   string icon = 10; | ||||
|   EntityCategory entity_category = 11; | ||||
|   bool supports_stop = 12; | ||||
|   uint32 device_id = 13; | ||||
| } | ||||
|  | ||||
| enum LegacyCoverState { | ||||
| @@ -388,6 +407,7 @@ message ListEntitiesFanResponse { | ||||
|   string icon = 10; | ||||
|   EntityCategory entity_category = 11; | ||||
|   repeated string supported_preset_modes = 12; | ||||
|   uint32 device_id = 13; | ||||
| } | ||||
| enum FanSpeed { | ||||
|   FAN_SPEED_LOW = 0; | ||||
| @@ -471,6 +491,7 @@ message ListEntitiesLightResponse { | ||||
|   bool disabled_by_default = 13; | ||||
|   string icon = 14; | ||||
|   EntityCategory entity_category = 15; | ||||
|   uint32 device_id = 16; | ||||
| } | ||||
| message LightStateResponse { | ||||
|   option (id) = 24; | ||||
| @@ -563,6 +584,7 @@ message ListEntitiesSensorResponse { | ||||
|   SensorLastResetType legacy_last_reset_type = 11; | ||||
|   bool disabled_by_default = 12; | ||||
|   EntityCategory entity_category = 13; | ||||
|   uint32 device_id = 14; | ||||
| } | ||||
| message SensorStateResponse { | ||||
|   option (id) = 25; | ||||
| @@ -595,6 +617,7 @@ message ListEntitiesSwitchResponse { | ||||
|   bool disabled_by_default = 7; | ||||
|   EntityCategory entity_category = 8; | ||||
|   string device_class = 9; | ||||
|   uint32 device_id = 10; | ||||
| } | ||||
| message SwitchStateResponse { | ||||
|   option (id) = 26; | ||||
| @@ -632,6 +655,7 @@ message ListEntitiesTextSensorResponse { | ||||
|   bool disabled_by_default = 6; | ||||
|   EntityCategory entity_category = 7; | ||||
|   string device_class = 8; | ||||
|   uint32 device_id = 9; | ||||
| } | ||||
| message TextSensorStateResponse { | ||||
|   option (id) = 27; | ||||
| @@ -814,6 +838,7 @@ message ListEntitiesCameraResponse { | ||||
|   bool disabled_by_default = 5; | ||||
|   string icon = 6; | ||||
|   EntityCategory entity_category = 7; | ||||
|   uint32 device_id = 8; | ||||
| } | ||||
|  | ||||
| message CameraImageResponse { | ||||
| @@ -916,6 +941,7 @@ message ListEntitiesClimateResponse { | ||||
|   bool supports_target_humidity = 23; | ||||
|   float visual_min_humidity = 24; | ||||
|   float visual_max_humidity = 25; | ||||
|   uint32 device_id = 26; | ||||
| } | ||||
| message ClimateStateResponse { | ||||
|   option (id) = 47; | ||||
| @@ -999,6 +1025,7 @@ message ListEntitiesNumberResponse { | ||||
|   string unit_of_measurement = 11; | ||||
|   NumberMode mode = 12; | ||||
|   string device_class = 13; | ||||
|   uint32 device_id = 14; | ||||
| } | ||||
| message NumberStateResponse { | ||||
|   option (id) = 50; | ||||
| @@ -1039,6 +1066,7 @@ message ListEntitiesSelectResponse { | ||||
|   repeated string options = 6; | ||||
|   bool disabled_by_default = 7; | ||||
|   EntityCategory entity_category = 8; | ||||
|   uint32 device_id = 9; | ||||
| } | ||||
| message SelectStateResponse { | ||||
|   option (id) = 53; | ||||
| @@ -1081,6 +1109,7 @@ message ListEntitiesSirenResponse { | ||||
|   bool supports_duration = 8; | ||||
|   bool supports_volume = 9; | ||||
|   EntityCategory entity_category = 10; | ||||
|   uint32 device_id = 11; | ||||
| } | ||||
| message SirenStateResponse { | ||||
|   option (id) = 56; | ||||
| @@ -1144,6 +1173,7 @@ message ListEntitiesLockResponse { | ||||
|  | ||||
|   // Not yet implemented: | ||||
|   string code_format = 11; | ||||
|   uint32 device_id = 12; | ||||
| } | ||||
| message LockStateResponse { | ||||
|   option (id) = 59; | ||||
| @@ -1183,6 +1213,7 @@ message ListEntitiesButtonResponse { | ||||
|   bool disabled_by_default = 6; | ||||
|   EntityCategory entity_category = 7; | ||||
|   string device_class = 8; | ||||
|   uint32 device_id = 9; | ||||
| } | ||||
| message ButtonCommandRequest { | ||||
|   option (id) = 62; | ||||
| @@ -1238,6 +1269,8 @@ message ListEntitiesMediaPlayerResponse { | ||||
|   bool supports_pause = 8; | ||||
|  | ||||
|   repeated MediaPlayerSupportedFormat supported_formats = 9; | ||||
|  | ||||
|   uint32 device_id = 10; | ||||
| } | ||||
| message MediaPlayerStateResponse { | ||||
|   option (id) = 64; | ||||
| @@ -1778,6 +1811,7 @@ message ListEntitiesAlarmControlPanelResponse { | ||||
|   uint32 supported_features = 8; | ||||
|   bool requires_code = 9; | ||||
|   bool requires_code_to_arm = 10; | ||||
|   uint32 device_id = 11; | ||||
| } | ||||
|  | ||||
| message AlarmControlPanelStateResponse { | ||||
| @@ -1823,6 +1857,7 @@ message ListEntitiesTextResponse { | ||||
|   uint32 max_length = 9; | ||||
|   string pattern = 10; | ||||
|   TextMode mode = 11; | ||||
|   uint32 device_id = 12; | ||||
| } | ||||
| message TextStateResponse { | ||||
|   option (id) = 98; | ||||
| @@ -1863,6 +1898,7 @@ message ListEntitiesDateResponse { | ||||
|   string icon = 5; | ||||
|   bool disabled_by_default = 6; | ||||
|   EntityCategory entity_category = 7; | ||||
|   uint32 device_id = 8; | ||||
| } | ||||
| message DateStateResponse { | ||||
|   option (id) = 101; | ||||
| @@ -1906,6 +1942,7 @@ message ListEntitiesTimeResponse { | ||||
|   string icon = 5; | ||||
|   bool disabled_by_default = 6; | ||||
|   EntityCategory entity_category = 7; | ||||
|   uint32 device_id = 8; | ||||
| } | ||||
| message TimeStateResponse { | ||||
|   option (id) = 104; | ||||
| @@ -1952,6 +1989,7 @@ message ListEntitiesEventResponse { | ||||
|   string device_class = 8; | ||||
|  | ||||
|   repeated string event_types = 9; | ||||
|   uint32 device_id = 10; | ||||
| } | ||||
| message EventResponse { | ||||
|   option (id) = 108; | ||||
| @@ -1983,6 +2021,7 @@ message ListEntitiesValveResponse { | ||||
|   bool assumed_state = 9; | ||||
|   bool supports_position = 10; | ||||
|   bool supports_stop = 11; | ||||
|   uint32 device_id = 12; | ||||
| } | ||||
|  | ||||
| enum ValveOperation { | ||||
| @@ -2029,6 +2068,7 @@ message ListEntitiesDateTimeResponse { | ||||
|   string icon = 5; | ||||
|   bool disabled_by_default = 6; | ||||
|   EntityCategory entity_category = 7; | ||||
|   uint32 device_id = 8; | ||||
| } | ||||
| message DateTimeStateResponse { | ||||
|   option (id) = 113; | ||||
| @@ -2069,6 +2109,7 @@ message ListEntitiesUpdateResponse { | ||||
|   bool disabled_by_default = 6; | ||||
|   EntityCategory entity_category = 7; | ||||
|   string device_class = 8; | ||||
|   uint32 device_id = 9; | ||||
| } | ||||
| message UpdateStateResponse { | ||||
|   option (id) = 117; | ||||
|   | ||||
| @@ -28,8 +28,19 @@ | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| // Read a maximum of 5 messages per loop iteration to prevent starving other components. | ||||
| // This is a balance between API responsiveness and allowing other components to run. | ||||
| // Since each message could contain multiple protobuf messages when using packet batching, | ||||
| // this limits the number of messages processed, not the number of TCP packets. | ||||
| static constexpr uint8_t MAX_MESSAGES_PER_LOOP = 5; | ||||
| static constexpr uint8_t MAX_PING_RETRIES = 60; | ||||
| static constexpr uint16_t PING_RETRY_INTERVAL = 1000; | ||||
| static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2; | ||||
|  | ||||
| static const char *const TAG = "api.connection"; | ||||
| #ifdef USE_ESP32_CAMERA | ||||
| static const int ESP32_CAMERA_STOP_STREAM = 5000; | ||||
| #endif | ||||
|  | ||||
| APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) | ||||
|     : parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) { | ||||
| @@ -54,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(); | ||||
| @@ -83,21 +90,24 @@ APIConnection::~APIConnection() { | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void APIConnection::loop() { | ||||
|   if (this->remove_) | ||||
|     return; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void APIConnection::log_batch_item_(const DeferredBatch::BatchItem &item) { | ||||
|   // Set log-only mode | ||||
|   this->flags_.log_only_mode = true; | ||||
|  | ||||
|   if (!network::is_connected()) { | ||||
|     // when network is disconnected force disconnect immediately | ||||
|     // don't wait for timeout | ||||
|     this->on_fatal_error(); | ||||
|     ESP_LOGW(TAG, "%s: Network unavailable; disconnecting", this->get_client_combined_info().c_str()); | ||||
|     return; | ||||
|   } | ||||
|   if (this->next_close_) { | ||||
|   // 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->flags_.next_close) { | ||||
|     // requested a disconnect | ||||
|     this->helper_->close(); | ||||
|     this->remove_ = true; | ||||
|     this->flags_.remove = true; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
| @@ -109,72 +119,67 @@ void APIConnection::loop() { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   const uint32_t now = App.get_loop_component_start_time(); | ||||
|   // Check if socket has data ready before attempting to read | ||||
|   if (this->helper_->is_socket_ready()) { | ||||
|     ReadPacketBuffer buffer; | ||||
|     err = this->helper_->read_packet(&buffer); | ||||
|     if (err == APIError::WOULD_BLOCK) { | ||||
|       // pass | ||||
|     } else if (err != APIError::OK) { | ||||
|       on_fatal_error(); | ||||
|       if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) { | ||||
|         ESP_LOGW(TAG, "%s: Connection reset", this->get_client_combined_info().c_str()); | ||||
|       } else if (err == APIError::CONNECTION_CLOSED) { | ||||
|         ESP_LOGW(TAG, "%s: Connection closed", this->get_client_combined_info().c_str()); | ||||
|       } else { | ||||
|         ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->get_client_combined_info().c_str(), | ||||
|                  api_error_to_str(err), errno); | ||||
|       } | ||||
|       return; | ||||
|     } else { | ||||
|       this->last_traffic_ = App.get_loop_component_start_time(); | ||||
|       // read a packet | ||||
|       if (buffer.data_len > 0) { | ||||
|         this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]); | ||||
|       } else { | ||||
|         this->read_message(0, buffer.type, nullptr); | ||||
|       } | ||||
|       if (this->remove_) | ||||
|     // Read up to MAX_MESSAGES_PER_LOOP messages per loop to improve throughput | ||||
|     for (uint8_t message_count = 0; message_count < MAX_MESSAGES_PER_LOOP; message_count++) { | ||||
|       ReadPacketBuffer buffer; | ||||
|       err = this->helper_->read_packet(&buffer); | ||||
|       if (err == APIError::WOULD_BLOCK) { | ||||
|         // No more data available | ||||
|         break; | ||||
|       } else if (err != APIError::OK) { | ||||
|         on_fatal_error(); | ||||
|         if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) { | ||||
|           ESP_LOGW(TAG, "%s: Connection reset", this->get_client_combined_info().c_str()); | ||||
|         } else if (err == APIError::CONNECTION_CLOSED) { | ||||
|           ESP_LOGW(TAG, "%s: Connection closed", this->get_client_combined_info().c_str()); | ||||
|         } else { | ||||
|           ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->get_client_combined_info().c_str(), | ||||
|                    api_error_to_str(err), errno); | ||||
|         } | ||||
|         return; | ||||
|       } else { | ||||
|         this->last_traffic_ = now; | ||||
|         // read a packet | ||||
|         if (buffer.data_len > 0) { | ||||
|           this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]); | ||||
|         } else { | ||||
|           this->read_message(0, buffer.type, nullptr); | ||||
|         } | ||||
|         if (this->flags_.remove) | ||||
|           return; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Process deferred batch if scheduled | ||||
|   if (this->deferred_batch_.batch_scheduled && | ||||
|       App.get_loop_component_start_time() - 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_(); | ||||
|   } | ||||
|  | ||||
|   if (!this->list_entities_iterator_.completed()) | ||||
|   if (!this->list_entities_iterator_.completed()) { | ||||
|     this->list_entities_iterator_.advance(); | ||||
|   if (!this->initial_state_iterator_.completed() && this->list_entities_iterator_.completed()) | ||||
|   } else if (!this->initial_state_iterator_.completed()) { | ||||
|     this->initial_state_iterator_.advance(); | ||||
|   } | ||||
|  | ||||
|   static uint8_t max_ping_retries = 60; | ||||
|   static uint16_t ping_retry_interval = 1000; | ||||
|   const uint32_t now = App.get_loop_component_start_time(); | ||||
|   if (this->sent_ping_) { | ||||
|   if (this->flags_.sent_ping) { | ||||
|     // Disconnect if not responded within 2.5*keepalive | ||||
|     if (now - this->last_traffic_ > (KEEPALIVE_TIMEOUT_MS * 5) / 2) { | ||||
|     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_++; | ||||
|       std::string warn_str = str_sprintf("%s: Sending keepalive failed %u time(s);", | ||||
|                                          this->get_client_combined_info().c_str(), this->ping_retries_); | ||||
|       if (this->ping_retries_ >= max_ping_retries) { | ||||
|         on_fatal_error(); | ||||
|         ESP_LOGE(TAG, "%s disconnecting", warn_str.c_str()); | ||||
|       } else if (this->ping_retries_ >= 10) { | ||||
|         ESP_LOGW(TAG, "%s retrying in %u ms", warn_str.c_str(), ping_retry_interval); | ||||
|       } else { | ||||
|         ESP_LOGD(TAG, "%s retrying in %u ms", warn_str.c_str(), ping_retry_interval); | ||||
|       } | ||||
|     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 | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -197,22 +202,20 @@ void APIConnection::loop() { | ||||
|     // bool done = 3; | ||||
|     buffer.encode_bool(3, done); | ||||
|  | ||||
|     bool success = this->send_buffer(buffer, 44); | ||||
|     bool success = this->send_buffer(buffer, CameraImageResponse::MESSAGE_TYPE); | ||||
|  | ||||
|     if (success) { | ||||
|       this->image_reader_.consume_data(to_send); | ||||
|     } | ||||
|     if (success && done) { | ||||
|       this->image_reader_.return_image(); | ||||
|       if (done) { | ||||
|         this->image_reader_.return_image(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| #endif | ||||
|  | ||||
|   if (state_subs_at_ != -1) { | ||||
|   if (state_subs_at_ >= 0) { | ||||
|     const auto &subs = this->parent_->get_state_subs(); | ||||
|     if (state_subs_at_ >= (int) subs.size()) { | ||||
|       state_subs_at_ = -1; | ||||
|     } else { | ||||
|     if (state_subs_at_ < static_cast<int>(subs.size())) { | ||||
|       auto &it = subs[state_subs_at_]; | ||||
|       SubscribeHomeAssistantStateResponse resp; | ||||
|       resp.entity_id = it.entity_id; | ||||
| @@ -221,6 +224,8 @@ void APIConnection::loop() { | ||||
|       if (this->send_message(resp)) { | ||||
|         state_subs_at_++; | ||||
|       } | ||||
|     } else { | ||||
|       state_subs_at_ = -1; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -234,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); | ||||
| @@ -290,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) { | ||||
| @@ -321,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); | ||||
| @@ -385,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); | ||||
| @@ -447,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); | ||||
| @@ -542,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) { | ||||
| @@ -577,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) { | ||||
| @@ -618,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) { | ||||
| @@ -682,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); | ||||
| @@ -752,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) { | ||||
| @@ -806,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); | ||||
| @@ -843,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); | ||||
| @@ -882,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); | ||||
| @@ -908,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) { | ||||
| @@ -949,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) { | ||||
| @@ -985,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); | ||||
| @@ -1010,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) { | ||||
| @@ -1066,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); | ||||
| @@ -1114,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); | ||||
| @@ -1161,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; | ||||
| @@ -1169,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); | ||||
| @@ -1378,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); | ||||
| @@ -1430,10 +1379,7 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe | ||||
|  | ||||
| #ifdef USE_EVENT | ||||
| void APIConnection::send_event(event::Event *event, const std::string &event_type) { | ||||
|   this->schedule_message_(event, MessageCreator(event_type, EventResponse::MESSAGE_TYPE), EventResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_event_info(event::Event *event) { | ||||
|   this->schedule_message_(event, &APIConnection::try_send_event_info, ListEntitiesEventResponse::MESSAGE_TYPE); | ||||
|   this->schedule_message_(event, MessageCreator(event_type), EventResponse::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) { | ||||
| @@ -1480,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); | ||||
| @@ -1515,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 | ||||
| @@ -1556,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) { | ||||
| @@ -1567,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(); | ||||
| @@ -1619,6 +1564,23 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { | ||||
| #endif | ||||
| #ifdef USE_API_NOISE | ||||
|   resp.api_encryption_supported = true; | ||||
| #endif | ||||
| #ifdef USE_DEVICES | ||||
|   for (auto const &device : App.get_devices()) { | ||||
|     DeviceInfo device_info; | ||||
|     device_info.device_id = device->get_device_id(); | ||||
|     device_info.name = device->get_name(); | ||||
|     device_info.area_id = device->get_area_id(); | ||||
|     resp.devices.push_back(device_info); | ||||
|   } | ||||
| #endif | ||||
| #ifdef USE_AREAS | ||||
|   for (auto const &area : App.get_areas()) { | ||||
|     AreaInfo area_info; | ||||
|     area_info.area_id = area->get_area_id(); | ||||
|     area_info.name = area->get_name(); | ||||
|     resp.areas.push_back(area_info); | ||||
|   } | ||||
| #endif | ||||
|   return resp; | ||||
| } | ||||
| @@ -1664,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; | ||||
| @@ -1714,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) { | ||||
| @@ -1733,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; | ||||
| @@ -1744,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; | ||||
|   } | ||||
|  | ||||
| @@ -1768,7 +1735,8 @@ void APIConnection::process_batch_() { | ||||
|     const auto &item = this->deferred_batch_.items[0]; | ||||
|  | ||||
|     // Let the creator calculate size and encode if it fits | ||||
|     uint16_t payload_size = item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true); | ||||
|     uint16_t payload_size = | ||||
|         item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true, item.message_type); | ||||
|  | ||||
|     if (payload_size > 0 && | ||||
|         this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, item.message_type)) { | ||||
| @@ -1803,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(); | ||||
| @@ -1818,7 +1786,7 @@ void APIConnection::process_batch_() { | ||||
|   for (const auto &item : this->deferred_batch_.items) { | ||||
|     // Try to encode message | ||||
|     // The creator will calculate overhead to determine if the message fits | ||||
|     uint16_t payload_size = item.creator(item.entity, this, remaining_size, false); | ||||
|     uint16_t payload_size = item.creator(item.entity, this, remaining_size, false, item.message_type); | ||||
|  | ||||
|     if (payload_size == 0) { | ||||
|       // Message won't fit, stop processing | ||||
| @@ -1866,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 | ||||
| @@ -1881,21 +1858,23 @@ void APIConnection::process_batch_() { | ||||
| } | ||||
|  | ||||
| uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                                    bool is_single) const { | ||||
|   switch (message_type_) { | ||||
|     case 0:  // Function pointer | ||||
|       return data_.ptr(entity, conn, remaining_size, is_single); | ||||
|  | ||||
|                                                    bool is_single, uint16_t message_type) const { | ||||
|   if (has_tagged_string_ptr_()) { | ||||
|     // Handle string-based messages | ||||
|     switch (message_type) { | ||||
| #ifdef USE_EVENT | ||||
|     case EventResponse::MESSAGE_TYPE: { | ||||
|       auto *e = static_cast<event::Event *>(entity); | ||||
|       return APIConnection::try_send_event_response(e, *data_.string_ptr, conn, remaining_size, is_single); | ||||
|     } | ||||
|       case EventResponse::MESSAGE_TYPE: { | ||||
|         auto *e = static_cast<event::Event *>(entity); | ||||
|         return APIConnection::try_send_event_response(e, *get_string_ptr_(), conn, remaining_size, is_single); | ||||
|       } | ||||
| #endif | ||||
|  | ||||
|     default: | ||||
|       // Should not happen, return 0 to indicate no message | ||||
|       return 0; | ||||
|       default: | ||||
|         // Should not happen, return 0 to indicate no message | ||||
|         return 0; | ||||
|     } | ||||
|   } else { | ||||
|     // Function pointer case | ||||
|     return data_.ptr(entity, conn, remaining_size, is_single); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -1911,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; | ||||
| @@ -301,6 +282,9 @@ class APIConnection : public APIServerConnection { | ||||
|     response.icon = entity->get_icon(); | ||||
|     response.disabled_by_default = entity->is_disabled_by_default(); | ||||
|     response.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category()); | ||||
| #ifdef USE_DEVICES | ||||
|     response.device_id = entity->get_device_id(); | ||||
| #endif | ||||
|   } | ||||
|  | ||||
|   // Helper function to fill common entity state fields | ||||
| @@ -438,97 +422,86 @@ 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); | ||||
|  | ||||
|   // Optimized MessageCreator class using union dispatch | ||||
|   // Optimized MessageCreator class using tagged pointer | ||||
|   class MessageCreator { | ||||
|     // Ensure pointer alignment allows LSB tagging | ||||
|     static_assert(alignof(std::string *) > 1, "String pointer alignment must be > 1 for LSB tagging"); | ||||
|  | ||||
|    public: | ||||
|     // Constructor for function pointer (message_type = 0) | ||||
|     MessageCreator(MessageCreatorPtr ptr) : message_type_(0) { data_.ptr = ptr; } | ||||
|     // Constructor for function pointer | ||||
|     MessageCreator(MessageCreatorPtr ptr) { | ||||
|       // Function pointers are always aligned, so LSB is 0 | ||||
|       data_.ptr = ptr; | ||||
|     } | ||||
|  | ||||
|     // Constructor for string state capture | ||||
|     MessageCreator(const std::string &value, uint16_t msg_type) : message_type_(msg_type) { | ||||
|       data_.string_ptr = new std::string(value); | ||||
|     explicit MessageCreator(const std::string &str_value) { | ||||
|       // Allocate string and tag the pointer | ||||
|       auto *str = new std::string(str_value); | ||||
|       // Set LSB to 1 to indicate string pointer | ||||
|       data_.tagged = reinterpret_cast<uintptr_t>(str) | 1; | ||||
|     } | ||||
|  | ||||
|     // Destructor | ||||
|     ~MessageCreator() { | ||||
|       // Clean up string data for string-based message types | ||||
|       if (uses_string_data_()) { | ||||
|         delete data_.string_ptr; | ||||
|       if (has_tagged_string_ptr_()) { | ||||
|         delete get_string_ptr_(); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // Copy constructor | ||||
|     MessageCreator(const MessageCreator &other) : message_type_(other.message_type_) { | ||||
|       if (message_type_ == 0) { | ||||
|         data_.ptr = other.data_.ptr; | ||||
|       } else if (uses_string_data_()) { | ||||
|         data_.string_ptr = new std::string(*other.data_.string_ptr); | ||||
|     MessageCreator(const MessageCreator &other) { | ||||
|       if (other.has_tagged_string_ptr_()) { | ||||
|         auto *str = new std::string(*other.get_string_ptr_()); | ||||
|         data_.tagged = reinterpret_cast<uintptr_t>(str) | 1; | ||||
|       } else { | ||||
|         data_ = other.data_;  // For POD types | ||||
|         data_ = other.data_; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // Move constructor | ||||
|     MessageCreator(MessageCreator &&other) noexcept : data_(other.data_), message_type_(other.message_type_) { | ||||
|       other.message_type_ = 0;  // Reset other to function pointer type | ||||
|       other.data_.ptr = nullptr; | ||||
|     } | ||||
|     MessageCreator(MessageCreator &&other) noexcept : data_(other.data_) { other.data_.ptr = nullptr; } | ||||
|  | ||||
|     // Assignment operators (needed for batch deduplication) | ||||
|     MessageCreator &operator=(const MessageCreator &other) { | ||||
|       if (this != &other) { | ||||
|         // Clean up current string data if needed | ||||
|         if (uses_string_data_()) { | ||||
|           delete data_.string_ptr; | ||||
|         if (has_tagged_string_ptr_()) { | ||||
|           delete get_string_ptr_(); | ||||
|         } | ||||
|         // Copy new data | ||||
|         message_type_ = other.message_type_; | ||||
|         if (other.message_type_ == 0) { | ||||
|           data_.ptr = other.data_.ptr; | ||||
|         } else if (other.uses_string_data_()) { | ||||
|           data_.string_ptr = new std::string(*other.data_.string_ptr); | ||||
|         if (other.has_tagged_string_ptr_()) { | ||||
|           auto *str = new std::string(*other.get_string_ptr_()); | ||||
|           data_.tagged = reinterpret_cast<uintptr_t>(str) | 1; | ||||
|         } else { | ||||
|           data_ = other.data_; | ||||
|         } | ||||
| @@ -539,30 +512,35 @@ class APIConnection : public APIServerConnection { | ||||
|     MessageCreator &operator=(MessageCreator &&other) noexcept { | ||||
|       if (this != &other) { | ||||
|         // Clean up current string data if needed | ||||
|         if (uses_string_data_()) { | ||||
|           delete data_.string_ptr; | ||||
|         if (has_tagged_string_ptr_()) { | ||||
|           delete get_string_ptr_(); | ||||
|         } | ||||
|         // Move data | ||||
|         message_type_ = other.message_type_; | ||||
|         data_ = other.data_; | ||||
|         // Reset other to safe state | ||||
|         other.message_type_ = 0; | ||||
|         other.data_.ptr = nullptr; | ||||
|       } | ||||
|       return *this; | ||||
|     } | ||||
|  | ||||
|     // Call operator | ||||
|     uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) const; | ||||
|     // Call operator - now accepts message_type as parameter | ||||
|     uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single, | ||||
|                         uint16_t message_type) const; | ||||
|  | ||||
|    private: | ||||
|     // Helper to check if this message type uses heap-allocated strings | ||||
|     bool uses_string_data_() const { return message_type_ == EventResponse::MESSAGE_TYPE; } | ||||
|     union CreatorData { | ||||
|       MessageCreatorPtr ptr;    // 8 bytes | ||||
|       std::string *string_ptr;  // 8 bytes | ||||
|     } data_;                    // 8 bytes | ||||
|     uint16_t message_type_;     // 2 bytes (0 = function ptr, >0 = state capture) | ||||
|     // Check if this contains a string pointer | ||||
|     bool has_tagged_string_ptr_() const { return (data_.tagged & 1) != 0; } | ||||
|  | ||||
|     // Get the actual string pointer (clears the tag bit) | ||||
|     std::string *get_string_ptr_() const { | ||||
|       // NOLINTNEXTLINE(performance-no-int-to-ptr) | ||||
|       return reinterpret_cast<std::string *>(data_.tagged & ~uintptr_t(1)); | ||||
|     } | ||||
|  | ||||
|     union { | ||||
|       MessageCreatorPtr ptr; | ||||
|       uintptr_t tagged; | ||||
|     } data_;  // 4 bytes on 32-bit | ||||
|   }; | ||||
|  | ||||
|   // Generic batching mechanism for both state updates and entity info | ||||
| @@ -579,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 | ||||
| @@ -588,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. | ||||
| @@ -614,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) { | ||||
| @@ -627,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 | ||||
|   | ||||
| @@ -66,6 +66,17 @@ const char *api_error_to_str(APIError err) { | ||||
|   return "UNKNOWN"; | ||||
| } | ||||
|  | ||||
| // Default implementation for loop - handles sending buffered data | ||||
| APIError APIFrameHelper::loop() { | ||||
|   if (!this->tx_buf_.empty()) { | ||||
|     APIError err = try_send_tx_buf_(); | ||||
|     if (err != APIError::OK && err != APIError::WOULD_BLOCK) { | ||||
|       return err; | ||||
|     } | ||||
|   } | ||||
|   return APIError::OK;  // Convert WOULD_BLOCK to OK to avoid connection termination | ||||
| } | ||||
|  | ||||
| // Helper method to buffer data from IOVs | ||||
| void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) { | ||||
|   SendBuffer buffer; | ||||
| @@ -274,17 +285,21 @@ APIError APINoiseFrameHelper::init() { | ||||
| } | ||||
| /// Run through handshake messages (if in that phase) | ||||
| APIError APINoiseFrameHelper::loop() { | ||||
|   APIError err = state_action_(); | ||||
|   if (err != APIError::OK && err != APIError::WOULD_BLOCK) { | ||||
|     return err; | ||||
|   } | ||||
|   if (!this->tx_buf_.empty()) { | ||||
|     err = try_send_tx_buf_(); | ||||
|   // During handshake phase, process as many actions as possible until we can't progress | ||||
|   // socket_->ready() stays true until next main loop, but state_action() will return | ||||
|   // WOULD_BLOCK when no more data is available to read | ||||
|   while (state_ != State::DATA && this->socket_->ready()) { | ||||
|     APIError err = state_action_(); | ||||
|     if (err != APIError::OK && err != APIError::WOULD_BLOCK) { | ||||
|       return err; | ||||
|     } | ||||
|     if (err == APIError::WOULD_BLOCK) { | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|   return APIError::OK;  // Convert WOULD_BLOCK to OK to avoid connection termination | ||||
|  | ||||
|   // Use base class implementation for buffer sending | ||||
|   return APIFrameHelper::loop(); | ||||
| } | ||||
|  | ||||
| /** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter | ||||
| @@ -330,17 +345,15 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { | ||||
|       return APIError::WOULD_BLOCK; | ||||
|     } | ||||
|  | ||||
|     if (rx_header_buf_[0] != 0x01) { | ||||
|       state_ = State::FAILED; | ||||
|       HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]); | ||||
|       return APIError::BAD_INDICATOR; | ||||
|     } | ||||
|     // header reading done | ||||
|   } | ||||
|  | ||||
|   // read body | ||||
|   uint8_t indicator = rx_header_buf_[0]; | ||||
|   if (indicator != 0x01) { | ||||
|     state_ = State::FAILED; | ||||
|     HELPER_LOG("Bad indicator byte %u", indicator); | ||||
|     return APIError::BAD_INDICATOR; | ||||
|   } | ||||
|  | ||||
|   uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2]; | ||||
|  | ||||
|   if (state_ != State::DATA && msg_size > 128) { | ||||
| @@ -586,10 +599,6 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) { | ||||
|     return APIError::BAD_DATA_PACKET; | ||||
|   } | ||||
|  | ||||
|   // uint16_t type; | ||||
|   // uint16_t data_len; | ||||
|   // uint8_t *data; | ||||
|   // uint8_t *padding;  zero or more bytes to fill up the rest of the packet | ||||
|   uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1]; | ||||
|   uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3]; | ||||
|   if (data_len > msg_size - 4) { | ||||
| @@ -822,18 +831,12 @@ APIError APIPlaintextFrameHelper::init() { | ||||
|   state_ = State::DATA; | ||||
|   return APIError::OK; | ||||
| } | ||||
| /// Not used for plaintext | ||||
| APIError APIPlaintextFrameHelper::loop() { | ||||
|   if (state_ != State::DATA) { | ||||
|     return APIError::BAD_STATE; | ||||
|   } | ||||
|   if (!this->tx_buf_.empty()) { | ||||
|     APIError err = try_send_tx_buf_(); | ||||
|     if (err != APIError::OK && err != APIError::WOULD_BLOCK) { | ||||
|       return err; | ||||
|     } | ||||
|   } | ||||
|   return APIError::OK;  // Convert WOULD_BLOCK to OK to avoid connection termination | ||||
|   // Use base class implementation for buffer sending | ||||
|   return APIFrameHelper::loop(); | ||||
| } | ||||
|  | ||||
| /** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter | ||||
|   | ||||
| @@ -38,7 +38,7 @@ struct PacketInfo { | ||||
|       : message_type(type), offset(off), payload_size(size), padding(0) {} | ||||
| }; | ||||
|  | ||||
| enum class APIError : int { | ||||
| enum class APIError : uint16_t { | ||||
|   OK = 0, | ||||
|   WOULD_BLOCK = 1001, | ||||
|   BAD_HANDSHAKE_PACKET_LEN = 1002, | ||||
| @@ -74,7 +74,7 @@ class APIFrameHelper { | ||||
|   } | ||||
|   virtual ~APIFrameHelper() = default; | ||||
|   virtual APIError init() = 0; | ||||
|   virtual APIError loop() = 0; | ||||
|   virtual APIError loop(); | ||||
|   virtual APIError read_packet(ReadPacketBuffer *buffer) = 0; | ||||
|   bool can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); } | ||||
|   std::string getpeername() { return socket_->getpeername(); } | ||||
|   | ||||
| @@ -812,6 +812,103 @@ void PingResponse::dump_to(std::string &out) const { out.append("PingResponse {} | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfoRequest {}"); } | ||||
| #endif | ||||
| bool AreaInfo::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->area_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool AreaInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||
|   switch (field_id) { | ||||
|     case 2: { | ||||
|       this->name = value.as_string(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void AreaInfo::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_uint32(1, this->area_id); | ||||
|   buffer.encode_string(2, this->name); | ||||
| } | ||||
| void AreaInfo::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->area_id, false); | ||||
|   ProtoSize::add_string_field(total_size, 1, this->name, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void AreaInfo::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("AreaInfo {\n"); | ||||
|   out.append("  area_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->area_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  name: "); | ||||
|   out.append("'").append(this->name).append("'"); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| bool DeviceInfo::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     case 3: { | ||||
|       this->area_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool DeviceInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||
|   switch (field_id) { | ||||
|     case 2: { | ||||
|       this->name = value.as_string(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void DeviceInfo::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_uint32(1, this->device_id); | ||||
|   buffer.encode_string(2, this->name); | ||||
|   buffer.encode_uint32(3, this->area_id); | ||||
| } | ||||
| void DeviceInfo::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
|   ProtoSize::add_string_field(total_size, 1, this->name, false); | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->area_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void DeviceInfo::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("DeviceInfo {\n"); | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  name: "); | ||||
|   out.append("'").append(this->name).append("'"); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  area_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->area_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
| @@ -896,6 +993,18 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v | ||||
|       this->bluetooth_mac_address = value.as_string(); | ||||
|       return true; | ||||
|     } | ||||
|     case 20: { | ||||
|       this->devices.push_back(value.as_message<DeviceInfo>()); | ||||
|       return true; | ||||
|     } | ||||
|     case 21: { | ||||
|       this->areas.push_back(value.as_message<AreaInfo>()); | ||||
|       return true; | ||||
|     } | ||||
|     case 22: { | ||||
|       this->area = value.as_message<AreaInfo>(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -920,6 +1029,13 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_string(16, this->suggested_area); | ||||
|   buffer.encode_string(18, this->bluetooth_mac_address); | ||||
|   buffer.encode_bool(19, this->api_encryption_supported); | ||||
|   for (auto &it : this->devices) { | ||||
|     buffer.encode_message<DeviceInfo>(20, it, true); | ||||
|   } | ||||
|   for (auto &it : this->areas) { | ||||
|     buffer.encode_message<AreaInfo>(21, it, true); | ||||
|   } | ||||
|   buffer.encode_message<AreaInfo>(22, this->area); | ||||
| } | ||||
| void DeviceInfoResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->uses_password, false); | ||||
| @@ -941,6 +1057,9 @@ void DeviceInfoResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 2, this->suggested_area, false); | ||||
|   ProtoSize::add_string_field(total_size, 2, this->bluetooth_mac_address, false); | ||||
|   ProtoSize::add_bool_field(total_size, 2, this->api_encryption_supported, false); | ||||
|   ProtoSize::add_repeated_message(total_size, 2, this->devices); | ||||
|   ProtoSize::add_repeated_message(total_size, 2, this->areas); | ||||
|   ProtoSize::add_message_object(total_size, 2, this->area, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void DeviceInfoResponse::dump_to(std::string &out) const { | ||||
| @@ -1026,6 +1145,22 @@ void DeviceInfoResponse::dump_to(std::string &out) const { | ||||
|   out.append("  api_encryption_supported: "); | ||||
|   out.append(YESNO(this->api_encryption_supported)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   for (const auto &it : this->devices) { | ||||
|     out.append("  devices: "); | ||||
|     it.dump_to(out); | ||||
|     out.append("\n"); | ||||
|   } | ||||
|  | ||||
|   for (const auto &it : this->areas) { | ||||
|     out.append("  areas: "); | ||||
|     it.dump_to(out); | ||||
|     out.append("\n"); | ||||
|   } | ||||
|  | ||||
|   out.append("  area: "); | ||||
|   this->area.dump_to(out); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -1052,6 +1187,10 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     case 10: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -1102,6 +1241,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_bool(7, this->disabled_by_default); | ||||
|   buffer.encode_string(8, this->icon); | ||||
|   buffer.encode_enum<enums::EntityCategory>(9, this->entity_category); | ||||
|   buffer.encode_uint32(10, this->device_id); | ||||
| } | ||||
| void ListEntitiesBinarySensorResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -1113,6 +1253,7 @@ void ListEntitiesBinarySensorResponse::calculate_size(uint32_t &total_size) cons | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||
|   ProtoSize::add_string_field(total_size, 1, this->icon, false); | ||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { | ||||
| @@ -1154,6 +1295,11 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { | ||||
|   out.append("  entity_category: "); | ||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -1236,6 +1382,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val | ||||
|       this->supports_stop = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     case 13: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -1289,6 +1439,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_string(10, this->icon); | ||||
|   buffer.encode_enum<enums::EntityCategory>(11, this->entity_category); | ||||
|   buffer.encode_bool(12, this->supports_stop); | ||||
|   buffer.encode_uint32(13, this->device_id); | ||||
| } | ||||
| void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -1303,6 +1454,7 @@ void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->icon, false); | ||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false); | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesCoverResponse::dump_to(std::string &out) const { | ||||
| @@ -1356,6 +1508,11 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { | ||||
|   out.append("  supports_stop: "); | ||||
|   out.append(YESNO(this->supports_stop)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -1565,6 +1722,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     case 13: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -1620,6 +1781,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   for (auto &it : this->supported_preset_modes) { | ||||
|     buffer.encode_string(12, it, true); | ||||
|   } | ||||
|   buffer.encode_uint32(13, this->device_id); | ||||
| } | ||||
| void ListEntitiesFanResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -1638,6 +1800,7 @@ void ListEntitiesFanResponse::calculate_size(uint32_t &total_size) const { | ||||
|       ProtoSize::add_string_field(total_size, 1, it, true); | ||||
|     } | ||||
|   } | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesFanResponse::dump_to(std::string &out) const { | ||||
| @@ -1694,6 +1857,11 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { | ||||
|     out.append("'").append(it).append("'"); | ||||
|     out.append("\n"); | ||||
|   } | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -1987,6 +2155,10 @@ bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt val | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     case 16: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -2055,6 +2227,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_bool(13, this->disabled_by_default); | ||||
|   buffer.encode_string(14, this->icon); | ||||
|   buffer.encode_enum<enums::EntityCategory>(15, this->entity_category); | ||||
|   buffer.encode_uint32(16, this->device_id); | ||||
| } | ||||
| void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -2080,6 +2253,7 @@ void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||
|   ProtoSize::add_string_field(total_size, 1, this->icon, false); | ||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||
|   ProtoSize::add_uint32_field(total_size, 2, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesLightResponse::dump_to(std::string &out) const { | ||||
| @@ -2151,6 +2325,11 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { | ||||
|   out.append("  entity_category: "); | ||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -2658,6 +2837,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     case 14: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -2716,6 +2899,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_enum<enums::SensorLastResetType>(11, this->legacy_last_reset_type); | ||||
|   buffer.encode_bool(12, this->disabled_by_default); | ||||
|   buffer.encode_enum<enums::EntityCategory>(13, this->entity_category); | ||||
|   buffer.encode_uint32(14, this->device_id); | ||||
| } | ||||
| void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -2731,6 +2915,7 @@ void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->legacy_last_reset_type), false); | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesSensorResponse::dump_to(std::string &out) const { | ||||
| @@ -2789,6 +2974,11 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { | ||||
|   out.append("  entity_category: "); | ||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -2860,6 +3050,10 @@ bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt va | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     case 10: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -2910,6 +3104,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_bool(7, this->disabled_by_default); | ||||
|   buffer.encode_enum<enums::EntityCategory>(8, this->entity_category); | ||||
|   buffer.encode_string(9, this->device_class); | ||||
|   buffer.encode_uint32(10, this->device_id); | ||||
| } | ||||
| void ListEntitiesSwitchResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -2921,6 +3116,7 @@ void ListEntitiesSwitchResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||
|   ProtoSize::add_string_field(total_size, 1, this->device_class, false); | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesSwitchResponse::dump_to(std::string &out) const { | ||||
| @@ -2962,6 +3158,11 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const { | ||||
|   out.append("  device_class: "); | ||||
|   out.append("'").append(this->device_class).append("'"); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -3061,6 +3262,10 @@ bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarIn | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     case 9: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -3110,6 +3315,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_bool(6, this->disabled_by_default); | ||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||
|   buffer.encode_string(8, this->device_class); | ||||
|   buffer.encode_uint32(9, this->device_id); | ||||
| } | ||||
| void ListEntitiesTextSensorResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -3120,6 +3326,7 @@ void ListEntitiesTextSensorResponse::calculate_size(uint32_t &total_size) const | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||
|   ProtoSize::add_string_field(total_size, 1, this->device_class, false); | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { | ||||
| @@ -3157,6 +3364,11 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { | ||||
|   out.append("  device_class: "); | ||||
|   out.append("'").append(this->device_class).append("'"); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -3922,6 +4134,10 @@ bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt va | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     case 8: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -3966,6 +4182,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_bool(5, this->disabled_by_default); | ||||
|   buffer.encode_string(6, this->icon); | ||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||
|   buffer.encode_uint32(8, this->device_id); | ||||
| } | ||||
| void ListEntitiesCameraResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -3975,6 +4192,7 @@ void ListEntitiesCameraResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||
|   ProtoSize::add_string_field(total_size, 1, this->icon, false); | ||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesCameraResponse::dump_to(std::string &out) const { | ||||
| @@ -4008,6 +4226,11 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const { | ||||
|   out.append("  entity_category: "); | ||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -4156,6 +4379,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v | ||||
|       this->supports_target_humidity = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     case 26: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -4262,6 +4489,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_bool(23, this->supports_target_humidity); | ||||
|   buffer.encode_float(24, this->visual_min_humidity); | ||||
|   buffer.encode_float(25, this->visual_max_humidity); | ||||
|   buffer.encode_uint32(26, this->device_id); | ||||
| } | ||||
| void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -4313,6 +4541,7 @@ void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_bool_field(total_size, 2, this->supports_target_humidity, false); | ||||
|   ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_min_humidity != 0.0f, false); | ||||
|   ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_max_humidity != 0.0f, false); | ||||
|   ProtoSize::add_uint32_field(total_size, 2, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesClimateResponse::dump_to(std::string &out) const { | ||||
| @@ -4436,6 +4665,11 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { | ||||
|   sprintf(buffer, "%g", this->visual_max_humidity); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -4901,6 +5135,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va | ||||
|       this->mode = value.as_enum<enums::NumberMode>(); | ||||
|       return true; | ||||
|     } | ||||
|     case 14: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -4971,6 +5209,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_string(11, this->unit_of_measurement); | ||||
|   buffer.encode_enum<enums::NumberMode>(12, this->mode); | ||||
|   buffer.encode_string(13, this->device_class); | ||||
|   buffer.encode_uint32(14, this->device_id); | ||||
| } | ||||
| void ListEntitiesNumberResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -4986,6 +5225,7 @@ void ListEntitiesNumberResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->unit_of_measurement, false); | ||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->mode), false); | ||||
|   ProtoSize::add_string_field(total_size, 1, this->device_class, false); | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesNumberResponse::dump_to(std::string &out) const { | ||||
| @@ -5046,6 +5286,11 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { | ||||
|   out.append("  device_class: "); | ||||
|   out.append("'").append(this->device_class).append("'"); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -5151,6 +5396,10 @@ bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt va | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     case 9: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -5202,6 +5451,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   } | ||||
|   buffer.encode_bool(7, this->disabled_by_default); | ||||
|   buffer.encode_enum<enums::EntityCategory>(8, this->entity_category); | ||||
|   buffer.encode_uint32(9, this->device_id); | ||||
| } | ||||
| void ListEntitiesSelectResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -5216,6 +5466,7 @@ void ListEntitiesSelectResponse::calculate_size(uint32_t &total_size) const { | ||||
|   } | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesSelectResponse::dump_to(std::string &out) const { | ||||
| @@ -5255,6 +5506,11 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const { | ||||
|   out.append("  entity_category: "); | ||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -5378,6 +5634,10 @@ bool ListEntitiesSirenResponse::decode_varint(uint32_t field_id, ProtoVarInt val | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     case 11: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -5431,6 +5691,7 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_bool(8, this->supports_duration); | ||||
|   buffer.encode_bool(9, this->supports_volume); | ||||
|   buffer.encode_enum<enums::EntityCategory>(10, this->entity_category); | ||||
|   buffer.encode_uint32(11, this->device_id); | ||||
| } | ||||
| void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -5447,6 +5708,7 @@ void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->supports_duration, false); | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->supports_volume, false); | ||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesSirenResponse::dump_to(std::string &out) const { | ||||
| @@ -5494,6 +5756,11 @@ void ListEntitiesSirenResponse::dump_to(std::string &out) const { | ||||
|   out.append("  entity_category: "); | ||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -5683,6 +5950,10 @@ bool ListEntitiesLockResponse::decode_varint(uint32_t field_id, ProtoVarInt valu | ||||
|       this->requires_code = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     case 12: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -5735,6 +6006,7 @@ void ListEntitiesLockResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_bool(9, this->supports_open); | ||||
|   buffer.encode_bool(10, this->requires_code); | ||||
|   buffer.encode_string(11, this->code_format); | ||||
|   buffer.encode_uint32(12, this->device_id); | ||||
| } | ||||
| void ListEntitiesLockResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -5748,6 +6020,7 @@ void ListEntitiesLockResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->supports_open, false); | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->requires_code, false); | ||||
|   ProtoSize::add_string_field(total_size, 1, this->code_format, false); | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesLockResponse::dump_to(std::string &out) const { | ||||
| @@ -5797,6 +6070,11 @@ void ListEntitiesLockResponse::dump_to(std::string &out) const { | ||||
|   out.append("  code_format: "); | ||||
|   out.append("'").append(this->code_format).append("'"); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -5922,6 +6200,10 @@ bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt va | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     case 9: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -5971,6 +6253,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_bool(6, this->disabled_by_default); | ||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||
|   buffer.encode_string(8, this->device_class); | ||||
|   buffer.encode_uint32(9, this->device_id); | ||||
| } | ||||
| void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -5981,6 +6264,7 @@ void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||
|   ProtoSize::add_string_field(total_size, 1, this->device_class, false); | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesButtonResponse::dump_to(std::string &out) const { | ||||
| @@ -6018,6 +6302,11 @@ void ListEntitiesButtonResponse::dump_to(std::string &out) const { | ||||
|   out.append("  device_class: "); | ||||
|   out.append("'").append(this->device_class).append("'"); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -6135,6 +6424,10 @@ bool ListEntitiesMediaPlayerResponse::decode_varint(uint32_t field_id, ProtoVarI | ||||
|       this->supports_pause = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     case 10: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -6187,6 +6480,7 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   for (auto &it : this->supported_formats) { | ||||
|     buffer.encode_message<MediaPlayerSupportedFormat>(9, it, true); | ||||
|   } | ||||
|   buffer.encode_uint32(10, this->device_id); | ||||
| } | ||||
| void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -6198,6 +6492,7 @@ void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const | ||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->supports_pause, false); | ||||
|   ProtoSize::add_repeated_message(total_size, 1, this->supported_formats); | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { | ||||
| @@ -6241,6 +6536,11 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { | ||||
|     it.dump_to(out); | ||||
|     out.append("\n"); | ||||
|   } | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -8551,6 +8851,10 @@ bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, Pro | ||||
|       this->requires_code_to_arm = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     case 11: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -8598,6 +8902,7 @@ void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer buffer) cons | ||||
|   buffer.encode_uint32(8, this->supported_features); | ||||
|   buffer.encode_bool(9, this->requires_code); | ||||
|   buffer.encode_bool(10, this->requires_code_to_arm); | ||||
|   buffer.encode_uint32(11, this->device_id); | ||||
| } | ||||
| void ListEntitiesAlarmControlPanelResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -8610,6 +8915,7 @@ void ListEntitiesAlarmControlPanelResponse::calculate_size(uint32_t &total_size) | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->supported_features, false); | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->requires_code, false); | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->requires_code_to_arm, false); | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { | ||||
| @@ -8656,6 +8962,11 @@ void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { | ||||
|   out.append("  requires_code_to_arm: "); | ||||
|   out.append(YESNO(this->requires_code_to_arm)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -8783,6 +9094,10 @@ bool ListEntitiesTextResponse::decode_varint(uint32_t field_id, ProtoVarInt valu | ||||
|       this->mode = value.as_enum<enums::TextMode>(); | ||||
|       return true; | ||||
|     } | ||||
|     case 12: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -8835,6 +9150,7 @@ void ListEntitiesTextResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_uint32(9, this->max_length); | ||||
|   buffer.encode_string(10, this->pattern); | ||||
|   buffer.encode_enum<enums::TextMode>(11, this->mode); | ||||
|   buffer.encode_uint32(12, this->device_id); | ||||
| } | ||||
| void ListEntitiesTextResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -8848,6 +9164,7 @@ void ListEntitiesTextResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->max_length, false); | ||||
|   ProtoSize::add_string_field(total_size, 1, this->pattern, false); | ||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->mode), false); | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesTextResponse::dump_to(std::string &out) const { | ||||
| @@ -8899,6 +9216,11 @@ void ListEntitiesTextResponse::dump_to(std::string &out) const { | ||||
|   out.append("  mode: "); | ||||
|   out.append(proto_enum_to_string<enums::TextMode>(this->mode)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -9014,6 +9336,10 @@ bool ListEntitiesDateResponse::decode_varint(uint32_t field_id, ProtoVarInt valu | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     case 8: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -9058,6 +9384,7 @@ void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_string(5, this->icon); | ||||
|   buffer.encode_bool(6, this->disabled_by_default); | ||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||
|   buffer.encode_uint32(8, this->device_id); | ||||
| } | ||||
| void ListEntitiesDateResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -9067,6 +9394,7 @@ void ListEntitiesDateResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->icon, false); | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesDateResponse::dump_to(std::string &out) const { | ||||
| @@ -9100,6 +9428,11 @@ void ListEntitiesDateResponse::dump_to(std::string &out) const { | ||||
|   out.append("  entity_category: "); | ||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -9255,6 +9588,10 @@ bool ListEntitiesTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt valu | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     case 8: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -9299,6 +9636,7 @@ void ListEntitiesTimeResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_string(5, this->icon); | ||||
|   buffer.encode_bool(6, this->disabled_by_default); | ||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||
|   buffer.encode_uint32(8, this->device_id); | ||||
| } | ||||
| void ListEntitiesTimeResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -9308,6 +9646,7 @@ void ListEntitiesTimeResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->icon, false); | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesTimeResponse::dump_to(std::string &out) const { | ||||
| @@ -9341,6 +9680,11 @@ void ListEntitiesTimeResponse::dump_to(std::string &out) const { | ||||
|   out.append("  entity_category: "); | ||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -9496,6 +9840,10 @@ bool ListEntitiesEventResponse::decode_varint(uint32_t field_id, ProtoVarInt val | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     case 10: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -9552,6 +9900,7 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   for (auto &it : this->event_types) { | ||||
|     buffer.encode_string(9, it, true); | ||||
|   } | ||||
|   buffer.encode_uint32(10, this->device_id); | ||||
| } | ||||
| void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -9567,6 +9916,7 @@ void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const { | ||||
|       ProtoSize::add_string_field(total_size, 1, it, true); | ||||
|     } | ||||
|   } | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesEventResponse::dump_to(std::string &out) const { | ||||
| @@ -9610,6 +9960,11 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const { | ||||
|     out.append("'").append(it).append("'"); | ||||
|     out.append("\n"); | ||||
|   } | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -9678,6 +10033,10 @@ bool ListEntitiesValveResponse::decode_varint(uint32_t field_id, ProtoVarInt val | ||||
|       this->supports_stop = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     case 12: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -9730,6 +10089,7 @@ void ListEntitiesValveResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_bool(9, this->assumed_state); | ||||
|   buffer.encode_bool(10, this->supports_position); | ||||
|   buffer.encode_bool(11, this->supports_stop); | ||||
|   buffer.encode_uint32(12, this->device_id); | ||||
| } | ||||
| void ListEntitiesValveResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -9743,6 +10103,7 @@ void ListEntitiesValveResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->assumed_state, false); | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->supports_position, false); | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false); | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesValveResponse::dump_to(std::string &out) const { | ||||
| @@ -9792,6 +10153,11 @@ void ListEntitiesValveResponse::dump_to(std::string &out) const { | ||||
|   out.append("  supports_stop: "); | ||||
|   out.append(YESNO(this->supports_stop)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -9923,6 +10289,10 @@ bool ListEntitiesDateTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     case 8: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -9967,6 +10337,7 @@ void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_string(5, this->icon); | ||||
|   buffer.encode_bool(6, this->disabled_by_default); | ||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||
|   buffer.encode_uint32(8, this->device_id); | ||||
| } | ||||
| void ListEntitiesDateTimeResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -9976,6 +10347,7 @@ void ListEntitiesDateTimeResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->icon, false); | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesDateTimeResponse::dump_to(std::string &out) const { | ||||
| @@ -10009,6 +10381,11 @@ void ListEntitiesDateTimeResponse::dump_to(std::string &out) const { | ||||
|   out.append("  entity_category: "); | ||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -10114,6 +10491,10 @@ bool ListEntitiesUpdateResponse::decode_varint(uint32_t field_id, ProtoVarInt va | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     case 9: { | ||||
|       this->device_id = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -10163,6 +10544,7 @@ void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_bool(6, this->disabled_by_default); | ||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||
|   buffer.encode_string(8, this->device_class); | ||||
|   buffer.encode_uint32(9, this->device_id); | ||||
| } | ||||
| void ListEntitiesUpdateResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||
| @@ -10173,6 +10555,7 @@ void ListEntitiesUpdateResponse::calculate_size(uint32_t &total_size) const { | ||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||
|   ProtoSize::add_string_field(total_size, 1, this->device_class, false); | ||||
|   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesUpdateResponse::dump_to(std::string &out) const { | ||||
| @@ -10210,6 +10593,11 @@ void ListEntitiesUpdateResponse::dump_to(std::string &out) const { | ||||
|   out.append("  device_class: "); | ||||
|   out.append("'").append(this->device_class).append("'"); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  device_id: "); | ||||
|   sprintf(buffer, "%" PRIu32, this->device_id); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -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) { | ||||
|   | ||||
| @@ -19,7 +19,7 @@ class APIServerConnectionBase : public ProtoService { | ||||
|  | ||||
|   template<typename T> bool send_message(const T &msg) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|     this->log_send_message_(T::message_name(), msg.dump()); | ||||
|     this->log_send_message_(msg.message_name(), msg.dump()); | ||||
| #endif | ||||
|     return this->send_message_(msg, T::MESSAGE_TYPE); | ||||
|   } | ||||
| @@ -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); | ||||
|   | ||||
| @@ -47,6 +47,11 @@ void APIServer::setup() { | ||||
|   } | ||||
| #endif | ||||
|  | ||||
|   // Schedule reboot if no clients connect within timeout | ||||
|   if (this->reboot_timeout_ != 0) { | ||||
|     this->schedule_reboot_timeout_(); | ||||
|   } | ||||
|  | ||||
|   this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0);  // monitored for incoming connections | ||||
|   if (this->socket_ == nullptr) { | ||||
|     ESP_LOGW(TAG, "Could not create socket"); | ||||
| @@ -99,21 +104,19 @@ void APIServer::setup() { | ||||
|         return; | ||||
|       } | ||||
|       for (auto &c : this->clients_) { | ||||
|         if (!c->remove_) | ||||
|         if (!c->flags_.remove) | ||||
|           c->try_send_log_message(level, tag, message); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| #endif | ||||
|  | ||||
|   this->last_connected_ = App.get_loop_component_start_time(); | ||||
|  | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|   if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) { | ||||
|     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); | ||||
|           } | ||||
|         }); | ||||
| @@ -121,6 +124,16 @@ void APIServer::setup() { | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void APIServer::schedule_reboot_timeout_() { | ||||
|   this->status_set_warning(); | ||||
|   this->set_timeout("api_reboot", this->reboot_timeout_, []() { | ||||
|     if (!global_api_server->is_connected()) { | ||||
|       ESP_LOGE(TAG, "No clients; rebooting"); | ||||
|       App.reboot(); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
|  | ||||
| void APIServer::loop() { | ||||
|   // Accept new clients only if the socket exists and has incoming connections | ||||
|   if (this->socket_ && this->socket_->ready()) { | ||||
| @@ -130,51 +143,63 @@ void APIServer::loop() { | ||||
|       auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len); | ||||
|       if (!sock) | ||||
|         break; | ||||
|       ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str()); | ||||
|       ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str()); | ||||
|  | ||||
|       auto *conn = new APIConnection(std::move(sock), this); | ||||
|       this->clients_.emplace_back(conn); | ||||
|       conn->start(); | ||||
|  | ||||
|       // Clear warning status and cancel reboot when first client connects | ||||
|       if (this->clients_.size() == 1 && this->reboot_timeout_ != 0) { | ||||
|         this->status_clear_warning(); | ||||
|         this->cancel_timeout("api_reboot"); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (this->clients_.empty()) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // Process clients and remove disconnected ones in a single pass | ||||
|   if (!this->clients_.empty()) { | ||||
|     size_t client_index = 0; | ||||
|     while (client_index < this->clients_.size()) { | ||||
|       auto &client = this->clients_[client_index]; | ||||
|  | ||||
|       if (client->remove_) { | ||||
|         // Handle disconnection | ||||
|         this->client_disconnected_trigger_->trigger(client->client_info_, client->client_peername_); | ||||
|         ESP_LOGV(TAG, "Removing connection to %s", client->client_info_.c_str()); | ||||
|  | ||||
|         // Swap with the last element and pop (avoids expensive vector shifts) | ||||
|         if (client_index < this->clients_.size() - 1) { | ||||
|           std::swap(this->clients_[client_index], this->clients_.back()); | ||||
|         } | ||||
|         this->clients_.pop_back(); | ||||
|         // Don't increment client_index since we need to process the swapped element | ||||
|       } else { | ||||
|         // Process active client | ||||
|         client->loop(); | ||||
|         client_index++;  // Move to next client | ||||
|       } | ||||
|   // Check network connectivity once for all clients | ||||
|   if (!network::is_connected()) { | ||||
|     // Network is down - disconnect all clients | ||||
|     for (auto &client : this->clients_) { | ||||
|       client->on_fatal_error(); | ||||
|       ESP_LOGW(TAG, "%s: Network down; disconnect", client->get_client_combined_info().c_str()); | ||||
|     } | ||||
|     // Continue to process and clean up the clients below | ||||
|   } | ||||
|  | ||||
|   if (this->reboot_timeout_ != 0) { | ||||
|     const uint32_t now = App.get_loop_component_start_time(); | ||||
|     if (!this->is_connected()) { | ||||
|       if (now - this->last_connected_ > this->reboot_timeout_) { | ||||
|         ESP_LOGE(TAG, "No client connected; rebooting"); | ||||
|         App.reboot(); | ||||
|       } | ||||
|       this->status_set_warning(); | ||||
|     } else { | ||||
|       this->last_connected_ = now; | ||||
|       this->status_clear_warning(); | ||||
|   size_t client_index = 0; | ||||
|   while (client_index < this->clients_.size()) { | ||||
|     auto &client = this->clients_[client_index]; | ||||
|  | ||||
|     if (!client->flags_.remove) { | ||||
|       // Common case: process active client | ||||
|       client->loop(); | ||||
|       client_index++; | ||||
|       continue; | ||||
|     } | ||||
|  | ||||
|     // 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) | ||||
|     if (client_index < this->clients_.size() - 1) { | ||||
|       std::swap(this->clients_[client_index], this->clients_.back()); | ||||
|     } | ||||
|     this->clients_.pop_back(); | ||||
|  | ||||
|     // Schedule reboot when last client disconnects | ||||
|     if (this->clients_.empty() && this->reboot_timeout_ != 0) { | ||||
|       this->schedule_reboot_timeout_(); | ||||
|     } | ||||
|     // Don't increment client_index since we need to process the swapped element | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -408,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_) { | ||||
| @@ -479,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(); | ||||
|   } | ||||
| } | ||||
| @@ -503,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}; | ||||
|   uint32_t last_connected_{0}; | ||||
|  | ||||
|   // 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; } | ||||
|   | ||||
| @@ -335,6 +335,7 @@ class ProtoMessage { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   std::string dump() const; | ||||
|   virtual void dump_to(std::string &out) const = 0; | ||||
|   virtual const char *message_name() const { return "unknown"; } | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
| @@ -363,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; } | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| #include "atm90e32.h" | ||||
| #include <cinttypes> | ||||
| #include <cmath> | ||||
| #include <numbers> | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| @@ -848,7 +849,7 @@ uint16_t ATM90E32Component::calculate_voltage_threshold(int line_freq, uint16_t | ||||
|   float nominal_voltage = (line_freq == 60) ? 120.0f : 220.0f; | ||||
|   float target_voltage = nominal_voltage * multiplier; | ||||
|  | ||||
|   float peak_01v = target_voltage * 100.0f * std::sqrt(2.0f);  // convert RMS → peak, scale to 0.01V | ||||
|   float peak_01v = target_voltage * 100.0f * std::numbers::sqrt2_v<float>;  // convert RMS → peak, scale to 0.01V | ||||
|   float divider = (2.0f * ugain) / 32768.0f; | ||||
|  | ||||
|   float threshold = peak_01v / divider; | ||||
|   | ||||
| @@ -312,7 +312,7 @@ FileDecoderState AudioDecoder::decode_mp3_() { | ||||
|   if (err) { | ||||
|     switch (err) { | ||||
|       case esp_audio_libs::helix_decoder::ERR_MP3_OUT_OF_MEMORY: | ||||
|         // Intentional fallthrough | ||||
|         [[fallthrough]]; | ||||
|       case esp_audio_libs::helix_decoder::ERR_MP3_NULL_POINTER: | ||||
|         return FileDecoderState::FAILED; | ||||
|         break; | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| #include "esphome/core/defines.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE | ||||
| #include "esp_crt_bundle.h" | ||||
| @@ -16,13 +17,13 @@ namespace audio { | ||||
| static const uint32_t READ_WRITE_TIMEOUT_MS = 20; | ||||
|  | ||||
| static const uint32_t CONNECTION_TIMEOUT_MS = 5000; | ||||
|  | ||||
| // The number of times the http read times out with no data before throwing an error | ||||
| static const uint32_t ERROR_COUNT_NO_DATA_READ_TIMEOUT = 100; | ||||
| static const uint8_t MAX_FETCHING_HEADER_ATTEMPTS = 6; | ||||
|  | ||||
| static const size_t HTTP_STREAM_BUFFER_SIZE = 2048; | ||||
|  | ||||
| static const uint8_t MAX_REDIRECTION = 5; | ||||
| static const uint8_t MAX_REDIRECTIONS = 5; | ||||
|  | ||||
| static const char *const TAG = "audio_reader"; | ||||
|  | ||||
| // Some common HTTP status codes - borrowed from http_request component accessed 20241224 | ||||
| enum HttpStatus { | ||||
| @@ -94,7 +95,7 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) { | ||||
|   client_config.url = uri.c_str(); | ||||
|   client_config.cert_pem = nullptr; | ||||
|   client_config.disable_auto_redirect = false; | ||||
|   client_config.max_redirection_count = 10; | ||||
|   client_config.max_redirection_count = MAX_REDIRECTIONS; | ||||
|   client_config.event_handler = http_event_handler; | ||||
|   client_config.user_data = this; | ||||
|   client_config.buffer_size = HTTP_STREAM_BUFFER_SIZE; | ||||
| @@ -116,12 +117,29 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) { | ||||
|   esp_err_t err = esp_http_client_open(this->client_, 0); | ||||
|  | ||||
|   if (err != ESP_OK) { | ||||
|     ESP_LOGE(TAG, "Failed to open URL"); | ||||
|     this->cleanup_connection_(); | ||||
|     return err; | ||||
|   } | ||||
|  | ||||
|   int64_t header_length = esp_http_client_fetch_headers(this->client_); | ||||
|   uint8_t reattempt_count = 0; | ||||
|   while ((header_length < 0) && (reattempt_count < MAX_FETCHING_HEADER_ATTEMPTS)) { | ||||
|     this->cleanup_connection_(); | ||||
|     if (header_length != -ESP_ERR_HTTP_EAGAIN) { | ||||
|       // Serious error, no recovery | ||||
|       return ESP_FAIL; | ||||
|     } else { | ||||
|       // Reconnect from a fresh state to avoid a bug where it never reads the headers even if made available | ||||
|       this->client_ = esp_http_client_init(&client_config); | ||||
|       esp_http_client_open(this->client_, 0); | ||||
|       header_length = esp_http_client_fetch_headers(this->client_); | ||||
|       ++reattempt_count; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (header_length < 0) { | ||||
|     ESP_LOGE(TAG, "Failed to fetch headers"); | ||||
|     this->cleanup_connection_(); | ||||
|     return ESP_FAIL; | ||||
|   } | ||||
| @@ -135,7 +153,7 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) { | ||||
|  | ||||
|   ssize_t redirect_count = 0; | ||||
|  | ||||
|   while ((esp_http_client_set_redirection(this->client_) == ESP_OK) && (redirect_count < MAX_REDIRECTION)) { | ||||
|   while ((esp_http_client_set_redirection(this->client_) == ESP_OK) && (redirect_count < MAX_REDIRECTIONS)) { | ||||
|     err = esp_http_client_open(this->client_, 0); | ||||
|     if (err != ESP_OK) { | ||||
|       this->cleanup_connection_(); | ||||
| @@ -267,27 +285,29 @@ AudioReaderState AudioReader::http_read_() { | ||||
|       return AudioReaderState::FINISHED; | ||||
|     } | ||||
|   } else if (this->output_transfer_buffer_->free() > 0) { | ||||
|     size_t bytes_to_read = this->output_transfer_buffer_->free(); | ||||
|     int received_len = | ||||
|         esp_http_client_read(this->client_, (char *) this->output_transfer_buffer_->get_buffer_end(), bytes_to_read); | ||||
|     int received_len = esp_http_client_read(this->client_, (char *) this->output_transfer_buffer_->get_buffer_end(), | ||||
|                                             this->output_transfer_buffer_->free()); | ||||
|  | ||||
|     if (received_len > 0) { | ||||
|       this->output_transfer_buffer_->increase_buffer_length(received_len); | ||||
|       this->last_data_read_ms_ = millis(); | ||||
|     } else if (received_len < 0) { | ||||
|       return AudioReaderState::READING; | ||||
|     } else if (received_len <= 0) { | ||||
|       // HTTP read error | ||||
|       this->cleanup_connection_(); | ||||
|       return AudioReaderState::FAILED; | ||||
|     } else { | ||||
|       if (bytes_to_read > 0) { | ||||
|         // Read timed out | ||||
|         if ((millis() - this->last_data_read_ms_) > CONNECTION_TIMEOUT_MS) { | ||||
|           this->cleanup_connection_(); | ||||
|           return AudioReaderState::FAILED; | ||||
|         } | ||||
|  | ||||
|         delay(READ_WRITE_TIMEOUT_MS); | ||||
|       if (received_len == -1) { | ||||
|         // A true connection error occured, no chance at recovery | ||||
|         this->cleanup_connection_(); | ||||
|         return AudioReaderState::FAILED; | ||||
|       } | ||||
|  | ||||
|       // Read timed out, manually verify if it has been too long since the last successful read | ||||
|       if ((millis() - this->last_data_read_ms_) > MAX_FETCHING_HEADER_ATTEMPTS * CONNECTION_TIMEOUT_MS) { | ||||
|         ESP_LOGE(TAG, "Timed out"); | ||||
|         this->cleanup_connection_(); | ||||
|         return AudioReaderState::FAILED; | ||||
|       } | ||||
|  | ||||
|       delay(READ_WRITE_TIMEOUT_MS); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -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; } | ||||
|   | ||||
| @@ -7,11 +7,13 @@ | ||||
|  | ||||
| extern "C" { | ||||
| #include "rtos_pub.h" | ||||
| #include "spi.h" | ||||
| // rtos_pub.h must be included before the rest of the includes | ||||
|  | ||||
| #include "arm_arch.h" | ||||
| #include "general_dma_pub.h" | ||||
| #include "gpio_pub.h" | ||||
| #include "icu_pub.h" | ||||
| #include "spi.h" | ||||
| #undef SPI_DAT | ||||
| #undef SPI_BASE | ||||
| }; | ||||
| @@ -124,7 +126,7 @@ void BekenSPILEDStripLightOutput::setup() { | ||||
|   size_t buffer_size = this->get_buffer_size_(); | ||||
|   size_t dma_buffer_size = (buffer_size * 8) + (2 * 64); | ||||
|  | ||||
|   ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); | ||||
|   RAMAllocator<uint8_t> allocator; | ||||
|   this->buf_ = allocator.allocate(buffer_size); | ||||
|   if (this->buf_ == nullptr) { | ||||
|     ESP_LOGE(TAG, "Cannot allocate LED buffer!"); | ||||
|   | ||||
| @@ -50,7 +50,7 @@ void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function< | ||||
|   // turn on (after one-shot sensor automatically powers down) | ||||
|   uint8_t turn_on = BH1750_COMMAND_POWER_ON; | ||||
|   if (this->write(&turn_on, 1) != i2c::ERROR_OK) { | ||||
|     ESP_LOGW(TAG, "Turning on BH1750 failed"); | ||||
|     ESP_LOGW(TAG, "Power on failed"); | ||||
|     f(NAN); | ||||
|     return; | ||||
|   } | ||||
| @@ -60,7 +60,7 @@ void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function< | ||||
|     uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> 5) & 0b111); | ||||
|     uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> 0) & 0b11111); | ||||
|     if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) { | ||||
|       ESP_LOGW(TAG, "Setting measurement time for BH1750 failed"); | ||||
|       ESP_LOGW(TAG, "Set measurement time failed"); | ||||
|       active_mtreg_ = 0; | ||||
|       f(NAN); | ||||
|       return; | ||||
| @@ -88,7 +88,7 @@ void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function< | ||||
|       return; | ||||
|   } | ||||
|   if (this->write(&cmd, 1) != i2c::ERROR_OK) { | ||||
|     ESP_LOGW(TAG, "Starting measurement for BH1750 failed"); | ||||
|     ESP_LOGW(TAG, "Start measurement failed"); | ||||
|     f(NAN); | ||||
|     return; | ||||
|   } | ||||
| @@ -99,7 +99,7 @@ void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function< | ||||
|   this->set_timeout("read", meas_time, [this, mode, mtreg, f]() { | ||||
|     uint16_t raw_value; | ||||
|     if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) { | ||||
|       ESP_LOGW(TAG, "Reading BH1750 data failed"); | ||||
|       ESP_LOGW(TAG, "Read data failed"); | ||||
|       f(NAN); | ||||
|       return; | ||||
|     } | ||||
| @@ -156,7 +156,7 @@ void BH1750Sensor::update() { | ||||
|         this->publish_state(NAN); | ||||
|         return; | ||||
|       } | ||||
|       ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), val); | ||||
|       ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), val); | ||||
|       this->status_clear_warning(); | ||||
|       this->publish_state(val); | ||||
|     }); | ||||
|   | ||||
| @@ -60,8 +60,8 @@ from esphome.const import ( | ||||
|     DEVICE_CLASS_WINDOW, | ||||
| ) | ||||
| from esphome.core import CORE, coroutine_with_priority | ||||
| from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||
| from esphome.cpp_generator import MockObjClass | ||||
| from esphome.cpp_helpers import setup_entity | ||||
| from esphome.util import Registry | ||||
|  | ||||
| CODEOWNERS = ["@esphome/core"] | ||||
| @@ -148,6 +148,7 @@ BinarySensorCondition = binary_sensor_ns.class_("BinarySensorCondition", Conditi | ||||
|  | ||||
| # Filters | ||||
| Filter = binary_sensor_ns.class_("Filter") | ||||
| TimeoutFilter = binary_sensor_ns.class_("TimeoutFilter", Filter, cg.Component) | ||||
| DelayedOnOffFilter = binary_sensor_ns.class_("DelayedOnOffFilter", Filter, cg.Component) | ||||
| DelayedOnFilter = binary_sensor_ns.class_("DelayedOnFilter", Filter, cg.Component) | ||||
| DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Component) | ||||
| @@ -171,6 +172,19 @@ async def invert_filter_to_code(config, filter_id): | ||||
|     return cg.new_Pvariable(filter_id) | ||||
|  | ||||
|  | ||||
| @register_filter( | ||||
|     "timeout", | ||||
|     TimeoutFilter, | ||||
|     cv.templatable(cv.positive_time_period_milliseconds), | ||||
| ) | ||||
| async def timeout_filter_to_code(config, filter_id): | ||||
|     var = cg.new_Pvariable(filter_id) | ||||
|     await cg.register_component(var, {}) | ||||
|     template_ = await cg.templatable(config, [], cg.uint32) | ||||
|     cg.add(var.set_timeout_value(template_)) | ||||
|     return var | ||||
|  | ||||
|  | ||||
| @register_filter( | ||||
|     "delayed_on_off", | ||||
|     DelayedOnOffFilter, | ||||
| @@ -491,6 +505,9 @@ _BINARY_SENSOR_SCHEMA = ( | ||||
| ) | ||||
|  | ||||
|  | ||||
| _BINARY_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("binary_sensor")) | ||||
|  | ||||
|  | ||||
| def binary_sensor_schema( | ||||
|     class_: MockObjClass = cv.UNDEFINED, | ||||
|     *, | ||||
| @@ -521,7 +538,7 @@ BINARY_SENSOR_SCHEMA.add_extra(cv.deprecated_schema_constant("binary_sensor")) | ||||
|  | ||||
|  | ||||
| async def setup_binary_sensor_core_(var, config): | ||||
|     await setup_entity(var, config) | ||||
|     await setup_entity(var, config, "binary_sensor") | ||||
|  | ||||
|     if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: | ||||
|         cg.add(var.set_device_class(device_class)) | ||||
|   | ||||
| @@ -25,6 +25,12 @@ void Filter::input(bool value) { | ||||
|   } | ||||
| } | ||||
|  | ||||
| void TimeoutFilter::input(bool value) { | ||||
|   this->set_timeout("timeout", this->timeout_delay_.value(), [this]() { this->parent_->invalidate_state(); }); | ||||
|   // we do not de-dup here otherwise changes from invalid to valid state will not be output | ||||
|   this->output(value); | ||||
| } | ||||
|  | ||||
| optional<bool> DelayedOnOffFilter::new_value(bool value) { | ||||
|   if (value) { | ||||
|     this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); }); | ||||
|   | ||||
| @@ -16,7 +16,7 @@ class Filter { | ||||
|  public: | ||||
|   virtual optional<bool> new_value(bool value) = 0; | ||||
|  | ||||
|   void input(bool value); | ||||
|   virtual void input(bool value); | ||||
|  | ||||
|   void output(bool value); | ||||
|  | ||||
| @@ -28,6 +28,16 @@ class Filter { | ||||
|   Deduplicator<bool> dedup_; | ||||
| }; | ||||
|  | ||||
| class TimeoutFilter : public Filter, public Component { | ||||
|  public: | ||||
|   optional<bool> new_value(bool value) override { return value; } | ||||
|   void input(bool value) override; | ||||
|   template<typename T> void set_timeout_value(T timeout) { this->timeout_delay_ = timeout; } | ||||
|  | ||||
|  protected: | ||||
|   TemplatableValue<uint32_t> timeout_delay_{}; | ||||
| }; | ||||
|  | ||||
| class DelayedOnOffFilter : public Filter, public Component { | ||||
|  public: | ||||
|   optional<bool> new_value(bool value) override; | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -18,8 +18,8 @@ from esphome.const import ( | ||||
|     DEVICE_CLASS_UPDATE, | ||||
| ) | ||||
| from esphome.core import CORE, coroutine_with_priority | ||||
| from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||
| from esphome.cpp_generator import MockObjClass | ||||
| from esphome.cpp_helpers import setup_entity | ||||
|  | ||||
| CODEOWNERS = ["@esphome/core"] | ||||
| IS_PLATFORM_COMPONENT = True | ||||
| @@ -61,6 +61,9 @@ _BUTTON_SCHEMA = ( | ||||
| ) | ||||
|  | ||||
|  | ||||
| _BUTTON_SCHEMA.add_extra(entity_duplicate_validator("button")) | ||||
|  | ||||
|  | ||||
| def button_schema( | ||||
|     class_: MockObjClass, | ||||
|     *, | ||||
| @@ -87,7 +90,7 @@ BUTTON_SCHEMA.add_extra(cv.deprecated_schema_constant("button")) | ||||
|  | ||||
|  | ||||
| async def setup_button_core_(var, config): | ||||
|     await setup_entity(var, config) | ||||
|     await setup_entity(var, config, "button") | ||||
|  | ||||
|     for conf in config.get(CONF_ON_PRESS, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|   | ||||
| @@ -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; } | ||||
|   | ||||
| @@ -48,8 +48,8 @@ from esphome.const import ( | ||||
|     CONF_WEB_SERVER, | ||||
| ) | ||||
| from esphome.core import CORE, coroutine_with_priority | ||||
| from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||
| from esphome.cpp_generator import MockObjClass | ||||
| from esphome.cpp_helpers import setup_entity | ||||
|  | ||||
| IS_PLATFORM_COMPONENT = True | ||||
|  | ||||
| @@ -247,6 +247,9 @@ _CLIMATE_SCHEMA = ( | ||||
| ) | ||||
|  | ||||
|  | ||||
| _CLIMATE_SCHEMA.add_extra(entity_duplicate_validator("climate")) | ||||
|  | ||||
|  | ||||
| def climate_schema( | ||||
|     class_: MockObjClass, | ||||
|     *, | ||||
| @@ -273,7 +276,7 @@ CLIMATE_SCHEMA.add_extra(cv.deprecated_schema_constant("climate")) | ||||
|  | ||||
|  | ||||
| async def setup_climate_core_(var, config): | ||||
|     await setup_entity(var, config) | ||||
|     await setup_entity(var, config, "climate") | ||||
|  | ||||
|     visual = config[CONF_VISUAL] | ||||
|     if (min_temp := visual.get(CONF_MIN_TEMPERATURE)) is not None: | ||||
|   | ||||
| @@ -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_; | ||||
|   | ||||
| @@ -33,8 +33,8 @@ from esphome.const import ( | ||||
|     DEVICE_CLASS_WINDOW, | ||||
| ) | ||||
| from esphome.core import CORE, coroutine_with_priority | ||||
| from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||
| from esphome.cpp_generator import MockObjClass | ||||
| from esphome.cpp_helpers import setup_entity | ||||
|  | ||||
| IS_PLATFORM_COMPONENT = True | ||||
|  | ||||
| @@ -126,6 +126,9 @@ _COVER_SCHEMA = ( | ||||
| ) | ||||
|  | ||||
|  | ||||
| _COVER_SCHEMA.add_extra(entity_duplicate_validator("cover")) | ||||
|  | ||||
|  | ||||
| def cover_schema( | ||||
|     class_: MockObjClass, | ||||
|     *, | ||||
| @@ -154,7 +157,7 @@ COVER_SCHEMA.add_extra(cv.deprecated_schema_constant("cover")) | ||||
|  | ||||
|  | ||||
| async def setup_cover_core_(var, config): | ||||
|     await setup_entity(var, config) | ||||
|     await setup_entity(var, config, "cover") | ||||
|  | ||||
|     if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: | ||||
|         cg.add(var.set_device_class(device_class)) | ||||
|   | ||||
| @@ -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: | ||||
|   | ||||
| @@ -22,8 +22,8 @@ from esphome.const import ( | ||||
|     CONF_YEAR, | ||||
| ) | ||||
| from esphome.core import CORE, coroutine_with_priority | ||||
| from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||
| from esphome.cpp_generator import MockObjClass | ||||
| from esphome.cpp_helpers import setup_entity | ||||
|  | ||||
| CODEOWNERS = ["@rfdarter", "@jesserockz"] | ||||
|  | ||||
| @@ -84,6 +84,8 @@ _DATETIME_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( | ||||
|     .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) | ||||
| ).add_extra(_validate_time_present) | ||||
|  | ||||
| _DATETIME_SCHEMA.add_extra(entity_duplicate_validator("datetime")) | ||||
|  | ||||
|  | ||||
| def date_schema(class_: MockObjClass) -> cv.Schema: | ||||
|     schema = cv.Schema( | ||||
| @@ -133,7 +135,7 @@ def datetime_schema(class_: MockObjClass) -> cv.Schema: | ||||
|  | ||||
|  | ||||
| async def setup_datetime_core_(var, config): | ||||
|     await setup_entity(var, config) | ||||
|     await setup_entity(var, config, "datetime") | ||||
|  | ||||
|     if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: | ||||
|         mqtt_ = cg.new_Pvariable(mqtt_id, var) | ||||
|   | ||||
| @@ -455,7 +455,7 @@ CONFIG_SCHEMA = cv.Schema( | ||||
|                     CONF_NAME: "Demo Plain Sensor", | ||||
|                 }, | ||||
|                 { | ||||
|                     CONF_NAME: "Demo Temperature Sensor", | ||||
|                     CONF_NAME: "Demo Temperature Sensor 1", | ||||
|                     CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS, | ||||
|                     CONF_ICON: ICON_THERMOMETER, | ||||
|                     CONF_ACCURACY_DECIMALS: 1, | ||||
| @@ -463,7 +463,7 @@ CONFIG_SCHEMA = cv.Schema( | ||||
|                     CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT, | ||||
|                 }, | ||||
|                 { | ||||
|                     CONF_NAME: "Demo Temperature Sensor", | ||||
|                     CONF_NAME: "Demo Temperature Sensor 2", | ||||
|                     CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS, | ||||
|                     CONF_ICON: ICON_THERMOMETER, | ||||
|                     CONF_ACCURACY_DECIMALS: 1, | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| #include "display.h" | ||||
| #include <utility> | ||||
| #include <numbers> | ||||
| #include "display_color_utils.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/core/log.h" | ||||
| @@ -424,15 +425,15 @@ void HOT Display::get_regular_polygon_vertex(int vertex_id, int *vertex_x, int * | ||||
|     // hence we rotate the shape by 270° to orient the polygon up. | ||||
|     rotation_degrees += ROTATION_270_DEGREES; | ||||
|     // Convert the rotation to radians, easier to use in trigonometrical calculations | ||||
|     float rotation_radians = rotation_degrees * PI / 180; | ||||
|     float rotation_radians = rotation_degrees * std::numbers::pi / 180; | ||||
|     // A pointy top variation means the first vertex of the polygon is at the top center of the shape, this requires no | ||||
|     // additional rotation of the shape. | ||||
|     // A flat top variation means the first point of the polygon has to be rotated so that the first edge is horizontal, | ||||
|     // this requires to rotate the shape by π/edges radians counter-clockwise so that the first point is located on the | ||||
|     // left side of the first horizontal edge. | ||||
|     rotation_radians -= (variation == VARIATION_FLAT_TOP) ? PI / edges : 0.0; | ||||
|     rotation_radians -= (variation == VARIATION_FLAT_TOP) ? std::numbers::pi / edges : 0.0; | ||||
|  | ||||
|     float vertex_angle = ((float) vertex_id) / edges * 2 * PI + rotation_radians; | ||||
|     float vertex_angle = ((float) vertex_id) / edges * 2 * std::numbers::pi + rotation_radians; | ||||
|     *vertex_x = (int) round(cos(vertex_angle) * radius) + center_x; | ||||
|     *vertex_y = (int) round(sin(vertex_angle) * radius) + center_y; | ||||
|   } | ||||
|   | ||||
| @@ -138,8 +138,6 @@ enum DisplayRotation { | ||||
|   DISPLAY_ROTATION_270_DEGREES = 270, | ||||
| }; | ||||
|  | ||||
| #define PI 3.1415926535897932384626433832795 | ||||
|  | ||||
| const int EDGES_TRIGON = 3; | ||||
| const int EDGES_TRIANGLE = 3; | ||||
| const int EDGES_TETRAGON = 4; | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
|   //////////////////////// | ||||
|   | ||||
| @@ -4,7 +4,7 @@ import logging | ||||
| import os | ||||
| from pathlib import Path | ||||
|  | ||||
| from esphome import git | ||||
| from esphome import yaml_util | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
| @@ -23,7 +23,6 @@ from esphome.const import ( | ||||
|     CONF_REFRESH, | ||||
|     CONF_SOURCE, | ||||
|     CONF_TYPE, | ||||
|     CONF_URL, | ||||
|     CONF_VARIANT, | ||||
|     CONF_VERSION, | ||||
|     KEY_CORE, | ||||
| @@ -32,14 +31,13 @@ from esphome.const import ( | ||||
|     KEY_TARGET_FRAMEWORK, | ||||
|     KEY_TARGET_PLATFORM, | ||||
|     PLATFORM_ESP32, | ||||
|     TYPE_GIT, | ||||
|     TYPE_LOCAL, | ||||
|     __version__, | ||||
| ) | ||||
| from esphome.core import CORE, HexInt, TimePeriod | ||||
| from esphome.cpp_generator import RawExpression | ||||
| import esphome.final_validate as fv | ||||
| from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed | ||||
| from esphome.types import ConfigType | ||||
|  | ||||
| from .boards import BOARDS | ||||
| from .const import (  # noqa | ||||
| @@ -49,10 +47,8 @@ from .const import (  # noqa | ||||
|     KEY_EXTRA_BUILD_FILES, | ||||
|     KEY_PATH, | ||||
|     KEY_REF, | ||||
|     KEY_REFRESH, | ||||
|     KEY_REPO, | ||||
|     KEY_SDKCONFIG_OPTIONS, | ||||
|     KEY_SUBMODULES, | ||||
|     KEY_VARIANT, | ||||
|     VARIANT_ESP32, | ||||
|     VARIANT_ESP32C2, | ||||
| @@ -235,7 +231,7 @@ def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType): | ||||
| def add_idf_component( | ||||
|     *, | ||||
|     name: str, | ||||
|     repo: str, | ||||
|     repo: str = None, | ||||
|     ref: str = None, | ||||
|     path: str = None, | ||||
|     refresh: TimePeriod = None, | ||||
| @@ -245,30 +241,27 @@ def add_idf_component( | ||||
|     """Add an esp-idf component to the project.""" | ||||
|     if not CORE.using_esp_idf: | ||||
|         raise ValueError("Not an esp-idf project") | ||||
|     if components is None: | ||||
|         components = [] | ||||
|     if name not in CORE.data[KEY_ESP32][KEY_COMPONENTS]: | ||||
|     if not repo and not ref and not path: | ||||
|         raise ValueError("Requires at least one of repo, ref or path") | ||||
|     if refresh or submodules or components: | ||||
|         _LOGGER.warning( | ||||
|             "The refresh, components and submodules parameters in add_idf_component() are " | ||||
|             "deprecated and will be removed in ESPHome 2026.1. If you are seeing this, report " | ||||
|             "an issue to the external_component author and ask them to update it." | ||||
|         ) | ||||
|     if components: | ||||
|         for comp in components: | ||||
|             CORE.data[KEY_ESP32][KEY_COMPONENTS][comp] = { | ||||
|                 KEY_REPO: repo, | ||||
|                 KEY_REF: ref, | ||||
|                 KEY_PATH: f"{path}/{comp}" if path else comp, | ||||
|             } | ||||
|     else: | ||||
|         CORE.data[KEY_ESP32][KEY_COMPONENTS][name] = { | ||||
|             KEY_REPO: repo, | ||||
|             KEY_REF: ref, | ||||
|             KEY_PATH: path, | ||||
|             KEY_REFRESH: refresh, | ||||
|             KEY_COMPONENTS: components, | ||||
|             KEY_SUBMODULES: submodules, | ||||
|         } | ||||
|     else: | ||||
|         component_config = CORE.data[KEY_ESP32][KEY_COMPONENTS][name] | ||||
|         if components is not None: | ||||
|             component_config[KEY_COMPONENTS] = list( | ||||
|                 set(component_config[KEY_COMPONENTS] + components) | ||||
|             ) | ||||
|         if submodules is not None: | ||||
|             if component_config[KEY_SUBMODULES] is None: | ||||
|                 component_config[KEY_SUBMODULES] = submodules | ||||
|             else: | ||||
|                 component_config[KEY_SUBMODULES] = list( | ||||
|                     set(component_config[KEY_SUBMODULES] + submodules) | ||||
|                 ) | ||||
|  | ||||
|  | ||||
| def add_extra_script(stage: str, filename: str, path: str): | ||||
| @@ -348,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), | ||||
| @@ -575,6 +569,17 @@ CONF_ENABLE_LWIP_DHCP_SERVER = "enable_lwip_dhcp_server" | ||||
| CONF_ENABLE_LWIP_MDNS_QUERIES = "enable_lwip_mdns_queries" | ||||
| CONF_ENABLE_LWIP_BRIDGE_INTERFACE = "enable_lwip_bridge_interface" | ||||
|  | ||||
|  | ||||
| def _validate_idf_component(config: ConfigType) -> ConfigType: | ||||
|     """Validate IDF component config and warn about deprecated options.""" | ||||
|     if CONF_REFRESH in config: | ||||
|         _LOGGER.warning( | ||||
|             "The 'refresh' option for IDF components is deprecated and has no effect. " | ||||
|             "It will be removed in ESPHome 2026.1. Please remove it from your configuration." | ||||
|         ) | ||||
|     return config | ||||
|  | ||||
|  | ||||
| ESP_IDF_FRAMEWORK_SCHEMA = cv.All( | ||||
|     cv.Schema( | ||||
|         { | ||||
| @@ -606,7 +611,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All( | ||||
|                         CONF_ENABLE_LWIP_DHCP_SERVER, "wifi", default=False | ||||
|                     ): cv.boolean, | ||||
|                     cv.Optional( | ||||
|                         CONF_ENABLE_LWIP_MDNS_QUERIES, default=False | ||||
|                         CONF_ENABLE_LWIP_MDNS_QUERIES, default=True | ||||
|                     ): cv.boolean, | ||||
|                     cv.Optional( | ||||
|                         CONF_ENABLE_LWIP_BRIDGE_INTERFACE, default=False | ||||
| @@ -614,15 +619,19 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All( | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list( | ||||
|                 cv.Schema( | ||||
|                     { | ||||
|                         cv.Required(CONF_NAME): cv.string_strict, | ||||
|                         cv.Required(CONF_SOURCE): cv.SOURCE_SCHEMA, | ||||
|                         cv.Optional(CONF_PATH): cv.string, | ||||
|                         cv.Optional(CONF_REFRESH, default="1d"): cv.All( | ||||
|                             cv.string, cv.source_refresh | ||||
|                         ), | ||||
|                     } | ||||
|                 cv.All( | ||||
|                     cv.Schema( | ||||
|                         { | ||||
|                             cv.Required(CONF_NAME): cv.string_strict, | ||||
|                             cv.Optional(CONF_SOURCE): cv.git_ref, | ||||
|                             cv.Optional(CONF_REF): cv.string, | ||||
|                             cv.Optional(CONF_PATH): cv.string, | ||||
|                             cv.Optional(CONF_REFRESH): cv.All( | ||||
|                                 cv.string, cv.source_refresh | ||||
|                             ), | ||||
|                         } | ||||
|                     ), | ||||
|                     _validate_idf_component, | ||||
|                 ) | ||||
|             ), | ||||
|         } | ||||
| @@ -696,7 +705,7 @@ FINAL_VALIDATE_SCHEMA = cv.Schema(final_validate) | ||||
| async def to_code(config): | ||||
|     cg.add_platformio_option("board", config[CONF_BOARD]) | ||||
|     cg.add_platformio_option("board_upload.flash_size", config[CONF_FLASH_SIZE]) | ||||
|     cg.set_cpp_standard("gnu++17") | ||||
|     cg.set_cpp_standard("gnu++20") | ||||
|     cg.add_build_flag("-DUSE_ESP32") | ||||
|     cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) | ||||
|     cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}") | ||||
| @@ -750,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) | ||||
|  | ||||
| @@ -762,7 +774,7 @@ async def to_code(config): | ||||
|             and not advanced[CONF_ENABLE_LWIP_DHCP_SERVER] | ||||
|         ): | ||||
|             add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False) | ||||
|         if not advanced.get(CONF_ENABLE_LWIP_MDNS_QUERIES, False): | ||||
|         if not advanced.get(CONF_ENABLE_LWIP_MDNS_QUERIES, True): | ||||
|             add_idf_sdkconfig_option("CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES", False) | ||||
|         if not advanced.get(CONF_ENABLE_LWIP_BRIDGE_INTERFACE, False): | ||||
|             add_idf_sdkconfig_option("CONFIG_LWIP_BRIDGEIF_MAX_PORTS", 0) | ||||
| @@ -814,18 +826,12 @@ async def to_code(config): | ||||
|             add_idf_sdkconfig_option(name, RawSdkconfigValue(value)) | ||||
|  | ||||
|         for component in conf[CONF_COMPONENTS]: | ||||
|             source = component[CONF_SOURCE] | ||||
|             if source[CONF_TYPE] == TYPE_GIT: | ||||
|                 add_idf_component( | ||||
|                     name=component[CONF_NAME], | ||||
|                     repo=source[CONF_URL], | ||||
|                     ref=source.get(CONF_REF), | ||||
|                     path=component.get(CONF_PATH), | ||||
|                     refresh=component[CONF_REFRESH], | ||||
|                 ) | ||||
|             elif source[CONF_TYPE] == TYPE_LOCAL: | ||||
|                 _LOGGER.warning("Local components are not implemented yet.") | ||||
|  | ||||
|             add_idf_component( | ||||
|                 name=component[CONF_NAME], | ||||
|                 repo=component.get(CONF_SOURCE), | ||||
|                 ref=component.get(CONF_REF), | ||||
|                 path=component.get(CONF_PATH), | ||||
|             ) | ||||
|     elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: | ||||
|         cg.add_platformio_option("framework", "arduino") | ||||
|         cg.add_build_flag("-DUSE_ARDUINO") | ||||
| @@ -924,6 +930,26 @@ def _write_sdkconfig(): | ||||
|         write_file_if_changed(sdk_path, contents) | ||||
|  | ||||
|  | ||||
| def _write_idf_component_yml(): | ||||
|     yml_path = Path(CORE.relative_build_path("src/idf_component.yml")) | ||||
|     if CORE.data[KEY_ESP32][KEY_COMPONENTS]: | ||||
|         components: dict = CORE.data[KEY_ESP32][KEY_COMPONENTS] | ||||
|         dependencies = {} | ||||
|         for name, component in components.items(): | ||||
|             dependency = {} | ||||
|             if component[KEY_REF]: | ||||
|                 dependency["version"] = component[KEY_REF] | ||||
|             if component[KEY_REPO]: | ||||
|                 dependency["git"] = component[KEY_REPO] | ||||
|             if component[KEY_PATH]: | ||||
|                 dependency["path"] = component[KEY_PATH] | ||||
|             dependencies[name] = dependency | ||||
|         contents = yaml_util.dump({"dependencies": dependencies}) | ||||
|     else: | ||||
|         contents = "" | ||||
|     write_file_if_changed(yml_path, contents) | ||||
|  | ||||
|  | ||||
| # Called by writer.py | ||||
| def copy_files(): | ||||
|     if CORE.using_arduino: | ||||
| @@ -936,6 +962,7 @@ def copy_files(): | ||||
|             ) | ||||
|     if CORE.using_esp_idf: | ||||
|         _write_sdkconfig() | ||||
|         _write_idf_component_yml() | ||||
|         if "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]: | ||||
|             write_file_if_changed( | ||||
|                 CORE.relative_build_path("partitions.csv"), | ||||
| @@ -952,55 +979,6 @@ def copy_files(): | ||||
|             __version__, | ||||
|         ) | ||||
|  | ||||
|         import shutil | ||||
|  | ||||
|         shutil.rmtree(CORE.relative_build_path("components"), ignore_errors=True) | ||||
|  | ||||
|         if CORE.data[KEY_ESP32][KEY_COMPONENTS]: | ||||
|             components: dict = CORE.data[KEY_ESP32][KEY_COMPONENTS] | ||||
|  | ||||
|             for name, component in components.items(): | ||||
|                 repo_dir, _ = git.clone_or_update( | ||||
|                     url=component[KEY_REPO], | ||||
|                     ref=component[KEY_REF], | ||||
|                     refresh=component[KEY_REFRESH], | ||||
|                     domain="idf_components", | ||||
|                     submodules=component[KEY_SUBMODULES], | ||||
|                 ) | ||||
|                 mkdir_p(CORE.relative_build_path("components")) | ||||
|                 component_dir = repo_dir | ||||
|                 if component[KEY_PATH] is not None: | ||||
|                     component_dir = component_dir / component[KEY_PATH] | ||||
|  | ||||
|                 if component[KEY_COMPONENTS] == ["*"]: | ||||
|                     shutil.copytree( | ||||
|                         component_dir, | ||||
|                         CORE.relative_build_path("components"), | ||||
|                         dirs_exist_ok=True, | ||||
|                         ignore=shutil.ignore_patterns(".git*"), | ||||
|                         symlinks=True, | ||||
|                         ignore_dangling_symlinks=True, | ||||
|                     ) | ||||
|                 elif len(component[KEY_COMPONENTS]) > 0: | ||||
|                     for comp in component[KEY_COMPONENTS]: | ||||
|                         shutil.copytree( | ||||
|                             component_dir / comp, | ||||
|                             CORE.relative_build_path(f"components/{comp}"), | ||||
|                             dirs_exist_ok=True, | ||||
|                             ignore=shutil.ignore_patterns(".git*"), | ||||
|                             symlinks=True, | ||||
|                             ignore_dangling_symlinks=True, | ||||
|                         ) | ||||
|                 else: | ||||
|                     shutil.copytree( | ||||
|                         component_dir, | ||||
|                         CORE.relative_build_path(f"components/{name}"), | ||||
|                         dirs_exist_ok=True, | ||||
|                         ignore=shutil.ignore_patterns(".git*"), | ||||
|                         symlinks=True, | ||||
|                         ignore_dangling_symlinks=True, | ||||
|                     ) | ||||
|  | ||||
|     for _, file in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES].items(): | ||||
|         if file[KEY_PATH].startswith("http"): | ||||
|             import requests | ||||
|   | ||||
| @@ -29,9 +29,9 @@ class ESP32InternalGPIOPin : public InternalGPIOPin { | ||||
|   void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; | ||||
|  | ||||
|   gpio_num_t pin_; | ||||
|   bool inverted_; | ||||
|   gpio_drive_cap_t drive_strength_; | ||||
|   gpio::Flags flags_; | ||||
|   bool inverted_; | ||||
|   // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | ||||
|   static bool isr_service_installed; | ||||
| }; | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| #include "ble.h" | ||||
| #include "ble_event_pool.h" | ||||
|  | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #include <esp_bt.h> | ||||
| @@ -516,13 +516,12 @@ void ESP32BLE::dump_config() { | ||||
|         break; | ||||
|     } | ||||
|     ESP_LOGCONFIG(TAG, | ||||
|                   "ESP32 BLE:\n" | ||||
|                   "  MAC address: %02X:%02X:%02X:%02X:%02X:%02X\n" | ||||
|                   "BLE:\n" | ||||
|                   "  MAC address: %s\n" | ||||
|                   "  IO Capability: %s", | ||||
|                   mac_address[0], mac_address[1], mac_address[2], mac_address[3], mac_address[4], mac_address[5], | ||||
|                   io_capability_s); | ||||
|                   format_mac_address_pretty(mac_address).c_str(), io_capability_s); | ||||
|   } else { | ||||
|     ESP_LOGCONFIG(TAG, "ESP32 BLE: bluetooth stack is not enabled"); | ||||
|     ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled"); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| @@ -496,17 +496,17 @@ float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) { | ||||
|       if (length > 2) { | ||||
|         return (float) encode_uint16(value[1], value[2]); | ||||
|       } | ||||
|       // fall through | ||||
|       [[fallthrough]]; | ||||
|     case 0x7:  // uint24. | ||||
|       if (length > 3) { | ||||
|         return (float) encode_uint24(value[1], value[2], value[3]); | ||||
|       } | ||||
|       // fall through | ||||
|       [[fallthrough]]; | ||||
|     case 0x8:  // uint32. | ||||
|       if (length > 4) { | ||||
|         return (float) encode_uint32(value[1], value[2], value[3], value[4]); | ||||
|       } | ||||
|       // fall through | ||||
|       [[fallthrough]]; | ||||
|     case 0xC:  // int8. | ||||
|       return (float) ((int8_t) value[1]); | ||||
|     case 0xD:  // int12. | ||||
| @@ -514,12 +514,12 @@ float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) { | ||||
|       if (length > 2) { | ||||
|         return (float) ((int16_t) (value[1] << 8) + (int16_t) value[2]); | ||||
|       } | ||||
|       // fall through | ||||
|       [[fallthrough]]; | ||||
|     case 0xF:  // int24. | ||||
|       if (length > 3) { | ||||
|         return (float) ((int32_t) (value[1] << 16) + (int32_t) (value[2] << 8) + (int32_t) (value[3])); | ||||
|       } | ||||
|       // fall through | ||||
|       [[fallthrough]]; | ||||
|     case 0x10:  // int32. | ||||
|       if (length > 4) { | ||||
|         return (float) ((int32_t) (value[1] << 24) + (int32_t) (value[2] << 16) + (int32_t) (value[3] << 8) + | ||||
|   | ||||
| @@ -522,6 +522,7 @@ optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData | ||||
| } | ||||
|  | ||||
| void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) { | ||||
|   this->scan_result_ = &scan_result; | ||||
|   for (uint8_t i = 0; i < ESP_BD_ADDR_LEN; i++) | ||||
|     this->address_[i] = scan_result.bda[i]; | ||||
|   this->address_type_ = static_cast<esp_ble_addr_type_t>(scan_result.ble_addr_type); | ||||
|   | ||||
| @@ -85,6 +85,9 @@ class ESPBTDevice { | ||||
|  | ||||
|   const std::vector<ServiceData> &get_service_datas() const { return service_datas_; } | ||||
|  | ||||
|   // Exposed through a function for use in lambdas | ||||
|   const BLEScanResult &get_scan_result() const { return *scan_result_; } | ||||
|  | ||||
|   bool resolve_irk(const uint8_t *irk) const; | ||||
|  | ||||
|   optional<ESPBLEiBeacon> get_ibeacon() const { | ||||
| @@ -111,6 +114,7 @@ class ESPBTDevice { | ||||
|   std::vector<ESPBTUUID> service_uuids_{}; | ||||
|   std::vector<ServiceData> manufacturer_datas_{}; | ||||
|   std::vector<ServiceData> service_datas_{}; | ||||
|   const BLEScanResult *scan_result_{nullptr}; | ||||
| }; | ||||
|  | ||||
| class ESP32BLETracker; | ||||
|   | ||||
| @@ -19,7 +19,7 @@ from esphome.const import ( | ||||
|     CONF_VSYNC_PIN, | ||||
| ) | ||||
| from esphome.core import CORE | ||||
| from esphome.cpp_helpers import setup_entity | ||||
| from esphome.core.entity_helpers import setup_entity | ||||
|  | ||||
| DEPENDENCIES = ["esp32"] | ||||
|  | ||||
| @@ -284,7 +284,7 @@ SETTERS = { | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await setup_entity(var, config) | ||||
|     await setup_entity(var, config, "camera") | ||||
|     await cg.register_component(var, config) | ||||
|  | ||||
|     for key, setter in SETTERS.items(): | ||||
| @@ -310,11 +310,7 @@ async def to_code(config): | ||||
|     cg.add_define("USE_ESP32_CAMERA") | ||||
|  | ||||
|     if CORE.using_esp_idf: | ||||
|         add_idf_component( | ||||
|             name="esp32-camera", | ||||
|             repo="https://github.com/espressif/esp32-camera.git", | ||||
|             ref="v2.0.15", | ||||
|         ) | ||||
|         add_idf_component(name="espressif/esp32-camera", ref="2.0.15") | ||||
|  | ||||
|     for conf in config.get(CONF_ON_STREAM_START, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|   | ||||
							
								
								
									
										0
									
								
								esphome/components/esp32_hall/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/esp32_hall/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										5
									
								
								esphome/components/esp32_hall/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								esphome/components/esp32_hall/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| import esphome.config_validation as cv | ||||
|  | ||||
| CONFIG_SCHEMA = cv.invalid( | ||||
|     "The esp32_hall component has been removed as of ESPHome 2025.7.0. See https://github.com/esphome/esphome/pull/9117 for details." | ||||
| ) | ||||
							
								
								
									
										101
									
								
								esphome/components/esp32_hosted/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								esphome/components/esp32_hosted/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | ||||
| import os | ||||
|  | ||||
| from esphome import pins | ||||
| from esphome.components import esp32 | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_CLK_PIN, | ||||
|     CONF_RESET_PIN, | ||||
|     CONF_VARIANT, | ||||
|     KEY_CORE, | ||||
|     KEY_FRAMEWORK_VERSION, | ||||
| ) | ||||
| from esphome.core import CORE | ||||
|  | ||||
| CODEOWNERS = ["@swoboda1337"] | ||||
|  | ||||
| CONF_ACTIVE_HIGH = "active_high" | ||||
| CONF_CMD_PIN = "cmd_pin" | ||||
| CONF_D0_PIN = "d0_pin" | ||||
| CONF_D1_PIN = "d1_pin" | ||||
| CONF_D2_PIN = "d2_pin" | ||||
| CONF_D3_PIN = "d3_pin" | ||||
| CONF_SLOT = "slot" | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.Required(CONF_VARIANT): cv.one_of(*esp32.VARIANTS, upper=True), | ||||
|             cv.Required(CONF_ACTIVE_HIGH): cv.boolean, | ||||
|             cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_number, | ||||
|             cv.Required(CONF_CMD_PIN): pins.internal_gpio_output_pin_number, | ||||
|             cv.Required(CONF_D0_PIN): pins.internal_gpio_output_pin_number, | ||||
|             cv.Required(CONF_D1_PIN): pins.internal_gpio_output_pin_number, | ||||
|             cv.Required(CONF_D2_PIN): pins.internal_gpio_output_pin_number, | ||||
|             cv.Required(CONF_D3_PIN): pins.internal_gpio_output_pin_number, | ||||
|             cv.Required(CONF_RESET_PIN): pins.internal_gpio_output_pin_number, | ||||
|             cv.Optional(CONF_SLOT, default=1): cv.int_range(min=0, max=1), | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     if config[CONF_ACTIVE_HIGH]: | ||||
|         esp32.add_idf_sdkconfig_option( | ||||
|             "CONFIG_ESP_HOSTED_SDIO_RESET_ACTIVE_HIGH", | ||||
|             True, | ||||
|         ) | ||||
|     else: | ||||
|         esp32.add_idf_sdkconfig_option( | ||||
|             "CONFIG_ESP_HOSTED_SDIO_RESET_ACTIVE_LOW", | ||||
|             True, | ||||
|         ) | ||||
|     esp32.add_idf_sdkconfig_option( | ||||
|         "CONFIG_ESP_HOSTED_SDIO_GPIO_RESET_SLAVE",  # NOLINT | ||||
|         config[CONF_RESET_PIN], | ||||
|     ) | ||||
|     esp32.add_idf_sdkconfig_option( | ||||
|         f"CONFIG_SLAVE_IDF_TARGET_{config[CONF_VARIANT]}",  # NOLINT | ||||
|         True, | ||||
|     ) | ||||
|     esp32.add_idf_sdkconfig_option( | ||||
|         f"CONFIG_ESP_HOSTED_SDIO_SLOT_{config[CONF_SLOT]}", | ||||
|         True, | ||||
|     ) | ||||
|     esp32.add_idf_sdkconfig_option( | ||||
|         f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CLK_SLOT_{config[CONF_SLOT]}", | ||||
|         config[CONF_CLK_PIN], | ||||
|     ) | ||||
|     esp32.add_idf_sdkconfig_option( | ||||
|         f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CMD_SLOT_{config[CONF_SLOT]}", | ||||
|         config[CONF_CMD_PIN], | ||||
|     ) | ||||
|     esp32.add_idf_sdkconfig_option( | ||||
|         f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D0_SLOT_{config[CONF_SLOT]}", | ||||
|         config[CONF_D0_PIN], | ||||
|     ) | ||||
|     esp32.add_idf_sdkconfig_option( | ||||
|         f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D1_4BIT_BUS_SLOT_{config[CONF_SLOT]}", | ||||
|         config[CONF_D1_PIN], | ||||
|     ) | ||||
|     esp32.add_idf_sdkconfig_option( | ||||
|         f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D2_4BIT_BUS_SLOT_{config[CONF_SLOT]}", | ||||
|         config[CONF_D2_PIN], | ||||
|     ) | ||||
|     esp32.add_idf_sdkconfig_option( | ||||
|         f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D3_4BIT_BUS_SLOT_{config[CONF_SLOT]}", | ||||
|         config[CONF_D3_PIN], | ||||
|     ) | ||||
|     esp32.add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_CUSTOM_SDIO_PINS", True) | ||||
|  | ||||
|     framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] | ||||
|     os.environ["ESP_IDF_VERSION"] = f"{framework_ver.major}.{framework_ver.minor}" | ||||
|     esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="0.10.2") | ||||
|     esp32.add_idf_component(name="espressif/eppp_link", ref="0.2.0") | ||||
|     esp32.add_idf_component(name="espressif/esp_hosted", ref="2.0.11") | ||||
|     esp32.add_extra_script( | ||||
|         "post", | ||||
|         "esp32_hosted.py", | ||||
|         os.path.join(os.path.dirname(__file__), "esp32_hosted.py.script"), | ||||
|     ) | ||||
							
								
								
									
										12
									
								
								esphome/components/esp32_hosted/esp32_hosted.py.script
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								esphome/components/esp32_hosted/esp32_hosted.py.script
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| # pylint: disable=E0602 | ||||
| Import("env")  # noqa | ||||
|  | ||||
| # Workaround whole archive issue | ||||
| if "__LIB_DEPS" in env and "libespressif__esp_hosted.a" in env["__LIB_DEPS"]: | ||||
|     env.Append( | ||||
|         LINKFLAGS=[ | ||||
|             "-Wl,--whole-archive", | ||||
|             env["BUILD_DIR"] + "/esp-idf/espressif__esp_hosted/libespressif__esp_hosted.a", | ||||
|             "-Wl,--no-whole-archive", | ||||
|         ] | ||||
|     ) | ||||
| @@ -183,7 +183,7 @@ async def to_code(config): | ||||
|  | ||||
|     cg.add_platformio_option("board", config[CONF_BOARD]) | ||||
|     cg.add_build_flag("-DUSE_ESP8266") | ||||
|     cg.set_cpp_standard("gnu++17") | ||||
|     cg.set_cpp_standard("gnu++20") | ||||
|     cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) | ||||
|     cg.add_define("ESPHOME_VARIANT", "ESP8266") | ||||
|  | ||||
|   | ||||
| @@ -129,9 +129,9 @@ void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) { | ||||
|     } | ||||
|   } else { | ||||
|     if (value != arg->inverted) { | ||||
|       *arg->out_set_reg |= 1; | ||||
|       *arg->out_set_reg = *arg->out_set_reg | 1; | ||||
|     } else { | ||||
|       *arg->out_set_reg &= ~1; | ||||
|       *arg->out_set_reg = *arg->out_set_reg & ~1; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -147,7 +147,7 @@ void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) { | ||||
|     if (flags & gpio::FLAG_OUTPUT) { | ||||
|       *arg->mode_set_reg = arg->mask; | ||||
|       if (flags & gpio::FLAG_OPEN_DRAIN) { | ||||
|         *arg->control_reg |= 1 << GPCD; | ||||
|         *arg->control_reg = *arg->control_reg | (1 << GPCD); | ||||
|       } else { | ||||
|         *arg->control_reg &= ~(1 << GPCD); | ||||
|       } | ||||
| @@ -155,21 +155,21 @@ void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) { | ||||
|       *arg->mode_clr_reg = arg->mask; | ||||
|     } | ||||
|     if (flags & gpio::FLAG_PULLUP) { | ||||
|       *arg->func_reg |= 1 << GPFPU; | ||||
|       *arg->control_reg |= 1 << GPCD; | ||||
|       *arg->func_reg = *arg->func_reg | (1 << GPFPU); | ||||
|       *arg->control_reg = *arg->control_reg | (1 << GPCD); | ||||
|     } else { | ||||
|       *arg->func_reg &= ~(1 << GPFPU); | ||||
|       *arg->func_reg = *arg->func_reg & ~(1 << GPFPU); | ||||
|     } | ||||
|   } else { | ||||
|     if (flags & gpio::FLAG_OUTPUT) { | ||||
|       *arg->mode_set_reg |= 1; | ||||
|       *arg->mode_set_reg = *arg->mode_set_reg | 1; | ||||
|     } else { | ||||
|       *arg->mode_set_reg &= ~1; | ||||
|       *arg->mode_set_reg = *arg->mode_set_reg & ~1; | ||||
|     } | ||||
|     if (flags & gpio::FLAG_PULLDOWN) { | ||||
|       *arg->func_reg |= 1 << GP16FPD; | ||||
|       *arg->func_reg = *arg->func_reg | (1 << GP16FPD); | ||||
|     } else { | ||||
|       *arg->func_reg &= ~(1 << GP16FPD); | ||||
|       *arg->func_reg = *arg->func_reg & ~(1 << GP16FPD); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -15,7 +15,7 @@ | ||||
| namespace esphome { | ||||
| namespace ethernet { | ||||
|  | ||||
| enum EthernetType { | ||||
| enum EthernetType : uint8_t { | ||||
|   ETHERNET_TYPE_UNKNOWN = 0, | ||||
|   ETHERNET_TYPE_LAN8720, | ||||
|   ETHERNET_TYPE_RTL8201, | ||||
| @@ -42,7 +42,7 @@ struct PHYRegister { | ||||
|   uint32_t page; | ||||
| }; | ||||
|  | ||||
| enum class EthernetComponentState { | ||||
| enum class EthernetComponentState : uint8_t { | ||||
|   STOPPED, | ||||
|   CONNECTING, | ||||
|   CONNECTED, | ||||
| @@ -119,25 +119,31 @@ class EthernetComponent : public Component { | ||||
|   uint32_t polling_interval_{0}; | ||||
| #endif | ||||
| #else | ||||
|   uint8_t phy_addr_{0}; | ||||
|   // Group all 32-bit members first | ||||
|   int power_pin_{-1}; | ||||
|   uint8_t mdc_pin_{23}; | ||||
|   uint8_t mdio_pin_{18}; | ||||
|   emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN}; | ||||
|   emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO}; | ||||
|   std::vector<PHYRegister> phy_registers_{}; | ||||
| #endif | ||||
|   EthernetType type_{ETHERNET_TYPE_UNKNOWN}; | ||||
|   optional<ManualIP> manual_ip_{}; | ||||
|  | ||||
|   // Group all 8-bit members together | ||||
|   uint8_t phy_addr_{0}; | ||||
|   uint8_t mdc_pin_{23}; | ||||
|   uint8_t mdio_pin_{18}; | ||||
| #endif | ||||
|   optional<ManualIP> manual_ip_{}; | ||||
|   uint32_t connect_begin_; | ||||
|  | ||||
|   // Group all uint8_t types together (enums and bools) | ||||
|   EthernetType type_{ETHERNET_TYPE_UNKNOWN}; | ||||
|   EthernetComponentState state_{EthernetComponentState::STOPPED}; | ||||
|   bool started_{false}; | ||||
|   bool connected_{false}; | ||||
|   bool got_ipv4_address_{false}; | ||||
| #if LWIP_IPV6 | ||||
|   uint8_t ipv6_count_{0}; | ||||
| #endif /* LWIP_IPV6 */ | ||||
|   EthernetComponentState state_{EthernetComponentState::STOPPED}; | ||||
|   uint32_t connect_begin_; | ||||
|  | ||||
|   // Pointers at the end (naturally aligned) | ||||
|   esp_netif_t *eth_netif_{nullptr}; | ||||
|   esp_eth_handle_t eth_handle_; | ||||
|   esp_eth_phy_t *phy_{nullptr}; | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user