mirror of
https://github.com/esphome/esphome.git
synced 2025-07-27 21:56:34 +00:00
[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:
parent
7c28134214
commit
b18ff48b4a
@ -14,8 +14,8 @@ from esphome.const import (
|
||||
CONF_WEB_SERVER,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@grahambrown11", "@hwstar"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@ -149,6 +149,9 @@ _ALARM_CONTROL_PANEL_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_ALARM_CONTROL_PANEL_SCHEMA.add_extra(entity_duplicate_validator("alarm_control_panel"))
|
||||
|
||||
|
||||
def alarm_control_panel_schema(
|
||||
class_: MockObjClass,
|
||||
*,
|
||||
@ -190,7 +193,7 @@ ALARM_CONTROL_PANEL_CONDITION_SCHEMA = maybe_simple_id(
|
||||
|
||||
|
||||
async def setup_alarm_control_panel_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "alarm_control_panel")
|
||||
for conf in config.get(CONF_ON_STATE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
@ -188,6 +188,17 @@ message DeviceInfoRequest {
|
||||
// Empty
|
||||
}
|
||||
|
||||
message AreaInfo {
|
||||
uint32 area_id = 1;
|
||||
string name = 2;
|
||||
}
|
||||
|
||||
message DeviceInfo {
|
||||
uint32 device_id = 1;
|
||||
string name = 2;
|
||||
uint32 area_id = 3;
|
||||
}
|
||||
|
||||
message DeviceInfoResponse {
|
||||
option (id) = 10;
|
||||
option (source) = SOURCE_SERVER;
|
||||
@ -236,6 +247,12 @@ message DeviceInfoResponse {
|
||||
|
||||
// Supports receiving and saving api encryption key
|
||||
bool api_encryption_supported = 19;
|
||||
|
||||
repeated DeviceInfo devices = 20;
|
||||
repeated AreaInfo areas = 21;
|
||||
|
||||
// Top-level area info to phase out suggested_area
|
||||
AreaInfo area = 22;
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
@ -280,6 +297,7 @@ message ListEntitiesBinarySensorResponse {
|
||||
bool disabled_by_default = 7;
|
||||
string icon = 8;
|
||||
EntityCategory entity_category = 9;
|
||||
uint32 device_id = 10;
|
||||
}
|
||||
message BinarySensorStateResponse {
|
||||
option (id) = 21;
|
||||
@ -315,6 +333,7 @@ message ListEntitiesCoverResponse {
|
||||
string icon = 10;
|
||||
EntityCategory entity_category = 11;
|
||||
bool supports_stop = 12;
|
||||
uint32 device_id = 13;
|
||||
}
|
||||
|
||||
enum LegacyCoverState {
|
||||
@ -388,6 +407,7 @@ message ListEntitiesFanResponse {
|
||||
string icon = 10;
|
||||
EntityCategory entity_category = 11;
|
||||
repeated string supported_preset_modes = 12;
|
||||
uint32 device_id = 13;
|
||||
}
|
||||
enum FanSpeed {
|
||||
FAN_SPEED_LOW = 0;
|
||||
@ -471,6 +491,7 @@ message ListEntitiesLightResponse {
|
||||
bool disabled_by_default = 13;
|
||||
string icon = 14;
|
||||
EntityCategory entity_category = 15;
|
||||
uint32 device_id = 16;
|
||||
}
|
||||
message LightStateResponse {
|
||||
option (id) = 24;
|
||||
@ -563,6 +584,7 @@ message ListEntitiesSensorResponse {
|
||||
SensorLastResetType legacy_last_reset_type = 11;
|
||||
bool disabled_by_default = 12;
|
||||
EntityCategory entity_category = 13;
|
||||
uint32 device_id = 14;
|
||||
}
|
||||
message SensorStateResponse {
|
||||
option (id) = 25;
|
||||
@ -595,6 +617,7 @@ message ListEntitiesSwitchResponse {
|
||||
bool disabled_by_default = 7;
|
||||
EntityCategory entity_category = 8;
|
||||
string device_class = 9;
|
||||
uint32 device_id = 10;
|
||||
}
|
||||
message SwitchStateResponse {
|
||||
option (id) = 26;
|
||||
@ -632,6 +655,7 @@ message ListEntitiesTextSensorResponse {
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
string device_class = 8;
|
||||
uint32 device_id = 9;
|
||||
}
|
||||
message TextSensorStateResponse {
|
||||
option (id) = 27;
|
||||
@ -814,6 +838,7 @@ message ListEntitiesCameraResponse {
|
||||
bool disabled_by_default = 5;
|
||||
string icon = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
uint32 device_id = 8;
|
||||
}
|
||||
|
||||
message CameraImageResponse {
|
||||
@ -916,6 +941,7 @@ message ListEntitiesClimateResponse {
|
||||
bool supports_target_humidity = 23;
|
||||
float visual_min_humidity = 24;
|
||||
float visual_max_humidity = 25;
|
||||
uint32 device_id = 26;
|
||||
}
|
||||
message ClimateStateResponse {
|
||||
option (id) = 47;
|
||||
@ -999,6 +1025,7 @@ message ListEntitiesNumberResponse {
|
||||
string unit_of_measurement = 11;
|
||||
NumberMode mode = 12;
|
||||
string device_class = 13;
|
||||
uint32 device_id = 14;
|
||||
}
|
||||
message NumberStateResponse {
|
||||
option (id) = 50;
|
||||
@ -1039,6 +1066,7 @@ message ListEntitiesSelectResponse {
|
||||
repeated string options = 6;
|
||||
bool disabled_by_default = 7;
|
||||
EntityCategory entity_category = 8;
|
||||
uint32 device_id = 9;
|
||||
}
|
||||
message SelectStateResponse {
|
||||
option (id) = 53;
|
||||
@ -1081,6 +1109,7 @@ message ListEntitiesSirenResponse {
|
||||
bool supports_duration = 8;
|
||||
bool supports_volume = 9;
|
||||
EntityCategory entity_category = 10;
|
||||
uint32 device_id = 11;
|
||||
}
|
||||
message SirenStateResponse {
|
||||
option (id) = 56;
|
||||
@ -1144,6 +1173,7 @@ message ListEntitiesLockResponse {
|
||||
|
||||
// Not yet implemented:
|
||||
string code_format = 11;
|
||||
uint32 device_id = 12;
|
||||
}
|
||||
message LockStateResponse {
|
||||
option (id) = 59;
|
||||
@ -1183,6 +1213,7 @@ message ListEntitiesButtonResponse {
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
string device_class = 8;
|
||||
uint32 device_id = 9;
|
||||
}
|
||||
message ButtonCommandRequest {
|
||||
option (id) = 62;
|
||||
@ -1238,6 +1269,8 @@ message ListEntitiesMediaPlayerResponse {
|
||||
bool supports_pause = 8;
|
||||
|
||||
repeated MediaPlayerSupportedFormat supported_formats = 9;
|
||||
|
||||
uint32 device_id = 10;
|
||||
}
|
||||
message MediaPlayerStateResponse {
|
||||
option (id) = 64;
|
||||
@ -1778,6 +1811,7 @@ message ListEntitiesAlarmControlPanelResponse {
|
||||
uint32 supported_features = 8;
|
||||
bool requires_code = 9;
|
||||
bool requires_code_to_arm = 10;
|
||||
uint32 device_id = 11;
|
||||
}
|
||||
|
||||
message AlarmControlPanelStateResponse {
|
||||
@ -1823,6 +1857,7 @@ message ListEntitiesTextResponse {
|
||||
uint32 max_length = 9;
|
||||
string pattern = 10;
|
||||
TextMode mode = 11;
|
||||
uint32 device_id = 12;
|
||||
}
|
||||
message TextStateResponse {
|
||||
option (id) = 98;
|
||||
@ -1863,6 +1898,7 @@ message ListEntitiesDateResponse {
|
||||
string icon = 5;
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
uint32 device_id = 8;
|
||||
}
|
||||
message DateStateResponse {
|
||||
option (id) = 101;
|
||||
@ -1906,6 +1942,7 @@ message ListEntitiesTimeResponse {
|
||||
string icon = 5;
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
uint32 device_id = 8;
|
||||
}
|
||||
message TimeStateResponse {
|
||||
option (id) = 104;
|
||||
@ -1952,6 +1989,7 @@ message ListEntitiesEventResponse {
|
||||
string device_class = 8;
|
||||
|
||||
repeated string event_types = 9;
|
||||
uint32 device_id = 10;
|
||||
}
|
||||
message EventResponse {
|
||||
option (id) = 108;
|
||||
@ -1983,6 +2021,7 @@ message ListEntitiesValveResponse {
|
||||
bool assumed_state = 9;
|
||||
bool supports_position = 10;
|
||||
bool supports_stop = 11;
|
||||
uint32 device_id = 12;
|
||||
}
|
||||
|
||||
enum ValveOperation {
|
||||
@ -2029,6 +2068,7 @@ message ListEntitiesDateTimeResponse {
|
||||
string icon = 5;
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
uint32 device_id = 8;
|
||||
}
|
||||
message DateTimeStateResponse {
|
||||
option (id) = 113;
|
||||
@ -2069,6 +2109,7 @@ message ListEntitiesUpdateResponse {
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
string device_class = 8;
|
||||
uint32 device_id = 9;
|
||||
}
|
||||
message UpdateStateResponse {
|
||||
option (id) = 117;
|
||||
|
@ -1629,6 +1629,23 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
resp.api_encryption_supported = true;
|
||||
#endif
|
||||
#ifdef USE_DEVICES
|
||||
for (auto const &device : App.get_devices()) {
|
||||
DeviceInfo device_info;
|
||||
device_info.device_id = device->get_device_id();
|
||||
device_info.name = device->get_name();
|
||||
device_info.area_id = device->get_area_id();
|
||||
resp.devices.push_back(device_info);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_AREAS
|
||||
for (auto const &area : App.get_areas()) {
|
||||
AreaInfo area_info;
|
||||
area_info.area_id = area->get_area_id();
|
||||
area_info.name = area->get_name();
|
||||
resp.areas.push_back(area_info);
|
||||
}
|
||||
#endif
|
||||
return resp;
|
||||
}
|
||||
|
@ -301,6 +301,9 @@ class APIConnection : public APIServerConnection {
|
||||
response.icon = entity->get_icon();
|
||||
response.disabled_by_default = entity->is_disabled_by_default();
|
||||
response.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category());
|
||||
#ifdef USE_DEVICES
|
||||
response.device_id = entity->get_device_id();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Helper function to fill common entity state fields
|
||||
|
@ -812,6 +812,103 @@ void PingResponse::dump_to(std::string &out) const { out.append("PingResponse {}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfoRequest {}"); }
|
||||
#endif
|
||||
bool AreaInfo::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->area_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool AreaInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->name = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void AreaInfo::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint32(1, this->area_id);
|
||||
buffer.encode_string(2, this->name);
|
||||
}
|
||||
void AreaInfo::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->area_id, false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->name, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void AreaInfo::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("AreaInfo {\n");
|
||||
out.append(" area_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->area_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" name: ");
|
||||
out.append("'").append(this->name).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool DeviceInfo::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->area_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool DeviceInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->name = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void DeviceInfo::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint32(1, this->device_id);
|
||||
buffer.encode_string(2, this->name);
|
||||
buffer.encode_uint32(3, this->area_id);
|
||||
}
|
||||
void DeviceInfo::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->name, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->area_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void DeviceInfo::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("DeviceInfo {\n");
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" name: ");
|
||||
out.append("'").append(this->name).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" area_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->area_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
@ -896,6 +993,18 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v
|
||||
this->bluetooth_mac_address = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 20: {
|
||||
this->devices.push_back(value.as_message<DeviceInfo>());
|
||||
return true;
|
||||
}
|
||||
case 21: {
|
||||
this->areas.push_back(value.as_message<AreaInfo>());
|
||||
return true;
|
||||
}
|
||||
case 22: {
|
||||
this->area = value.as_message<AreaInfo>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -920,6 +1029,13 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(16, this->suggested_area);
|
||||
buffer.encode_string(18, this->bluetooth_mac_address);
|
||||
buffer.encode_bool(19, this->api_encryption_supported);
|
||||
for (auto &it : this->devices) {
|
||||
buffer.encode_message<DeviceInfo>(20, it, true);
|
||||
}
|
||||
for (auto &it : this->areas) {
|
||||
buffer.encode_message<AreaInfo>(21, it, true);
|
||||
}
|
||||
buffer.encode_message<AreaInfo>(22, this->area);
|
||||
}
|
||||
void DeviceInfoResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->uses_password, false);
|
||||
@ -941,6 +1057,9 @@ void DeviceInfoResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 2, this->suggested_area, false);
|
||||
ProtoSize::add_string_field(total_size, 2, this->bluetooth_mac_address, false);
|
||||
ProtoSize::add_bool_field(total_size, 2, this->api_encryption_supported, false);
|
||||
ProtoSize::add_repeated_message(total_size, 2, this->devices);
|
||||
ProtoSize::add_repeated_message(total_size, 2, this->areas);
|
||||
ProtoSize::add_message_object(total_size, 2, this->area, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
@ -1026,6 +1145,22 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
out.append(" api_encryption_supported: ");
|
||||
out.append(YESNO(this->api_encryption_supported));
|
||||
out.append("\n");
|
||||
|
||||
for (const auto &it : this->devices) {
|
||||
out.append(" devices: ");
|
||||
it.dump_to(out);
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
for (const auto &it : this->areas) {
|
||||
out.append(" areas: ");
|
||||
it.dump_to(out);
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" area: ");
|
||||
this->area.dump_to(out);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -1052,6 +1187,10 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -1102,6 +1241,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(7, this->disabled_by_default);
|
||||
buffer.encode_string(8, this->icon);
|
||||
buffer.encode_enum<enums::EntityCategory>(9, this->entity_category);
|
||||
buffer.encode_uint32(10, this->device_id);
|
||||
}
|
||||
void ListEntitiesBinarySensorResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -1113,6 +1253,7 @@ void ListEntitiesBinarySensorResponse::calculate_size(uint32_t &total_size) cons
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->icon, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
|
||||
@ -1154,6 +1295,11 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -1236,6 +1382,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val
|
||||
this->supports_stop = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 13: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -1289,6 +1439,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(10, this->icon);
|
||||
buffer.encode_enum<enums::EntityCategory>(11, this->entity_category);
|
||||
buffer.encode_bool(12, this->supports_stop);
|
||||
buffer.encode_uint32(13, this->device_id);
|
||||
}
|
||||
void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -1303,6 +1454,7 @@ void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->icon, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesCoverResponse::dump_to(std::string &out) const {
|
||||
@ -1356,6 +1508,11 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const {
|
||||
out.append(" supports_stop: ");
|
||||
out.append(YESNO(this->supports_stop));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -1565,6 +1722,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 13: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -1620,6 +1781,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
for (auto &it : this->supported_preset_modes) {
|
||||
buffer.encode_string(12, it, true);
|
||||
}
|
||||
buffer.encode_uint32(13, this->device_id);
|
||||
}
|
||||
void ListEntitiesFanResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -1638,6 +1800,7 @@ void ListEntitiesFanResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, it, true);
|
||||
}
|
||||
}
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
||||
@ -1694,6 +1857,11 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
||||
out.append("'").append(it).append("'");
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -1987,6 +2155,10 @@ bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt val
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 16: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -2055,6 +2227,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(13, this->disabled_by_default);
|
||||
buffer.encode_string(14, this->icon);
|
||||
buffer.encode_enum<enums::EntityCategory>(15, this->entity_category);
|
||||
buffer.encode_uint32(16, this->device_id);
|
||||
}
|
||||
void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -2080,6 +2253,7 @@ void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->icon, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_uint32_field(total_size, 2, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesLightResponse::dump_to(std::string &out) const {
|
||||
@ -2151,6 +2325,11 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -2658,6 +2837,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 14: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -2716,6 +2899,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_enum<enums::SensorLastResetType>(11, this->legacy_last_reset_type);
|
||||
buffer.encode_bool(12, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(13, this->entity_category);
|
||||
buffer.encode_uint32(14, this->device_id);
|
||||
}
|
||||
void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -2731,6 +2915,7 @@ void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->legacy_last_reset_type), false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
||||
@ -2789,6 +2974,11 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -2860,6 +3050,10 @@ bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -2910,6 +3104,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(7, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
|
||||
buffer.encode_string(9, this->device_class);
|
||||
buffer.encode_uint32(10, this->device_id);
|
||||
}
|
||||
void ListEntitiesSwitchResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -2921,6 +3116,7 @@ void ListEntitiesSwitchResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->device_class, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
|
||||
@ -2962,6 +3158,11 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
|
||||
out.append(" device_class: ");
|
||||
out.append("'").append(this->device_class).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -3061,6 +3262,10 @@ bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarIn
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 9: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -3110,6 +3315,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
buffer.encode_string(8, this->device_class);
|
||||
buffer.encode_uint32(9, this->device_id);
|
||||
}
|
||||
void ListEntitiesTextSensorResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -3120,6 +3326,7 @@ void ListEntitiesTextSensorResponse::calculate_size(uint32_t &total_size) const
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->device_class, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
|
||||
@ -3157,6 +3364,11 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
|
||||
out.append(" device_class: ");
|
||||
out.append("'").append(this->device_class).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -3922,6 +4134,10 @@ bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -3966,6 +4182,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(5, this->disabled_by_default);
|
||||
buffer.encode_string(6, this->icon);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
buffer.encode_uint32(8, this->device_id);
|
||||
}
|
||||
void ListEntitiesCameraResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -3975,6 +4192,7 @@ void ListEntitiesCameraResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->icon, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesCameraResponse::dump_to(std::string &out) const {
|
||||
@ -4008,6 +4226,11 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -4156,6 +4379,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
|
||||
this->supports_target_humidity = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 26: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -4262,6 +4489,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(23, this->supports_target_humidity);
|
||||
buffer.encode_float(24, this->visual_min_humidity);
|
||||
buffer.encode_float(25, this->visual_max_humidity);
|
||||
buffer.encode_uint32(26, this->device_id);
|
||||
}
|
||||
void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -4313,6 +4541,7 @@ void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 2, this->supports_target_humidity, false);
|
||||
ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_min_humidity != 0.0f, false);
|
||||
ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_max_humidity != 0.0f, false);
|
||||
ProtoSize::add_uint32_field(total_size, 2, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||
@ -4436,6 +4665,11 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||
sprintf(buffer, "%g", this->visual_max_humidity);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -4901,6 +5135,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->mode = value.as_enum<enums::NumberMode>();
|
||||
return true;
|
||||
}
|
||||
case 14: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -4971,6 +5209,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(11, this->unit_of_measurement);
|
||||
buffer.encode_enum<enums::NumberMode>(12, this->mode);
|
||||
buffer.encode_string(13, this->device_class);
|
||||
buffer.encode_uint32(14, this->device_id);
|
||||
}
|
||||
void ListEntitiesNumberResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -4986,6 +5225,7 @@ void ListEntitiesNumberResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->unit_of_measurement, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->mode), false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->device_class, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesNumberResponse::dump_to(std::string &out) const {
|
||||
@ -5046,6 +5286,11 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const {
|
||||
out.append(" device_class: ");
|
||||
out.append("'").append(this->device_class).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -5151,6 +5396,10 @@ bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 9: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -5202,6 +5451,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
}
|
||||
buffer.encode_bool(7, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
|
||||
buffer.encode_uint32(9, this->device_id);
|
||||
}
|
||||
void ListEntitiesSelectResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -5216,6 +5466,7 @@ void ListEntitiesSelectResponse::calculate_size(uint32_t &total_size) const {
|
||||
}
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesSelectResponse::dump_to(std::string &out) const {
|
||||
@ -5255,6 +5506,11 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -5378,6 +5634,10 @@ bool ListEntitiesSirenResponse::decode_varint(uint32_t field_id, ProtoVarInt val
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 11: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -5431,6 +5691,7 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(8, this->supports_duration);
|
||||
buffer.encode_bool(9, this->supports_volume);
|
||||
buffer.encode_enum<enums::EntityCategory>(10, this->entity_category);
|
||||
buffer.encode_uint32(11, this->device_id);
|
||||
}
|
||||
void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -5447,6 +5708,7 @@ void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->supports_duration, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->supports_volume, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesSirenResponse::dump_to(std::string &out) const {
|
||||
@ -5494,6 +5756,11 @@ void ListEntitiesSirenResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -5683,6 +5950,10 @@ bool ListEntitiesLockResponse::decode_varint(uint32_t field_id, ProtoVarInt valu
|
||||
this->requires_code = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 12: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -5735,6 +6006,7 @@ void ListEntitiesLockResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(9, this->supports_open);
|
||||
buffer.encode_bool(10, this->requires_code);
|
||||
buffer.encode_string(11, this->code_format);
|
||||
buffer.encode_uint32(12, this->device_id);
|
||||
}
|
||||
void ListEntitiesLockResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -5748,6 +6020,7 @@ void ListEntitiesLockResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->supports_open, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->requires_code, false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->code_format, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesLockResponse::dump_to(std::string &out) const {
|
||||
@ -5797,6 +6070,11 @@ void ListEntitiesLockResponse::dump_to(std::string &out) const {
|
||||
out.append(" code_format: ");
|
||||
out.append("'").append(this->code_format).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -5922,6 +6200,10 @@ bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 9: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -5971,6 +6253,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
buffer.encode_string(8, this->device_class);
|
||||
buffer.encode_uint32(9, this->device_id);
|
||||
}
|
||||
void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -5981,6 +6264,7 @@ void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->device_class, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesButtonResponse::dump_to(std::string &out) const {
|
||||
@ -6018,6 +6302,11 @@ void ListEntitiesButtonResponse::dump_to(std::string &out) const {
|
||||
out.append(" device_class: ");
|
||||
out.append("'").append(this->device_class).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -6135,6 +6424,10 @@ bool ListEntitiesMediaPlayerResponse::decode_varint(uint32_t field_id, ProtoVarI
|
||||
this->supports_pause = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -6187,6 +6480,7 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
for (auto &it : this->supported_formats) {
|
||||
buffer.encode_message<MediaPlayerSupportedFormat>(9, it, true);
|
||||
}
|
||||
buffer.encode_uint32(10, this->device_id);
|
||||
}
|
||||
void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -6198,6 +6492,7 @@ void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->supports_pause, false);
|
||||
ProtoSize::add_repeated_message(total_size, 1, this->supported_formats);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const {
|
||||
@ -6241,6 +6536,11 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const {
|
||||
it.dump_to(out);
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -8551,6 +8851,10 @@ bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, Pro
|
||||
this->requires_code_to_arm = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 11: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -8598,6 +8902,7 @@ void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer buffer) cons
|
||||
buffer.encode_uint32(8, this->supported_features);
|
||||
buffer.encode_bool(9, this->requires_code);
|
||||
buffer.encode_bool(10, this->requires_code_to_arm);
|
||||
buffer.encode_uint32(11, this->device_id);
|
||||
}
|
||||
void ListEntitiesAlarmControlPanelResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -8610,6 +8915,7 @@ void ListEntitiesAlarmControlPanelResponse::calculate_size(uint32_t &total_size)
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->supported_features, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->requires_code, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->requires_code_to_arm, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const {
|
||||
@ -8656,6 +8962,11 @@ void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const {
|
||||
out.append(" requires_code_to_arm: ");
|
||||
out.append(YESNO(this->requires_code_to_arm));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -8783,6 +9094,10 @@ bool ListEntitiesTextResponse::decode_varint(uint32_t field_id, ProtoVarInt valu
|
||||
this->mode = value.as_enum<enums::TextMode>();
|
||||
return true;
|
||||
}
|
||||
case 12: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -8835,6 +9150,7 @@ void ListEntitiesTextResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint32(9, this->max_length);
|
||||
buffer.encode_string(10, this->pattern);
|
||||
buffer.encode_enum<enums::TextMode>(11, this->mode);
|
||||
buffer.encode_uint32(12, this->device_id);
|
||||
}
|
||||
void ListEntitiesTextResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -8848,6 +9164,7 @@ void ListEntitiesTextResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->max_length, false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->pattern, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->mode), false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesTextResponse::dump_to(std::string &out) const {
|
||||
@ -8899,6 +9216,11 @@ void ListEntitiesTextResponse::dump_to(std::string &out) const {
|
||||
out.append(" mode: ");
|
||||
out.append(proto_enum_to_string<enums::TextMode>(this->mode));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -9014,6 +9336,10 @@ bool ListEntitiesDateResponse::decode_varint(uint32_t field_id, ProtoVarInt valu
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -9058,6 +9384,7 @@ void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(5, this->icon);
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
buffer.encode_uint32(8, this->device_id);
|
||||
}
|
||||
void ListEntitiesDateResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -9067,6 +9394,7 @@ void ListEntitiesDateResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->icon, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesDateResponse::dump_to(std::string &out) const {
|
||||
@ -9100,6 +9428,11 @@ void ListEntitiesDateResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -9255,6 +9588,10 @@ bool ListEntitiesTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt valu
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -9299,6 +9636,7 @@ void ListEntitiesTimeResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(5, this->icon);
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
buffer.encode_uint32(8, this->device_id);
|
||||
}
|
||||
void ListEntitiesTimeResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -9308,6 +9646,7 @@ void ListEntitiesTimeResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->icon, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesTimeResponse::dump_to(std::string &out) const {
|
||||
@ -9341,6 +9680,11 @@ void ListEntitiesTimeResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -9496,6 +9840,10 @@ bool ListEntitiesEventResponse::decode_varint(uint32_t field_id, ProtoVarInt val
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -9552,6 +9900,7 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
for (auto &it : this->event_types) {
|
||||
buffer.encode_string(9, it, true);
|
||||
}
|
||||
buffer.encode_uint32(10, this->device_id);
|
||||
}
|
||||
void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -9567,6 +9916,7 @@ void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, it, true);
|
||||
}
|
||||
}
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesEventResponse::dump_to(std::string &out) const {
|
||||
@ -9610,6 +9960,11 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const {
|
||||
out.append("'").append(it).append("'");
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -9678,6 +10033,10 @@ bool ListEntitiesValveResponse::decode_varint(uint32_t field_id, ProtoVarInt val
|
||||
this->supports_stop = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 12: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -9730,6 +10089,7 @@ void ListEntitiesValveResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(9, this->assumed_state);
|
||||
buffer.encode_bool(10, this->supports_position);
|
||||
buffer.encode_bool(11, this->supports_stop);
|
||||
buffer.encode_uint32(12, this->device_id);
|
||||
}
|
||||
void ListEntitiesValveResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -9743,6 +10103,7 @@ void ListEntitiesValveResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->assumed_state, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->supports_position, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesValveResponse::dump_to(std::string &out) const {
|
||||
@ -9792,6 +10153,11 @@ void ListEntitiesValveResponse::dump_to(std::string &out) const {
|
||||
out.append(" supports_stop: ");
|
||||
out.append(YESNO(this->supports_stop));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -9923,6 +10289,10 @@ bool ListEntitiesDateTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -9967,6 +10337,7 @@ void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(5, this->icon);
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
buffer.encode_uint32(8, this->device_id);
|
||||
}
|
||||
void ListEntitiesDateTimeResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -9976,6 +10347,7 @@ void ListEntitiesDateTimeResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->icon, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesDateTimeResponse::dump_to(std::string &out) const {
|
||||
@ -10009,6 +10381,11 @@ void ListEntitiesDateTimeResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -10114,6 +10491,10 @@ bool ListEntitiesUpdateResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 9: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -10163,6 +10544,7 @@ void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
buffer.encode_string(8, this->device_class);
|
||||
buffer.encode_uint32(9, this->device_id);
|
||||
}
|
||||
void ListEntitiesUpdateResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@ -10173,6 +10555,7 @@ void ListEntitiesUpdateResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->device_class, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesUpdateResponse::dump_to(std::string &out) const {
|
||||
@ -10210,6 +10593,11 @@ void ListEntitiesUpdateResponse::dump_to(std::string &out) const {
|
||||
out.append(" device_class: ");
|
||||
out.append("'").append(this->device_class).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
@ -264,6 +264,7 @@ class InfoResponseProtoMessage : public ProtoMessage {
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
enums::EntityCategory entity_category{};
|
||||
uint32_t device_id{0};
|
||||
|
||||
protected:
|
||||
};
|
||||
@ -415,10 +416,39 @@ class DeviceInfoRequest : public ProtoMessage {
|
||||
|
||||
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 {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "device_info_response"; }
|
||||
#endif
|
||||
@ -441,6 +471,9 @@ class DeviceInfoResponse : public ProtoMessage {
|
||||
std::string suggested_area{};
|
||||
std::string bluetooth_mac_address{};
|
||||
bool api_encryption_supported{false};
|
||||
std::vector<DeviceInfo> devices{};
|
||||
std::vector<AreaInfo> areas{};
|
||||
AreaInfo area{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@ -493,7 +526,7 @@ class SubscribeStatesRequest : public ProtoMessage {
|
||||
class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_binary_sensor_response"; }
|
||||
#endif
|
||||
@ -532,7 +565,7 @@ class BinarySensorStateResponse : public StateResponseProtoMessage {
|
||||
class ListEntitiesCoverResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_cover_response"; }
|
||||
#endif
|
||||
@ -601,7 +634,7 @@ class CoverCommandRequest : public ProtoMessage {
|
||||
class ListEntitiesFanResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_fan_response"; }
|
||||
#endif
|
||||
@ -679,7 +712,7 @@ class FanCommandRequest : public ProtoMessage {
|
||||
class ListEntitiesLightResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_light_response"; }
|
||||
#endif
|
||||
@ -780,7 +813,7 @@ class LightCommandRequest : public ProtoMessage {
|
||||
class ListEntitiesSensorResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_sensor_response"; }
|
||||
#endif
|
||||
@ -823,7 +856,7 @@ class SensorStateResponse : public StateResponseProtoMessage {
|
||||
class ListEntitiesSwitchResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_switch_response"; }
|
||||
#endif
|
||||
@ -880,7 +913,7 @@ class SwitchCommandRequest : public ProtoMessage {
|
||||
class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_text_sensor_response"; }
|
||||
#endif
|
||||
@ -1196,7 +1229,7 @@ class ExecuteServiceRequest : public ProtoMessage {
|
||||
class ListEntitiesCameraResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_camera_response"; }
|
||||
#endif
|
||||
@ -1253,7 +1286,7 @@ class CameraImageRequest : public ProtoMessage {
|
||||
class ListEntitiesClimateResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_climate_response"; }
|
||||
#endif
|
||||
@ -1362,7 +1395,7 @@ class ClimateCommandRequest : public ProtoMessage {
|
||||
class ListEntitiesNumberResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_number_response"; }
|
||||
#endif
|
||||
@ -1423,7 +1456,7 @@ class NumberCommandRequest : public ProtoMessage {
|
||||
class ListEntitiesSelectResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_select_response"; }
|
||||
#endif
|
||||
@ -1481,7 +1514,7 @@ class SelectCommandRequest : public ProtoMessage {
|
||||
class ListEntitiesSirenResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_siren_response"; }
|
||||
#endif
|
||||
@ -1547,7 +1580,7 @@ class SirenCommandRequest : public ProtoMessage {
|
||||
class ListEntitiesLockResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_lock_response"; }
|
||||
#endif
|
||||
@ -1609,7 +1642,7 @@ class LockCommandRequest : public ProtoMessage {
|
||||
class ListEntitiesButtonResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_button_response"; }
|
||||
#endif
|
||||
@ -1662,7 +1695,7 @@ class MediaPlayerSupportedFormat : public ProtoMessage {
|
||||
class ListEntitiesMediaPlayerResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_media_player_response"; }
|
||||
#endif
|
||||
@ -2532,7 +2565,7 @@ class VoiceAssistantSetConfiguration : public ProtoMessage {
|
||||
class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_alarm_control_panel_response"; }
|
||||
#endif
|
||||
@ -2592,7 +2625,7 @@ class AlarmControlPanelCommandRequest : public ProtoMessage {
|
||||
class ListEntitiesTextResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_text_response"; }
|
||||
#endif
|
||||
@ -2653,7 +2686,7 @@ class TextCommandRequest : public ProtoMessage {
|
||||
class ListEntitiesDateResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_date_response"; }
|
||||
#endif
|
||||
@ -2713,7 +2746,7 @@ class DateCommandRequest : public ProtoMessage {
|
||||
class ListEntitiesTimeResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_time_response"; }
|
||||
#endif
|
||||
@ -2773,7 +2806,7 @@ class TimeCommandRequest : public ProtoMessage {
|
||||
class ListEntitiesEventResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_event_response"; }
|
||||
#endif
|
||||
@ -2811,7 +2844,7 @@ class EventResponse : public StateResponseProtoMessage {
|
||||
class ListEntitiesValveResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_valve_response"; }
|
||||
#endif
|
||||
@ -2873,7 +2906,7 @@ class ValveCommandRequest : public ProtoMessage {
|
||||
class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_date_time_response"; }
|
||||
#endif
|
||||
@ -2928,7 +2961,7 @@ class DateTimeCommandRequest : public ProtoMessage {
|
||||
class ListEntitiesUpdateResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
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
|
||||
static constexpr const char *message_name() { return "list_entities_update_response"; }
|
||||
#endif
|
||||
|
@ -60,8 +60,8 @@ from esphome.const import (
|
||||
DEVICE_CLASS_WINDOW,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
from esphome.util import Registry
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
@ -491,6 +491,9 @@ _BINARY_SENSOR_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_BINARY_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("binary_sensor"))
|
||||
|
||||
|
||||
def binary_sensor_schema(
|
||||
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):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "binary_sensor")
|
||||
|
||||
if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
|
||||
cg.add(var.set_device_class(device_class))
|
||||
|
@ -18,8 +18,8 @@ from esphome.const import (
|
||||
DEVICE_CLASS_UPDATE,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@ -61,6 +61,9 @@ _BUTTON_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_BUTTON_SCHEMA.add_extra(entity_duplicate_validator("button"))
|
||||
|
||||
|
||||
def button_schema(
|
||||
class_: MockObjClass,
|
||||
*,
|
||||
@ -87,7 +90,7 @@ BUTTON_SCHEMA.add_extra(cv.deprecated_schema_constant("button"))
|
||||
|
||||
|
||||
async def setup_button_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "button")
|
||||
|
||||
for conf in config.get(CONF_ON_PRESS, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
|
@ -48,8 +48,8 @@ from esphome.const import (
|
||||
CONF_WEB_SERVER,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
@ -247,6 +247,9 @@ _CLIMATE_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_CLIMATE_SCHEMA.add_extra(entity_duplicate_validator("climate"))
|
||||
|
||||
|
||||
def climate_schema(
|
||||
class_: MockObjClass,
|
||||
*,
|
||||
@ -273,7 +276,7 @@ CLIMATE_SCHEMA.add_extra(cv.deprecated_schema_constant("climate"))
|
||||
|
||||
|
||||
async def setup_climate_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "climate")
|
||||
|
||||
visual = config[CONF_VISUAL]
|
||||
if (min_temp := visual.get(CONF_MIN_TEMPERATURE)) is not None:
|
||||
|
@ -33,8 +33,8 @@ from esphome.const import (
|
||||
DEVICE_CLASS_WINDOW,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
@ -126,6 +126,9 @@ _COVER_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_COVER_SCHEMA.add_extra(entity_duplicate_validator("cover"))
|
||||
|
||||
|
||||
def cover_schema(
|
||||
class_: MockObjClass,
|
||||
*,
|
||||
@ -154,7 +157,7 @@ COVER_SCHEMA.add_extra(cv.deprecated_schema_constant("cover"))
|
||||
|
||||
|
||||
async def setup_cover_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "cover")
|
||||
|
||||
if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
|
||||
cg.add(var.set_device_class(device_class))
|
||||
|
@ -22,8 +22,8 @@ from esphome.const import (
|
||||
CONF_YEAR,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@rfdarter", "@jesserockz"]
|
||||
|
||||
@ -84,6 +84,8 @@ _DATETIME_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
||||
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||
).add_extra(_validate_time_present)
|
||||
|
||||
_DATETIME_SCHEMA.add_extra(entity_duplicate_validator("datetime"))
|
||||
|
||||
|
||||
def date_schema(class_: MockObjClass) -> cv.Schema:
|
||||
schema = cv.Schema(
|
||||
@ -133,7 +135,7 @@ def datetime_schema(class_: MockObjClass) -> cv.Schema:
|
||||
|
||||
|
||||
async def setup_datetime_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "datetime")
|
||||
|
||||
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||
|
@ -455,7 +455,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
CONF_NAME: "Demo Plain Sensor",
|
||||
},
|
||||
{
|
||||
CONF_NAME: "Demo Temperature Sensor",
|
||||
CONF_NAME: "Demo Temperature Sensor 1",
|
||||
CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS,
|
||||
CONF_ICON: ICON_THERMOMETER,
|
||||
CONF_ACCURACY_DECIMALS: 1,
|
||||
@ -463,7 +463,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
{
|
||||
CONF_NAME: "Demo Temperature Sensor",
|
||||
CONF_NAME: "Demo Temperature Sensor 2",
|
||||
CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS,
|
||||
CONF_ICON: ICON_THERMOMETER,
|
||||
CONF_ACCURACY_DECIMALS: 1,
|
||||
|
@ -19,7 +19,7 @@ from esphome.const import (
|
||||
CONF_VSYNC_PIN,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
from esphome.core.entity_helpers import setup_entity
|
||||
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
@ -284,7 +284,7 @@ SETTERS = {
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "camera")
|
||||
await cg.register_component(var, config)
|
||||
|
||||
for key, setter in SETTERS.items():
|
||||
|
@ -18,8 +18,8 @@ from esphome.const import (
|
||||
DEVICE_CLASS_MOTION,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@nohat"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@ -59,6 +59,9 @@ _EVENT_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_EVENT_SCHEMA.add_extra(entity_duplicate_validator("event"))
|
||||
|
||||
|
||||
def event_schema(
|
||||
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]):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "event")
|
||||
|
||||
for conf in config.get(CONF_ON_EVENT, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
|
@ -32,7 +32,7 @@ from esphome.const import (
|
||||
CONF_WEB_SERVER,
|
||||
)
|
||||
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
|
||||
|
||||
@ -161,6 +161,9 @@ _FAN_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_FAN_SCHEMA.add_extra(entity_duplicate_validator("fan"))
|
||||
|
||||
|
||||
def fan_schema(
|
||||
class_: cg.Pvariable,
|
||||
*,
|
||||
@ -225,7 +228,7 @@ def validate_preset_modes(value):
|
||||
|
||||
|
||||
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]))
|
||||
|
||||
|
@ -38,8 +38,8 @@ from esphome.const import (
|
||||
CONF_WHITE,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
from .automation import LIGHT_STATE_SCHEMA
|
||||
from .effects import (
|
||||
@ -110,6 +110,8 @@ LIGHT_SCHEMA = (
|
||||
)
|
||||
)
|
||||
|
||||
LIGHT_SCHEMA.add_extra(entity_duplicate_validator("light"))
|
||||
|
||||
BINARY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend(
|
||||
{
|
||||
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):
|
||||
await setup_entity(light_var, config)
|
||||
await setup_entity(light_var, config, "light")
|
||||
|
||||
cg.add(light_var.set_restore_mode(config[CONF_RESTORE_MODE]))
|
||||
|
||||
|
@ -14,8 +14,8 @@ from esphome.const import (
|
||||
CONF_WEB_SERVER,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@ -67,6 +67,9 @@ _LOCK_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_LOCK_SCHEMA.add_extra(entity_duplicate_validator("lock"))
|
||||
|
||||
|
||||
def lock_schema(
|
||||
class_: MockObjClass = cv.UNDEFINED,
|
||||
*,
|
||||
@ -94,7 +97,7 @@ LOCK_SCHEMA.add_extra(cv.deprecated_schema_constant("lock"))
|
||||
|
||||
|
||||
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, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
|
@ -11,9 +11,9 @@ from esphome.const import (
|
||||
CONF_VOLUME,
|
||||
)
|
||||
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.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
|
||||
@ -81,7 +81,7 @@ IsAnnouncingCondition = media_player_ns.class_(
|
||||
|
||||
|
||||
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, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
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(
|
||||
class_: MockObjClass,
|
||||
@ -166,7 +168,6 @@ def media_player_schema(
|
||||
MEDIA_PLAYER_SCHEMA = media_player_schema(MediaPlayer)
|
||||
MEDIA_PLAYER_SCHEMA.add_extra(cv.deprecated_schema_constant("media_player"))
|
||||
|
||||
|
||||
MEDIA_PLAYER_ACTION_SCHEMA = automation.maybe_simple_id(
|
||||
cv.Schema(
|
||||
{
|
||||
|
@ -76,8 +76,8 @@ from esphome.const import (
|
||||
DEVICE_CLASS_WIND_SPEED,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DEVICE_CLASSES = [
|
||||
@ -207,6 +207,9 @@ _NUMBER_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_NUMBER_SCHEMA.add_extra(entity_duplicate_validator("number"))
|
||||
|
||||
|
||||
def number_schema(
|
||||
class_: MockObjClass,
|
||||
*,
|
||||
@ -237,7 +240,7 @@ NUMBER_SCHEMA.add_extra(cv.deprecated_schema_constant("number"))
|
||||
async def setup_number_core_(
|
||||
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_max_value(max_value))
|
||||
|
@ -17,8 +17,8 @@ from esphome.const import (
|
||||
CONF_WEB_SERVER,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@ -65,6 +65,9 @@ _SELECT_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_SELECT_SCHEMA.add_extra(entity_duplicate_validator("select"))
|
||||
|
||||
|
||||
def select_schema(
|
||||
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]):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "select")
|
||||
|
||||
cg.add(var.traits.set_options(options))
|
||||
|
||||
|
@ -101,8 +101,8 @@ from esphome.const import (
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
from esphome.util import Registry
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
@ -318,6 +318,8 @@ _SENSOR_SCHEMA = (
|
||||
)
|
||||
)
|
||||
|
||||
_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("sensor"))
|
||||
|
||||
|
||||
def sensor_schema(
|
||||
class_: MockObjClass = cv.UNDEFINED,
|
||||
@ -787,7 +789,7 @@ async def build_filters(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:
|
||||
cg.add(var.set_device_class(device_class))
|
||||
|
@ -20,8 +20,8 @@ from esphome.const import (
|
||||
DEVICE_CLASS_SWITCH,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@ -91,6 +91,9 @@ _SWITCH_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_SWITCH_SCHEMA.add_extra(entity_duplicate_validator("switch"))
|
||||
|
||||
|
||||
def switch_schema(
|
||||
class_: MockObjClass,
|
||||
*,
|
||||
@ -131,7 +134,7 @@ SWITCH_SCHEMA.add_extra(cv.deprecated_schema_constant("switch"))
|
||||
|
||||
|
||||
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:
|
||||
cg.add(var.set_inverted(inverted))
|
||||
|
@ -14,8 +14,8 @@ from esphome.const import (
|
||||
CONF_WEB_SERVER,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@mauritskorse"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@ -58,6 +58,9 @@ _TEXT_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_TEXT_SCHEMA.add_extra(entity_duplicate_validator("text"))
|
||||
|
||||
|
||||
def text_schema(
|
||||
class_: MockObjClass = cv.UNDEFINED,
|
||||
*,
|
||||
@ -94,7 +97,7 @@ async def setup_text_core_(
|
||||
max_length: int | 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_max_length(max_length))
|
||||
|
@ -21,8 +21,8 @@ from esphome.const import (
|
||||
DEVICE_CLASS_TIMESTAMP,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
from esphome.util import Registry
|
||||
|
||||
DEVICE_CLASSES = [
|
||||
@ -153,6 +153,9 @@ _TEXT_SENSOR_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_TEXT_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("text_sensor"))
|
||||
|
||||
|
||||
def text_sensor_schema(
|
||||
class_: MockObjClass = cv.UNDEFINED,
|
||||
*,
|
||||
@ -186,7 +189,7 @@ async def build_filters(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:
|
||||
cg.add(var.set_device_class(device_class))
|
||||
|
@ -15,8 +15,8 @@ from esphome.const import (
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@ -58,6 +58,9 @@ _UPDATE_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_UPDATE_SCHEMA.add_extra(entity_duplicate_validator("update"))
|
||||
|
||||
|
||||
def update_schema(
|
||||
class_: MockObjClass = cv.UNDEFINED,
|
||||
*,
|
||||
@ -87,7 +90,7 @@ UPDATE_SCHEMA.add_extra(cv.deprecated_schema_constant("update"))
|
||||
|
||||
|
||||
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):
|
||||
cg.add(var.set_device_class(device_class_config))
|
||||
|
@ -22,8 +22,8 @@ from esphome.const import (
|
||||
DEVICE_CLASS_WATER,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
@ -103,6 +103,9 @@ _VALVE_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_VALVE_SCHEMA.add_extra(entity_duplicate_validator("valve"))
|
||||
|
||||
|
||||
def valve_schema(
|
||||
class_: MockObjClass = cv.UNDEFINED,
|
||||
*,
|
||||
@ -132,7 +135,7 @@ VALVE_SCHEMA.add_extra(cv.deprecated_schema_constant("valve"))
|
||||
|
||||
|
||||
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):
|
||||
cg.add(var.set_device_class(device_class_config))
|
||||
|
@ -1,5 +1,7 @@
|
||||
"""Helpers for config validation using voluptuous."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
@ -29,6 +31,7 @@ from esphome.const import (
|
||||
CONF_COMMAND_RETAIN,
|
||||
CONF_COMMAND_TOPIC,
|
||||
CONF_DAY,
|
||||
CONF_DEVICE_ID,
|
||||
CONF_DISABLED_BY_DEFAULT,
|
||||
CONF_DISCOVERY,
|
||||
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):
|
||||
"""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_ICON): icon,
|
||||
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}"
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value: str) -> "Version":
|
||||
def parse(cls, value: str) -> Version:
|
||||
match = re.match(r"^(\d+).(\d+).(\d+)-?\w*$", value)
|
||||
if match is None:
|
||||
raise ValueError(f"Not a valid version number {value}")
|
||||
|
@ -56,6 +56,8 @@ CONF_AP = "ap"
|
||||
CONF_APPARENT_POWER = "apparent_power"
|
||||
CONF_ARDUINO_VERSION = "arduino_version"
|
||||
CONF_AREA = "area"
|
||||
CONF_AREA_ID = "area_id"
|
||||
CONF_AREAS = "areas"
|
||||
CONF_ARGS = "args"
|
||||
CONF_ASSUMED_STATE = "assumed_state"
|
||||
CONF_AT = "at"
|
||||
@ -217,6 +219,7 @@ CONF_DEST = "dest"
|
||||
CONF_DEVICE = "device"
|
||||
CONF_DEVICE_CLASS = "device_class"
|
||||
CONF_DEVICE_FACTOR = "device_factor"
|
||||
CONF_DEVICE_ID = "device_id"
|
||||
CONF_DEVICES = "devices"
|
||||
CONF_DIELECTRIC_CONSTANT = "dielectric_constant"
|
||||
CONF_DIMENSIONS = "dimensions"
|
||||
|
@ -522,6 +522,9 @@ class EsphomeCore:
|
||||
# Dict to track platform entity counts for pre-allocation
|
||||
# Key: platform name (e.g. "sensor", "binary_sensor"), Value: count
|
||||
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
|
||||
self.verbose = False
|
||||
# Whether ESPHome was started in quiet mode
|
||||
@ -553,6 +556,7 @@ class EsphomeCore:
|
||||
self.loaded_integrations = set()
|
||||
self.component_ids = set()
|
||||
self.platform_counts = defaultdict(int)
|
||||
self.unique_ids = set()
|
||||
PIN_SCHEMA_REGISTRY.reset()
|
||||
|
||||
@property
|
||||
|
@ -9,6 +9,13 @@
|
||||
#include "esphome/core/preferences.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
|
||||
#include <sys/select.h>
|
||||
#endif
|
||||
@ -87,7 +94,7 @@ static const uint32_t TEARDOWN_TIMEOUT_REBOOT_MS = 1000; // 1 second for quick
|
||||
|
||||
class Application {
|
||||
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) {
|
||||
arch_init();
|
||||
this->name_add_mac_suffix_ = name_add_mac_suffix;
|
||||
@ -102,11 +109,17 @@ class Application {
|
||||
this->name_ = name;
|
||||
this->friendly_name_ = friendly_name;
|
||||
}
|
||||
this->area_ = area;
|
||||
this->comment_ = comment;
|
||||
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; }
|
||||
Component *get_current_component() { return this->current_component_; }
|
||||
|
||||
@ -264,6 +277,12 @@ class Application {
|
||||
#ifdef USE_UPDATE
|
||||
void reserve_update(size_t count) { this->updates_.reserve(count); }
|
||||
#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.
|
||||
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_; }
|
||||
|
||||
/// 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().
|
||||
std::string get_comment() const { return this->comment_; }
|
||||
@ -334,6 +361,12 @@ class Application {
|
||||
|
||||
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
|
||||
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) {
|
||||
@ -610,6 +643,12 @@ class Application {
|
||||
uint16_t current_loop_index_{0};
|
||||
bool in_loop_{false};
|
||||
|
||||
#ifdef USE_DEVICES
|
||||
std::vector<Device *> devices_{};
|
||||
#endif
|
||||
#ifdef USE_AREAS
|
||||
std::vector<Area *> areas_{};
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
std::vector<binary_sensor::BinarySensor *> binary_sensors_{};
|
||||
#endif
|
||||
@ -676,7 +715,6 @@ class Application {
|
||||
|
||||
std::string name_;
|
||||
std::string friendly_name_;
|
||||
const char *area_{nullptr};
|
||||
const char *comment_{nullptr};
|
||||
const char *compilation_time_{nullptr};
|
||||
bool name_add_mac_suffix_;
|
||||
|
19
esphome/core/area.h
Normal file
19
esphome/core/area.h
Normal 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
|
@ -1,18 +1,24 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from esphome import automation
|
||||
from esphome import automation, core
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_AREA,
|
||||
CONF_AREA_ID,
|
||||
CONF_AREAS,
|
||||
CONF_BUILD_PATH,
|
||||
CONF_COMMENT,
|
||||
CONF_COMPILE_PROCESS_LIMIT,
|
||||
CONF_DEBUG_SCHEDULER,
|
||||
CONF_DEVICES,
|
||||
CONF_ESPHOME,
|
||||
CONF_FRIENDLY_NAME,
|
||||
CONF_ID,
|
||||
CONF_INCLUDES,
|
||||
CONF_LIBRARIES,
|
||||
CONF_MIN_VERSION,
|
||||
@ -32,7 +38,13 @@ from esphome.const import (
|
||||
__version__ as ESPHOME_VERSION,
|
||||
)
|
||||
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__)
|
||||
|
||||
@ -48,7 +60,8 @@ LoopTrigger = cg.esphome_ns.class_(
|
||||
ProjectUpdateTrigger = cg.esphome_ns.class_(
|
||||
"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"}
|
||||
|
||||
@ -71,6 +84,56 @@ def validate_hostname(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):
|
||||
# Look for "<...>" includes
|
||||
if value.startswith("<") and value.endswith(">"):
|
||||
@ -111,13 +174,32 @@ if "ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT" in os.environ:
|
||||
else:
|
||||
_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(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.valid_name,
|
||||
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.Required(CONF_BUILD_PATH): cv.string,
|
||||
cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema(
|
||||
@ -167,11 +249,17 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(
|
||||
CONF_COMPILE_PROCESS_LIMIT, default=_compile_process_limit_default
|
||||
): 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,
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(validate_ids_and_references)
|
||||
|
||||
|
||||
PRELOAD_CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.valid_name,
|
||||
@ -336,7 +424,7 @@ async def _add_platform_reserves() -> None:
|
||||
|
||||
|
||||
@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)
|
||||
# These can be used by user lambdas, put them to default scope
|
||||
cg.add_global(cg.RawExpression("using std::isnan"))
|
||||
@ -347,7 +435,6 @@ async def to_code(config):
|
||||
cg.App.pre_setup(
|
||||
config[CONF_NAME],
|
||||
config[CONF_FRIENDLY_NAME],
|
||||
config[CONF_AREA],
|
||||
config.get(CONF_COMMENT, ""),
|
||||
cg.RawExpression('__DATE__ ", " __TIME__'),
|
||||
config[CONF_NAME_ADD_MAC_SUFFIX],
|
||||
@ -417,3 +504,50 @@ async def to_code(config):
|
||||
|
||||
if 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))
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
// Feature flags
|
||||
#define USE_ALARM_CONTROL_PANEL
|
||||
#define USE_AREAS
|
||||
#define USE_BINARY_SENSOR
|
||||
#define USE_BUTTON
|
||||
#define USE_CLIMATE
|
||||
@ -29,6 +30,7 @@
|
||||
#define USE_DATETIME_DATETIME
|
||||
#define USE_DATETIME_TIME
|
||||
#define USE_DEEP_SLEEP
|
||||
#define USE_DEVICES
|
||||
#define USE_DISPLAY
|
||||
#define USE_ESP32_IMPROV_STATE_CALLBACK
|
||||
#define USE_EVENT
|
||||
|
20
esphome/core/device.h
Normal file
20
esphome/core/device.h
Normal 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
|
@ -11,7 +11,14 @@ const StringRef &EntityBase::get_name() const { return this->name_; }
|
||||
void EntityBase::set_name(const char *name) {
|
||||
this->name_ = StringRef(name);
|
||||
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;
|
||||
} else {
|
||||
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
|
||||
void EntityBase::calc_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_);
|
||||
}
|
||||
}
|
||||
void EntityBase::calc_object_id_() { this->object_id_hash_ = fnv1_hash(this->get_object_id()); }
|
||||
|
||||
uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; }
|
||||
|
||||
|
@ -6,6 +6,10 @@
|
||||
#include "helpers.h"
|
||||
#include "log.h"
|
||||
|
||||
#ifdef USE_DEVICES
|
||||
#include "device.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
|
||||
enum EntityCategory : uint8_t {
|
||||
@ -51,6 +55,17 @@ class EntityBase {
|
||||
std::string get_icon() const;
|
||||
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
|
||||
bool has_state() const { return this->flags_.has_state; }
|
||||
|
||||
@ -67,6 +82,9 @@ class EntityBase {
|
||||
const char *object_id_c_str_{nullptr};
|
||||
const char *icon_c_str_{nullptr};
|
||||
uint32_t object_id_hash_{};
|
||||
#ifdef USE_DEVICES
|
||||
Device *device_{};
|
||||
#endif
|
||||
|
||||
// Bit-packed flags to save memory (1 byte instead of 5)
|
||||
struct EntityFlags {
|
||||
|
@ -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
|
||||
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):
|
||||
@ -54,3 +165,48 @@ def inherit_property_from(property_to_inherit, parent_id_property, transform=Non
|
||||
return config
|
||||
|
||||
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
|
||||
|
@ -1,11 +1,6 @@
|
||||
import logging
|
||||
|
||||
from esphome.const import (
|
||||
CONF_DISABLED_BY_DEFAULT,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_ICON,
|
||||
CONF_INTERNAL,
|
||||
CONF_NAME,
|
||||
CONF_SAFE_MODE,
|
||||
CONF_SETUP_PRIORITY,
|
||||
CONF_TYPE_ID,
|
||||
@ -16,7 +11,6 @@ from esphome.core import CORE, ID, coroutine
|
||||
from esphome.coroutine import FakeAwaitable
|
||||
from esphome.cpp_generator import add, get_variable
|
||||
from esphome.cpp_types import App
|
||||
from esphome.helpers import sanitize, snake_case
|
||||
from esphome.types import ConfigFragmentType, ConfigType
|
||||
from esphome.util import Registry, RegistryEntry
|
||||
|
||||
@ -96,22 +90,6 @@ async def register_parented(var, value):
|
||||
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(
|
||||
registry: Registry,
|
||||
full_config: ConfigType,
|
||||
|
@ -1,25 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unicodedata
|
||||
|
||||
from esphome.const import ALLOWED_NAME_CHARS
|
||||
from esphome.helpers import slugify
|
||||
|
||||
|
||||
def strip_accents(value):
|
||||
return "".join(
|
||||
c
|
||||
for c in unicodedata.normalize("NFD", str(value))
|
||||
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)
|
||||
def friendly_name_slugify(value: str) -> str:
|
||||
"""Convert a friendly name to a slug with dashes instead of underscores."""
|
||||
# First use the standard slugify, then convert underscores to dashes
|
||||
return slugify(value).replace("_", "-")
|
||||
|
@ -29,6 +29,53 @@ def ensure_unique_string(preferred_string, current_strings):
|
||||
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=" "):
|
||||
lines = text.splitlines(True)
|
||||
if len(lines) <= 2:
|
||||
|
@ -12,12 +12,12 @@ sensor:
|
||||
frequency: 60Hz
|
||||
phase_a:
|
||||
name: Channel A
|
||||
voltage: Voltage
|
||||
current: Current
|
||||
active_power: Active Power
|
||||
power_factor: Power Factor
|
||||
forward_active_energy: Forward Active Energy
|
||||
reverse_active_energy: Reverse Active Energy
|
||||
voltage: Channel A Voltage
|
||||
current: Channel A Current
|
||||
active_power: Channel A Active Power
|
||||
power_factor: Channel A Power Factor
|
||||
forward_active_energy: Channel A Forward Active Energy
|
||||
reverse_active_energy: Channel A Reverse Active Energy
|
||||
calibration:
|
||||
current_gain: 3116628
|
||||
voltage_gain: -757178
|
||||
@ -25,12 +25,12 @@ sensor:
|
||||
phase_angle: 188
|
||||
phase_b:
|
||||
name: Channel B
|
||||
voltage: Voltage
|
||||
current: Current
|
||||
active_power: Active Power
|
||||
power_factor: Power Factor
|
||||
forward_active_energy: Forward Active Energy
|
||||
reverse_active_energy: Reverse Active Energy
|
||||
voltage: Channel B Voltage
|
||||
current: Channel B Current
|
||||
active_power: Channel B Active Power
|
||||
power_factor: Channel B Power Factor
|
||||
forward_active_energy: Channel B Forward Active Energy
|
||||
reverse_active_energy: Channel B Reverse Active Energy
|
||||
calibration:
|
||||
current_gain: 3133655
|
||||
voltage_gain: -755235
|
||||
@ -38,12 +38,12 @@ sensor:
|
||||
phase_angle: 188
|
||||
phase_c:
|
||||
name: Channel C
|
||||
voltage: Voltage
|
||||
current: Current
|
||||
active_power: Active Power
|
||||
power_factor: Power Factor
|
||||
forward_active_energy: Forward Active Energy
|
||||
reverse_active_energy: Reverse Active Energy
|
||||
voltage: Channel C Voltage
|
||||
current: Channel C Current
|
||||
active_power: Channel C Active Power
|
||||
power_factor: Channel C Power Factor
|
||||
forward_active_energy: Channel C Forward Active Energy
|
||||
reverse_active_energy: Channel C Reverse Active Energy
|
||||
calibration:
|
||||
current_gain: 3111158
|
||||
voltage_gain: -743813
|
||||
@ -51,6 +51,6 @@ sensor:
|
||||
phase_angle: 180
|
||||
neutral:
|
||||
name: Neutral
|
||||
current: Current
|
||||
current: Neutral Current
|
||||
calibration:
|
||||
current_gain: 3189
|
||||
|
@ -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())));
|
||||
- platform: template
|
||||
id: alarmcontrolpanel2
|
||||
name: Alarm Panel
|
||||
name: Alarm Panel 2
|
||||
codes:
|
||||
- "1234"
|
||||
requires_code_to_arm: true
|
||||
|
@ -26,7 +26,7 @@ binary_sensor:
|
||||
|
||||
sensor:
|
||||
- platform: binary_sensor_map
|
||||
name: Binary Sensor Map
|
||||
name: Binary Sensor Map Group
|
||||
type: group
|
||||
channels:
|
||||
- binary_sensor: bin1
|
||||
@ -36,7 +36,7 @@ sensor:
|
||||
- binary_sensor: bin3
|
||||
value: 100.0
|
||||
- platform: binary_sensor_map
|
||||
name: Binary Sensor Map
|
||||
name: Binary Sensor Map Sum
|
||||
type: sum
|
||||
channels:
|
||||
- binary_sensor: bin1
|
||||
@ -46,7 +46,7 @@ sensor:
|
||||
- binary_sensor: bin3
|
||||
value: 100.0
|
||||
- platform: binary_sensor_map
|
||||
name: Binary Sensor Map
|
||||
name: Binary Sensor Map Bayesian
|
||||
type: bayesian
|
||||
prior: 0.4
|
||||
observations:
|
||||
|
@ -5,7 +5,7 @@ one_wire:
|
||||
sensor:
|
||||
- platform: dallas_temp
|
||||
address: 0x1C0000031EDD2A28
|
||||
name: Dallas Temperature
|
||||
name: Dallas Temperature 1
|
||||
resolution: 9
|
||||
- platform: dallas_temp
|
||||
name: Dallas Temperature
|
||||
name: Dallas Temperature 2
|
||||
|
@ -2,7 +2,9 @@ esphome:
|
||||
debug_scheduler: true
|
||||
platformio_options:
|
||||
board_build.flash_mode: dio
|
||||
area: testing
|
||||
area:
|
||||
id: testing_area
|
||||
name: Testing Area
|
||||
on_boot:
|
||||
logger.log: on_boot
|
||||
on_shutdown:
|
||||
@ -17,4 +19,20 @@ esphome:
|
||||
version: "1.1"
|
||||
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
|
||||
|
@ -7,20 +7,20 @@ climate:
|
||||
protocol: mitsubishi_heavy_zm
|
||||
horizontal_default: left
|
||||
vertical_default: up
|
||||
name: HeatpumpIR Climate
|
||||
name: HeatpumpIR Climate Mitsubishi
|
||||
min_temperature: 18
|
||||
max_temperature: 30
|
||||
- platform: heatpumpir
|
||||
protocol: daikin
|
||||
horizontal_default: mleft
|
||||
vertical_default: mup
|
||||
name: HeatpumpIR Climate
|
||||
name: HeatpumpIR Climate Daikin
|
||||
min_temperature: 18
|
||||
max_temperature: 30
|
||||
- platform: heatpumpir
|
||||
protocol: panasonic_altdke
|
||||
horizontal_default: mright
|
||||
vertical_default: mdown
|
||||
name: HeatpumpIR Climate
|
||||
name: HeatpumpIR Climate Panasonic
|
||||
min_temperature: 18
|
||||
max_temperature: 30
|
||||
|
@ -114,7 +114,7 @@ light:
|
||||
warm_white_color_temperature: 500 mireds
|
||||
- platform: rgb
|
||||
id: test_rgb_light_initial_state
|
||||
name: RGB Light
|
||||
name: RGB Light Initial State
|
||||
red: test_ledc_1
|
||||
green: test_ledc_2
|
||||
blue: test_ledc_3
|
||||
|
@ -6,13 +6,13 @@ i2c:
|
||||
sensor:
|
||||
- platform: ltr390
|
||||
uv:
|
||||
name: LTR390 UV
|
||||
name: LTR390 UV 1
|
||||
uv_index:
|
||||
name: LTR390 UVI
|
||||
name: LTR390 UVI 1
|
||||
light:
|
||||
name: LTR390 Light
|
||||
name: LTR390 Light 1
|
||||
ambient_light:
|
||||
name: LTR390 ALS
|
||||
name: LTR390 ALS 1
|
||||
gain: X3
|
||||
resolution: 18
|
||||
window_correction_factor: 1.0
|
||||
@ -20,13 +20,13 @@ sensor:
|
||||
update_interval: 60s
|
||||
- platform: ltr390
|
||||
uv:
|
||||
name: LTR390 UV
|
||||
name: LTR390 UV 2
|
||||
uv_index:
|
||||
name: LTR390 UVI
|
||||
name: LTR390 UVI 2
|
||||
light:
|
||||
name: LTR390 Light
|
||||
name: LTR390 Light 2
|
||||
ambient_light:
|
||||
name: LTR390 ALS
|
||||
name: LTR390 ALS 2
|
||||
gain:
|
||||
ambient_light: X9
|
||||
uv: X3
|
||||
|
@ -24,33 +24,33 @@ sensor:
|
||||
widget: lv_arc
|
||||
- platform: lvgl
|
||||
widget: slider_id
|
||||
name: LVGL Slider
|
||||
name: LVGL Slider Sensor
|
||||
- platform: lvgl
|
||||
widget: bar_id
|
||||
id: lvgl_bar_sensor
|
||||
name: LVGL Bar
|
||||
name: LVGL Bar Sensor
|
||||
- platform: lvgl
|
||||
widget: spinbox_id
|
||||
name: LVGL Spinbox
|
||||
name: LVGL Spinbox Sensor
|
||||
|
||||
number:
|
||||
- platform: lvgl
|
||||
widget: slider_id
|
||||
name: LVGL Slider
|
||||
name: LVGL Slider Number
|
||||
update_on_release: true
|
||||
restore_value: true
|
||||
- platform: lvgl
|
||||
widget: lv_arc
|
||||
id: lvgl_arc_number
|
||||
name: LVGL Arc
|
||||
name: LVGL Arc Number
|
||||
- platform: lvgl
|
||||
widget: bar_id
|
||||
id: lvgl_bar_number
|
||||
name: LVGL Bar
|
||||
name: LVGL Bar Number
|
||||
- platform: lvgl
|
||||
widget: spinbox_id
|
||||
id: lvgl_spinbox_number
|
||||
name: LVGL Spinbox
|
||||
name: LVGL Spinbox Number
|
||||
|
||||
light:
|
||||
- platform: lvgl
|
||||
|
@ -170,4 +170,4 @@ switch:
|
||||
otc_active:
|
||||
name: "Boiler Outside temperature compensation active"
|
||||
ch2_active:
|
||||
name: "Boiler Central Heating 2 active"
|
||||
name: "Boiler Central Heating 2 active status"
|
||||
|
@ -5,7 +5,7 @@ packages:
|
||||
- !include package.yaml
|
||||
- github://esphome/esphome/tests/components/template/common.yaml@dev
|
||||
- url: https://github.com/esphome/esphome
|
||||
file: tests/components/binary_sensor_map/common.yaml
|
||||
file: tests/components/absolute_humidity/common.yaml
|
||||
ref: dev
|
||||
refresh: 1d
|
||||
|
||||
|
@ -7,7 +7,7 @@ packages:
|
||||
shorthand: github://esphome/esphome/tests/components/template/common.yaml@dev
|
||||
github:
|
||||
url: https://github.com/esphome/esphome
|
||||
file: tests/components/binary_sensor_map/common.yaml
|
||||
file: tests/components/absolute_humidity/common.yaml
|
||||
ref: dev
|
||||
refresh: 1d
|
||||
|
||||
|
@ -115,7 +115,7 @@ button:
|
||||
address: 0x00
|
||||
command: 0x0B
|
||||
- platform: template
|
||||
name: RC5
|
||||
name: RC5 Raw
|
||||
on_press:
|
||||
remote_transmitter.transmit_raw:
|
||||
code: [1000, -1000]
|
||||
|
@ -12,7 +12,7 @@
|
||||
using namespace esphome;
|
||||
|
||||
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
|
||||
log->pre_setup();
|
||||
log->set_uart_selection(logger::UART_SELECTION_UART0);
|
||||
|
@ -203,6 +203,7 @@ async def compile_esphome(
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
def _read_config_and_get_binary():
|
||||
CORE.reset() # Reset CORE state between test runs
|
||||
CORE.config_path = str(config_path)
|
||||
config = esphome.config.read_config(
|
||||
{"command": "compile", "config": str(config_path)}
|
||||
|
57
tests/integration/fixtures/areas_and_devices.yaml
Normal file
57
tests/integration/fixtures/areas_and_devices.yaml
Normal 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
|
||||
|
@ -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: []
|
15
tests/integration/fixtures/legacy_area.yaml
Normal file
15
tests/integration/fixtures/legacy_area.yaml
Normal 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
|
121
tests/integration/test_areas_and_devices.py
Normal file
121
tests/integration/test_areas_and_devices.py
Normal 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}"
|
||||
)
|
184
tests/integration/test_duplicate_entities.py
Normal file
184
tests/integration/test_duplicate_entities.py
Normal 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}"
|
||||
)
|
41
tests/integration/test_legacy_area.py
Normal file
41
tests/integration/test_legacy_area.py
Normal 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)
|
@ -14,6 +14,8 @@ import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.core import CORE
|
||||
|
||||
here = Path(__file__).parent
|
||||
|
||||
# Configure location of package root
|
||||
@ -21,6 +23,13 @@ package_root = here.parent.parent
|
||||
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
|
||||
def fixture_path() -> Path:
|
||||
"""
|
||||
|
0
tests/unit_tests/core/__init__.py
Normal file
0
tests/unit_tests/core/__init__.py
Normal file
33
tests/unit_tests/core/common.py
Normal file
33
tests/unit_tests/core/common.py
Normal 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)
|
18
tests/unit_tests/core/conftest.py
Normal file
18
tests/unit_tests/core/conftest.py
Normal 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
|
225
tests/unit_tests/core/test_config.py
Normal file
225
tests/unit_tests/core/test_config.py
Normal 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
|
595
tests/unit_tests/core/test_entity_helpers.py
Normal file
595
tests/unit_tests/core/test_entity_helpers.py
Normal 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
|
10
tests/unit_tests/fixtures/core/config/area_id_collision.yaml
Normal file
10
tests/unit_tests/fixtures/core/config/area_id_collision.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
esphome:
|
||||
name: test-collision
|
||||
area:
|
||||
id: duplicate_id
|
||||
name: Area 1
|
||||
areas:
|
||||
- id: duplicate_id
|
||||
name: Area 2
|
||||
|
||||
host:
|
@ -0,0 +1,10 @@
|
||||
esphome:
|
||||
name: test
|
||||
areas:
|
||||
- id: test_2258
|
||||
name: "Area 1"
|
||||
- id: d6ka
|
||||
name: "Area 2"
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
@ -0,0 +1,10 @@
|
||||
esphome:
|
||||
name: test
|
||||
devices:
|
||||
- id: duplicate_device
|
||||
name: "Device 1"
|
||||
- id: duplicate_device
|
||||
name: "Device 2"
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
@ -0,0 +1,10 @@
|
||||
esphome:
|
||||
name: test
|
||||
devices:
|
||||
- id: test_2258
|
||||
name: "Device 1"
|
||||
- id: d6ka
|
||||
name: "Device 2"
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
@ -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
|
@ -0,0 +1,7 @@
|
||||
esphome:
|
||||
name: test-device-no-area
|
||||
devices:
|
||||
- id: test_device
|
||||
name: Test Device
|
||||
|
||||
host:
|
@ -0,0 +1,5 @@
|
||||
esphome:
|
||||
name: test-legacy-area
|
||||
area: Living Room
|
||||
|
||||
host:
|
@ -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:
|
11
tests/unit_tests/fixtures/core/config/valid_area_device.yaml
Normal file
11
tests/unit_tests/fixtures/core/config/valid_area_device.yaml
Normal 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:
|
@ -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;
|
@ -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;
|
@ -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"};
|
Loading…
x
Reference in New Issue
Block a user