From 14d7c4bdbde327b74919af6f4c98fd181043bc27 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 3 Jul 2025 19:31:03 -0500 Subject: [PATCH] Add device_id to entity state messages for sub-device support (#9304) --- esphome/components/api/api.proto | 21 +++ esphome/components/api/api_connection.h | 3 + esphome/components/api/api_pb2.cpp | 132 ++++++++++++++ esphome/components/api/api_pb2.h | 44 ++--- esphome/components/api/api_pb2_dump.cpp | 105 ++++++++++++ .../fixtures/device_id_in_state.yaml | 85 +++++++++ tests/integration/test_device_id_in_state.py | 161 ++++++++++++++++++ 7 files changed, 530 insertions(+), 21 deletions(-) create mode 100644 tests/integration/fixtures/device_id_in_state.yaml create mode 100644 tests/integration/test_device_id_in_state.py diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 58a0b52555..a9aa0b4bff 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -311,6 +311,7 @@ message BinarySensorStateResponse { // If the binary sensor does not have a valid state yet. // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller bool missing_state = 3; + uint32 device_id = 4; } // ==================== COVER ==================== @@ -360,6 +361,7 @@ message CoverStateResponse { float position = 3; float tilt = 4; CoverOperation current_operation = 5; + uint32 device_id = 6; } enum LegacyCoverCommand { @@ -432,6 +434,7 @@ message FanStateResponse { FanDirection direction = 5; int32 speed_level = 6; string preset_mode = 7; + uint32 device_id = 8; } message FanCommandRequest { option (id) = 31; @@ -513,6 +516,7 @@ message LightStateResponse { float cold_white = 12; float warm_white = 13; string effect = 9; + uint32 device_id = 14; } message LightCommandRequest { option (id) = 32; @@ -598,6 +602,7 @@ message SensorStateResponse { // If the sensor does not have a valid state yet. // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller bool missing_state = 3; + uint32 device_id = 4; } // ==================== SWITCH ==================== @@ -628,6 +633,7 @@ message SwitchStateResponse { fixed32 key = 1; bool state = 2; + uint32 device_id = 3; } message SwitchCommandRequest { option (id) = 33; @@ -669,6 +675,7 @@ message TextSensorStateResponse { // If the text sensor does not have a valid state yet. // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller bool missing_state = 3; + uint32 device_id = 4; } // ==================== SUBSCRIBE LOGS ==================== @@ -966,6 +973,7 @@ message ClimateStateResponse { string custom_preset = 13; float current_humidity = 14; float target_humidity = 15; + uint32 device_id = 16; } message ClimateCommandRequest { option (id) = 48; @@ -1039,6 +1047,7 @@ message NumberStateResponse { // If the number does not have a valid state yet. // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller bool missing_state = 3; + uint32 device_id = 4; } message NumberCommandRequest { option (id) = 51; @@ -1080,6 +1089,7 @@ message SelectStateResponse { // If the select does not have a valid state yet. // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller bool missing_state = 3; + uint32 device_id = 4; } message SelectCommandRequest { option (id) = 54; @@ -1120,6 +1130,7 @@ message SirenStateResponse { fixed32 key = 1; bool state = 2; + uint32 device_id = 3; } message SirenCommandRequest { option (id) = 57; @@ -1183,6 +1194,7 @@ message LockStateResponse { option (no_delay) = true; fixed32 key = 1; LockState state = 2; + uint32 device_id = 3; } message LockCommandRequest { option (id) = 60; @@ -1282,6 +1294,7 @@ message MediaPlayerStateResponse { MediaPlayerState state = 2; float volume = 3; bool muted = 4; + uint32 device_id = 5; } message MediaPlayerCommandRequest { option (id) = 65; @@ -1822,6 +1835,7 @@ message AlarmControlPanelStateResponse { option (no_delay) = true; fixed32 key = 1; AlarmControlPanelState state = 2; + uint32 device_id = 3; } message AlarmControlPanelCommandRequest { @@ -1871,6 +1885,7 @@ message TextStateResponse { // If the Text does not have a valid state yet. // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller bool missing_state = 3; + uint32 device_id = 4; } message TextCommandRequest { option (id) = 99; @@ -1914,6 +1929,7 @@ message DateStateResponse { uint32 year = 3; uint32 month = 4; uint32 day = 5; + uint32 device_id = 6; } message DateCommandRequest { option (id) = 102; @@ -1958,6 +1974,7 @@ message TimeStateResponse { uint32 hour = 3; uint32 minute = 4; uint32 second = 5; + uint32 device_id = 6; } message TimeCommandRequest { option (id) = 105; @@ -1999,6 +2016,7 @@ message EventResponse { fixed32 key = 1; string event_type = 2; + uint32 device_id = 3; } // ==================== VALVE ==================== @@ -2039,6 +2057,7 @@ message ValveStateResponse { fixed32 key = 1; float position = 2; ValveOperation current_operation = 3; + uint32 device_id = 4; } message ValveCommandRequest { @@ -2082,6 +2101,7 @@ message DateTimeStateResponse { // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller bool missing_state = 2; fixed32 epoch_seconds = 3; + uint32 device_id = 4; } message DateTimeCommandRequest { option (id) = 114; @@ -2128,6 +2148,7 @@ message UpdateStateResponse { string title = 8; string release_summary = 9; string release_url = 10; + uint32 device_id = 11; } enum UpdateCommand { UPDATE_COMMAND_NONE = 0; diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 8922aab94a..dc4b84a535 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -292,6 +292,9 @@ class APIConnection : public APIServerConnection { // Helper function to fill common entity state fields static void fill_entity_state_base(esphome::EntityBase *entity, StateResponseProtoMessage &response) { 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 diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 01140fbfc8..5c2b22d22a 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -417,6 +417,10 @@ bool BinarySensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt val this->missing_state = value.as_bool(); return true; } + case 4: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -435,11 +439,13 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_bool(2, this->state); buffer.encode_bool(3, this->missing_state); + buffer.encode_uint32(4, this->device_id); } void BinarySensorStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); ProtoSize::add_bool_field(total_size, 1, this->state, false); ProtoSize::add_bool_field(total_size, 1, this->missing_state, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } #endif #ifdef USE_COVER @@ -553,6 +559,10 @@ bool CoverStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->current_operation = value.as_enum(); return true; } + case 6: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -581,6 +591,7 @@ void CoverStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(3, this->position); buffer.encode_float(4, this->tilt); buffer.encode_enum(5, this->current_operation); + buffer.encode_uint32(6, this->device_id); } void CoverStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); @@ -588,6 +599,7 @@ void CoverStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->position != 0.0f, false); ProtoSize::add_fixed_field<4>(total_size, 1, this->tilt != 0.0f, false); ProtoSize::add_enum_field(total_size, 1, static_cast(this->current_operation), false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } bool CoverCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -783,6 +795,10 @@ bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->speed_level = value.as_int32(); return true; } + case 8: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -815,6 +831,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_enum(5, this->direction); buffer.encode_int32(6, this->speed_level); buffer.encode_string(7, this->preset_mode); + buffer.encode_uint32(8, this->device_id); } void FanStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); @@ -824,6 +841,7 @@ void FanStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_enum_field(total_size, 1, static_cast(this->direction), false); ProtoSize::add_int32_field(total_size, 1, this->speed_level, false); ProtoSize::add_string_field(total_size, 1, this->preset_mode, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -1067,6 +1085,10 @@ bool LightStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->color_mode = value.as_enum(); return true; } + case 14: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -1141,6 +1163,7 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(12, this->cold_white); buffer.encode_float(13, this->warm_white); buffer.encode_string(9, this->effect); + buffer.encode_uint32(14, this->device_id); } void LightStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); @@ -1156,6 +1179,7 @@ void LightStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->cold_white != 0.0f, false); ProtoSize::add_fixed_field<4>(total_size, 1, this->warm_white != 0.0f, false); ProtoSize::add_string_field(total_size, 1, this->effect, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -1455,6 +1479,10 @@ bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->missing_state = value.as_bool(); return true; } + case 4: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -1477,11 +1505,13 @@ void SensorStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_float(2, this->state); buffer.encode_bool(3, this->missing_state); + buffer.encode_uint32(4, this->device_id); } void SensorStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); ProtoSize::add_fixed_field<4>(total_size, 1, this->state != 0.0f, false); ProtoSize::add_bool_field(total_size, 1, this->missing_state, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } #endif #ifdef USE_SWITCH @@ -1573,6 +1603,10 @@ bool SwitchStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->state = value.as_bool(); return true; } + case 3: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -1590,10 +1624,12 @@ bool SwitchStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_bool(2, this->state); + buffer.encode_uint32(3, this->device_id); } void SwitchStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); ProtoSize::add_bool_field(total_size, 1, this->state, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } bool SwitchCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -1707,6 +1743,10 @@ bool TextSensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value this->missing_state = value.as_bool(); return true; } + case 4: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -1735,11 +1775,13 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_string(2, this->state); buffer.encode_bool(3, this->missing_state); + buffer.encode_uint32(4, this->device_id); } void TextSensorStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); ProtoSize::add_string_field(total_size, 1, this->state, false); ProtoSize::add_bool_field(total_size, 1, this->missing_state, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } #endif bool SubscribeLogsRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -2549,6 +2591,10 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->preset = value.as_enum(); return true; } + case 16: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -2617,6 +2663,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(13, this->custom_preset); buffer.encode_float(14, this->current_humidity); buffer.encode_float(15, this->target_humidity); + buffer.encode_uint32(16, this->device_id); } void ClimateStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); @@ -2634,6 +2681,7 @@ void ClimateStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_string_field(total_size, 1, this->custom_preset, false); ProtoSize::add_fixed_field<4>(total_size, 1, this->current_humidity != 0.0f, false); ProtoSize::add_fixed_field<4>(total_size, 1, this->target_humidity != 0.0f, false); + ProtoSize::add_uint32_field(total_size, 2, this->device_id, false); } bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -2909,6 +2957,10 @@ bool NumberStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->missing_state = value.as_bool(); return true; } + case 4: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -2931,11 +2983,13 @@ void NumberStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_float(2, this->state); buffer.encode_bool(3, this->missing_state); + buffer.encode_uint32(4, this->device_id); } void NumberStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); ProtoSize::add_fixed_field<4>(total_size, 1, this->state != 0.0f, false); ProtoSize::add_bool_field(total_size, 1, this->missing_state, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } bool NumberCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { switch (field_id) { @@ -3049,6 +3103,10 @@ bool SelectStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->missing_state = value.as_bool(); return true; } + case 4: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -3077,11 +3135,13 @@ void SelectStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_string(2, this->state); buffer.encode_bool(3, this->missing_state); + buffer.encode_uint32(4, this->device_id); } void SelectStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); ProtoSize::add_string_field(total_size, 1, this->state, false); ProtoSize::add_bool_field(total_size, 1, this->missing_state, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } bool SelectCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { @@ -3213,6 +3273,10 @@ bool SirenStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->state = value.as_bool(); return true; } + case 3: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -3230,10 +3294,12 @@ bool SirenStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { void SirenStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_bool(2, this->state); + buffer.encode_uint32(3, this->device_id); } void SirenStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); ProtoSize::add_bool_field(total_size, 1, this->state, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } bool SirenCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -3413,6 +3479,10 @@ bool LockStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->state = value.as_enum(); return true; } + case 3: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -3430,10 +3500,12 @@ bool LockStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { void LockStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_enum(2, this->state); + buffer.encode_uint32(3, this->device_id); } void LockStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); ProtoSize::add_enum_field(total_size, 1, static_cast(this->state), false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } bool LockCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -3715,6 +3787,10 @@ bool MediaPlayerStateResponse::decode_varint(uint32_t field_id, ProtoVarInt valu this->muted = value.as_bool(); return true; } + case 5: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -3738,12 +3814,14 @@ void MediaPlayerStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_enum(2, this->state); buffer.encode_float(3, this->volume); buffer.encode_bool(4, this->muted); + buffer.encode_uint32(5, this->device_id); } void MediaPlayerStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); ProtoSize::add_enum_field(total_size, 1, static_cast(this->state), false); ProtoSize::add_fixed_field<4>(total_size, 1, this->volume != 0.0f, false); ProtoSize::add_bool_field(total_size, 1, this->muted, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } bool MediaPlayerCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -5199,6 +5277,10 @@ bool AlarmControlPanelStateResponse::decode_varint(uint32_t field_id, ProtoVarIn this->state = value.as_enum(); return true; } + case 3: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -5216,10 +5298,12 @@ bool AlarmControlPanelStateResponse::decode_32bit(uint32_t field_id, Proto32Bit void AlarmControlPanelStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_enum(2, this->state); + buffer.encode_uint32(3, this->device_id); } void AlarmControlPanelStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); ProtoSize::add_enum_field(total_size, 1, static_cast(this->state), false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } bool AlarmControlPanelCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -5363,6 +5447,10 @@ bool TextStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->missing_state = value.as_bool(); return true; } + case 4: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -5391,11 +5479,13 @@ void TextStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_string(2, this->state); buffer.encode_bool(3, this->missing_state); + buffer.encode_uint32(4, this->device_id); } void TextStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); ProtoSize::add_string_field(total_size, 1, this->state, false); ProtoSize::add_bool_field(total_size, 1, this->missing_state, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } bool TextCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { @@ -5515,6 +5605,10 @@ bool DateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->day = value.as_uint32(); return true; } + case 6: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -5535,6 +5629,7 @@ void DateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(3, this->year); buffer.encode_uint32(4, this->month); buffer.encode_uint32(5, this->day); + buffer.encode_uint32(6, this->device_id); } void DateStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); @@ -5542,6 +5637,7 @@ void DateStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_uint32_field(total_size, 1, this->year, false); ProtoSize::add_uint32_field(total_size, 1, this->month, false); ProtoSize::add_uint32_field(total_size, 1, this->day, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } bool DateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -5673,6 +5769,10 @@ bool TimeStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->second = value.as_uint32(); return true; } + case 6: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -5693,6 +5793,7 @@ void TimeStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(3, this->hour); buffer.encode_uint32(4, this->minute); buffer.encode_uint32(5, this->second); + buffer.encode_uint32(6, this->device_id); } void TimeStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); @@ -5700,6 +5801,7 @@ void TimeStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_uint32_field(total_size, 1, this->hour, false); ProtoSize::add_uint32_field(total_size, 1, this->minute, false); ProtoSize::add_uint32_field(total_size, 1, this->second, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } bool TimeCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -5831,6 +5933,16 @@ void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const { } ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } +bool EventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 3: { + this->device_id = value.as_uint32(); + return true; + } + default: + return false; + } +} bool EventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: { @@ -5854,10 +5966,12 @@ bool EventResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { void EventResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_string(2, this->event_type); + buffer.encode_uint32(3, this->device_id); } void EventResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); ProtoSize::add_string_field(total_size, 1, this->event_type, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } #endif #ifdef USE_VALVE @@ -5961,6 +6075,10 @@ bool ValveStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->current_operation = value.as_enum(); return true; } + case 4: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -5983,11 +6101,13 @@ void ValveStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_float(2, this->position); buffer.encode_enum(3, this->current_operation); + buffer.encode_uint32(4, this->device_id); } void ValveStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); ProtoSize::add_fixed_field<4>(total_size, 1, this->position != 0.0f, false); ProtoSize::add_enum_field(total_size, 1, static_cast(this->current_operation), false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } bool ValveCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -6107,6 +6227,10 @@ bool DateTimeStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) this->missing_state = value.as_bool(); return true; } + case 4: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -6129,11 +6253,13 @@ void DateTimeStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_bool(2, this->missing_state); buffer.encode_fixed32(3, this->epoch_seconds); + buffer.encode_uint32(4, this->device_id); } void DateTimeStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); ProtoSize::add_bool_field(total_size, 1, this->missing_state, false); ProtoSize::add_fixed_field<4>(total_size, 1, this->epoch_seconds != 0, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } bool DateTimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { switch (field_id) { @@ -6249,6 +6375,10 @@ bool UpdateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->has_progress = value.as_bool(); return true; } + case 11: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -6304,6 +6434,7 @@ void UpdateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(8, this->title); buffer.encode_string(9, this->release_summary); buffer.encode_string(10, this->release_url); + buffer.encode_uint32(11, this->device_id); } void UpdateStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); @@ -6316,6 +6447,7 @@ void UpdateStateResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_string_field(total_size, 1, this->title, false); ProtoSize::add_string_field(total_size, 1, this->release_summary, false); ProtoSize::add_string_field(total_size, 1, this->release_url, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } bool UpdateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 24b0e891c9..c0079bd29c 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -303,6 +303,7 @@ class StateResponseProtoMessage : public ProtoMessage { public: ~StateResponseProtoMessage() override = default; uint32_t key{0}; + uint32_t device_id{0}; protected: }; @@ -577,7 +578,7 @@ class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage { class BinarySensorStateResponse : public StateResponseProtoMessage { public: 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 const char *message_name() const override { return "binary_sensor_state_response"; } #endif @@ -621,7 +622,7 @@ class ListEntitiesCoverResponse : public InfoResponseProtoMessage { class CoverStateResponse : public StateResponseProtoMessage { public: 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 const char *message_name() const override { return "cover_state_response"; } #endif @@ -692,7 +693,7 @@ class ListEntitiesFanResponse : public InfoResponseProtoMessage { class FanStateResponse : public StateResponseProtoMessage { public: 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 const char *message_name() const override { return "fan_state_response"; } #endif @@ -775,7 +776,7 @@ class ListEntitiesLightResponse : public InfoResponseProtoMessage { class LightStateResponse : public StateResponseProtoMessage { public: 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 const char *message_name() const override { return "light_state_response"; } #endif @@ -876,7 +877,7 @@ class ListEntitiesSensorResponse : public InfoResponseProtoMessage { class SensorStateResponse : public StateResponseProtoMessage { public: 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 const char *message_name() const override { return "sensor_state_response"; } #endif @@ -917,7 +918,7 @@ class ListEntitiesSwitchResponse : public InfoResponseProtoMessage { class SwitchStateResponse : public StateResponseProtoMessage { public: 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 const char *message_name() const override { return "switch_state_response"; } #endif @@ -975,7 +976,7 @@ class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage { class TextSensorStateResponse : public StateResponseProtoMessage { public: 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 const char *message_name() const override { return "text_sensor_state_response"; } #endif @@ -1371,7 +1372,7 @@ class ListEntitiesClimateResponse : public InfoResponseProtoMessage { class ClimateStateResponse : public StateResponseProtoMessage { public: 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 const char *message_name() const override { return "climate_state_response"; } #endif @@ -1470,7 +1471,7 @@ class ListEntitiesNumberResponse : public InfoResponseProtoMessage { class NumberStateResponse : public StateResponseProtoMessage { public: 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 const char *message_name() const override { return "number_state_response"; } #endif @@ -1528,7 +1529,7 @@ class ListEntitiesSelectResponse : public InfoResponseProtoMessage { class SelectStateResponse : public StateResponseProtoMessage { public: 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 const char *message_name() const override { return "select_state_response"; } #endif @@ -1590,7 +1591,7 @@ class ListEntitiesSirenResponse : public InfoResponseProtoMessage { class SirenStateResponse : public StateResponseProtoMessage { public: 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 const char *message_name() const override { return "siren_state_response"; } #endif @@ -1659,7 +1660,7 @@ class ListEntitiesLockResponse : public InfoResponseProtoMessage { class LockStateResponse : public StateResponseProtoMessage { public: 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 const char *message_name() const override { return "lock_state_response"; } #endif @@ -1776,7 +1777,7 @@ class ListEntitiesMediaPlayerResponse : public InfoResponseProtoMessage { class MediaPlayerStateResponse : public StateResponseProtoMessage { public: 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 const char *message_name() const override { return "media_player_state_response"; } #endif @@ -2653,7 +2654,7 @@ class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage { class AlarmControlPanelStateResponse : public StateResponseProtoMessage { public: 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 const char *message_name() const override { return "alarm_control_panel_state_response"; } #endif @@ -2716,7 +2717,7 @@ class ListEntitiesTextResponse : public InfoResponseProtoMessage { class TextStateResponse : public StateResponseProtoMessage { public: 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 const char *message_name() const override { return "text_state_response"; } #endif @@ -2775,7 +2776,7 @@ class ListEntitiesDateResponse : public InfoResponseProtoMessage { class DateStateResponse : public StateResponseProtoMessage { public: 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 const char *message_name() const override { return "date_state_response"; } #endif @@ -2837,7 +2838,7 @@ class ListEntitiesTimeResponse : public InfoResponseProtoMessage { class TimeStateResponse : public StateResponseProtoMessage { public: 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 const char *message_name() const override { return "time_state_response"; } #endif @@ -2901,7 +2902,7 @@ class ListEntitiesEventResponse : public InfoResponseProtoMessage { class EventResponse : public StateResponseProtoMessage { public: 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 const char *message_name() const override { return "event_response"; } #endif @@ -2915,6 +2916,7 @@ class EventResponse : public StateResponseProtoMessage { protected: bool decode_32bit(uint32_t field_id, Proto32Bit 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 @@ -2943,7 +2945,7 @@ class ListEntitiesValveResponse : public InfoResponseProtoMessage { class ValveStateResponse : public StateResponseProtoMessage { public: 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 const char *message_name() const override { return "valve_state_response"; } #endif @@ -3003,7 +3005,7 @@ class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage { class DateTimeStateResponse : public StateResponseProtoMessage { public: 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 const char *message_name() const override { return "date_time_state_response"; } #endif @@ -3061,7 +3063,7 @@ class ListEntitiesUpdateResponse : public InfoResponseProtoMessage { class UpdateStateResponse : public StateResponseProtoMessage { public: 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 const char *message_name() const override { return "update_state_response"; } #endif diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 6658fd754b..db330a17fb 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -850,6 +850,11 @@ void BinarySensorStateResponse::dump_to(std::string &out) const { out.append(" missing_state: "); out.append(YESNO(this->missing_state)); out.append("\n"); + + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); + out.append(buffer); + out.append("\n"); out.append("}"); } #endif @@ -937,6 +942,11 @@ void CoverStateResponse::dump_to(std::string &out) const { out.append(" current_operation: "); out.append(proto_enum_to_string(this->current_operation)); out.append("\n"); + + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); + out.append(buffer); + out.append("\n"); out.append("}"); } void CoverCommandRequest::dump_to(std::string &out) const { @@ -1073,6 +1083,11 @@ void FanStateResponse::dump_to(std::string &out) const { out.append(" preset_mode: "); out.append("'").append(this->preset_mode).append("'"); out.append("\n"); + + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); + out.append(buffer); + out.append("\n"); out.append("}"); } void FanCommandRequest::dump_to(std::string &out) const { @@ -1275,6 +1290,11 @@ void LightStateResponse::dump_to(std::string &out) const { out.append(" effect: "); out.append("'").append(this->effect).append("'"); out.append("\n"); + + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); + out.append(buffer); + out.append("\n"); out.append("}"); } void LightCommandRequest::dump_to(std::string &out) const { @@ -1482,6 +1502,11 @@ void SensorStateResponse::dump_to(std::string &out) const { out.append(" missing_state: "); out.append(YESNO(this->missing_state)); out.append("\n"); + + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); + out.append(buffer); + out.append("\n"); out.append("}"); } #endif @@ -1543,6 +1568,11 @@ void SwitchStateResponse::dump_to(std::string &out) const { out.append(" state: "); out.append(YESNO(this->state)); out.append("\n"); + + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); + out.append(buffer); + out.append("\n"); out.append("}"); } void SwitchCommandRequest::dump_to(std::string &out) const { @@ -1617,6 +1647,11 @@ void TextSensorStateResponse::dump_to(std::string &out) const { out.append(" missing_state: "); out.append(YESNO(this->missing_state)); out.append("\n"); + + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); + out.append(buffer); + out.append("\n"); out.append("}"); } #endif @@ -2122,6 +2157,11 @@ void ClimateStateResponse::dump_to(std::string &out) const { sprintf(buffer, "%g", this->target_humidity); out.append(buffer); out.append("\n"); + + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); + out.append(buffer); + out.append("\n"); out.append("}"); } void ClimateCommandRequest::dump_to(std::string &out) const { @@ -2308,6 +2348,11 @@ void NumberStateResponse::dump_to(std::string &out) const { out.append(" missing_state: "); out.append(YESNO(this->missing_state)); out.append("\n"); + + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); + out.append(buffer); + out.append("\n"); out.append("}"); } void NumberCommandRequest::dump_to(std::string &out) const { @@ -2385,6 +2430,11 @@ void SelectStateResponse::dump_to(std::string &out) const { out.append(" missing_state: "); out.append(YESNO(this->missing_state)); out.append("\n"); + + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); + out.append(buffer); + out.append("\n"); out.append("}"); } void SelectCommandRequest::dump_to(std::string &out) const { @@ -2465,6 +2515,11 @@ void SirenStateResponse::dump_to(std::string &out) const { out.append(" state: "); out.append(YESNO(this->state)); out.append("\n"); + + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); + out.append(buffer); + out.append("\n"); out.append("}"); } void SirenCommandRequest::dump_to(std::string &out) const { @@ -2577,6 +2632,11 @@ void LockStateResponse::dump_to(std::string &out) const { out.append(" state: "); out.append(proto_enum_to_string(this->state)); out.append("\n"); + + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); + out.append(buffer); + out.append("\n"); out.append("}"); } void LockCommandRequest::dump_to(std::string &out) const { @@ -2750,6 +2810,11 @@ void MediaPlayerStateResponse::dump_to(std::string &out) const { out.append(" muted: "); out.append(YESNO(this->muted)); out.append("\n"); + + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); + out.append(buffer); + out.append("\n"); out.append("}"); } void MediaPlayerCommandRequest::dump_to(std::string &out) const { @@ -3595,6 +3660,11 @@ void AlarmControlPanelStateResponse::dump_to(std::string &out) const { out.append(" state: "); out.append(proto_enum_to_string(this->state)); out.append("\n"); + + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); + out.append(buffer); + out.append("\n"); out.append("}"); } void AlarmControlPanelCommandRequest::dump_to(std::string &out) const { @@ -3687,6 +3757,11 @@ void TextStateResponse::dump_to(std::string &out) const { out.append(" missing_state: "); out.append(YESNO(this->missing_state)); out.append("\n"); + + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); + out.append(buffer); + out.append("\n"); out.append("}"); } void TextCommandRequest::dump_to(std::string &out) const { @@ -3768,6 +3843,11 @@ void DateStateResponse::dump_to(std::string &out) const { sprintf(buffer, "%" PRIu32, this->day); out.append(buffer); out.append("\n"); + + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); + out.append(buffer); + out.append("\n"); out.append("}"); } void DateCommandRequest::dump_to(std::string &out) const { @@ -3860,6 +3940,11 @@ void TimeStateResponse::dump_to(std::string &out) const { sprintf(buffer, "%" PRIu32, this->second); out.append(buffer); out.append("\n"); + + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); + out.append(buffer); + out.append("\n"); out.append("}"); } void TimeCommandRequest::dump_to(std::string &out) const { @@ -3947,6 +4032,11 @@ void EventResponse::dump_to(std::string &out) const { out.append(" event_type: "); out.append("'").append(this->event_type).append("'"); out.append("\n"); + + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); + out.append(buffer); + out.append("\n"); out.append("}"); } #endif @@ -4021,6 +4111,11 @@ void ValveStateResponse::dump_to(std::string &out) const { out.append(" current_operation: "); out.append(proto_enum_to_string(this->current_operation)); out.append("\n"); + + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); + out.append(buffer); + out.append("\n"); out.append("}"); } void ValveCommandRequest::dump_to(std::string &out) const { @@ -4101,6 +4196,11 @@ void DateTimeStateResponse::dump_to(std::string &out) const { sprintf(buffer, "%" PRIu32, this->epoch_seconds); out.append(buffer); out.append("\n"); + + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); + out.append(buffer); + out.append("\n"); out.append("}"); } void DateTimeCommandRequest::dump_to(std::string &out) const { @@ -4205,6 +4305,11 @@ void UpdateStateResponse::dump_to(std::string &out) const { out.append(" release_url: "); out.append("'").append(this->release_url).append("'"); out.append("\n"); + + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); + out.append(buffer); + out.append("\n"); out.append("}"); } void UpdateCommandRequest::dump_to(std::string &out) const { diff --git a/tests/integration/fixtures/device_id_in_state.yaml b/tests/integration/fixtures/device_id_in_state.yaml new file mode 100644 index 0000000000..f2e320a2e2 --- /dev/null +++ b/tests/integration/fixtures/device_id_in_state.yaml @@ -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); + diff --git a/tests/integration/test_device_id_in_state.py b/tests/integration/test_device_id_in_state.py new file mode 100644 index 0000000000..3c5181595f --- /dev/null +++ b/tests/integration/test_device_id_in_state.py @@ -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}" + )