[API] Sub devices and areas (#8544)

Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
This commit is contained in:
DanielV 2025-06-25 14:03:41 +02:00 committed by GitHub
parent 7c28134214
commit b18ff48b4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
79 changed files with 2756 additions and 186 deletions

View File

@ -14,8 +14,8 @@ from esphome.const import (
CONF_WEB_SERVER, CONF_WEB_SERVER,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@grahambrown11", "@hwstar"] CODEOWNERS = ["@grahambrown11", "@hwstar"]
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
@ -149,6 +149,9 @@ _ALARM_CONTROL_PANEL_SCHEMA = (
) )
_ALARM_CONTROL_PANEL_SCHEMA.add_extra(entity_duplicate_validator("alarm_control_panel"))
def alarm_control_panel_schema( def alarm_control_panel_schema(
class_: MockObjClass, class_: MockObjClass,
*, *,
@ -190,7 +193,7 @@ ALARM_CONTROL_PANEL_CONDITION_SCHEMA = maybe_simple_id(
async def setup_alarm_control_panel_core_(var, config): async def setup_alarm_control_panel_core_(var, config):
await setup_entity(var, config) await setup_entity(var, config, "alarm_control_panel")
for conf in config.get(CONF_ON_STATE, []): for conf in config.get(CONF_ON_STATE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) await automation.build_automation(trigger, [], conf)

View File

@ -188,6 +188,17 @@ message DeviceInfoRequest {
// Empty // Empty
} }
message AreaInfo {
uint32 area_id = 1;
string name = 2;
}
message DeviceInfo {
uint32 device_id = 1;
string name = 2;
uint32 area_id = 3;
}
message DeviceInfoResponse { message DeviceInfoResponse {
option (id) = 10; option (id) = 10;
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
@ -236,6 +247,12 @@ message DeviceInfoResponse {
// Supports receiving and saving api encryption key // Supports receiving and saving api encryption key
bool api_encryption_supported = 19; bool api_encryption_supported = 19;
repeated DeviceInfo devices = 20;
repeated AreaInfo areas = 21;
// Top-level area info to phase out suggested_area
AreaInfo area = 22;
} }
message ListEntitiesRequest { message ListEntitiesRequest {
@ -280,6 +297,7 @@ message ListEntitiesBinarySensorResponse {
bool disabled_by_default = 7; bool disabled_by_default = 7;
string icon = 8; string icon = 8;
EntityCategory entity_category = 9; EntityCategory entity_category = 9;
uint32 device_id = 10;
} }
message BinarySensorStateResponse { message BinarySensorStateResponse {
option (id) = 21; option (id) = 21;
@ -315,6 +333,7 @@ message ListEntitiesCoverResponse {
string icon = 10; string icon = 10;
EntityCategory entity_category = 11; EntityCategory entity_category = 11;
bool supports_stop = 12; bool supports_stop = 12;
uint32 device_id = 13;
} }
enum LegacyCoverState { enum LegacyCoverState {
@ -388,6 +407,7 @@ message ListEntitiesFanResponse {
string icon = 10; string icon = 10;
EntityCategory entity_category = 11; EntityCategory entity_category = 11;
repeated string supported_preset_modes = 12; repeated string supported_preset_modes = 12;
uint32 device_id = 13;
} }
enum FanSpeed { enum FanSpeed {
FAN_SPEED_LOW = 0; FAN_SPEED_LOW = 0;
@ -471,6 +491,7 @@ message ListEntitiesLightResponse {
bool disabled_by_default = 13; bool disabled_by_default = 13;
string icon = 14; string icon = 14;
EntityCategory entity_category = 15; EntityCategory entity_category = 15;
uint32 device_id = 16;
} }
message LightStateResponse { message LightStateResponse {
option (id) = 24; option (id) = 24;
@ -563,6 +584,7 @@ message ListEntitiesSensorResponse {
SensorLastResetType legacy_last_reset_type = 11; SensorLastResetType legacy_last_reset_type = 11;
bool disabled_by_default = 12; bool disabled_by_default = 12;
EntityCategory entity_category = 13; EntityCategory entity_category = 13;
uint32 device_id = 14;
} }
message SensorStateResponse { message SensorStateResponse {
option (id) = 25; option (id) = 25;
@ -595,6 +617,7 @@ message ListEntitiesSwitchResponse {
bool disabled_by_default = 7; bool disabled_by_default = 7;
EntityCategory entity_category = 8; EntityCategory entity_category = 8;
string device_class = 9; string device_class = 9;
uint32 device_id = 10;
} }
message SwitchStateResponse { message SwitchStateResponse {
option (id) = 26; option (id) = 26;
@ -632,6 +655,7 @@ message ListEntitiesTextSensorResponse {
bool disabled_by_default = 6; bool disabled_by_default = 6;
EntityCategory entity_category = 7; EntityCategory entity_category = 7;
string device_class = 8; string device_class = 8;
uint32 device_id = 9;
} }
message TextSensorStateResponse { message TextSensorStateResponse {
option (id) = 27; option (id) = 27;
@ -814,6 +838,7 @@ message ListEntitiesCameraResponse {
bool disabled_by_default = 5; bool disabled_by_default = 5;
string icon = 6; string icon = 6;
EntityCategory entity_category = 7; EntityCategory entity_category = 7;
uint32 device_id = 8;
} }
message CameraImageResponse { message CameraImageResponse {
@ -916,6 +941,7 @@ message ListEntitiesClimateResponse {
bool supports_target_humidity = 23; bool supports_target_humidity = 23;
float visual_min_humidity = 24; float visual_min_humidity = 24;
float visual_max_humidity = 25; float visual_max_humidity = 25;
uint32 device_id = 26;
} }
message ClimateStateResponse { message ClimateStateResponse {
option (id) = 47; option (id) = 47;
@ -999,6 +1025,7 @@ message ListEntitiesNumberResponse {
string unit_of_measurement = 11; string unit_of_measurement = 11;
NumberMode mode = 12; NumberMode mode = 12;
string device_class = 13; string device_class = 13;
uint32 device_id = 14;
} }
message NumberStateResponse { message NumberStateResponse {
option (id) = 50; option (id) = 50;
@ -1039,6 +1066,7 @@ message ListEntitiesSelectResponse {
repeated string options = 6; repeated string options = 6;
bool disabled_by_default = 7; bool disabled_by_default = 7;
EntityCategory entity_category = 8; EntityCategory entity_category = 8;
uint32 device_id = 9;
} }
message SelectStateResponse { message SelectStateResponse {
option (id) = 53; option (id) = 53;
@ -1081,6 +1109,7 @@ message ListEntitiesSirenResponse {
bool supports_duration = 8; bool supports_duration = 8;
bool supports_volume = 9; bool supports_volume = 9;
EntityCategory entity_category = 10; EntityCategory entity_category = 10;
uint32 device_id = 11;
} }
message SirenStateResponse { message SirenStateResponse {
option (id) = 56; option (id) = 56;
@ -1144,6 +1173,7 @@ message ListEntitiesLockResponse {
// Not yet implemented: // Not yet implemented:
string code_format = 11; string code_format = 11;
uint32 device_id = 12;
} }
message LockStateResponse { message LockStateResponse {
option (id) = 59; option (id) = 59;
@ -1183,6 +1213,7 @@ message ListEntitiesButtonResponse {
bool disabled_by_default = 6; bool disabled_by_default = 6;
EntityCategory entity_category = 7; EntityCategory entity_category = 7;
string device_class = 8; string device_class = 8;
uint32 device_id = 9;
} }
message ButtonCommandRequest { message ButtonCommandRequest {
option (id) = 62; option (id) = 62;
@ -1238,6 +1269,8 @@ message ListEntitiesMediaPlayerResponse {
bool supports_pause = 8; bool supports_pause = 8;
repeated MediaPlayerSupportedFormat supported_formats = 9; repeated MediaPlayerSupportedFormat supported_formats = 9;
uint32 device_id = 10;
} }
message MediaPlayerStateResponse { message MediaPlayerStateResponse {
option (id) = 64; option (id) = 64;
@ -1778,6 +1811,7 @@ message ListEntitiesAlarmControlPanelResponse {
uint32 supported_features = 8; uint32 supported_features = 8;
bool requires_code = 9; bool requires_code = 9;
bool requires_code_to_arm = 10; bool requires_code_to_arm = 10;
uint32 device_id = 11;
} }
message AlarmControlPanelStateResponse { message AlarmControlPanelStateResponse {
@ -1823,6 +1857,7 @@ message ListEntitiesTextResponse {
uint32 max_length = 9; uint32 max_length = 9;
string pattern = 10; string pattern = 10;
TextMode mode = 11; TextMode mode = 11;
uint32 device_id = 12;
} }
message TextStateResponse { message TextStateResponse {
option (id) = 98; option (id) = 98;
@ -1863,6 +1898,7 @@ message ListEntitiesDateResponse {
string icon = 5; string icon = 5;
bool disabled_by_default = 6; bool disabled_by_default = 6;
EntityCategory entity_category = 7; EntityCategory entity_category = 7;
uint32 device_id = 8;
} }
message DateStateResponse { message DateStateResponse {
option (id) = 101; option (id) = 101;
@ -1906,6 +1942,7 @@ message ListEntitiesTimeResponse {
string icon = 5; string icon = 5;
bool disabled_by_default = 6; bool disabled_by_default = 6;
EntityCategory entity_category = 7; EntityCategory entity_category = 7;
uint32 device_id = 8;
} }
message TimeStateResponse { message TimeStateResponse {
option (id) = 104; option (id) = 104;
@ -1952,6 +1989,7 @@ message ListEntitiesEventResponse {
string device_class = 8; string device_class = 8;
repeated string event_types = 9; repeated string event_types = 9;
uint32 device_id = 10;
} }
message EventResponse { message EventResponse {
option (id) = 108; option (id) = 108;
@ -1983,6 +2021,7 @@ message ListEntitiesValveResponse {
bool assumed_state = 9; bool assumed_state = 9;
bool supports_position = 10; bool supports_position = 10;
bool supports_stop = 11; bool supports_stop = 11;
uint32 device_id = 12;
} }
enum ValveOperation { enum ValveOperation {
@ -2029,6 +2068,7 @@ message ListEntitiesDateTimeResponse {
string icon = 5; string icon = 5;
bool disabled_by_default = 6; bool disabled_by_default = 6;
EntityCategory entity_category = 7; EntityCategory entity_category = 7;
uint32 device_id = 8;
} }
message DateTimeStateResponse { message DateTimeStateResponse {
option (id) = 113; option (id) = 113;
@ -2069,6 +2109,7 @@ message ListEntitiesUpdateResponse {
bool disabled_by_default = 6; bool disabled_by_default = 6;
EntityCategory entity_category = 7; EntityCategory entity_category = 7;
string device_class = 8; string device_class = 8;
uint32 device_id = 9;
} }
message UpdateStateResponse { message UpdateStateResponse {
option (id) = 117; option (id) = 117;

View File

@ -1629,6 +1629,23 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
#endif #endif
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
resp.api_encryption_supported = true; resp.api_encryption_supported = true;
#endif
#ifdef USE_DEVICES
for (auto const &device : App.get_devices()) {
DeviceInfo device_info;
device_info.device_id = device->get_device_id();
device_info.name = device->get_name();
device_info.area_id = device->get_area_id();
resp.devices.push_back(device_info);
}
#endif
#ifdef USE_AREAS
for (auto const &area : App.get_areas()) {
AreaInfo area_info;
area_info.area_id = area->get_area_id();
area_info.name = area->get_name();
resp.areas.push_back(area_info);
}
#endif #endif
return resp; return resp;
} }

View File

@ -301,6 +301,9 @@ class APIConnection : public APIServerConnection {
response.icon = entity->get_icon(); response.icon = entity->get_icon();
response.disabled_by_default = entity->is_disabled_by_default(); response.disabled_by_default = entity->is_disabled_by_default();
response.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category()); response.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category());
#ifdef USE_DEVICES
response.device_id = entity->get_device_id();
#endif
} }
// Helper function to fill common entity state fields // Helper function to fill common entity state fields

View File

@ -812,6 +812,103 @@ void PingResponse::dump_to(std::string &out) const { out.append("PingResponse {}
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfoRequest {}"); } void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfoRequest {}"); }
#endif #endif
bool AreaInfo::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->area_id = value.as_uint32();
return true;
}
default:
return false;
}
}
bool AreaInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
this->name = value.as_string();
return true;
}
default:
return false;
}
}
void AreaInfo::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(1, this->area_id);
buffer.encode_string(2, this->name);
}
void AreaInfo::calculate_size(uint32_t &total_size) const {
ProtoSize::add_uint32_field(total_size, 1, this->area_id, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void AreaInfo::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("AreaInfo {\n");
out.append(" area_id: ");
sprintf(buffer, "%" PRIu32, this->area_id);
out.append(buffer);
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
out.append("\n");
out.append("}");
}
#endif
bool DeviceInfo::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->device_id = value.as_uint32();
return true;
}
case 3: {
this->area_id = value.as_uint32();
return true;
}
default:
return false;
}
}
bool DeviceInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
this->name = value.as_string();
return true;
}
default:
return false;
}
}
void DeviceInfo::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(1, this->device_id);
buffer.encode_string(2, this->name);
buffer.encode_uint32(3, this->area_id);
}
void DeviceInfo::calculate_size(uint32_t &total_size) const {
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_uint32_field(total_size, 1, this->area_id, false);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void DeviceInfo::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("DeviceInfo {\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" area_id: ");
sprintf(buffer, "%" PRIu32, this->area_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) { switch (field_id) {
case 1: { case 1: {
@ -896,6 +993,18 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v
this->bluetooth_mac_address = value.as_string(); this->bluetooth_mac_address = value.as_string();
return true; return true;
} }
case 20: {
this->devices.push_back(value.as_message<DeviceInfo>());
return true;
}
case 21: {
this->areas.push_back(value.as_message<AreaInfo>());
return true;
}
case 22: {
this->area = value.as_message<AreaInfo>();
return true;
}
default: default:
return false; return false;
} }
@ -920,6 +1029,13 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(16, this->suggested_area); buffer.encode_string(16, this->suggested_area);
buffer.encode_string(18, this->bluetooth_mac_address); buffer.encode_string(18, this->bluetooth_mac_address);
buffer.encode_bool(19, this->api_encryption_supported); buffer.encode_bool(19, this->api_encryption_supported);
for (auto &it : this->devices) {
buffer.encode_message<DeviceInfo>(20, it, true);
}
for (auto &it : this->areas) {
buffer.encode_message<AreaInfo>(21, it, true);
}
buffer.encode_message<AreaInfo>(22, this->area);
} }
void DeviceInfoResponse::calculate_size(uint32_t &total_size) const { void DeviceInfoResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_bool_field(total_size, 1, this->uses_password, false); ProtoSize::add_bool_field(total_size, 1, this->uses_password, false);
@ -941,6 +1057,9 @@ void DeviceInfoResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 2, this->suggested_area, false); ProtoSize::add_string_field(total_size, 2, this->suggested_area, false);
ProtoSize::add_string_field(total_size, 2, this->bluetooth_mac_address, false); ProtoSize::add_string_field(total_size, 2, this->bluetooth_mac_address, false);
ProtoSize::add_bool_field(total_size, 2, this->api_encryption_supported, false); ProtoSize::add_bool_field(total_size, 2, this->api_encryption_supported, false);
ProtoSize::add_repeated_message(total_size, 2, this->devices);
ProtoSize::add_repeated_message(total_size, 2, this->areas);
ProtoSize::add_message_object(total_size, 2, this->area, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void DeviceInfoResponse::dump_to(std::string &out) const { void DeviceInfoResponse::dump_to(std::string &out) const {
@ -1026,6 +1145,22 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
out.append(" api_encryption_supported: "); out.append(" api_encryption_supported: ");
out.append(YESNO(this->api_encryption_supported)); out.append(YESNO(this->api_encryption_supported));
out.append("\n"); out.append("\n");
for (const auto &it : this->devices) {
out.append(" devices: ");
it.dump_to(out);
out.append("\n");
}
for (const auto &it : this->areas) {
out.append(" areas: ");
it.dump_to(out);
out.append("\n");
}
out.append(" area: ");
this->area.dump_to(out);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -1052,6 +1187,10 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar
this->entity_category = value.as_enum<enums::EntityCategory>(); this->entity_category = value.as_enum<enums::EntityCategory>();
return true; return true;
} }
case 10: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -1102,6 +1241,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(7, this->disabled_by_default); buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_string(8, this->icon); buffer.encode_string(8, this->icon);
buffer.encode_enum<enums::EntityCategory>(9, this->entity_category); buffer.encode_enum<enums::EntityCategory>(9, this->entity_category);
buffer.encode_uint32(10, this->device_id);
} }
void ListEntitiesBinarySensorResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesBinarySensorResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -1113,6 +1253,7 @@ void ListEntitiesBinarySensorResponse::calculate_size(uint32_t &total_size) cons
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false); ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
@ -1154,6 +1295,11 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
out.append(" entity_category: "); out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n"); out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -1236,6 +1382,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->supports_stop = value.as_bool(); this->supports_stop = value.as_bool();
return true; return true;
} }
case 13: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -1289,6 +1439,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(10, this->icon); buffer.encode_string(10, this->icon);
buffer.encode_enum<enums::EntityCategory>(11, this->entity_category); buffer.encode_enum<enums::EntityCategory>(11, this->entity_category);
buffer.encode_bool(12, this->supports_stop); buffer.encode_bool(12, this->supports_stop);
buffer.encode_uint32(13, this->device_id);
} }
void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -1303,6 +1454,7 @@ void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->icon, false); ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false); ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false);
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesCoverResponse::dump_to(std::string &out) const { void ListEntitiesCoverResponse::dump_to(std::string &out) const {
@ -1356,6 +1508,11 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const {
out.append(" supports_stop: "); out.append(" supports_stop: ");
out.append(YESNO(this->supports_stop)); out.append(YESNO(this->supports_stop));
out.append("\n"); out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -1565,6 +1722,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value
this->entity_category = value.as_enum<enums::EntityCategory>(); this->entity_category = value.as_enum<enums::EntityCategory>();
return true; return true;
} }
case 13: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -1620,6 +1781,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
for (auto &it : this->supported_preset_modes) { for (auto &it : this->supported_preset_modes) {
buffer.encode_string(12, it, true); buffer.encode_string(12, it, true);
} }
buffer.encode_uint32(13, this->device_id);
} }
void ListEntitiesFanResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesFanResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -1638,6 +1800,7 @@ void ListEntitiesFanResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, it, true); ProtoSize::add_string_field(total_size, 1, it, true);
} }
} }
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesFanResponse::dump_to(std::string &out) const { void ListEntitiesFanResponse::dump_to(std::string &out) const {
@ -1694,6 +1857,11 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
out.append("'").append(it).append("'"); out.append("'").append(it).append("'");
out.append("\n"); out.append("\n");
} }
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -1987,6 +2155,10 @@ bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->entity_category = value.as_enum<enums::EntityCategory>(); this->entity_category = value.as_enum<enums::EntityCategory>();
return true; return true;
} }
case 16: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -2055,6 +2227,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(13, this->disabled_by_default); buffer.encode_bool(13, this->disabled_by_default);
buffer.encode_string(14, this->icon); buffer.encode_string(14, this->icon);
buffer.encode_enum<enums::EntityCategory>(15, this->entity_category); buffer.encode_enum<enums::EntityCategory>(15, this->entity_category);
buffer.encode_uint32(16, this->device_id);
} }
void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -2080,6 +2253,7 @@ void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false); ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
ProtoSize::add_uint32_field(total_size, 2, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesLightResponse::dump_to(std::string &out) const { void ListEntitiesLightResponse::dump_to(std::string &out) const {
@ -2151,6 +2325,11 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
out.append(" entity_category: "); out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n"); out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -2658,6 +2837,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->entity_category = value.as_enum<enums::EntityCategory>(); this->entity_category = value.as_enum<enums::EntityCategory>();
return true; return true;
} }
case 14: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -2716,6 +2899,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_enum<enums::SensorLastResetType>(11, this->legacy_last_reset_type); buffer.encode_enum<enums::SensorLastResetType>(11, this->legacy_last_reset_type);
buffer.encode_bool(12, this->disabled_by_default); buffer.encode_bool(12, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(13, this->entity_category); buffer.encode_enum<enums::EntityCategory>(13, this->entity_category);
buffer.encode_uint32(14, this->device_id);
} }
void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -2731,6 +2915,7 @@ void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->legacy_last_reset_type), false); ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->legacy_last_reset_type), false);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSensorResponse::dump_to(std::string &out) const { void ListEntitiesSensorResponse::dump_to(std::string &out) const {
@ -2789,6 +2974,11 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
out.append(" entity_category: "); out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n"); out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -2860,6 +3050,10 @@ bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->entity_category = value.as_enum<enums::EntityCategory>(); this->entity_category = value.as_enum<enums::EntityCategory>();
return true; return true;
} }
case 10: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -2910,6 +3104,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(7, this->disabled_by_default); buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(8, this->entity_category); buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
buffer.encode_string(9, this->device_class); buffer.encode_string(9, this->device_class);
buffer.encode_uint32(10, this->device_id);
} }
void ListEntitiesSwitchResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesSwitchResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -2921,6 +3116,7 @@ void ListEntitiesSwitchResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
ProtoSize::add_string_field(total_size, 1, this->device_class, false); ProtoSize::add_string_field(total_size, 1, this->device_class, false);
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSwitchResponse::dump_to(std::string &out) const { void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
@ -2962,6 +3158,11 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
out.append(" device_class: "); out.append(" device_class: ");
out.append("'").append(this->device_class).append("'"); out.append("'").append(this->device_class).append("'");
out.append("\n"); out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -3061,6 +3262,10 @@ bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarIn
this->entity_category = value.as_enum<enums::EntityCategory>(); this->entity_category = value.as_enum<enums::EntityCategory>();
return true; return true;
} }
case 9: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -3110,6 +3315,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(6, this->disabled_by_default); buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_string(8, this->device_class); buffer.encode_string(8, this->device_class);
buffer.encode_uint32(9, this->device_id);
} }
void ListEntitiesTextSensorResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesTextSensorResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -3120,6 +3326,7 @@ void ListEntitiesTextSensorResponse::calculate_size(uint32_t &total_size) const
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
ProtoSize::add_string_field(total_size, 1, this->device_class, false); ProtoSize::add_string_field(total_size, 1, this->device_class, false);
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
@ -3157,6 +3364,11 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
out.append(" device_class: "); out.append(" device_class: ");
out.append("'").append(this->device_class).append("'"); out.append("'").append(this->device_class).append("'");
out.append("\n"); out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -3922,6 +4134,10 @@ bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->entity_category = value.as_enum<enums::EntityCategory>(); this->entity_category = value.as_enum<enums::EntityCategory>();
return true; return true;
} }
case 8: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -3966,6 +4182,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(5, this->disabled_by_default); buffer.encode_bool(5, this->disabled_by_default);
buffer.encode_string(6, this->icon); buffer.encode_string(6, this->icon);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_uint32(8, this->device_id);
} }
void ListEntitiesCameraResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesCameraResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -3975,6 +4192,7 @@ void ListEntitiesCameraResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false); ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesCameraResponse::dump_to(std::string &out) const { void ListEntitiesCameraResponse::dump_to(std::string &out) const {
@ -4008,6 +4226,11 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const {
out.append(" entity_category: "); out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n"); out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -4156,6 +4379,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
this->supports_target_humidity = value.as_bool(); this->supports_target_humidity = value.as_bool();
return true; return true;
} }
case 26: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -4262,6 +4489,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(23, this->supports_target_humidity); buffer.encode_bool(23, this->supports_target_humidity);
buffer.encode_float(24, this->visual_min_humidity); buffer.encode_float(24, this->visual_min_humidity);
buffer.encode_float(25, this->visual_max_humidity); buffer.encode_float(25, this->visual_max_humidity);
buffer.encode_uint32(26, this->device_id);
} }
void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -4313,6 +4541,7 @@ void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_bool_field(total_size, 2, this->supports_target_humidity, false); ProtoSize::add_bool_field(total_size, 2, this->supports_target_humidity, false);
ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_min_humidity != 0.0f, false); ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_min_humidity != 0.0f, false);
ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_max_humidity != 0.0f, false); ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_max_humidity != 0.0f, false);
ProtoSize::add_uint32_field(total_size, 2, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesClimateResponse::dump_to(std::string &out) const { void ListEntitiesClimateResponse::dump_to(std::string &out) const {
@ -4436,6 +4665,11 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
sprintf(buffer, "%g", this->visual_max_humidity); sprintf(buffer, "%g", this->visual_max_humidity);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -4901,6 +5135,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->mode = value.as_enum<enums::NumberMode>(); this->mode = value.as_enum<enums::NumberMode>();
return true; return true;
} }
case 14: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -4971,6 +5209,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(11, this->unit_of_measurement); buffer.encode_string(11, this->unit_of_measurement);
buffer.encode_enum<enums::NumberMode>(12, this->mode); buffer.encode_enum<enums::NumberMode>(12, this->mode);
buffer.encode_string(13, this->device_class); buffer.encode_string(13, this->device_class);
buffer.encode_uint32(14, this->device_id);
} }
void ListEntitiesNumberResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesNumberResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -4986,6 +5225,7 @@ void ListEntitiesNumberResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->unit_of_measurement, false); ProtoSize::add_string_field(total_size, 1, this->unit_of_measurement, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->mode), false); ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->mode), false);
ProtoSize::add_string_field(total_size, 1, this->device_class, false); ProtoSize::add_string_field(total_size, 1, this->device_class, false);
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesNumberResponse::dump_to(std::string &out) const { void ListEntitiesNumberResponse::dump_to(std::string &out) const {
@ -5046,6 +5286,11 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const {
out.append(" device_class: "); out.append(" device_class: ");
out.append("'").append(this->device_class).append("'"); out.append("'").append(this->device_class).append("'");
out.append("\n"); out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -5151,6 +5396,10 @@ bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->entity_category = value.as_enum<enums::EntityCategory>(); this->entity_category = value.as_enum<enums::EntityCategory>();
return true; return true;
} }
case 9: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -5202,6 +5451,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const {
} }
buffer.encode_bool(7, this->disabled_by_default); buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(8, this->entity_category); buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
buffer.encode_uint32(9, this->device_id);
} }
void ListEntitiesSelectResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesSelectResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -5216,6 +5466,7 @@ void ListEntitiesSelectResponse::calculate_size(uint32_t &total_size) const {
} }
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSelectResponse::dump_to(std::string &out) const { void ListEntitiesSelectResponse::dump_to(std::string &out) const {
@ -5255,6 +5506,11 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const {
out.append(" entity_category: "); out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n"); out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -5378,6 +5634,10 @@ bool ListEntitiesSirenResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->entity_category = value.as_enum<enums::EntityCategory>(); this->entity_category = value.as_enum<enums::EntityCategory>();
return true; return true;
} }
case 11: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -5431,6 +5691,7 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(8, this->supports_duration); buffer.encode_bool(8, this->supports_duration);
buffer.encode_bool(9, this->supports_volume); buffer.encode_bool(9, this->supports_volume);
buffer.encode_enum<enums::EntityCategory>(10, this->entity_category); buffer.encode_enum<enums::EntityCategory>(10, this->entity_category);
buffer.encode_uint32(11, this->device_id);
} }
void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -5447,6 +5708,7 @@ void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_bool_field(total_size, 1, this->supports_duration, false); ProtoSize::add_bool_field(total_size, 1, this->supports_duration, false);
ProtoSize::add_bool_field(total_size, 1, this->supports_volume, false); ProtoSize::add_bool_field(total_size, 1, this->supports_volume, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSirenResponse::dump_to(std::string &out) const { void ListEntitiesSirenResponse::dump_to(std::string &out) const {
@ -5494,6 +5756,11 @@ void ListEntitiesSirenResponse::dump_to(std::string &out) const {
out.append(" entity_category: "); out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n"); out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -5683,6 +5950,10 @@ bool ListEntitiesLockResponse::decode_varint(uint32_t field_id, ProtoVarInt valu
this->requires_code = value.as_bool(); this->requires_code = value.as_bool();
return true; return true;
} }
case 12: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -5735,6 +6006,7 @@ void ListEntitiesLockResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(9, this->supports_open); buffer.encode_bool(9, this->supports_open);
buffer.encode_bool(10, this->requires_code); buffer.encode_bool(10, this->requires_code);
buffer.encode_string(11, this->code_format); buffer.encode_string(11, this->code_format);
buffer.encode_uint32(12, this->device_id);
} }
void ListEntitiesLockResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesLockResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -5748,6 +6020,7 @@ void ListEntitiesLockResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_bool_field(total_size, 1, this->supports_open, false); ProtoSize::add_bool_field(total_size, 1, this->supports_open, false);
ProtoSize::add_bool_field(total_size, 1, this->requires_code, false); ProtoSize::add_bool_field(total_size, 1, this->requires_code, false);
ProtoSize::add_string_field(total_size, 1, this->code_format, false); ProtoSize::add_string_field(total_size, 1, this->code_format, false);
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesLockResponse::dump_to(std::string &out) const { void ListEntitiesLockResponse::dump_to(std::string &out) const {
@ -5797,6 +6070,11 @@ void ListEntitiesLockResponse::dump_to(std::string &out) const {
out.append(" code_format: "); out.append(" code_format: ");
out.append("'").append(this->code_format).append("'"); out.append("'").append(this->code_format).append("'");
out.append("\n"); out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -5922,6 +6200,10 @@ bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->entity_category = value.as_enum<enums::EntityCategory>(); this->entity_category = value.as_enum<enums::EntityCategory>();
return true; return true;
} }
case 9: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -5971,6 +6253,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(6, this->disabled_by_default); buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_string(8, this->device_class); buffer.encode_string(8, this->device_class);
buffer.encode_uint32(9, this->device_id);
} }
void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -5981,6 +6264,7 @@ void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
ProtoSize::add_string_field(total_size, 1, this->device_class, false); ProtoSize::add_string_field(total_size, 1, this->device_class, false);
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesButtonResponse::dump_to(std::string &out) const { void ListEntitiesButtonResponse::dump_to(std::string &out) const {
@ -6018,6 +6302,11 @@ void ListEntitiesButtonResponse::dump_to(std::string &out) const {
out.append(" device_class: "); out.append(" device_class: ");
out.append("'").append(this->device_class).append("'"); out.append("'").append(this->device_class).append("'");
out.append("\n"); out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -6135,6 +6424,10 @@ bool ListEntitiesMediaPlayerResponse::decode_varint(uint32_t field_id, ProtoVarI
this->supports_pause = value.as_bool(); this->supports_pause = value.as_bool();
return true; return true;
} }
case 10: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -6187,6 +6480,7 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const {
for (auto &it : this->supported_formats) { for (auto &it : this->supported_formats) {
buffer.encode_message<MediaPlayerSupportedFormat>(9, it, true); buffer.encode_message<MediaPlayerSupportedFormat>(9, it, true);
} }
buffer.encode_uint32(10, this->device_id);
} }
void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -6198,6 +6492,7 @@ void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
ProtoSize::add_bool_field(total_size, 1, this->supports_pause, false); ProtoSize::add_bool_field(total_size, 1, this->supports_pause, false);
ProtoSize::add_repeated_message(total_size, 1, this->supported_formats); ProtoSize::add_repeated_message(total_size, 1, this->supported_formats);
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const {
@ -6241,6 +6536,11 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const {
it.dump_to(out); it.dump_to(out);
out.append("\n"); out.append("\n");
} }
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -8551,6 +8851,10 @@ bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, Pro
this->requires_code_to_arm = value.as_bool(); this->requires_code_to_arm = value.as_bool();
return true; return true;
} }
case 11: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -8598,6 +8902,7 @@ void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer buffer) cons
buffer.encode_uint32(8, this->supported_features); buffer.encode_uint32(8, this->supported_features);
buffer.encode_bool(9, this->requires_code); buffer.encode_bool(9, this->requires_code);
buffer.encode_bool(10, this->requires_code_to_arm); buffer.encode_bool(10, this->requires_code_to_arm);
buffer.encode_uint32(11, this->device_id);
} }
void ListEntitiesAlarmControlPanelResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesAlarmControlPanelResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -8610,6 +8915,7 @@ void ListEntitiesAlarmControlPanelResponse::calculate_size(uint32_t &total_size)
ProtoSize::add_uint32_field(total_size, 1, this->supported_features, false); ProtoSize::add_uint32_field(total_size, 1, this->supported_features, false);
ProtoSize::add_bool_field(total_size, 1, this->requires_code, false); ProtoSize::add_bool_field(total_size, 1, this->requires_code, false);
ProtoSize::add_bool_field(total_size, 1, this->requires_code_to_arm, false); ProtoSize::add_bool_field(total_size, 1, this->requires_code_to_arm, false);
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const {
@ -8656,6 +8962,11 @@ void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const {
out.append(" requires_code_to_arm: "); out.append(" requires_code_to_arm: ");
out.append(YESNO(this->requires_code_to_arm)); out.append(YESNO(this->requires_code_to_arm));
out.append("\n"); out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -8783,6 +9094,10 @@ bool ListEntitiesTextResponse::decode_varint(uint32_t field_id, ProtoVarInt valu
this->mode = value.as_enum<enums::TextMode>(); this->mode = value.as_enum<enums::TextMode>();
return true; return true;
} }
case 12: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -8835,6 +9150,7 @@ void ListEntitiesTextResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(9, this->max_length); buffer.encode_uint32(9, this->max_length);
buffer.encode_string(10, this->pattern); buffer.encode_string(10, this->pattern);
buffer.encode_enum<enums::TextMode>(11, this->mode); buffer.encode_enum<enums::TextMode>(11, this->mode);
buffer.encode_uint32(12, this->device_id);
} }
void ListEntitiesTextResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesTextResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -8848,6 +9164,7 @@ void ListEntitiesTextResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_uint32_field(total_size, 1, this->max_length, false); ProtoSize::add_uint32_field(total_size, 1, this->max_length, false);
ProtoSize::add_string_field(total_size, 1, this->pattern, false); ProtoSize::add_string_field(total_size, 1, this->pattern, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->mode), false); ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->mode), false);
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesTextResponse::dump_to(std::string &out) const { void ListEntitiesTextResponse::dump_to(std::string &out) const {
@ -8899,6 +9216,11 @@ void ListEntitiesTextResponse::dump_to(std::string &out) const {
out.append(" mode: "); out.append(" mode: ");
out.append(proto_enum_to_string<enums::TextMode>(this->mode)); out.append(proto_enum_to_string<enums::TextMode>(this->mode));
out.append("\n"); out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -9014,6 +9336,10 @@ bool ListEntitiesDateResponse::decode_varint(uint32_t field_id, ProtoVarInt valu
this->entity_category = value.as_enum<enums::EntityCategory>(); this->entity_category = value.as_enum<enums::EntityCategory>();
return true; return true;
} }
case 8: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -9058,6 +9384,7 @@ void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(5, this->icon); buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default); buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_uint32(8, this->device_id);
} }
void ListEntitiesDateResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesDateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -9067,6 +9394,7 @@ void ListEntitiesDateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->icon, false); ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesDateResponse::dump_to(std::string &out) const { void ListEntitiesDateResponse::dump_to(std::string &out) const {
@ -9100,6 +9428,11 @@ void ListEntitiesDateResponse::dump_to(std::string &out) const {
out.append(" entity_category: "); out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n"); out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -9255,6 +9588,10 @@ bool ListEntitiesTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt valu
this->entity_category = value.as_enum<enums::EntityCategory>(); this->entity_category = value.as_enum<enums::EntityCategory>();
return true; return true;
} }
case 8: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -9299,6 +9636,7 @@ void ListEntitiesTimeResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(5, this->icon); buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default); buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_uint32(8, this->device_id);
} }
void ListEntitiesTimeResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesTimeResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -9308,6 +9646,7 @@ void ListEntitiesTimeResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->icon, false); ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesTimeResponse::dump_to(std::string &out) const { void ListEntitiesTimeResponse::dump_to(std::string &out) const {
@ -9341,6 +9680,11 @@ void ListEntitiesTimeResponse::dump_to(std::string &out) const {
out.append(" entity_category: "); out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n"); out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -9496,6 +9840,10 @@ bool ListEntitiesEventResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->entity_category = value.as_enum<enums::EntityCategory>(); this->entity_category = value.as_enum<enums::EntityCategory>();
return true; return true;
} }
case 10: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -9552,6 +9900,7 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const {
for (auto &it : this->event_types) { for (auto &it : this->event_types) {
buffer.encode_string(9, it, true); buffer.encode_string(9, it, true);
} }
buffer.encode_uint32(10, this->device_id);
} }
void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -9567,6 +9916,7 @@ void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, it, true); ProtoSize::add_string_field(total_size, 1, it, true);
} }
} }
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesEventResponse::dump_to(std::string &out) const { void ListEntitiesEventResponse::dump_to(std::string &out) const {
@ -9610,6 +9960,11 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const {
out.append("'").append(it).append("'"); out.append("'").append(it).append("'");
out.append("\n"); out.append("\n");
} }
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -9678,6 +10033,10 @@ bool ListEntitiesValveResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->supports_stop = value.as_bool(); this->supports_stop = value.as_bool();
return true; return true;
} }
case 12: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -9730,6 +10089,7 @@ void ListEntitiesValveResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(9, this->assumed_state); buffer.encode_bool(9, this->assumed_state);
buffer.encode_bool(10, this->supports_position); buffer.encode_bool(10, this->supports_position);
buffer.encode_bool(11, this->supports_stop); buffer.encode_bool(11, this->supports_stop);
buffer.encode_uint32(12, this->device_id);
} }
void ListEntitiesValveResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesValveResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -9743,6 +10103,7 @@ void ListEntitiesValveResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_bool_field(total_size, 1, this->assumed_state, false); ProtoSize::add_bool_field(total_size, 1, this->assumed_state, false);
ProtoSize::add_bool_field(total_size, 1, this->supports_position, false); ProtoSize::add_bool_field(total_size, 1, this->supports_position, false);
ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false); ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false);
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesValveResponse::dump_to(std::string &out) const { void ListEntitiesValveResponse::dump_to(std::string &out) const {
@ -9792,6 +10153,11 @@ void ListEntitiesValveResponse::dump_to(std::string &out) const {
out.append(" supports_stop: "); out.append(" supports_stop: ");
out.append(YESNO(this->supports_stop)); out.append(YESNO(this->supports_stop));
out.append("\n"); out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -9923,6 +10289,10 @@ bool ListEntitiesDateTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt
this->entity_category = value.as_enum<enums::EntityCategory>(); this->entity_category = value.as_enum<enums::EntityCategory>();
return true; return true;
} }
case 8: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -9967,6 +10337,7 @@ void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(5, this->icon); buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default); buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_uint32(8, this->device_id);
} }
void ListEntitiesDateTimeResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesDateTimeResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -9976,6 +10347,7 @@ void ListEntitiesDateTimeResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->icon, false); ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesDateTimeResponse::dump_to(std::string &out) const { void ListEntitiesDateTimeResponse::dump_to(std::string &out) const {
@ -10009,6 +10381,11 @@ void ListEntitiesDateTimeResponse::dump_to(std::string &out) const {
out.append(" entity_category: "); out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n"); out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -10114,6 +10491,10 @@ bool ListEntitiesUpdateResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->entity_category = value.as_enum<enums::EntityCategory>(); this->entity_category = value.as_enum<enums::EntityCategory>();
return true; return true;
} }
case 9: {
this->device_id = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -10163,6 +10544,7 @@ void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(6, this->disabled_by_default); buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_string(8, this->device_class); buffer.encode_string(8, this->device_class);
buffer.encode_uint32(9, this->device_id);
} }
void ListEntitiesUpdateResponse::calculate_size(uint32_t &total_size) const { void ListEntitiesUpdateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false); ProtoSize::add_string_field(total_size, 1, this->object_id, false);
@ -10173,6 +10555,7 @@ void ListEntitiesUpdateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
ProtoSize::add_string_field(total_size, 1, this->device_class, false); ProtoSize::add_string_field(total_size, 1, this->device_class, false);
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesUpdateResponse::dump_to(std::string &out) const { void ListEntitiesUpdateResponse::dump_to(std::string &out) const {
@ -10210,6 +10593,11 @@ void ListEntitiesUpdateResponse::dump_to(std::string &out) const {
out.append(" device_class: "); out.append(" device_class: ");
out.append("'").append(this->device_class).append("'"); out.append("'").append(this->device_class).append("'");
out.append("\n"); out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif

View File

@ -264,6 +264,7 @@ class InfoResponseProtoMessage : public ProtoMessage {
bool disabled_by_default{false}; bool disabled_by_default{false};
std::string icon{}; std::string icon{};
enums::EntityCategory entity_category{}; enums::EntityCategory entity_category{};
uint32_t device_id{0};
protected: protected:
}; };
@ -415,10 +416,39 @@ class DeviceInfoRequest : public ProtoMessage {
protected: protected:
}; };
class AreaInfo : public ProtoMessage {
public:
uint32_t area_id{0};
std::string name{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class DeviceInfo : public ProtoMessage {
public:
uint32_t device_id{0};
std::string name{};
uint32_t area_id{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class DeviceInfoResponse : public ProtoMessage { class DeviceInfoResponse : public ProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 10; static constexpr uint16_t MESSAGE_TYPE = 10;
static constexpr uint16_t ESTIMATED_SIZE = 129; static constexpr uint16_t ESTIMATED_SIZE = 219;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "device_info_response"; } static constexpr const char *message_name() { return "device_info_response"; }
#endif #endif
@ -441,6 +471,9 @@ class DeviceInfoResponse : public ProtoMessage {
std::string suggested_area{}; std::string suggested_area{};
std::string bluetooth_mac_address{}; std::string bluetooth_mac_address{};
bool api_encryption_supported{false}; bool api_encryption_supported{false};
std::vector<DeviceInfo> devices{};
std::vector<AreaInfo> areas{};
AreaInfo area{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@ -493,7 +526,7 @@ class SubscribeStatesRequest : public ProtoMessage {
class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage { class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 12; static constexpr uint16_t MESSAGE_TYPE = 12;
static constexpr uint16_t ESTIMATED_SIZE = 56; static constexpr uint16_t ESTIMATED_SIZE = 60;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_binary_sensor_response"; } static constexpr const char *message_name() { return "list_entities_binary_sensor_response"; }
#endif #endif
@ -532,7 +565,7 @@ class BinarySensorStateResponse : public StateResponseProtoMessage {
class ListEntitiesCoverResponse : public InfoResponseProtoMessage { class ListEntitiesCoverResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 13; static constexpr uint16_t MESSAGE_TYPE = 13;
static constexpr uint16_t ESTIMATED_SIZE = 62; static constexpr uint16_t ESTIMATED_SIZE = 66;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_cover_response"; } static constexpr const char *message_name() { return "list_entities_cover_response"; }
#endif #endif
@ -601,7 +634,7 @@ class CoverCommandRequest : public ProtoMessage {
class ListEntitiesFanResponse : public InfoResponseProtoMessage { class ListEntitiesFanResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 14; static constexpr uint16_t MESSAGE_TYPE = 14;
static constexpr uint16_t ESTIMATED_SIZE = 73; static constexpr uint16_t ESTIMATED_SIZE = 77;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_fan_response"; } static constexpr const char *message_name() { return "list_entities_fan_response"; }
#endif #endif
@ -679,7 +712,7 @@ class FanCommandRequest : public ProtoMessage {
class ListEntitiesLightResponse : public InfoResponseProtoMessage { class ListEntitiesLightResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 15; static constexpr uint16_t MESSAGE_TYPE = 15;
static constexpr uint16_t ESTIMATED_SIZE = 85; static constexpr uint16_t ESTIMATED_SIZE = 90;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_light_response"; } static constexpr const char *message_name() { return "list_entities_light_response"; }
#endif #endif
@ -780,7 +813,7 @@ class LightCommandRequest : public ProtoMessage {
class ListEntitiesSensorResponse : public InfoResponseProtoMessage { class ListEntitiesSensorResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 16; static constexpr uint16_t MESSAGE_TYPE = 16;
static constexpr uint16_t ESTIMATED_SIZE = 73; static constexpr uint16_t ESTIMATED_SIZE = 77;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_sensor_response"; } static constexpr const char *message_name() { return "list_entities_sensor_response"; }
#endif #endif
@ -823,7 +856,7 @@ class SensorStateResponse : public StateResponseProtoMessage {
class ListEntitiesSwitchResponse : public InfoResponseProtoMessage { class ListEntitiesSwitchResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 17; static constexpr uint16_t MESSAGE_TYPE = 17;
static constexpr uint16_t ESTIMATED_SIZE = 56; static constexpr uint16_t ESTIMATED_SIZE = 60;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_switch_response"; } static constexpr const char *message_name() { return "list_entities_switch_response"; }
#endif #endif
@ -880,7 +913,7 @@ class SwitchCommandRequest : public ProtoMessage {
class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage { class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 18; static constexpr uint16_t MESSAGE_TYPE = 18;
static constexpr uint16_t ESTIMATED_SIZE = 54; static constexpr uint16_t ESTIMATED_SIZE = 58;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_text_sensor_response"; } static constexpr const char *message_name() { return "list_entities_text_sensor_response"; }
#endif #endif
@ -1196,7 +1229,7 @@ class ExecuteServiceRequest : public ProtoMessage {
class ListEntitiesCameraResponse : public InfoResponseProtoMessage { class ListEntitiesCameraResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 43; static constexpr uint16_t MESSAGE_TYPE = 43;
static constexpr uint16_t ESTIMATED_SIZE = 45; static constexpr uint16_t ESTIMATED_SIZE = 49;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_camera_response"; } static constexpr const char *message_name() { return "list_entities_camera_response"; }
#endif #endif
@ -1253,7 +1286,7 @@ class CameraImageRequest : public ProtoMessage {
class ListEntitiesClimateResponse : public InfoResponseProtoMessage { class ListEntitiesClimateResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 46; static constexpr uint16_t MESSAGE_TYPE = 46;
static constexpr uint16_t ESTIMATED_SIZE = 151; static constexpr uint16_t ESTIMATED_SIZE = 156;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_climate_response"; } static constexpr const char *message_name() { return "list_entities_climate_response"; }
#endif #endif
@ -1362,7 +1395,7 @@ class ClimateCommandRequest : public ProtoMessage {
class ListEntitiesNumberResponse : public InfoResponseProtoMessage { class ListEntitiesNumberResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 49; static constexpr uint16_t MESSAGE_TYPE = 49;
static constexpr uint16_t ESTIMATED_SIZE = 80; static constexpr uint16_t ESTIMATED_SIZE = 84;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_number_response"; } static constexpr const char *message_name() { return "list_entities_number_response"; }
#endif #endif
@ -1423,7 +1456,7 @@ class NumberCommandRequest : public ProtoMessage {
class ListEntitiesSelectResponse : public InfoResponseProtoMessage { class ListEntitiesSelectResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 52; static constexpr uint16_t MESSAGE_TYPE = 52;
static constexpr uint16_t ESTIMATED_SIZE = 63; static constexpr uint16_t ESTIMATED_SIZE = 67;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_select_response"; } static constexpr const char *message_name() { return "list_entities_select_response"; }
#endif #endif
@ -1481,7 +1514,7 @@ class SelectCommandRequest : public ProtoMessage {
class ListEntitiesSirenResponse : public InfoResponseProtoMessage { class ListEntitiesSirenResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 55; static constexpr uint16_t MESSAGE_TYPE = 55;
static constexpr uint16_t ESTIMATED_SIZE = 67; static constexpr uint16_t ESTIMATED_SIZE = 71;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_siren_response"; } static constexpr const char *message_name() { return "list_entities_siren_response"; }
#endif #endif
@ -1547,7 +1580,7 @@ class SirenCommandRequest : public ProtoMessage {
class ListEntitiesLockResponse : public InfoResponseProtoMessage { class ListEntitiesLockResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 58; static constexpr uint16_t MESSAGE_TYPE = 58;
static constexpr uint16_t ESTIMATED_SIZE = 60; static constexpr uint16_t ESTIMATED_SIZE = 64;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_lock_response"; } static constexpr const char *message_name() { return "list_entities_lock_response"; }
#endif #endif
@ -1609,7 +1642,7 @@ class LockCommandRequest : public ProtoMessage {
class ListEntitiesButtonResponse : public InfoResponseProtoMessage { class ListEntitiesButtonResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 61; static constexpr uint16_t MESSAGE_TYPE = 61;
static constexpr uint16_t ESTIMATED_SIZE = 54; static constexpr uint16_t ESTIMATED_SIZE = 58;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_button_response"; } static constexpr const char *message_name() { return "list_entities_button_response"; }
#endif #endif
@ -1662,7 +1695,7 @@ class MediaPlayerSupportedFormat : public ProtoMessage {
class ListEntitiesMediaPlayerResponse : public InfoResponseProtoMessage { class ListEntitiesMediaPlayerResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 63; static constexpr uint16_t MESSAGE_TYPE = 63;
static constexpr uint16_t ESTIMATED_SIZE = 81; static constexpr uint16_t ESTIMATED_SIZE = 85;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_media_player_response"; } static constexpr const char *message_name() { return "list_entities_media_player_response"; }
#endif #endif
@ -2532,7 +2565,7 @@ class VoiceAssistantSetConfiguration : public ProtoMessage {
class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage { class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 94; static constexpr uint16_t MESSAGE_TYPE = 94;
static constexpr uint16_t ESTIMATED_SIZE = 53; static constexpr uint16_t ESTIMATED_SIZE = 57;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_alarm_control_panel_response"; } static constexpr const char *message_name() { return "list_entities_alarm_control_panel_response"; }
#endif #endif
@ -2592,7 +2625,7 @@ class AlarmControlPanelCommandRequest : public ProtoMessage {
class ListEntitiesTextResponse : public InfoResponseProtoMessage { class ListEntitiesTextResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 97; static constexpr uint16_t MESSAGE_TYPE = 97;
static constexpr uint16_t ESTIMATED_SIZE = 64; static constexpr uint16_t ESTIMATED_SIZE = 68;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_text_response"; } static constexpr const char *message_name() { return "list_entities_text_response"; }
#endif #endif
@ -2653,7 +2686,7 @@ class TextCommandRequest : public ProtoMessage {
class ListEntitiesDateResponse : public InfoResponseProtoMessage { class ListEntitiesDateResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 100; static constexpr uint16_t MESSAGE_TYPE = 100;
static constexpr uint16_t ESTIMATED_SIZE = 45; static constexpr uint16_t ESTIMATED_SIZE = 49;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_date_response"; } static constexpr const char *message_name() { return "list_entities_date_response"; }
#endif #endif
@ -2713,7 +2746,7 @@ class DateCommandRequest : public ProtoMessage {
class ListEntitiesTimeResponse : public InfoResponseProtoMessage { class ListEntitiesTimeResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 103; static constexpr uint16_t MESSAGE_TYPE = 103;
static constexpr uint16_t ESTIMATED_SIZE = 45; static constexpr uint16_t ESTIMATED_SIZE = 49;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_time_response"; } static constexpr const char *message_name() { return "list_entities_time_response"; }
#endif #endif
@ -2773,7 +2806,7 @@ class TimeCommandRequest : public ProtoMessage {
class ListEntitiesEventResponse : public InfoResponseProtoMessage { class ListEntitiesEventResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 107; static constexpr uint16_t MESSAGE_TYPE = 107;
static constexpr uint16_t ESTIMATED_SIZE = 72; static constexpr uint16_t ESTIMATED_SIZE = 76;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_event_response"; } static constexpr const char *message_name() { return "list_entities_event_response"; }
#endif #endif
@ -2811,7 +2844,7 @@ class EventResponse : public StateResponseProtoMessage {
class ListEntitiesValveResponse : public InfoResponseProtoMessage { class ListEntitiesValveResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 109; static constexpr uint16_t MESSAGE_TYPE = 109;
static constexpr uint16_t ESTIMATED_SIZE = 60; static constexpr uint16_t ESTIMATED_SIZE = 64;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_valve_response"; } static constexpr const char *message_name() { return "list_entities_valve_response"; }
#endif #endif
@ -2873,7 +2906,7 @@ class ValveCommandRequest : public ProtoMessage {
class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage { class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 112; static constexpr uint16_t MESSAGE_TYPE = 112;
static constexpr uint16_t ESTIMATED_SIZE = 45; static constexpr uint16_t ESTIMATED_SIZE = 49;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_date_time_response"; } static constexpr const char *message_name() { return "list_entities_date_time_response"; }
#endif #endif
@ -2928,7 +2961,7 @@ class DateTimeCommandRequest : public ProtoMessage {
class ListEntitiesUpdateResponse : public InfoResponseProtoMessage { class ListEntitiesUpdateResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 116; static constexpr uint16_t MESSAGE_TYPE = 116;
static constexpr uint16_t ESTIMATED_SIZE = 54; static constexpr uint16_t ESTIMATED_SIZE = 58;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
static constexpr const char *message_name() { return "list_entities_update_response"; } static constexpr const char *message_name() { return "list_entities_update_response"; }
#endif #endif

View File

@ -60,8 +60,8 @@ from esphome.const import (
DEVICE_CLASS_WINDOW, DEVICE_CLASS_WINDOW,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
from esphome.util import Registry from esphome.util import Registry
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
@ -491,6 +491,9 @@ _BINARY_SENSOR_SCHEMA = (
) )
_BINARY_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("binary_sensor"))
def binary_sensor_schema( def binary_sensor_schema(
class_: MockObjClass = cv.UNDEFINED, class_: MockObjClass = cv.UNDEFINED,
*, *,
@ -521,7 +524,7 @@ BINARY_SENSOR_SCHEMA.add_extra(cv.deprecated_schema_constant("binary_sensor"))
async def setup_binary_sensor_core_(var, config): async def setup_binary_sensor_core_(var, config):
await setup_entity(var, config) await setup_entity(var, config, "binary_sensor")
if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
cg.add(var.set_device_class(device_class)) cg.add(var.set_device_class(device_class))

View File

@ -18,8 +18,8 @@ from esphome.const import (
DEVICE_CLASS_UPDATE, DEVICE_CLASS_UPDATE,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
@ -61,6 +61,9 @@ _BUTTON_SCHEMA = (
) )
_BUTTON_SCHEMA.add_extra(entity_duplicate_validator("button"))
def button_schema( def button_schema(
class_: MockObjClass, class_: MockObjClass,
*, *,
@ -87,7 +90,7 @@ BUTTON_SCHEMA.add_extra(cv.deprecated_schema_constant("button"))
async def setup_button_core_(var, config): async def setup_button_core_(var, config):
await setup_entity(var, config) await setup_entity(var, config, "button")
for conf in config.get(CONF_ON_PRESS, []): for conf in config.get(CONF_ON_PRESS, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)

View File

@ -48,8 +48,8 @@ from esphome.const import (
CONF_WEB_SERVER, CONF_WEB_SERVER,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
@ -247,6 +247,9 @@ _CLIMATE_SCHEMA = (
) )
_CLIMATE_SCHEMA.add_extra(entity_duplicate_validator("climate"))
def climate_schema( def climate_schema(
class_: MockObjClass, class_: MockObjClass,
*, *,
@ -273,7 +276,7 @@ CLIMATE_SCHEMA.add_extra(cv.deprecated_schema_constant("climate"))
async def setup_climate_core_(var, config): async def setup_climate_core_(var, config):
await setup_entity(var, config) await setup_entity(var, config, "climate")
visual = config[CONF_VISUAL] visual = config[CONF_VISUAL]
if (min_temp := visual.get(CONF_MIN_TEMPERATURE)) is not None: if (min_temp := visual.get(CONF_MIN_TEMPERATURE)) is not None:

View File

@ -33,8 +33,8 @@ from esphome.const import (
DEVICE_CLASS_WINDOW, DEVICE_CLASS_WINDOW,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
@ -126,6 +126,9 @@ _COVER_SCHEMA = (
) )
_COVER_SCHEMA.add_extra(entity_duplicate_validator("cover"))
def cover_schema( def cover_schema(
class_: MockObjClass, class_: MockObjClass,
*, *,
@ -154,7 +157,7 @@ COVER_SCHEMA.add_extra(cv.deprecated_schema_constant("cover"))
async def setup_cover_core_(var, config): async def setup_cover_core_(var, config):
await setup_entity(var, config) await setup_entity(var, config, "cover")
if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
cg.add(var.set_device_class(device_class)) cg.add(var.set_device_class(device_class))

View File

@ -22,8 +22,8 @@ from esphome.const import (
CONF_YEAR, CONF_YEAR,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@rfdarter", "@jesserockz"] CODEOWNERS = ["@rfdarter", "@jesserockz"]
@ -84,6 +84,8 @@ _DATETIME_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
).add_extra(_validate_time_present) ).add_extra(_validate_time_present)
_DATETIME_SCHEMA.add_extra(entity_duplicate_validator("datetime"))
def date_schema(class_: MockObjClass) -> cv.Schema: def date_schema(class_: MockObjClass) -> cv.Schema:
schema = cv.Schema( schema = cv.Schema(
@ -133,7 +135,7 @@ def datetime_schema(class_: MockObjClass) -> cv.Schema:
async def setup_datetime_core_(var, config): async def setup_datetime_core_(var, config):
await setup_entity(var, config) await setup_entity(var, config, "datetime")
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
mqtt_ = cg.new_Pvariable(mqtt_id, var) mqtt_ = cg.new_Pvariable(mqtt_id, var)

View File

@ -455,7 +455,7 @@ CONFIG_SCHEMA = cv.Schema(
CONF_NAME: "Demo Plain Sensor", CONF_NAME: "Demo Plain Sensor",
}, },
{ {
CONF_NAME: "Demo Temperature Sensor", CONF_NAME: "Demo Temperature Sensor 1",
CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS, CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS,
CONF_ICON: ICON_THERMOMETER, CONF_ICON: ICON_THERMOMETER,
CONF_ACCURACY_DECIMALS: 1, CONF_ACCURACY_DECIMALS: 1,
@ -463,7 +463,7 @@ CONFIG_SCHEMA = cv.Schema(
CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT, CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT,
}, },
{ {
CONF_NAME: "Demo Temperature Sensor", CONF_NAME: "Demo Temperature Sensor 2",
CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS, CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS,
CONF_ICON: ICON_THERMOMETER, CONF_ICON: ICON_THERMOMETER,
CONF_ACCURACY_DECIMALS: 1, CONF_ACCURACY_DECIMALS: 1,

View File

@ -19,7 +19,7 @@ from esphome.const import (
CONF_VSYNC_PIN, CONF_VSYNC_PIN,
) )
from esphome.core import CORE from esphome.core import CORE
from esphome.cpp_helpers import setup_entity from esphome.core.entity_helpers import setup_entity
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
@ -284,7 +284,7 @@ SETTERS = {
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await setup_entity(var, config) await setup_entity(var, config, "camera")
await cg.register_component(var, config) await cg.register_component(var, config)
for key, setter in SETTERS.items(): for key, setter in SETTERS.items():

View File

@ -18,8 +18,8 @@ from esphome.const import (
DEVICE_CLASS_MOTION, DEVICE_CLASS_MOTION,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@nohat"] CODEOWNERS = ["@nohat"]
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
@ -59,6 +59,9 @@ _EVENT_SCHEMA = (
) )
_EVENT_SCHEMA.add_extra(entity_duplicate_validator("event"))
def event_schema( def event_schema(
class_: MockObjClass = cv.UNDEFINED, class_: MockObjClass = cv.UNDEFINED,
*, *,
@ -88,7 +91,7 @@ EVENT_SCHEMA.add_extra(cv.deprecated_schema_constant("event"))
async def setup_event_core_(var, config, *, event_types: list[str]): async def setup_event_core_(var, config, *, event_types: list[str]):
await setup_entity(var, config) await setup_entity(var, config, "event")
for conf in config.get(CONF_ON_EVENT, []): for conf in config.get(CONF_ON_EVENT, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)

View File

@ -32,7 +32,7 @@ from esphome.const import (
CONF_WEB_SERVER, CONF_WEB_SERVER,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
@ -161,6 +161,9 @@ _FAN_SCHEMA = (
) )
_FAN_SCHEMA.add_extra(entity_duplicate_validator("fan"))
def fan_schema( def fan_schema(
class_: cg.Pvariable, class_: cg.Pvariable,
*, *,
@ -225,7 +228,7 @@ def validate_preset_modes(value):
async def setup_fan_core_(var, config): async def setup_fan_core_(var, config):
await setup_entity(var, config) await setup_entity(var, config, "fan")
cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE]))

View File

@ -38,8 +38,8 @@ from esphome.const import (
CONF_WHITE, CONF_WHITE,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
from .automation import LIGHT_STATE_SCHEMA from .automation import LIGHT_STATE_SCHEMA
from .effects import ( from .effects import (
@ -110,6 +110,8 @@ LIGHT_SCHEMA = (
) )
) )
LIGHT_SCHEMA.add_extra(entity_duplicate_validator("light"))
BINARY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend( BINARY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend(
{ {
cv.Optional(CONF_EFFECTS): validate_effects(BINARY_EFFECTS), cv.Optional(CONF_EFFECTS): validate_effects(BINARY_EFFECTS),
@ -207,7 +209,7 @@ def validate_color_temperature_channels(value):
async def setup_light_core_(light_var, output_var, config): async def setup_light_core_(light_var, output_var, config):
await setup_entity(light_var, config) await setup_entity(light_var, config, "light")
cg.add(light_var.set_restore_mode(config[CONF_RESTORE_MODE])) cg.add(light_var.set_restore_mode(config[CONF_RESTORE_MODE]))

View File

@ -14,8 +14,8 @@ from esphome.const import (
CONF_WEB_SERVER, CONF_WEB_SERVER,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
@ -67,6 +67,9 @@ _LOCK_SCHEMA = (
) )
_LOCK_SCHEMA.add_extra(entity_duplicate_validator("lock"))
def lock_schema( def lock_schema(
class_: MockObjClass = cv.UNDEFINED, class_: MockObjClass = cv.UNDEFINED,
*, *,
@ -94,7 +97,7 @@ LOCK_SCHEMA.add_extra(cv.deprecated_schema_constant("lock"))
async def _setup_lock_core(var, config): async def _setup_lock_core(var, config):
await setup_entity(var, config) await setup_entity(var, config, "lock")
for conf in config.get(CONF_ON_LOCK, []): for conf in config.get(CONF_ON_LOCK, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)

View File

@ -11,9 +11,9 @@ from esphome.const import (
CONF_VOLUME, CONF_VOLUME,
) )
from esphome.core import CORE from esphome.core import CORE
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.coroutine import coroutine_with_priority from esphome.coroutine import coroutine_with_priority
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
@ -81,7 +81,7 @@ IsAnnouncingCondition = media_player_ns.class_(
async def setup_media_player_core_(var, config): async def setup_media_player_core_(var, config):
await setup_entity(var, config) await setup_entity(var, config, "media_player")
for conf in config.get(CONF_ON_STATE, []): for conf in config.get(CONF_ON_STATE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) await automation.build_automation(trigger, [], conf)
@ -143,6 +143,8 @@ _MEDIA_PLAYER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
} }
) )
_MEDIA_PLAYER_SCHEMA.add_extra(entity_duplicate_validator("media_player"))
def media_player_schema( def media_player_schema(
class_: MockObjClass, class_: MockObjClass,
@ -166,7 +168,6 @@ def media_player_schema(
MEDIA_PLAYER_SCHEMA = media_player_schema(MediaPlayer) MEDIA_PLAYER_SCHEMA = media_player_schema(MediaPlayer)
MEDIA_PLAYER_SCHEMA.add_extra(cv.deprecated_schema_constant("media_player")) MEDIA_PLAYER_SCHEMA.add_extra(cv.deprecated_schema_constant("media_player"))
MEDIA_PLAYER_ACTION_SCHEMA = automation.maybe_simple_id( MEDIA_PLAYER_ACTION_SCHEMA = automation.maybe_simple_id(
cv.Schema( cv.Schema(
{ {

View File

@ -76,8 +76,8 @@ from esphome.const import (
DEVICE_CLASS_WIND_SPEED, DEVICE_CLASS_WIND_SPEED,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
DEVICE_CLASSES = [ DEVICE_CLASSES = [
@ -207,6 +207,9 @@ _NUMBER_SCHEMA = (
) )
_NUMBER_SCHEMA.add_extra(entity_duplicate_validator("number"))
def number_schema( def number_schema(
class_: MockObjClass, class_: MockObjClass,
*, *,
@ -237,7 +240,7 @@ NUMBER_SCHEMA.add_extra(cv.deprecated_schema_constant("number"))
async def setup_number_core_( async def setup_number_core_(
var, config, *, min_value: float, max_value: float, step: float var, config, *, min_value: float, max_value: float, step: float
): ):
await setup_entity(var, config) await setup_entity(var, config, "number")
cg.add(var.traits.set_min_value(min_value)) cg.add(var.traits.set_min_value(min_value))
cg.add(var.traits.set_max_value(max_value)) cg.add(var.traits.set_max_value(max_value))

View File

@ -17,8 +17,8 @@ from esphome.const import (
CONF_WEB_SERVER, CONF_WEB_SERVER,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
@ -65,6 +65,9 @@ _SELECT_SCHEMA = (
) )
_SELECT_SCHEMA.add_extra(entity_duplicate_validator("select"))
def select_schema( def select_schema(
class_: MockObjClass, class_: MockObjClass,
*, *,
@ -89,7 +92,7 @@ SELECT_SCHEMA.add_extra(cv.deprecated_schema_constant("select"))
async def setup_select_core_(var, config, *, options: list[str]): async def setup_select_core_(var, config, *, options: list[str]):
await setup_entity(var, config) await setup_entity(var, config, "select")
cg.add(var.traits.set_options(options)) cg.add(var.traits.set_options(options))

View File

@ -101,8 +101,8 @@ from esphome.const import (
ENTITY_CATEGORY_CONFIG, ENTITY_CATEGORY_CONFIG,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
from esphome.util import Registry from esphome.util import Registry
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
@ -318,6 +318,8 @@ _SENSOR_SCHEMA = (
) )
) )
_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("sensor"))
def sensor_schema( def sensor_schema(
class_: MockObjClass = cv.UNDEFINED, class_: MockObjClass = cv.UNDEFINED,
@ -787,7 +789,7 @@ async def build_filters(config):
async def setup_sensor_core_(var, config): async def setup_sensor_core_(var, config):
await setup_entity(var, config) await setup_entity(var, config, "sensor")
if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
cg.add(var.set_device_class(device_class)) cg.add(var.set_device_class(device_class))

View File

@ -20,8 +20,8 @@ from esphome.const import (
DEVICE_CLASS_SWITCH, DEVICE_CLASS_SWITCH,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
@ -91,6 +91,9 @@ _SWITCH_SCHEMA = (
) )
_SWITCH_SCHEMA.add_extra(entity_duplicate_validator("switch"))
def switch_schema( def switch_schema(
class_: MockObjClass, class_: MockObjClass,
*, *,
@ -131,7 +134,7 @@ SWITCH_SCHEMA.add_extra(cv.deprecated_schema_constant("switch"))
async def setup_switch_core_(var, config): async def setup_switch_core_(var, config):
await setup_entity(var, config) await setup_entity(var, config, "switch")
if (inverted := config.get(CONF_INVERTED)) is not None: if (inverted := config.get(CONF_INVERTED)) is not None:
cg.add(var.set_inverted(inverted)) cg.add(var.set_inverted(inverted))

View File

@ -14,8 +14,8 @@ from esphome.const import (
CONF_WEB_SERVER, CONF_WEB_SERVER,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@mauritskorse"] CODEOWNERS = ["@mauritskorse"]
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
@ -58,6 +58,9 @@ _TEXT_SCHEMA = (
) )
_TEXT_SCHEMA.add_extra(entity_duplicate_validator("text"))
def text_schema( def text_schema(
class_: MockObjClass = cv.UNDEFINED, class_: MockObjClass = cv.UNDEFINED,
*, *,
@ -94,7 +97,7 @@ async def setup_text_core_(
max_length: int | None, max_length: int | None,
pattern: str | None, pattern: str | None,
): ):
await setup_entity(var, config) await setup_entity(var, config, "text")
cg.add(var.traits.set_min_length(min_length)) cg.add(var.traits.set_min_length(min_length))
cg.add(var.traits.set_max_length(max_length)) cg.add(var.traits.set_max_length(max_length))

View File

@ -21,8 +21,8 @@ from esphome.const import (
DEVICE_CLASS_TIMESTAMP, DEVICE_CLASS_TIMESTAMP,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
from esphome.util import Registry from esphome.util import Registry
DEVICE_CLASSES = [ DEVICE_CLASSES = [
@ -153,6 +153,9 @@ _TEXT_SENSOR_SCHEMA = (
) )
_TEXT_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("text_sensor"))
def text_sensor_schema( def text_sensor_schema(
class_: MockObjClass = cv.UNDEFINED, class_: MockObjClass = cv.UNDEFINED,
*, *,
@ -186,7 +189,7 @@ async def build_filters(config):
async def setup_text_sensor_core_(var, config): async def setup_text_sensor_core_(var, config):
await setup_entity(var, config) await setup_entity(var, config, "text_sensor")
if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
cg.add(var.set_device_class(device_class)) cg.add(var.set_device_class(device_class))

View File

@ -15,8 +15,8 @@ from esphome.const import (
ENTITY_CATEGORY_CONFIG, ENTITY_CATEGORY_CONFIG,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
@ -58,6 +58,9 @@ _UPDATE_SCHEMA = (
) )
_UPDATE_SCHEMA.add_extra(entity_duplicate_validator("update"))
def update_schema( def update_schema(
class_: MockObjClass = cv.UNDEFINED, class_: MockObjClass = cv.UNDEFINED,
*, *,
@ -87,7 +90,7 @@ UPDATE_SCHEMA.add_extra(cv.deprecated_schema_constant("update"))
async def setup_update_core_(var, config): async def setup_update_core_(var, config):
await setup_entity(var, config) await setup_entity(var, config, "update")
if device_class_config := config.get(CONF_DEVICE_CLASS): if device_class_config := config.get(CONF_DEVICE_CLASS):
cg.add(var.set_device_class(device_class_config)) cg.add(var.set_device_class(device_class_config))

View File

@ -22,8 +22,8 @@ from esphome.const import (
DEVICE_CLASS_WATER, DEVICE_CLASS_WATER,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
@ -103,6 +103,9 @@ _VALVE_SCHEMA = (
) )
_VALVE_SCHEMA.add_extra(entity_duplicate_validator("valve"))
def valve_schema( def valve_schema(
class_: MockObjClass = cv.UNDEFINED, class_: MockObjClass = cv.UNDEFINED,
*, *,
@ -132,7 +135,7 @@ VALVE_SCHEMA.add_extra(cv.deprecated_schema_constant("valve"))
async def _setup_valve_core(var, config): async def _setup_valve_core(var, config):
await setup_entity(var, config) await setup_entity(var, config, "valve")
if device_class_config := config.get(CONF_DEVICE_CLASS): if device_class_config := config.get(CONF_DEVICE_CLASS):
cg.add(var.set_device_class(device_class_config)) cg.add(var.set_device_class(device_class_config))

View File

@ -1,5 +1,7 @@
"""Helpers for config validation using voluptuous.""" """Helpers for config validation using voluptuous."""
from __future__ import annotations
from contextlib import contextmanager from contextlib import contextmanager
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
@ -29,6 +31,7 @@ from esphome.const import (
CONF_COMMAND_RETAIN, CONF_COMMAND_RETAIN,
CONF_COMMAND_TOPIC, CONF_COMMAND_TOPIC,
CONF_DAY, CONF_DAY,
CONF_DEVICE_ID,
CONF_DISABLED_BY_DEFAULT, CONF_DISABLED_BY_DEFAULT,
CONF_DISCOVERY, CONF_DISCOVERY,
CONF_ENTITY_CATEGORY, CONF_ENTITY_CATEGORY,
@ -355,6 +358,13 @@ def icon(value):
) )
def sub_device_id(value: str | None) -> core.ID:
# Lazy import to avoid circular imports
from esphome.core.config import Device
return use_id(Device)(value)
def boolean(value): def boolean(value):
"""Validate the given config option to be a boolean. """Validate the given config option to be a boolean.
@ -1896,6 +1906,7 @@ ENTITY_BASE_SCHEMA = Schema(
Optional(CONF_DISABLED_BY_DEFAULT, default=False): boolean, Optional(CONF_DISABLED_BY_DEFAULT, default=False): boolean,
Optional(CONF_ICON): icon, Optional(CONF_ICON): icon,
Optional(CONF_ENTITY_CATEGORY): entity_category, Optional(CONF_ENTITY_CATEGORY): entity_category,
Optional(CONF_DEVICE_ID): sub_device_id,
} }
) )
@ -1964,7 +1975,7 @@ class Version:
return f"{self.major}.{self.minor}.{self.patch}" return f"{self.major}.{self.minor}.{self.patch}"
@classmethod @classmethod
def parse(cls, value: str) -> "Version": def parse(cls, value: str) -> Version:
match = re.match(r"^(\d+).(\d+).(\d+)-?\w*$", value) match = re.match(r"^(\d+).(\d+).(\d+)-?\w*$", value)
if match is None: if match is None:
raise ValueError(f"Not a valid version number {value}") raise ValueError(f"Not a valid version number {value}")

View File

@ -56,6 +56,8 @@ CONF_AP = "ap"
CONF_APPARENT_POWER = "apparent_power" CONF_APPARENT_POWER = "apparent_power"
CONF_ARDUINO_VERSION = "arduino_version" CONF_ARDUINO_VERSION = "arduino_version"
CONF_AREA = "area" CONF_AREA = "area"
CONF_AREA_ID = "area_id"
CONF_AREAS = "areas"
CONF_ARGS = "args" CONF_ARGS = "args"
CONF_ASSUMED_STATE = "assumed_state" CONF_ASSUMED_STATE = "assumed_state"
CONF_AT = "at" CONF_AT = "at"
@ -217,6 +219,7 @@ CONF_DEST = "dest"
CONF_DEVICE = "device" CONF_DEVICE = "device"
CONF_DEVICE_CLASS = "device_class" CONF_DEVICE_CLASS = "device_class"
CONF_DEVICE_FACTOR = "device_factor" CONF_DEVICE_FACTOR = "device_factor"
CONF_DEVICE_ID = "device_id"
CONF_DEVICES = "devices" CONF_DEVICES = "devices"
CONF_DIELECTRIC_CONSTANT = "dielectric_constant" CONF_DIELECTRIC_CONSTANT = "dielectric_constant"
CONF_DIMENSIONS = "dimensions" CONF_DIMENSIONS = "dimensions"

View File

@ -522,6 +522,9 @@ class EsphomeCore:
# Dict to track platform entity counts for pre-allocation # Dict to track platform entity counts for pre-allocation
# Key: platform name (e.g. "sensor", "binary_sensor"), Value: count # Key: platform name (e.g. "sensor", "binary_sensor"), Value: count
self.platform_counts: defaultdict[str, int] = defaultdict(int) self.platform_counts: defaultdict[str, int] = defaultdict(int)
# Track entity unique IDs to handle duplicates
# Set of (device_id, platform, sanitized_name) tuples
self.unique_ids: set[tuple[str, str, str]] = set()
# Whether ESPHome was started in verbose mode # Whether ESPHome was started in verbose mode
self.verbose = False self.verbose = False
# Whether ESPHome was started in quiet mode # Whether ESPHome was started in quiet mode
@ -553,6 +556,7 @@ class EsphomeCore:
self.loaded_integrations = set() self.loaded_integrations = set()
self.component_ids = set() self.component_ids = set()
self.platform_counts = defaultdict(int) self.platform_counts = defaultdict(int)
self.unique_ids = set()
PIN_SCHEMA_REGISTRY.reset() PIN_SCHEMA_REGISTRY.reset()
@property @property

View File

@ -9,6 +9,13 @@
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
#include "esphome/core/scheduler.h" #include "esphome/core/scheduler.h"
#ifdef USE_DEVICES
#include "esphome/core/device.h"
#endif
#ifdef USE_AREAS
#include "esphome/core/area.h"
#endif
#ifdef USE_SOCKET_SELECT_SUPPORT #ifdef USE_SOCKET_SELECT_SUPPORT
#include <sys/select.h> #include <sys/select.h>
#endif #endif
@ -87,7 +94,7 @@ static const uint32_t TEARDOWN_TIMEOUT_REBOOT_MS = 1000; // 1 second for quick
class Application { class Application {
public: public:
void pre_setup(const std::string &name, const std::string &friendly_name, const char *area, const char *comment, void pre_setup(const std::string &name, const std::string &friendly_name, const char *comment,
const char *compilation_time, bool name_add_mac_suffix) { const char *compilation_time, bool name_add_mac_suffix) {
arch_init(); arch_init();
this->name_add_mac_suffix_ = name_add_mac_suffix; this->name_add_mac_suffix_ = name_add_mac_suffix;
@ -102,11 +109,17 @@ class Application {
this->name_ = name; this->name_ = name;
this->friendly_name_ = friendly_name; this->friendly_name_ = friendly_name;
} }
this->area_ = area;
this->comment_ = comment; this->comment_ = comment;
this->compilation_time_ = compilation_time; this->compilation_time_ = compilation_time;
} }
#ifdef USE_DEVICES
void register_device(Device *device) { this->devices_.push_back(device); }
#endif
#ifdef USE_AREAS
void register_area(Area *area) { this->areas_.push_back(area); }
#endif
void set_current_component(Component *component) { this->current_component_ = component; } void set_current_component(Component *component) { this->current_component_ = component; }
Component *get_current_component() { return this->current_component_; } Component *get_current_component() { return this->current_component_; }
@ -264,6 +277,12 @@ class Application {
#ifdef USE_UPDATE #ifdef USE_UPDATE
void reserve_update(size_t count) { this->updates_.reserve(count); } void reserve_update(size_t count) { this->updates_.reserve(count); }
#endif #endif
#ifdef USE_AREAS
void reserve_area(size_t count) { this->areas_.reserve(count); }
#endif
#ifdef USE_DEVICES
void reserve_device(size_t count) { this->devices_.reserve(count); }
#endif
/// Register the component in this Application instance. /// Register the component in this Application instance.
template<class C> C *register_component(C *c) { template<class C> C *register_component(C *c) {
@ -285,7 +304,15 @@ class Application {
const std::string &get_friendly_name() const { return this->friendly_name_; } const std::string &get_friendly_name() const { return this->friendly_name_; }
/// Get the area of this Application set by pre_setup(). /// Get the area of this Application set by pre_setup().
std::string get_area() const { return this->area_ == nullptr ? "" : this->area_; } const char *get_area() const {
#ifdef USE_AREAS
// If we have areas registered, return the name of the first one (which is the top-level area)
if (!this->areas_.empty() && this->areas_[0] != nullptr) {
return this->areas_[0]->get_name();
}
#endif
return "";
}
/// Get the comment of this Application set by pre_setup(). /// Get the comment of this Application set by pre_setup().
std::string get_comment() const { return this->comment_; } std::string get_comment() const { return this->comment_; }
@ -334,6 +361,12 @@ class Application {
uint8_t get_app_state() const { return this->app_state_; } uint8_t get_app_state() const { return this->app_state_; }
#ifdef USE_DEVICES
const std::vector<Device *> &get_devices() { return this->devices_; }
#endif
#ifdef USE_AREAS
const std::vector<Area *> &get_areas() { return this->areas_; }
#endif
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
const std::vector<binary_sensor::BinarySensor *> &get_binary_sensors() { return this->binary_sensors_; } const std::vector<binary_sensor::BinarySensor *> &get_binary_sensors() { return this->binary_sensors_; }
binary_sensor::BinarySensor *get_binary_sensor_by_key(uint32_t key, bool include_internal = false) { binary_sensor::BinarySensor *get_binary_sensor_by_key(uint32_t key, bool include_internal = false) {
@ -610,6 +643,12 @@ class Application {
uint16_t current_loop_index_{0}; uint16_t current_loop_index_{0};
bool in_loop_{false}; bool in_loop_{false};
#ifdef USE_DEVICES
std::vector<Device *> devices_{};
#endif
#ifdef USE_AREAS
std::vector<Area *> areas_{};
#endif
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
std::vector<binary_sensor::BinarySensor *> binary_sensors_{}; std::vector<binary_sensor::BinarySensor *> binary_sensors_{};
#endif #endif
@ -676,7 +715,6 @@ class Application {
std::string name_; std::string name_;
std::string friendly_name_; std::string friendly_name_;
const char *area_{nullptr};
const char *comment_{nullptr}; const char *comment_{nullptr};
const char *compilation_time_{nullptr}; const char *compilation_time_{nullptr};
bool name_add_mac_suffix_; bool name_add_mac_suffix_;

19
esphome/core/area.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include <cstdint>
namespace esphome {
class Area {
public:
void set_area_id(uint32_t area_id) { this->area_id_ = area_id; }
uint32_t get_area_id() { return this->area_id_; }
void set_name(const char *name) { this->name_ = name; }
const char *get_name() { return this->name_; }
protected:
uint32_t area_id_{};
const char *name_ = "";
};
} // namespace esphome

View File

@ -1,18 +1,24 @@
from __future__ import annotations
import logging import logging
import os import os
from pathlib import Path from pathlib import Path
from esphome import automation from esphome import automation, core
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_AREA, CONF_AREA,
CONF_AREA_ID,
CONF_AREAS,
CONF_BUILD_PATH, CONF_BUILD_PATH,
CONF_COMMENT, CONF_COMMENT,
CONF_COMPILE_PROCESS_LIMIT, CONF_COMPILE_PROCESS_LIMIT,
CONF_DEBUG_SCHEDULER, CONF_DEBUG_SCHEDULER,
CONF_DEVICES,
CONF_ESPHOME, CONF_ESPHOME,
CONF_FRIENDLY_NAME, CONF_FRIENDLY_NAME,
CONF_ID,
CONF_INCLUDES, CONF_INCLUDES,
CONF_LIBRARIES, CONF_LIBRARIES,
CONF_MIN_VERSION, CONF_MIN_VERSION,
@ -32,7 +38,13 @@ from esphome.const import (
__version__ as ESPHOME_VERSION, __version__ as ESPHOME_VERSION,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.helpers import copy_file_if_changed, get_str_env, walk_files from esphome.helpers import (
copy_file_if_changed,
fnv1a_32bit_hash,
get_str_env,
walk_files,
)
from esphome.types import ConfigType
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -48,7 +60,8 @@ LoopTrigger = cg.esphome_ns.class_(
ProjectUpdateTrigger = cg.esphome_ns.class_( ProjectUpdateTrigger = cg.esphome_ns.class_(
"ProjectUpdateTrigger", cg.Component, automation.Trigger.template(cg.std_string) "ProjectUpdateTrigger", cg.Component, automation.Trigger.template(cg.std_string)
) )
Device = cg.esphome_ns.class_("Device")
Area = cg.esphome_ns.class_("Area")
VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"}
@ -71,6 +84,56 @@ def validate_hostname(config):
return config return config
def validate_ids_and_references(config: ConfigType) -> ConfigType:
"""Validate that there are no hash collisions between IDs and that area_id references are valid.
This validation is critical because we use 32-bit hashes for performance on microcontrollers.
By detecting collisions at compile time, we prevent any runtime issues while maintaining
optimal performance on 32-bit platforms. In practice, with typical deployments having only
a handful of areas and devices, hash collisions are virtually impossible.
"""
# Helper to check hash collisions
def check_hash_collision(
id_obj: core.ID,
hash_dict: dict[int, str],
item_type: str,
path: list[str | int],
) -> None:
hash_val: int = fnv1a_32bit_hash(id_obj.id)
if hash_val in hash_dict and hash_dict[hash_val] != id_obj.id:
raise cv.Invalid(
f"{item_type} ID '{id_obj.id}' with hash {hash_val} collides with "
f"existing {item_type.lower()} ID '{hash_dict[hash_val]}'",
path=path,
)
hash_dict[hash_val] = id_obj.id
# Collect all areas
all_areas: list[dict[str, str | core.ID]] = []
if CONF_AREA in config:
all_areas.append(config[CONF_AREA])
all_areas.extend(config[CONF_AREAS])
# Validate area hash collisions and collect IDs
area_hashes: dict[int, str] = {}
area_ids: set[str] = set()
for area in all_areas:
area_id: core.ID = area[CONF_ID]
check_hash_collision(area_id, area_hashes, "Area", [CONF_AREAS, area_id.id])
area_ids.add(area_id.id)
# Validate device hash collisions and area references
device_hashes: dict[int, str] = {}
for device in config[CONF_DEVICES]:
device_id: core.ID = device[CONF_ID]
check_hash_collision(
device_id, device_hashes, "Device", [CONF_DEVICES, device_id.id]
)
return config
def valid_include(value): def valid_include(value):
# Look for "<...>" includes # Look for "<...>" includes
if value.startswith("<") and value.endswith(">"): if value.startswith("<") and value.endswith(">"):
@ -111,13 +174,32 @@ if "ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT" in os.environ:
else: else:
_compile_process_limit_default = cv.UNDEFINED _compile_process_limit_default = cv.UNDEFINED
AREA_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_ID): cv.declare_id(Area),
cv.Required(CONF_NAME): cv.string,
}
)
DEVICE_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_ID): cv.declare_id(Device),
cv.Required(CONF_NAME): cv.string,
cv.Optional(CONF_AREA_ID): cv.use_id(Area),
}
)
def validate_area_config(config: dict | str) -> dict[str, str | core.ID]:
return cv.maybe_simple_value(AREA_SCHEMA, key=CONF_NAME)(config)
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
cv.Required(CONF_NAME): cv.valid_name, cv.Required(CONF_NAME): cv.valid_name,
cv.Optional(CONF_FRIENDLY_NAME, ""): cv.string, cv.Optional(CONF_FRIENDLY_NAME, ""): cv.string,
cv.Optional(CONF_AREA, ""): cv.string, cv.Optional(CONF_AREA): validate_area_config,
cv.Optional(CONF_COMMENT): cv.string, cv.Optional(CONF_COMMENT): cv.string,
cv.Required(CONF_BUILD_PATH): cv.string, cv.Required(CONF_BUILD_PATH): cv.string,
cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema( cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema(
@ -167,11 +249,17 @@ CONFIG_SCHEMA = cv.All(
cv.Optional( cv.Optional(
CONF_COMPILE_PROCESS_LIMIT, default=_compile_process_limit_default CONF_COMPILE_PROCESS_LIMIT, default=_compile_process_limit_default
): cv.int_range(min=1, max=get_usable_cpu_count()), ): cv.int_range(min=1, max=get_usable_cpu_count()),
cv.Optional(CONF_AREAS, default=[]): cv.ensure_list(AREA_SCHEMA),
cv.Optional(CONF_DEVICES, default=[]): cv.ensure_list(DEVICE_SCHEMA),
} }
), ),
validate_hostname, validate_hostname,
) )
FINAL_VALIDATE_SCHEMA = cv.All(validate_ids_and_references)
PRELOAD_CONFIG_SCHEMA = cv.Schema( PRELOAD_CONFIG_SCHEMA = cv.Schema(
{ {
cv.Required(CONF_NAME): cv.valid_name, cv.Required(CONF_NAME): cv.valid_name,
@ -336,7 +424,7 @@ async def _add_platform_reserves() -> None:
@coroutine_with_priority(100.0) @coroutine_with_priority(100.0)
async def to_code(config): async def to_code(config: ConfigType) -> None:
cg.add_global(cg.global_ns.namespace("esphome").using) cg.add_global(cg.global_ns.namespace("esphome").using)
# These can be used by user lambdas, put them to default scope # These can be used by user lambdas, put them to default scope
cg.add_global(cg.RawExpression("using std::isnan")) cg.add_global(cg.RawExpression("using std::isnan"))
@ -347,7 +435,6 @@ async def to_code(config):
cg.App.pre_setup( cg.App.pre_setup(
config[CONF_NAME], config[CONF_NAME],
config[CONF_FRIENDLY_NAME], config[CONF_FRIENDLY_NAME],
config[CONF_AREA],
config.get(CONF_COMMENT, ""), config.get(CONF_COMMENT, ""),
cg.RawExpression('__DATE__ ", " __TIME__'), cg.RawExpression('__DATE__ ", " __TIME__'),
config[CONF_NAME_ADD_MAC_SUFFIX], config[CONF_NAME_ADD_MAC_SUFFIX],
@ -417,3 +504,50 @@ async def to_code(config):
if config[CONF_PLATFORMIO_OPTIONS]: if config[CONF_PLATFORMIO_OPTIONS]:
CORE.add_job(_add_platformio_options, config[CONF_PLATFORMIO_OPTIONS]) CORE.add_job(_add_platformio_options, config[CONF_PLATFORMIO_OPTIONS])
# Process areas
all_areas: list[dict[str, str | core.ID]] = []
if CONF_AREA in config:
all_areas.append(config[CONF_AREA])
all_areas.extend(config[CONF_AREAS])
if all_areas:
cg.add(cg.RawStatement(f"App.reserve_area({len(all_areas)});"))
cg.add_define("USE_AREAS")
for area_conf in all_areas:
area_id: core.ID = area_conf[CONF_ID]
area_id_hash: int = fnv1a_32bit_hash(area_id.id)
area_name: str = area_conf[CONF_NAME]
area_var = cg.new_Pvariable(area_id)
cg.add(area_var.set_area_id(area_id_hash))
cg.add(area_var.set_name(area_name))
cg.add(cg.App.register_area(area_var))
# Process devices
devices: list[dict[str, str | core.ID]] = config[CONF_DEVICES]
if not devices:
return
# Reserve space for devices
cg.add(cg.RawStatement(f"App.reserve_device({len(devices)});"))
cg.add_define("USE_DEVICES")
# Process each device
for dev_conf in devices:
device_id: core.ID = dev_conf[CONF_ID]
device_id_hash = fnv1a_32bit_hash(device_id.id)
device_name: str = dev_conf[CONF_NAME]
dev = cg.new_Pvariable(device_id)
cg.add(dev.set_device_id(device_id_hash))
cg.add(dev.set_name(device_name))
# Set area if specified
if CONF_AREA_ID in dev_conf:
area_id: core.ID = dev_conf[CONF_AREA_ID]
area_id_hash = fnv1a_32bit_hash(area_id.id)
cg.add(dev.set_area_id(area_id_hash))
cg.add(cg.App.register_device(dev))

View File

@ -20,6 +20,7 @@
// Feature flags // Feature flags
#define USE_ALARM_CONTROL_PANEL #define USE_ALARM_CONTROL_PANEL
#define USE_AREAS
#define USE_BINARY_SENSOR #define USE_BINARY_SENSOR
#define USE_BUTTON #define USE_BUTTON
#define USE_CLIMATE #define USE_CLIMATE
@ -29,6 +30,7 @@
#define USE_DATETIME_DATETIME #define USE_DATETIME_DATETIME
#define USE_DATETIME_TIME #define USE_DATETIME_TIME
#define USE_DEEP_SLEEP #define USE_DEEP_SLEEP
#define USE_DEVICES
#define USE_DISPLAY #define USE_DISPLAY
#define USE_ESP32_IMPROV_STATE_CALLBACK #define USE_ESP32_IMPROV_STATE_CALLBACK
#define USE_EVENT #define USE_EVENT

20
esphome/core/device.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
namespace esphome {
class Device {
public:
void set_device_id(uint32_t device_id) { this->device_id_ = device_id; }
uint32_t get_device_id() { return this->device_id_; }
void set_name(const char *name) { this->name_ = name; }
const char *get_name() { return this->name_; }
void set_area_id(uint32_t area_id) { this->area_id_ = area_id; }
uint32_t get_area_id() { return this->area_id_; }
protected:
uint32_t device_id_{};
uint32_t area_id_{};
const char *name_ = "";
};
} // namespace esphome

View File

@ -11,7 +11,14 @@ const StringRef &EntityBase::get_name() const { return this->name_; }
void EntityBase::set_name(const char *name) { void EntityBase::set_name(const char *name) {
this->name_ = StringRef(name); this->name_ = StringRef(name);
if (this->name_.empty()) { if (this->name_.empty()) {
this->name_ = StringRef(App.get_friendly_name()); #ifdef USE_DEVICES
if (this->device_ != nullptr) {
this->name_ = StringRef(this->device_->get_name());
} else
#endif
{
this->name_ = StringRef(App.get_friendly_name());
}
this->flags_.has_own_name = false; this->flags_.has_own_name = false;
} else { } else {
this->flags_.has_own_name = true; this->flags_.has_own_name = true;
@ -47,19 +54,7 @@ void EntityBase::set_object_id(const char *object_id) {
} }
// Calculate Object ID Hash from Entity Name // Calculate Object ID Hash from Entity Name
void EntityBase::calc_object_id_() { void EntityBase::calc_object_id_() { this->object_id_hash_ = fnv1_hash(this->get_object_id()); }
// Check if `App.get_friendly_name()` is constant or dynamic.
if (!this->flags_.has_own_name && App.is_name_add_mac_suffix_enabled()) {
// `App.get_friendly_name()` is dynamic.
const auto object_id = str_sanitize(str_snake_case(App.get_friendly_name()));
// FNV-1 hash
this->object_id_hash_ = fnv1_hash(object_id);
} else {
// `App.get_friendly_name()` is constant.
// FNV-1 hash
this->object_id_hash_ = fnv1_hash(this->object_id_c_str_);
}
}
uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; } uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; }

View File

@ -6,6 +6,10 @@
#include "helpers.h" #include "helpers.h"
#include "log.h" #include "log.h"
#ifdef USE_DEVICES
#include "device.h"
#endif
namespace esphome { namespace esphome {
enum EntityCategory : uint8_t { enum EntityCategory : uint8_t {
@ -51,6 +55,17 @@ class EntityBase {
std::string get_icon() const; std::string get_icon() const;
void set_icon(const char *icon); void set_icon(const char *icon);
#ifdef USE_DEVICES
// Get/set this entity's device id
uint32_t get_device_id() const {
if (this->device_ == nullptr) {
return 0; // No device set, return 0
}
return this->device_->get_device_id();
}
void set_device(Device *device) { this->device_ = device; }
#endif
// Check if this entity has state // Check if this entity has state
bool has_state() const { return this->flags_.has_state; } bool has_state() const { return this->flags_.has_state; }
@ -67,6 +82,9 @@ class EntityBase {
const char *object_id_c_str_{nullptr}; const char *object_id_c_str_{nullptr};
const char *icon_c_str_{nullptr}; const char *icon_c_str_{nullptr};
uint32_t object_id_hash_{}; uint32_t object_id_hash_{};
#ifdef USE_DEVICES
Device *device_{};
#endif
// Bit-packed flags to save memory (1 byte instead of 5) // Bit-packed flags to save memory (1 byte instead of 5)
struct EntityFlags { struct EntityFlags {

View File

@ -1,5 +1,116 @@
from esphome.const import CONF_ID from collections.abc import Callable
import logging
import esphome.config_validation as cv
from esphome.const import (
CONF_DEVICE_ID,
CONF_DISABLED_BY_DEFAULT,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_ID,
CONF_INTERNAL,
CONF_NAME,
)
from esphome.core import CORE, ID
from esphome.cpp_generator import MockObj, add, get_variable
import esphome.final_validate as fv import esphome.final_validate as fv
from esphome.helpers import sanitize, snake_case
from esphome.types import ConfigType
_LOGGER = logging.getLogger(__name__)
def get_base_entity_object_id(
name: str, friendly_name: str | None, device_name: str | None = None
) -> str:
"""Calculate the base object ID for an entity that will be set via set_object_id().
This function calculates what object_id_c_str_ should be set to in C++.
The C++ EntityBase::get_object_id() (entity_base.cpp lines 38-49) works as:
- If !has_own_name && is_name_add_mac_suffix_enabled():
return str_sanitize(str_snake_case(App.get_friendly_name())) // Dynamic
- Else:
return object_id_c_str_ ?? "" // What we set via set_object_id()
Since we're calculating what to pass to set_object_id(), we always need to
generate the object_id the same way, regardless of name_add_mac_suffix setting.
Args:
name: The entity name (empty string if no name)
friendly_name: The friendly name from CORE.friendly_name
device_name: The device name if entity is on a sub-device
Returns:
The base object ID to use for duplicate checking and to pass to set_object_id()
"""
if name:
# Entity has its own name (has_own_name will be true)
base_str = name
elif device_name:
# Entity has empty name and is on a sub-device
# C++ EntityBase::set_name() uses device->get_name() when device is set
base_str = device_name
elif friendly_name:
# Entity has empty name (has_own_name will be false)
# C++ uses App.get_friendly_name() which returns friendly_name or device name
base_str = friendly_name
else:
# Fallback to device name
base_str = CORE.name
return sanitize(snake_case(base_str))
async def setup_entity(var: MockObj, config: ConfigType, platform: str) -> None:
"""Set up generic properties of an Entity.
This function sets up the common entity properties like name, icon,
entity category, etc.
Args:
var: The entity variable to set up
config: Configuration dictionary containing entity settings
platform: The platform name (e.g., "sensor", "binary_sensor")
"""
# Get device info
device_name: str | None = None
if CONF_DEVICE_ID in config:
device_id_obj: ID = config[CONF_DEVICE_ID]
device: MockObj = await get_variable(device_id_obj)
add(var.set_device(device))
# Get device name for object ID calculation
device_name = device_id_obj.id
add(var.set_name(config[CONF_NAME]))
# Calculate base object_id using the same logic as C++
# This must match the C++ behavior in esphome/core/entity_base.cpp
base_object_id = get_base_entity_object_id(
config[CONF_NAME], CORE.friendly_name, device_name
)
if not config[CONF_NAME]:
_LOGGER.debug(
"Entity has empty name, using '%s' as object_id base", base_object_id
)
# Set the object ID
add(var.set_object_id(base_object_id))
_LOGGER.debug(
"Setting object_id '%s' for entity '%s' on platform '%s'",
base_object_id,
config[CONF_NAME],
platform,
)
add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
if CONF_INTERNAL in config:
add(var.set_internal(config[CONF_INTERNAL]))
if CONF_ICON in config:
add(var.set_icon(config[CONF_ICON]))
if CONF_ENTITY_CATEGORY in config:
add(var.set_entity_category(config[CONF_ENTITY_CATEGORY]))
def inherit_property_from(property_to_inherit, parent_id_property, transform=None): def inherit_property_from(property_to_inherit, parent_id_property, transform=None):
@ -54,3 +165,48 @@ def inherit_property_from(property_to_inherit, parent_id_property, transform=Non
return config return config
return inherit_property return inherit_property
def entity_duplicate_validator(platform: str) -> Callable[[ConfigType], ConfigType]:
"""Create a validator function to check for duplicate entity names.
This validator is meant to be used with schema.add_extra() for entity base schemas.
Args:
platform: The platform name (e.g., "sensor", "binary_sensor")
Returns:
A validator function that checks for duplicate names
"""
def validator(config: ConfigType) -> ConfigType:
if CONF_NAME not in config:
# No name to validate
return config
# Get the entity name and device info
entity_name = config[CONF_NAME]
device_id = "" # Empty string for main device
if CONF_DEVICE_ID in config:
device_id_obj = config[CONF_DEVICE_ID]
# Use the device ID string directly for uniqueness
device_id = device_id_obj.id
# For duplicate detection, just use the sanitized name
name_key = sanitize(snake_case(entity_name))
# Check for duplicates
unique_key = (device_id, platform, name_key)
if unique_key in CORE.unique_ids:
device_prefix = f" on device '{device_id}'" if device_id else ""
raise cv.Invalid(
f"Duplicate {platform} entity with name '{entity_name}' found{device_prefix}. "
f"Each entity on a device must have a unique name within its platform."
)
# Add to tracking set
CORE.unique_ids.add(unique_key)
return config
return validator

View File

@ -1,11 +1,6 @@
import logging import logging
from esphome.const import ( from esphome.const import (
CONF_DISABLED_BY_DEFAULT,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_INTERNAL,
CONF_NAME,
CONF_SAFE_MODE, CONF_SAFE_MODE,
CONF_SETUP_PRIORITY, CONF_SETUP_PRIORITY,
CONF_TYPE_ID, CONF_TYPE_ID,
@ -16,7 +11,6 @@ from esphome.core import CORE, ID, coroutine
from esphome.coroutine import FakeAwaitable from esphome.coroutine import FakeAwaitable
from esphome.cpp_generator import add, get_variable from esphome.cpp_generator import add, get_variable
from esphome.cpp_types import App from esphome.cpp_types import App
from esphome.helpers import sanitize, snake_case
from esphome.types import ConfigFragmentType, ConfigType from esphome.types import ConfigFragmentType, ConfigType
from esphome.util import Registry, RegistryEntry from esphome.util import Registry, RegistryEntry
@ -96,22 +90,6 @@ async def register_parented(var, value):
add(var.set_parent(paren)) add(var.set_parent(paren))
async def setup_entity(var, config):
"""Set up generic properties of an Entity"""
add(var.set_name(config[CONF_NAME]))
if not config[CONF_NAME]:
add(var.set_object_id(sanitize(snake_case(CORE.friendly_name))))
else:
add(var.set_object_id(sanitize(snake_case(config[CONF_NAME]))))
add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
if CONF_INTERNAL in config:
add(var.set_internal(config[CONF_INTERNAL]))
if CONF_ICON in config:
add(var.set_icon(config[CONF_ICON]))
if CONF_ENTITY_CATEGORY in config:
add(var.set_entity_category(config[CONF_ENTITY_CATEGORY]))
def extract_registry_entry_config( def extract_registry_entry_config(
registry: Registry, registry: Registry,
full_config: ConfigType, full_config: ConfigType,

View File

@ -1,25 +1,9 @@
from __future__ import annotations from __future__ import annotations
import unicodedata from esphome.helpers import slugify
from esphome.const import ALLOWED_NAME_CHARS
def strip_accents(value): def friendly_name_slugify(value: str) -> str:
return "".join( """Convert a friendly name to a slug with dashes instead of underscores."""
c # First use the standard slugify, then convert underscores to dashes
for c in unicodedata.normalize("NFD", str(value)) return slugify(value).replace("_", "-")
if unicodedata.category(c) != "Mn"
)
def friendly_name_slugify(value):
value = (
strip_accents(value)
.lower()
.replace(" ", "-")
.replace("_", "-")
.replace("--", "-")
.strip("-")
)
return "".join(c for c in value if c in ALLOWED_NAME_CHARS)

View File

@ -29,6 +29,53 @@ def ensure_unique_string(preferred_string, current_strings):
return test_string return test_string
def fnv1a_32bit_hash(string: str) -> int:
"""FNV-1a 32-bit hash function.
Note: This uses 32-bit hash instead of 64-bit for several reasons:
1. ESPHome targets 32-bit microcontrollers with limited RAM (often <320KB)
2. Using 64-bit hashes would double the RAM usage for storing IDs
3. 64-bit operations are slower on 32-bit processors
While there's a ~50% collision probability at ~77,000 unique IDs,
ESPHome validates for collisions at compile time, preventing any
runtime issues. In practice, most ESPHome installations only have
a handful of area_ids and device_ids (typically <10 areas and <100
devices), making collisions virtually impossible.
"""
hash_value = 2166136261
for char in string:
hash_value ^= ord(char)
hash_value = (hash_value * 16777619) & 0xFFFFFFFF
return hash_value
def strip_accents(value: str) -> str:
"""Remove accents from a string."""
import unicodedata
return "".join(
c
for c in unicodedata.normalize("NFD", str(value))
if unicodedata.category(c) != "Mn"
)
def slugify(value: str) -> str:
"""Convert a string to a valid C++ identifier slug."""
from esphome.const import ALLOWED_NAME_CHARS
value = (
strip_accents(value)
.lower()
.replace(" ", "_")
.replace("-", "_")
.replace("__", "_")
.strip("_")
)
return "".join(c for c in value if c in ALLOWED_NAME_CHARS)
def indent_all_but_first_and_last(text, padding=" "): def indent_all_but_first_and_last(text, padding=" "):
lines = text.splitlines(True) lines = text.splitlines(True)
if len(lines) <= 2: if len(lines) <= 2:

View File

@ -12,12 +12,12 @@ sensor:
frequency: 60Hz frequency: 60Hz
phase_a: phase_a:
name: Channel A name: Channel A
voltage: Voltage voltage: Channel A Voltage
current: Current current: Channel A Current
active_power: Active Power active_power: Channel A Active Power
power_factor: Power Factor power_factor: Channel A Power Factor
forward_active_energy: Forward Active Energy forward_active_energy: Channel A Forward Active Energy
reverse_active_energy: Reverse Active Energy reverse_active_energy: Channel A Reverse Active Energy
calibration: calibration:
current_gain: 3116628 current_gain: 3116628
voltage_gain: -757178 voltage_gain: -757178
@ -25,12 +25,12 @@ sensor:
phase_angle: 188 phase_angle: 188
phase_b: phase_b:
name: Channel B name: Channel B
voltage: Voltage voltage: Channel B Voltage
current: Current current: Channel B Current
active_power: Active Power active_power: Channel B Active Power
power_factor: Power Factor power_factor: Channel B Power Factor
forward_active_energy: Forward Active Energy forward_active_energy: Channel B Forward Active Energy
reverse_active_energy: Reverse Active Energy reverse_active_energy: Channel B Reverse Active Energy
calibration: calibration:
current_gain: 3133655 current_gain: 3133655
voltage_gain: -755235 voltage_gain: -755235
@ -38,12 +38,12 @@ sensor:
phase_angle: 188 phase_angle: 188
phase_c: phase_c:
name: Channel C name: Channel C
voltage: Voltage voltage: Channel C Voltage
current: Current current: Channel C Current
active_power: Active Power active_power: Channel C Active Power
power_factor: Power Factor power_factor: Channel C Power Factor
forward_active_energy: Forward Active Energy forward_active_energy: Channel C Forward Active Energy
reverse_active_energy: Reverse Active Energy reverse_active_energy: Channel C Reverse Active Energy
calibration: calibration:
current_gain: 3111158 current_gain: 3111158
voltage_gain: -743813 voltage_gain: -743813
@ -51,6 +51,6 @@ sensor:
phase_angle: 180 phase_angle: 180
neutral: neutral:
name: Neutral name: Neutral
current: Current current: Neutral Current
calibration: calibration:
current_gain: 3189 current_gain: 3189

View File

@ -26,7 +26,7 @@ alarm_control_panel:
ESP_LOGD("TEST", "State change %s", LOG_STR_ARG(alarm_control_panel_state_to_string(id(alarmcontrolpanel1)->get_state()))); ESP_LOGD("TEST", "State change %s", LOG_STR_ARG(alarm_control_panel_state_to_string(id(alarmcontrolpanel1)->get_state())));
- platform: template - platform: template
id: alarmcontrolpanel2 id: alarmcontrolpanel2
name: Alarm Panel name: Alarm Panel 2
codes: codes:
- "1234" - "1234"
requires_code_to_arm: true requires_code_to_arm: true

View File

@ -26,7 +26,7 @@ binary_sensor:
sensor: sensor:
- platform: binary_sensor_map - platform: binary_sensor_map
name: Binary Sensor Map name: Binary Sensor Map Group
type: group type: group
channels: channels:
- binary_sensor: bin1 - binary_sensor: bin1
@ -36,7 +36,7 @@ sensor:
- binary_sensor: bin3 - binary_sensor: bin3
value: 100.0 value: 100.0
- platform: binary_sensor_map - platform: binary_sensor_map
name: Binary Sensor Map name: Binary Sensor Map Sum
type: sum type: sum
channels: channels:
- binary_sensor: bin1 - binary_sensor: bin1
@ -46,7 +46,7 @@ sensor:
- binary_sensor: bin3 - binary_sensor: bin3
value: 100.0 value: 100.0
- platform: binary_sensor_map - platform: binary_sensor_map
name: Binary Sensor Map name: Binary Sensor Map Bayesian
type: bayesian type: bayesian
prior: 0.4 prior: 0.4
observations: observations:

View File

@ -5,7 +5,7 @@ one_wire:
sensor: sensor:
- platform: dallas_temp - platform: dallas_temp
address: 0x1C0000031EDD2A28 address: 0x1C0000031EDD2A28
name: Dallas Temperature name: Dallas Temperature 1
resolution: 9 resolution: 9
- platform: dallas_temp - platform: dallas_temp
name: Dallas Temperature name: Dallas Temperature 2

View File

@ -2,7 +2,9 @@ esphome:
debug_scheduler: true debug_scheduler: true
platformio_options: platformio_options:
board_build.flash_mode: dio board_build.flash_mode: dio
area: testing area:
id: testing_area
name: Testing Area
on_boot: on_boot:
logger.log: on_boot logger.log: on_boot
on_shutdown: on_shutdown:
@ -17,4 +19,20 @@ esphome:
version: "1.1" version: "1.1"
on_update: on_update:
logger.log: on_update logger.log: on_update
areas:
- id: another_area
name: Another area
devices:
- id: other_device
name: Another device
area_id: another_area
- id: test_device
name: Test device in main area
area_id: testing_area # Reference the main area (not in areas)
- id: no_area_device
name: Device without area # This device has no area_id
binary_sensor:
- platform: template
name: Other device sensor
device_id: other_device

View File

@ -7,20 +7,20 @@ climate:
protocol: mitsubishi_heavy_zm protocol: mitsubishi_heavy_zm
horizontal_default: left horizontal_default: left
vertical_default: up vertical_default: up
name: HeatpumpIR Climate name: HeatpumpIR Climate Mitsubishi
min_temperature: 18 min_temperature: 18
max_temperature: 30 max_temperature: 30
- platform: heatpumpir - platform: heatpumpir
protocol: daikin protocol: daikin
horizontal_default: mleft horizontal_default: mleft
vertical_default: mup vertical_default: mup
name: HeatpumpIR Climate name: HeatpumpIR Climate Daikin
min_temperature: 18 min_temperature: 18
max_temperature: 30 max_temperature: 30
- platform: heatpumpir - platform: heatpumpir
protocol: panasonic_altdke protocol: panasonic_altdke
horizontal_default: mright horizontal_default: mright
vertical_default: mdown vertical_default: mdown
name: HeatpumpIR Climate name: HeatpumpIR Climate Panasonic
min_temperature: 18 min_temperature: 18
max_temperature: 30 max_temperature: 30

View File

@ -114,7 +114,7 @@ light:
warm_white_color_temperature: 500 mireds warm_white_color_temperature: 500 mireds
- platform: rgb - platform: rgb
id: test_rgb_light_initial_state id: test_rgb_light_initial_state
name: RGB Light name: RGB Light Initial State
red: test_ledc_1 red: test_ledc_1
green: test_ledc_2 green: test_ledc_2
blue: test_ledc_3 blue: test_ledc_3

View File

@ -6,13 +6,13 @@ i2c:
sensor: sensor:
- platform: ltr390 - platform: ltr390
uv: uv:
name: LTR390 UV name: LTR390 UV 1
uv_index: uv_index:
name: LTR390 UVI name: LTR390 UVI 1
light: light:
name: LTR390 Light name: LTR390 Light 1
ambient_light: ambient_light:
name: LTR390 ALS name: LTR390 ALS 1
gain: X3 gain: X3
resolution: 18 resolution: 18
window_correction_factor: 1.0 window_correction_factor: 1.0
@ -20,13 +20,13 @@ sensor:
update_interval: 60s update_interval: 60s
- platform: ltr390 - platform: ltr390
uv: uv:
name: LTR390 UV name: LTR390 UV 2
uv_index: uv_index:
name: LTR390 UVI name: LTR390 UVI 2
light: light:
name: LTR390 Light name: LTR390 Light 2
ambient_light: ambient_light:
name: LTR390 ALS name: LTR390 ALS 2
gain: gain:
ambient_light: X9 ambient_light: X9
uv: X3 uv: X3

View File

@ -24,33 +24,33 @@ sensor:
widget: lv_arc widget: lv_arc
- platform: lvgl - platform: lvgl
widget: slider_id widget: slider_id
name: LVGL Slider name: LVGL Slider Sensor
- platform: lvgl - platform: lvgl
widget: bar_id widget: bar_id
id: lvgl_bar_sensor id: lvgl_bar_sensor
name: LVGL Bar name: LVGL Bar Sensor
- platform: lvgl - platform: lvgl
widget: spinbox_id widget: spinbox_id
name: LVGL Spinbox name: LVGL Spinbox Sensor
number: number:
- platform: lvgl - platform: lvgl
widget: slider_id widget: slider_id
name: LVGL Slider name: LVGL Slider Number
update_on_release: true update_on_release: true
restore_value: true restore_value: true
- platform: lvgl - platform: lvgl
widget: lv_arc widget: lv_arc
id: lvgl_arc_number id: lvgl_arc_number
name: LVGL Arc name: LVGL Arc Number
- platform: lvgl - platform: lvgl
widget: bar_id widget: bar_id
id: lvgl_bar_number id: lvgl_bar_number
name: LVGL Bar name: LVGL Bar Number
- platform: lvgl - platform: lvgl
widget: spinbox_id widget: spinbox_id
id: lvgl_spinbox_number id: lvgl_spinbox_number
name: LVGL Spinbox name: LVGL Spinbox Number
light: light:
- platform: lvgl - platform: lvgl

View File

@ -170,4 +170,4 @@ switch:
otc_active: otc_active:
name: "Boiler Outside temperature compensation active" name: "Boiler Outside temperature compensation active"
ch2_active: ch2_active:
name: "Boiler Central Heating 2 active" name: "Boiler Central Heating 2 active status"

View File

@ -5,7 +5,7 @@ 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/binary_sensor_map/common.yaml file: tests/components/absolute_humidity/common.yaml
ref: dev ref: dev
refresh: 1d refresh: 1d

View File

@ -7,7 +7,7 @@ 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/binary_sensor_map/common.yaml file: tests/components/absolute_humidity/common.yaml
ref: dev ref: dev
refresh: 1d refresh: 1d

View File

@ -115,7 +115,7 @@ button:
address: 0x00 address: 0x00
command: 0x0B command: 0x0B
- platform: template - platform: template
name: RC5 name: RC5 Raw
on_press: on_press:
remote_transmitter.transmit_raw: remote_transmitter.transmit_raw:
code: [1000, -1000] code: [1000, -1000]

View File

@ -12,7 +12,7 @@
using namespace esphome; using namespace esphome;
void setup() { void setup() {
App.pre_setup("livingroom", "LivingRoom", "LivingRoomArea", "comment", __DATE__ ", " __TIME__, false); App.pre_setup("livingroom", "LivingRoom", "comment", __DATE__ ", " __TIME__, false);
auto *log = new logger::Logger(115200, 512); // NOLINT auto *log = new logger::Logger(115200, 512); // NOLINT
log->pre_setup(); log->pre_setup();
log->set_uart_selection(logger::UART_SELECTION_UART0); log->set_uart_selection(logger::UART_SELECTION_UART0);

View File

@ -203,6 +203,7 @@ async def compile_esphome(
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
def _read_config_and_get_binary(): def _read_config_and_get_binary():
CORE.reset() # Reset CORE state between test runs
CORE.config_path = str(config_path) CORE.config_path = str(config_path)
config = esphome.config.read_config( config = esphome.config.read_config(
{"command": "compile", "config": str(config_path)} {"command": "compile", "config": str(config_path)}

View File

@ -0,0 +1,57 @@
esphome:
name: areas-devices-test
# Define top-level area
area:
id: living_room_area
name: Living Room
# Define additional areas
areas:
- id: bedroom_area
name: Bedroom
- id: kitchen_area
name: Kitchen
# Define devices with area assignments
devices:
- id: light_controller_device
name: Light Controller
area_id: living_room_area # Uses top-level area
- id: temp_sensor_device
name: Temperature Sensor
area_id: bedroom_area
- id: motion_detector_device
name: Motion Detector
area_id: living_room_area # Reuses top-level area
- id: smart_switch_device
name: Smart Switch
area_id: kitchen_area
host:
api:
logger:
# Sensors assigned to different devices
sensor:
- platform: template
name: Light Controller Sensor
device_id: light_controller_device
lambda: return 1.0;
update_interval: 0.1s
- platform: template
name: Temperature Sensor Reading
device_id: temp_sensor_device
lambda: return 2.0;
update_interval: 0.1s
- platform: template
name: Motion Detector Status
device_id: motion_detector_device
lambda: return 3.0;
update_interval: 0.1s
- platform: template
name: Smart Switch Power
device_id: smart_switch_device
lambda: return 4.0;
update_interval: 0.1s

View File

@ -0,0 +1,154 @@
esphome:
name: duplicate-entities-test
# Define devices to test multi-device duplicate handling
devices:
- id: controller_1
name: Controller 1
- id: controller_2
name: Controller 2
- id: controller_3
name: Controller 3
host:
api: # Port will be automatically injected
logger:
# Test that duplicate entity names are allowed on different devices
# Scenario 1: Same sensor name on different devices (allowed)
sensor:
- platform: template
name: Temperature
device_id: controller_1
lambda: return 21.0;
update_interval: 0.1s
- platform: template
name: Temperature
device_id: controller_2
lambda: return 22.0;
update_interval: 0.1s
- platform: template
name: Temperature
device_id: controller_3
lambda: return 23.0;
update_interval: 0.1s
# Main device sensor (no device_id)
- platform: template
name: Temperature
lambda: return 20.0;
update_interval: 0.1s
# Different sensor with unique name
- platform: template
name: Humidity
lambda: return 60.0;
update_interval: 0.1s
# Scenario 2: Same binary sensor name on different devices (allowed)
binary_sensor:
- platform: template
name: Status
device_id: controller_1
lambda: return true;
- platform: template
name: Status
device_id: controller_2
lambda: return false;
- platform: template
name: Status
lambda: return true; # Main device
# Different platform can have same name as sensor
- platform: template
name: Temperature
lambda: return true;
# Scenario 3: Same text sensor name on different devices
text_sensor:
- platform: template
name: Device Info
device_id: controller_1
lambda: return {"Controller 1 Active"};
update_interval: 0.1s
- platform: template
name: Device Info
device_id: controller_2
lambda: return {"Controller 2 Active"};
update_interval: 0.1s
- platform: template
name: Device Info
lambda: return {"Main Device Active"};
update_interval: 0.1s
# Scenario 4: Same switch name on different devices
switch:
- platform: template
name: Power
device_id: controller_1
lambda: return false;
turn_on_action: []
turn_off_action: []
- platform: template
name: Power
device_id: controller_2
lambda: return true;
turn_on_action: []
turn_off_action: []
- platform: template
name: Power
device_id: controller_3
lambda: return false;
turn_on_action: []
turn_off_action: []
# Unique switch on main device
- platform: template
name: Main Power
lambda: return true;
turn_on_action: []
turn_off_action: []
# Scenario 5: Empty names on different devices (should use device name)
button:
- platform: template
name: ""
device_id: controller_1
on_press: []
- platform: template
name: ""
device_id: controller_2
on_press: []
- platform: template
name: ""
on_press: [] # Main device
# Scenario 6: Special characters in names
number:
- platform: template
name: "Temperature Setpoint!"
device_id: controller_1
min_value: 10.0
max_value: 30.0
step: 0.1
lambda: return 21.0;
set_action: []
- platform: template
name: "Temperature Setpoint!"
device_id: controller_2
min_value: 10.0
max_value: 30.0
step: 0.1
lambda: return 22.0;
set_action: []

View File

@ -0,0 +1,15 @@
esphome:
name: legacy-area-test
# Using legacy string-based area configuration
area: Master Bedroom
host:
api:
logger:
# Simple sensor to ensure the device compiles and runs
sensor:
- platform: template
name: Test Sensor
lambda: return 42.0;
update_interval: 1s

View File

@ -0,0 +1,121 @@
"""Integration test for areas and devices feature."""
from __future__ import annotations
import asyncio
from aioesphomeapi import EntityState
import pytest
from .types import APIClientConnectedFactory, RunCompiledFunction
@pytest.mark.asyncio
async def test_areas_and_devices(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""Test areas and devices configuration with entity mapping."""
async with run_compiled(yaml_config), api_client_connected() as client:
# Get device info which includes areas and devices
device_info = await client.device_info()
assert device_info is not None
# Verify areas are reported
areas = device_info.areas
assert len(areas) >= 2, f"Expected at least 2 areas, got {len(areas)}"
# Find our specific areas
main_area = next((a for a in areas if a.name == "Living Room"), None)
bedroom_area = next((a for a in areas if a.name == "Bedroom"), None)
kitchen_area = next((a for a in areas if a.name == "Kitchen"), None)
assert main_area is not None, "Living Room area not found"
assert bedroom_area is not None, "Bedroom area not found"
assert kitchen_area is not None, "Kitchen area not found"
# Verify devices are reported
devices = device_info.devices
assert len(devices) >= 4, f"Expected at least 4 devices, got {len(devices)}"
# Find our specific devices
light_controller = next(
(d for d in devices if d.name == "Light Controller"), None
)
temp_sensor = next((d for d in devices if d.name == "Temperature Sensor"), None)
motion_detector = next(
(d for d in devices if d.name == "Motion Detector"), None
)
smart_switch = next((d for d in devices if d.name == "Smart Switch"), None)
assert light_controller is not None, "Light Controller device not found"
assert temp_sensor is not None, "Temperature Sensor device not found"
assert motion_detector is not None, "Motion Detector device not found"
assert smart_switch is not None, "Smart Switch device not found"
# Verify device area assignments
assert light_controller.area_id == main_area.area_id, (
"Light Controller should be in Living Room"
)
assert temp_sensor.area_id == bedroom_area.area_id, (
"Temperature Sensor should be in Bedroom"
)
assert motion_detector.area_id == main_area.area_id, (
"Motion Detector should be in Living Room"
)
assert smart_switch.area_id == kitchen_area.area_id, (
"Smart Switch should be in Kitchen"
)
# Verify suggested_area is set to the top-level area name
assert device_info.suggested_area == "Living Room", (
f"Expected suggested_area to be 'Living Room', got '{device_info.suggested_area}'"
)
# Get entity list to verify device_id mapping
entities = await client.list_entities_services()
# Collect sensor entities
sensor_entities = [e for e in entities[0] if hasattr(e, "device_id")]
assert len(sensor_entities) >= 4, (
f"Expected at least 4 sensor entities, got {len(sensor_entities)}"
)
# Subscribe to states to get sensor values
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 all expected sensor states
if len(states) >= 4 and not states_future.done():
states_future.set_result(True)
client.subscribe_states(on_state)
# Wait for sensor states
try:
await asyncio.wait_for(states_future, timeout=10.0)
except asyncio.TimeoutError:
pytest.fail(
f"Did not receive all sensor states within 10 seconds. "
f"Received {len(states)} states"
)
# Verify we have sensor entities with proper device_id assignments
device_id_mapping = {
"Light Controller Sensor": light_controller.device_id,
"Temperature Sensor Reading": temp_sensor.device_id,
"Motion Detector Status": motion_detector.device_id,
"Smart Switch Power": smart_switch.device_id,
}
for entity in sensor_entities:
if entity.name in device_id_mapping:
expected_device_id = device_id_mapping[entity.name]
assert entity.device_id == expected_device_id, (
f"{entity.name} has device_id {entity.device_id}, "
f"expected {expected_device_id}"
)

View File

@ -0,0 +1,184 @@
"""Integration test for duplicate entity handling with new validation."""
from __future__ import annotations
import asyncio
from aioesphomeapi import EntityInfo
import pytest
from .types import APIClientConnectedFactory, RunCompiledFunction
@pytest.mark.asyncio
async def test_duplicate_entities_on_different_devices(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""Test that duplicate entity names are allowed on different devices."""
async with run_compiled(yaml_config), api_client_connected() as client:
# Get device info
device_info = await client.device_info()
assert device_info is not None
# Get devices
devices = device_info.devices
assert len(devices) >= 3, f"Expected at least 3 devices, got {len(devices)}"
# Find our test devices
controller_1 = next((d for d in devices if d.name == "Controller 1"), None)
controller_2 = next((d for d in devices if d.name == "Controller 2"), None)
controller_3 = next((d for d in devices if d.name == "Controller 3"), None)
assert controller_1 is not None, "Controller 1 device not found"
assert controller_2 is not None, "Controller 2 device not found"
assert controller_3 is not None, "Controller 3 device not found"
# Get entity list
entities = await client.list_entities_services()
all_entities: list[EntityInfo] = []
for entity_list in entities[0]:
all_entities.append(entity_list)
# Group entities by type for easier testing
sensors = [e for e in all_entities if e.__class__.__name__ == "SensorInfo"]
binary_sensors = [
e for e in all_entities if e.__class__.__name__ == "BinarySensorInfo"
]
text_sensors = [
e for e in all_entities if e.__class__.__name__ == "TextSensorInfo"
]
switches = [e for e in all_entities if e.__class__.__name__ == "SwitchInfo"]
buttons = [e for e in all_entities if e.__class__.__name__ == "ButtonInfo"]
numbers = [e for e in all_entities if e.__class__.__name__ == "NumberInfo"]
# Scenario 1: Check sensors with same "Temperature" name on different devices
temp_sensors = [s for s in sensors if s.name == "Temperature"]
assert len(temp_sensors) == 4, (
f"Expected exactly 4 temperature sensors, got {len(temp_sensors)}"
)
# Verify each sensor is on a different device
temp_device_ids = set()
temp_object_ids = set()
for sensor in temp_sensors:
temp_device_ids.add(sensor.device_id)
temp_object_ids.add(sensor.object_id)
# All should have object_id "temperature" (no suffix)
assert sensor.object_id == "temperature", (
f"Expected object_id 'temperature', got '{sensor.object_id}'"
)
# Should have 4 different device IDs (including None for main device)
assert len(temp_device_ids) == 4, (
f"Temperature sensors should be on different devices, got {temp_device_ids}"
)
# Scenario 2: Check binary sensors "Status" on different devices
status_binary = [b for b in binary_sensors if b.name == "Status"]
assert len(status_binary) == 3, (
f"Expected exactly 3 status binary sensors, got {len(status_binary)}"
)
# All should have object_id "status"
for binary in status_binary:
assert binary.object_id == "status", (
f"Expected object_id 'status', got '{binary.object_id}'"
)
# Scenario 3: Check that sensor and binary_sensor can have same name
temp_binary = [b for b in binary_sensors if b.name == "Temperature"]
assert len(temp_binary) == 1, (
f"Expected exactly 1 temperature binary sensor, got {len(temp_binary)}"
)
assert temp_binary[0].object_id == "temperature"
# Scenario 4: Check text sensors "Device Info" on different devices
info_text = [t for t in text_sensors if t.name == "Device Info"]
assert len(info_text) == 3, (
f"Expected exactly 3 device info text sensors, got {len(info_text)}"
)
# All should have object_id "device_info"
for text in info_text:
assert text.object_id == "device_info", (
f"Expected object_id 'device_info', got '{text.object_id}'"
)
# Scenario 5: Check switches "Power" on different devices
power_switches = [s for s in switches if s.name == "Power"]
assert len(power_switches) == 3, (
f"Expected exactly 3 power switches, got {len(power_switches)}"
)
# All should have object_id "power"
for switch in power_switches:
assert switch.object_id == "power", (
f"Expected object_id 'power', got '{switch.object_id}'"
)
# Scenario 6: Check empty name buttons (should use device name)
empty_buttons = [b for b in buttons if b.name == ""]
assert len(empty_buttons) == 3, (
f"Expected exactly 3 empty name buttons, got {len(empty_buttons)}"
)
# Group by device
c1_buttons = [b for b in empty_buttons if b.device_id == controller_1.device_id]
c2_buttons = [b for b in empty_buttons if b.device_id == controller_2.device_id]
# For main device, device_id is 0
main_buttons = [b for b in empty_buttons if b.device_id == 0]
# Check object IDs for empty name entities
assert len(c1_buttons) == 1 and c1_buttons[0].object_id == "controller_1"
assert len(c2_buttons) == 1 and c2_buttons[0].object_id == "controller_2"
assert (
len(main_buttons) == 1
and main_buttons[0].object_id == "duplicate-entities-test"
)
# Scenario 7: Check special characters in number names
temp_numbers = [n for n in numbers if n.name == "Temperature Setpoint!"]
assert len(temp_numbers) == 2, (
f"Expected exactly 2 temperature setpoint numbers, got {len(temp_numbers)}"
)
# Special characters should be sanitized to _ in object_id
for number in temp_numbers:
assert number.object_id == "temperature_setpoint_", (
f"Expected object_id 'temperature_setpoint_', got '{number.object_id}'"
)
# Verify we can get states for all entities (ensures they're functional)
loop = asyncio.get_running_loop()
states_future: asyncio.Future[None] = loop.create_future()
state_count = 0
expected_count = (
len(sensors)
+ len(binary_sensors)
+ len(text_sensors)
+ len(switches)
+ len(buttons)
+ len(numbers)
)
def on_state(state) -> None:
nonlocal state_count
state_count += 1
if state_count >= expected_count and not states_future.done():
states_future.set_result(None)
client.subscribe_states(on_state)
# Wait for all entity 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"Expected {expected_count}, received {state_count}"
)

View File

@ -0,0 +1,41 @@
"""Integration test for legacy string-based area configuration."""
from __future__ import annotations
import pytest
from .types import APIClientConnectedFactory, RunCompiledFunction
@pytest.mark.asyncio
async def test_legacy_area(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""Test legacy string-based area configuration."""
async with run_compiled(yaml_config), api_client_connected() as client:
# Get device info which includes areas
device_info = await client.device_info()
assert device_info is not None
# Verify the area is reported (should be converted to structured format)
areas = device_info.areas
assert len(areas) == 1, f"Expected exactly 1 area, got {len(areas)}"
# Find the area - should be slugified from "Master Bedroom"
area = areas[0]
assert area.name == "Master Bedroom", (
f"Expected area name 'Master Bedroom', got '{area.name}'"
)
# Verify area.id is set (it should be a hash)
assert area.area_id > 0, "Area ID should be a positive hash value"
# The suggested_area field should be set for backward compatibility
assert device_info.suggested_area == "Master Bedroom", (
f"Expected suggested_area to be 'Master Bedroom', got '{device_info.suggested_area}'"
)
# Verify deprecated warning would have been logged during compilation
# (We can't check logs directly in integration tests, but the code should work)

View File

@ -14,6 +14,8 @@ import sys
import pytest import pytest
from esphome.core import CORE
here = Path(__file__).parent here = Path(__file__).parent
# Configure location of package root # Configure location of package root
@ -21,6 +23,13 @@ package_root = here.parent.parent
sys.path.insert(0, package_root.as_posix()) sys.path.insert(0, package_root.as_posix())
@pytest.fixture(autouse=True)
def reset_core():
"""Reset CORE after each test."""
yield
CORE.reset()
@pytest.fixture @pytest.fixture
def fixture_path() -> Path: def fixture_path() -> Path:
""" """

View File

View File

@ -0,0 +1,33 @@
"""Common test utilities for core unit tests."""
from collections.abc import Callable
from pathlib import Path
from unittest.mock import patch
from esphome import config, yaml_util
from esphome.config import Config
from esphome.core import CORE
def load_config_from_yaml(
yaml_file: Callable[[str], str], yaml_content: str
) -> Config | None:
"""Load configuration from YAML content."""
yaml_path = yaml_file(yaml_content)
parsed_yaml = yaml_util.load_yaml(yaml_path)
# Mock yaml_util.load_yaml to return our parsed content
with (
patch.object(yaml_util, "load_yaml", return_value=parsed_yaml),
patch.object(CORE, "config_path", yaml_path),
):
return config.read_config({})
def load_config_from_fixture(
yaml_file: Callable[[str], str], fixture_name: str, fixtures_dir: Path
) -> Config | None:
"""Load configuration from a fixture file."""
fixture_path = fixtures_dir / fixture_name
yaml_content = fixture_path.read_text()
return load_config_from_yaml(yaml_file, yaml_content)

View File

@ -0,0 +1,18 @@
"""Shared fixtures for core unit tests."""
from collections.abc import Callable
from pathlib import Path
import pytest
@pytest.fixture
def yaml_file(tmp_path: Path) -> Callable[[str], str]:
"""Create a temporary YAML file for testing."""
def _yaml_file(content: str) -> str:
yaml_path = tmp_path / "test.yaml"
yaml_path.write_text(content)
return str(yaml_path)
return _yaml_file

View File

@ -0,0 +1,225 @@
"""Unit tests for core config functionality including areas and devices."""
from collections.abc import Callable
from pathlib import Path
from typing import Any
import pytest
from esphome import config_validation as cv, core
from esphome.const import CONF_AREA, CONF_AREAS, CONF_DEVICES
from esphome.core.config import Area, validate_area_config
from .common import load_config_from_fixture
FIXTURES_DIR = Path(__file__).parent.parent / "fixtures" / "core" / "config"
def test_validate_area_config_with_string() -> None:
"""Test that string area config is converted to structured format."""
result = validate_area_config("Living Room")
assert isinstance(result, dict)
assert "id" in result
assert "name" in result
assert result["name"] == "Living Room"
assert isinstance(result["id"], core.ID)
assert result["id"].is_declaration
assert not result["id"].is_manual
def test_validate_area_config_with_dict() -> None:
"""Test that structured area config passes through unchanged."""
area_id = cv.declare_id(Area)("test_area")
input_config: dict[str, Any] = {
"id": area_id,
"name": "Test Area",
}
result = validate_area_config(input_config)
assert result == input_config
assert result["id"] == area_id
assert result["name"] == "Test Area"
def test_device_with_valid_area_id(yaml_file: Callable[[str], str]) -> None:
"""Test that device with valid area_id works correctly."""
result = load_config_from_fixture(yaml_file, "valid_area_device.yaml", FIXTURES_DIR)
assert result is not None
esphome_config = result["esphome"]
# Verify areas were parsed correctly
assert CONF_AREAS in esphome_config
areas = esphome_config[CONF_AREAS]
assert len(areas) == 1
assert areas[0]["id"].id == "bedroom_area"
assert areas[0]["name"] == "Bedroom"
# Verify devices were parsed correctly
assert CONF_DEVICES in esphome_config
devices = esphome_config[CONF_DEVICES]
assert len(devices) == 1
assert devices[0]["id"].id == "test_device"
assert devices[0]["name"] == "Test Device"
assert devices[0]["area_id"].id == "bedroom_area"
def test_multiple_areas_and_devices(yaml_file: Callable[[str], str]) -> None:
"""Test multiple areas and devices configuration."""
result = load_config_from_fixture(
yaml_file, "multiple_areas_devices.yaml", FIXTURES_DIR
)
assert result is not None
esphome_config = result["esphome"]
# Verify main area
assert CONF_AREA in esphome_config
main_area = esphome_config[CONF_AREA]
assert main_area["id"].id == "main_area"
assert main_area["name"] == "Main Area"
# Verify additional areas
assert CONF_AREAS in esphome_config
areas = esphome_config[CONF_AREAS]
assert len(areas) == 2
area_ids = {area["id"].id for area in areas}
assert area_ids == {"area1", "area2"}
# Verify devices
assert CONF_DEVICES in esphome_config
devices = esphome_config[CONF_DEVICES]
assert len(devices) == 3
# Check device-area associations
device_area_map = {dev["id"].id: dev["area_id"].id for dev in devices}
assert device_area_map == {
"device1": "main_area",
"device2": "area1",
"device3": "area2",
}
def test_legacy_string_area(
yaml_file: Callable[[str], str], caplog: pytest.LogCaptureFixture
) -> None:
"""Test legacy string area configuration with deprecation warning."""
result = load_config_from_fixture(
yaml_file, "legacy_string_area.yaml", FIXTURES_DIR
)
assert result is not None
esphome_config = result["esphome"]
# Verify the string was converted to structured format
assert CONF_AREA in esphome_config
area = esphome_config[CONF_AREA]
assert isinstance(area, dict)
assert area["name"] == "Living Room"
assert isinstance(area["id"], core.ID)
assert area["id"].is_declaration
assert not area["id"].is_manual
def test_area_id_collision(
yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str]
) -> None:
"""Test that duplicate area IDs are detected."""
result = load_config_from_fixture(yaml_file, "area_id_collision.yaml", FIXTURES_DIR)
assert result is None
# Check for the specific error message in stdout
captured = capsys.readouterr()
# Exact duplicates are now caught by IDPassValidationStep
assert "ID duplicate_id redefined! Check esphome->area->id." in captured.out
def test_device_without_area(yaml_file: Callable[[str], str]) -> None:
"""Test that devices without area_id work correctly."""
result = load_config_from_fixture(
yaml_file, "device_without_area.yaml", FIXTURES_DIR
)
assert result is not None
esphome_config = result["esphome"]
# Verify device was parsed
assert CONF_DEVICES in esphome_config
devices = esphome_config[CONF_DEVICES]
assert len(devices) == 1
device = devices[0]
assert device["id"].id == "test_device"
assert device["name"] == "Test Device"
# Verify no area_id is present
assert "area_id" not in device
def test_device_with_invalid_area_id(
yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str]
) -> None:
"""Test that device with non-existent area_id fails validation."""
result = load_config_from_fixture(
yaml_file, "device_invalid_area.yaml", FIXTURES_DIR
)
assert result is None
# Check for the specific error message in stdout
captured = capsys.readouterr()
assert (
"Couldn't find ID 'nonexistent_area'. Please check you have defined an ID with that name in your configuration."
in captured.out
)
def test_device_id_hash_collision(
yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str]
) -> None:
"""Test that device IDs with hash collisions are detected."""
result = load_config_from_fixture(
yaml_file, "device_id_collision.yaml", FIXTURES_DIR
)
assert result is None
# Check for the specific error message about hash collision
captured = capsys.readouterr()
# The error message shows the ID that collides and includes the hash value
assert (
"Device ID 'd6ka' with hash 3082558663 collides with existing device ID 'test_2258'"
in captured.out
)
def test_area_id_hash_collision(
yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str]
) -> None:
"""Test that area IDs with hash collisions are detected."""
result = load_config_from_fixture(
yaml_file, "area_id_hash_collision.yaml", FIXTURES_DIR
)
assert result is None
# Check for the specific error message about hash collision
captured = capsys.readouterr()
# The error message shows the ID that collides and includes the hash value
assert (
"Area ID 'd6ka' with hash 3082558663 collides with existing area ID 'test_2258'"
in captured.out
)
def test_device_duplicate_id(
yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str]
) -> None:
"""Test that duplicate device IDs are detected by IDPassValidationStep."""
result = load_config_from_fixture(
yaml_file, "device_duplicate_id.yaml", FIXTURES_DIR
)
assert result is None
# Check for the specific error message from IDPassValidationStep
captured = capsys.readouterr()
assert "ID duplicate_device redefined!" in captured.out

View File

@ -0,0 +1,595 @@
"""Test get_base_entity_object_id function matches C++ behavior."""
from collections.abc import Callable, Generator
from pathlib import Path
import re
from typing import Any
import pytest
from esphome.config_validation import Invalid
from esphome.const import CONF_DEVICE_ID, CONF_DISABLED_BY_DEFAULT, CONF_ICON, CONF_NAME
from esphome.core import CORE, ID, entity_helpers
from esphome.core.entity_helpers import get_base_entity_object_id, setup_entity
from esphome.cpp_generator import MockObj
from esphome.helpers import sanitize, snake_case
from .common import load_config_from_fixture
# Pre-compiled regex pattern for extracting object IDs from expressions
OBJECT_ID_PATTERN = re.compile(r'\.set_object_id\(["\'](.*?)["\']\)')
FIXTURES_DIR = Path(__file__).parent.parent / "fixtures" / "core" / "entity_helpers"
@pytest.fixture(autouse=True)
def restore_core_state() -> Generator[None, None, None]:
"""Save and restore CORE state for tests."""
original_name = CORE.name
original_friendly_name = CORE.friendly_name
yield
CORE.name = original_name
CORE.friendly_name = original_friendly_name
def test_with_entity_name() -> None:
"""Test when entity has its own name - should use entity name."""
# Simple name
assert get_base_entity_object_id("Temperature Sensor", None) == "temperature_sensor"
assert (
get_base_entity_object_id("Temperature Sensor", "Device Name")
== "temperature_sensor"
)
# Even with device name, entity name takes precedence
assert (
get_base_entity_object_id("Temperature Sensor", "Device Name", "Sub Device")
== "temperature_sensor"
)
# Name with special characters
assert (
get_base_entity_object_id("Temp!@#$%^&*()Sensor", None)
== "temp__________sensor"
)
assert get_base_entity_object_id("Temp-Sensor_123", None) == "temp-sensor_123"
# Already snake_case
assert get_base_entity_object_id("temperature_sensor", None) == "temperature_sensor"
# Mixed case
assert get_base_entity_object_id("TemperatureSensor", None) == "temperaturesensor"
assert get_base_entity_object_id("TEMPERATURE SENSOR", None) == "temperature_sensor"
def test_empty_name_with_device_name() -> None:
"""Test when entity has empty name and is on a sub-device - should use device name."""
# C++ behavior: when has_own_name is false and device is set, uses device->get_name()
assert (
get_base_entity_object_id("", "Friendly Device", "Sub Device 1")
== "sub_device_1"
)
assert (
get_base_entity_object_id("", "Kitchen Controller", "controller_1")
== "controller_1"
)
assert get_base_entity_object_id("", None, "Test-Device_123") == "test-device_123"
def test_empty_name_with_friendly_name() -> None:
"""Test when entity has empty name and no device - should use friendly name."""
# C++ behavior: when has_own_name is false, uses App.get_friendly_name()
assert get_base_entity_object_id("", "Friendly Device") == "friendly_device"
assert get_base_entity_object_id("", "Kitchen Controller") == "kitchen_controller"
assert get_base_entity_object_id("", "Test-Device_123") == "test-device_123"
# Special characters in friendly name
assert get_base_entity_object_id("", "Device!@#$%") == "device_____"
def test_empty_name_no_friendly_name() -> None:
"""Test when entity has empty name and no friendly name - should use device name."""
# Test with CORE.name set
CORE.name = "device-name"
assert get_base_entity_object_id("", None) == "device-name"
CORE.name = "Test Device"
assert get_base_entity_object_id("", None) == "test_device"
def test_edge_cases() -> None:
"""Test edge cases."""
# Only spaces
assert get_base_entity_object_id(" ", None) == "___"
# Unicode characters (should be replaced)
assert get_base_entity_object_id("Température", None) == "temp_rature"
assert get_base_entity_object_id("测试", None) == "__"
# Empty string with empty friendly name (empty friendly name is treated as None)
# Falls back to CORE.name
CORE.name = "device"
assert get_base_entity_object_id("", "") == "device"
# Very long name (should work fine)
long_name = "a" * 100 + " " + "b" * 100
expected = "a" * 100 + "_" + "b" * 100
assert get_base_entity_object_id(long_name, None) == expected
@pytest.mark.parametrize(
("name", "expected"),
[
("Temperature Sensor", "temperature_sensor"),
("Living Room Light", "living_room_light"),
("Test-Device_123", "test-device_123"),
("Special!@#Chars", "special___chars"),
("UPPERCASE NAME", "uppercase_name"),
("lowercase name", "lowercase_name"),
("Mixed Case Name", "mixed_case_name"),
(" Spaces ", "___spaces___"),
],
)
def test_matches_cpp_helpers(name: str, expected: str) -> None:
"""Test that the logic matches using snake_case and sanitize directly."""
# For non-empty names, verify our function produces same result as direct snake_case + sanitize
assert get_base_entity_object_id(name, None) == sanitize(snake_case(name))
assert get_base_entity_object_id(name, None) == expected
def test_empty_name_fallback() -> None:
"""Test empty name handling which falls back to friendly_name or CORE.name."""
# Empty name is handled specially - it doesn't just use sanitize(snake_case(""))
# Instead it falls back to friendly_name or CORE.name
assert sanitize(snake_case("")) == "" # Direct conversion gives empty string
# But our function returns a fallback
CORE.name = "device"
assert get_base_entity_object_id("", None) == "device" # Uses device name
def test_name_add_mac_suffix_behavior() -> None:
"""Test behavior related to name_add_mac_suffix.
In C++, when name_add_mac_suffix is enabled and entity has no name,
get_object_id() returns str_sanitize(str_snake_case(App.get_friendly_name()))
dynamically. Our function always returns the same result since we're
calculating the base for duplicate tracking.
"""
# The function should always return the same result regardless of
# name_add_mac_suffix setting, as we're calculating the base object_id
assert get_base_entity_object_id("", "Test Device") == "test_device"
assert get_base_entity_object_id("Entity Name", "Test Device") == "entity_name"
def test_priority_order() -> None:
"""Test the priority order: entity name > device name > friendly name > CORE.name."""
CORE.name = "core-device"
# 1. Entity name has highest priority
assert (
get_base_entity_object_id("Entity Name", "Friendly Name", "Device Name")
== "entity_name"
)
# 2. Device name is next priority (when entity name is empty)
assert (
get_base_entity_object_id("", "Friendly Name", "Device Name") == "device_name"
)
# 3. Friendly name is next (when entity and device names are empty)
assert get_base_entity_object_id("", "Friendly Name", None) == "friendly_name"
# 4. CORE.name is last resort
assert get_base_entity_object_id("", None, None) == "core-device"
@pytest.mark.parametrize(
("name", "friendly_name", "device_name", "expected"),
[
# name, friendly_name, device_name, expected
("Living Room Light", None, None, "living_room_light"),
("", "Kitchen Controller", None, "kitchen_controller"),
(
"",
"ESP32 Device",
"controller_1",
"controller_1",
), # Device name takes precedence
("GPIO2 Button", None, None, "gpio2_button"),
("WiFi Signal", "My Device", None, "wifi_signal"),
("", None, "esp32_node", "esp32_node"),
("Front Door Sensor", "Home Assistant", "door_controller", "front_door_sensor"),
],
)
def test_real_world_examples(
name: str, friendly_name: str | None, device_name: str | None, expected: str
) -> None:
"""Test real-world entity naming scenarios."""
result = get_base_entity_object_id(name, friendly_name, device_name)
assert result == expected
def test_issue_6953_scenarios() -> None:
"""Test specific scenarios from issue #6953."""
# Scenario 1: Multiple empty names on main device with name_add_mac_suffix
# The Python code calculates the base, C++ might append MAC suffix dynamically
CORE.name = "device-name"
CORE.friendly_name = "Friendly Device"
# All empty names should resolve to same base
assert get_base_entity_object_id("", CORE.friendly_name) == "friendly_device"
assert get_base_entity_object_id("", CORE.friendly_name) == "friendly_device"
assert get_base_entity_object_id("", CORE.friendly_name) == "friendly_device"
# Scenario 2: Empty names on sub-devices
assert (
get_base_entity_object_id("", "Main Device", "controller_1") == "controller_1"
)
assert (
get_base_entity_object_id("", "Main Device", "controller_2") == "controller_2"
)
# Scenario 3: xyz duplicates
assert get_base_entity_object_id("xyz", None) == "xyz"
assert get_base_entity_object_id("xyz", "Device") == "xyz"
# Tests for setup_entity function
@pytest.fixture
def setup_test_environment() -> Generator[list[str], None, None]:
"""Set up test environment for setup_entity tests."""
# Set CORE state for tests
CORE.name = "test-device"
CORE.friendly_name = "Test Device"
# Store original add function
original_add = entity_helpers.add
# Track what gets added
added_expressions: list[str] = []
def mock_add(expression: Any) -> Any:
added_expressions.append(str(expression))
return original_add(expression)
# Patch add function in entity_helpers module
entity_helpers.add = mock_add
yield added_expressions
# Clean up
entity_helpers.add = original_add
def extract_object_id_from_expressions(expressions: list[str]) -> str | None:
"""Extract the object ID that was set from the generated expressions."""
for expr in expressions:
# Look for set_object_id calls with regex to handle various formats
# Matches: var.set_object_id("temperature_2") or var.set_object_id('temperature_2')
if match := OBJECT_ID_PATTERN.search(expr):
return match.group(1)
return None
@pytest.mark.asyncio
async def test_setup_entity_no_duplicates(setup_test_environment: list[str]) -> None:
"""Test setup_entity with unique names."""
added_expressions = setup_test_environment
# Create mock entities
var1 = MockObj("sensor1")
var2 = MockObj("sensor2")
# Set up first entity
config1 = {
CONF_NAME: "Temperature",
CONF_DISABLED_BY_DEFAULT: False,
}
await setup_entity(var1, config1, "sensor")
# Get object ID from first entity
object_id1 = extract_object_id_from_expressions(added_expressions)
assert object_id1 == "temperature"
# Clear for next entity
added_expressions.clear()
# Set up second entity with different name
config2 = {
CONF_NAME: "Humidity",
CONF_DISABLED_BY_DEFAULT: False,
}
await setup_entity(var2, config2, "sensor")
# Get object ID from second entity
object_id2 = extract_object_id_from_expressions(added_expressions)
assert object_id2 == "humidity"
@pytest.mark.asyncio
async def test_setup_entity_different_platforms(
setup_test_environment: list[str],
) -> None:
"""Test that same name on different platforms doesn't conflict."""
added_expressions = setup_test_environment
# Create mock entities
sensor = MockObj("sensor1")
binary_sensor = MockObj("binary_sensor1")
text_sensor = MockObj("text_sensor1")
config = {
CONF_NAME: "Status",
CONF_DISABLED_BY_DEFAULT: False,
}
# Set up entities on different platforms
platforms = [
(sensor, "sensor"),
(binary_sensor, "binary_sensor"),
(text_sensor, "text_sensor"),
]
object_ids: list[str] = []
for var, platform in platforms:
added_expressions.clear()
await setup_entity(var, config, platform)
object_id = extract_object_id_from_expressions(added_expressions)
object_ids.append(object_id)
# All should get base object ID without suffix
assert all(obj_id == "status" for obj_id in object_ids)
@pytest.fixture
def mock_get_variable() -> Generator[dict[ID, MockObj], None, None]:
"""Mock get_variable to return test devices."""
devices = {}
original_get_variable = entity_helpers.get_variable
async def _mock_get_variable(device_id: ID) -> MockObj:
if device_id in devices:
return devices[device_id]
return await original_get_variable(device_id)
entity_helpers.get_variable = _mock_get_variable
yield devices
# Clean up
entity_helpers.get_variable = original_get_variable
@pytest.mark.asyncio
async def test_setup_entity_with_devices(
setup_test_environment: list[str], mock_get_variable: dict[ID, MockObj]
) -> None:
"""Test that same name on different devices doesn't conflict."""
added_expressions = setup_test_environment
# Create mock devices
device1_id = ID("device1", type="Device")
device2_id = ID("device2", type="Device")
device1 = MockObj("device1_obj")
device2 = MockObj("device2_obj")
# Register devices with the mock
mock_get_variable[device1_id] = device1
mock_get_variable[device2_id] = device2
# Create sensors with same name on different devices
sensor1 = MockObj("sensor1")
sensor2 = MockObj("sensor2")
config1 = {
CONF_NAME: "Temperature",
CONF_DEVICE_ID: device1_id,
CONF_DISABLED_BY_DEFAULT: False,
}
config2 = {
CONF_NAME: "Temperature",
CONF_DEVICE_ID: device2_id,
CONF_DISABLED_BY_DEFAULT: False,
}
# Get object IDs
object_ids: list[str] = []
for var, config in [(sensor1, config1), (sensor2, config2)]:
added_expressions.clear()
await setup_entity(var, config, "sensor")
object_id = extract_object_id_from_expressions(added_expressions)
object_ids.append(object_id)
# Both should get base object ID without suffix (different devices)
assert object_ids[0] == "temperature"
assert object_ids[1] == "temperature"
@pytest.mark.asyncio
async def test_setup_entity_empty_name(setup_test_environment: list[str]) -> None:
"""Test setup_entity with empty entity name."""
added_expressions = setup_test_environment
var = MockObj("sensor1")
config = {
CONF_NAME: "",
CONF_DISABLED_BY_DEFAULT: False,
}
await setup_entity(var, config, "sensor")
object_id = extract_object_id_from_expressions(added_expressions)
# Should use friendly name
assert object_id == "test_device"
@pytest.mark.asyncio
async def test_setup_entity_special_characters(
setup_test_environment: list[str],
) -> None:
"""Test setup_entity with names containing special characters."""
added_expressions = setup_test_environment
var = MockObj("sensor1")
config = {
CONF_NAME: "Temperature Sensor!",
CONF_DISABLED_BY_DEFAULT: False,
}
await setup_entity(var, config, "sensor")
object_id = extract_object_id_from_expressions(added_expressions)
# Special characters should be sanitized
assert object_id == "temperature_sensor_"
@pytest.mark.asyncio
async def test_setup_entity_with_icon(setup_test_environment: list[str]) -> None:
"""Test setup_entity sets icon correctly."""
added_expressions = setup_test_environment
var = MockObj("sensor1")
config = {
CONF_NAME: "Temperature",
CONF_DISABLED_BY_DEFAULT: False,
CONF_ICON: "mdi:thermometer",
}
await setup_entity(var, config, "sensor")
# Check icon was set
assert any(
'sensor1.set_icon("mdi:thermometer")' in expr for expr in added_expressions
)
@pytest.mark.asyncio
async def test_setup_entity_disabled_by_default(
setup_test_environment: list[str],
) -> None:
"""Test setup_entity sets disabled_by_default correctly."""
added_expressions = setup_test_environment
var = MockObj("sensor1")
config = {
CONF_NAME: "Temperature",
CONF_DISABLED_BY_DEFAULT: True,
}
await setup_entity(var, config, "sensor")
# Check disabled_by_default was set
assert any(
"sensor1.set_disabled_by_default(true)" in expr for expr in added_expressions
)
def test_entity_duplicate_validator() -> None:
"""Test the entity_duplicate_validator function."""
from esphome.core.entity_helpers import entity_duplicate_validator
# Reset CORE unique_ids for clean test
CORE.unique_ids.clear()
# Create validator for sensor platform
validator = entity_duplicate_validator("sensor")
# First entity should pass
config1 = {CONF_NAME: "Temperature"}
validated1 = validator(config1)
assert validated1 == config1
assert ("", "sensor", "temperature") in CORE.unique_ids
# Second entity with different name should pass
config2 = {CONF_NAME: "Humidity"}
validated2 = validator(config2)
assert validated2 == config2
assert ("", "sensor", "humidity") in CORE.unique_ids
# Duplicate entity should fail
config3 = {CONF_NAME: "Temperature"}
with pytest.raises(
Invalid, match=r"Duplicate sensor entity with name 'Temperature' found"
):
validator(config3)
def test_entity_duplicate_validator_with_devices() -> None:
"""Test entity_duplicate_validator with devices."""
from esphome.core.entity_helpers import entity_duplicate_validator
# Reset CORE unique_ids for clean test
CORE.unique_ids.clear()
# Create validator for sensor platform
validator = entity_duplicate_validator("sensor")
# Create mock device IDs
device1 = ID("device1", type="Device")
device2 = ID("device2", type="Device")
# Same name on different devices should pass
config1 = {CONF_NAME: "Temperature", CONF_DEVICE_ID: device1}
validated1 = validator(config1)
assert validated1 == config1
assert ("device1", "sensor", "temperature") in CORE.unique_ids
config2 = {CONF_NAME: "Temperature", CONF_DEVICE_ID: device2}
validated2 = validator(config2)
assert validated2 == config2
assert ("device2", "sensor", "temperature") in CORE.unique_ids
# Duplicate on same device should fail
config3 = {CONF_NAME: "Temperature", CONF_DEVICE_ID: device1}
with pytest.raises(
Invalid,
match=r"Duplicate sensor entity with name 'Temperature' found on device 'device1'",
):
validator(config3)
def test_duplicate_entity_yaml_validation(
yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str]
) -> None:
"""Test that duplicate entity names are caught during YAML config validation."""
result = load_config_from_fixture(yaml_file, "duplicate_entity.yaml", FIXTURES_DIR)
assert result is None
# Check for the duplicate entity error message
captured = capsys.readouterr()
assert "Duplicate sensor entity with name 'Temperature' found" in captured.out
def test_duplicate_entity_with_devices_yaml_validation(
yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str]
) -> None:
"""Test duplicate entity validation with devices."""
result = load_config_from_fixture(
yaml_file, "duplicate_entity_with_devices.yaml", FIXTURES_DIR
)
assert result is None
# Check for the duplicate entity error message with device
captured = capsys.readouterr()
assert (
"Duplicate sensor entity with name 'Temperature' found on device 'device1'"
in captured.out
)
def test_entity_different_platforms_yaml_validation(
yaml_file: Callable[[str], str],
) -> None:
"""Test that same entity name on different platforms is allowed."""
result = load_config_from_fixture(
yaml_file, "entity_different_platforms.yaml", FIXTURES_DIR
)
# This should succeed
assert result is not None

View File

@ -0,0 +1,10 @@
esphome:
name: test-collision
area:
id: duplicate_id
name: Area 1
areas:
- id: duplicate_id
name: Area 2
host:

View File

@ -0,0 +1,10 @@
esphome:
name: test
areas:
- id: test_2258
name: "Area 1"
- id: d6ka
name: "Area 2"
esp32:
board: esp32dev

View File

@ -0,0 +1,10 @@
esphome:
name: test
devices:
- id: duplicate_device
name: "Device 1"
- id: duplicate_device
name: "Device 2"
esp32:
board: esp32dev

View File

@ -0,0 +1,10 @@
esphome:
name: test
devices:
- id: test_2258
name: "Device 1"
- id: d6ka
name: "Device 2"
esp32:
board: esp32dev

View File

@ -0,0 +1,12 @@
esphome:
name: test
areas:
- id: valid_area
name: "Valid Area"
devices:
- id: test_device
name: "Test Device"
area_id: nonexistent_area
esp32:
board: esp32dev

View File

@ -0,0 +1,7 @@
esphome:
name: test-device-no-area
devices:
- id: test_device
name: Test Device
host:

View File

@ -0,0 +1,5 @@
esphome:
name: test-legacy-area
area: Living Room
host:

View File

@ -0,0 +1,22 @@
esphome:
name: test-multiple
area:
id: main_area
name: Main Area
areas:
- id: area1
name: Area 1
- id: area2
name: Area 2
devices:
- id: device1
name: Device 1
area_id: main_area
- id: device2
name: Device 2
area_id: area1
- id: device3
name: Device 3
area_id: area2
host:

View File

@ -0,0 +1,11 @@
esphome:
name: test-valid-area
areas:
- id: bedroom_area
name: Bedroom
devices:
- id: test_device
name: Test Device
area_id: bedroom_area
host:

View File

@ -0,0 +1,13 @@
esphome:
name: test-duplicate
esp32:
board: esp32dev
sensor:
- platform: template
name: "Temperature"
lambda: return 21.0;
- platform: template
name: "Temperature" # Duplicate - should fail
lambda: return 22.0;

View File

@ -0,0 +1,26 @@
esphome:
name: test-duplicate-devices
devices:
- id: device1
name: "Device 1"
- id: device2
name: "Device 2"
esp32:
board: esp32dev
sensor:
# Same name on different devices - should pass
- platform: template
device_id: device1
name: "Temperature"
lambda: return 21.0;
- platform: template
device_id: device2
name: "Temperature"
lambda: return 22.0;
# Duplicate on same device - should fail
- platform: template
device_id: device1
name: "Temperature"
lambda: return 23.0;

View File

@ -0,0 +1,20 @@
esphome:
name: test-different-platforms
esp32:
board: esp32dev
sensor:
- platform: template
name: "Status"
lambda: return 1.0;
binary_sensor:
- platform: template
name: "Status" # Same name, different platform - should pass
lambda: return true;
text_sensor:
- platform: template
name: "Status" # Same name, different platform - should pass
lambda: return {"OK"};