mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Merge branch 'device_id_state' into integration
This commit is contained in:
		| @@ -124,6 +124,7 @@ esphome/components/dht/* @OttoWinter | |||||||
| esphome/components/display_menu_base/* @numo68 | esphome/components/display_menu_base/* @numo68 | ||||||
| esphome/components/dps310/* @kbx81 | esphome/components/dps310/* @kbx81 | ||||||
| esphome/components/ds1307/* @badbadc0ffee | esphome/components/ds1307/* @badbadc0ffee | ||||||
|  | esphome/components/ds2484/* @mrk-its | ||||||
| esphome/components/dsmr/* @glmnet @zuidwijk | esphome/components/dsmr/* @glmnet @zuidwijk | ||||||
| esphome/components/duty_time/* @dudanov | esphome/components/duty_time/* @dudanov | ||||||
| esphome/components/ee895/* @Stock-M | esphome/components/ee895/* @Stock-M | ||||||
|   | |||||||
| @@ -311,6 +311,7 @@ message BinarySensorStateResponse { | |||||||
|   // If the binary sensor does not have a valid state yet. |   // If the binary sensor does not have a valid state yet. | ||||||
|   // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller |   // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller | ||||||
|   bool missing_state = 3; |   bool missing_state = 3; | ||||||
|  |   uint32 device_id = 4; | ||||||
| } | } | ||||||
|  |  | ||||||
| // ==================== COVER ==================== | // ==================== COVER ==================== | ||||||
| @@ -360,6 +361,7 @@ message CoverStateResponse { | |||||||
|   float position = 3; |   float position = 3; | ||||||
|   float tilt = 4; |   float tilt = 4; | ||||||
|   CoverOperation current_operation = 5; |   CoverOperation current_operation = 5; | ||||||
|  |   uint32 device_id = 6; | ||||||
| } | } | ||||||
|  |  | ||||||
| enum LegacyCoverCommand { | enum LegacyCoverCommand { | ||||||
| @@ -432,6 +434,7 @@ message FanStateResponse { | |||||||
|   FanDirection direction = 5; |   FanDirection direction = 5; | ||||||
|   int32 speed_level = 6; |   int32 speed_level = 6; | ||||||
|   string preset_mode = 7; |   string preset_mode = 7; | ||||||
|  |   uint32 device_id = 8; | ||||||
| } | } | ||||||
| message FanCommandRequest { | message FanCommandRequest { | ||||||
|   option (id) = 31; |   option (id) = 31; | ||||||
| @@ -513,6 +516,7 @@ message LightStateResponse { | |||||||
|   float cold_white = 12; |   float cold_white = 12; | ||||||
|   float warm_white = 13; |   float warm_white = 13; | ||||||
|   string effect = 9; |   string effect = 9; | ||||||
|  |   uint32 device_id = 14; | ||||||
| } | } | ||||||
| message LightCommandRequest { | message LightCommandRequest { | ||||||
|   option (id) = 32; |   option (id) = 32; | ||||||
| @@ -598,6 +602,7 @@ message SensorStateResponse { | |||||||
|   // If the sensor does not have a valid state yet. |   // If the sensor does not have a valid state yet. | ||||||
|   // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller |   // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller | ||||||
|   bool missing_state = 3; |   bool missing_state = 3; | ||||||
|  |   uint32 device_id = 4; | ||||||
| } | } | ||||||
|  |  | ||||||
| // ==================== SWITCH ==================== | // ==================== SWITCH ==================== | ||||||
| @@ -628,6 +633,7 @@ message SwitchStateResponse { | |||||||
|  |  | ||||||
|   fixed32 key = 1; |   fixed32 key = 1; | ||||||
|   bool state = 2; |   bool state = 2; | ||||||
|  |   uint32 device_id = 3; | ||||||
| } | } | ||||||
| message SwitchCommandRequest { | message SwitchCommandRequest { | ||||||
|   option (id) = 33; |   option (id) = 33; | ||||||
| @@ -669,6 +675,7 @@ message TextSensorStateResponse { | |||||||
|   // If the text sensor does not have a valid state yet. |   // If the text sensor does not have a valid state yet. | ||||||
|   // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller |   // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller | ||||||
|   bool missing_state = 3; |   bool missing_state = 3; | ||||||
|  |   uint32 device_id = 4; | ||||||
| } | } | ||||||
|  |  | ||||||
| // ==================== SUBSCRIBE LOGS ==================== | // ==================== SUBSCRIBE LOGS ==================== | ||||||
| @@ -966,6 +973,7 @@ message ClimateStateResponse { | |||||||
|   string custom_preset = 13; |   string custom_preset = 13; | ||||||
|   float current_humidity = 14; |   float current_humidity = 14; | ||||||
|   float target_humidity = 15; |   float target_humidity = 15; | ||||||
|  |   uint32 device_id = 16; | ||||||
| } | } | ||||||
| message ClimateCommandRequest { | message ClimateCommandRequest { | ||||||
|   option (id) = 48; |   option (id) = 48; | ||||||
| @@ -1039,6 +1047,7 @@ message NumberStateResponse { | |||||||
|   // If the number does not have a valid state yet. |   // If the number does not have a valid state yet. | ||||||
|   // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller |   // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller | ||||||
|   bool missing_state = 3; |   bool missing_state = 3; | ||||||
|  |   uint32 device_id = 4; | ||||||
| } | } | ||||||
| message NumberCommandRequest { | message NumberCommandRequest { | ||||||
|   option (id) = 51; |   option (id) = 51; | ||||||
| @@ -1080,6 +1089,7 @@ message SelectStateResponse { | |||||||
|   // If the select does not have a valid state yet. |   // If the select does not have a valid state yet. | ||||||
|   // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller |   // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller | ||||||
|   bool missing_state = 3; |   bool missing_state = 3; | ||||||
|  |   uint32 device_id = 4; | ||||||
| } | } | ||||||
| message SelectCommandRequest { | message SelectCommandRequest { | ||||||
|   option (id) = 54; |   option (id) = 54; | ||||||
| @@ -1120,6 +1130,7 @@ message SirenStateResponse { | |||||||
|  |  | ||||||
|   fixed32 key = 1; |   fixed32 key = 1; | ||||||
|   bool state = 2; |   bool state = 2; | ||||||
|  |   uint32 device_id = 3; | ||||||
| } | } | ||||||
| message SirenCommandRequest { | message SirenCommandRequest { | ||||||
|   option (id) = 57; |   option (id) = 57; | ||||||
| @@ -1183,6 +1194,7 @@ message LockStateResponse { | |||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
|   fixed32 key = 1; |   fixed32 key = 1; | ||||||
|   LockState state = 2; |   LockState state = 2; | ||||||
|  |   uint32 device_id = 3; | ||||||
| } | } | ||||||
| message LockCommandRequest { | message LockCommandRequest { | ||||||
|   option (id) = 60; |   option (id) = 60; | ||||||
| @@ -1282,6 +1294,7 @@ message MediaPlayerStateResponse { | |||||||
|   MediaPlayerState state = 2; |   MediaPlayerState state = 2; | ||||||
|   float volume = 3; |   float volume = 3; | ||||||
|   bool muted = 4; |   bool muted = 4; | ||||||
|  |   uint32 device_id = 5; | ||||||
| } | } | ||||||
| message MediaPlayerCommandRequest { | message MediaPlayerCommandRequest { | ||||||
|   option (id) = 65; |   option (id) = 65; | ||||||
| @@ -1822,6 +1835,7 @@ message AlarmControlPanelStateResponse { | |||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
|   fixed32 key = 1; |   fixed32 key = 1; | ||||||
|   AlarmControlPanelState state = 2; |   AlarmControlPanelState state = 2; | ||||||
|  |   uint32 device_id = 3; | ||||||
| } | } | ||||||
|  |  | ||||||
| message AlarmControlPanelCommandRequest { | message AlarmControlPanelCommandRequest { | ||||||
| @@ -1871,6 +1885,7 @@ message TextStateResponse { | |||||||
|   // If the Text does not have a valid state yet. |   // If the Text does not have a valid state yet. | ||||||
|   // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller |   // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller | ||||||
|   bool missing_state = 3; |   bool missing_state = 3; | ||||||
|  |   uint32 device_id = 4; | ||||||
| } | } | ||||||
| message TextCommandRequest { | message TextCommandRequest { | ||||||
|   option (id) = 99; |   option (id) = 99; | ||||||
| @@ -1914,6 +1929,7 @@ message DateStateResponse { | |||||||
|   uint32 year = 3; |   uint32 year = 3; | ||||||
|   uint32 month = 4; |   uint32 month = 4; | ||||||
|   uint32 day = 5; |   uint32 day = 5; | ||||||
|  |   uint32 device_id = 6; | ||||||
| } | } | ||||||
| message DateCommandRequest { | message DateCommandRequest { | ||||||
|   option (id) = 102; |   option (id) = 102; | ||||||
| @@ -1958,6 +1974,7 @@ message TimeStateResponse { | |||||||
|   uint32 hour = 3; |   uint32 hour = 3; | ||||||
|   uint32 minute = 4; |   uint32 minute = 4; | ||||||
|   uint32 second = 5; |   uint32 second = 5; | ||||||
|  |   uint32 device_id = 6; | ||||||
| } | } | ||||||
| message TimeCommandRequest { | message TimeCommandRequest { | ||||||
|   option (id) = 105; |   option (id) = 105; | ||||||
| @@ -1999,6 +2016,7 @@ message EventResponse { | |||||||
|  |  | ||||||
|   fixed32 key = 1; |   fixed32 key = 1; | ||||||
|   string event_type = 2; |   string event_type = 2; | ||||||
|  |   uint32 device_id = 3; | ||||||
| } | } | ||||||
|  |  | ||||||
| // ==================== VALVE ==================== | // ==================== VALVE ==================== | ||||||
| @@ -2039,6 +2057,7 @@ message ValveStateResponse { | |||||||
|   fixed32 key = 1; |   fixed32 key = 1; | ||||||
|   float position = 2; |   float position = 2; | ||||||
|   ValveOperation current_operation = 3; |   ValveOperation current_operation = 3; | ||||||
|  |   uint32 device_id = 4; | ||||||
| } | } | ||||||
|  |  | ||||||
| message ValveCommandRequest { | message ValveCommandRequest { | ||||||
| @@ -2082,6 +2101,7 @@ message DateTimeStateResponse { | |||||||
|   // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller |   // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller | ||||||
|   bool missing_state = 2; |   bool missing_state = 2; | ||||||
|   fixed32 epoch_seconds = 3; |   fixed32 epoch_seconds = 3; | ||||||
|  |   uint32 device_id = 4; | ||||||
| } | } | ||||||
| message DateTimeCommandRequest { | message DateTimeCommandRequest { | ||||||
|   option (id) = 114; |   option (id) = 114; | ||||||
| @@ -2128,6 +2148,7 @@ message UpdateStateResponse { | |||||||
|   string title = 8; |   string title = 8; | ||||||
|   string release_summary = 9; |   string release_summary = 9; | ||||||
|   string release_url = 10; |   string release_url = 10; | ||||||
|  |   uint32 device_id = 11; | ||||||
| } | } | ||||||
| enum UpdateCommand { | enum UpdateCommand { | ||||||
|   UPDATE_COMMAND_NONE = 0; |   UPDATE_COMMAND_NONE = 0; | ||||||
|   | |||||||
| @@ -90,19 +90,6 @@ APIConnection::~APIConnection() { | |||||||
| #endif | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP |  | ||||||
| void APIConnection::log_batch_item_(const DeferredBatch::BatchItem &item) { |  | ||||||
|   // Set log-only mode |  | ||||||
|   this->flags_.log_only_mode = true; |  | ||||||
|  |  | ||||||
|   // Call the creator - it will create the message and log it via encode_message_to_buffer |  | ||||||
|   item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true, item.message_type); |  | ||||||
|  |  | ||||||
|   // Clear log-only mode |  | ||||||
|   this->flags_.log_only_mode = false; |  | ||||||
| } |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| void APIConnection::loop() { | void APIConnection::loop() { | ||||||
|   if (this->flags_.next_close) { |   if (this->flags_.next_close) { | ||||||
|     // requested a disconnect |     // requested a disconnect | ||||||
| @@ -154,15 +141,25 @@ void APIConnection::loop() { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Process deferred batch if scheduled |   // Process deferred batch if scheduled and timer has expired | ||||||
|   if (this->flags_.batch_scheduled && now - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) { |   if (this->flags_.batch_scheduled && now - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) { | ||||||
|     this->process_batch_(); |     this->process_batch_(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (!this->list_entities_iterator_.completed()) { |   if (!this->list_entities_iterator_.completed()) { | ||||||
|     this->list_entities_iterator_.advance(); |     this->process_iterator_batch_(this->list_entities_iterator_); | ||||||
|   } else if (!this->initial_state_iterator_.completed()) { |   } else if (!this->initial_state_iterator_.completed()) { | ||||||
|     this->initial_state_iterator_.advance(); |     this->process_iterator_batch_(this->initial_state_iterator_); | ||||||
|  |  | ||||||
|  |     // If we've completed initial states, process any remaining and clear the flag | ||||||
|  |     if (this->initial_state_iterator_.completed()) { | ||||||
|  |       // Process any remaining batched messages immediately | ||||||
|  |       if (!this->deferred_batch_.empty()) { | ||||||
|  |         this->process_batch_(); | ||||||
|  |       } | ||||||
|  |       // Now that everything is sent, enable immediate sending for future state changes | ||||||
|  |       this->flags_.should_try_send_immediately = true; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (this->flags_.sent_ping) { |   if (this->flags_.sent_ping) { | ||||||
| @@ -300,7 +297,7 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t mes | |||||||
|  |  | ||||||
| #ifdef USE_BINARY_SENSOR | #ifdef USE_BINARY_SENSOR | ||||||
| bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) { | bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) { | ||||||
|   return this->schedule_message_(binary_sensor, &APIConnection::try_send_binary_sensor_state, |   return this->send_message_smart_(binary_sensor, &APIConnection::try_send_binary_sensor_state, | ||||||
|                                    BinarySensorStateResponse::MESSAGE_TYPE); |                                    BinarySensorStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -328,7 +325,7 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne | |||||||
|  |  | ||||||
| #ifdef USE_COVER | #ifdef USE_COVER | ||||||
| bool APIConnection::send_cover_state(cover::Cover *cover) { | bool APIConnection::send_cover_state(cover::Cover *cover) { | ||||||
|   return this->schedule_message_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                              bool is_single) { |                                              bool is_single) { | ||||||
| @@ -389,7 +386,7 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_FAN | #ifdef USE_FAN | ||||||
| bool APIConnection::send_fan_state(fan::Fan *fan) { | bool APIConnection::send_fan_state(fan::Fan *fan) { | ||||||
|   return this->schedule_message_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                            bool is_single) { |                                            bool is_single) { | ||||||
| @@ -448,7 +445,7 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_LIGHT | #ifdef USE_LIGHT | ||||||
| bool APIConnection::send_light_state(light::LightState *light) { | bool APIConnection::send_light_state(light::LightState *light) { | ||||||
|   return this->schedule_message_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                              bool is_single) { |                                              bool is_single) { | ||||||
| @@ -540,7 +537,7 @@ void APIConnection::light_command(const LightCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
| bool APIConnection::send_sensor_state(sensor::Sensor *sensor) { | bool APIConnection::send_sensor_state(sensor::Sensor *sensor) { | ||||||
|   return this->schedule_message_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
|  |  | ||||||
| uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
| @@ -572,7 +569,7 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection * | |||||||
|  |  | ||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
| bool APIConnection::send_switch_state(switch_::Switch *a_switch) { | bool APIConnection::send_switch_state(switch_::Switch *a_switch) { | ||||||
|   return this->schedule_message_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
|  |  | ||||||
| uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
| @@ -609,7 +606,7 @@ void APIConnection::switch_command(const SwitchCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
| bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) { | bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) { | ||||||
|   return this->schedule_message_(text_sensor, &APIConnection::try_send_text_sensor_state, |   return this->send_message_smart_(text_sensor, &APIConnection::try_send_text_sensor_state, | ||||||
|                                    TextSensorStateResponse::MESSAGE_TYPE); |                                    TextSensorStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -637,7 +634,7 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect | |||||||
|  |  | ||||||
| #ifdef USE_CLIMATE | #ifdef USE_CLIMATE | ||||||
| bool APIConnection::send_climate_state(climate::Climate *climate) { | bool APIConnection::send_climate_state(climate::Climate *climate) { | ||||||
|   return this->schedule_message_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                                bool is_single) { |                                                bool is_single) { | ||||||
| @@ -737,7 +734,7 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_NUMBER | #ifdef USE_NUMBER | ||||||
| bool APIConnection::send_number_state(number::Number *number) { | bool APIConnection::send_number_state(number::Number *number) { | ||||||
|   return this->schedule_message_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
|  |  | ||||||
| uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
| @@ -777,7 +774,7 @@ void APIConnection::number_command(const NumberCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_DATETIME_DATE | #ifdef USE_DATETIME_DATE | ||||||
| bool APIConnection::send_date_state(datetime::DateEntity *date) { | bool APIConnection::send_date_state(datetime::DateEntity *date) { | ||||||
|   return this->schedule_message_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                             bool is_single) { |                                             bool is_single) { | ||||||
| @@ -811,7 +808,7 @@ void APIConnection::date_command(const DateCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_DATETIME_TIME | #ifdef USE_DATETIME_TIME | ||||||
| bool APIConnection::send_time_state(datetime::TimeEntity *time) { | bool APIConnection::send_time_state(datetime::TimeEntity *time) { | ||||||
|   return this->schedule_message_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                             bool is_single) { |                                             bool is_single) { | ||||||
| @@ -845,7 +842,7 @@ void APIConnection::time_command(const TimeCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_DATETIME_DATETIME | #ifdef USE_DATETIME_DATETIME | ||||||
| bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) { | bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) { | ||||||
|   return this->schedule_message_(datetime, &APIConnection::try_send_datetime_state, |   return this->send_message_smart_(datetime, &APIConnection::try_send_datetime_state, | ||||||
|                                    DateTimeStateResponse::MESSAGE_TYPE); |                                    DateTimeStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
| @@ -881,7 +878,7 @@ void APIConnection::datetime_command(const DateTimeCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_TEXT | #ifdef USE_TEXT | ||||||
| bool APIConnection::send_text_state(text::Text *text) { | bool APIConnection::send_text_state(text::Text *text) { | ||||||
|   return this->schedule_message_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
|  |  | ||||||
| uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
| @@ -919,7 +916,7 @@ void APIConnection::text_command(const TextCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
| bool APIConnection::send_select_state(select::Select *select) { | bool APIConnection::send_select_state(select::Select *select) { | ||||||
|   return this->schedule_message_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
|  |  | ||||||
| uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
| @@ -974,7 +971,7 @@ void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg | |||||||
|  |  | ||||||
| #ifdef USE_LOCK | #ifdef USE_LOCK | ||||||
| bool APIConnection::send_lock_state(lock::Lock *a_lock) { | bool APIConnection::send_lock_state(lock::Lock *a_lock) { | ||||||
|   return this->schedule_message_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
|  |  | ||||||
| uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
| @@ -1018,7 +1015,7 @@ void APIConnection::lock_command(const LockCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_VALVE | #ifdef USE_VALVE | ||||||
| bool APIConnection::send_valve_state(valve::Valve *valve) { | bool APIConnection::send_valve_state(valve::Valve *valve) { | ||||||
|   return this->schedule_message_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                              bool is_single) { |                                              bool is_single) { | ||||||
| @@ -1058,7 +1055,7 @@ void APIConnection::valve_command(const ValveCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_MEDIA_PLAYER | #ifdef USE_MEDIA_PLAYER | ||||||
| bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) { | bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) { | ||||||
|   return this->schedule_message_(media_player, &APIConnection::try_send_media_player_state, |   return this->send_message_smart_(media_player, &APIConnection::try_send_media_player_state, | ||||||
|                                    MediaPlayerStateResponse::MESSAGE_TYPE); |                                    MediaPlayerStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
| @@ -1320,7 +1317,7 @@ void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetCon | |||||||
|  |  | ||||||
| #ifdef USE_ALARM_CONTROL_PANEL | #ifdef USE_ALARM_CONTROL_PANEL | ||||||
| bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { | bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { | ||||||
|   return this->schedule_message_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_state, |   return this->send_message_smart_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_state, | ||||||
|                                    AlarmControlPanelStateResponse::MESSAGE_TYPE); |                                    AlarmControlPanelStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn, | uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn, | ||||||
| @@ -1404,7 +1401,7 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c | |||||||
|  |  | ||||||
| #ifdef USE_UPDATE | #ifdef USE_UPDATE | ||||||
| bool APIConnection::send_update_state(update::UpdateEntity *update) { | bool APIConnection::send_update_state(update::UpdateEntity *update) { | ||||||
|   return this->schedule_message_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                               bool is_single) { |                                               bool is_single) { | ||||||
| @@ -1751,11 +1748,16 @@ void APIConnection::process_batch_() { | |||||||
|  |  | ||||||
|     if (payload_size > 0 && |     if (payload_size > 0 && | ||||||
|         this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, item.message_type)) { |         this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, item.message_type)) { | ||||||
|       this->deferred_batch_.clear(); | #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 | ||||||
|  |       this->log_batch_item_(item); | ||||||
|  | #endif | ||||||
|  |       this->clear_batch_(); | ||||||
|     } else if (payload_size == 0) { |     } else if (payload_size == 0) { | ||||||
|       // Message too large |       // Message too large | ||||||
|       ESP_LOGW(TAG, "Message too large to send: type=%u", item.message_type); |       ESP_LOGW(TAG, "Message too large to send: type=%u", item.message_type); | ||||||
|       this->deferred_batch_.clear(); |       this->clear_batch_(); | ||||||
|     } |     } | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| @@ -1864,7 +1866,7 @@ void APIConnection::process_batch_() { | |||||||
|     this->schedule_batch_(); |     this->schedule_batch_(); | ||||||
|   } else { |   } else { | ||||||
|     // All items processed |     // All items processed | ||||||
|     this->deferred_batch_.clear(); |     this->clear_batch_(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -18,6 +18,8 @@ namespace api { | |||||||
|  |  | ||||||
| // Keepalive timeout in milliseconds | // Keepalive timeout in milliseconds | ||||||
| static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000; | static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000; | ||||||
|  | // Maximum number of entities to process in a single batch during initial state/info sending | ||||||
|  | static constexpr size_t MAX_INITIAL_PER_BATCH = 20; | ||||||
|  |  | ||||||
| class APIConnection : public APIServerConnection { | class APIConnection : public APIServerConnection { | ||||||
|  public: |  public: | ||||||
| @@ -290,12 +292,29 @@ class APIConnection : public APIServerConnection { | |||||||
|   // Helper function to fill common entity state fields |   // Helper function to fill common entity state fields | ||||||
|   static void fill_entity_state_base(esphome::EntityBase *entity, StateResponseProtoMessage &response) { |   static void fill_entity_state_base(esphome::EntityBase *entity, StateResponseProtoMessage &response) { | ||||||
|     response.key = entity->get_object_id_hash(); |     response.key = entity->get_object_id_hash(); | ||||||
|  | #ifdef USE_DEVICES | ||||||
|  |     response.device_id = entity->get_device_id(); | ||||||
|  | #endif | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Non-template helper to encode any ProtoMessage |   // Non-template helper to encode any ProtoMessage | ||||||
|   static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn, |   static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn, | ||||||
|                                            uint32_t remaining_size, bool is_single); |                                            uint32_t remaining_size, bool is_single); | ||||||
|  |  | ||||||
|  |   // Helper method to process multiple entities from an iterator in a batch | ||||||
|  |   template<typename Iterator> void process_iterator_batch_(Iterator &iterator) { | ||||||
|  |     size_t initial_size = this->deferred_batch_.size(); | ||||||
|  |     while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < MAX_INITIAL_PER_BATCH) { | ||||||
|  |       iterator.advance(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // If the batch is full, process it immediately | ||||||
|  |     // Note: iterator.advance() already calls schedule_batch_() via schedule_message_() | ||||||
|  |     if (this->deferred_batch_.size() >= MAX_INITIAL_PER_BATCH) { | ||||||
|  |       this->process_batch_(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
| #ifdef USE_BINARY_SENSOR | #ifdef USE_BINARY_SENSOR | ||||||
|   static uint16_t try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, |   static uint16_t try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                                bool is_single); |                                                bool is_single); | ||||||
| @@ -583,6 +602,7 @@ class APIConnection : public APIServerConnection { | |||||||
|     uint8_t next_close : 1; |     uint8_t next_close : 1; | ||||||
|     uint8_t batch_scheduled : 1; |     uint8_t batch_scheduled : 1; | ||||||
|     uint8_t batch_first_message : 1;          // For batch buffer allocation |     uint8_t batch_first_message : 1;          // For batch buffer allocation | ||||||
|  |     uint8_t should_try_send_immediately : 1;  // True after initial states are sent | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|     uint8_t log_only_mode : 1; |     uint8_t log_only_mode : 1; | ||||||
| #endif | #endif | ||||||
| @@ -609,11 +629,50 @@ class APIConnection : public APIServerConnection { | |||||||
|  |  | ||||||
|   bool schedule_batch_(); |   bool schedule_batch_(); | ||||||
|   void process_batch_(); |   void process_batch_(); | ||||||
|  |   void clear_batch_() { | ||||||
|  |     this->deferred_batch_.clear(); | ||||||
|  |     this->flags_.batch_scheduled = false; | ||||||
|  |   } | ||||||
|  |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void log_batch_item_(const DeferredBatch::BatchItem &item); |   // Helper to log a proto message from a MessageCreator object | ||||||
|  |   void log_proto_message_(EntityBase *entity, const MessageCreator &creator, uint16_t message_type) { | ||||||
|  |     this->flags_.log_only_mode = true; | ||||||
|  |     creator(entity, this, MAX_PACKET_SIZE, true, message_type); | ||||||
|  |     this->flags_.log_only_mode = false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void log_batch_item_(const DeferredBatch::BatchItem &item) { | ||||||
|  |     // Use the helper to log the message | ||||||
|  |     this->log_proto_message_(item.entity, item.creator, item.message_type); | ||||||
|  |   } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  |   // Helper method to send a message either immediately or via batching | ||||||
|  |   bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint16_t message_type) { | ||||||
|  |     // Try to send immediately if: | ||||||
|  |     // 1. We should try to send immediately (should_try_send_immediately = true) | ||||||
|  |     // 2. Batch delay is 0 (user has opted in to immediate sending) | ||||||
|  |     // 3. Buffer has space available | ||||||
|  |     if (this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0 && | ||||||
|  |         this->helper_->can_write_without_blocking()) { | ||||||
|  |       // Now actually encode and send | ||||||
|  |       if (creator(entity, this, MAX_PACKET_SIZE, true) && | ||||||
|  |           this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) { | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |         // Log the message in verbose mode | ||||||
|  |         this->log_proto_message_(entity, MessageCreator(creator), message_type); | ||||||
|  | #endif | ||||||
|  |         return true; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       // If immediate send failed, fall through to batching | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Fall back to scheduled batching | ||||||
|  |     return this->schedule_message_(entity, creator, message_type); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   // Helper function to schedule a deferred message with known message type |   // Helper function to schedule a deferred message with known message type | ||||||
|   bool schedule_message_(EntityBase *entity, MessageCreator creator, uint16_t message_type) { |   bool schedule_message_(EntityBase *entity, MessageCreator creator, uint16_t message_type) { | ||||||
|     this->deferred_batch_.add_item(entity, std::move(creator), message_type); |     this->deferred_batch_.add_item(entity, std::move(creator), message_type); | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -2,6 +2,8 @@ | |||||||
| // See script/api_protobuf/api_protobuf.py | // See script/api_protobuf/api_protobuf.py | ||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/defines.h" | ||||||
|  |  | ||||||
| #include "proto.h" | #include "proto.h" | ||||||
| #include "api_pb2_size.h" | #include "api_pb2_size.h" | ||||||
|  |  | ||||||
| @@ -15,6 +17,7 @@ enum EntityCategory : uint32_t { | |||||||
|   ENTITY_CATEGORY_CONFIG = 1, |   ENTITY_CATEGORY_CONFIG = 1, | ||||||
|   ENTITY_CATEGORY_DIAGNOSTIC = 2, |   ENTITY_CATEGORY_DIAGNOSTIC = 2, | ||||||
| }; | }; | ||||||
|  | #ifdef USE_COVER | ||||||
| enum LegacyCoverState : uint32_t { | enum LegacyCoverState : uint32_t { | ||||||
|   LEGACY_COVER_STATE_OPEN = 0, |   LEGACY_COVER_STATE_OPEN = 0, | ||||||
|   LEGACY_COVER_STATE_CLOSED = 1, |   LEGACY_COVER_STATE_CLOSED = 1, | ||||||
| @@ -29,6 +32,8 @@ enum LegacyCoverCommand : uint32_t { | |||||||
|   LEGACY_COVER_COMMAND_CLOSE = 1, |   LEGACY_COVER_COMMAND_CLOSE = 1, | ||||||
|   LEGACY_COVER_COMMAND_STOP = 2, |   LEGACY_COVER_COMMAND_STOP = 2, | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_FAN | ||||||
| enum FanSpeed : uint32_t { | enum FanSpeed : uint32_t { | ||||||
|   FAN_SPEED_LOW = 0, |   FAN_SPEED_LOW = 0, | ||||||
|   FAN_SPEED_MEDIUM = 1, |   FAN_SPEED_MEDIUM = 1, | ||||||
| @@ -38,6 +43,8 @@ enum FanDirection : uint32_t { | |||||||
|   FAN_DIRECTION_FORWARD = 0, |   FAN_DIRECTION_FORWARD = 0, | ||||||
|   FAN_DIRECTION_REVERSE = 1, |   FAN_DIRECTION_REVERSE = 1, | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_LIGHT | ||||||
| enum ColorMode : uint32_t { | enum ColorMode : uint32_t { | ||||||
|   COLOR_MODE_UNKNOWN = 0, |   COLOR_MODE_UNKNOWN = 0, | ||||||
|   COLOR_MODE_ON_OFF = 1, |   COLOR_MODE_ON_OFF = 1, | ||||||
| @@ -51,6 +58,8 @@ enum ColorMode : uint32_t { | |||||||
|   COLOR_MODE_RGB_COLOR_TEMPERATURE = 47, |   COLOR_MODE_RGB_COLOR_TEMPERATURE = 47, | ||||||
|   COLOR_MODE_RGB_COLD_WARM_WHITE = 51, |   COLOR_MODE_RGB_COLD_WARM_WHITE = 51, | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_SENSOR | ||||||
| enum SensorStateClass : uint32_t { | enum SensorStateClass : uint32_t { | ||||||
|   STATE_CLASS_NONE = 0, |   STATE_CLASS_NONE = 0, | ||||||
|   STATE_CLASS_MEASUREMENT = 1, |   STATE_CLASS_MEASUREMENT = 1, | ||||||
| @@ -62,6 +71,7 @@ enum SensorLastResetType : uint32_t { | |||||||
|   LAST_RESET_NEVER = 1, |   LAST_RESET_NEVER = 1, | ||||||
|   LAST_RESET_AUTO = 2, |   LAST_RESET_AUTO = 2, | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
| enum LogLevel : uint32_t { | enum LogLevel : uint32_t { | ||||||
|   LOG_LEVEL_NONE = 0, |   LOG_LEVEL_NONE = 0, | ||||||
|   LOG_LEVEL_ERROR = 1, |   LOG_LEVEL_ERROR = 1, | ||||||
| @@ -82,6 +92,7 @@ enum ServiceArgType : uint32_t { | |||||||
|   SERVICE_ARG_TYPE_FLOAT_ARRAY = 6, |   SERVICE_ARG_TYPE_FLOAT_ARRAY = 6, | ||||||
|   SERVICE_ARG_TYPE_STRING_ARRAY = 7, |   SERVICE_ARG_TYPE_STRING_ARRAY = 7, | ||||||
| }; | }; | ||||||
|  | #ifdef USE_CLIMATE | ||||||
| enum ClimateMode : uint32_t { | enum ClimateMode : uint32_t { | ||||||
|   CLIMATE_MODE_OFF = 0, |   CLIMATE_MODE_OFF = 0, | ||||||
|   CLIMATE_MODE_HEAT_COOL = 1, |   CLIMATE_MODE_HEAT_COOL = 1, | ||||||
| @@ -127,11 +138,15 @@ enum ClimatePreset : uint32_t { | |||||||
|   CLIMATE_PRESET_SLEEP = 6, |   CLIMATE_PRESET_SLEEP = 6, | ||||||
|   CLIMATE_PRESET_ACTIVITY = 7, |   CLIMATE_PRESET_ACTIVITY = 7, | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_NUMBER | ||||||
| enum NumberMode : uint32_t { | enum NumberMode : uint32_t { | ||||||
|   NUMBER_MODE_AUTO = 0, |   NUMBER_MODE_AUTO = 0, | ||||||
|   NUMBER_MODE_BOX = 1, |   NUMBER_MODE_BOX = 1, | ||||||
|   NUMBER_MODE_SLIDER = 2, |   NUMBER_MODE_SLIDER = 2, | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_LOCK | ||||||
| enum LockState : uint32_t { | enum LockState : uint32_t { | ||||||
|   LOCK_STATE_NONE = 0, |   LOCK_STATE_NONE = 0, | ||||||
|   LOCK_STATE_LOCKED = 1, |   LOCK_STATE_LOCKED = 1, | ||||||
| @@ -145,6 +160,8 @@ enum LockCommand : uint32_t { | |||||||
|   LOCK_LOCK = 1, |   LOCK_LOCK = 1, | ||||||
|   LOCK_OPEN = 2, |   LOCK_OPEN = 2, | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_MEDIA_PLAYER | ||||||
| enum MediaPlayerState : uint32_t { | enum MediaPlayerState : uint32_t { | ||||||
|   MEDIA_PLAYER_STATE_NONE = 0, |   MEDIA_PLAYER_STATE_NONE = 0, | ||||||
|   MEDIA_PLAYER_STATE_IDLE = 1, |   MEDIA_PLAYER_STATE_IDLE = 1, | ||||||
| @@ -162,6 +179,8 @@ enum MediaPlayerFormatPurpose : uint32_t { | |||||||
|   MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0, |   MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0, | ||||||
|   MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT = 1, |   MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT = 1, | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BLUETOOTH_PROXY | ||||||
| enum BluetoothDeviceRequestType : uint32_t { | enum BluetoothDeviceRequestType : uint32_t { | ||||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0, |   BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0, | ||||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1, |   BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1, | ||||||
| @@ -183,6 +202,7 @@ enum BluetoothScannerMode : uint32_t { | |||||||
|   BLUETOOTH_SCANNER_MODE_PASSIVE = 0, |   BLUETOOTH_SCANNER_MODE_PASSIVE = 0, | ||||||
|   BLUETOOTH_SCANNER_MODE_ACTIVE = 1, |   BLUETOOTH_SCANNER_MODE_ACTIVE = 1, | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
| enum VoiceAssistantSubscribeFlag : uint32_t { | enum VoiceAssistantSubscribeFlag : uint32_t { | ||||||
|   VOICE_ASSISTANT_SUBSCRIBE_NONE = 0, |   VOICE_ASSISTANT_SUBSCRIBE_NONE = 0, | ||||||
|   VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO = 1, |   VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO = 1, | ||||||
| @@ -192,6 +212,7 @@ enum VoiceAssistantRequestFlag : uint32_t { | |||||||
|   VOICE_ASSISTANT_REQUEST_USE_VAD = 1, |   VOICE_ASSISTANT_REQUEST_USE_VAD = 1, | ||||||
|   VOICE_ASSISTANT_REQUEST_USE_WAKE_WORD = 2, |   VOICE_ASSISTANT_REQUEST_USE_WAKE_WORD = 2, | ||||||
| }; | }; | ||||||
|  | #ifdef USE_VOICE_ASSISTANT | ||||||
| enum VoiceAssistantEvent : uint32_t { | enum VoiceAssistantEvent : uint32_t { | ||||||
|   VOICE_ASSISTANT_ERROR = 0, |   VOICE_ASSISTANT_ERROR = 0, | ||||||
|   VOICE_ASSISTANT_RUN_START = 1, |   VOICE_ASSISTANT_RUN_START = 1, | ||||||
| @@ -216,6 +237,8 @@ enum VoiceAssistantTimerEvent : uint32_t { | |||||||
|   VOICE_ASSISTANT_TIMER_CANCELLED = 2, |   VOICE_ASSISTANT_TIMER_CANCELLED = 2, | ||||||
|   VOICE_ASSISTANT_TIMER_FINISHED = 3, |   VOICE_ASSISTANT_TIMER_FINISHED = 3, | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_ALARM_CONTROL_PANEL | ||||||
| enum AlarmControlPanelState : uint32_t { | enum AlarmControlPanelState : uint32_t { | ||||||
|   ALARM_STATE_DISARMED = 0, |   ALARM_STATE_DISARMED = 0, | ||||||
|   ALARM_STATE_ARMED_HOME = 1, |   ALARM_STATE_ARMED_HOME = 1, | ||||||
| @@ -237,20 +260,27 @@ enum AlarmControlPanelStateCommand : uint32_t { | |||||||
|   ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS = 5, |   ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS = 5, | ||||||
|   ALARM_CONTROL_PANEL_TRIGGER = 6, |   ALARM_CONTROL_PANEL_TRIGGER = 6, | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_TEXT | ||||||
| enum TextMode : uint32_t { | enum TextMode : uint32_t { | ||||||
|   TEXT_MODE_TEXT = 0, |   TEXT_MODE_TEXT = 0, | ||||||
|   TEXT_MODE_PASSWORD = 1, |   TEXT_MODE_PASSWORD = 1, | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_VALVE | ||||||
| enum ValveOperation : uint32_t { | enum ValveOperation : uint32_t { | ||||||
|   VALVE_OPERATION_IDLE = 0, |   VALVE_OPERATION_IDLE = 0, | ||||||
|   VALVE_OPERATION_IS_OPENING = 1, |   VALVE_OPERATION_IS_OPENING = 1, | ||||||
|   VALVE_OPERATION_IS_CLOSING = 2, |   VALVE_OPERATION_IS_CLOSING = 2, | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_UPDATE | ||||||
| enum UpdateCommand : uint32_t { | enum UpdateCommand : uint32_t { | ||||||
|   UPDATE_COMMAND_NONE = 0, |   UPDATE_COMMAND_NONE = 0, | ||||||
|   UPDATE_COMMAND_UPDATE = 1, |   UPDATE_COMMAND_UPDATE = 1, | ||||||
|   UPDATE_COMMAND_CHECK = 2, |   UPDATE_COMMAND_CHECK = 2, | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  |  | ||||||
| }  // namespace enums | }  // namespace enums | ||||||
|  |  | ||||||
| @@ -273,6 +303,7 @@ class StateResponseProtoMessage : public ProtoMessage { | |||||||
|  public: |  public: | ||||||
|   ~StateResponseProtoMessage() override = default; |   ~StateResponseProtoMessage() override = default; | ||||||
|   uint32_t key{0}; |   uint32_t key{0}; | ||||||
|  |   uint32_t device_id{0}; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
| }; | }; | ||||||
| @@ -523,6 +554,7 @@ class SubscribeStatesRequest : public ProtoMessage { | |||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
| }; | }; | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
| class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage { | class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 12; |   static constexpr uint16_t MESSAGE_TYPE = 12; | ||||||
| @@ -546,7 +578,7 @@ class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage { | |||||||
| class BinarySensorStateResponse : public StateResponseProtoMessage { | class BinarySensorStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 21; |   static constexpr uint16_t MESSAGE_TYPE = 21; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 9; |   static constexpr uint16_t ESTIMATED_SIZE = 13; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "binary_sensor_state_response"; } |   const char *message_name() const override { return "binary_sensor_state_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -562,6 +594,8 @@ class BinarySensorStateResponse : public StateResponseProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_COVER | ||||||
| class ListEntitiesCoverResponse : public InfoResponseProtoMessage { | class ListEntitiesCoverResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 13; |   static constexpr uint16_t MESSAGE_TYPE = 13; | ||||||
| @@ -588,7 +622,7 @@ class ListEntitiesCoverResponse : public InfoResponseProtoMessage { | |||||||
| class CoverStateResponse : public StateResponseProtoMessage { | class CoverStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 22; |   static constexpr uint16_t MESSAGE_TYPE = 22; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 19; |   static constexpr uint16_t ESTIMATED_SIZE = 23; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "cover_state_response"; } |   const char *message_name() const override { return "cover_state_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -631,6 +665,8 @@ class CoverCommandRequest : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_FAN | ||||||
| class ListEntitiesFanResponse : public InfoResponseProtoMessage { | class ListEntitiesFanResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 14; |   static constexpr uint16_t MESSAGE_TYPE = 14; | ||||||
| @@ -657,7 +693,7 @@ class ListEntitiesFanResponse : public InfoResponseProtoMessage { | |||||||
| class FanStateResponse : public StateResponseProtoMessage { | class FanStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 23; |   static constexpr uint16_t MESSAGE_TYPE = 23; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 26; |   static constexpr uint16_t ESTIMATED_SIZE = 30; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "fan_state_response"; } |   const char *message_name() const override { return "fan_state_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -709,6 +745,8 @@ class FanCommandRequest : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_LIGHT | ||||||
| class ListEntitiesLightResponse : public InfoResponseProtoMessage { | class ListEntitiesLightResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 15; |   static constexpr uint16_t MESSAGE_TYPE = 15; | ||||||
| @@ -738,7 +776,7 @@ class ListEntitiesLightResponse : public InfoResponseProtoMessage { | |||||||
| class LightStateResponse : public StateResponseProtoMessage { | class LightStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 24; |   static constexpr uint16_t MESSAGE_TYPE = 24; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 63; |   static constexpr uint16_t ESTIMATED_SIZE = 67; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "light_state_response"; } |   const char *message_name() const override { return "light_state_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -810,6 +848,8 @@ class LightCommandRequest : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_SENSOR | ||||||
| class ListEntitiesSensorResponse : public InfoResponseProtoMessage { | class ListEntitiesSensorResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 16; |   static constexpr uint16_t MESSAGE_TYPE = 16; | ||||||
| @@ -837,7 +877,7 @@ class ListEntitiesSensorResponse : public InfoResponseProtoMessage { | |||||||
| class SensorStateResponse : public StateResponseProtoMessage { | class SensorStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 25; |   static constexpr uint16_t MESSAGE_TYPE = 25; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 12; |   static constexpr uint16_t ESTIMATED_SIZE = 16; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "sensor_state_response"; } |   const char *message_name() const override { return "sensor_state_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -853,6 +893,8 @@ class SensorStateResponse : public StateResponseProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_SWITCH | ||||||
| class ListEntitiesSwitchResponse : public InfoResponseProtoMessage { | class ListEntitiesSwitchResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 17; |   static constexpr uint16_t MESSAGE_TYPE = 17; | ||||||
| @@ -876,7 +918,7 @@ class ListEntitiesSwitchResponse : public InfoResponseProtoMessage { | |||||||
| class SwitchStateResponse : public StateResponseProtoMessage { | class SwitchStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 26; |   static constexpr uint16_t MESSAGE_TYPE = 26; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 7; |   static constexpr uint16_t ESTIMATED_SIZE = 11; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "switch_state_response"; } |   const char *message_name() const override { return "switch_state_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -910,6 +952,8 @@ class SwitchCommandRequest : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_TEXT_SENSOR | ||||||
| class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage { | class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 18; |   static constexpr uint16_t MESSAGE_TYPE = 18; | ||||||
| @@ -932,7 +976,7 @@ class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage { | |||||||
| class TextSensorStateResponse : public StateResponseProtoMessage { | class TextSensorStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 27; |   static constexpr uint16_t MESSAGE_TYPE = 27; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 16; |   static constexpr uint16_t ESTIMATED_SIZE = 20; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "text_sensor_state_response"; } |   const char *message_name() const override { return "text_sensor_state_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -949,6 +993,7 @@ class TextSensorStateResponse : public StateResponseProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
| class SubscribeLogsRequest : public ProtoMessage { | class SubscribeLogsRequest : public ProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 28; |   static constexpr uint16_t MESSAGE_TYPE = 28; | ||||||
| @@ -987,6 +1032,7 @@ class SubscribeLogsResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | #ifdef USE_API_NOISE | ||||||
| class NoiseEncryptionSetKeyRequest : public ProtoMessage { | class NoiseEncryptionSetKeyRequest : public ProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 124; |   static constexpr uint16_t MESSAGE_TYPE = 124; | ||||||
| @@ -1021,6 +1067,7 @@ class NoiseEncryptionSetKeyResponse : public ProtoMessage { | |||||||
|  protected: |  protected: | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
| class SubscribeHomeassistantServicesRequest : public ProtoMessage { | class SubscribeHomeassistantServicesRequest : public ProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 34; |   static constexpr uint16_t MESSAGE_TYPE = 34; | ||||||
| @@ -1226,6 +1273,7 @@ class ExecuteServiceRequest : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
| }; | }; | ||||||
|  | #ifdef USE_ESP32_CAMERA | ||||||
| class ListEntitiesCameraResponse : public InfoResponseProtoMessage { | class ListEntitiesCameraResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 43; |   static constexpr uint16_t MESSAGE_TYPE = 43; | ||||||
| @@ -1283,6 +1331,8 @@ class CameraImageRequest : public ProtoMessage { | |||||||
|  protected: |  protected: | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_CLIMATE | ||||||
| class ListEntitiesClimateResponse : public InfoResponseProtoMessage { | class ListEntitiesClimateResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 46; |   static constexpr uint16_t MESSAGE_TYPE = 46; | ||||||
| @@ -1322,7 +1372,7 @@ class ListEntitiesClimateResponse : public InfoResponseProtoMessage { | |||||||
| class ClimateStateResponse : public StateResponseProtoMessage { | class ClimateStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 47; |   static constexpr uint16_t MESSAGE_TYPE = 47; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 65; |   static constexpr uint16_t ESTIMATED_SIZE = 70; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "climate_state_response"; } |   const char *message_name() const override { return "climate_state_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -1392,6 +1442,8 @@ class ClimateCommandRequest : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_NUMBER | ||||||
| class ListEntitiesNumberResponse : public InfoResponseProtoMessage { | class ListEntitiesNumberResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 49; |   static constexpr uint16_t MESSAGE_TYPE = 49; | ||||||
| @@ -1419,7 +1471,7 @@ class ListEntitiesNumberResponse : public InfoResponseProtoMessage { | |||||||
| class NumberStateResponse : public StateResponseProtoMessage { | class NumberStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 50; |   static constexpr uint16_t MESSAGE_TYPE = 50; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 12; |   static constexpr uint16_t ESTIMATED_SIZE = 16; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "number_state_response"; } |   const char *message_name() const override { return "number_state_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -1453,6 +1505,8 @@ class NumberCommandRequest : public ProtoMessage { | |||||||
|  protected: |  protected: | ||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_SELECT | ||||||
| class ListEntitiesSelectResponse : public InfoResponseProtoMessage { | class ListEntitiesSelectResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 52; |   static constexpr uint16_t MESSAGE_TYPE = 52; | ||||||
| @@ -1475,7 +1529,7 @@ class ListEntitiesSelectResponse : public InfoResponseProtoMessage { | |||||||
| class SelectStateResponse : public StateResponseProtoMessage { | class SelectStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 53; |   static constexpr uint16_t MESSAGE_TYPE = 53; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 16; |   static constexpr uint16_t ESTIMATED_SIZE = 20; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "select_state_response"; } |   const char *message_name() const override { return "select_state_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -1511,6 +1565,8 @@ class SelectCommandRequest : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_SIREN | ||||||
| class ListEntitiesSirenResponse : public InfoResponseProtoMessage { | class ListEntitiesSirenResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 55; |   static constexpr uint16_t MESSAGE_TYPE = 55; | ||||||
| @@ -1535,7 +1591,7 @@ class ListEntitiesSirenResponse : public InfoResponseProtoMessage { | |||||||
| class SirenStateResponse : public StateResponseProtoMessage { | class SirenStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 56; |   static constexpr uint16_t MESSAGE_TYPE = 56; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 7; |   static constexpr uint16_t ESTIMATED_SIZE = 11; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "siren_state_response"; } |   const char *message_name() const override { return "siren_state_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -1577,6 +1633,8 @@ class SirenCommandRequest : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_LOCK | ||||||
| class ListEntitiesLockResponse : public InfoResponseProtoMessage { | class ListEntitiesLockResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 58; |   static constexpr uint16_t MESSAGE_TYPE = 58; | ||||||
| @@ -1602,7 +1660,7 @@ class ListEntitiesLockResponse : public InfoResponseProtoMessage { | |||||||
| class LockStateResponse : public StateResponseProtoMessage { | class LockStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 59; |   static constexpr uint16_t MESSAGE_TYPE = 59; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 7; |   static constexpr uint16_t ESTIMATED_SIZE = 11; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "lock_state_response"; } |   const char *message_name() const override { return "lock_state_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -1639,6 +1697,8 @@ class LockCommandRequest : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
| class ListEntitiesButtonResponse : public InfoResponseProtoMessage { | class ListEntitiesButtonResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 61; |   static constexpr uint16_t MESSAGE_TYPE = 61; | ||||||
| @@ -1675,6 +1735,8 @@ class ButtonCommandRequest : public ProtoMessage { | |||||||
|  protected: |  protected: | ||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_MEDIA_PLAYER | ||||||
| class MediaPlayerSupportedFormat : public ProtoMessage { | class MediaPlayerSupportedFormat : public ProtoMessage { | ||||||
|  public: |  public: | ||||||
|   std::string format{}; |   std::string format{}; | ||||||
| @@ -1715,7 +1777,7 @@ class ListEntitiesMediaPlayerResponse : public InfoResponseProtoMessage { | |||||||
| class MediaPlayerStateResponse : public StateResponseProtoMessage { | class MediaPlayerStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 64; |   static constexpr uint16_t MESSAGE_TYPE = 64; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 14; |   static constexpr uint16_t ESTIMATED_SIZE = 18; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "media_player_state_response"; } |   const char *message_name() const override { return "media_player_state_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -1759,6 +1821,8 @@ class MediaPlayerCommandRequest : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BLUETOOTH_PROXY | ||||||
| class SubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage { | class SubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 66; |   static constexpr uint16_t MESSAGE_TYPE = 66; | ||||||
| @@ -2313,6 +2377,8 @@ class BluetoothScannerSetModeRequest : public ProtoMessage { | |||||||
|  protected: |  protected: | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_VOICE_ASSISTANT | ||||||
| class SubscribeVoiceAssistantRequest : public ProtoMessage { | class SubscribeVoiceAssistantRequest : public ProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 89; |   static constexpr uint16_t MESSAGE_TYPE = 89; | ||||||
| @@ -2562,6 +2628,8 @@ class VoiceAssistantSetConfiguration : public ProtoMessage { | |||||||
|  protected: |  protected: | ||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_ALARM_CONTROL_PANEL | ||||||
| class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage { | class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 94; |   static constexpr uint16_t MESSAGE_TYPE = 94; | ||||||
| @@ -2586,7 +2654,7 @@ class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage { | |||||||
| class AlarmControlPanelStateResponse : public StateResponseProtoMessage { | class AlarmControlPanelStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 95; |   static constexpr uint16_t MESSAGE_TYPE = 95; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 7; |   static constexpr uint16_t ESTIMATED_SIZE = 11; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "alarm_control_panel_state_response"; } |   const char *message_name() const override { return "alarm_control_panel_state_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -2622,6 +2690,8 @@ class AlarmControlPanelCommandRequest : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_TEXT | ||||||
| class ListEntitiesTextResponse : public InfoResponseProtoMessage { | class ListEntitiesTextResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 97; |   static constexpr uint16_t MESSAGE_TYPE = 97; | ||||||
| @@ -2647,7 +2717,7 @@ class ListEntitiesTextResponse : public InfoResponseProtoMessage { | |||||||
| class TextStateResponse : public StateResponseProtoMessage { | class TextStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 98; |   static constexpr uint16_t MESSAGE_TYPE = 98; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 16; |   static constexpr uint16_t ESTIMATED_SIZE = 20; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "text_state_response"; } |   const char *message_name() const override { return "text_state_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -2683,6 +2753,8 @@ class TextCommandRequest : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_DATETIME_DATE | ||||||
| class ListEntitiesDateResponse : public InfoResponseProtoMessage { | class ListEntitiesDateResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 100; |   static constexpr uint16_t MESSAGE_TYPE = 100; | ||||||
| @@ -2704,7 +2776,7 @@ class ListEntitiesDateResponse : public InfoResponseProtoMessage { | |||||||
| class DateStateResponse : public StateResponseProtoMessage { | class DateStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 101; |   static constexpr uint16_t MESSAGE_TYPE = 101; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 19; |   static constexpr uint16_t ESTIMATED_SIZE = 23; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "date_state_response"; } |   const char *message_name() const override { return "date_state_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -2743,6 +2815,8 @@ class DateCommandRequest : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_DATETIME_TIME | ||||||
| class ListEntitiesTimeResponse : public InfoResponseProtoMessage { | class ListEntitiesTimeResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 103; |   static constexpr uint16_t MESSAGE_TYPE = 103; | ||||||
| @@ -2764,7 +2838,7 @@ class ListEntitiesTimeResponse : public InfoResponseProtoMessage { | |||||||
| class TimeStateResponse : public StateResponseProtoMessage { | class TimeStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 104; |   static constexpr uint16_t MESSAGE_TYPE = 104; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 19; |   static constexpr uint16_t ESTIMATED_SIZE = 23; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "time_state_response"; } |   const char *message_name() const override { return "time_state_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -2803,6 +2877,8 @@ class TimeCommandRequest : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_EVENT | ||||||
| class ListEntitiesEventResponse : public InfoResponseProtoMessage { | class ListEntitiesEventResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 107; |   static constexpr uint16_t MESSAGE_TYPE = 107; | ||||||
| @@ -2826,7 +2902,7 @@ class ListEntitiesEventResponse : public InfoResponseProtoMessage { | |||||||
| class EventResponse : public StateResponseProtoMessage { | class EventResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 108; |   static constexpr uint16_t MESSAGE_TYPE = 108; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 14; |   static constexpr uint16_t ESTIMATED_SIZE = 18; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "event_response"; } |   const char *message_name() const override { return "event_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -2840,7 +2916,10 @@ class EventResponse : public StateResponseProtoMessage { | |||||||
|  protected: |  protected: | ||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|  |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_VALVE | ||||||
| class ListEntitiesValveResponse : public InfoResponseProtoMessage { | class ListEntitiesValveResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 109; |   static constexpr uint16_t MESSAGE_TYPE = 109; | ||||||
| @@ -2866,7 +2945,7 @@ class ListEntitiesValveResponse : public InfoResponseProtoMessage { | |||||||
| class ValveStateResponse : public StateResponseProtoMessage { | class ValveStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 110; |   static constexpr uint16_t MESSAGE_TYPE = 110; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 12; |   static constexpr uint16_t ESTIMATED_SIZE = 16; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "valve_state_response"; } |   const char *message_name() const override { return "valve_state_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -2903,6 +2982,8 @@ class ValveCommandRequest : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_DATETIME_DATETIME | ||||||
| class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage { | class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 112; |   static constexpr uint16_t MESSAGE_TYPE = 112; | ||||||
| @@ -2924,7 +3005,7 @@ class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage { | |||||||
| class DateTimeStateResponse : public StateResponseProtoMessage { | class DateTimeStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 113; |   static constexpr uint16_t MESSAGE_TYPE = 113; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 12; |   static constexpr uint16_t ESTIMATED_SIZE = 16; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "date_time_state_response"; } |   const char *message_name() const override { return "date_time_state_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -2958,6 +3039,8 @@ class DateTimeCommandRequest : public ProtoMessage { | |||||||
|  protected: |  protected: | ||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_UPDATE | ||||||
| class ListEntitiesUpdateResponse : public InfoResponseProtoMessage { | class ListEntitiesUpdateResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 116; |   static constexpr uint16_t MESSAGE_TYPE = 116; | ||||||
| @@ -2980,7 +3063,7 @@ class ListEntitiesUpdateResponse : public InfoResponseProtoMessage { | |||||||
| class UpdateStateResponse : public StateResponseProtoMessage { | class UpdateStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 117; |   static constexpr uint16_t MESSAGE_TYPE = 117; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 61; |   static constexpr uint16_t ESTIMATED_SIZE = 65; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "update_state_response"; } |   const char *message_name() const override { return "update_state_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -3023,6 +3106,7 @@ class UpdateCommandRequest : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | #endif | ||||||
|  |  | ||||||
| }  // namespace api | }  // namespace api | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
							
								
								
									
										4333
									
								
								esphome/components/api/api_pb2_dump.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4333
									
								
								esphome/components/api/api_pb2_dump.cpp
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -2,9 +2,10 @@ | |||||||
| // See script/api_protobuf/api_protobuf.py | // See script/api_protobuf/api_protobuf.py | ||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "api_pb2.h" |  | ||||||
| #include "esphome/core/defines.h" | #include "esphome/core/defines.h" | ||||||
|  |  | ||||||
|  | #include "api_pb2.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace api { | namespace api { | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								esphome/components/ds2484/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/ds2484/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | CODEOWNERS = ["@mrk-its"] | ||||||
							
								
								
									
										209
									
								
								esphome/components/ds2484/ds2484.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								esphome/components/ds2484/ds2484.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,209 @@ | |||||||
|  | #include "ds2484.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ds2484 { | ||||||
|  | static const char *const TAG = "ds2484.onewire"; | ||||||
|  |  | ||||||
|  | void DS2484OneWireBus::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Running setup"); | ||||||
|  |   this->reset_device(); | ||||||
|  |   this->search(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DS2484OneWireBus::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "1-wire bus:"); | ||||||
|  |   this->dump_devices_(TAG); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool DS2484OneWireBus::read_status_(uint8_t *status) { | ||||||
|  |   for (uint8_t retry_nr = 0; retry_nr < 10; retry_nr++) { | ||||||
|  |     if (this->read(status, 1) != i2c::ERROR_OK) { | ||||||
|  |       ESP_LOGE(TAG, "read status error"); | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |     ESP_LOGVV(TAG, "status: %02x", *status); | ||||||
|  |     if (!(*status & 1)) { | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   ESP_LOGE(TAG, "read status error: too many retries"); | ||||||
|  |   return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool DS2484OneWireBus::wait_for_completion_() { | ||||||
|  |   uint8_t status; | ||||||
|  |   return this->read_status_(&status); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool DS2484OneWireBus::reset_device() { | ||||||
|  |   ESP_LOGVV(TAG, "reset_device"); | ||||||
|  |   uint8_t device_reset_cmd = 0xf0; | ||||||
|  |   uint8_t response; | ||||||
|  |   if (this->write(&device_reset_cmd, 1) != i2c::ERROR_OK) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   if (!this->wait_for_completion_()) { | ||||||
|  |     ESP_LOGE(TAG, "reset_device: can't complete"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   uint8_t config = (this->active_pullup_ ? 1 : 0) | (this->strong_pullup_ ? 4 : 0); | ||||||
|  |   uint8_t write_config[2] = {0xd2, (uint8_t) (config | (~config << 4))}; | ||||||
|  |   if (this->write(write_config, 2) != i2c::ERROR_OK) { | ||||||
|  |     ESP_LOGE(TAG, "reset_device: can't write config"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   if (this->read(&response, 1) != i2c::ERROR_OK) { | ||||||
|  |     ESP_LOGE(TAG, "can't read read8 response"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   if (response != (write_config[1] & 0xf)) { | ||||||
|  |     ESP_LOGE(TAG, "configuration didn't update"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   return true; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | int DS2484OneWireBus::reset_int() { | ||||||
|  |   ESP_LOGVV(TAG, "reset"); | ||||||
|  |   uint8_t reset_cmd = 0xb4; | ||||||
|  |   if (this->write(&reset_cmd, 1) != i2c::ERROR_OK) { | ||||||
|  |     return -1; | ||||||
|  |   } | ||||||
|  |   return this->wait_for_completion_() ? 1 : 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | void DS2484OneWireBus::write8_(uint8_t value) { | ||||||
|  |   uint8_t buffer[2] = {0xa5, value}; | ||||||
|  |   this->write(buffer, 2); | ||||||
|  |   this->wait_for_completion_(); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | void DS2484OneWireBus::write8(uint8_t value) { | ||||||
|  |   ESP_LOGVV(TAG, "write8: %02x", value); | ||||||
|  |   this->write8_(value); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | void DS2484OneWireBus::write64(uint64_t value) { | ||||||
|  |   ESP_LOGVV(TAG, "write64: %llx", value); | ||||||
|  |   for (uint8_t i = 0; i < 8; i++) { | ||||||
|  |     this->write8_((value >> (i * 8)) & 0xff); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint8_t DS2484OneWireBus::read8() { | ||||||
|  |   uint8_t read8_cmd = 0x96; | ||||||
|  |   uint8_t set_read_reg_cmd[2] = {0xe1, 0xe1}; | ||||||
|  |   uint8_t response = 0; | ||||||
|  |   if (this->write(&read8_cmd, 1) != i2c::ERROR_OK) { | ||||||
|  |     ESP_LOGE(TAG, "can't write read8 cmd"); | ||||||
|  |     return 0; | ||||||
|  |   } | ||||||
|  |   this->wait_for_completion_(); | ||||||
|  |   if (this->write(set_read_reg_cmd, 2) != i2c::ERROR_OK) { | ||||||
|  |     ESP_LOGE(TAG, "can't set read data reg"); | ||||||
|  |     return 0; | ||||||
|  |   } | ||||||
|  |   if (this->read(&response, 1) != i2c::ERROR_OK) { | ||||||
|  |     ESP_LOGE(TAG, "can't read read8 response"); | ||||||
|  |     return 0; | ||||||
|  |   } | ||||||
|  |   return response; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint64_t DS2484OneWireBus::read64() { | ||||||
|  |   uint8_t response = 0; | ||||||
|  |   for (uint8_t i = 0; i < 8; i++) { | ||||||
|  |     response |= (this->read8() << (i * 8)); | ||||||
|  |   } | ||||||
|  |   return response; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DS2484OneWireBus::reset_search() { | ||||||
|  |   this->last_discrepancy_ = 0; | ||||||
|  |   this->last_device_flag_ = false; | ||||||
|  |   this->address_ = 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool DS2484OneWireBus::one_wire_triple_(bool *branch, bool *id_bit, bool *cmp_id_bit) { | ||||||
|  |   uint8_t buffer[2] = {(uint8_t) 0x78, (uint8_t) (*branch ? 0x80u : 0)}; | ||||||
|  |   uint8_t status; | ||||||
|  |   if (!this->read_status_(&status)) { | ||||||
|  |     ESP_LOGE(TAG, "one_wire_triple start: read status error"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   if (this->write(buffer, 2) != i2c::ERROR_OK) { | ||||||
|  |     ESP_LOGV(TAG, "one_wire_triple: can't write cmd"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   if (!this->read_status_(&status)) { | ||||||
|  |     ESP_LOGE(TAG, "one_wire_triple: read status error"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   *id_bit = bool(status & 0x20); | ||||||
|  |   *cmp_id_bit = bool(status & 0x40); | ||||||
|  |   *branch = bool(status & 0x80); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint64_t IRAM_ATTR DS2484OneWireBus::search_int() { | ||||||
|  |   ESP_LOGVV(TAG, "search_int"); | ||||||
|  |   if (this->last_device_flag_) { | ||||||
|  |     ESP_LOGVV(TAG, "last device flag set, quitting"); | ||||||
|  |     return 0u; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint8_t last_zero = 0; | ||||||
|  |   uint64_t bit_mask = 1; | ||||||
|  |   uint64_t address = this->address_; | ||||||
|  |  | ||||||
|  |   // Initiate search | ||||||
|  |   for (uint8_t bit_number = 1; bit_number <= 64; bit_number++, bit_mask <<= 1) { | ||||||
|  |     bool branch; | ||||||
|  |  | ||||||
|  |     // compute branch value for the case when there is a discrepancy | ||||||
|  |     // (there are devices with both 0s and 1s at this bit) | ||||||
|  |     if (bit_number < this->last_discrepancy_) { | ||||||
|  |       branch = (address & bit_mask) > 0; | ||||||
|  |     } else { | ||||||
|  |       branch = bit_number == this->last_discrepancy_; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     bool id_bit, cmp_id_bit; | ||||||
|  |     bool branch_before = branch; | ||||||
|  |     if (!this->one_wire_triple_(&branch, &id_bit, &cmp_id_bit)) { | ||||||
|  |       ESP_LOGW(TAG, "one wire triple error, quitting"); | ||||||
|  |       return 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (id_bit && cmp_id_bit) { | ||||||
|  |       ESP_LOGW(TAG, "no devices on the bus, quitting"); | ||||||
|  |       // No devices participating in search | ||||||
|  |       return 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (!id_bit && !cmp_id_bit && !branch) { | ||||||
|  |       last_zero = bit_number; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ESP_LOGVV(TAG, "%d %d branch: %d %d", id_bit, cmp_id_bit, branch_before, branch); | ||||||
|  |  | ||||||
|  |     if (branch) { | ||||||
|  |       address |= bit_mask; | ||||||
|  |     } else { | ||||||
|  |       address &= ~bit_mask; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   ESP_LOGVV(TAG, "last_discepancy: %d", last_zero); | ||||||
|  |   ESP_LOGVV(TAG, "address: %llx", address); | ||||||
|  |   this->last_discrepancy_ = last_zero; | ||||||
|  |   if (this->last_discrepancy_ == 0) { | ||||||
|  |     // we're at root and have no choices left, so this was the last one. | ||||||
|  |     this->last_device_flag_ = true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->address_ = address; | ||||||
|  |   return address; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace ds2484 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										43
									
								
								esphome/components/ds2484/ds2484.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								esphome/components/ds2484/ds2484.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/core/preferences.h" | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  | #include "esphome/components/one_wire/one_wire.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ds2484 { | ||||||
|  |  | ||||||
|  | class DS2484OneWireBus : public one_wire::OneWireBus, public i2c::I2CDevice, public Component { | ||||||
|  |  public: | ||||||
|  |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |   float get_setup_priority() const override { return setup_priority::BUS - 1.0; } | ||||||
|  |  | ||||||
|  |   bool reset_device(); | ||||||
|  |   int reset_int() override; | ||||||
|  |   void write8(uint8_t) override; | ||||||
|  |   void write64(uint64_t) override; | ||||||
|  |   uint8_t read8() override; | ||||||
|  |   uint64_t read64() override; | ||||||
|  |  | ||||||
|  |   void set_active_pullup(bool value) { this->active_pullup_ = value; } | ||||||
|  |   void set_strong_pullup(bool value) { this->strong_pullup_ = value; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void reset_search() override; | ||||||
|  |   uint64_t search_int() override; | ||||||
|  |   bool read_status_(uint8_t *); | ||||||
|  |   bool wait_for_completion_(); | ||||||
|  |   void write8_(uint8_t); | ||||||
|  |   bool one_wire_triple_(bool *branch, bool *id_bit, bool *cmp_id_bit); | ||||||
|  |  | ||||||
|  |   uint64_t address_; | ||||||
|  |   uint8_t last_discrepancy_{0}; | ||||||
|  |   bool last_device_flag_{false}; | ||||||
|  |   bool active_pullup_{false}; | ||||||
|  |   bool strong_pullup_{false}; | ||||||
|  | }; | ||||||
|  | }  // namespace ds2484 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										37
									
								
								esphome/components/ds2484/one_wire.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								esphome/components/ds2484/one_wire.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import i2c | ||||||
|  | from esphome.components.one_wire import OneWireBus | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import CONF_ID | ||||||
|  |  | ||||||
|  | ds2484_ns = cg.esphome_ns.namespace("ds2484") | ||||||
|  |  | ||||||
|  | CONF_ACTIVE_PULLUP = "active_pullup" | ||||||
|  | CONF_STRONG_PULLUP = "strong_pullup" | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@mrk-its"] | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
|  | DS2484OneWireBus = ds2484_ns.class_( | ||||||
|  |     "DS2484OneWireBus", OneWireBus, i2c.I2CDevice, cg.Component | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(DS2484OneWireBus), | ||||||
|  |             cv.Optional(CONF_ACTIVE_PULLUP, default=False): cv.boolean, | ||||||
|  |             cv.Optional(CONF_STRONG_PULLUP, default=False): cv.boolean, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x18)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await i2c.register_i2c_device(var, config) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     cg.add(var.set_active_pullup(config[CONF_ACTIVE_PULLUP])) | ||||||
|  |     cg.add(var.set_strong_pullup(config[CONF_STRONG_PULLUP])) | ||||||
| @@ -63,6 +63,7 @@ BASE_SCHEMA = cv.All( | |||||||
|     cv.Schema( |     cv.Schema( | ||||||
|         { |         { | ||||||
|             cv.Required(CONF_URL): cv.url, |             cv.Required(CONF_URL): cv.url, | ||||||
|  |             cv.Optional(CONF_PATH): cv.string, | ||||||
|             cv.Optional(CONF_USERNAME): cv.string, |             cv.Optional(CONF_USERNAME): cv.string, | ||||||
|             cv.Optional(CONF_PASSWORD): cv.string, |             cv.Optional(CONF_PASSWORD): cv.string, | ||||||
|             cv.Exclusive(CONF_FILE, CONF_FILES): validate_yaml_filename, |             cv.Exclusive(CONF_FILE, CONF_FILES): validate_yaml_filename, | ||||||
| @@ -116,6 +117,9 @@ def _process_base_package(config: dict) -> dict: | |||||||
|     ) |     ) | ||||||
|     files = [] |     files = [] | ||||||
|  |  | ||||||
|  |     if base_path := config.get(CONF_PATH): | ||||||
|  |         repo_dir = repo_dir / base_path | ||||||
|  |  | ||||||
|     for file in config[CONF_FILES]: |     for file in config[CONF_FILES]: | ||||||
|         if isinstance(file, str): |         if isinstance(file, str): | ||||||
|             files.append({CONF_PATH: file, CONF_VARS: {}}) |             files.append({CONF_PATH: file, CONF_VARS: {}}) | ||||||
|   | |||||||
| @@ -1,19 +1,76 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| from esphome.components import binary_sensor | from esphome.components import binary_sensor | ||||||
| from esphome.const import CONF_ID | import esphome.config_validation as cv | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_DATA, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_NAME, | ||||||
|  |     CONF_STATUS, | ||||||
|  |     CONF_TYPE, | ||||||
|  |     DEVICE_CLASS_CONNECTIVITY, | ||||||
|  |     ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  | ) | ||||||
|  | import esphome.final_validate as fv | ||||||
|  |  | ||||||
| from . import ( | from . import ( | ||||||
|  |     CONF_ENCRYPTION, | ||||||
|  |     CONF_PING_PONG_ENABLE, | ||||||
|     CONF_PROVIDER, |     CONF_PROVIDER, | ||||||
|  |     CONF_PROVIDERS, | ||||||
|     CONF_REMOTE_ID, |     CONF_REMOTE_ID, | ||||||
|     CONF_TRANSPORT_ID, |     CONF_TRANSPORT_ID, | ||||||
|  |     PacketTransport, | ||||||
|     packet_transport_sensor_schema, |     packet_transport_sensor_schema, | ||||||
|  |     provider_name_validate, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = packet_transport_sensor_schema(binary_sensor.binary_sensor_schema()) | STATUS_SENSOR_SCHEMA = binary_sensor.binary_sensor_schema( | ||||||
|  |     device_class=DEVICE_CLASS_CONNECTIVITY, | ||||||
|  |     entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  | ).extend( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_TRANSPORT_ID): cv.use_id(PacketTransport), | ||||||
|  |         cv.Required(CONF_PROVIDER): provider_name_validate, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.typed_schema( | ||||||
|  |     { | ||||||
|  |         CONF_DATA: packet_transport_sensor_schema(binary_sensor.binary_sensor_schema()), | ||||||
|  |         CONF_STATUS: STATUS_SENSOR_SCHEMA, | ||||||
|  |     }, | ||||||
|  |     key=CONF_TYPE, | ||||||
|  |     default_type=CONF_DATA, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _final_validate(config): | ||||||
|  |     if config[CONF_TYPE] != CONF_STATUS: | ||||||
|  |         # Only run this validation if a status sensor is being configured | ||||||
|  |         return config | ||||||
|  |     full_config = fv.full_config.get() | ||||||
|  |     transport_path = full_config.get_path_for_id(config[CONF_TRANSPORT_ID])[:-1] | ||||||
|  |     transport_config = full_config.get_config_for_path(transport_path) | ||||||
|  |     if transport_config[CONF_PING_PONG_ENABLE] and any( | ||||||
|  |         CONF_ENCRYPTION in p | ||||||
|  |         for p in transport_config[CONF_PROVIDERS] | ||||||
|  |         if p[CONF_NAME] == config[CONF_PROVIDER] | ||||||
|  |     ): | ||||||
|  |         return config | ||||||
|  |     raise cv.Invalid( | ||||||
|  |         "Status sensor requires ping-pong to be enabled and the nominated provider to use encryption." | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | FINAL_VALIDATE_SCHEMA = _final_validate | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     var = await binary_sensor.new_binary_sensor(config) |     var = await binary_sensor.new_binary_sensor(config) | ||||||
|     comp = await cg.get_variable(config[CONF_TRANSPORT_ID]) |     comp = await cg.get_variable(config[CONF_TRANSPORT_ID]) | ||||||
|  |     if config[CONF_TYPE] == CONF_STATUS: | ||||||
|  |         cg.add(comp.set_provider_status_sensor(config[CONF_PROVIDER], var)) | ||||||
|  |         cg.add_define("USE_STATUS_SENSOR") | ||||||
|  |     else:  # CONF_DATA is default | ||||||
|         remote_id = str(config.get(CONF_REMOTE_ID) or config.get(CONF_ID)) |         remote_id = str(config.get(CONF_REMOTE_ID) or config.get(CONF_ID)) | ||||||
|         cg.add(comp.add_remote_binary_sensor(config[CONF_PROVIDER], remote_id, var)) |         cg.add(comp.add_remote_binary_sensor(config[CONF_PROVIDER], remote_id, var)) | ||||||
|   | |||||||
| @@ -317,8 +317,37 @@ void PacketTransport::update() { | |||||||
|   auto now = millis() / 1000; |   auto now = millis() / 1000; | ||||||
|   if (this->last_key_time_ + this->ping_pong_recyle_time_ < now) { |   if (this->last_key_time_ + this->ping_pong_recyle_time_ < now) { | ||||||
|     this->resend_ping_key_ = this->ping_pong_enable_; |     this->resend_ping_key_ = this->ping_pong_enable_; | ||||||
|  |     ESP_LOGV(TAG, "Ping request, age %u", now - this->last_key_time_); | ||||||
|     this->last_key_time_ = now; |     this->last_key_time_ = now; | ||||||
|   } |   } | ||||||
|  |   for (const auto &provider : this->providers_) { | ||||||
|  |     uint32_t key_response_age = now - provider.second.last_key_response_time; | ||||||
|  |     if (key_response_age > (this->ping_pong_recyle_time_ * 2u)) { | ||||||
|  | #ifdef USE_STATUS_SENSOR | ||||||
|  |       if (provider.second.status_sensor != nullptr && provider.second.status_sensor->state) { | ||||||
|  |         ESP_LOGI(TAG, "Ping status for %s timeout at %u with age %u", provider.first.c_str(), now, key_response_age); | ||||||
|  |         provider.second.status_sensor->publish_state(false); | ||||||
|  |       } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |       for (auto &sensor : this->remote_sensors_[provider.first]) { | ||||||
|  |         sensor.second->publish_state(NAN); | ||||||
|  |       } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|  |       for (auto &sensor : this->remote_binary_sensors_[provider.first]) { | ||||||
|  |         sensor.second->invalidate_state(); | ||||||
|  |       } | ||||||
|  | #endif | ||||||
|  |     } else { | ||||||
|  | #ifdef USE_STATUS_SENSOR | ||||||
|  |       if (provider.second.status_sensor != nullptr && !provider.second.status_sensor->state) { | ||||||
|  |         ESP_LOGI(TAG, "Ping status for %s restored at %u with age %u", provider.first.c_str(), now, key_response_age); | ||||||
|  |         provider.second.status_sensor->publish_state(true); | ||||||
|  |       } | ||||||
|  | #endif | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void PacketTransport::add_key_(const char *name, uint32_t key) { | void PacketTransport::add_key_(const char *name, uint32_t key) { | ||||||
| @@ -437,7 +466,8 @@ void PacketTransport::process_(const std::vector<uint8_t> &data) { | |||||||
|     if (decoder.decode(PING_KEY, key) == DECODE_OK) { |     if (decoder.decode(PING_KEY, key) == DECODE_OK) { | ||||||
|       if (key == this->ping_key_) { |       if (key == this->ping_key_) { | ||||||
|         ping_key_seen = true; |         ping_key_seen = true; | ||||||
|         ESP_LOGV(TAG, "Found good ping key %X", (unsigned) key); |         provider.last_key_response_time = millis() / 1000; | ||||||
|  |         ESP_LOGV(TAG, "Found good ping key %X at timestamp %" PRIu32, (unsigned) key, provider.last_key_response_time); | ||||||
|       } else { |       } else { | ||||||
|         ESP_LOGV(TAG, "Unknown ping key %X", (unsigned) key); |         ESP_LOGV(TAG, "Unknown ping key %X", (unsigned) key); | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
| #ifdef USE_BINARY_SENSOR | #ifdef USE_BINARY_SENSOR | ||||||
| #include "esphome/components/binary_sensor/binary_sensor.h" | #include "esphome/components/binary_sensor/binary_sensor.h" | ||||||
| #endif | #endif | ||||||
| # |  | ||||||
| #include <vector> | #include <vector> | ||||||
| #include <map> | #include <map> | ||||||
|  |  | ||||||
| @@ -27,6 +27,10 @@ struct Provider { | |||||||
|   std::vector<uint8_t> encryption_key; |   std::vector<uint8_t> encryption_key; | ||||||
|   const char *name; |   const char *name; | ||||||
|   uint32_t last_code[2]; |   uint32_t last_code[2]; | ||||||
|  |   uint32_t last_key_response_time; | ||||||
|  | #ifdef USE_STATUS_SENSOR | ||||||
|  |   binary_sensor::BinarySensor *status_sensor{nullptr}; | ||||||
|  | #endif | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
| @@ -75,10 +79,7 @@ class PacketTransport : public PollingComponent { | |||||||
|  |  | ||||||
|   void add_provider(const char *hostname) { |   void add_provider(const char *hostname) { | ||||||
|     if (this->providers_.count(hostname) == 0) { |     if (this->providers_.count(hostname) == 0) { | ||||||
|       Provider provider; |       Provider provider{}; | ||||||
|       provider.encryption_key = std::vector<uint8_t>{}; |  | ||||||
|       provider.last_code[0] = 0; |  | ||||||
|       provider.last_code[1] = 0; |  | ||||||
|       provider.name = hostname; |       provider.name = hostname; | ||||||
|       this->providers_[hostname] = provider; |       this->providers_[hostname] = provider; | ||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
| @@ -97,6 +98,11 @@ class PacketTransport : public PollingComponent { | |||||||
|   void set_provider_encryption(const char *name, std::vector<uint8_t> key) { |   void set_provider_encryption(const char *name, std::vector<uint8_t> key) { | ||||||
|     this->providers_[name].encryption_key = std::move(key); |     this->providers_[name].encryption_key = std::move(key); | ||||||
|   } |   } | ||||||
|  | #ifdef USE_STATUS_SENSOR | ||||||
|  |   void set_provider_status_sensor(const char *name, binary_sensor::BinarySensor *sensor) { | ||||||
|  |     this->providers_[name].status_sensor = sensor; | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|   void set_platform_name(const char *name) { this->platform_name_ = name; } |   void set_platform_name(const char *name) { this->platform_name_ = name; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   | |||||||
| @@ -86,6 +86,7 @@ | |||||||
| #define USE_SELECT | #define USE_SELECT | ||||||
| #define USE_SENSOR | #define USE_SENSOR | ||||||
| #define USE_STATUS_LED | #define USE_STATUS_LED | ||||||
|  | #define USE_STATUS_SENSOR | ||||||
| #define USE_SWITCH | #define USE_SWITCH | ||||||
| #define USE_TEXT | #define USE_TEXT | ||||||
| #define USE_TEXT_SENSOR | #define USE_TEXT_SENSOR | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ platformio==6.1.18  # When updating platformio, also update /docker/Dockerfile | |||||||
| esptool==4.9.0 | esptool==4.9.0 | ||||||
| click==8.1.7 | click==8.1.7 | ||||||
| esphome-dashboard==20250514.0 | esphome-dashboard==20250514.0 | ||||||
| aioesphomeapi==34.0.0 | aioesphomeapi==34.1.0 | ||||||
| zeroconf==0.147.0 | zeroconf==0.147.0 | ||||||
| puremagic==1.29 | puremagic==1.29 | ||||||
| ruamel.yaml==0.18.14 # dashboard_import | ruamel.yaml==0.18.14 # dashboard_import | ||||||
|   | |||||||
| @@ -813,27 +813,137 @@ class RepeatedTypeInfo(TypeInfo): | |||||||
|         return underlying_size * 2 |         return underlying_size * 2 | ||||||
|  |  | ||||||
|  |  | ||||||
| def build_enum_type(desc) -> tuple[str, str]: | def build_type_usage_map( | ||||||
|     """Builds the enum type.""" |     file_desc: descriptor.FileDescriptorProto, | ||||||
|  | ) -> tuple[dict[str, str | None], dict[str, str | None]]: | ||||||
|  |     """Build mappings for both enums and messages to their ifdefs based on usage. | ||||||
|  |  | ||||||
|  |     Returns: | ||||||
|  |         tuple: (enum_ifdef_map, message_ifdef_map) | ||||||
|  |     """ | ||||||
|  |     enum_ifdef_map: dict[str, str | None] = {} | ||||||
|  |     message_ifdef_map: dict[str, str | None] = {} | ||||||
|  |  | ||||||
|  |     # Build maps of which types are used by which messages | ||||||
|  |     enum_usage: dict[ | ||||||
|  |         str, set[str] | ||||||
|  |     ] = {}  # enum_name -> set of message names that use it | ||||||
|  |     message_usage: dict[ | ||||||
|  |         str, set[str] | ||||||
|  |     ] = {}  # message_name -> set of message names that use it | ||||||
|  |  | ||||||
|  |     # Build message name to ifdef mapping for quick lookup | ||||||
|  |     message_to_ifdef: dict[str, str | None] = { | ||||||
|  |         msg.name: get_opt(msg, pb.ifdef) for msg in file_desc.message_type | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     # Analyze field usage | ||||||
|  |     for message in file_desc.message_type: | ||||||
|  |         for field in message.field: | ||||||
|  |             type_name = field.type_name.split(".")[-1] if field.type_name else None | ||||||
|  |             if not type_name: | ||||||
|  |                 continue | ||||||
|  |  | ||||||
|  |             # Track enum usage | ||||||
|  |             if field.type == 14:  # TYPE_ENUM | ||||||
|  |                 enum_usage.setdefault(type_name, set()).add(message.name) | ||||||
|  |             # Track message usage | ||||||
|  |             elif field.type == 11:  # TYPE_MESSAGE | ||||||
|  |                 message_usage.setdefault(type_name, set()).add(message.name) | ||||||
|  |  | ||||||
|  |     # Helper to get unique ifdef from a set of messages | ||||||
|  |     def get_unique_ifdef(message_names: set[str]) -> str | None: | ||||||
|  |         ifdefs: set[str] = { | ||||||
|  |             message_to_ifdef[name] | ||||||
|  |             for name in message_names | ||||||
|  |             if message_to_ifdef.get(name) | ||||||
|  |         } | ||||||
|  |         return ifdefs.pop() if len(ifdefs) == 1 else None | ||||||
|  |  | ||||||
|  |     # Build enum ifdef map | ||||||
|  |     for enum in file_desc.enum_type: | ||||||
|  |         if enum.name in enum_usage: | ||||||
|  |             enum_ifdef_map[enum.name] = get_unique_ifdef(enum_usage[enum.name]) | ||||||
|  |         else: | ||||||
|  |             enum_ifdef_map[enum.name] = None | ||||||
|  |  | ||||||
|  |     # Build message ifdef map | ||||||
|  |     for message in file_desc.message_type: | ||||||
|  |         # Explicit ifdef takes precedence | ||||||
|  |         explicit_ifdef = message_to_ifdef.get(message.name) | ||||||
|  |         if explicit_ifdef: | ||||||
|  |             message_ifdef_map[message.name] = explicit_ifdef | ||||||
|  |         elif message.name in message_usage: | ||||||
|  |             # Inherit ifdef if all parent messages have the same one | ||||||
|  |             message_ifdef_map[message.name] = get_unique_ifdef( | ||||||
|  |                 message_usage[message.name] | ||||||
|  |             ) | ||||||
|  |         else: | ||||||
|  |             message_ifdef_map[message.name] = None | ||||||
|  |  | ||||||
|  |     # Second pass: propagate ifdefs recursively | ||||||
|  |     # Keep iterating until no more changes are made | ||||||
|  |     changed = True | ||||||
|  |     iterations = 0 | ||||||
|  |     while changed and iterations < 10:  # Add safety limit | ||||||
|  |         changed = False | ||||||
|  |         iterations += 1 | ||||||
|  |         for message in file_desc.message_type: | ||||||
|  |             # Skip if already has an ifdef | ||||||
|  |             if message_ifdef_map.get(message.name): | ||||||
|  |                 continue | ||||||
|  |  | ||||||
|  |             # Check if this message is used by other messages | ||||||
|  |             if message.name not in message_usage: | ||||||
|  |                 continue | ||||||
|  |  | ||||||
|  |             # Get ifdefs from all messages that use this one | ||||||
|  |             parent_ifdefs: set[str] = { | ||||||
|  |                 message_ifdef_map.get(parent) | ||||||
|  |                 for parent in message_usage[message.name] | ||||||
|  |                 if message_ifdef_map.get(parent) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             # If all parents have the same ifdef, inherit it | ||||||
|  |             if len(parent_ifdefs) == 1 and None not in parent_ifdefs: | ||||||
|  |                 message_ifdef_map[message.name] = parent_ifdefs.pop() | ||||||
|  |                 changed = True | ||||||
|  |  | ||||||
|  |     return enum_ifdef_map, message_ifdef_map | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def build_enum_type(desc, enum_ifdef_map) -> tuple[str, str, str]: | ||||||
|  |     """Builds the enum type. | ||||||
|  |  | ||||||
|  |     Args: | ||||||
|  |         desc: The enum descriptor | ||||||
|  |         enum_ifdef_map: Mapping of enum names to their ifdefs | ||||||
|  |  | ||||||
|  |     Returns: | ||||||
|  |         tuple: (header_content, cpp_content, dump_cpp_content) | ||||||
|  |     """ | ||||||
|     name = desc.name |     name = desc.name | ||||||
|  |  | ||||||
|     out = f"enum {name} : uint32_t {{\n" |     out = f"enum {name} : uint32_t {{\n" | ||||||
|     for v in desc.value: |     for v in desc.value: | ||||||
|         out += f"  {v.name} = {v.number},\n" |         out += f"  {v.name} = {v.number},\n" | ||||||
|     out += "};\n" |     out += "};\n" | ||||||
|  |  | ||||||
|     cpp = "#ifdef HAS_PROTO_MESSAGE_DUMP\n" |     # Regular cpp file has no enum content anymore | ||||||
|     cpp += f"template<> const char *proto_enum_to_string<enums::{name}>(enums::{name} value) {{\n" |     cpp = "" | ||||||
|     cpp += "  switch (value) {\n" |  | ||||||
|     for v in desc.value: |  | ||||||
|         cpp += f"    case enums::{v.name}:\n" |  | ||||||
|         cpp += f'      return "{v.name}";\n' |  | ||||||
|     cpp += "    default:\n" |  | ||||||
|     cpp += '      return "UNKNOWN";\n' |  | ||||||
|     cpp += "  }\n" |  | ||||||
|     cpp += "}\n" |  | ||||||
|     cpp += "#endif\n" |  | ||||||
|  |  | ||||||
|     return out, cpp |     # Dump cpp content for enum string conversion | ||||||
|  |     dump_cpp = f"template<> const char *proto_enum_to_string<enums::{name}>(enums::{name} value) {{\n" | ||||||
|  |     dump_cpp += "  switch (value) {\n" | ||||||
|  |     for v in desc.value: | ||||||
|  |         dump_cpp += f"    case enums::{v.name}:\n" | ||||||
|  |         dump_cpp += f'      return "{v.name}";\n' | ||||||
|  |     dump_cpp += "    default:\n" | ||||||
|  |     dump_cpp += '      return "UNKNOWN";\n' | ||||||
|  |     dump_cpp += "  }\n" | ||||||
|  |     dump_cpp += "}\n" | ||||||
|  |  | ||||||
|  |     return out, cpp, dump_cpp | ||||||
|  |  | ||||||
|  |  | ||||||
| def calculate_message_estimated_size(desc: descriptor.DescriptorProto) -> int: | def calculate_message_estimated_size(desc: descriptor.DescriptorProto) -> int: | ||||||
| @@ -855,7 +965,7 @@ def calculate_message_estimated_size(desc: descriptor.DescriptorProto) -> int: | |||||||
| def build_message_type( | def build_message_type( | ||||||
|     desc: descriptor.DescriptorProto, |     desc: descriptor.DescriptorProto, | ||||||
|     base_class_fields: dict[str, list[descriptor.FieldDescriptorProto]] = None, |     base_class_fields: dict[str, list[descriptor.FieldDescriptorProto]] = None, | ||||||
| ) -> tuple[str, str]: | ) -> tuple[str, str, str]: | ||||||
|     public_content: list[str] = [] |     public_content: list[str] = [] | ||||||
|     protected_content: list[str] = [] |     protected_content: list[str] = [] | ||||||
|     decode_varint: list[str] = [] |     decode_varint: list[str] = [] | ||||||
| @@ -886,7 +996,7 @@ def build_message_type( | |||||||
|             f"static constexpr uint16_t ESTIMATED_SIZE = {estimated_size};" |             f"static constexpr uint16_t ESTIMATED_SIZE = {estimated_size};" | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         # Add message_name method for debugging |         # Add message_name method inline in header | ||||||
|         public_content.append("#ifdef HAS_PROTO_MESSAGE_DUMP") |         public_content.append("#ifdef HAS_PROTO_MESSAGE_DUMP") | ||||||
|         snake_name = camel_to_snake(desc.name) |         snake_name = camel_to_snake(desc.name) | ||||||
|         public_content.append( |         public_content.append( | ||||||
| @@ -993,32 +1103,32 @@ def build_message_type( | |||||||
|         public_content.append(prot) |         public_content.append(prot) | ||||||
|     # If no fields to calculate size for, the default implementation in ProtoMessage will be used |     # If no fields to calculate size for, the default implementation in ProtoMessage will be used | ||||||
|  |  | ||||||
|     o = f"void {desc.name}::dump_to(std::string &out) const {{" |     # dump_to method declaration in header | ||||||
|     if dump: |  | ||||||
|         if len(dump) == 1 and len(dump[0]) + len(o) + 3 < 120: |  | ||||||
|             o += f" {dump[0]} " |  | ||||||
|         else: |  | ||||||
|             o += "\n" |  | ||||||
|             o += "  __attribute__((unused)) char buffer[64];\n" |  | ||||||
|             o += f'  out.append("{desc.name} {{\\n");\n' |  | ||||||
|             o += indent("\n".join(dump)) + "\n" |  | ||||||
|             o += '  out.append("}");\n' |  | ||||||
|     else: |  | ||||||
|         o2 = f'out.append("{desc.name} {{}}");' |  | ||||||
|         if len(o) + len(o2) + 3 < 120: |  | ||||||
|             o += f" {o2} " |  | ||||||
|         else: |  | ||||||
|             o += "\n" |  | ||||||
|             o += f"  {o2}\n" |  | ||||||
|     o += "}\n" |  | ||||||
|     cpp += "#ifdef HAS_PROTO_MESSAGE_DUMP\n" |  | ||||||
|     cpp += o |  | ||||||
|     cpp += "#endif\n" |  | ||||||
|     prot = "#ifdef HAS_PROTO_MESSAGE_DUMP\n" |     prot = "#ifdef HAS_PROTO_MESSAGE_DUMP\n" | ||||||
|     prot += "void dump_to(std::string &out) const override;\n" |     prot += "void dump_to(std::string &out) const override;\n" | ||||||
|     prot += "#endif\n" |     prot += "#endif\n" | ||||||
|     public_content.append(prot) |     public_content.append(prot) | ||||||
|  |  | ||||||
|  |     # dump_to implementation will go in dump_cpp | ||||||
|  |     dump_impl = f"void {desc.name}::dump_to(std::string &out) const {{" | ||||||
|  |     if dump: | ||||||
|  |         if len(dump) == 1 and len(dump[0]) + len(dump_impl) + 3 < 120: | ||||||
|  |             dump_impl += f" {dump[0]} " | ||||||
|  |         else: | ||||||
|  |             dump_impl += "\n" | ||||||
|  |             dump_impl += "  __attribute__((unused)) char buffer[64];\n" | ||||||
|  |             dump_impl += f'  out.append("{desc.name} {{\\n");\n' | ||||||
|  |             dump_impl += indent("\n".join(dump)) + "\n" | ||||||
|  |             dump_impl += '  out.append("}");\n' | ||||||
|  |     else: | ||||||
|  |         o2 = f'out.append("{desc.name} {{}}");' | ||||||
|  |         if len(dump_impl) + len(o2) + 3 < 120: | ||||||
|  |             dump_impl += f" {o2} " | ||||||
|  |         else: | ||||||
|  |             dump_impl += "\n" | ||||||
|  |             dump_impl += f"  {o2}\n" | ||||||
|  |     dump_impl += "}\n" | ||||||
|  |  | ||||||
|     if base_class: |     if base_class: | ||||||
|         out = f"class {desc.name} : public {base_class} {{\n" |         out = f"class {desc.name} : public {base_class} {{\n" | ||||||
|     else: |     else: | ||||||
| @@ -1031,7 +1141,11 @@ def build_message_type( | |||||||
|     if len(protected_content) > 0: |     if len(protected_content) > 0: | ||||||
|         out += "\n" |         out += "\n" | ||||||
|     out += "};\n" |     out += "};\n" | ||||||
|     return out, cpp |  | ||||||
|  |     # Build dump_cpp content with dump_to implementation | ||||||
|  |     dump_cpp = dump_impl | ||||||
|  |  | ||||||
|  |     return out, cpp, dump_cpp | ||||||
|  |  | ||||||
|  |  | ||||||
| SOURCE_BOTH = 0 | SOURCE_BOTH = 0 | ||||||
| @@ -1119,7 +1233,7 @@ def find_common_fields( | |||||||
| def build_base_class( | def build_base_class( | ||||||
|     base_class_name: str, |     base_class_name: str, | ||||||
|     common_fields: list[descriptor.FieldDescriptorProto], |     common_fields: list[descriptor.FieldDescriptorProto], | ||||||
| ) -> tuple[str, str]: | ) -> tuple[str, str, str]: | ||||||
|     """Build the base class definition and implementation.""" |     """Build the base class definition and implementation.""" | ||||||
|     public_content = [] |     public_content = [] | ||||||
|     protected_content = [] |     protected_content = [] | ||||||
| @@ -1156,16 +1270,18 @@ def build_base_class( | |||||||
|     out += "};\n" |     out += "};\n" | ||||||
|  |  | ||||||
|     # No implementation needed for base classes |     # No implementation needed for base classes | ||||||
|  |     dump_cpp = "" | ||||||
|  |  | ||||||
|     return out, cpp |     return out, cpp, dump_cpp | ||||||
|  |  | ||||||
|  |  | ||||||
| def generate_base_classes( | def generate_base_classes( | ||||||
|     base_class_groups: dict[str, list[descriptor.DescriptorProto]], |     base_class_groups: dict[str, list[descriptor.DescriptorProto]], | ||||||
| ) -> tuple[str, str]: | ) -> tuple[str, str, str]: | ||||||
|     """Generate all base classes.""" |     """Generate all base classes.""" | ||||||
|     all_headers = [] |     all_headers = [] | ||||||
|     all_cpp = [] |     all_cpp = [] | ||||||
|  |     all_dump_cpp = [] | ||||||
|  |  | ||||||
|     for base_class_name, messages in base_class_groups.items(): |     for base_class_name, messages in base_class_groups.items(): | ||||||
|         # Find common fields |         # Find common fields | ||||||
| @@ -1173,11 +1289,12 @@ def generate_base_classes( | |||||||
|  |  | ||||||
|         if common_fields: |         if common_fields: | ||||||
|             # Generate base class |             # Generate base class | ||||||
|             header, cpp = build_base_class(base_class_name, common_fields) |             header, cpp, dump_cpp = build_base_class(base_class_name, common_fields) | ||||||
|             all_headers.append(header) |             all_headers.append(header) | ||||||
|             all_cpp.append(cpp) |             all_cpp.append(cpp) | ||||||
|  |             all_dump_cpp.append(dump_cpp) | ||||||
|  |  | ||||||
|     return "\n".join(all_headers), "\n".join(all_cpp) |     return "\n".join(all_headers), "\n".join(all_cpp), "\n".join(all_dump_cpp) | ||||||
|  |  | ||||||
|  |  | ||||||
| def build_service_message_type( | def build_service_message_type( | ||||||
| @@ -1246,6 +1363,8 @@ def main() -> None: | |||||||
|     content += """\ |     content += """\ | ||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/defines.h" | ||||||
|  |  | ||||||
| #include "proto.h" | #include "proto.h" | ||||||
| #include "api_pb2_size.h" | #include "api_pb2_size.h" | ||||||
|  |  | ||||||
| @@ -1261,8 +1380,21 @@ def main() -> None: | |||||||
|     #include "esphome/core/log.h" |     #include "esphome/core/log.h" | ||||||
|     #include "esphome/core/helpers.h" |     #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace api { | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |  | ||||||
|  |     # Initialize dump cpp content | ||||||
|  |     dump_cpp = FILE_HEADER | ||||||
|  |     dump_cpp += """\ | ||||||
|  | #include "api_pb2.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
| #include <cinttypes> | #include <cinttypes> | ||||||
|  |  | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace api { | namespace api { | ||||||
|  |  | ||||||
| @@ -1270,10 +1402,34 @@ def main() -> None: | |||||||
|  |  | ||||||
|     content += "namespace enums {\n\n" |     content += "namespace enums {\n\n" | ||||||
|  |  | ||||||
|  |     # Build dynamic ifdef mappings for both enums and messages | ||||||
|  |     enum_ifdef_map, message_ifdef_map = build_type_usage_map(file) | ||||||
|  |  | ||||||
|  |     # Simple grouping of enums by ifdef | ||||||
|  |     current_ifdef = None | ||||||
|  |  | ||||||
|     for enum in file.enum_type: |     for enum in file.enum_type: | ||||||
|         s, c = build_enum_type(enum) |         s, c, dc = build_enum_type(enum, enum_ifdef_map) | ||||||
|  |         enum_ifdef = enum_ifdef_map.get(enum.name) | ||||||
|  |  | ||||||
|  |         # Handle ifdef changes | ||||||
|  |         if enum_ifdef != current_ifdef: | ||||||
|  |             if current_ifdef is not None: | ||||||
|  |                 content += "#endif\n" | ||||||
|  |                 dump_cpp += "#endif\n" | ||||||
|  |             if enum_ifdef is not None: | ||||||
|  |                 content += f"#ifdef {enum_ifdef}\n" | ||||||
|  |                 dump_cpp += f"#ifdef {enum_ifdef}\n" | ||||||
|  |             current_ifdef = enum_ifdef | ||||||
|  |  | ||||||
|         content += s |         content += s | ||||||
|         cpp += c |         cpp += c | ||||||
|  |         dump_cpp += dc | ||||||
|  |  | ||||||
|  |     # Close last ifdef | ||||||
|  |     if current_ifdef is not None: | ||||||
|  |         content += "#endif\n" | ||||||
|  |         dump_cpp += "#endif\n" | ||||||
|  |  | ||||||
|     content += "\n}  // namespace enums\n\n" |     content += "\n}  // namespace enums\n\n" | ||||||
|  |  | ||||||
| @@ -1291,15 +1447,42 @@ def main() -> None: | |||||||
|  |  | ||||||
|     # Generate base classes |     # Generate base classes | ||||||
|     if base_class_fields: |     if base_class_fields: | ||||||
|         base_headers, base_cpp = generate_base_classes(base_class_groups) |         base_headers, base_cpp, base_dump_cpp = generate_base_classes(base_class_groups) | ||||||
|         content += base_headers |         content += base_headers | ||||||
|         cpp += base_cpp |         cpp += base_cpp | ||||||
|  |         dump_cpp += base_dump_cpp | ||||||
|  |  | ||||||
|     # Generate message types with base class information |     # Generate message types with base class information | ||||||
|  |     # Simple grouping by ifdef | ||||||
|  |     current_ifdef = None | ||||||
|  |  | ||||||
|     for m in mt: |     for m in mt: | ||||||
|         s, c = build_message_type(m, base_class_fields) |         s, c, dc = build_message_type(m, base_class_fields) | ||||||
|  |         msg_ifdef = message_ifdef_map.get(m.name) | ||||||
|  |  | ||||||
|  |         # Handle ifdef changes | ||||||
|  |         if msg_ifdef != current_ifdef: | ||||||
|  |             if current_ifdef is not None: | ||||||
|  |                 content += "#endif\n" | ||||||
|  |                 if cpp: | ||||||
|  |                     cpp += "#endif\n" | ||||||
|  |                 if dump_cpp: | ||||||
|  |                     dump_cpp += "#endif\n" | ||||||
|  |             if msg_ifdef is not None: | ||||||
|  |                 content += f"#ifdef {msg_ifdef}\n" | ||||||
|  |                 cpp += f"#ifdef {msg_ifdef}\n" | ||||||
|  |                 dump_cpp += f"#ifdef {msg_ifdef}\n" | ||||||
|  |             current_ifdef = msg_ifdef | ||||||
|  |  | ||||||
|         content += s |         content += s | ||||||
|         cpp += c |         cpp += c | ||||||
|  |         dump_cpp += dc | ||||||
|  |  | ||||||
|  |     # Close last ifdef | ||||||
|  |     if current_ifdef is not None: | ||||||
|  |         content += "#endif\n" | ||||||
|  |         cpp += "#endif\n" | ||||||
|  |         dump_cpp += "#endif\n" | ||||||
|  |  | ||||||
|     content += """\ |     content += """\ | ||||||
|  |  | ||||||
| @@ -1310,6 +1493,14 @@ def main() -> None: | |||||||
|  |  | ||||||
| }  // namespace api | }  // namespace api | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  | """ | ||||||
|  |  | ||||||
|  |     dump_cpp += """\ | ||||||
|  |  | ||||||
|  | }  // namespace api | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif  // HAS_PROTO_MESSAGE_DUMP | ||||||
| """ | """ | ||||||
|  |  | ||||||
|     with open(root / "api_pb2.h", "w", encoding="utf-8") as f: |     with open(root / "api_pb2.h", "w", encoding="utf-8") as f: | ||||||
| @@ -1318,13 +1509,17 @@ def main() -> None: | |||||||
|     with open(root / "api_pb2.cpp", "w", encoding="utf-8") as f: |     with open(root / "api_pb2.cpp", "w", encoding="utf-8") as f: | ||||||
|         f.write(cpp) |         f.write(cpp) | ||||||
|  |  | ||||||
|  |     with open(root / "api_pb2_dump.cpp", "w", encoding="utf-8") as f: | ||||||
|  |         f.write(dump_cpp) | ||||||
|  |  | ||||||
|     hpp = FILE_HEADER |     hpp = FILE_HEADER | ||||||
|     hpp += """\ |     hpp += """\ | ||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
|     #include "api_pb2.h" |  | ||||||
| #include "esphome/core/defines.h" | #include "esphome/core/defines.h" | ||||||
|  |  | ||||||
|  | #include "api_pb2.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace api { | namespace api { | ||||||
|  |  | ||||||
| @@ -1419,7 +1614,7 @@ def main() -> None: | |||||||
|         needs_conn = get_opt(m, pb.needs_setup_connection, True) |         needs_conn = get_opt(m, pb.needs_setup_connection, True) | ||||||
|         needs_auth = get_opt(m, pb.needs_authentication, True) |         needs_auth = get_opt(m, pb.needs_authentication, True) | ||||||
|  |  | ||||||
|         ifdef = ifdefs.get(inp, None) |         ifdef = message_ifdef_map.get(inp, ifdefs.get(inp, None)) | ||||||
|  |  | ||||||
|         if ifdef is not None: |         if ifdef is not None: | ||||||
|             hpp += f"#ifdef {ifdef}\n" |             hpp += f"#ifdef {ifdef}\n" | ||||||
| @@ -1506,6 +1701,8 @@ def main() -> None: | |||||||
|         exec_clang_format(root / "api_pb2_service.cpp") |         exec_clang_format(root / "api_pb2_service.cpp") | ||||||
|         exec_clang_format(root / "api_pb2.h") |         exec_clang_format(root / "api_pb2.h") | ||||||
|         exec_clang_format(root / "api_pb2.cpp") |         exec_clang_format(root / "api_pb2.cpp") | ||||||
|  |         exec_clang_format(root / "api_pb2_dump.h") | ||||||
|  |         exec_clang_format(root / "api_pb2_dump.cpp") | ||||||
|     except ImportError: |     except ImportError: | ||||||
|         pass |         pass | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								tests/components/ds2484/common.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								tests/components/ds2484/common.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | i2c: | ||||||
|  |   - id: i2c_ds2484 | ||||||
|  |     scl: ${scl_pin} | ||||||
|  |     sda: ${sda_pin} | ||||||
|  |  | ||||||
|  | one_wire: | ||||||
|  |   platform: ds2484 | ||||||
|  |   i2c_id: i2c_ds2484 | ||||||
|  |   address: 0x18 | ||||||
|  |   active_pullup: true | ||||||
|  |   strong_pullup: false | ||||||
							
								
								
									
										5
									
								
								tests/components/ds2484/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/ds2484/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO16 | ||||||
|  |   sda_pin: GPIO17 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/ds2484/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/ds2484/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO5 | ||||||
|  |   sda_pin: GPIO4 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/ds2484/test.esp32-c3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/ds2484/test.esp32-c3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO5 | ||||||
|  |   sda_pin: GPIO4 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/ds2484/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/ds2484/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO16 | ||||||
|  |   sda_pin: GPIO17 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/ds2484/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/ds2484/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO5 | ||||||
|  |   sda_pin: GPIO4 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/ds2484/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/ds2484/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO5 | ||||||
|  |   sda_pin: GPIO4 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
| @@ -5,7 +5,8 @@ packages: | |||||||
|   - !include package.yaml |   - !include package.yaml | ||||||
|   - github://esphome/esphome/tests/components/template/common.yaml@dev |   - github://esphome/esphome/tests/components/template/common.yaml@dev | ||||||
|   - url: https://github.com/esphome/esphome |   - url: https://github.com/esphome/esphome | ||||||
|     file: tests/components/absolute_humidity/common.yaml |     path: tests/components/absolute_humidity | ||||||
|  |     file: common.yaml | ||||||
|     ref: dev |     ref: dev | ||||||
|     refresh: 1d |     refresh: 1d | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,7 +7,8 @@ packages: | |||||||
|   shorthand: github://esphome/esphome/tests/components/template/common.yaml@dev |   shorthand: github://esphome/esphome/tests/components/template/common.yaml@dev | ||||||
|   github: |   github: | ||||||
|     url: https://github.com/esphome/esphome |     url: https://github.com/esphome/esphome | ||||||
|     file: tests/components/absolute_humidity/common.yaml |     path: tests/components/absolute_humidity | ||||||
|  |     file: common.yaml | ||||||
|     ref: dev |     ref: dev | ||||||
|     refresh: 1d |     refresh: 1d | ||||||
|  |  | ||||||
|   | |||||||
| @@ -36,5 +36,9 @@ binary_sensor: | |||||||
|   - platform: packet_transport |   - platform: packet_transport | ||||||
|     provider: unencrypted-device |     provider: unencrypted-device | ||||||
|     id: other_binary_sensor_id |     id: other_binary_sensor_id | ||||||
|  |   - platform: packet_transport | ||||||
|  |     provider: some-device-name | ||||||
|  |     type: status | ||||||
|  |     name: Some-Device Status | ||||||
|   - platform: template |   - platform: template | ||||||
|     id: binary_sensor_id1 |     id: binary_sensor_id1 | ||||||
|   | |||||||
| @@ -0,0 +1,43 @@ | |||||||
|  | esphome: | ||||||
|  |   name: rapid-transitions-test | ||||||
|  | host: | ||||||
|  | api: | ||||||
|  |   batch_delay: 0ms  # Enable immediate sending for rapid transitions | ||||||
|  | logger: | ||||||
|  |   level: DEBUG | ||||||
|  |  | ||||||
|  | # Add a sensor that updates frequently to trigger lambda evaluations | ||||||
|  | sensor: | ||||||
|  |   - platform: template | ||||||
|  |     name: "Update Trigger" | ||||||
|  |     id: update_trigger | ||||||
|  |     lambda: |- | ||||||
|  |       return 0; | ||||||
|  |     update_interval: 10ms | ||||||
|  |     internal: true | ||||||
|  |  | ||||||
|  | # Simulate an IR remote binary sensor with rapid ON/OFF transitions | ||||||
|  | binary_sensor: | ||||||
|  |   - platform: template | ||||||
|  |     name: "Simulated IR Remote Button" | ||||||
|  |     id: ir_remote_button | ||||||
|  |     lambda: |- | ||||||
|  |       // Simulate rapid button presses every ~100ms | ||||||
|  |       // Each "press" is ON for ~30ms then OFF | ||||||
|  |       uint32_t now = millis(); | ||||||
|  |       uint32_t press_cycle = now % 100;  // 100ms cycle | ||||||
|  |  | ||||||
|  |       // ON for first 30ms of each cycle | ||||||
|  |       if (press_cycle < 30) { | ||||||
|  |         // Only log state change | ||||||
|  |         if (!id(ir_remote_button).state) { | ||||||
|  |           ESP_LOGD("test", "Button ON at %u", now); | ||||||
|  |         } | ||||||
|  |         return true; | ||||||
|  |       } else { | ||||||
|  |         // Only log state change | ||||||
|  |         if (id(ir_remote_button).state) { | ||||||
|  |           ESP_LOGD("test", "Button OFF at %u", now); | ||||||
|  |         } | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
							
								
								
									
										85
									
								
								tests/integration/fixtures/device_id_in_state.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								tests/integration/fixtures/device_id_in_state.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | |||||||
|  | esphome: | ||||||
|  |   name: device-id-state-test | ||||||
|  |   # Define areas | ||||||
|  |   areas: | ||||||
|  |     - id: living_room | ||||||
|  |       name: Living Room | ||||||
|  |     - id: bedroom | ||||||
|  |       name: Bedroom | ||||||
|  |   # Define devices | ||||||
|  |   devices: | ||||||
|  |     - id: temperature_monitor | ||||||
|  |       name: Temperature Monitor | ||||||
|  |       area_id: living_room | ||||||
|  |     - id: humidity_monitor | ||||||
|  |       name: Humidity Monitor | ||||||
|  |       area_id: bedroom | ||||||
|  |     - id: motion_sensor | ||||||
|  |       name: Motion Sensor | ||||||
|  |       area_id: living_room | ||||||
|  |  | ||||||
|  | host: | ||||||
|  | api: | ||||||
|  | logger: | ||||||
|  |  | ||||||
|  | # Test different entity types with device assignments | ||||||
|  | sensor: | ||||||
|  |   - platform: template | ||||||
|  |     name: Temperature | ||||||
|  |     device_id: temperature_monitor | ||||||
|  |     lambda: return 25.5; | ||||||
|  |     update_interval: 0.1s | ||||||
|  |     unit_of_measurement: "°C" | ||||||
|  |  | ||||||
|  |   - platform: template | ||||||
|  |     name: Humidity | ||||||
|  |     device_id: humidity_monitor | ||||||
|  |     lambda: return 65.0; | ||||||
|  |     update_interval: 0.1s | ||||||
|  |     unit_of_measurement: "%" | ||||||
|  |  | ||||||
|  |   # Test entity without device_id (should have device_id 0) | ||||||
|  |   - platform: template | ||||||
|  |     name: No Device Sensor | ||||||
|  |     lambda: return 100.0; | ||||||
|  |     update_interval: 0.1s | ||||||
|  |  | ||||||
|  | binary_sensor: | ||||||
|  |   - platform: template | ||||||
|  |     name: Motion Detected | ||||||
|  |     device_id: motion_sensor | ||||||
|  |     lambda: return true; | ||||||
|  |  | ||||||
|  | switch: | ||||||
|  |   - platform: template | ||||||
|  |     name: Temperature Monitor Power | ||||||
|  |     device_id: temperature_monitor | ||||||
|  |     lambda: return true; | ||||||
|  |     turn_on_action: | ||||||
|  |       - lambda: |- | ||||||
|  |           ESP_LOGD("test", "Turning on"); | ||||||
|  |     turn_off_action: | ||||||
|  |       - lambda: |- | ||||||
|  |           ESP_LOGD("test", "Turning off"); | ||||||
|  |  | ||||||
|  | text_sensor: | ||||||
|  |   - platform: template | ||||||
|  |     name: Temperature Status | ||||||
|  |     device_id: temperature_monitor | ||||||
|  |     lambda: return {"Normal"}; | ||||||
|  |     update_interval: 0.1s | ||||||
|  |  | ||||||
|  | light: | ||||||
|  |   - platform: binary | ||||||
|  |     name: Motion Light | ||||||
|  |     device_id: motion_sensor | ||||||
|  |     output: motion_light_output | ||||||
|  |  | ||||||
|  | output: | ||||||
|  |   - platform: template | ||||||
|  |     id: motion_light_output | ||||||
|  |     type: binary | ||||||
|  |     write_action: | ||||||
|  |       - lambda: |- | ||||||
|  |           ESP_LOGD("test", "Light output: %d", state); | ||||||
|  |  | ||||||
							
								
								
									
										58
									
								
								tests/integration/test_batch_delay_zero_rapid_transitions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								tests/integration/test_batch_delay_zero_rapid_transitions.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | """Integration test for API batch_delay: 0 with rapid state transitions.""" | ||||||
|  |  | ||||||
|  | from __future__ import annotations | ||||||
|  |  | ||||||
|  | import asyncio | ||||||
|  | import time | ||||||
|  |  | ||||||
|  | from aioesphomeapi import BinarySensorInfo, BinarySensorState, EntityState | ||||||
|  | import pytest | ||||||
|  |  | ||||||
|  | from .types import APIClientConnectedFactory, RunCompiledFunction | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_batch_delay_zero_rapid_transitions( | ||||||
|  |     yaml_config: str, | ||||||
|  |     run_compiled: RunCompiledFunction, | ||||||
|  |     api_client_connected: APIClientConnectedFactory, | ||||||
|  | ) -> None: | ||||||
|  |     """Test that rapid binary sensor transitions are preserved with batch_delay: 0ms.""" | ||||||
|  |     async with run_compiled(yaml_config), api_client_connected() as client: | ||||||
|  |         # Track state changes | ||||||
|  |         state_changes: list[tuple[bool, float]] = [] | ||||||
|  |  | ||||||
|  |         def on_state(state: EntityState) -> None: | ||||||
|  |             """Track state changes with timestamps.""" | ||||||
|  |             if isinstance(state, BinarySensorState): | ||||||
|  |                 state_changes.append((state.state, time.monotonic())) | ||||||
|  |  | ||||||
|  |         # Subscribe to state changes | ||||||
|  |         client.subscribe_states(on_state) | ||||||
|  |  | ||||||
|  |         # Wait for entity info | ||||||
|  |         entity_info, _ = await client.list_entities_services() | ||||||
|  |         binary_sensors = [e for e in entity_info if isinstance(e, BinarySensorInfo)] | ||||||
|  |         assert len(binary_sensors) == 1, "Expected 1 binary sensor" | ||||||
|  |  | ||||||
|  |         # Collect states for 2 seconds | ||||||
|  |         await asyncio.sleep(2.1) | ||||||
|  |  | ||||||
|  |         # Count ON->OFF transitions | ||||||
|  |         on_off_count = 0 | ||||||
|  |         for i in range(1, len(state_changes)): | ||||||
|  |             if state_changes[i - 1][0] and not state_changes[i][0]:  # ON to OFF | ||||||
|  |                 on_off_count += 1 | ||||||
|  |  | ||||||
|  |         # With batch_delay: 0, we should capture rapid transitions | ||||||
|  |         # The test timing can be variable in CI, so we're being conservative | ||||||
|  |         # We mainly want to verify that we capture multiple rapid transitions | ||||||
|  |         assert on_off_count >= 5, ( | ||||||
|  |             f"Expected at least 5 ON->OFF transitions with batch_delay: 0ms, got {on_off_count}. " | ||||||
|  |             "Rapid transitions may have been lost." | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Also verify that state changes are happening frequently | ||||||
|  |         assert len(state_changes) >= 10, ( | ||||||
|  |             f"Expected at least 10 state changes, got {len(state_changes)}" | ||||||
|  |         ) | ||||||
							
								
								
									
										161
									
								
								tests/integration/test_device_id_in_state.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								tests/integration/test_device_id_in_state.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,161 @@ | |||||||
|  | """Integration test for device_id in entity state responses.""" | ||||||
|  |  | ||||||
|  | from __future__ import annotations | ||||||
|  |  | ||||||
|  | import asyncio | ||||||
|  |  | ||||||
|  | from aioesphomeapi import EntityState | ||||||
|  | import pytest | ||||||
|  |  | ||||||
|  | from .types import APIClientConnectedFactory, RunCompiledFunction | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_device_id_in_state( | ||||||
|  |     yaml_config: str, | ||||||
|  |     run_compiled: RunCompiledFunction, | ||||||
|  |     api_client_connected: APIClientConnectedFactory, | ||||||
|  | ) -> None: | ||||||
|  |     """Test that device_id is included in entity state responses.""" | ||||||
|  |     async with run_compiled(yaml_config), api_client_connected() as client: | ||||||
|  |         # Get device info to verify devices are configured | ||||||
|  |         device_info = await client.device_info() | ||||||
|  |         assert device_info is not None | ||||||
|  |  | ||||||
|  |         # Verify devices exist | ||||||
|  |         devices = device_info.devices | ||||||
|  |         assert len(devices) >= 3, f"Expected at least 3 devices, got {len(devices)}" | ||||||
|  |  | ||||||
|  |         # Get device IDs for verification | ||||||
|  |         device_ids = {device.name: device.device_id for device in devices} | ||||||
|  |         assert "Temperature Monitor" in device_ids | ||||||
|  |         assert "Humidity Monitor" in device_ids | ||||||
|  |         assert "Motion Sensor" in device_ids | ||||||
|  |  | ||||||
|  |         # Get entity list | ||||||
|  |         entities = await client.list_entities_services() | ||||||
|  |         all_entities = entities[0] | ||||||
|  |  | ||||||
|  |         # Create a mapping of entity key to expected device_id | ||||||
|  |         entity_device_mapping: dict[int, int] = {} | ||||||
|  |  | ||||||
|  |         for entity in all_entities: | ||||||
|  |             if hasattr(entity, "name") and hasattr(entity, "key"): | ||||||
|  |                 if entity.name == "Temperature": | ||||||
|  |                     entity_device_mapping[entity.key] = device_ids[ | ||||||
|  |                         "Temperature Monitor" | ||||||
|  |                     ] | ||||||
|  |                 elif entity.name == "Humidity": | ||||||
|  |                     entity_device_mapping[entity.key] = device_ids["Humidity Monitor"] | ||||||
|  |                 elif entity.name == "Motion Detected": | ||||||
|  |                     entity_device_mapping[entity.key] = device_ids["Motion Sensor"] | ||||||
|  |                 elif entity.name == "Temperature Monitor Power": | ||||||
|  |                     entity_device_mapping[entity.key] = device_ids[ | ||||||
|  |                         "Temperature Monitor" | ||||||
|  |                     ] | ||||||
|  |                 elif entity.name == "Temperature Status": | ||||||
|  |                     entity_device_mapping[entity.key] = device_ids[ | ||||||
|  |                         "Temperature Monitor" | ||||||
|  |                     ] | ||||||
|  |                 elif entity.name == "Motion Light": | ||||||
|  |                     entity_device_mapping[entity.key] = device_ids["Motion Sensor"] | ||||||
|  |                 elif entity.name == "No Device Sensor": | ||||||
|  |                     # Entity without device_id should have device_id 0 | ||||||
|  |                     entity_device_mapping[entity.key] = 0 | ||||||
|  |  | ||||||
|  |         assert len(entity_device_mapping) >= 6, ( | ||||||
|  |             f"Expected at least 6 mapped entities, got {len(entity_device_mapping)}" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Subscribe to states | ||||||
|  |         loop = asyncio.get_running_loop() | ||||||
|  |         states: dict[int, EntityState] = {} | ||||||
|  |         states_future: asyncio.Future[bool] = loop.create_future() | ||||||
|  |  | ||||||
|  |         def on_state(state: EntityState) -> None: | ||||||
|  |             states[state.key] = state | ||||||
|  |             # Check if we have states for all mapped entities | ||||||
|  |             if len(states) >= len(entity_device_mapping) and not states_future.done(): | ||||||
|  |                 states_future.set_result(True) | ||||||
|  |  | ||||||
|  |         client.subscribe_states(on_state) | ||||||
|  |  | ||||||
|  |         # Wait for states | ||||||
|  |         try: | ||||||
|  |             await asyncio.wait_for(states_future, timeout=10.0) | ||||||
|  |         except asyncio.TimeoutError: | ||||||
|  |             pytest.fail( | ||||||
|  |                 f"Did not receive all entity states within 10 seconds. " | ||||||
|  |                 f"Received {len(states)} states, expected {len(entity_device_mapping)}" | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         # Verify each state has the correct device_id | ||||||
|  |         verified_count = 0 | ||||||
|  |         for key, expected_device_id in entity_device_mapping.items(): | ||||||
|  |             if key in states: | ||||||
|  |                 state = states[key] | ||||||
|  |  | ||||||
|  |                 assert state.device_id == expected_device_id, ( | ||||||
|  |                     f"State for key {key} has device_id {state.device_id}, " | ||||||
|  |                     f"expected {expected_device_id}" | ||||||
|  |                 ) | ||||||
|  |                 verified_count += 1 | ||||||
|  |  | ||||||
|  |         assert verified_count >= 6, ( | ||||||
|  |             f"Only verified {verified_count} states, expected at least 6" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Test specific state types to ensure device_id is present | ||||||
|  |         # Find a sensor state with device_id | ||||||
|  |         sensor_state = next( | ||||||
|  |             ( | ||||||
|  |                 s | ||||||
|  |                 for s in states.values() | ||||||
|  |                 if hasattr(s, "state") | ||||||
|  |                 and isinstance(s.state, float) | ||||||
|  |                 and s.device_id != 0 | ||||||
|  |             ), | ||||||
|  |             None, | ||||||
|  |         ) | ||||||
|  |         assert sensor_state is not None, "No sensor state with device_id found" | ||||||
|  |         assert sensor_state.device_id > 0, "Sensor state should have non-zero device_id" | ||||||
|  |  | ||||||
|  |         # Find a binary sensor state | ||||||
|  |         binary_sensor_state = next( | ||||||
|  |             ( | ||||||
|  |                 s | ||||||
|  |                 for s in states.values() | ||||||
|  |                 if hasattr(s, "state") and isinstance(s.state, bool) | ||||||
|  |             ), | ||||||
|  |             None, | ||||||
|  |         ) | ||||||
|  |         assert binary_sensor_state is not None, "No binary sensor state found" | ||||||
|  |         assert binary_sensor_state.device_id > 0, ( | ||||||
|  |             "Binary sensor state should have non-zero device_id" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Find a text sensor state | ||||||
|  |         text_sensor_state = next( | ||||||
|  |             ( | ||||||
|  |                 s | ||||||
|  |                 for s in states.values() | ||||||
|  |                 if hasattr(s, "state") and isinstance(s.state, str) | ||||||
|  |             ), | ||||||
|  |             None, | ||||||
|  |         ) | ||||||
|  |         assert text_sensor_state is not None, "No text sensor state found" | ||||||
|  |         assert text_sensor_state.device_id > 0, ( | ||||||
|  |             "Text sensor state should have non-zero device_id" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Verify the "No Device Sensor" has device_id = 0 | ||||||
|  |         no_device_key = next( | ||||||
|  |             (key for key, device_id in entity_device_mapping.items() if device_id == 0), | ||||||
|  |             None, | ||||||
|  |         ) | ||||||
|  |         assert no_device_key is not None, "No entity mapped to device_id 0" | ||||||
|  |         assert no_device_key in states, f"State for key {no_device_key} not found" | ||||||
|  |         no_device_state = states[no_device_key] | ||||||
|  |         assert no_device_state.device_id == 0, ( | ||||||
|  |             f"Entity without device_id should have device_id=0, got {no_device_state.device_id}" | ||||||
|  |         ) | ||||||
| @@ -74,37 +74,41 @@ async def test_host_mode_empty_string_options( | |||||||
|         # If we got here without protobuf decoding errors, the fix is working |         # If we got here without protobuf decoding errors, the fix is working | ||||||
|         # The bug would have caused "Invalid protobuf message" errors with trailing bytes |         # The bug would have caused "Invalid protobuf message" errors with trailing bytes | ||||||
|  |  | ||||||
|         # Also verify we can interact with the select entities |         # Also verify we can receive state updates for select entities | ||||||
|         # Subscribe to state changes |         # This ensures empty strings work properly in state messages too | ||||||
|         states: dict[int, EntityState] = {} |         states: dict[int, EntityState] = {} | ||||||
|         state_change_future: asyncio.Future[None] = loop.create_future() |         states_received_future: asyncio.Future[None] = loop.create_future() | ||||||
|  |         expected_select_keys = {empty_first.key, empty_middle.key, empty_last.key} | ||||||
|  |         received_select_keys = set() | ||||||
|  |  | ||||||
|         def on_state(state: EntityState) -> None: |         def on_state(state: EntityState) -> None: | ||||||
|             """Track state changes.""" |             """Track state changes.""" | ||||||
|             states[state.key] = state |             states[state.key] = state | ||||||
|             # When we receive the state change for our select, resolve the future |             # Track which select entities we've received states for | ||||||
|             if state.key == empty_first.key and not state_change_future.done(): |             if state.key in expected_select_keys: | ||||||
|                 state_change_future.set_result(None) |                 received_select_keys.add(state.key) | ||||||
|  |                 # Once we have all select states, we're done | ||||||
|  |                 if ( | ||||||
|  |                     received_select_keys == expected_select_keys | ||||||
|  |                     and not states_received_future.done() | ||||||
|  |                 ): | ||||||
|  |                     states_received_future.set_result(None) | ||||||
|  |  | ||||||
|         client.subscribe_states(on_state) |         client.subscribe_states(on_state) | ||||||
|  |  | ||||||
|         # Try setting a select to an empty string option |         # Wait for initial states with timeout | ||||||
|         # This further tests that empty strings are handled correctly |  | ||||||
|         client.select_command(empty_first.key, "") |  | ||||||
|  |  | ||||||
|         # Wait for state update with timeout |  | ||||||
|         try: |         try: | ||||||
|             await asyncio.wait_for(state_change_future, timeout=5.0) |             await asyncio.wait_for(states_received_future, timeout=5.0) | ||||||
|         except asyncio.TimeoutError: |         except asyncio.TimeoutError: | ||||||
|             pytest.fail( |             pytest.fail( | ||||||
|                 "Did not receive state update after setting select to empty string" |                 f"Did not receive states for all select entities. " | ||||||
|  |                 f"Expected keys: {expected_select_keys}, Received: {received_select_keys}" | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|         # Verify the state was set to empty string |         # Verify we received states for all select entities | ||||||
|         assert empty_first.key in states |         assert empty_first.key in states | ||||||
|         select_state = states[empty_first.key] |         assert empty_middle.key in states | ||||||
|         assert hasattr(select_state, "state") |         assert empty_last.key in states | ||||||
|         assert select_state.state == "" |  | ||||||
|  |  | ||||||
|         # The test passes if no protobuf decoding errors occurred |         # The main test is that we got here without protobuf errors | ||||||
|         # With the bug, we would have gotten "Invalid protobuf message" errors |         # The select entities with empty string options were properly encoded | ||||||
|   | |||||||
| @@ -46,14 +46,22 @@ async def test_host_mode_fan_preset( | |||||||
|         # Subscribe to states |         # Subscribe to states | ||||||
|         states: dict[int, FanState] = {} |         states: dict[int, FanState] = {} | ||||||
|         state_event = asyncio.Event() |         state_event = asyncio.Event() | ||||||
|  |         initial_states_received = set() | ||||||
|  |  | ||||||
|         def on_state(state: FanState) -> None: |         def on_state(state: FanState) -> None: | ||||||
|             if isinstance(state, FanState): |             if isinstance(state, FanState): | ||||||
|                 states[state.key] = state |                 states[state.key] = state | ||||||
|  |                 initial_states_received.add(state.key) | ||||||
|                 state_event.set() |                 state_event.set() | ||||||
|  |  | ||||||
|         client.subscribe_states(on_state) |         client.subscribe_states(on_state) | ||||||
|  |  | ||||||
|  |         # Wait for initial states to be received for all fans | ||||||
|  |         expected_fan_keys = {fan.key for fan in fans} | ||||||
|  |         while initial_states_received != expected_fan_keys: | ||||||
|  |             state_event.clear() | ||||||
|  |             await asyncio.wait_for(state_event.wait(), timeout=2.0) | ||||||
|  |  | ||||||
|         # Test 1: Turn on fan without speed or preset - should set speed to 100% |         # Test 1: Turn on fan without speed or preset - should set speed to 100% | ||||||
|         state_event.clear() |         state_event.clear() | ||||||
|         client.fan_command( |         client.fan_command( | ||||||
|   | |||||||
| @@ -22,36 +22,51 @@ async def test_host_mode_many_entities( | |||||||
|     async with run_compiled(yaml_config), api_client_connected() as client: |     async with run_compiled(yaml_config), api_client_connected() as client: | ||||||
|         # Subscribe to state changes |         # Subscribe to state changes | ||||||
|         states: dict[int, EntityState] = {} |         states: dict[int, EntityState] = {} | ||||||
|         entity_count_future: asyncio.Future[int] = loop.create_future() |         sensor_count_future: asyncio.Future[int] = loop.create_future() | ||||||
|  |  | ||||||
|         def on_state(state: EntityState) -> None: |         def on_state(state: EntityState) -> None: | ||||||
|             states[state.key] = state |             states[state.key] = state | ||||||
|             # When we have received states from a good number of entities, resolve the future |             # Count sensor states specifically | ||||||
|             if len(states) >= 50 and not entity_count_future.done(): |             sensor_states = [ | ||||||
|                 entity_count_future.set_result(len(states)) |                 s | ||||||
|  |                 for s in states.values() | ||||||
|  |                 if hasattr(s, "state") and isinstance(s.state, float) | ||||||
|  |             ] | ||||||
|  |             # When we have received states from at least 50 sensors, resolve the future | ||||||
|  |             if len(sensor_states) >= 50 and not sensor_count_future.done(): | ||||||
|  |                 sensor_count_future.set_result(len(sensor_states)) | ||||||
|  |  | ||||||
|         client.subscribe_states(on_state) |         client.subscribe_states(on_state) | ||||||
|  |  | ||||||
|         # Wait for states from at least 50 entities with timeout |         # Wait for states from at least 50 sensors with timeout | ||||||
|         try: |         try: | ||||||
|             entity_count = await asyncio.wait_for(entity_count_future, timeout=10.0) |             sensor_count = await asyncio.wait_for(sensor_count_future, timeout=10.0) | ||||||
|         except asyncio.TimeoutError: |         except asyncio.TimeoutError: | ||||||
|  |             sensor_states = [ | ||||||
|  |                 s | ||||||
|  |                 for s in states.values() | ||||||
|  |                 if hasattr(s, "state") and isinstance(s.state, float) | ||||||
|  |             ] | ||||||
|             pytest.fail( |             pytest.fail( | ||||||
|                 f"Did not receive states from at least 50 entities within 10 seconds. " |                 f"Did not receive states from at least 50 sensors within 10 seconds. " | ||||||
|                 f"Received {len(states)} states: {list(states.keys())}" |                 f"Received {len(sensor_states)} sensor states out of {len(states)} total states" | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|         # Verify we received a good number of entity states |         # Verify we received a good number of entity states | ||||||
|         assert entity_count >= 50, f"Expected at least 50 entities, got {entity_count}" |         assert len(states) >= 50, ( | ||||||
|         assert len(states) >= 50, f"Expected at least 50 states, got {len(states)}" |             f"Expected at least 50 total states, got {len(states)}" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|         # Verify we have different entity types by checking some expected values |         # Verify we have the expected sensor states | ||||||
|         sensor_states = [ |         sensor_states = [ | ||||||
|             s |             s | ||||||
|             for s in states.values() |             for s in states.values() | ||||||
|             if hasattr(s, "state") and isinstance(s.state, float) |             if hasattr(s, "state") and isinstance(s.state, float) | ||||||
|         ] |         ] | ||||||
|  |  | ||||||
|  |         assert sensor_count >= 50, ( | ||||||
|  |             f"Expected at least 50 sensor states, got {sensor_count}" | ||||||
|  |         ) | ||||||
|         assert len(sensor_states) >= 50, ( |         assert len(sensor_states) >= 50, ( | ||||||
|             f"Expected at least 50 sensor states, got {len(sensor_states)}" |             f"Expected at least 50 sensor states, got {len(sensor_states)}" | ||||||
|         ) |         ) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user