From 25b5a6c4ae3f5a951f324206a4c3fd6e818dd54d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Fri, 5 Apr 2024 11:53:43 +0200 Subject: [PATCH 01/99] Add device_id to entity_base --- esphome/components/api/api.proto | 2 ++ esphome/components/api/api_connection.cpp | 2 ++ esphome/components/api/api_pb2.cpp | 18 ++++++++++++++++++ esphome/components/api/api_pb2.h | 2 ++ esphome/config_validation.py | 15 +++++++++++++++ esphome/const.py | 2 ++ esphome/core/entity_base.cpp | 9 +++++++++ esphome/core/entity_base.h | 5 +++++ esphome/cpp_helpers.py | 4 ++++ 9 files changed, 59 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index d59b5e0d3e..e90586a42b 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -273,6 +273,7 @@ message ListEntitiesBinarySensorResponse { bool disabled_by_default = 7; string icon = 8; EntityCategory entity_category = 9; + string device_name = 10; } message BinarySensorStateResponse { option (id) = 21; @@ -306,6 +307,7 @@ message ListEntitiesCoverResponse { string icon = 10; EntityCategory entity_category = 11; bool supports_stop = 12; + string device_name = 13; } enum LegacyCoverState { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 9d7b8c1780..2dddc3b4e0 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -280,6 +280,7 @@ bool APIConnection::try_send_binary_sensor_info(APIConnection *api, void *v_bina msg.disabled_by_default = binary_sensor->is_disabled_by_default(); msg.icon = binary_sensor->get_icon(); msg.entity_category = static_cast(binary_sensor->get_entity_category()); + msg.device_name = binary_sensor->get_device_name(); return api->send_list_entities_binary_sensor_response(msg); } #endif @@ -330,6 +331,7 @@ bool APIConnection::try_send_cover_info(APIConnection *api, void *v_cover) { msg.disabled_by_default = cover->is_disabled_by_default(); msg.icon = cover->get_icon(); msg.entity_category = static_cast(cover->get_entity_category()); + msg.device_name = cover->get_device_name(); return api->send_list_entities_cover_response(msg); } void APIConnection::cover_command(const CoverCommandRequest &msg) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 8001a74b6d..f386924d5e 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1001,6 +1001,10 @@ bool ListEntitiesBinarySensorResponse::decode_length(uint32_t field_id, ProtoLen this->icon = value.as_string(); return true; } + case 10: { + this->device_name = value.as_string(); + return true; + } default: return false; } @@ -1025,6 +1029,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(7, this->disabled_by_default); buffer.encode_string(8, this->icon); buffer.encode_enum(9, this->entity_category); + buffer.encode_string(10, this->device_name); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { @@ -1066,6 +1071,10 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { out.append(" entity_category: "); out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); + + out.append(" device_name: "); + out.append("'").append(this->device_name).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -1169,6 +1178,10 @@ bool ListEntitiesCoverResponse::decode_length(uint32_t field_id, ProtoLengthDeli this->icon = value.as_string(); return true; } + case 13: { + this->device_name = value.as_string(); + return true; + } default: return false; } @@ -1196,6 +1209,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(10, this->icon); buffer.encode_enum(11, this->entity_category); buffer.encode_bool(12, this->supports_stop); + buffer.encode_string(13, this->device_name); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCoverResponse::dump_to(std::string &out) const { @@ -1249,6 +1263,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { out.append(" supports_stop: "); out.append(YESNO(this->supports_stop)); out.append("\n"); + + out.append(" device_name: "); + out.append("'").append(this->device_name).append("'"); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 455e3ff6cf..247ec0d65a 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -402,6 +402,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage { bool disabled_by_default{false}; std::string icon{}; enums::EntityCategory entity_category{}; + std::string device_name{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -440,6 +441,7 @@ class ListEntitiesCoverResponse : public ProtoMessage { std::string icon{}; enums::EntityCategory entity_category{}; bool supports_stop{false}; + std::string device_name{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 858c6e197c..0abbfc1aff 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -21,6 +21,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, @@ -348,6 +349,18 @@ def icon(value): ) +def device_name(value): + """Validate that a given config value is a valid device name.""" + value = string_strict(value) + if not value: + return value + # if re.match("^[\\w\\-]+:[\\w\\-]+$", value): + # return value + raise Invalid( + 'device name must be string that matches a defined device in "deviced:" section' + ) + + def boolean(value): """Validate the given config option to be a boolean. @@ -1867,6 +1880,8 @@ 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): device_name, + } ) diff --git a/esphome/const.py b/esphome/const.py index f6f9b7df80..361d8147bd 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -210,8 +210,10 @@ CONF_DELIMITER = "delimiter" CONF_DELTA = "delta" CONF_DEST = "dest" CONF_DEVICE = "device" +CONF_DEVICES = "devices" CONF_DEVICE_CLASS = "device_class" CONF_DEVICE_FACTOR = "device_factor" +CONF_DEVICE_ID = "device_id" CONF_DIELECTRIC_CONSTANT = "dielectric_constant" CONF_DIMENSIONS = "dimensions" CONF_DIO_PIN = "dio_pin" diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index 725a8569a3..883c23e9f3 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -35,6 +35,15 @@ std::string EntityBase::get_icon() const { } void EntityBase::set_icon(const char *icon) { this->icon_c_str_ = icon; } +// Entity Device Name +std::string EntityBase::get_device_name() const { + if (this->device_name_c_str_ == nullptr) { + return ""; + } + return this->device_name_c_str_; +} +void EntityBase::set_device_name(const char *device_name) { this->device_name_c_str_ = device_name; } + // Entity Category EntityCategory EntityBase::get_entity_category() const { return this->entity_category_; } void EntityBase::set_entity_category(EntityCategory entity_category) { this->entity_category_ = entity_category; } diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 4ca21f9ee5..342a1fc042 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -47,6 +47,10 @@ class EntityBase { std::string get_icon() const; void set_icon(const char *icon); + // Get/set this entity's device name + std::string get_device_name() const; + void set_device_name(const char *icon); + protected: /// The hash_base() function has been deprecated. It is kept in this /// class for now, to prevent external components from not compiling. @@ -61,6 +65,7 @@ class EntityBase { bool internal_{false}; bool disabled_by_default_{false}; EntityCategory entity_category_{ENTITY_CATEGORY_NONE}; + const char *device_name_c_str_{nullptr}; }; class EntityBase_DeviceClass { // NOLINT(readability-identifier-naming) diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 9a775bad33..c1b1828d1c 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -1,6 +1,7 @@ import logging from esphome.const import ( + CONF_DEVICE_ID, CONF_DISABLED_BY_DEFAULT, CONF_ENTITY_CATEGORY, CONF_ICON, @@ -110,6 +111,9 @@ async def setup_entity(var, config): add(var.set_icon(config[CONF_ICON])) if CONF_ENTITY_CATEGORY in config: add(var.set_entity_category(config[CONF_ENTITY_CATEGORY])) + if CONF_DEVICE_ID in config: + # TODO: lookup the device from devices: section and get the real name + add(var.set_device_name(config[CONF_DEVICE_ID])) def extract_registry_entry_config( From 1bd8985dff9e0027dcea6320e735ed828208d5e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Fri, 5 Apr 2024 13:50:21 +0200 Subject: [PATCH 02/99] Add a device component --- esphome/components/device/__init__.py | 35 +++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 esphome/components/device/__init__.py diff --git a/esphome/components/device/__init__.py b/esphome/components/device/__init__.py new file mode 100644 index 0000000000..7e45eb9c75 --- /dev/null +++ b/esphome/components/device/__init__.py @@ -0,0 +1,35 @@ +from esphome import config_validation as cv +from esphome import codegen as cg +from esphome.const import CONF_ID, CONF_NAME + +DeviceStruct = cg.esphome_ns.struct("Device") + +MULTI_CONF = True + + +CONFIG_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(DeviceStruct), + cv.Required(CONF_NAME): cv.string, + # cv.Exclusive(CONF_RED, "red"): cv.percentage, + # cv.Exclusive(CONF_RED_INT, "red"): cv.uint8_t, + # cv.Exclusive(CONF_GREEN, "green"): cv.percentage, + # cv.Exclusive(CONF_GREEN_INT, "green"): cv.uint8_t, + # cv.Exclusive(CONF_BLUE, "blue"): cv.percentage, + # cv.Exclusive(CONF_BLUE_INT, "blue"): cv.uint8_t, + # cv.Exclusive(CONF_WHITE, "white"): cv.percentage, + # cv.Exclusive(CONF_WHITE_INT, "white"): cv.uint8_t, + }).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + # paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID]) + # var = cg.new_Pvariable(config[CONF_ID], paren) + # await cg.register_component(var, config) + # cg.add_define("USE_CAPTIVE_PORTAL") + + cg.new_variable( + config[CONF_ID], + cg.new_Pvariable(config[CONF_NAME]), + ) + # cg.add_define("USE_DEVICE_ID") From a8b76c617c09af7fb73ed1873e6c62c0d61d5acd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Sat, 6 Apr 2024 00:03:26 +0200 Subject: [PATCH 03/99] Some basic chain working --- esphome/components/device/__init__.py | 32 ++++++++++++--------------- esphome/components/device/device.h | 14 ++++++++++++ esphome/config_validation.py | 18 +++++---------- esphome/const.py | 1 - esphome/core/entity_base.cpp | 10 ++++----- esphome/core/entity_base.h | 6 ++--- esphome/cpp_helpers.py | 4 ++-- 7 files changed, 44 insertions(+), 41 deletions(-) create mode 100644 esphome/components/device/device.h diff --git a/esphome/components/device/__init__.py b/esphome/components/device/__init__.py index 7e45eb9c75..4d1be53a0b 100644 --- a/esphome/components/device/__init__.py +++ b/esphome/components/device/__init__.py @@ -2,34 +2,30 @@ from esphome import config_validation as cv from esphome import codegen as cg from esphome.const import CONF_ID, CONF_NAME -DeviceStruct = cg.esphome_ns.struct("Device") +# DeviceStruct = cg.esphome_ns.struct("Device") +# StringVar = cg.std_ns.struct("string") +StringRef = cg.esphome_ns.struct("StringRef") MULTI_CONF = True CONFIG_SCHEMA = cv.Schema( { - cv.Required(CONF_ID): cv.declare_id(DeviceStruct), + # cv.Required(CONF_ID): cv.declare_id(DeviceStruct), + # cv.Required(CONF_ID): cv.declare_id(StringVar), + cv.Required(CONF_ID): cv.declare_id(StringRef), cv.Required(CONF_NAME): cv.string, - # cv.Exclusive(CONF_RED, "red"): cv.percentage, - # cv.Exclusive(CONF_RED_INT, "red"): cv.uint8_t, - # cv.Exclusive(CONF_GREEN, "green"): cv.percentage, - # cv.Exclusive(CONF_GREEN_INT, "green"): cv.uint8_t, - # cv.Exclusive(CONF_BLUE, "blue"): cv.percentage, - # cv.Exclusive(CONF_BLUE_INT, "blue"): cv.uint8_t, - # cv.Exclusive(CONF_WHITE, "white"): cv.percentage, - # cv.Exclusive(CONF_WHITE_INT, "white"): cv.uint8_t, - }).extend(cv.COMPONENT_SCHEMA) + } +).extend(cv.COMPONENT_SCHEMA) async def to_code(config): - # paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID]) - # var = cg.new_Pvariable(config[CONF_ID], paren) - # await cg.register_component(var, config) - # cg.add_define("USE_CAPTIVE_PORTAL") - - cg.new_variable( + # cg.new_variable( + # config[CONF_ID], + # config[CONF_NAME], + # ) + cg.new_Pvariable( config[CONF_ID], - cg.new_Pvariable(config[CONF_NAME]), + config[CONF_NAME], ) # cg.add_define("USE_DEVICE_ID") diff --git a/esphome/components/device/device.h b/esphome/components/device/device.h new file mode 100644 index 0000000000..936c48b0da --- /dev/null +++ b/esphome/components/device/device.h @@ -0,0 +1,14 @@ +#pragma once + +namespace esphome { + +class Device { + public: + void set_name(std::string name) { name_ = name; } + std::string get_name(void) {return name_;} + + protected: + std::string name_ = ""; +}; + +} // namespace esphome diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 0abbfc1aff..14a64d2277 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -349,16 +349,10 @@ def icon(value): ) -def device_name(value): - """Validate that a given config value is a valid device name.""" - value = string_strict(value) - if not value: - return value - # if re.match("^[\\w\\-]+:[\\w\\-]+$", value): - # return value - raise Invalid( - 'device name must be string that matches a defined device in "deviced:" section' - ) +def device_id(value): + StringRef = cg.esphome_ns.struct("StringRef") + validator = use_id(StringRef) + return validator(value) def boolean(value): @@ -1880,8 +1874,8 @@ 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): device_name, - + # Optional(CONF_DEVICE_ID): use_id(StringRef), + Optional(CONF_DEVICE_ID): device_id, } ) diff --git a/esphome/const.py b/esphome/const.py index 361d8147bd..55580e5bcd 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -210,7 +210,6 @@ CONF_DELIMITER = "delimiter" CONF_DELTA = "delta" CONF_DEST = "dest" CONF_DEVICE = "device" -CONF_DEVICES = "devices" CONF_DEVICE_CLASS = "device_class" CONF_DEVICE_FACTOR = "device_factor" CONF_DEVICE_ID = "device_id" diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index 883c23e9f3..15864e793c 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -36,13 +36,13 @@ std::string EntityBase::get_icon() const { void EntityBase::set_icon(const char *icon) { this->icon_c_str_ = icon; } // Entity Device Name -std::string EntityBase::get_device_name() const { - if (this->device_name_c_str_ == nullptr) { - return ""; +StringRef EntityBase::get_device_name() const { + if (this->device_name_.empty()) { + return StringRef(""); } - return this->device_name_c_str_; + return this->device_name_; } -void EntityBase::set_device_name(const char *device_name) { this->device_name_c_str_ = device_name; } +void EntityBase::set_device_name(const StringRef *device_name) { this->device_name_ = *device_name; } // Entity Category EntityCategory EntityBase::get_entity_category() const { return this->entity_category_; } diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 342a1fc042..0f6b222efd 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -48,8 +48,8 @@ class EntityBase { void set_icon(const char *icon); // Get/set this entity's device name - std::string get_device_name() const; - void set_device_name(const char *icon); + StringRef get_device_name() const; + void set_device_name(const StringRef *device_name); protected: /// The hash_base() function has been deprecated. It is kept in this @@ -65,7 +65,7 @@ class EntityBase { bool internal_{false}; bool disabled_by_default_{false}; EntityCategory entity_category_{ENTITY_CATEGORY_NONE}; - const char *device_name_c_str_{nullptr}; + StringRef device_name_; }; class EntityBase_DeviceClass { // NOLINT(readability-identifier-naming) diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index c1b1828d1c..afd951b504 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -112,8 +112,8 @@ async def setup_entity(var, config): if CONF_ENTITY_CATEGORY in config: add(var.set_entity_category(config[CONF_ENTITY_CATEGORY])) if CONF_DEVICE_ID in config: - # TODO: lookup the device from devices: section and get the real name - add(var.set_device_name(config[CONF_DEVICE_ID])) + parent = await get_variable(config[CONF_DEVICE_ID]) + add(var.set_device_name(parent)) def extract_registry_entry_config( From 7b647c3faeedf263b67ec10efffec961f1515969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Sat, 6 Apr 2024 00:08:43 +0200 Subject: [PATCH 04/99] Add a single test --- tests/components/device/common.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tests/components/device/common.yaml diff --git a/tests/components/device/common.yaml b/tests/components/device/common.yaml new file mode 100644 index 0000000000..0f24038167 --- /dev/null +++ b/tests/components/device/common.yaml @@ -0,0 +1,11 @@ +device: + - id: other_device + name: Another device + +binary_sensor: + - platform: template + name: Basic sensor + + - platform: template + name: Other device sensor + device_id: other_device From 583e5ea47f4a654a5815ec384b2f206193de601b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Sat, 6 Apr 2024 00:13:14 +0200 Subject: [PATCH 05/99] Add code-owner tag --- esphome/components/device/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/device/__init__.py b/esphome/components/device/__init__.py index 4d1be53a0b..b21ab7ec23 100644 --- a/esphome/components/device/__init__.py +++ b/esphome/components/device/__init__.py @@ -8,6 +8,7 @@ StringRef = cg.esphome_ns.struct("StringRef") MULTI_CONF = True +CODEOWNERS = ["@dala318"] CONFIG_SCHEMA = cv.Schema( { From 3b5fbc359f6fb119a6a820870f36dc4e7a4fd569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Sat, 6 Apr 2024 00:29:08 +0200 Subject: [PATCH 06/99] Formating updates --- esphome/components/device/__init__.py | 11 +++-------- esphome/components/device/device.h | 4 +++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/esphome/components/device/__init__.py b/esphome/components/device/__init__.py index b21ab7ec23..c7ecbb31f1 100644 --- a/esphome/components/device/__init__.py +++ b/esphome/components/device/__init__.py @@ -2,8 +2,8 @@ from esphome import config_validation as cv from esphome import codegen as cg from esphome.const import CONF_ID, CONF_NAME -# DeviceStruct = cg.esphome_ns.struct("Device") -# StringVar = cg.std_ns.struct("string") +# ns = cg.esphome_ns.namespace("device") +# DeviceClass = ns.Class("Device") StringRef = cg.esphome_ns.struct("StringRef") MULTI_CONF = True @@ -12,8 +12,7 @@ CODEOWNERS = ["@dala318"] CONFIG_SCHEMA = cv.Schema( { - # cv.Required(CONF_ID): cv.declare_id(DeviceStruct), - # cv.Required(CONF_ID): cv.declare_id(StringVar), + # cv.Required(CONF_ID): cv.declare_id(DeviceClass), cv.Required(CONF_ID): cv.declare_id(StringRef), cv.Required(CONF_NAME): cv.string, } @@ -21,10 +20,6 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): - # cg.new_variable( - # config[CONF_ID], - # config[CONF_NAME], - # ) cg.new_Pvariable( config[CONF_ID], config[CONF_NAME], diff --git a/esphome/components/device/device.h b/esphome/components/device/device.h index 936c48b0da..49a7b88704 100644 --- a/esphome/components/device/device.h +++ b/esphome/components/device/device.h @@ -1,14 +1,16 @@ #pragma once namespace esphome { +namespace device { class Device { public: void set_name(std::string name) { name_ = name; } - std::string get_name(void) {return name_;} + std::string get_name(void) { return name_; } protected: std::string name_ = ""; }; +} // namespace device } // namespace esphome From 68ecc0811149a2a7b3719bea99883c6770e5494c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Wed, 9 Apr 2025 00:11:05 +0200 Subject: [PATCH 07/99] Register device_id to entity and separate struct for all device info --- esphome/components/api/api.proto | 12 +++++++++-- esphome/components/api/api_connection.cpp | 4 ++-- esphome/components/api/api_pb2.cpp | 16 +++++++-------- esphome/components/api/api_pb2.h | 4 ++-- .../{device => devices}/__init__.py | 9 ++++----- .../{device/device.h => devices/devices.h} | 8 +++++--- esphome/core/application.h | 20 +++++++++++++++++++ esphome/core/defines.h | 1 + esphome/core/entity_base.cpp | 8 ++++---- esphome/core/entity_base.h | 6 +++--- esphome/cpp_helpers.py | 6 +++++- tests/components/device/common.yaml | 2 +- 12 files changed, 65 insertions(+), 31 deletions(-) rename esphome/components/{device => devices}/__init__.py (71%) rename esphome/components/{device/device.h => devices/devices.h} (62%) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index e90586a42b..10f5aace5e 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -185,6 +185,12 @@ message DeviceInfoRequest { // Empty } +message SubDeviceInfo { + string id = 1; + string name = 2; + string suggested_area = 3; +} + message DeviceInfoResponse { option (id) = 10; option (source) = SOURCE_SERVER; @@ -230,6 +236,8 @@ message DeviceInfoResponse { // The Bluetooth mac address of the device. For example "AC:BC:32:89:0E:AA" string bluetooth_mac_address = 18; + + repeated SubDeviceInfo sub_devices = 19; } message ListEntitiesRequest { @@ -273,7 +281,7 @@ message ListEntitiesBinarySensorResponse { bool disabled_by_default = 7; string icon = 8; EntityCategory entity_category = 9; - string device_name = 10; + string device_id = 10; } message BinarySensorStateResponse { option (id) = 21; @@ -307,7 +315,7 @@ message ListEntitiesCoverResponse { string icon = 10; EntityCategory entity_category = 11; bool supports_stop = 12; - string device_name = 13; + string device_id = 13; } enum LegacyCoverState { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 2dddc3b4e0..2fdf95192b 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -280,7 +280,7 @@ bool APIConnection::try_send_binary_sensor_info(APIConnection *api, void *v_bina msg.disabled_by_default = binary_sensor->is_disabled_by_default(); msg.icon = binary_sensor->get_icon(); msg.entity_category = static_cast(binary_sensor->get_entity_category()); - msg.device_name = binary_sensor->get_device_name(); + msg.device_id = binary_sensor->get_device_id(); return api->send_list_entities_binary_sensor_response(msg); } #endif @@ -331,7 +331,7 @@ bool APIConnection::try_send_cover_info(APIConnection *api, void *v_cover) { msg.disabled_by_default = cover->is_disabled_by_default(); msg.icon = cover->get_icon(); msg.entity_category = static_cast(cover->get_entity_category()); - msg.device_name = cover->get_device_name(); + msg.device_id = cover->get_device_id(); return api->send_list_entities_cover_response(msg); } void APIConnection::cover_command(const CoverCommandRequest &msg) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index f386924d5e..61a53e4a0c 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1002,7 +1002,7 @@ bool ListEntitiesBinarySensorResponse::decode_length(uint32_t field_id, ProtoLen return true; } case 10: { - this->device_name = value.as_string(); + this->device_id = value.as_string(); return true; } default: @@ -1029,7 +1029,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(7, this->disabled_by_default); buffer.encode_string(8, this->icon); buffer.encode_enum(9, this->entity_category); - buffer.encode_string(10, this->device_name); + buffer.encode_string(10, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { @@ -1072,8 +1072,8 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); - out.append(" device_name: "); - out.append("'").append(this->device_name).append("'"); + out.append(" device_id: "); + out.append("'").append(this->device_id).append("'"); out.append("\n"); out.append("}"); } @@ -1179,7 +1179,7 @@ bool ListEntitiesCoverResponse::decode_length(uint32_t field_id, ProtoLengthDeli return true; } case 13: { - this->device_name = value.as_string(); + this->device_id = value.as_string(); return true; } default: @@ -1209,7 +1209,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(10, this->icon); buffer.encode_enum(11, this->entity_category); buffer.encode_bool(12, this->supports_stop); - buffer.encode_string(13, this->device_name); + buffer.encode_string(13, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCoverResponse::dump_to(std::string &out) const { @@ -1264,8 +1264,8 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { out.append(YESNO(this->supports_stop)); out.append("\n"); - out.append(" device_name: "); - out.append("'").append(this->device_name).append("'"); + out.append(" device_id: "); + out.append("'").append(this->device_id).append("'"); out.append("\n"); out.append("}"); } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 247ec0d65a..fc1b71e8ee 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -402,7 +402,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage { bool disabled_by_default{false}; std::string icon{}; enums::EntityCategory entity_category{}; - std::string device_name{}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -441,7 +441,7 @@ class ListEntitiesCoverResponse : public ProtoMessage { std::string icon{}; enums::EntityCategory entity_category{}; bool supports_stop{false}; - std::string device_name{}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/device/__init__.py b/esphome/components/devices/__init__.py similarity index 71% rename from esphome/components/device/__init__.py rename to esphome/components/devices/__init__.py index c7ecbb31f1..c8249f6f91 100644 --- a/esphome/components/device/__init__.py +++ b/esphome/components/devices/__init__.py @@ -1,9 +1,8 @@ -from esphome import config_validation as cv -from esphome import codegen as cg +from esphome import codegen as cg, config_validation as cv from esphome.const import CONF_ID, CONF_NAME -# ns = cg.esphome_ns.namespace("device") -# DeviceClass = ns.Class("Device") +# ns = cg.esphome_ns.namespace("devices") +# DeviceClass = ns.Class("SubDevice") StringRef = cg.esphome_ns.struct("StringRef") MULTI_CONF = True @@ -24,4 +23,4 @@ async def to_code(config): config[CONF_ID], config[CONF_NAME], ) - # cg.add_define("USE_DEVICE_ID") + cg.add_define("USE_SUB_DEVICE") diff --git a/esphome/components/device/device.h b/esphome/components/devices/devices.h similarity index 62% rename from esphome/components/device/device.h rename to esphome/components/devices/devices.h index 49a7b88704..80d7d9923c 100644 --- a/esphome/components/device/device.h +++ b/esphome/components/devices/devices.h @@ -1,16 +1,18 @@ #pragma once namespace esphome { -namespace device { +namespace devices { -class Device { +class SubDevice { public: void set_name(std::string name) { name_ = name; } std::string get_name(void) { return name_; } protected: + // std::string id_ = ""; std::string name_ = ""; + std::string suggested_area_ = ""; }; -} // namespace device +} // namespace devices } // namespace esphome diff --git a/esphome/core/application.h b/esphome/core/application.h index 462beb1f25..4336ea43d5 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -9,6 +9,9 @@ #include "esphome/core/preferences.h" #include "esphome/core/scheduler.h" +#ifdef USE_SUB_DEVICE +#include "esphome/components/devices/devices.h" +#endif #ifdef USE_BINARY_SENSOR #include "esphome/components/binary_sensor/binary_sensor.h" #endif @@ -97,6 +100,10 @@ class Application { this->compilation_time_ = compilation_time; } +#ifdef USE_SUB_DEVICE + void register_sub_device(devices::SubDevice *sub_device) { this->sub_devices_.push_back(sub_device); } +#endif + #ifdef USE_BINARY_SENSOR void register_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { this->binary_sensors_.push_back(binary_sensor); @@ -243,6 +250,16 @@ class Application { uint32_t get_app_state() const { return this->app_state_; } +#ifdef USE_SUB_DEVICE + const std::vector &get_sub_devices() { return this->sub_devices_; } + // devices::SubDevice *get_sub_device_by_key(uint32_t key, bool include_internal = false) { + // for (auto *obj : this->sub_devices_) { + // if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) + // return obj; + // } + // return nullptr; + // } +#endif #ifdef USE_BINARY_SENSOR const std::vector &get_binary_sensors() { return this->binary_sensors_; } binary_sensor::BinarySensor *get_binary_sensor_by_key(uint32_t key, bool include_internal = false) { @@ -473,6 +490,9 @@ class Application { std::vector components_{}; std::vector looping_components_{}; +#ifdef USE_SUB_DEVICE + std::vector sub_devices_{}; +#endif #ifdef USE_BINARY_SENSOR std::vector binary_sensors_{}; #endif diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 64de41f23a..464ee800d4 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -99,6 +99,7 @@ #define USE_SELECT #define USE_SENSOR #define USE_STATUS_LED +#define USE_SUB_DEVICE #define USE_SWITCH #define USE_TEXT #define USE_TEXT_SENSOR diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index 15864e793c..a08cab622a 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -36,13 +36,13 @@ std::string EntityBase::get_icon() const { void EntityBase::set_icon(const char *icon) { this->icon_c_str_ = icon; } // Entity Device Name -StringRef EntityBase::get_device_name() const { - if (this->device_name_.empty()) { +StringRef EntityBase::get_device_id() const { + if (this->device_id_.empty()) { return StringRef(""); } - return this->device_name_; + return this->device_id_; } -void EntityBase::set_device_name(const StringRef *device_name) { this->device_name_ = *device_name; } +void EntityBase::set_device_id(const StringRef *device_id) { this->device_id_ = *device_id; } // Entity Category EntityCategory EntityBase::get_entity_category() const { return this->entity_category_; } diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 0f6b222efd..6975c524f6 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -48,8 +48,8 @@ class EntityBase { void set_icon(const char *icon); // Get/set this entity's device name - StringRef get_device_name() const; - void set_device_name(const StringRef *device_name); + StringRef get_device_id() const; + void set_device_id(const StringRef *device_id); protected: /// The hash_base() function has been deprecated. It is kept in this @@ -65,7 +65,7 @@ class EntityBase { bool internal_{false}; bool disabled_by_default_{false}; EntityCategory entity_category_{ENTITY_CATEGORY_NONE}; - StringRef device_name_; + StringRef device_id_; }; class EntityBase_DeviceClass { // NOLINT(readability-identifier-naming) diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index afd951b504..df191bafe2 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -89,6 +89,10 @@ async def register_component(var, config): return var +# async def register_sub_device(var, value): +# pass + + async def register_parented(var, value): if isinstance(value, ID): paren = await get_variable(value) @@ -113,7 +117,7 @@ async def setup_entity(var, config): add(var.set_entity_category(config[CONF_ENTITY_CATEGORY])) if CONF_DEVICE_ID in config: parent = await get_variable(config[CONF_DEVICE_ID]) - add(var.set_device_name(parent)) + add(var.set_device_id(parent)) def extract_registry_entry_config( diff --git a/tests/components/device/common.yaml b/tests/components/device/common.yaml index 0f24038167..232bb631c9 100644 --- a/tests/components/device/common.yaml +++ b/tests/components/device/common.yaml @@ -1,4 +1,4 @@ -device: +devices: - id: other_device name: Another device From e79e244eee0ae9ff454dc5ffec48fbe813682907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Wed, 9 Apr 2025 00:43:40 +0200 Subject: [PATCH 08/99] Fix generated proto-files --- .github/workflows/ci-api-proto.yml | 2 ++ esphome/components/api/api_pb2.cpp | 54 ++++++++++++++++++++++++++++++ esphome/components/api/api_pb2.h | 14 ++++++++ 3 files changed, 70 insertions(+) diff --git a/.github/workflows/ci-api-proto.yml b/.github/workflows/ci-api-proto.yml index 233fb64693..a57ea17eb4 100644 --- a/.github/workflows/ci-api-proto.yml +++ b/.github/workflows/ci-api-proto.yml @@ -37,6 +37,8 @@ jobs: run: pip install aioesphomeapi -c requirements.txt -r requirements_dev.txt - name: Generate files run: script/api_protobuf/api_protobuf.py + - name: Show changes + run: git diff - name: Check for changes run: | if ! git diff --quiet; then diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 61a53e4a0c..6f7fcf3604 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -762,6 +762,47 @@ void DeviceInfoRequest::encode(ProtoWriteBuffer buffer) const {} #ifdef HAS_PROTO_MESSAGE_DUMP void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfoRequest {}"); } #endif +bool SubDeviceInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 1: { + this->id = value.as_string(); + return true; + } + case 2: { + this->name = value.as_string(); + return true; + } + case 3: { + this->suggested_area = value.as_string(); + return true; + } + default: + return false; + } +} +void SubDeviceInfo::encode(ProtoWriteBuffer buffer) const { + buffer.encode_string(1, this->id); + buffer.encode_string(2, this->name); + buffer.encode_string(3, this->suggested_area); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void SubDeviceInfo::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("SubDeviceInfo {\n"); + out.append(" id: "); + out.append("'").append(this->id).append("'"); + out.append("\n"); + + out.append(" name: "); + out.append("'").append(this->name).append("'"); + out.append("\n"); + + out.append(" suggested_area: "); + out.append("'").append(this->suggested_area).append("'"); + out.append("\n"); + out.append("}"); +} +#endif bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { @@ -842,6 +883,10 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v this->bluetooth_mac_address = value.as_string(); return true; } + case 19: { + this->sub_devices.push_back(value.as_message()); + return true; + } default: return false; } @@ -865,6 +910,9 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(17, this->voice_assistant_feature_flags); buffer.encode_string(16, this->suggested_area); buffer.encode_string(18, this->bluetooth_mac_address); + for (auto &it : this->sub_devices) { + buffer.encode_message(19, it, true); + } } #ifdef HAS_PROTO_MESSAGE_DUMP void DeviceInfoResponse::dump_to(std::string &out) const { @@ -946,6 +994,12 @@ void DeviceInfoResponse::dump_to(std::string &out) const { out.append(" bluetooth_mac_address: "); out.append("'").append(this->bluetooth_mac_address).append("'"); out.append("\n"); + + for (const auto &it : this->sub_devices) { + out.append(" sub_devices: "); + it.dump_to(out); + out.append("\n"); + } out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index fc1b71e8ee..913e375cbf 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -335,6 +335,19 @@ class DeviceInfoRequest : public ProtoMessage { protected: }; +class SubDeviceInfo : public ProtoMessage { + public: + std::string id{}; + std::string name{}; + std::string suggested_area{}; + void encode(ProtoWriteBuffer buffer) 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; +}; class DeviceInfoResponse : public ProtoMessage { public: bool uses_password{false}; @@ -355,6 +368,7 @@ class DeviceInfoResponse : public ProtoMessage { uint32_t voice_assistant_feature_flags{0}; std::string suggested_area{}; std::string bluetooth_mac_address{}; + std::vector sub_devices{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; From c1fd597757bacc127b1c614995035bc4a838b785 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Wed, 9 Apr 2025 01:12:14 +0200 Subject: [PATCH 09/99] Add CODEOWNER --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/CODEOWNERS b/CODEOWNERS index f6f7ac6f9c..2fdf6cc155 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -116,6 +116,7 @@ esphome/components/dashboard_import/* @esphome/core esphome/components/datetime/* @jesserockz @rfdarter esphome/components/debug/* @OttoWinter esphome/components/delonghi/* @grob6000 +esphome/components/devices/* @dala318 esphome/components/dfplayer/* @glmnet esphome/components/dfrobot_sen0395/* @niklasweber esphome/components/dht/* @OttoWinter From 01ac59ce2afc1f694aaa858a1c2b5868c53f2806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Wed, 9 Apr 2025 01:15:48 +0200 Subject: [PATCH 10/99] Store proto with all additions but commented out --- esphome/components/api/api.proto | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 10f5aace5e..9087ff18e2 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -387,6 +387,7 @@ message ListEntitiesFanResponse { string icon = 10; EntityCategory entity_category = 11; repeated string supported_preset_modes = 12; + // string device_id = 13; } enum FanSpeed { FAN_SPEED_LOW = 0; @@ -467,6 +468,7 @@ message ListEntitiesLightResponse { bool disabled_by_default = 13; string icon = 14; EntityCategory entity_category = 15; + // string device_id = 16; } message LightStateResponse { option (id) = 24; @@ -557,6 +559,7 @@ message ListEntitiesSensorResponse { SensorLastResetType legacy_last_reset_type = 11; bool disabled_by_default = 12; EntityCategory entity_category = 13; + // string device_id = 14; } message SensorStateResponse { option (id) = 25; @@ -587,6 +590,7 @@ message ListEntitiesSwitchResponse { bool disabled_by_default = 7; EntityCategory entity_category = 8; string device_class = 9; + // string device_id = 10; } message SwitchStateResponse { option (id) = 26; @@ -622,6 +626,7 @@ message ListEntitiesTextSensorResponse { bool disabled_by_default = 6; EntityCategory entity_category = 7; string device_class = 8; + // string device_id = 9; } message TextSensorStateResponse { option (id) = 27; @@ -785,6 +790,7 @@ message ListEntitiesCameraResponse { bool disabled_by_default = 5; string icon = 6; EntityCategory entity_category = 7; + // string device_id = 8; } message CameraImageResponse { @@ -886,6 +892,7 @@ message ListEntitiesClimateResponse { bool supports_target_humidity = 23; float visual_min_humidity = 24; float visual_max_humidity = 25; + // string device_id = 26; } message ClimateStateResponse { option (id) = 47; @@ -965,6 +972,7 @@ message ListEntitiesNumberResponse { string unit_of_measurement = 11; NumberMode mode = 12; string device_class = 13; + // string device_id = 14; } message NumberStateResponse { option (id) = 50; @@ -1003,6 +1011,7 @@ message ListEntitiesSelectResponse { repeated string options = 6; bool disabled_by_default = 7; EntityCategory entity_category = 8; + // string device_id = 9; } message SelectStateResponse { option (id) = 53; @@ -1061,6 +1070,7 @@ message ListEntitiesLockResponse { // Not yet implemented: string code_format = 11; + // string device_id = 12; } message LockStateResponse { option (id) = 59; @@ -1098,6 +1108,7 @@ message ListEntitiesButtonResponse { bool disabled_by_default = 6; EntityCategory entity_category = 7; string device_class = 8; + // string device_id = 9; } message ButtonCommandRequest { option (id) = 62; @@ -1152,6 +1163,8 @@ message ListEntitiesMediaPlayerResponse { bool supports_pause = 8; repeated MediaPlayerSupportedFormat supported_formats = 9; + + // string device_id = 10; } message MediaPlayerStateResponse { option (id) = 64; @@ -1658,6 +1671,7 @@ message ListEntitiesAlarmControlPanelResponse { uint32 supported_features = 8; bool requires_code = 9; bool requires_code_to_arm = 10; + // string device_id = 11; } message AlarmControlPanelStateResponse { @@ -1701,6 +1715,7 @@ message ListEntitiesTextResponse { uint32 max_length = 9; string pattern = 10; TextMode mode = 11; + // string device_id = 12; } message TextStateResponse { option (id) = 98; @@ -1739,6 +1754,7 @@ message ListEntitiesDateResponse { string icon = 5; bool disabled_by_default = 6; EntityCategory entity_category = 7; + // string device_id = 8; } message DateStateResponse { option (id) = 101; @@ -1780,6 +1796,7 @@ message ListEntitiesTimeResponse { string icon = 5; bool disabled_by_default = 6; EntityCategory entity_category = 7; + // string device_id = 8; } message TimeStateResponse { option (id) = 104; @@ -1824,6 +1841,7 @@ message ListEntitiesEventResponse { string device_class = 8; repeated string event_types = 9; + // string device_id = 10; } message EventResponse { option (id) = 108; @@ -1853,6 +1871,7 @@ message ListEntitiesValveResponse { bool assumed_state = 9; bool supports_position = 10; bool supports_stop = 11; + // string device_id = 12; } enum ValveOperation { @@ -1897,6 +1916,7 @@ message ListEntitiesDateTimeResponse { string icon = 5; bool disabled_by_default = 6; EntityCategory entity_category = 7; + // string device_id = 8; } message DateTimeStateResponse { option (id) = 113; @@ -1935,6 +1955,7 @@ message ListEntitiesUpdateResponse { bool disabled_by_default = 6; EntityCategory entity_category = 7; string device_class = 8; + // string device_id = 9; } message UpdateStateResponse { option (id) = 117; From 0651f7cb3ca479b3965efa1e3c0a46b6a0382605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Wed, 9 Apr 2025 01:39:24 +0200 Subject: [PATCH 11/99] Work on sub-device creation --- esphome/components/devices/__init__.py | 29 ++++++++++++++++++-------- esphome/components/devices/devices.h | 4 +++- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/esphome/components/devices/__init__.py b/esphome/components/devices/__init__.py index c8249f6f91..b38c051259 100644 --- a/esphome/components/devices/__init__.py +++ b/esphome/components/devices/__init__.py @@ -1,8 +1,8 @@ from esphome import codegen as cg, config_validation as cv -from esphome.const import CONF_ID, CONF_NAME +from esphome.const import CONF_AREA, CONF_ID, CONF_NAME -# ns = cg.esphome_ns.namespace("devices") -# DeviceClass = ns.Class("SubDevice") +ns = cg.esphome_ns.namespace("devices") +DeviceClass = ns.Class("SubDevice") StringRef = cg.esphome_ns.struct("StringRef") MULTI_CONF = True @@ -11,16 +11,27 @@ CODEOWNERS = ["@dala318"] CONFIG_SCHEMA = cv.Schema( { - # cv.Required(CONF_ID): cv.declare_id(DeviceClass), - cv.Required(CONF_ID): cv.declare_id(StringRef), + cv.GenerateID(CONF_ID): cv.declare_id(DeviceClass), + # cv.Required(CONF_NAME): cv.declare_id(StringRef), + # cv.Optional(CONF_AREA, ""): cv.declare_id(StringRef), cv.Required(CONF_NAME): cv.string, + cv.Optional(CONF_AREA, ""): cv.string, } ).extend(cv.COMPONENT_SCHEMA) async def to_code(config): - cg.new_Pvariable( - config[CONF_ID], - config[CONF_NAME], - ) + dev = cg.new_Pvariable(config[CONF_ID]) + cg.add(dev.set_name(config[CONF_NAME])) + if CONF_AREA in config: + cg.add(dev.set_area(config[CONF_AREA])) + cg.add(cg.App.register_sub_device(dev)) + # cg.add( + # cg.App.register_sub_device( + # config[CONF_ID], + # config[CONF_NAME], + # config[CONF_AREA], + # # config.get(CONF_COMMENT, ""), + # ) + # ) cg.add_define("USE_SUB_DEVICE") diff --git a/esphome/components/devices/devices.h b/esphome/components/devices/devices.h index 80d7d9923c..a9e8f311fa 100644 --- a/esphome/components/devices/devices.h +++ b/esphome/components/devices/devices.h @@ -7,11 +7,13 @@ class SubDevice { public: void set_name(std::string name) { name_ = name; } std::string get_name(void) { return name_; } + void set_area(std::string area) { area_ = area; } + std::string get_area(void) { return area_; } protected: // std::string id_ = ""; std::string name_ = ""; - std::string suggested_area_ = ""; + std::string area_ = ""; }; } // namespace devices From 2c01bc5795c19adbe01006f23fcb12c482e09293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Wed, 9 Apr 2025 15:22:40 +0200 Subject: [PATCH 12/99] Fix clang-tidy --- esphome/components/devices/devices.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/devices/devices.h b/esphome/components/devices/devices.h index a9e8f311fa..96e3b84887 100644 --- a/esphome/components/devices/devices.h +++ b/esphome/components/devices/devices.h @@ -5,10 +5,10 @@ namespace devices { class SubDevice { public: - void set_name(std::string name) { name_ = name; } - std::string get_name(void) { return name_; } - void set_area(std::string area) { area_ = area; } - std::string get_area(void) { return area_; } + void set_name(std::string name) { name_ = std::move(name); } + std::string get_name() { return name_; } + void set_area(std::string area) { area_ = std::move(area); } + std::string get_area() { return area_; } protected: // std::string id_ = ""; From 962e0c4c336be1869a6d5960074fadb5122e59c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Wed, 9 Apr 2025 19:09:31 +0200 Subject: [PATCH 13/99] Make it a Class but only use the id in entities --- esphome/components/devices/__init__.py | 23 ++++++----------------- esphome/components/devices/devices.h | 6 +++++- esphome/config_validation.py | 10 +++++----- esphome/core/entity_base.cpp | 4 ++-- esphome/core/entity_base.h | 6 +++--- esphome/cpp_helpers.py | 2 +- 6 files changed, 22 insertions(+), 29 deletions(-) diff --git a/esphome/components/devices/__init__.py b/esphome/components/devices/__init__.py index b38c051259..5a70be82a7 100644 --- a/esphome/components/devices/__init__.py +++ b/esphome/components/devices/__init__.py @@ -1,9 +1,8 @@ from esphome import codegen as cg, config_validation as cv from esphome.const import CONF_AREA, CONF_ID, CONF_NAME -ns = cg.esphome_ns.namespace("devices") -DeviceClass = ns.Class("SubDevice") -StringRef = cg.esphome_ns.struct("StringRef") +devices_ns = cg.esphome_ns.namespace("devices") +SubDevice = devices_ns.class_("SubDevice") MULTI_CONF = True @@ -11,27 +10,17 @@ CODEOWNERS = ["@dala318"] CONFIG_SCHEMA = cv.Schema( { - cv.GenerateID(CONF_ID): cv.declare_id(DeviceClass), - # cv.Required(CONF_NAME): cv.declare_id(StringRef), - # cv.Optional(CONF_AREA, ""): cv.declare_id(StringRef), + cv.GenerateID(CONF_ID): cv.declare_id(SubDevice), cv.Required(CONF_NAME): cv.string, - cv.Optional(CONF_AREA, ""): cv.string, + cv.Optional(CONF_AREA, default=""): cv.string, } ).extend(cv.COMPONENT_SCHEMA) async def to_code(config): dev = cg.new_Pvariable(config[CONF_ID]) + cg.add(dev.set_id(str(config[CONF_ID]))) cg.add(dev.set_name(config[CONF_NAME])) - if CONF_AREA in config: - cg.add(dev.set_area(config[CONF_AREA])) + cg.add(dev.set_area(config[CONF_AREA])) cg.add(cg.App.register_sub_device(dev)) - # cg.add( - # cg.App.register_sub_device( - # config[CONF_ID], - # config[CONF_NAME], - # config[CONF_AREA], - # # config.get(CONF_COMMENT, ""), - # ) - # ) cg.add_define("USE_SUB_DEVICE") diff --git a/esphome/components/devices/devices.h b/esphome/components/devices/devices.h index 96e3b84887..d8bd0d70a3 100644 --- a/esphome/components/devices/devices.h +++ b/esphome/components/devices/devices.h @@ -1,17 +1,21 @@ #pragma once +#include "esphome/core/string_ref.h" + namespace esphome { namespace devices { class SubDevice { public: + void set_id(std::string id) { id_ = std::move(id); } + std::string get_id() { return id_; } void set_name(std::string name) { name_ = std::move(name); } std::string get_name() { return name_; } void set_area(std::string area) { area_ = std::move(area); } std::string get_area() { return area_; } protected: - // std::string id_ = ""; + std::string id_ = ""; std::string name_ = ""; std::string area_ = ""; }; diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 14a64d2277..f883b6fed9 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -349,9 +349,10 @@ def icon(value): ) -def device_id(value): - StringRef = cg.esphome_ns.struct("StringRef") - validator = use_id(StringRef) +def sub_device_id(value): + devices_ns = cg.esphome_ns.namespace("devices") + SubDevice = devices_ns.class_("SubDevice") + validator = use_id(SubDevice) return validator(value) @@ -1874,8 +1875,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): use_id(StringRef), - Optional(CONF_DEVICE_ID): device_id, + Optional(CONF_DEVICE_ID): sub_device_id, } ) diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index a08cab622a..8073879982 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -36,13 +36,13 @@ std::string EntityBase::get_icon() const { void EntityBase::set_icon(const char *icon) { this->icon_c_str_ = icon; } // Entity Device Name -StringRef EntityBase::get_device_id() const { +const StringRef &EntityBase::get_device_id() const { if (this->device_id_.empty()) { return StringRef(""); } return this->device_id_; } -void EntityBase::set_device_id(const StringRef *device_id) { this->device_id_ = *device_id; } +void EntityBase::set_device_id(const std::string device_id) { this->device_id_ = StringRef(device_id); } // Entity Category EntityCategory EntityBase::get_entity_category() const { return this->entity_category_; } diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 6975c524f6..e52406c425 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -47,9 +47,9 @@ class EntityBase { std::string get_icon() const; void set_icon(const char *icon); - // Get/set this entity's device name - StringRef get_device_id() const; - void set_device_id(const StringRef *device_id); + // Get/set this entity's device id + const StringRef &get_device_id() const; + void set_device_id(const std::string device_id); protected: /// The hash_base() function has been deprecated. It is kept in this diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index df191bafe2..bfc9b3dc9b 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -117,7 +117,7 @@ async def setup_entity(var, config): add(var.set_entity_category(config[CONF_ENTITY_CATEGORY])) if CONF_DEVICE_ID in config: parent = await get_variable(config[CONF_DEVICE_ID]) - add(var.set_device_id(parent)) + add(var.set_device_id(parent.get_id())) def extract_registry_entry_config( From 32f4e4ca130188895cb1eafdd6b17c5f05937515 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Wed, 9 Apr 2025 19:20:28 +0200 Subject: [PATCH 14/99] Cleaning up --- esphome/core/application.h | 2 ++ esphome/core/entity_base.cpp | 2 +- esphome/cpp_helpers.py | 4 ---- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/esphome/core/application.h b/esphome/core/application.h index 4336ea43d5..2691f760a3 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -252,6 +252,8 @@ class Application { #ifdef USE_SUB_DEVICE const std::vector &get_sub_devices() { return this->sub_devices_; } + // /* Very likely no need for get_sub_device_by_key as it only seem to be used when requesting update from API + // and the sub_devices shaould only be sent once at connection. */ // devices::SubDevice *get_sub_device_by_key(uint32_t key, bool include_internal = false) { // for (auto *obj : this->sub_devices_) { // if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index 8073879982..e5231ed759 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -35,7 +35,7 @@ std::string EntityBase::get_icon() const { } void EntityBase::set_icon(const char *icon) { this->icon_c_str_ = icon; } -// Entity Device Name +// Entity Device id const StringRef &EntityBase::get_device_id() const { if (this->device_id_.empty()) { return StringRef(""); diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index bfc9b3dc9b..3c91eafcf4 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -89,10 +89,6 @@ async def register_component(var, config): return var -# async def register_sub_device(var, value): -# pass - - async def register_parented(var, value): if isinstance(value, ID): paren = await get_variable(value) From f5f1651b31f5407172143351ae9e73af5478b2c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Wed, 9 Apr 2025 22:33:03 +0200 Subject: [PATCH 15/99] Fix clang --- esphome/core/entity_base.cpp | 9 --------- esphome/core/entity_base.h | 6 +++--- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index e5231ed759..725a8569a3 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -35,15 +35,6 @@ std::string EntityBase::get_icon() const { } void EntityBase::set_icon(const char *icon) { this->icon_c_str_ = icon; } -// Entity Device id -const StringRef &EntityBase::get_device_id() const { - if (this->device_id_.empty()) { - return StringRef(""); - } - return this->device_id_; -} -void EntityBase::set_device_id(const std::string device_id) { this->device_id_ = StringRef(device_id); } - // Entity Category EntityCategory EntityBase::get_entity_category() const { return this->entity_category_; } void EntityBase::set_entity_category(EntityCategory entity_category) { this->entity_category_ = entity_category; } diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index e52406c425..e66fbb66e6 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -48,8 +48,8 @@ class EntityBase { void set_icon(const char *icon); // Get/set this entity's device id - const StringRef &get_device_id() const; - void set_device_id(const std::string device_id); + const StringRef &get_device_id() const { return this->device_id_; } + void set_device_id(const std::string &device_id) { this->device_id_ = StringRef(device_id); } protected: /// The hash_base() function has been deprecated. It is kept in this @@ -65,7 +65,7 @@ class EntityBase { bool internal_{false}; bool disabled_by_default_{false}; EntityCategory entity_category_{ENTITY_CATEGORY_NONE}; - StringRef device_id_; + StringRef device_id_{""}; }; class EntityBase_DeviceClass { // NOLINT(readability-identifier-naming) From 3922950951191ed3052964b000dac2651595c419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Mon, 14 Apr 2025 21:36:50 +0200 Subject: [PATCH 16/99] Improve stability for unrelated test --- tests/dashboard/test_web_server.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/dashboard/test_web_server.py b/tests/dashboard/test_web_server.py index a61850abf3..13d2bbbf33 100644 --- a/tests/dashboard/test_web_server.py +++ b/tests/dashboard/test_web_server.py @@ -75,6 +75,9 @@ async def test_devices_page(dashboard: DashboardTestHelper) -> None: assert response.headers["content-type"] == "application/json" json_data = json.loads(response.body.decode()) configured_devices = json_data["configured"] - first_device = configured_devices[0] - assert first_device["name"] == "pico" - assert first_device["configuration"] == "pico.yaml" + if len(configured_devices) == 0: + assert len(configured_devices) != 0 + else: + first_device = configured_devices[0] + assert first_device["name"] == "pico" + assert first_device["configuration"] == "pico.yaml" From 825c0593e1001f95f5bd30411ec0b6a0b675e378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Sat, 19 Apr 2025 19:07:50 +0200 Subject: [PATCH 17/99] Fix generated code after merge --- esphome/components/api/api_pb2.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index cf1d61ab39..d3b16f7d2b 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -887,7 +887,7 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v this->bluetooth_mac_address = value.as_string(); return true; } - case 19: { + case 20: { this->sub_devices.push_back(value.as_message()); return true; } @@ -1009,7 +1009,6 @@ void DeviceInfoResponse::dump_to(std::string &out) const { it.dump_to(out); out.append("\n"); } - out.append("}"); } #endif From 298cc58433d07ecfa5aa2aef341392cf137c0ef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Sat, 19 Apr 2025 22:48:20 +0200 Subject: [PATCH 18/99] Activate the rest of entities --- esphome/components/api/api.proto | 40 +++---- esphome/components/api/api_pb2.cpp | 180 +++++++++++++++++++++++++++++ esphome/components/api/api_pb2.h | 20 ++++ 3 files changed, 220 insertions(+), 20 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 43a54fb4c2..e9958225e7 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -391,7 +391,7 @@ message ListEntitiesFanResponse { string icon = 10; EntityCategory entity_category = 11; repeated string supported_preset_modes = 12; - // string device_id = 13; + string device_id = 13; } enum FanSpeed { FAN_SPEED_LOW = 0; @@ -472,7 +472,7 @@ message ListEntitiesLightResponse { bool disabled_by_default = 13; string icon = 14; EntityCategory entity_category = 15; - // string device_id = 16; + string device_id = 16; } message LightStateResponse { option (id) = 24; @@ -563,7 +563,7 @@ message ListEntitiesSensorResponse { SensorLastResetType legacy_last_reset_type = 11; bool disabled_by_default = 12; EntityCategory entity_category = 13; - // string device_id = 14; + string device_id = 14; } message SensorStateResponse { option (id) = 25; @@ -594,7 +594,7 @@ message ListEntitiesSwitchResponse { bool disabled_by_default = 7; EntityCategory entity_category = 8; string device_class = 9; - // string device_id = 10; + string device_id = 10; } message SwitchStateResponse { option (id) = 26; @@ -630,7 +630,7 @@ message ListEntitiesTextSensorResponse { bool disabled_by_default = 6; EntityCategory entity_category = 7; string device_class = 8; - // string device_id = 9; + string device_id = 9; } message TextSensorStateResponse { option (id) = 27; @@ -811,7 +811,7 @@ message ListEntitiesCameraResponse { bool disabled_by_default = 5; string icon = 6; EntityCategory entity_category = 7; - // string device_id = 8; + string device_id = 8; } message CameraImageResponse { @@ -913,7 +913,7 @@ message ListEntitiesClimateResponse { bool supports_target_humidity = 23; float visual_min_humidity = 24; float visual_max_humidity = 25; - // string device_id = 26; + string device_id = 26; } message ClimateStateResponse { option (id) = 47; @@ -993,7 +993,7 @@ message ListEntitiesNumberResponse { string unit_of_measurement = 11; NumberMode mode = 12; string device_class = 13; - // string device_id = 14; + string device_id = 14; } message NumberStateResponse { option (id) = 50; @@ -1032,7 +1032,7 @@ message ListEntitiesSelectResponse { repeated string options = 6; bool disabled_by_default = 7; EntityCategory entity_category = 8; - // string device_id = 9; + string device_id = 9; } message SelectStateResponse { option (id) = 53; @@ -1091,7 +1091,7 @@ message ListEntitiesLockResponse { // Not yet implemented: string code_format = 11; - // string device_id = 12; + string device_id = 12; } message LockStateResponse { option (id) = 59; @@ -1129,7 +1129,7 @@ message ListEntitiesButtonResponse { bool disabled_by_default = 6; EntityCategory entity_category = 7; string device_class = 8; - // string device_id = 9; + string device_id = 9; } message ButtonCommandRequest { option (id) = 62; @@ -1185,7 +1185,7 @@ message ListEntitiesMediaPlayerResponse { repeated MediaPlayerSupportedFormat supported_formats = 9; - // string device_id = 10; + string device_id = 10; } message MediaPlayerStateResponse { option (id) = 64; @@ -1692,7 +1692,7 @@ message ListEntitiesAlarmControlPanelResponse { uint32 supported_features = 8; bool requires_code = 9; bool requires_code_to_arm = 10; - // string device_id = 11; + string device_id = 11; } message AlarmControlPanelStateResponse { @@ -1736,7 +1736,7 @@ message ListEntitiesTextResponse { uint32 max_length = 9; string pattern = 10; TextMode mode = 11; - // string device_id = 12; + string device_id = 12; } message TextStateResponse { option (id) = 98; @@ -1775,7 +1775,7 @@ message ListEntitiesDateResponse { string icon = 5; bool disabled_by_default = 6; EntityCategory entity_category = 7; - // string device_id = 8; + string device_id = 8; } message DateStateResponse { option (id) = 101; @@ -1817,7 +1817,7 @@ message ListEntitiesTimeResponse { string icon = 5; bool disabled_by_default = 6; EntityCategory entity_category = 7; - // string device_id = 8; + string device_id = 8; } message TimeStateResponse { option (id) = 104; @@ -1862,7 +1862,7 @@ message ListEntitiesEventResponse { string device_class = 8; repeated string event_types = 9; - // string device_id = 10; + string device_id = 10; } message EventResponse { option (id) = 108; @@ -1892,7 +1892,7 @@ message ListEntitiesValveResponse { bool assumed_state = 9; bool supports_position = 10; bool supports_stop = 11; - // string device_id = 12; + string device_id = 12; } enum ValveOperation { @@ -1937,7 +1937,7 @@ message ListEntitiesDateTimeResponse { string icon = 5; bool disabled_by_default = 6; EntityCategory entity_category = 7; - // string device_id = 8; + string device_id = 8; } message DateTimeStateResponse { option (id) = 113; @@ -1976,7 +1976,7 @@ message ListEntitiesUpdateResponse { bool disabled_by_default = 6; EntityCategory entity_category = 7; string device_class = 8; - // string device_id = 9; + string device_id = 9; } message UpdateStateResponse { option (id) = 117; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index d3b16f7d2b..e1f17ccee4 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1548,6 +1548,10 @@ bool ListEntitiesFanResponse::decode_length(uint32_t field_id, ProtoLengthDelimi this->supported_preset_modes.push_back(value.as_string()); return true; } + case 13: { + this->device_id = value.as_string(); + return true; + } default: return false; } @@ -1577,6 +1581,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->supported_preset_modes) { buffer.encode_string(12, it, true); } + buffer.encode_string(13, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesFanResponse::dump_to(std::string &out) const { @@ -1633,6 +1638,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { out.append("'").append(it).append("'"); out.append("\n"); } + + out.append(" device_id: "); + out.append("'").append(this->device_id).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -1928,6 +1937,10 @@ bool ListEntitiesLightResponse::decode_length(uint32_t field_id, ProtoLengthDeli this->icon = value.as_string(); return true; } + case 16: { + this->device_id = value.as_string(); + return true; + } default: return false; } @@ -1970,6 +1983,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(13, this->disabled_by_default); buffer.encode_string(14, this->icon); buffer.encode_enum(15, this->entity_category); + buffer.encode_string(16, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesLightResponse::dump_to(std::string &out) const { @@ -2041,6 +2055,10 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { out.append(" entity_category: "); out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); + + out.append(" device_id: "); + out.append("'").append(this->device_id).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -2534,6 +2552,10 @@ bool ListEntitiesSensorResponse::decode_length(uint32_t field_id, ProtoLengthDel this->device_class = value.as_string(); return true; } + case 14: { + this->device_id = value.as_string(); + return true; + } default: return false; } @@ -2562,6 +2584,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_enum(11, this->legacy_last_reset_type); buffer.encode_bool(12, this->disabled_by_default); buffer.encode_enum(13, this->entity_category); + buffer.encode_string(14, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSensorResponse::dump_to(std::string &out) const { @@ -2620,6 +2643,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { out.append(" entity_category: "); out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); + + out.append(" device_id: "); + out.append("'").append(this->device_id).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -2712,6 +2739,10 @@ bool ListEntitiesSwitchResponse::decode_length(uint32_t field_id, ProtoLengthDel this->device_class = value.as_string(); return true; } + case 10: { + this->device_id = value.as_string(); + return true; + } default: return false; } @@ -2736,6 +2767,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(7, this->disabled_by_default); buffer.encode_enum(8, this->entity_category); buffer.encode_string(9, this->device_class); + buffer.encode_string(10, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSwitchResponse::dump_to(std::string &out) const { @@ -2777,6 +2809,10 @@ 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: "); + out.append("'").append(this->device_id).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -2894,6 +2930,10 @@ bool ListEntitiesTextSensorResponse::decode_length(uint32_t field_id, ProtoLengt this->device_class = value.as_string(); return true; } + case 9: { + this->device_id = value.as_string(); + return true; + } default: return false; } @@ -2917,6 +2957,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); buffer.encode_string(8, this->device_class); + buffer.encode_string(9, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { @@ -2954,6 +2995,10 @@ 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: "); + out.append("'").append(this->device_id).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -3660,6 +3705,10 @@ bool ListEntitiesCameraResponse::decode_length(uint32_t field_id, ProtoLengthDel this->icon = value.as_string(); return true; } + case 8: { + this->device_id = value.as_string(); + return true; + } default: return false; } @@ -3682,6 +3731,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(5, this->disabled_by_default); buffer.encode_string(6, this->icon); buffer.encode_enum(7, this->entity_category); + buffer.encode_string(8, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCameraResponse::dump_to(std::string &out) const { @@ -3715,6 +3765,10 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const { out.append(" entity_category: "); out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); + + out.append(" device_id: "); + out.append("'").append(this->device_id).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -3884,6 +3938,10 @@ bool ListEntitiesClimateResponse::decode_length(uint32_t field_id, ProtoLengthDe this->icon = value.as_string(); return true; } + case 26: { + this->device_id = value.as_string(); + return true; + } default: return false; } @@ -3960,6 +4018,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_string(26, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesClimateResponse::dump_to(std::string &out) const { @@ -4083,6 +4142,10 @@ 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: "); + out.append("'").append(this->device_id).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -4536,6 +4599,10 @@ bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDel this->device_class = value.as_string(); return true; } + case 14: { + this->device_id = value.as_string(); + return true; + } default: return false; } @@ -4576,6 +4643,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(11, this->unit_of_measurement); buffer.encode_enum(12, this->mode); buffer.encode_string(13, this->device_class); + buffer.encode_string(14, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { @@ -4636,6 +4704,10 @@ 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: "); + out.append("'").append(this->device_id).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -4758,6 +4830,10 @@ bool ListEntitiesSelectResponse::decode_length(uint32_t field_id, ProtoLengthDel this->options.push_back(value.as_string()); return true; } + case 9: { + this->device_id = value.as_string(); + return true; + } default: return false; } @@ -4783,6 +4859,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { } buffer.encode_bool(7, this->disabled_by_default); buffer.encode_enum(8, this->entity_category); + buffer.encode_string(9, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSelectResponse::dump_to(std::string &out) const { @@ -4822,6 +4899,10 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const { out.append(" entity_category: "); out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); + + out.append(" device_id: "); + out.append("'").append(this->device_id).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -4966,6 +5047,10 @@ bool ListEntitiesLockResponse::decode_length(uint32_t field_id, ProtoLengthDelim this->code_format = value.as_string(); return true; } + case 12: { + this->device_id = value.as_string(); + return true; + } default: return false; } @@ -4992,6 +5077,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_string(12, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesLockResponse::dump_to(std::string &out) const { @@ -5041,6 +5127,10 @@ 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: "); + out.append("'").append(this->device_id).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -5182,6 +5272,10 @@ bool ListEntitiesButtonResponse::decode_length(uint32_t field_id, ProtoLengthDel this->device_class = value.as_string(); return true; } + case 9: { + this->device_id = value.as_string(); + return true; + } default: return false; } @@ -5205,6 +5299,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); buffer.encode_string(8, this->device_class); + buffer.encode_string(9, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesButtonResponse::dump_to(std::string &out) const { @@ -5242,6 +5337,10 @@ 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: "); + out.append("'").append(this->device_id).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -5375,6 +5474,10 @@ bool ListEntitiesMediaPlayerResponse::decode_length(uint32_t field_id, ProtoLeng this->supported_formats.push_back(value.as_message()); return true; } + case 10: { + this->device_id = value.as_string(); + return true; + } default: return false; } @@ -5401,6 +5504,7 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->supported_formats) { buffer.encode_message(9, it, true); } + buffer.encode_string(10, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { @@ -5444,6 +5548,10 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { it.dump_to(out); out.append("\n"); } + + out.append(" device_id: "); + out.append("'").append(this->device_id).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -7472,6 +7580,10 @@ bool ListEntitiesAlarmControlPanelResponse::decode_length(uint32_t field_id, Pro this->icon = value.as_string(); return true; } + case 11: { + this->device_id = value.as_string(); + return true; + } default: return false; } @@ -7497,6 +7609,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_string(11, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { @@ -7543,6 +7656,10 @@ 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: "); + out.append("'").append(this->device_id).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -7687,6 +7804,10 @@ bool ListEntitiesTextResponse::decode_length(uint32_t field_id, ProtoLengthDelim this->pattern = value.as_string(); return true; } + case 12: { + this->device_id = value.as_string(); + return true; + } default: return false; } @@ -7713,6 +7834,7 @@ void ListEntitiesTextResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(9, this->max_length); buffer.encode_string(10, this->pattern); buffer.encode_enum(11, this->mode); + buffer.encode_string(12, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTextResponse::dump_to(std::string &out) const { @@ -7764,6 +7886,10 @@ void ListEntitiesTextResponse::dump_to(std::string &out) const { out.append(" mode: "); out.append(proto_enum_to_string(this->mode)); out.append("\n"); + + out.append(" device_id: "); + out.append("'").append(this->device_id).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -7892,6 +8018,10 @@ bool ListEntitiesDateResponse::decode_length(uint32_t field_id, ProtoLengthDelim this->icon = value.as_string(); return true; } + case 8: { + this->device_id = value.as_string(); + return true; + } default: return false; } @@ -7914,6 +8044,7 @@ void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); + buffer.encode_string(8, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesDateResponse::dump_to(std::string &out) const { @@ -7947,6 +8078,10 @@ void ListEntitiesDateResponse::dump_to(std::string &out) const { out.append(" entity_category: "); out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); + + out.append(" device_id: "); + out.append("'").append(this->device_id).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -8111,6 +8246,10 @@ bool ListEntitiesTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelim this->icon = value.as_string(); return true; } + case 8: { + this->device_id = value.as_string(); + return true; + } default: return false; } @@ -8133,6 +8272,7 @@ void ListEntitiesTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); + buffer.encode_string(8, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTimeResponse::dump_to(std::string &out) const { @@ -8166,6 +8306,10 @@ void ListEntitiesTimeResponse::dump_to(std::string &out) const { out.append(" entity_category: "); out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); + + out.append(" device_id: "); + out.append("'").append(this->device_id).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -8338,6 +8482,10 @@ bool ListEntitiesEventResponse::decode_length(uint32_t field_id, ProtoLengthDeli this->event_types.push_back(value.as_string()); return true; } + case 10: { + this->device_id = value.as_string(); + return true; + } default: return false; } @@ -8364,6 +8512,7 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->event_types) { buffer.encode_string(9, it, true); } + buffer.encode_string(10, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesEventResponse::dump_to(std::string &out) const { @@ -8407,6 +8556,10 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const { out.append("'").append(it).append("'"); out.append("\n"); } + + out.append(" device_id: "); + out.append("'").append(this->device_id).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -8497,6 +8650,10 @@ bool ListEntitiesValveResponse::decode_length(uint32_t field_id, ProtoLengthDeli this->device_class = value.as_string(); return true; } + case 12: { + this->device_id = value.as_string(); + return true; + } default: return false; } @@ -8523,6 +8680,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_string(12, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesValveResponse::dump_to(std::string &out) const { @@ -8572,6 +8730,10 @@ 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: "); + out.append("'").append(this->device_id).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -8714,6 +8876,10 @@ bool ListEntitiesDateTimeResponse::decode_length(uint32_t field_id, ProtoLengthD this->icon = value.as_string(); return true; } + case 8: { + this->device_id = value.as_string(); + return true; + } default: return false; } @@ -8736,6 +8902,7 @@ void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); + buffer.encode_string(8, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesDateTimeResponse::dump_to(std::string &out) const { @@ -8769,6 +8936,10 @@ void ListEntitiesDateTimeResponse::dump_to(std::string &out) const { out.append(" entity_category: "); out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); + + out.append(" device_id: "); + out.append("'").append(this->device_id).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -8891,6 +9062,10 @@ bool ListEntitiesUpdateResponse::decode_length(uint32_t field_id, ProtoLengthDel this->device_class = value.as_string(); return true; } + case 9: { + this->device_id = value.as_string(); + return true; + } default: return false; } @@ -8914,6 +9089,7 @@ void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); buffer.encode_string(8, this->device_class); + buffer.encode_string(9, this->device_id); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesUpdateResponse::dump_to(std::string &out) const { @@ -8951,6 +9127,10 @@ 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: "); + out.append("'").append(this->device_id).append("'"); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 78594d8401..f4120843ef 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -516,6 +516,7 @@ class ListEntitiesFanResponse : public ProtoMessage { std::string icon{}; enums::EntityCategory entity_category{}; std::vector supported_preset_modes{}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -587,6 +588,7 @@ class ListEntitiesLightResponse : public ProtoMessage { bool disabled_by_default{false}; std::string icon{}; enums::EntityCategory entity_category{}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -676,6 +678,7 @@ class ListEntitiesSensorResponse : public ProtoMessage { enums::SensorLastResetType legacy_last_reset_type{}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -711,6 +714,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage { bool disabled_by_default{false}; enums::EntityCategory entity_category{}; std::string device_class{}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -757,6 +761,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage { bool disabled_by_default{false}; enums::EntityCategory entity_category{}; std::string device_class{}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -993,6 +998,7 @@ class ListEntitiesCameraResponse : public ProtoMessage { bool disabled_by_default{false}; std::string icon{}; enums::EntityCategory entity_category{}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1057,6 +1063,7 @@ class ListEntitiesClimateResponse : public ProtoMessage { bool supports_target_humidity{false}; float visual_min_humidity{0.0f}; float visual_max_humidity{0.0f}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1144,6 +1151,7 @@ class ListEntitiesNumberResponse : public ProtoMessage { std::string unit_of_measurement{}; enums::NumberMode mode{}; std::string device_class{}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1190,6 +1198,7 @@ class ListEntitiesSelectResponse : public ProtoMessage { std::vector options{}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1241,6 +1250,7 @@ class ListEntitiesLockResponse : public ProtoMessage { bool supports_open{false}; bool requires_code{false}; std::string code_format{}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1290,6 +1300,7 @@ class ListEntitiesButtonResponse : public ProtoMessage { bool disabled_by_default{false}; enums::EntityCategory entity_category{}; std::string device_class{}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1338,6 +1349,7 @@ class ListEntitiesMediaPlayerResponse : public ProtoMessage { enums::EntityCategory entity_category{}; bool supports_pause{false}; std::vector supported_formats{}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1952,6 +1964,7 @@ class ListEntitiesAlarmControlPanelResponse : public ProtoMessage { uint32_t supported_features{0}; bool requires_code{false}; bool requires_code_to_arm{false}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2003,6 +2016,7 @@ class ListEntitiesTextResponse : public ProtoMessage { uint32_t max_length{0}; std::string pattern{}; enums::TextMode mode{}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2050,6 +2064,7 @@ class ListEntitiesDateResponse : public ProtoMessage { std::string icon{}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2100,6 +2115,7 @@ class ListEntitiesTimeResponse : public ProtoMessage { std::string icon{}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2152,6 +2168,7 @@ class ListEntitiesEventResponse : public ProtoMessage { enums::EntityCategory entity_category{}; std::string device_class{}; std::vector event_types{}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2188,6 +2205,7 @@ class ListEntitiesValveResponse : public ProtoMessage { bool assumed_state{false}; bool supports_position{false}; bool supports_stop{false}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2236,6 +2254,7 @@ class ListEntitiesDateTimeResponse : public ProtoMessage { std::string icon{}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2282,6 +2301,7 @@ class ListEntitiesUpdateResponse : public ProtoMessage { bool disabled_by_default{false}; enums::EntityCategory entity_category{}; std::string device_class{}; + std::string device_id{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; From 31f2376f15523e8125814a192b26b1100e8b693e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Tue, 22 Apr 2025 14:03:07 +0200 Subject: [PATCH 19/99] Rename ref in codegen --- esphome/cpp_helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 3c91eafcf4..a1a7d3f516 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -112,8 +112,8 @@ async def setup_entity(var, config): if CONF_ENTITY_CATEGORY in config: add(var.set_entity_category(config[CONF_ENTITY_CATEGORY])) if CONF_DEVICE_ID in config: - parent = await get_variable(config[CONF_DEVICE_ID]) - add(var.set_device_id(parent.get_id())) + device = await get_variable(config[CONF_DEVICE_ID]) + add(var.set_device_id(device.get_id())) def extract_registry_entry_config( From d4fda79ada6f193823c94d656c1951e6ab0e288d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Tue, 6 May 2025 02:07:59 +0200 Subject: [PATCH 20/99] Attempt to replace device_id:str with device_uid:uint32 --- esphome/components/api/api.proto | 46 ++-- esphome/components/api/api_connection.cpp | 34 ++- esphome/components/api/api_pb2.cpp | 253 ++++++++-------------- esphome/components/api/api_pb2.h | 46 ++-- esphome/components/devices/__init__.py | 2 +- esphome/components/devices/devices.h | 6 +- esphome/config_validation.py | 6 +- esphome/const.py | 2 +- esphome/core/entity_base.h | 6 +- esphome/cpp_helpers.py | 2 +- tests/components/device/common.yaml | 2 +- 11 files changed, 183 insertions(+), 222 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index d364afe46e..3f5965829a 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -188,7 +188,7 @@ message DeviceInfoRequest { } message SubDeviceInfo { - string id = 1; + uint32 uid = 1; string name = 2; string suggested_area = 3; } @@ -286,7 +286,7 @@ message ListEntitiesBinarySensorResponse { bool disabled_by_default = 7; string icon = 8; EntityCategory entity_category = 9; - string device_id = 10; + uint32 device_uid = 10; } message BinarySensorStateResponse { option (id) = 21; @@ -320,7 +320,7 @@ message ListEntitiesCoverResponse { string icon = 10; EntityCategory entity_category = 11; bool supports_stop = 12; - string device_id = 13; + uint32 device_uid = 13; } enum LegacyCoverState { @@ -392,7 +392,7 @@ message ListEntitiesFanResponse { string icon = 10; EntityCategory entity_category = 11; repeated string supported_preset_modes = 12; - string device_id = 13; + uint32 device_uid = 13; } enum FanSpeed { FAN_SPEED_LOW = 0; @@ -473,7 +473,7 @@ message ListEntitiesLightResponse { bool disabled_by_default = 13; string icon = 14; EntityCategory entity_category = 15; - string device_id = 16; + uint32 device_uid = 16; } message LightStateResponse { option (id) = 24; @@ -564,7 +564,7 @@ message ListEntitiesSensorResponse { SensorLastResetType legacy_last_reset_type = 11; bool disabled_by_default = 12; EntityCategory entity_category = 13; - string device_id = 14; + uint32 device_uid = 14; } message SensorStateResponse { option (id) = 25; @@ -595,7 +595,7 @@ message ListEntitiesSwitchResponse { bool disabled_by_default = 7; EntityCategory entity_category = 8; string device_class = 9; - string device_id = 10; + uint32 device_uid = 10; } message SwitchStateResponse { option (id) = 26; @@ -631,7 +631,7 @@ message ListEntitiesTextSensorResponse { bool disabled_by_default = 6; EntityCategory entity_category = 7; string device_class = 8; - string device_id = 9; + uint32 device_uid = 9; } message TextSensorStateResponse { option (id) = 27; @@ -812,7 +812,7 @@ message ListEntitiesCameraResponse { bool disabled_by_default = 5; string icon = 6; EntityCategory entity_category = 7; - string device_id = 8; + uint32 device_uid = 8; } message CameraImageResponse { @@ -914,7 +914,7 @@ message ListEntitiesClimateResponse { bool supports_target_humidity = 23; float visual_min_humidity = 24; float visual_max_humidity = 25; - string device_id = 26; + uint32 device_uid = 26; } message ClimateStateResponse { option (id) = 47; @@ -994,7 +994,7 @@ message ListEntitiesNumberResponse { string unit_of_measurement = 11; NumberMode mode = 12; string device_class = 13; - string device_id = 14; + uint32 device_uid = 14; } message NumberStateResponse { option (id) = 50; @@ -1033,7 +1033,7 @@ message ListEntitiesSelectResponse { repeated string options = 6; bool disabled_by_default = 7; EntityCategory entity_category = 8; - string device_id = 9; + uint32 device_uid = 9; } message SelectStateResponse { option (id) = 53; @@ -1092,7 +1092,7 @@ message ListEntitiesLockResponse { // Not yet implemented: string code_format = 11; - string device_id = 12; + uint32 device_uid = 12; } message LockStateResponse { option (id) = 59; @@ -1130,7 +1130,7 @@ message ListEntitiesButtonResponse { bool disabled_by_default = 6; EntityCategory entity_category = 7; string device_class = 8; - string device_id = 9; + uint32 device_uid = 9; } message ButtonCommandRequest { option (id) = 62; @@ -1186,7 +1186,7 @@ message ListEntitiesMediaPlayerResponse { repeated MediaPlayerSupportedFormat supported_formats = 9; - string device_id = 10; + uint32 device_uid = 10; } message MediaPlayerStateResponse { option (id) = 64; @@ -1724,7 +1724,7 @@ message ListEntitiesAlarmControlPanelResponse { uint32 supported_features = 8; bool requires_code = 9; bool requires_code_to_arm = 10; - string device_id = 11; + uint32 device_uid = 11; } message AlarmControlPanelStateResponse { @@ -1768,7 +1768,7 @@ message ListEntitiesTextResponse { uint32 max_length = 9; string pattern = 10; TextMode mode = 11; - string device_id = 12; + uint32 device_uid = 12; } message TextStateResponse { option (id) = 98; @@ -1807,7 +1807,7 @@ message ListEntitiesDateResponse { string icon = 5; bool disabled_by_default = 6; EntityCategory entity_category = 7; - string device_id = 8; + uint32 device_uid = 8; } message DateStateResponse { option (id) = 101; @@ -1849,7 +1849,7 @@ message ListEntitiesTimeResponse { string icon = 5; bool disabled_by_default = 6; EntityCategory entity_category = 7; - string device_id = 8; + uint32 device_uid = 8; } message TimeStateResponse { option (id) = 104; @@ -1894,7 +1894,7 @@ message ListEntitiesEventResponse { string device_class = 8; repeated string event_types = 9; - string device_id = 10; + uint32 device_uid = 10; } message EventResponse { option (id) = 108; @@ -1924,7 +1924,7 @@ message ListEntitiesValveResponse { bool assumed_state = 9; bool supports_position = 10; bool supports_stop = 11; - string device_id = 12; + uint32 device_uid = 12; } enum ValveOperation { @@ -1969,7 +1969,7 @@ message ListEntitiesDateTimeResponse { string icon = 5; bool disabled_by_default = 6; EntityCategory entity_category = 7; - string device_id = 8; + uint32 device_uid = 8; } message DateTimeStateResponse { option (id) = 113; @@ -2008,7 +2008,7 @@ message ListEntitiesUpdateResponse { bool disabled_by_default = 6; EntityCategory entity_category = 7; string device_class = 8; - string device_id = 9; + uint32 device_uid = 9; } message UpdateStateResponse { option (id) = 117; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index cf0be8d198..22a5c7b8c1 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -287,7 +287,7 @@ bool APIConnection::try_send_binary_sensor_info(APIConnection *api, void *v_bina msg.disabled_by_default = binary_sensor->is_disabled_by_default(); msg.icon = binary_sensor->get_icon(); msg.entity_category = static_cast(binary_sensor->get_entity_category()); - msg.device_id = binary_sensor->get_device_id(); + msg.device_uid = binary_sensor->get_device_uid(); return api->send_list_entities_binary_sensor_response(msg); } #endif @@ -338,7 +338,7 @@ bool APIConnection::try_send_cover_info(APIConnection *api, void *v_cover) { msg.disabled_by_default = cover->is_disabled_by_default(); msg.icon = cover->get_icon(); msg.entity_category = static_cast(cover->get_entity_category()); - msg.device_id = cover->get_device_id(); + msg.device_uid = cover->get_device_uid(); return api->send_list_entities_cover_response(msg); } void APIConnection::cover_command(const CoverCommandRequest &msg) { @@ -421,6 +421,7 @@ bool APIConnection::try_send_fan_info(APIConnection *api, void *v_fan) { msg.disabled_by_default = fan->is_disabled_by_default(); msg.icon = fan->get_icon(); msg.entity_category = static_cast(fan->get_entity_category()); + msg.device_uid = fan->get_device_uid(); return api->send_list_entities_fan_response(msg); } void APIConnection::fan_command(const FanCommandRequest &msg) { @@ -518,6 +519,7 @@ bool APIConnection::try_send_light_info(APIConnection *api, void *v_light) { for (auto *effect : light->get_effects()) msg.effects.push_back(effect->get_name()); } + msg.device_uid = light->get_device_uid(); return api->send_list_entities_light_response(msg); } void APIConnection::light_command(const LightCommandRequest &msg) { @@ -602,6 +604,7 @@ bool APIConnection::try_send_sensor_info(APIConnection *api, void *v_sensor) { msg.state_class = static_cast(sensor->get_state_class()); msg.disabled_by_default = sensor->is_disabled_by_default(); msg.entity_category = static_cast(sensor->get_entity_category()); + msg.device_uid = sensor->get_device_uid(); return api->send_list_entities_sensor_response(msg); } #endif @@ -645,6 +648,7 @@ bool APIConnection::try_send_switch_info(APIConnection *api, void *v_a_switch) { msg.disabled_by_default = a_switch->is_disabled_by_default(); msg.entity_category = static_cast(a_switch->get_entity_category()); msg.device_class = a_switch->get_device_class(); + msg.device_uid = a_switch->get_device_uid(); return api->send_list_entities_switch_response(msg); } void APIConnection::switch_command(const SwitchCommandRequest &msg) { @@ -701,6 +705,7 @@ bool APIConnection::try_send_text_sensor_info(APIConnection *api, void *v_text_s msg.disabled_by_default = text_sensor->is_disabled_by_default(); msg.entity_category = static_cast(text_sensor->get_entity_category()); msg.device_class = text_sensor->get_device_class(); + msg.device_uid = text_sensor->get_device_uid(); return api->send_list_entities_text_sensor_response(msg); } #endif @@ -795,6 +800,7 @@ bool APIConnection::try_send_climate_info(APIConnection *api, void *v_climate) { msg.supported_custom_presets.push_back(custom_preset); for (auto swing_mode : traits.get_supported_swing_modes()) msg.supported_swing_modes.push_back(static_cast(swing_mode)); + msg.device_uid = climate->get_device_uid(); return api->send_list_entities_climate_response(msg); } void APIConnection::climate_command(const ClimateCommandRequest &msg) { @@ -873,6 +879,8 @@ bool APIConnection::try_send_number_info(APIConnection *api, void *v_number) { msg.max_value = number->traits.get_max_value(); msg.step = number->traits.get_step(); + msg.device_uid = number->get_device_uid(); + return api->send_list_entities_number_response(msg); } void APIConnection::number_command(const NumberCommandRequest &msg) { @@ -923,6 +931,7 @@ bool APIConnection::try_send_date_info(APIConnection *api, void *v_date) { msg.icon = date->get_icon(); msg.disabled_by_default = date->is_disabled_by_default(); msg.entity_category = static_cast(date->get_entity_category()); + msg.device_uid = date->get_device_uid(); return api->send_list_entities_date_response(msg); } @@ -974,6 +983,7 @@ bool APIConnection::try_send_time_info(APIConnection *api, void *v_time) { msg.icon = time->get_icon(); msg.disabled_by_default = time->is_disabled_by_default(); msg.entity_category = static_cast(time->get_entity_category()); + msg.device_uid = time->get_device_uid(); return api->send_list_entities_time_response(msg); } @@ -1026,6 +1036,7 @@ bool APIConnection::try_send_datetime_info(APIConnection *api, void *v_datetime) msg.icon = datetime->get_icon(); msg.disabled_by_default = datetime->is_disabled_by_default(); msg.entity_category = static_cast(datetime->get_entity_category()); + msg.device_uid = datetime->get_device_uid(); return api->send_list_entities_date_time_response(msg); } @@ -1081,6 +1092,7 @@ bool APIConnection::try_send_text_info(APIConnection *api, void *v_text) { msg.min_length = text->traits.get_min_length(); msg.max_length = text->traits.get_max_length(); msg.pattern = text->traits.get_pattern(); + msg.device_uid = text->get_device_uid(); return api->send_list_entities_text_response(msg); } @@ -1136,6 +1148,7 @@ bool APIConnection::try_send_select_info(APIConnection *api, void *v_select) { for (const auto &option : select->traits.get_options()) msg.options.push_back(option); + msg.device_uid = select->get_device_uid(); return api->send_list_entities_select_response(msg); } @@ -1168,6 +1181,7 @@ bool APIConnection::try_send_button_info(APIConnection *api, void *v_button) { msg.disabled_by_default = button->is_disabled_by_default(); msg.entity_category = static_cast(button->get_entity_category()); msg.device_class = button->get_device_class(); + msg.device_uid = button->get_device_uid(); return api->send_list_entities_button_response(msg); } void APIConnection::button_command(const ButtonCommandRequest &msg) { @@ -1219,6 +1233,7 @@ bool APIConnection::try_send_lock_info(APIConnection *api, void *v_a_lock) { msg.entity_category = static_cast(a_lock->get_entity_category()); msg.supports_open = a_lock->traits.get_supports_open(); msg.requires_code = a_lock->traits.get_requires_code(); + msg.device_uid = a_lock->get_device_uid(); return api->send_list_entities_lock_response(msg); } void APIConnection::lock_command(const LockCommandRequest &msg) { @@ -1280,6 +1295,7 @@ bool APIConnection::try_send_valve_info(APIConnection *api, void *v_valve) { msg.assumed_state = traits.get_is_assumed_state(); msg.supports_position = traits.get_supports_position(); msg.supports_stop = traits.get_supports_stop(); + msg.device_uid = valve->get_device_uid(); return api->send_list_entities_valve_response(msg); } void APIConnection::valve_command(const ValveCommandRequest &msg) { @@ -1349,6 +1365,7 @@ bool APIConnection::try_send_media_player_info(APIConnection *api, void *v_media media_format.sample_bytes = supported_format.sample_bytes; msg.supported_formats.push_back(media_format); } + msg.device_uid = media_player->get_device_uid(); return api->send_list_entities_media_player_response(msg); } @@ -1400,6 +1417,7 @@ bool APIConnection::try_send_camera_info(APIConnection *api, void *v_camera) { msg.disabled_by_default = camera->is_disabled_by_default(); msg.icon = camera->get_icon(); msg.entity_category = static_cast(camera->get_entity_category()); + msg.device_uid = camera->get_device_uid(); return api->send_list_entities_camera_response(msg); } void APIConnection::camera_image(const CameraImageRequest &msg) { @@ -1625,6 +1643,7 @@ bool APIConnection::try_send_alarm_control_panel_info(APIConnection *api, void * msg.supported_features = a_alarm_control_panel->get_supported_features(); msg.requires_code = a_alarm_control_panel->get_requires_code(); msg.requires_code_to_arm = a_alarm_control_panel->get_requires_code_to_arm(); + msg.device_uid = a_alarm_control_panel->get_device_uid(); return api->send_list_entities_alarm_control_panel_response(msg); } void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) { @@ -1696,6 +1715,7 @@ bool APIConnection::try_send_event_info(APIConnection *api, void *v_event) { msg.device_class = event->get_device_class(); for (const auto &event_type : event->get_event_types()) msg.event_types.push_back(event_type); + msg.device_uid = event->get_device_uid(); return api->send_list_entities_event_response(msg); } #endif @@ -1748,6 +1768,7 @@ bool APIConnection::try_send_update_info(APIConnection *api, void *v_update) { msg.disabled_by_default = update->is_disabled_by_default(); msg.entity_category = static_cast(update->get_entity_category()); msg.device_class = update->get_device_class(); + msg.device_uid = update->get_device_uid(); return api->send_list_entities_update_response(msg); } void APIConnection::update_command(const UpdateCommandRequest &msg) { @@ -1865,6 +1886,15 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { #endif #ifdef USE_API_NOISE resp.api_encryption_supported = true; +#endif +#ifdef USE_SUB_DEVICE + for (auto const &sub_device : App.get_sub_devices()) { + SubDeviceInfo sub_device_info; + sub_device_info.uid = sub_device->get_uid(); + sub_device_info.name = sub_device->get_name(); + sub_device_info.suggested_area = sub_device->get_area(); + resp.sub_devices.push_back(sub_device_info); + } #endif return resp; } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index bac5994a5b..19549c9a6c 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -796,10 +796,6 @@ void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfo #endif bool SubDeviceInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 1: { - this->id = value.as_string(); - return true; - } case 2: { this->name = value.as_string(); return true; @@ -813,7 +809,7 @@ bool SubDeviceInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) } } void SubDeviceInfo::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->id); + buffer.encode_fixed32(1, this->uid); buffer.encode_string(2, this->name); buffer.encode_string(3, this->suggested_area); } @@ -821,8 +817,9 @@ void SubDeviceInfo::encode(ProtoWriteBuffer buffer) const { void SubDeviceInfo::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("SubDeviceInfo {\n"); - out.append(" id: "); - out.append("'").append(this->id).append("'"); + out.append(" uid: "); + sprintf(buffer, "%" PRIu32, this->uid); + out.append(buffer); out.append("\n"); out.append(" name: "); @@ -1096,10 +1093,6 @@ bool ListEntitiesBinarySensorResponse::decode_length(uint32_t field_id, ProtoLen this->icon = value.as_string(); return true; } - case 10: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -1124,7 +1117,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(7, this->disabled_by_default); buffer.encode_string(8, this->icon); buffer.encode_enum(9, this->entity_category); - buffer.encode_string(10, this->device_id); + buffer.encode_fixed32(10, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { @@ -1167,8 +1160,9 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -1273,10 +1267,6 @@ bool ListEntitiesCoverResponse::decode_length(uint32_t field_id, ProtoLengthDeli this->icon = value.as_string(); return true; } - case 13: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -1304,7 +1294,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(10, this->icon); buffer.encode_enum(11, this->entity_category); buffer.encode_bool(12, this->supports_stop); - buffer.encode_string(13, this->device_id); + buffer.encode_fixed32(13, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCoverResponse::dump_to(std::string &out) const { @@ -1359,8 +1349,9 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { out.append(YESNO(this->supports_stop)); out.append("\n"); - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -1580,10 +1571,6 @@ bool ListEntitiesFanResponse::decode_length(uint32_t field_id, ProtoLengthDelimi this->supported_preset_modes.push_back(value.as_string()); return true; } - case 13: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -1613,7 +1600,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->supported_preset_modes) { buffer.encode_string(12, it, true); } - buffer.encode_string(13, this->device_id); + buffer.encode_fixed32(13, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesFanResponse::dump_to(std::string &out) const { @@ -1671,8 +1658,9 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { out.append("\n"); } - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -1969,10 +1957,6 @@ bool ListEntitiesLightResponse::decode_length(uint32_t field_id, ProtoLengthDeli this->icon = value.as_string(); return true; } - case 16: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -2015,7 +1999,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(13, this->disabled_by_default); buffer.encode_string(14, this->icon); buffer.encode_enum(15, this->entity_category); - buffer.encode_string(16, this->device_id); + buffer.encode_fixed32(16, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesLightResponse::dump_to(std::string &out) const { @@ -2088,8 +2072,9 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -2584,10 +2569,6 @@ bool ListEntitiesSensorResponse::decode_length(uint32_t field_id, ProtoLengthDel this->device_class = value.as_string(); return true; } - case 14: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -2616,7 +2597,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_enum(11, this->legacy_last_reset_type); buffer.encode_bool(12, this->disabled_by_default); buffer.encode_enum(13, this->entity_category); - buffer.encode_string(14, this->device_id); + buffer.encode_fixed32(14, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSensorResponse::dump_to(std::string &out) const { @@ -2676,8 +2657,9 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -2771,10 +2753,6 @@ bool ListEntitiesSwitchResponse::decode_length(uint32_t field_id, ProtoLengthDel this->device_class = value.as_string(); return true; } - case 10: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -2799,7 +2777,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(7, this->disabled_by_default); buffer.encode_enum(8, this->entity_category); buffer.encode_string(9, this->device_class); - buffer.encode_string(10, this->device_id); + buffer.encode_fixed32(10, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSwitchResponse::dump_to(std::string &out) const { @@ -2842,8 +2820,9 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const { out.append("'").append(this->device_class).append("'"); out.append("\n"); - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -2962,10 +2941,6 @@ bool ListEntitiesTextSensorResponse::decode_length(uint32_t field_id, ProtoLengt this->device_class = value.as_string(); return true; } - case 9: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -2989,7 +2964,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); buffer.encode_string(8, this->device_class); - buffer.encode_string(9, this->device_id); + buffer.encode_fixed32(9, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { @@ -3028,8 +3003,9 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { out.append("'").append(this->device_class).append("'"); out.append("\n"); - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -3737,10 +3713,6 @@ bool ListEntitiesCameraResponse::decode_length(uint32_t field_id, ProtoLengthDel this->icon = value.as_string(); return true; } - case 8: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -3763,7 +3735,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(5, this->disabled_by_default); buffer.encode_string(6, this->icon); buffer.encode_enum(7, this->entity_category); - buffer.encode_string(8, this->device_id); + buffer.encode_fixed32(8, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCameraResponse::dump_to(std::string &out) const { @@ -3798,8 +3770,9 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -3970,10 +3943,6 @@ bool ListEntitiesClimateResponse::decode_length(uint32_t field_id, ProtoLengthDe this->icon = value.as_string(); return true; } - case 26: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -4050,7 +4019,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_string(26, this->device_id); + buffer.encode_fixed32(26, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesClimateResponse::dump_to(std::string &out) const { @@ -4175,8 +4144,9 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -4631,10 +4601,6 @@ bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDel this->device_class = value.as_string(); return true; } - case 14: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -4675,7 +4641,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(11, this->unit_of_measurement); buffer.encode_enum(12, this->mode); buffer.encode_string(13, this->device_class); - buffer.encode_string(14, this->device_id); + buffer.encode_fixed32(14, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { @@ -4737,8 +4703,9 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { out.append("'").append(this->device_class).append("'"); out.append("\n"); - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -4862,10 +4829,6 @@ bool ListEntitiesSelectResponse::decode_length(uint32_t field_id, ProtoLengthDel this->options.push_back(value.as_string()); return true; } - case 9: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -4891,7 +4854,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { } buffer.encode_bool(7, this->disabled_by_default); buffer.encode_enum(8, this->entity_category); - buffer.encode_string(9, this->device_id); + buffer.encode_fixed32(9, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSelectResponse::dump_to(std::string &out) const { @@ -4932,8 +4895,9 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -5079,10 +5043,6 @@ bool ListEntitiesLockResponse::decode_length(uint32_t field_id, ProtoLengthDelim this->code_format = value.as_string(); return true; } - case 12: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -5109,7 +5069,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_string(12, this->device_id); + buffer.encode_fixed32(12, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesLockResponse::dump_to(std::string &out) const { @@ -5160,8 +5120,9 @@ void ListEntitiesLockResponse::dump_to(std::string &out) const { out.append("'").append(this->code_format).append("'"); out.append("\n"); - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -5304,10 +5265,6 @@ bool ListEntitiesButtonResponse::decode_length(uint32_t field_id, ProtoLengthDel this->device_class = value.as_string(); return true; } - case 9: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -5331,7 +5288,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); buffer.encode_string(8, this->device_class); - buffer.encode_string(9, this->device_id); + buffer.encode_fixed32(9, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesButtonResponse::dump_to(std::string &out) const { @@ -5370,8 +5327,9 @@ void ListEntitiesButtonResponse::dump_to(std::string &out) const { out.append("'").append(this->device_class).append("'"); out.append("\n"); - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -5506,10 +5464,6 @@ bool ListEntitiesMediaPlayerResponse::decode_length(uint32_t field_id, ProtoLeng this->supported_formats.push_back(value.as_message()); return true; } - case 10: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -5536,7 +5490,7 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->supported_formats) { buffer.encode_message(9, it, true); } - buffer.encode_string(10, this->device_id); + buffer.encode_fixed32(10, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { @@ -5581,8 +5535,9 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { out.append("\n"); } - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -7667,10 +7622,6 @@ bool ListEntitiesAlarmControlPanelResponse::decode_length(uint32_t field_id, Pro this->icon = value.as_string(); return true; } - case 11: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -7696,7 +7647,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_string(11, this->device_id); + buffer.encode_fixed32(11, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { @@ -7744,8 +7695,9 @@ void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { out.append(YESNO(this->requires_code_to_arm)); out.append("\n"); - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -7891,10 +7843,6 @@ bool ListEntitiesTextResponse::decode_length(uint32_t field_id, ProtoLengthDelim this->pattern = value.as_string(); return true; } - case 12: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -7921,7 +7869,7 @@ void ListEntitiesTextResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(9, this->max_length); buffer.encode_string(10, this->pattern); buffer.encode_enum(11, this->mode); - buffer.encode_string(12, this->device_id); + buffer.encode_fixed32(12, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTextResponse::dump_to(std::string &out) const { @@ -7974,8 +7922,9 @@ void ListEntitiesTextResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->mode)); out.append("\n"); - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -8105,10 +8054,6 @@ bool ListEntitiesDateResponse::decode_length(uint32_t field_id, ProtoLengthDelim this->icon = value.as_string(); return true; } - case 8: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -8131,7 +8076,7 @@ void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); - buffer.encode_string(8, this->device_id); + buffer.encode_fixed32(8, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesDateResponse::dump_to(std::string &out) const { @@ -8166,8 +8111,9 @@ void ListEntitiesDateResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -8333,10 +8279,6 @@ bool ListEntitiesTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelim this->icon = value.as_string(); return true; } - case 8: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -8359,7 +8301,7 @@ void ListEntitiesTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); - buffer.encode_string(8, this->device_id); + buffer.encode_fixed32(8, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTimeResponse::dump_to(std::string &out) const { @@ -8394,8 +8336,9 @@ void ListEntitiesTimeResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -8569,10 +8512,6 @@ bool ListEntitiesEventResponse::decode_length(uint32_t field_id, ProtoLengthDeli this->event_types.push_back(value.as_string()); return true; } - case 10: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -8599,7 +8538,7 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->event_types) { buffer.encode_string(9, it, true); } - buffer.encode_string(10, this->device_id); + buffer.encode_fixed32(10, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesEventResponse::dump_to(std::string &out) const { @@ -8644,8 +8583,9 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const { out.append("\n"); } - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -8737,10 +8677,6 @@ bool ListEntitiesValveResponse::decode_length(uint32_t field_id, ProtoLengthDeli this->device_class = value.as_string(); return true; } - case 12: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -8767,7 +8703,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_string(12, this->device_id); + buffer.encode_fixed32(12, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesValveResponse::dump_to(std::string &out) const { @@ -8818,8 +8754,9 @@ void ListEntitiesValveResponse::dump_to(std::string &out) const { out.append(YESNO(this->supports_stop)); out.append("\n"); - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -8963,10 +8900,6 @@ bool ListEntitiesDateTimeResponse::decode_length(uint32_t field_id, ProtoLengthD this->icon = value.as_string(); return true; } - case 8: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -8989,7 +8922,7 @@ void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); - buffer.encode_string(8, this->device_id); + buffer.encode_fixed32(8, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesDateTimeResponse::dump_to(std::string &out) const { @@ -9024,8 +8957,9 @@ void ListEntitiesDateTimeResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -9149,10 +9083,6 @@ bool ListEntitiesUpdateResponse::decode_length(uint32_t field_id, ProtoLengthDel this->device_class = value.as_string(); return true; } - case 9: { - this->device_id = value.as_string(); - return true; - } default: return false; } @@ -9176,7 +9106,7 @@ void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); buffer.encode_string(8, this->device_class); - buffer.encode_string(9, this->device_id); + buffer.encode_fixed32(9, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesUpdateResponse::dump_to(std::string &out) const { @@ -9215,8 +9145,9 @@ void ListEntitiesUpdateResponse::dump_to(std::string &out) const { out.append("'").append(this->device_class).append("'"); out.append("\n"); - out.append(" device_id: "); - out.append("'").append(this->device_id).append("'"); + out.append(" device_uid: "); + sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(buffer); out.append("\n"); out.append("}"); } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index c8daf51e43..5a9d431d54 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -349,7 +349,7 @@ class DeviceInfoRequest : public ProtoMessage { }; class SubDeviceInfo : public ProtoMessage { public: - std::string id{}; + uint32_t uid{}; std::string name{}; std::string suggested_area{}; void encode(ProtoWriteBuffer buffer) const override; @@ -429,7 +429,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage { bool disabled_by_default{false}; std::string icon{}; enums::EntityCategory entity_category{}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -468,7 +468,7 @@ class ListEntitiesCoverResponse : public ProtoMessage { std::string icon{}; enums::EntityCategory entity_category{}; bool supports_stop{false}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -528,7 +528,7 @@ class ListEntitiesFanResponse : public ProtoMessage { std::string icon{}; enums::EntityCategory entity_category{}; std::vector supported_preset_modes{}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -600,7 +600,7 @@ class ListEntitiesLightResponse : public ProtoMessage { bool disabled_by_default{false}; std::string icon{}; enums::EntityCategory entity_category{}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -690,7 +690,7 @@ class ListEntitiesSensorResponse : public ProtoMessage { enums::SensorLastResetType legacy_last_reset_type{}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -726,7 +726,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage { bool disabled_by_default{false}; enums::EntityCategory entity_category{}; std::string device_class{}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -773,7 +773,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage { bool disabled_by_default{false}; enums::EntityCategory entity_category{}; std::string device_class{}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1010,7 +1010,7 @@ class ListEntitiesCameraResponse : public ProtoMessage { bool disabled_by_default{false}; std::string icon{}; enums::EntityCategory entity_category{}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1075,7 +1075,7 @@ class ListEntitiesClimateResponse : public ProtoMessage { bool supports_target_humidity{false}; float visual_min_humidity{0.0f}; float visual_max_humidity{0.0f}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1163,7 +1163,7 @@ class ListEntitiesNumberResponse : public ProtoMessage { std::string unit_of_measurement{}; enums::NumberMode mode{}; std::string device_class{}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1210,7 +1210,7 @@ class ListEntitiesSelectResponse : public ProtoMessage { std::vector options{}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1262,7 +1262,7 @@ class ListEntitiesLockResponse : public ProtoMessage { bool supports_open{false}; bool requires_code{false}; std::string code_format{}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1312,7 +1312,7 @@ class ListEntitiesButtonResponse : public ProtoMessage { bool disabled_by_default{false}; enums::EntityCategory entity_category{}; std::string device_class{}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1361,7 +1361,7 @@ class ListEntitiesMediaPlayerResponse : public ProtoMessage { enums::EntityCategory entity_category{}; bool supports_pause{false}; std::vector supported_formats{}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1999,7 +1999,7 @@ class ListEntitiesAlarmControlPanelResponse : public ProtoMessage { uint32_t supported_features{0}; bool requires_code{false}; bool requires_code_to_arm{false}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2051,7 +2051,7 @@ class ListEntitiesTextResponse : public ProtoMessage { uint32_t max_length{0}; std::string pattern{}; enums::TextMode mode{}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2099,7 +2099,7 @@ class ListEntitiesDateResponse : public ProtoMessage { std::string icon{}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2150,7 +2150,7 @@ class ListEntitiesTimeResponse : public ProtoMessage { std::string icon{}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2203,7 +2203,7 @@ class ListEntitiesEventResponse : public ProtoMessage { enums::EntityCategory entity_category{}; std::string device_class{}; std::vector event_types{}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2240,7 +2240,7 @@ class ListEntitiesValveResponse : public ProtoMessage { bool assumed_state{false}; bool supports_position{false}; bool supports_stop{false}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2289,7 +2289,7 @@ class ListEntitiesDateTimeResponse : public ProtoMessage { std::string icon{}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2336,7 +2336,7 @@ class ListEntitiesUpdateResponse : public ProtoMessage { bool disabled_by_default{false}; enums::EntityCategory entity_category{}; std::string device_class{}; - std::string device_id{}; + uint32_t device_uid{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/devices/__init__.py b/esphome/components/devices/__init__.py index 5a70be82a7..5365b8ba3e 100644 --- a/esphome/components/devices/__init__.py +++ b/esphome/components/devices/__init__.py @@ -19,7 +19,7 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): dev = cg.new_Pvariable(config[CONF_ID]) - cg.add(dev.set_id(str(config[CONF_ID]))) + cg.add(dev.set_uid(hash(str(config[CONF_ID])) % 0xFFFFFFFF)) cg.add(dev.set_name(config[CONF_NAME])) cg.add(dev.set_area(config[CONF_AREA])) cg.add(cg.App.register_sub_device(dev)) diff --git a/esphome/components/devices/devices.h b/esphome/components/devices/devices.h index d8bd0d70a3..06f9309360 100644 --- a/esphome/components/devices/devices.h +++ b/esphome/components/devices/devices.h @@ -7,15 +7,15 @@ namespace devices { class SubDevice { public: - void set_id(std::string id) { id_ = std::move(id); } - std::string get_id() { return id_; } + void set_uid(uint32_t uid) { uid_ = uid; } + uint32_t get_uid() { return uid_; } void set_name(std::string name) { name_ = std::move(name); } std::string get_name() { return name_; } void set_area(std::string area) { area_ = std::move(area); } std::string get_area() { return area_; } protected: - std::string id_ = ""; + uint32_t uid_{}; std::string name_ = ""; std::string area_ = ""; }; diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 4eef985b7c..c5feeea5b9 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -21,7 +21,7 @@ from esphome.const import ( CONF_COMMAND_RETAIN, CONF_COMMAND_TOPIC, CONF_DAY, - CONF_DEVICE_ID, + CONF_DEVICE_UID, CONF_DISABLED_BY_DEFAULT, CONF_DISCOVERY, CONF_ENTITY_CATEGORY, @@ -348,7 +348,7 @@ def icon(value): ) -def sub_device_id(value): +def sub_device_uid(value): devices_ns = cg.esphome_ns.namespace("devices") SubDevice = devices_ns.class_("SubDevice") validator = use_id(SubDevice) @@ -1832,7 +1832,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, + Optional(CONF_DEVICE_UID): sub_device_uid, } ) diff --git a/esphome/const.py b/esphome/const.py index 22320e824b..ddd02d8b7e 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -216,7 +216,7 @@ CONF_DEST = "dest" CONF_DEVICE = "device" CONF_DEVICE_CLASS = "device_class" CONF_DEVICE_FACTOR = "device_factor" -CONF_DEVICE_ID = "device_id" +CONF_DEVICE_UID = "device_uid" CONF_DIELECTRIC_CONSTANT = "dielectric_constant" CONF_DIMENSIONS = "dimensions" CONF_DIO_PIN = "dio_pin" diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index e66fbb66e6..86d695add8 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -48,8 +48,8 @@ class EntityBase { void set_icon(const char *icon); // Get/set this entity's device id - const StringRef &get_device_id() const { return this->device_id_; } - void set_device_id(const std::string &device_id) { this->device_id_ = StringRef(device_id); } + const uint32_t get_device_uid() const { return this->device_uid_; } + void set_device_uid(const uint32_t device_uid) { this->device_uid_ = device_uid; } protected: /// The hash_base() function has been deprecated. It is kept in this @@ -65,7 +65,7 @@ class EntityBase { bool internal_{false}; bool disabled_by_default_{false}; EntityCategory entity_category_{ENTITY_CATEGORY_NONE}; - StringRef device_id_{""}; + uint32_t device_uid_{}; }; class EntityBase_DeviceClass { // NOLINT(readability-identifier-naming) diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index a1a7d3f516..f63d9fcb54 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -113,7 +113,7 @@ async def setup_entity(var, config): add(var.set_entity_category(config[CONF_ENTITY_CATEGORY])) if CONF_DEVICE_ID in config: device = await get_variable(config[CONF_DEVICE_ID]) - add(var.set_device_id(device.get_id())) + add(var.set_device_uid(hash(str(device)) % 0xFFFFFFFF)) def extract_registry_entry_config( diff --git a/tests/components/device/common.yaml b/tests/components/device/common.yaml index 232bb631c9..879a7591b1 100644 --- a/tests/components/device/common.yaml +++ b/tests/components/device/common.yaml @@ -8,4 +8,4 @@ binary_sensor: - platform: template name: Other device sensor - device_id: other_device + device_uid: other_device From cef023283b337edde03b647e0023cf6942e7a2a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Tue, 6 May 2025 02:55:44 +0200 Subject: [PATCH 21/99] Fix generated files --- esphome/components/api/api_pb2.cpp | 144 ++++++++++++++++++++++++----- esphome/components/api/api_pb2.h | 47 +++++----- 2 files changed, 145 insertions(+), 46 deletions(-) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 19549c9a6c..a8a1d641f0 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -794,6 +794,16 @@ void DeviceInfoRequest::encode(ProtoWriteBuffer buffer) const {} #ifdef HAS_PROTO_MESSAGE_DUMP void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfoRequest {}"); } #endif +bool SubDeviceInfo::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->uid = value.as_uint32(); + return true; + } + default: + return false; + } +} bool SubDeviceInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: { @@ -809,7 +819,7 @@ bool SubDeviceInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) } } void SubDeviceInfo::encode(ProtoWriteBuffer buffer) const { - buffer.encode_fixed32(1, this->uid); + buffer.encode_uint32(1, this->uid); buffer.encode_string(2, this->name); buffer.encode_string(3, this->suggested_area); } @@ -1067,6 +1077,10 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar this->entity_category = value.as_enum(); return true; } + case 10: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -1117,7 +1131,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(7, this->disabled_by_default); buffer.encode_string(8, this->icon); buffer.encode_enum(9, this->entity_category); - buffer.encode_fixed32(10, this->device_uid); + buffer.encode_uint32(10, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { @@ -1241,6 +1255,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val this->supports_stop = value.as_bool(); return true; } + case 13: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -1294,7 +1312,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(10, this->icon); buffer.encode_enum(11, this->entity_category); buffer.encode_bool(12, this->supports_stop); - buffer.encode_fixed32(13, this->device_uid); + buffer.encode_uint32(13, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCoverResponse::dump_to(std::string &out) const { @@ -1545,6 +1563,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value this->entity_category = value.as_enum(); return true; } + case 13: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -1600,7 +1622,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->supported_preset_modes) { buffer.encode_string(12, it, true); } - buffer.encode_fixed32(13, this->device_uid); + buffer.encode_uint32(13, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesFanResponse::dump_to(std::string &out) const { @@ -1931,6 +1953,10 @@ bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt val this->entity_category = value.as_enum(); return true; } + case 16: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -1999,7 +2025,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(13, this->disabled_by_default); buffer.encode_string(14, this->icon); buffer.encode_enum(15, this->entity_category); - buffer.encode_fixed32(16, this->device_uid); + buffer.encode_uint32(16, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesLightResponse::dump_to(std::string &out) const { @@ -2569,6 +2595,10 @@ bool ListEntitiesSensorResponse::decode_length(uint32_t field_id, ProtoLengthDel this->device_class = value.as_string(); return true; } + case 14: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -2597,7 +2627,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_enum(11, this->legacy_last_reset_type); buffer.encode_bool(12, this->disabled_by_default); buffer.encode_enum(13, this->entity_category); - buffer.encode_fixed32(14, this->device_uid); + buffer.encode_uint32(14, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSensorResponse::dump_to(std::string &out) const { @@ -2727,6 +2757,10 @@ bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->entity_category = value.as_enum(); return true; } + case 10: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -2777,7 +2811,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(7, this->disabled_by_default); buffer.encode_enum(8, this->entity_category); buffer.encode_string(9, this->device_class); - buffer.encode_fixed32(10, this->device_uid); + buffer.encode_uint32(10, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSwitchResponse::dump_to(std::string &out) const { @@ -2915,6 +2949,10 @@ bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarIn this->entity_category = value.as_enum(); return true; } + case 9: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -2964,7 +3002,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); buffer.encode_string(8, this->device_class); - buffer.encode_fixed32(9, this->device_uid); + buffer.encode_uint32(9, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { @@ -3691,6 +3729,10 @@ bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->entity_category = value.as_enum(); return true; } + case 8: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -3735,7 +3777,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(5, this->disabled_by_default); buffer.encode_string(6, this->icon); buffer.encode_enum(7, this->entity_category); - buffer.encode_fixed32(8, this->device_uid); + buffer.encode_uint32(8, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCameraResponse::dump_to(std::string &out) const { @@ -3913,6 +3955,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v this->supports_target_humidity = value.as_bool(); return true; } + case 26: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -4019,7 +4065,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_fixed32(26, this->device_uid); + buffer.encode_uint32(26, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesClimateResponse::dump_to(std::string &out) const { @@ -4571,6 +4617,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->mode = value.as_enum(); return true; } + case 14: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -4641,7 +4691,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(11, this->unit_of_measurement); buffer.encode_enum(12, this->mode); buffer.encode_string(13, this->device_class); - buffer.encode_fixed32(14, this->device_uid); + buffer.encode_uint32(14, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { @@ -4829,6 +4879,10 @@ bool ListEntitiesSelectResponse::decode_length(uint32_t field_id, ProtoLengthDel this->options.push_back(value.as_string()); return true; } + case 9: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -4854,7 +4908,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { } buffer.encode_bool(7, this->disabled_by_default); buffer.encode_enum(8, this->entity_category); - buffer.encode_fixed32(9, this->device_uid); + buffer.encode_uint32(9, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSelectResponse::dump_to(std::string &out) const { @@ -5017,6 +5071,10 @@ bool ListEntitiesLockResponse::decode_varint(uint32_t field_id, ProtoVarInt valu this->requires_code = value.as_bool(); return true; } + case 12: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -5069,7 +5127,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_fixed32(12, this->device_uid); + buffer.encode_uint32(12, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesLockResponse::dump_to(std::string &out) const { @@ -5239,6 +5297,10 @@ bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->entity_category = value.as_enum(); return true; } + case 9: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -5288,7 +5350,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); buffer.encode_string(8, this->device_class); - buffer.encode_fixed32(9, this->device_uid); + buffer.encode_uint32(9, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesButtonResponse::dump_to(std::string &out) const { @@ -5438,6 +5500,10 @@ bool ListEntitiesMediaPlayerResponse::decode_varint(uint32_t field_id, ProtoVarI this->supports_pause = value.as_bool(); return true; } + case 10: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -5490,7 +5556,7 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->supported_formats) { buffer.encode_message(9, it, true); } - buffer.encode_fixed32(10, this->device_uid); + buffer.encode_uint32(10, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { @@ -7600,6 +7666,10 @@ bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, Pro this->requires_code_to_arm = value.as_bool(); return true; } + case 11: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -7647,7 +7717,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_fixed32(11, this->device_uid); + buffer.encode_uint32(11, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { @@ -7817,6 +7887,10 @@ bool ListEntitiesTextResponse::decode_varint(uint32_t field_id, ProtoVarInt valu this->mode = value.as_enum(); return true; } + case 12: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -7869,7 +7943,7 @@ void ListEntitiesTextResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(9, this->max_length); buffer.encode_string(10, this->pattern); buffer.encode_enum(11, this->mode); - buffer.encode_fixed32(12, this->device_uid); + buffer.encode_uint32(12, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTextResponse::dump_to(std::string &out) const { @@ -8032,6 +8106,10 @@ bool ListEntitiesDateResponse::decode_varint(uint32_t field_id, ProtoVarInt valu this->entity_category = value.as_enum(); return true; } + case 8: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -8076,7 +8154,7 @@ void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); - buffer.encode_fixed32(8, this->device_uid); + buffer.encode_uint32(8, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesDateResponse::dump_to(std::string &out) const { @@ -8257,6 +8335,10 @@ bool ListEntitiesTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt valu this->entity_category = value.as_enum(); return true; } + case 8: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -8301,7 +8383,7 @@ void ListEntitiesTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); - buffer.encode_fixed32(8, this->device_uid); + buffer.encode_uint32(8, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTimeResponse::dump_to(std::string &out) const { @@ -8482,6 +8564,10 @@ bool ListEntitiesEventResponse::decode_varint(uint32_t field_id, ProtoVarInt val this->entity_category = value.as_enum(); return true; } + case 10: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -8538,7 +8624,7 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->event_types) { buffer.encode_string(9, it, true); } - buffer.encode_fixed32(10, this->device_uid); + buffer.encode_uint32(10, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesEventResponse::dump_to(std::string &out) const { @@ -8651,6 +8737,10 @@ bool ListEntitiesValveResponse::decode_varint(uint32_t field_id, ProtoVarInt val this->supports_stop = value.as_bool(); return true; } + case 12: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -8703,7 +8793,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_fixed32(12, this->device_uid); + buffer.encode_uint32(12, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesValveResponse::dump_to(std::string &out) const { @@ -8878,6 +8968,10 @@ bool ListEntitiesDateTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt this->entity_category = value.as_enum(); return true; } + case 8: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -8922,7 +9016,7 @@ void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); - buffer.encode_fixed32(8, this->device_uid); + buffer.encode_uint32(8, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesDateTimeResponse::dump_to(std::string &out) const { @@ -9057,6 +9151,10 @@ bool ListEntitiesUpdateResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->entity_category = value.as_enum(); return true; } + case 9: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -9106,7 +9204,7 @@ void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); buffer.encode_string(8, this->device_class); - buffer.encode_fixed32(9, this->device_uid); + buffer.encode_uint32(9, this->device_uid); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesUpdateResponse::dump_to(std::string &out) const { diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 5a9d431d54..6c4e06345b 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -349,7 +349,7 @@ class DeviceInfoRequest : public ProtoMessage { }; class SubDeviceInfo : public ProtoMessage { public: - uint32_t uid{}; + uint32_t uid{0}; std::string name{}; std::string suggested_area{}; void encode(ProtoWriteBuffer buffer) const override; @@ -359,6 +359,7 @@ class SubDeviceInfo : public ProtoMessage { 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: @@ -429,7 +430,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage { bool disabled_by_default{false}; std::string icon{}; enums::EntityCategory entity_category{}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -468,7 +469,7 @@ class ListEntitiesCoverResponse : public ProtoMessage { std::string icon{}; enums::EntityCategory entity_category{}; bool supports_stop{false}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -528,7 +529,7 @@ class ListEntitiesFanResponse : public ProtoMessage { std::string icon{}; enums::EntityCategory entity_category{}; std::vector supported_preset_modes{}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -600,7 +601,7 @@ class ListEntitiesLightResponse : public ProtoMessage { bool disabled_by_default{false}; std::string icon{}; enums::EntityCategory entity_category{}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -690,7 +691,7 @@ class ListEntitiesSensorResponse : public ProtoMessage { enums::SensorLastResetType legacy_last_reset_type{}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -726,7 +727,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage { bool disabled_by_default{false}; enums::EntityCategory entity_category{}; std::string device_class{}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -773,7 +774,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage { bool disabled_by_default{false}; enums::EntityCategory entity_category{}; std::string device_class{}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1010,7 +1011,7 @@ class ListEntitiesCameraResponse : public ProtoMessage { bool disabled_by_default{false}; std::string icon{}; enums::EntityCategory entity_category{}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1075,7 +1076,7 @@ class ListEntitiesClimateResponse : public ProtoMessage { bool supports_target_humidity{false}; float visual_min_humidity{0.0f}; float visual_max_humidity{0.0f}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1163,7 +1164,7 @@ class ListEntitiesNumberResponse : public ProtoMessage { std::string unit_of_measurement{}; enums::NumberMode mode{}; std::string device_class{}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1210,7 +1211,7 @@ class ListEntitiesSelectResponse : public ProtoMessage { std::vector options{}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1262,7 +1263,7 @@ class ListEntitiesLockResponse : public ProtoMessage { bool supports_open{false}; bool requires_code{false}; std::string code_format{}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1312,7 +1313,7 @@ class ListEntitiesButtonResponse : public ProtoMessage { bool disabled_by_default{false}; enums::EntityCategory entity_category{}; std::string device_class{}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1361,7 +1362,7 @@ class ListEntitiesMediaPlayerResponse : public ProtoMessage { enums::EntityCategory entity_category{}; bool supports_pause{false}; std::vector supported_formats{}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1999,7 +2000,7 @@ class ListEntitiesAlarmControlPanelResponse : public ProtoMessage { uint32_t supported_features{0}; bool requires_code{false}; bool requires_code_to_arm{false}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2051,7 +2052,7 @@ class ListEntitiesTextResponse : public ProtoMessage { uint32_t max_length{0}; std::string pattern{}; enums::TextMode mode{}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2099,7 +2100,7 @@ class ListEntitiesDateResponse : public ProtoMessage { std::string icon{}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2150,7 +2151,7 @@ class ListEntitiesTimeResponse : public ProtoMessage { std::string icon{}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2203,7 +2204,7 @@ class ListEntitiesEventResponse : public ProtoMessage { enums::EntityCategory entity_category{}; std::string device_class{}; std::vector event_types{}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2240,7 +2241,7 @@ class ListEntitiesValveResponse : public ProtoMessage { bool assumed_state{false}; bool supports_position{false}; bool supports_stop{false}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2289,7 +2290,7 @@ class ListEntitiesDateTimeResponse : public ProtoMessage { std::string icon{}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2336,7 +2337,7 @@ class ListEntitiesUpdateResponse : public ProtoMessage { bool disabled_by_default{false}; enums::EntityCategory entity_category{}; std::string device_class{}; - uint32_t device_uid{}; + uint32_t device_uid{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; From 79bbc475f4e48ab7dfa1ef71dad37244add39fcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Tue, 6 May 2025 03:05:00 +0200 Subject: [PATCH 22/99] Fix generated files and revert entity config to device_id --- esphome/components/api/api_pb2.cpp | 16 ++++++++-------- esphome/config_validation.py | 6 +++--- esphome/const.py | 2 +- tests/components/device/common.yaml | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index a8a1d641f0..3f19dc5313 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -2565,6 +2565,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->entity_category = value.as_enum(); return true; } + case 14: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -2595,10 +2599,6 @@ bool ListEntitiesSensorResponse::decode_length(uint32_t field_id, ProtoLengthDel this->device_class = value.as_string(); return true; } - case 14: { - this->device_uid = value.as_uint32(); - return true; - } default: return false; } @@ -4853,6 +4853,10 @@ bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->entity_category = value.as_enum(); return true; } + case 9: { + this->device_uid = value.as_uint32(); + return true; + } default: return false; } @@ -4879,10 +4883,6 @@ bool ListEntitiesSelectResponse::decode_length(uint32_t field_id, ProtoLengthDel this->options.push_back(value.as_string()); return true; } - case 9: { - this->device_uid = value.as_uint32(); - return true; - } default: return false; } diff --git a/esphome/config_validation.py b/esphome/config_validation.py index c5feeea5b9..4eef985b7c 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -21,7 +21,7 @@ from esphome.const import ( CONF_COMMAND_RETAIN, CONF_COMMAND_TOPIC, CONF_DAY, - CONF_DEVICE_UID, + CONF_DEVICE_ID, CONF_DISABLED_BY_DEFAULT, CONF_DISCOVERY, CONF_ENTITY_CATEGORY, @@ -348,7 +348,7 @@ def icon(value): ) -def sub_device_uid(value): +def sub_device_id(value): devices_ns = cg.esphome_ns.namespace("devices") SubDevice = devices_ns.class_("SubDevice") validator = use_id(SubDevice) @@ -1832,7 +1832,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_UID): sub_device_uid, + Optional(CONF_DEVICE_ID): sub_device_id, } ) diff --git a/esphome/const.py b/esphome/const.py index ddd02d8b7e..22320e824b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -216,7 +216,7 @@ CONF_DEST = "dest" CONF_DEVICE = "device" CONF_DEVICE_CLASS = "device_class" CONF_DEVICE_FACTOR = "device_factor" -CONF_DEVICE_UID = "device_uid" +CONF_DEVICE_ID = "device_id" CONF_DIELECTRIC_CONSTANT = "dielectric_constant" CONF_DIMENSIONS = "dimensions" CONF_DIO_PIN = "dio_pin" diff --git a/tests/components/device/common.yaml b/tests/components/device/common.yaml index 879a7591b1..232bb631c9 100644 --- a/tests/components/device/common.yaml +++ b/tests/components/device/common.yaml @@ -8,4 +8,4 @@ binary_sensor: - platform: template name: Other device sensor - device_uid: other_device + device_id: other_device From 8fb8e7973009a5e284a5900438b8efb06b0fd997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Tue, 6 May 2025 03:20:22 +0200 Subject: [PATCH 23/99] Fix clang --- esphome/core/entity_base.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 86d695add8..60db74e616 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -48,7 +48,7 @@ class EntityBase { void set_icon(const char *icon); // Get/set this entity's device id - const uint32_t get_device_uid() const { return this->device_uid_; } + uint32_t get_device_uid() const { return this->device_uid_; } void set_device_uid(const uint32_t device_uid) { this->device_uid_ = device_uid; } protected: From 7b460b6224fc459762ba1378774e1e4baeeebfa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Tue, 6 May 2025 03:34:33 +0200 Subject: [PATCH 24/99] Restore ci-api-proto.yml --- .github/workflows/ci-api-proto.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci-api-proto.yml b/.github/workflows/ci-api-proto.yml index 77caad2d22..d6469236d5 100644 --- a/.github/workflows/ci-api-proto.yml +++ b/.github/workflows/ci-api-proto.yml @@ -37,8 +37,6 @@ jobs: run: pip install aioesphomeapi -c requirements.txt -r requirements_dev.txt - name: Generate files run: script/api_protobuf/api_protobuf.py - - name: Show changes - run: git diff - name: Check for changes run: | if ! git diff --quiet; then From 3915e1f0120b6ffc9483c7d845b1ba80eda77eab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Tue, 6 May 2025 03:36:03 +0200 Subject: [PATCH 25/99] Revert "Improve stability for unrelated test" This reverts commit 3922950951191ed3052964b000dac2651595c419. --- tests/dashboard/test_web_server.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/dashboard/test_web_server.py b/tests/dashboard/test_web_server.py index 13d2bbbf33..a61850abf3 100644 --- a/tests/dashboard/test_web_server.py +++ b/tests/dashboard/test_web_server.py @@ -75,9 +75,6 @@ async def test_devices_page(dashboard: DashboardTestHelper) -> None: assert response.headers["content-type"] == "application/json" json_data = json.loads(response.body.decode()) configured_devices = json_data["configured"] - if len(configured_devices) == 0: - assert len(configured_devices) != 0 - else: - first_device = configured_devices[0] - assert first_device["name"] == "pico" - assert first_device["configuration"] == "pico.yaml" + first_device = configured_devices[0] + assert first_device["name"] == "pico" + assert first_device["configuration"] == "pico.yaml" From ff626b428f28042cebccfee2e3162e527f520bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Tue, 6 May 2025 10:41:46 +0200 Subject: [PATCH 26/99] Attempt moving it to esphome config section --- esphome/components/devices/__init__.py | 26 ------------------- esphome/const.py | 1 + esphome/core/config.py | 22 +++++++++++++++- .../devices/devices.h => core/sub_device.h} | 2 -- tests/components/device/common.yaml | 11 -------- tests/components/esphome/common.yaml | 8 ++++++ 6 files changed, 30 insertions(+), 40 deletions(-) delete mode 100644 esphome/components/devices/__init__.py rename esphome/{components/devices/devices.h => core/sub_device.h} (92%) delete mode 100644 tests/components/device/common.yaml diff --git a/esphome/components/devices/__init__.py b/esphome/components/devices/__init__.py deleted file mode 100644 index 5365b8ba3e..0000000000 --- a/esphome/components/devices/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -from esphome import codegen as cg, config_validation as cv -from esphome.const import CONF_AREA, CONF_ID, CONF_NAME - -devices_ns = cg.esphome_ns.namespace("devices") -SubDevice = devices_ns.class_("SubDevice") - -MULTI_CONF = True - -CODEOWNERS = ["@dala318"] - -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(CONF_ID): cv.declare_id(SubDevice), - cv.Required(CONF_NAME): cv.string, - cv.Optional(CONF_AREA, default=""): cv.string, - } -).extend(cv.COMPONENT_SCHEMA) - - -async def to_code(config): - dev = cg.new_Pvariable(config[CONF_ID]) - cg.add(dev.set_uid(hash(str(config[CONF_ID])) % 0xFFFFFFFF)) - cg.add(dev.set_name(config[CONF_NAME])) - cg.add(dev.set_area(config[CONF_AREA])) - cg.add(cg.App.register_sub_device(dev)) - cg.add_define("USE_SUB_DEVICE") diff --git a/esphome/const.py b/esphome/const.py index 22320e824b..03e4010300 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -835,6 +835,7 @@ CONF_STEP_PIN = "step_pin" CONF_STOP = "stop" CONF_STOP_ACTION = "stop_action" CONF_STORE_BASELINE = "store_baseline" +CONF_SUB_DEVICES = "sub_devices" CONF_SUBNET = "subnet" CONF_SUBSCRIBE_QOS = "subscribe_qos" CONF_SUBSTITUTIONS = "substitutions" diff --git a/esphome/core/config.py b/esphome/core/config.py index 72e9f6a65c..f3d8b7e715 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -13,6 +13,7 @@ from esphome.const import ( CONF_DEBUG_SCHEDULER, CONF_ESPHOME, CONF_FRIENDLY_NAME, + CONF_ID, CONF_INCLUDES, CONF_LIBRARIES, CONF_MIN_VERSION, @@ -26,6 +27,7 @@ from esphome.const import ( CONF_PLATFORMIO_OPTIONS, CONF_PRIORITY, CONF_PROJECT, + CONF_SUB_DEVICES, CONF_TRIGGER_ID, CONF_VERSION, KEY_CORE, @@ -48,7 +50,7 @@ LoopTrigger = cg.esphome_ns.class_( ProjectUpdateTrigger = cg.esphome_ns.class_( "ProjectUpdateTrigger", cg.Component, automation.Trigger.template(cg.std_string) ) - +SubDevice = cg.esphome_ns.class_("SubDevice") VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} @@ -167,6 +169,15 @@ 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_SUB_DEVICES, default=[]): cv.ensure_list( + cv.Schema( + { + cv.GenerateID(CONF_ID): cv.declare_id(SubDevice), + cv.Required(CONF_NAME): cv.string, + cv.Optional(CONF_AREA, default=""): cv.string, + } + ), + ), } ), validate_hostname, @@ -405,3 +416,12 @@ async def to_code(config): if config[CONF_PLATFORMIO_OPTIONS]: CORE.add_job(_add_platformio_options, config[CONF_PLATFORMIO_OPTIONS]) + + if config[CONF_SUB_DEVICES]: + for dev_conf in config[CONF_SUB_DEVICES]: + dev = cg.new_Pvariable(dev_conf[CONF_ID]) + cg.add(dev.set_uid(hash(str(dev_conf[CONF_ID])) % 0xFFFFFFFF)) + cg.add(dev.set_name(dev_conf[CONF_NAME])) + cg.add(dev.set_area(dev_conf[CONF_AREA])) + cg.add(cg.App.register_sub_device(dev)) + cg.add_define("USE_SUB_DEVICE") diff --git a/esphome/components/devices/devices.h b/esphome/core/sub_device.h similarity index 92% rename from esphome/components/devices/devices.h rename to esphome/core/sub_device.h index 06f9309360..9e7c4d2261 100644 --- a/esphome/components/devices/devices.h +++ b/esphome/core/sub_device.h @@ -3,7 +3,6 @@ #include "esphome/core/string_ref.h" namespace esphome { -namespace devices { class SubDevice { public: @@ -20,5 +19,4 @@ class SubDevice { std::string area_ = ""; }; -} // namespace devices } // namespace esphome diff --git a/tests/components/device/common.yaml b/tests/components/device/common.yaml deleted file mode 100644 index 232bb631c9..0000000000 --- a/tests/components/device/common.yaml +++ /dev/null @@ -1,11 +0,0 @@ -devices: - - id: other_device - name: Another device - -binary_sensor: - - platform: template - name: Basic sensor - - - platform: template - name: Other device sensor - device_id: other_device diff --git a/tests/components/esphome/common.yaml b/tests/components/esphome/common.yaml index 05954e37d7..3754390e89 100644 --- a/tests/components/esphome/common.yaml +++ b/tests/components/esphome/common.yaml @@ -17,4 +17,12 @@ esphome: version: "1.1" on_update: logger.log: on_update + sub_devices: + - id: other_device + name: Another device + area: Another area +binary_sensor: + - platform: template + name: Other device sensor + device_id: other_device From 39beccbbb0f9c3a1c910e9a3b500fc17cc22e59d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Tue, 6 May 2025 10:50:09 +0200 Subject: [PATCH 27/99] remove from CODEOWNERS --- CODEOWNERS | 1 - 1 file changed, 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 7dca09e0ac..29919b6d70 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -117,7 +117,6 @@ esphome/components/dashboard_import/* @esphome/core esphome/components/datetime/* @jesserockz @rfdarter esphome/components/debug/* @OttoWinter esphome/components/delonghi/* @grob6000 -esphome/components/devices/* @dala318 esphome/components/dfplayer/* @glmnet esphome/components/dfrobot_sen0395/* @niklasweber esphome/components/dht/* @OttoWinter From dd2b931f6194c75b0ad9f95b907587c6cd0221c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Tue, 6 May 2025 11:46:23 +0200 Subject: [PATCH 28/99] Fix namespace error --- esphome/config_validation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 4eef985b7c..ae9d1308ce 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -349,8 +349,8 @@ def icon(value): def sub_device_id(value): - devices_ns = cg.esphome_ns.namespace("devices") - SubDevice = devices_ns.class_("SubDevice") + # Duplicate definition of SubDevice to avoid circular import + SubDevice = cg.esphome_ns.class_("SubDevice") validator = use_id(SubDevice) return validator(value) From 856829bcbb6bb56fa667898df026c91201aed2d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Tue, 6 May 2025 12:05:45 +0200 Subject: [PATCH 29/99] More namespace and import fixes --- esphome/core/application.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/core/application.h b/esphome/core/application.h index 796ce39ef9..a57cdb4bf2 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -10,7 +10,7 @@ #include "esphome/core/scheduler.h" #ifdef USE_SUB_DEVICE -#include "esphome/components/devices/devices.h" +#include "esphome/core/sub_device.h" #endif #ifdef USE_BINARY_SENSOR #include "esphome/components/binary_sensor/binary_sensor.h" @@ -101,7 +101,7 @@ class Application { } #ifdef USE_SUB_DEVICE - void register_sub_device(devices::SubDevice *sub_device) { this->sub_devices_.push_back(sub_device); } + void register_sub_device(SubDevice *sub_device) { this->sub_devices_.push_back(sub_device); } #endif void set_current_component(Component *component) { this->current_component_ = component; } @@ -254,10 +254,10 @@ class Application { uint32_t get_app_state() const { return this->app_state_; } #ifdef USE_SUB_DEVICE - const std::vector &get_sub_devices() { return this->sub_devices_; } + const std::vector &get_sub_devices() { return this->sub_devices_; } // /* Very likely no need for get_sub_device_by_key as it only seem to be used when requesting update from API // and the sub_devices shaould only be sent once at connection. */ - // devices::SubDevice *get_sub_device_by_key(uint32_t key, bool include_internal = false) { + // SubDevice *get_sub_device_by_key(uint32_t key, bool include_internal = false) { // for (auto *obj : this->sub_devices_) { // if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) // return obj; @@ -496,7 +496,7 @@ class Application { std::vector looping_components_{}; #ifdef USE_SUB_DEVICE - std::vector sub_devices_{}; + std::vector sub_devices_{}; #endif #ifdef USE_BINARY_SENSOR std::vector binary_sensors_{}; From a59a8c563e1357be79e71854e7379acb7f3f4479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vikstr=C3=B6m?= Date: Tue, 6 May 2025 12:30:04 +0200 Subject: [PATCH 30/99] Attempt fixing circular import by lazy import --- esphome/config_validation.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index ae9d1308ce..eca78746d8 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -349,8 +349,9 @@ def icon(value): def sub_device_id(value): - # Duplicate definition of SubDevice to avoid circular import - SubDevice = cg.esphome_ns.class_("SubDevice") + # Lazy import to avoid circular imports + from esphome.core.config import SubDevice + validator = use_id(SubDevice) return validator(value) From 9624efa21e456d18fc618029012a07cb2be19d8e Mon Sep 17 00:00:00 2001 From: Daniel Vikstrom Date: Thu, 22 May 2025 14:18:46 +0200 Subject: [PATCH 31/99] Fix proto generation and clang --- esphome/components/api/api_connection.cpp | 4 ++-- esphome/components/api/api_pb2.cpp | 28 +++++++++++++++++++++++ esphome/components/api/api_pb2.h | 1 + 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index f094ff7d46..6a6edbec02 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -298,8 +298,8 @@ bool APIConnection::try_send_binary_sensor_info_(binary_sensor::BinarySensor *bi msg.unique_id = get_default_unique_id("binary_sensor", binary_sensor); return this->try_send_entity_info_(static_cast(binary_sensor), msg, &APIConnection::send_list_entities_binary_sensor_response); -} -#endif +} +#endif #ifdef USE_COVER bool APIConnection::send_cover_state(cover::Cover *cover) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index f5fe4bca06..2674b9c475 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -848,6 +848,11 @@ void SubDeviceInfo::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(2, this->name); buffer.encode_string(3, this->suggested_area); } +void SubDeviceInfo::calculate_size(uint32_t &total_size) const { + ProtoSize::add_uint32_field(total_size, 1, this->uid, false); + ProtoSize::add_string_field(total_size, 1, this->name, false); + ProtoSize::add_string_field(total_size, 1, this->suggested_area, false); +} #ifdef HAS_PROTO_MESSAGE_DUMP void SubDeviceInfo::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; @@ -1003,6 +1008,7 @@ 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->sub_devices); } #ifdef HAS_PROTO_MESSAGE_DUMP void DeviceInfoResponse::dump_to(std::string &out) const { @@ -1192,6 +1198,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(this->entity_category), false); + ProtoSize::add_uint32_field(total_size, 1, this->device_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { @@ -1392,6 +1399,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(this->entity_category), false); ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCoverResponse::dump_to(std::string &out) const { @@ -1737,6 +1745,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_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesFanResponse::dump_to(std::string &out) const { @@ -2189,6 +2198,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(this->entity_category), false); + ProtoSize::add_uint32_field(total_size, 2, this->device_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesLightResponse::dump_to(std::string &out) const { @@ -2850,6 +2860,7 @@ void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_enum_field(total_size, 1, static_cast(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(this->entity_category), false); + ProtoSize::add_uint32_field(total_size, 1, this->device_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSensorResponse::dump_to(std::string &out) const { @@ -3050,6 +3061,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(this->entity_category), false); ProtoSize::add_string_field(total_size, 1, this->device_class, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSwitchResponse::dump_to(std::string &out) const { @@ -3259,6 +3271,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(this->entity_category), false); ProtoSize::add_string_field(total_size, 1, this->device_class, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { @@ -4130,6 +4143,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(this->entity_category), false); + ProtoSize::add_uint32_field(total_size, 1, this->device_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCameraResponse::dump_to(std::string &out) const { @@ -4478,6 +4492,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_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesClimateResponse::dump_to(std::string &out) const { @@ -5161,6 +5176,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(this->mode), false); ProtoSize::add_string_field(total_size, 1, this->device_class, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { @@ -5401,6 +5417,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(this->entity_category), false); + ProtoSize::add_uint32_field(total_size, 1, this->device_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSelectResponse::dump_to(std::string &out) const { @@ -5943,6 +5960,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_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesLockResponse::dump_to(std::string &out) const { @@ -6186,6 +6204,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(this->entity_category), false); ProtoSize::add_string_field(total_size, 1, this->device_class, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesButtonResponse::dump_to(std::string &out) const { @@ -6413,6 +6432,7 @@ void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const ProtoSize::add_enum_field(total_size, 1, static_cast(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_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { @@ -8841,6 +8861,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_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { @@ -9089,6 +9110,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(this->mode), false); + ProtoSize::add_uint32_field(total_size, 1, this->device_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTextResponse::dump_to(std::string &out) const { @@ -9318,6 +9340,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(this->entity_category), false); + ProtoSize::add_uint32_field(total_size, 1, this->device_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesDateResponse::dump_to(std::string &out) const { @@ -9569,6 +9592,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(this->entity_category), false); + ProtoSize::add_uint32_field(total_size, 1, this->device_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTimeResponse::dump_to(std::string &out) const { @@ -9838,6 +9862,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_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesEventResponse::dump_to(std::string &out) const { @@ -10024,6 +10049,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_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesValveResponse::dump_to(std::string &out) const { @@ -10267,6 +10293,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(this->entity_category), false); + ProtoSize::add_uint32_field(total_size, 1, this->device_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesDateTimeResponse::dump_to(std::string &out) const { @@ -10474,6 +10501,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(this->entity_category), false); ProtoSize::add_string_field(total_size, 1, this->device_class, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_uid, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesUpdateResponse::dump_to(std::string &out) const { diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index e78ba6b4ba..114f2c1604 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -364,6 +364,7 @@ class SubDeviceInfo : public ProtoMessage { std::string name{}; std::string suggested_area{}; 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 From f4a9221232ee270388bb7f5116344d10c3aae4f1 Mon Sep 17 00:00:00 2001 From: Daniel Vikstrom Date: Mon, 2 Jun 2025 08:31:06 +0200 Subject: [PATCH 32/99] Change hash method --- esphome/core/config.py | 11 ++++++++++- esphome/cpp_helpers.py | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index f3d8b7e715..d27ec1d6bf 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -340,6 +340,15 @@ async def _add_automations(config): await automation.build_automation(trigger, [], conf) +def fnv1a_32bit_hash(string: str) -> int: + """FNV-1a 32-bit hash function.""" + hash_value = 2166136261 + for char in string: + hash_value ^= ord(char) + hash_value = (hash_value * 16777619) & 0xFFFFFFFF + return hash_value + + @coroutine_with_priority(100.0) async def to_code(config): cg.add_global(cg.global_ns.namespace("esphome").using) @@ -420,7 +429,7 @@ async def to_code(config): if config[CONF_SUB_DEVICES]: for dev_conf in config[CONF_SUB_DEVICES]: dev = cg.new_Pvariable(dev_conf[CONF_ID]) - cg.add(dev.set_uid(hash(str(dev_conf[CONF_ID])) % 0xFFFFFFFF)) + cg.add(dev.set_uid(fnv1a_32bit_hash(str(dev_conf[CONF_ID])))) cg.add(dev.set_name(dev_conf[CONF_NAME])) cg.add(dev.set_area(dev_conf[CONF_AREA])) cg.add(cg.App.register_sub_device(dev)) diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index f63d9fcb54..7a8ad060e4 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -13,7 +13,7 @@ from esphome.const import ( CONF_UPDATE_INTERVAL, KEY_PAST_SAFE_MODE, ) -from esphome.core import CORE, ID, coroutine +from esphome.core import CORE, ID, coroutine, fnv1a_32bit_hash from esphome.coroutine import FakeAwaitable from esphome.cpp_generator import add, get_variable from esphome.cpp_types import App @@ -113,7 +113,7 @@ async def setup_entity(var, config): add(var.set_entity_category(config[CONF_ENTITY_CATEGORY])) if CONF_DEVICE_ID in config: device = await get_variable(config[CONF_DEVICE_ID]) - add(var.set_device_uid(hash(str(device)) % 0xFFFFFFFF)) + add(var.set_device_uid(fnv1a_32bit_hash(str(device)))) def extract_registry_entry_config( From 57f4067fbf425c4a456b928e2ee97cc5d28e4a6b Mon Sep 17 00:00:00 2001 From: Daniel Vikstrom Date: Mon, 2 Jun 2025 14:42:39 +0200 Subject: [PATCH 33/99] Move fnv1a_32bit_hash to helpers --- esphome/core/config.py | 16 ++++++---------- esphome/cpp_helpers.py | 4 ++-- esphome/helpers.py | 9 +++++++++ 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index d27ec1d6bf..22bb7b0472 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -34,7 +34,12 @@ 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, +) _LOGGER = logging.getLogger(__name__) @@ -340,15 +345,6 @@ async def _add_automations(config): await automation.build_automation(trigger, [], conf) -def fnv1a_32bit_hash(string: str) -> int: - """FNV-1a 32-bit hash function.""" - hash_value = 2166136261 - for char in string: - hash_value ^= ord(char) - hash_value = (hash_value * 16777619) & 0xFFFFFFFF - return hash_value - - @coroutine_with_priority(100.0) async def to_code(config): cg.add_global(cg.global_ns.namespace("esphome").using) diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 7a8ad060e4..66ff58f4a7 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -13,11 +13,11 @@ from esphome.const import ( CONF_UPDATE_INTERVAL, KEY_PAST_SAFE_MODE, ) -from esphome.core import CORE, ID, coroutine, fnv1a_32bit_hash +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.helpers import fnv1a_32bit_hash, sanitize, snake_case from esphome.types import ConfigFragmentType, ConfigType from esphome.util import Registry, RegistryEntry diff --git a/esphome/helpers.py b/esphome/helpers.py index d95546ac94..242c05e892 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -29,6 +29,15 @@ def ensure_unique_string(preferred_string, current_strings): return test_string +def fnv1a_32bit_hash(string: str) -> int: + """FNV-1a 32-bit hash function.""" + hash_value = 2166136261 + for char in string: + hash_value ^= ord(char) + hash_value = (hash_value * 16777619) & 0xFFFFFFFF + return hash_value + + def indent_all_but_first_and_last(text, padding=" "): lines = text.splitlines(True) if len(lines) <= 2: From bf9e901ab97da460a12cfc54bd94c99a0885a02b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 13:13:44 +0200 Subject: [PATCH 34/99] cleanups to address review comments --- esphome/components/api/api.proto | 56 ++-- esphome/components/api/api_connection.cpp | 32 +-- esphome/components/api/api_connection.h | 3 + esphome/components/api/api_pb2.cpp | 304 +++++++++++++--------- esphome/components/api/api_pb2.h | 65 +++-- esphome/const.py | 2 + esphome/core/application.h | 15 +- esphome/core/config.py | 36 ++- esphome/core/entity_base.h | 10 +- esphome/core/sub_area.h | 20 ++ esphome/core/sub_device.h | 12 +- esphome/cpp_helpers.py | 2 +- tests/components/esphome/common.yaml | 5 +- 13 files changed, 340 insertions(+), 222 deletions(-) create mode 100644 esphome/core/sub_area.h diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 9603694ae8..850ca4a575 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -188,10 +188,15 @@ message DeviceInfoRequest { // Empty } -message SubDeviceInfo { - uint32 uid = 1; +message SubAreaInfo { + uint32 area_id = 1; string name = 2; - string suggested_area = 3; +} + +message SubDeviceInfo { + uint32 device_id = 1; + string name = 2; + uint32 area_id = 3; } message DeviceInfoResponse { @@ -244,6 +249,7 @@ message DeviceInfoResponse { bool api_encryption_supported = 19; repeated SubDeviceInfo sub_devices = 20; + repeated SubAreaInfo sub_areas = 21; } message ListEntitiesRequest { @@ -288,7 +294,7 @@ message ListEntitiesBinarySensorResponse { bool disabled_by_default = 7; string icon = 8; EntityCategory entity_category = 9; - uint32 device_uid = 10; + uint32 device_id = 10; } message BinarySensorStateResponse { option (id) = 21; @@ -324,7 +330,7 @@ message ListEntitiesCoverResponse { string icon = 10; EntityCategory entity_category = 11; bool supports_stop = 12; - uint32 device_uid = 13; + uint32 device_id = 13; } enum LegacyCoverState { @@ -398,7 +404,7 @@ message ListEntitiesFanResponse { string icon = 10; EntityCategory entity_category = 11; repeated string supported_preset_modes = 12; - uint32 device_uid = 13; + uint32 device_id = 13; } enum FanSpeed { FAN_SPEED_LOW = 0; @@ -482,7 +488,7 @@ message ListEntitiesLightResponse { bool disabled_by_default = 13; string icon = 14; EntityCategory entity_category = 15; - uint32 device_uid = 16; + uint32 device_id = 16; } message LightStateResponse { option (id) = 24; @@ -575,7 +581,7 @@ message ListEntitiesSensorResponse { SensorLastResetType legacy_last_reset_type = 11; bool disabled_by_default = 12; EntityCategory entity_category = 13; - uint32 device_uid = 14; + uint32 device_id = 14; } message SensorStateResponse { option (id) = 25; @@ -608,7 +614,7 @@ message ListEntitiesSwitchResponse { bool disabled_by_default = 7; EntityCategory entity_category = 8; string device_class = 9; - uint32 device_uid = 10; + uint32 device_id = 10; } message SwitchStateResponse { option (id) = 26; @@ -646,7 +652,7 @@ message ListEntitiesTextSensorResponse { bool disabled_by_default = 6; EntityCategory entity_category = 7; string device_class = 8; - uint32 device_uid = 9; + uint32 device_id = 9; } message TextSensorStateResponse { option (id) = 27; @@ -829,7 +835,7 @@ message ListEntitiesCameraResponse { bool disabled_by_default = 5; string icon = 6; EntityCategory entity_category = 7; - uint32 device_uid = 8; + uint32 device_id = 8; } message CameraImageResponse { @@ -932,7 +938,7 @@ message ListEntitiesClimateResponse { bool supports_target_humidity = 23; float visual_min_humidity = 24; float visual_max_humidity = 25; - uint32 device_uid = 26; + uint32 device_id = 26; } message ClimateStateResponse { option (id) = 47; @@ -1016,7 +1022,7 @@ message ListEntitiesNumberResponse { string unit_of_measurement = 11; NumberMode mode = 12; string device_class = 13; - uint32 device_uid = 14; + uint32 device_id = 14; } message NumberStateResponse { option (id) = 50; @@ -1057,7 +1063,7 @@ message ListEntitiesSelectResponse { repeated string options = 6; bool disabled_by_default = 7; EntityCategory entity_category = 8; - uint32 device_uid = 9; + uint32 device_id = 9; } message SelectStateResponse { option (id) = 53; @@ -1163,7 +1169,7 @@ message ListEntitiesLockResponse { // Not yet implemented: string code_format = 11; - uint32 device_uid = 12; + uint32 device_id = 12; } message LockStateResponse { option (id) = 59; @@ -1203,7 +1209,7 @@ message ListEntitiesButtonResponse { bool disabled_by_default = 6; EntityCategory entity_category = 7; string device_class = 8; - uint32 device_uid = 9; + uint32 device_id = 9; } message ButtonCommandRequest { option (id) = 62; @@ -1260,7 +1266,7 @@ message ListEntitiesMediaPlayerResponse { repeated MediaPlayerSupportedFormat supported_formats = 9; - uint32 device_uid = 10; + uint32 device_id = 10; } message MediaPlayerStateResponse { option (id) = 64; @@ -1801,7 +1807,7 @@ message ListEntitiesAlarmControlPanelResponse { uint32 supported_features = 8; bool requires_code = 9; bool requires_code_to_arm = 10; - uint32 device_uid = 11; + uint32 device_id = 11; } message AlarmControlPanelStateResponse { @@ -1847,7 +1853,7 @@ message ListEntitiesTextResponse { uint32 max_length = 9; string pattern = 10; TextMode mode = 11; - uint32 device_uid = 12; + uint32 device_id = 12; } message TextStateResponse { option (id) = 98; @@ -1888,7 +1894,7 @@ message ListEntitiesDateResponse { string icon = 5; bool disabled_by_default = 6; EntityCategory entity_category = 7; - uint32 device_uid = 8; + uint32 device_id = 8; } message DateStateResponse { option (id) = 101; @@ -1932,7 +1938,7 @@ message ListEntitiesTimeResponse { string icon = 5; bool disabled_by_default = 6; EntityCategory entity_category = 7; - uint32 device_uid = 8; + uint32 device_id = 8; } message TimeStateResponse { option (id) = 104; @@ -1979,7 +1985,7 @@ message ListEntitiesEventResponse { string device_class = 8; repeated string event_types = 9; - uint32 device_uid = 10; + uint32 device_id = 10; } message EventResponse { option (id) = 108; @@ -2011,7 +2017,7 @@ message ListEntitiesValveResponse { bool assumed_state = 9; bool supports_position = 10; bool supports_stop = 11; - uint32 device_uid = 12; + uint32 device_id = 12; } enum ValveOperation { @@ -2058,7 +2064,7 @@ message ListEntitiesDateTimeResponse { string icon = 5; bool disabled_by_default = 6; EntityCategory entity_category = 7; - uint32 device_uid = 8; + uint32 device_id = 8; } message DateTimeStateResponse { option (id) = 113; @@ -2099,7 +2105,7 @@ message ListEntitiesUpdateResponse { bool disabled_by_default = 6; EntityCategory entity_category = 7; string device_class = 8; - uint32 device_uid = 9; + uint32 device_id = 9; } message UpdateStateResponse { option (id) = 117; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 0288419405..2e2e4ec003 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -311,7 +311,6 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne ListEntitiesBinarySensorResponse msg; msg.device_class = binary_sensor->get_device_class(); msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor(); - msg.device_uid = binary_sensor->get_device_uid(); msg.unique_id = get_default_unique_id("binary_sensor", binary_sensor); fill_entity_info_base(binary_sensor, msg); return encode_message_to_buffer(msg, ListEntitiesBinarySensorResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -349,7 +348,6 @@ uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *c msg.supports_tilt = traits.get_supports_tilt(); msg.supports_stop = traits.get_supports_stop(); msg.device_class = cover->get_device_class(); - msg.device_uid = cover->get_device_uid(); msg.unique_id = get_default_unique_id("cover", cover); fill_entity_info_base(cover, msg); return encode_message_to_buffer(msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -419,7 +417,6 @@ uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *con msg.supported_speed_count = traits.supported_speed_count(); for (auto const &preset : traits.supported_preset_modes()) msg.supported_preset_modes.push_back(preset); - msg.device_uid = fan->get_device_uid(); msg.unique_id = get_default_unique_id("fan", fan); fill_entity_info_base(fan, msg); return encode_message_to_buffer(msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -500,7 +497,6 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c msg.effects.push_back(effect->get_name()); } } - msg.device_uid = light->get_device_uid(); msg.unique_id = get_default_unique_id("light", light); fill_entity_info_base(light, msg); return encode_message_to_buffer(msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -569,7 +565,6 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection * msg.force_update = sensor->get_force_update(); msg.device_class = sensor->get_device_class(); msg.state_class = static_cast(sensor->get_state_class()); - msg.device_uid = sensor->get_device_uid(); msg.unique_id = sensor->unique_id(); if (msg.unique_id.empty()) msg.unique_id = get_default_unique_id("sensor", sensor); @@ -601,7 +596,6 @@ uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection * ListEntitiesSwitchResponse msg; msg.assumed_state = a_switch->assumed_state(); msg.device_class = a_switch->get_device_class(); - msg.device_uid = a_switch->get_device_uid(); msg.unique_id = get_default_unique_id("switch", a_switch); fill_entity_info_base(a_switch, msg); return encode_message_to_buffer(msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -644,7 +638,6 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect ListEntitiesTextSensorResponse msg; msg.device_class = text_sensor->get_device_class(); msg.unique_id = text_sensor->unique_id(); - msg.device_uid = text_sensor->get_device_uid(); if (msg.unique_id.empty()) msg.unique_id = get_default_unique_id("text_sensor", text_sensor); fill_entity_info_base(text_sensor, msg); @@ -721,7 +714,6 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection msg.supported_custom_presets.push_back(custom_preset); for (auto swing_mode : traits.get_supported_swing_modes()) msg.supported_swing_modes.push_back(static_cast(swing_mode)); - msg.device_uid = climate->get_device_uid(); msg.unique_id = get_default_unique_id("climate", climate); fill_entity_info_base(climate, msg); return encode_message_to_buffer(msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -784,7 +776,6 @@ uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection * msg.min_value = number->traits.get_min_value(); msg.max_value = number->traits.get_max_value(); msg.step = number->traits.get_step(); - msg.device_uid = number->get_device_uid(); msg.unique_id = get_default_unique_id("number", number); fill_entity_info_base(number, msg); return encode_message_to_buffer(msg, ListEntitiesNumberResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -822,7 +813,6 @@ uint16_t APIConnection::try_send_date_info(EntityBase *entity, APIConnection *co bool is_single) { auto *date = static_cast(entity); ListEntitiesDateResponse msg; - msg.device_uid = date->get_device_uid(); msg.unique_id = get_default_unique_id("date", date); fill_entity_info_base(date, msg); return encode_message_to_buffer(msg, ListEntitiesDateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -860,7 +850,6 @@ uint16_t APIConnection::try_send_time_info(EntityBase *entity, APIConnection *co bool is_single) { auto *time = static_cast(entity); ListEntitiesTimeResponse msg; - msg.device_uid = time->get_device_uid(); msg.unique_id = get_default_unique_id("time", time); fill_entity_info_base(time, msg); return encode_message_to_buffer(msg, ListEntitiesTimeResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -900,7 +889,6 @@ uint16_t APIConnection::try_send_datetime_info(EntityBase *entity, APIConnection bool is_single) { auto *datetime = static_cast(entity); ListEntitiesDateTimeResponse msg; - msg.device_uid = datetime->get_device_uid(); msg.unique_id = get_default_unique_id("datetime", datetime); fill_entity_info_base(datetime, msg); return encode_message_to_buffer(msg, ListEntitiesDateTimeResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -942,7 +930,6 @@ uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *co msg.min_length = text->traits.get_min_length(); msg.max_length = text->traits.get_max_length(); msg.pattern = text->traits.get_pattern(); - msg.device_uid = text->get_device_uid(); msg.unique_id = get_default_unique_id("text", text); fill_entity_info_base(text, msg); return encode_message_to_buffer(msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -982,7 +969,6 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection * ListEntitiesSelectResponse msg; for (const auto &option : select->traits.get_options()) msg.options.push_back(option); - msg.device_uid = select->get_device_uid(); msg.unique_id = get_default_unique_id("select", select); fill_entity_info_base(select, msg); return encode_message_to_buffer(msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -1007,7 +993,6 @@ uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection * auto *button = static_cast(entity); ListEntitiesButtonResponse msg; msg.device_class = button->get_device_class(); - msg.device_uid = button->get_device_uid(); msg.unique_id = get_default_unique_id("button", button); fill_entity_info_base(button, msg); return encode_message_to_buffer(msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -1045,7 +1030,6 @@ uint16_t APIConnection::try_send_lock_info(EntityBase *entity, APIConnection *co msg.assumed_state = a_lock->traits.get_assumed_state(); msg.supports_open = a_lock->traits.get_supports_open(); msg.requires_code = a_lock->traits.get_requires_code(); - msg.device_uid = a_lock->get_device_uid(); msg.unique_id = get_default_unique_id("lock", a_lock); fill_entity_info_base(a_lock, msg); return encode_message_to_buffer(msg, ListEntitiesLockResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -1094,7 +1078,6 @@ uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *c msg.assumed_state = traits.get_is_assumed_state(); msg.supports_position = traits.get_supports_position(); msg.supports_stop = traits.get_supports_stop(); - msg.device_uid = valve->get_device_uid(); msg.unique_id = get_default_unique_id("valve", valve); fill_entity_info_base(valve, msg); return encode_message_to_buffer(msg, ListEntitiesValveResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -1150,7 +1133,6 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec media_format.sample_bytes = supported_format.sample_bytes; msg.supported_formats.push_back(media_format); } - msg.device_uid = media_player->get_device_uid(); msg.unique_id = get_default_unique_id("media_player", media_player); fill_entity_info_base(media_player, msg); return encode_message_to_buffer(msg, ListEntitiesMediaPlayerResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -1194,7 +1176,6 @@ uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection * bool is_single) { auto *camera = static_cast(entity); ListEntitiesCameraResponse msg; - msg.device_uid = camera->get_device_uid(); msg.unique_id = get_default_unique_id("camera", camera); fill_entity_info_base(camera, msg); return encode_message_to_buffer(msg, ListEntitiesCameraResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -1408,7 +1389,6 @@ uint16_t APIConnection::try_send_alarm_control_panel_info(EntityBase *entity, AP msg.supported_features = a_alarm_control_panel->get_supported_features(); msg.requires_code = a_alarm_control_panel->get_requires_code(); msg.requires_code_to_arm = a_alarm_control_panel->get_requires_code_to_arm(); - msg.device_uid = a_alarm_control_panel->get_device_uid(); msg.unique_id = get_default_unique_id("alarm_control_panel", a_alarm_control_panel); fill_entity_info_base(a_alarm_control_panel, msg); return encode_message_to_buffer(msg, ListEntitiesAlarmControlPanelResponse::MESSAGE_TYPE, conn, remaining_size, @@ -1470,7 +1450,6 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c msg.device_class = event->get_device_class(); for (const auto &event_type : event->get_event_types()) msg.event_types.push_back(event_type); - msg.device_uid = event->get_device_uid(); msg.unique_id = get_default_unique_id("event", event); fill_entity_info_base(event, msg); return encode_message_to_buffer(msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -1509,7 +1488,6 @@ uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection * auto *update = static_cast(entity); ListEntitiesUpdateResponse msg; msg.device_class = update->get_device_class(); - msg.device_uid = update->get_device_uid(); msg.unique_id = get_default_unique_id("update", update); fill_entity_info_base(update, msg); return encode_message_to_buffer(msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -1645,11 +1623,17 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { #ifdef USE_SUB_DEVICE for (auto const &sub_device : App.get_sub_devices()) { SubDeviceInfo sub_device_info; - sub_device_info.uid = sub_device->get_uid(); + sub_device_info.device_id = sub_device->get_device_id(); sub_device_info.name = sub_device->get_name(); - sub_device_info.suggested_area = sub_device->get_area(); + sub_device_info.area_id = sub_device->get_area_id(); resp.sub_devices.push_back(sub_device_info); } + for (auto const &area : App.get_areas()) { + SubAreaInfo area_info; + area_info.area_id = area->get_area_id(); + area_info.name = area->get_name(); + resp.sub_areas.push_back(area_info); + } #endif return resp; } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 66b7ce38a7..9166dbbc94 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -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(entity->get_entity_category()); +#ifdef USE_SUB_DEVICE + response.device_id = entity->get_device_id(); +#endif } // Helper function to fill common entity state fields diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 682778a881..baa78f4358 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -812,10 +812,57 @@ 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 SubAreaInfo::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 SubAreaInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 2: { + this->name = value.as_string(); + return true; + } + default: + return false; + } +} +void SubAreaInfo::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint32(1, this->area_id); + buffer.encode_string(2, this->name); +} +void SubAreaInfo::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 SubAreaInfo::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("SubAreaInfo {\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 SubDeviceInfo::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { - this->uid = value.as_uint32(); + this->device_id = value.as_uint32(); + return true; + } + case 3: { + this->area_id = value.as_uint32(); return true; } default: @@ -828,30 +875,26 @@ bool SubDeviceInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) this->name = value.as_string(); return true; } - case 3: { - this->suggested_area = value.as_string(); - return true; - } default: return false; } } void SubDeviceInfo::encode(ProtoWriteBuffer buffer) const { - buffer.encode_uint32(1, this->uid); + buffer.encode_uint32(1, this->device_id); buffer.encode_string(2, this->name); - buffer.encode_string(3, this->suggested_area); + buffer.encode_uint32(3, this->area_id); } void SubDeviceInfo::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint32_field(total_size, 1, this->uid, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_string_field(total_size, 1, this->suggested_area, false); + ProtoSize::add_uint32_field(total_size, 1, this->area_id, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void SubDeviceInfo::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("SubDeviceInfo {\n"); - out.append(" uid: "); - sprintf(buffer, "%" PRIu32, this->uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); @@ -859,8 +902,9 @@ void SubDeviceInfo::dump_to(std::string &out) const { out.append("'").append(this->name).append("'"); out.append("\n"); - out.append(" suggested_area: "); - out.append("'").append(this->suggested_area).append("'"); + out.append(" area_id: "); + sprintf(buffer, "%" PRIu32, this->area_id); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -953,6 +997,10 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v this->sub_devices.push_back(value.as_message()); return true; } + case 21: { + this->sub_areas.push_back(value.as_message()); + return true; + } default: return false; } @@ -980,6 +1028,9 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->sub_devices) { buffer.encode_message(20, it, true); } + for (auto &it : this->sub_areas) { + buffer.encode_message(21, it, true); + } } void DeviceInfoResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_bool_field(total_size, 1, this->uses_password, false); @@ -1002,6 +1053,7 @@ void DeviceInfoResponse::calculate_size(uint32_t &total_size) const { 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->sub_devices); + ProtoSize::add_repeated_message(total_size, 2, this->sub_areas); } #ifdef HAS_PROTO_MESSAGE_DUMP void DeviceInfoResponse::dump_to(std::string &out) const { @@ -1093,6 +1145,12 @@ void DeviceInfoResponse::dump_to(std::string &out) const { it.dump_to(out); out.append("\n"); } + + for (const auto &it : this->sub_areas) { + out.append(" sub_areas: "); + it.dump_to(out); + out.append("\n"); + } out.append("}"); } #endif @@ -1120,7 +1178,7 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar return true; } case 10: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -1173,7 +1231,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(7, this->disabled_by_default); buffer.encode_string(8, this->icon); buffer.encode_enum(9, this->entity_category); - buffer.encode_uint32(10, this->device_uid); + 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); @@ -1185,7 +1243,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(this->entity_category), false); - ProtoSize::add_uint32_field(total_size, 1, this->device_uid, 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 { @@ -1228,8 +1286,8 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -1315,7 +1373,7 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val return true; } case 13: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -1371,7 +1429,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(10, this->icon); buffer.encode_enum(11, this->entity_category); buffer.encode_bool(12, this->supports_stop); - buffer.encode_uint32(13, this->device_uid); + 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); @@ -1386,7 +1444,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(this->entity_category), false); ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_uid, 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 { @@ -1441,8 +1499,8 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { out.append(YESNO(this->supports_stop)); out.append("\n"); - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -1655,7 +1713,7 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value return true; } case 13: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -1713,7 +1771,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_uid); + 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); @@ -1732,7 +1790,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_uid, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesFanResponse::dump_to(std::string &out) const { @@ -1790,8 +1848,8 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { out.append("\n"); } - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -2088,7 +2146,7 @@ bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt val return true; } case 16: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -2159,7 +2217,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(13, this->disabled_by_default); buffer.encode_string(14, this->icon); buffer.encode_enum(15, this->entity_category); - buffer.encode_uint32(16, this->device_uid); + 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); @@ -2185,7 +2243,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(this->entity_category), false); - ProtoSize::add_uint32_field(total_size, 2, this->device_uid, 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 { @@ -2258,8 +2316,8 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -2770,7 +2828,7 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va return true; } case 14: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -2831,7 +2889,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_enum(11, this->legacy_last_reset_type); buffer.encode_bool(12, this->disabled_by_default); buffer.encode_enum(13, this->entity_category); - buffer.encode_uint32(14, this->device_uid); + 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); @@ -2847,7 +2905,7 @@ void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_enum_field(total_size, 1, static_cast(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(this->entity_category), false); - ProtoSize::add_uint32_field(total_size, 1, this->device_uid, 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 { @@ -2907,8 +2965,8 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -2983,7 +3041,7 @@ bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt va return true; } case 10: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -3036,7 +3094,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(7, this->disabled_by_default); buffer.encode_enum(8, this->entity_category); buffer.encode_string(9, this->device_class); - buffer.encode_uint32(10, this->device_uid); + 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); @@ -3048,7 +3106,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(this->entity_category), false); ProtoSize::add_string_field(total_size, 1, this->device_class, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_uid, 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 { @@ -3091,8 +3149,8 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const { out.append("'").append(this->device_class).append("'"); out.append("\n"); - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -3195,7 +3253,7 @@ bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarIn return true; } case 9: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -3247,7 +3305,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); buffer.encode_string(8, this->device_class); - buffer.encode_uint32(9, this->device_uid); + 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); @@ -3258,7 +3316,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(this->entity_category), false); ProtoSize::add_string_field(total_size, 1, this->device_class, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_uid, 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 { @@ -3297,8 +3355,8 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { out.append("'").append(this->device_class).append("'"); out.append("\n"); - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -4067,7 +4125,7 @@ bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt va return true; } case 8: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -4114,7 +4172,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(5, this->disabled_by_default); buffer.encode_string(6, this->icon); buffer.encode_enum(7, this->entity_category); - buffer.encode_uint32(8, this->device_uid); + 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); @@ -4124,7 +4182,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(this->entity_category), false); - ProtoSize::add_uint32_field(total_size, 1, this->device_uid, 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 { @@ -4159,8 +4217,8 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -4312,7 +4370,7 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v return true; } case 26: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -4421,7 +4479,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_uid); + 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); @@ -4473,7 +4531,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_uid, 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 { @@ -4598,8 +4656,8 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -5068,7 +5126,7 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va return true; } case 14: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -5141,7 +5199,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(11, this->unit_of_measurement); buffer.encode_enum(12, this->mode); buffer.encode_string(13, this->device_class); - buffer.encode_uint32(14, this->device_uid); + 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); @@ -5157,7 +5215,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(this->mode), false); ProtoSize::add_string_field(total_size, 1, this->device_class, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_uid, 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 { @@ -5219,8 +5277,8 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { out.append("'").append(this->device_class).append("'"); out.append("\n"); - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -5329,7 +5387,7 @@ bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt va return true; } case 9: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -5383,7 +5441,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { } buffer.encode_bool(7, this->disabled_by_default); buffer.encode_enum(8, this->entity_category); - buffer.encode_uint32(9, this->device_uid); + 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); @@ -5398,7 +5456,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(this->entity_category), false); - ProtoSize::add_uint32_field(total_size, 1, this->device_uid, 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 { @@ -5439,8 +5497,8 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -5872,7 +5930,7 @@ bool ListEntitiesLockResponse::decode_varint(uint32_t field_id, ProtoVarInt valu return true; } case 12: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -5927,7 +5985,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_uid); + 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); @@ -5941,7 +5999,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_uid, 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 { @@ -5992,8 +6050,8 @@ void ListEntitiesLockResponse::dump_to(std::string &out) const { out.append("'").append(this->code_format).append("'"); out.append("\n"); - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -6122,7 +6180,7 @@ bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt va return true; } case 9: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -6174,7 +6232,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); buffer.encode_string(8, this->device_class); - buffer.encode_uint32(9, this->device_uid); + 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); @@ -6185,7 +6243,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(this->entity_category), false); ProtoSize::add_string_field(total_size, 1, this->device_class, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_uid, 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 { @@ -6224,8 +6282,8 @@ void ListEntitiesButtonResponse::dump_to(std::string &out) const { out.append("'").append(this->device_class).append("'"); out.append("\n"); - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -6346,7 +6404,7 @@ bool ListEntitiesMediaPlayerResponse::decode_varint(uint32_t field_id, ProtoVarI return true; } case 10: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -6401,7 +6459,7 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->supported_formats) { buffer.encode_message(9, it, true); } - buffer.encode_uint32(10, this->device_uid); + 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); @@ -6413,7 +6471,7 @@ void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const ProtoSize::add_enum_field(total_size, 1, static_cast(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_uid, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { @@ -6458,8 +6516,8 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { out.append("\n"); } - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -8773,7 +8831,7 @@ bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, Pro return true; } case 11: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -8823,7 +8881,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_uid); + 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); @@ -8836,7 +8894,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_uid, 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 { @@ -8884,8 +8942,8 @@ void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { out.append(YESNO(this->requires_code_to_arm)); out.append("\n"); - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -9016,7 +9074,7 @@ bool ListEntitiesTextResponse::decode_varint(uint32_t field_id, ProtoVarInt valu return true; } case 12: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -9071,7 +9129,7 @@ void ListEntitiesTextResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(9, this->max_length); buffer.encode_string(10, this->pattern); buffer.encode_enum(11, this->mode); - buffer.encode_uint32(12, this->device_uid); + 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); @@ -9085,7 +9143,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(this->mode), false); - ProtoSize::add_uint32_field(total_size, 1, this->device_uid, 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 { @@ -9138,8 +9196,8 @@ void ListEntitiesTextResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->mode)); out.append("\n"); - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -9258,7 +9316,7 @@ bool ListEntitiesDateResponse::decode_varint(uint32_t field_id, ProtoVarInt valu return true; } case 8: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -9305,7 +9363,7 @@ void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); - buffer.encode_uint32(8, this->device_uid); + 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); @@ -9315,7 +9373,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(this->entity_category), false); - ProtoSize::add_uint32_field(total_size, 1, this->device_uid, 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 { @@ -9350,8 +9408,8 @@ void ListEntitiesDateResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -9510,7 +9568,7 @@ bool ListEntitiesTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt valu return true; } case 8: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -9557,7 +9615,7 @@ void ListEntitiesTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); - buffer.encode_uint32(8, this->device_uid); + 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); @@ -9567,7 +9625,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(this->entity_category), false); - ProtoSize::add_uint32_field(total_size, 1, this->device_uid, 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 { @@ -9602,8 +9660,8 @@ void ListEntitiesTimeResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -9762,7 +9820,7 @@ bool ListEntitiesEventResponse::decode_varint(uint32_t field_id, ProtoVarInt val return true; } case 10: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -9821,7 +9879,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_uid); + 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); @@ -9837,7 +9895,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_uid, false); + ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesEventResponse::dump_to(std::string &out) const { @@ -9882,8 +9940,8 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const { out.append("\n"); } - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -9955,7 +10013,7 @@ bool ListEntitiesValveResponse::decode_varint(uint32_t field_id, ProtoVarInt val return true; } case 12: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -10010,7 +10068,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_uid); + 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); @@ -10024,7 +10082,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_uid, 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 { @@ -10075,8 +10133,8 @@ void ListEntitiesValveResponse::dump_to(std::string &out) const { out.append(YESNO(this->supports_stop)); out.append("\n"); - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -10211,7 +10269,7 @@ bool ListEntitiesDateTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt return true; } case 8: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -10258,7 +10316,7 @@ void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); - buffer.encode_uint32(8, this->device_uid); + 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); @@ -10268,7 +10326,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(this->entity_category), false); - ProtoSize::add_uint32_field(total_size, 1, this->device_uid, 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 { @@ -10303,8 +10361,8 @@ void ListEntitiesDateTimeResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -10413,7 +10471,7 @@ bool ListEntitiesUpdateResponse::decode_varint(uint32_t field_id, ProtoVarInt va return true; } case 9: { - this->device_uid = value.as_uint32(); + this->device_id = value.as_uint32(); return true; } default: @@ -10465,7 +10523,7 @@ void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->disabled_by_default); buffer.encode_enum(7, this->entity_category); buffer.encode_string(8, this->device_class); - buffer.encode_uint32(9, this->device_uid); + 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); @@ -10476,7 +10534,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(this->entity_category), false); ProtoSize::add_string_field(total_size, 1, this->device_class, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_uid, 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 { @@ -10515,8 +10573,8 @@ void ListEntitiesUpdateResponse::dump_to(std::string &out) const { out.append("'").append(this->device_class).append("'"); out.append("\n"); - out.append(" device_uid: "); - sprintf(buffer, "%" PRIu32, this->device_uid); + out.append(" device_id: "); + sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index ab30c3a593..7dedaa032d 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -415,11 +415,25 @@ class DeviceInfoRequest : public ProtoMessage { protected: }; +class SubAreaInfo : 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 SubDeviceInfo : public ProtoMessage { public: - uint32_t uid{0}; + uint32_t device_id{0}; std::string name{}; - std::string suggested_area{}; + 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 @@ -433,7 +447,7 @@ class SubDeviceInfo : public ProtoMessage { class DeviceInfoResponse : public ProtoMessage { public: static constexpr uint16_t MESSAGE_TYPE = 10; - static constexpr uint16_t ESTIMATED_SIZE = 165; + static constexpr uint16_t ESTIMATED_SIZE = 201; #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "device_info_response"; } #endif @@ -457,6 +471,7 @@ class DeviceInfoResponse : public ProtoMessage { std::string bluetooth_mac_address{}; bool api_encryption_supported{false}; std::vector sub_devices{}; + std::vector sub_areas{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -515,7 +530,7 @@ class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage { #endif std::string device_class{}; bool is_status_binary_sensor{false}; - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -558,7 +573,7 @@ class ListEntitiesCoverResponse : public InfoResponseProtoMessage { bool supports_tilt{false}; std::string device_class{}; bool supports_stop{false}; - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -628,7 +643,7 @@ class ListEntitiesFanResponse : public InfoResponseProtoMessage { bool supports_direction{false}; int32_t supported_speed_count{0}; std::vector supported_preset_modes{}; - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -710,7 +725,7 @@ class ListEntitiesLightResponse : public InfoResponseProtoMessage { float min_mireds{0.0f}; float max_mireds{0.0f}; std::vector effects{}; - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -810,7 +825,7 @@ class ListEntitiesSensorResponse : public InfoResponseProtoMessage { std::string device_class{}; enums::SensorStateClass state_class{}; enums::SensorLastResetType legacy_last_reset_type{}; - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -850,7 +865,7 @@ class ListEntitiesSwitchResponse : public InfoResponseProtoMessage { #endif bool assumed_state{false}; std::string device_class{}; - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -907,7 +922,7 @@ class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage { static constexpr const char *message_name() { return "list_entities_text_sensor_response"; } #endif std::string device_class{}; - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1223,7 +1238,7 @@ class ListEntitiesCameraResponse : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "list_entities_camera_response"; } #endif - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1299,7 +1314,7 @@ class ListEntitiesClimateResponse : public InfoResponseProtoMessage { bool supports_target_humidity{false}; float visual_min_humidity{0.0f}; float visual_max_humidity{0.0f}; - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1397,7 +1412,7 @@ class ListEntitiesNumberResponse : public InfoResponseProtoMessage { std::string unit_of_measurement{}; enums::NumberMode mode{}; std::string device_class{}; - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1454,7 +1469,7 @@ class ListEntitiesSelectResponse : public InfoResponseProtoMessage { static constexpr const char *message_name() { return "list_entities_select_response"; } #endif std::vector options{}; - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1582,7 +1597,7 @@ class ListEntitiesLockResponse : public InfoResponseProtoMessage { bool supports_open{false}; bool requires_code{false}; std::string code_format{}; - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1642,7 +1657,7 @@ class ListEntitiesButtonResponse : public InfoResponseProtoMessage { static constexpr const char *message_name() { return "list_entities_button_response"; } #endif std::string device_class{}; - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1697,7 +1712,7 @@ class ListEntitiesMediaPlayerResponse : public InfoResponseProtoMessage { #endif bool supports_pause{false}; std::vector supported_formats{}; - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2569,7 +2584,7 @@ class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage { uint32_t supported_features{0}; bool requires_code{false}; bool requires_code_to_arm{false}; - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2631,7 +2646,7 @@ class ListEntitiesTextResponse : public InfoResponseProtoMessage { uint32_t max_length{0}; std::string pattern{}; enums::TextMode mode{}; - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2689,7 +2704,7 @@ class ListEntitiesDateResponse : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "list_entities_date_response"; } #endif - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2750,7 +2765,7 @@ class ListEntitiesTimeResponse : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "list_entities_time_response"; } #endif - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2813,7 +2828,7 @@ class ListEntitiesEventResponse : public InfoResponseProtoMessage { #endif std::string device_class{}; std::vector event_types{}; - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2854,7 +2869,7 @@ class ListEntitiesValveResponse : public InfoResponseProtoMessage { bool assumed_state{false}; bool supports_position{false}; bool supports_stop{false}; - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2913,7 +2928,7 @@ class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "list_entities_date_time_response"; } #endif - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2970,7 +2985,7 @@ class ListEntitiesUpdateResponse : public InfoResponseProtoMessage { static constexpr const char *message_name() { return "list_entities_update_response"; } #endif std::string device_class{}; - uint32_t device_uid{0}; + uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP diff --git a/esphome/const.py b/esphome/const.py index 3a5cd2215f..47f20a71cb 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -56,6 +56,7 @@ CONF_AP = "ap" CONF_APPARENT_POWER = "apparent_power" CONF_ARDUINO_VERSION = "arduino_version" CONF_AREA = "area" +CONF_AREA_ID = "area_id" CONF_ARGS = "args" CONF_ASSUMED_STATE = "assumed_state" CONF_AT = "at" @@ -843,6 +844,7 @@ CONF_STILL_THRESHOLD = "still_threshold" CONF_STOP = "stop" CONF_STOP_ACTION = "stop_action" CONF_STORE_BASELINE = "store_baseline" +CONF_SUB_AREAS = "sub_areas" CONF_SUB_DEVICES = "sub_devices" CONF_SUBNET = "subnet" CONF_SUBSCRIBE_QOS = "subscribe_qos" diff --git a/esphome/core/application.h b/esphome/core/application.h index c17fd8ba74..ee1f5db726 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -11,6 +11,7 @@ #ifdef USE_SUB_DEVICE #include "esphome/core/sub_device.h" +#include "esphome/core/sub_area.h" #endif #ifdef USE_SOCKET_SELECT_SUPPORT @@ -114,6 +115,9 @@ class Application { #ifdef USE_SUB_DEVICE void register_sub_device(SubDevice *sub_device) { this->sub_devices_.push_back(sub_device); } #endif +#ifdef USE_SUB_DEVICE + void register_area(SubArea *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_; } @@ -344,15 +348,7 @@ class Application { #ifdef USE_SUB_DEVICE const std::vector &get_sub_devices() { return this->sub_devices_; } - // /* Very likely no need for get_sub_device_by_key as it only seem to be used when requesting update from API - // and the sub_devices shaould only be sent once at connection. */ - // SubDevice *get_sub_device_by_key(uint32_t key, bool include_internal = false) { - // for (auto *obj : this->sub_devices_) { - // if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - // return obj; - // } - // return nullptr; - // } + const std::vector &get_areas() { return this->areas_; } #endif #ifdef USE_BINARY_SENSOR const std::vector &get_binary_sensors() { return this->binary_sensors_; } @@ -632,6 +628,7 @@ class Application { #ifdef USE_SUB_DEVICE std::vector sub_devices_{}; + std::vector areas_{}; #endif #ifdef USE_BINARY_SENSOR std::vector binary_sensors_{}; diff --git a/esphome/core/config.py b/esphome/core/config.py index 484f0dbac0..fbbdf1217a 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -7,6 +7,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import ( CONF_AREA, + CONF_AREA_ID, CONF_BUILD_PATH, CONF_COMMENT, CONF_COMPILE_PROCESS_LIMIT, @@ -27,6 +28,7 @@ from esphome.const import ( CONF_PLATFORMIO_OPTIONS, CONF_PRIORITY, CONF_PROJECT, + CONF_SUB_AREAS, CONF_SUB_DEVICES, CONF_TRIGGER_ID, CONF_VERSION, @@ -56,6 +58,7 @@ ProjectUpdateTrigger = cg.esphome_ns.class_( "ProjectUpdateTrigger", cg.Component, automation.Trigger.template(cg.std_string) ) SubDevice = cg.esphome_ns.class_("SubDevice") +SubArea = cg.esphome_ns.class_("SubArea") VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} @@ -174,12 +177,20 @@ 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_SUB_AREAS, default=[]): cv.ensure_list( + cv.Schema( + { + cv.GenerateID(CONF_ID): cv.declare_id(SubArea), + cv.Required(CONF_NAME): cv.string, + } + ), + ), cv.Optional(CONF_SUB_DEVICES, default=[]): cv.ensure_list( cv.Schema( { cv.GenerateID(CONF_ID): cv.declare_id(SubDevice), cv.Required(CONF_NAME): cv.string, - cv.Optional(CONF_AREA, default=""): cv.string, + cv.Optional(CONF_AREA_ID): cv.use_id(SubArea), } ), ), @@ -434,11 +445,26 @@ async def to_code(config): if config[CONF_PLATFORMIO_OPTIONS]: CORE.add_job(_add_platformio_options, config[CONF_PLATFORMIO_OPTIONS]) - if config[CONF_SUB_DEVICES]: - for dev_conf in config[CONF_SUB_DEVICES]: + # Process sub-devices and areas + if sub_devices := config.get(CONF_SUB_DEVICES): + # Process areas first + if sub_areas := config.get(CONF_SUB_AREAS): + for area_conf in sub_areas: + area = cg.new_Pvariable(area_conf[CONF_ID]) + area_id = fnv1a_32bit_hash(str(area_conf[CONF_ID])) + cg.add(area.set_area_id(area_id)) + cg.add(area.set_name(area_conf[CONF_NAME])) + cg.add(cg.App.register_area(area)) + + # Process sub-devices + for dev_conf in sub_devices: dev = cg.new_Pvariable(dev_conf[CONF_ID]) - cg.add(dev.set_uid(fnv1a_32bit_hash(str(dev_conf[CONF_ID])))) + cg.add(dev.set_device_id(fnv1a_32bit_hash(str(dev_conf[CONF_ID])))) cg.add(dev.set_name(dev_conf[CONF_NAME])) - cg.add(dev.set_area(dev_conf[CONF_AREA])) + if CONF_AREA_ID in dev_conf: + # The area_id in dev_conf is already the ID reference from cv.use_id + # We need to get the hash of that area's ID + area_id = fnv1a_32bit_hash(str(dev_conf[CONF_AREA_ID])) + cg.add(dev.set_area_id(area_id)) cg.add(cg.App.register_sub_device(dev)) cg.add_define("USE_SUB_DEVICE") diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 165ae0e7cd..b21ae196f1 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -51,9 +51,11 @@ class EntityBase { std::string get_icon() const; void set_icon(const char *icon); +#ifdef USE_SUB_DEVICE // Get/set this entity's device id - uint32_t get_device_uid() const { return this->device_uid_; } - void set_device_uid(const uint32_t device_uid) { this->device_uid_ = device_uid; } + uint32_t get_device_id() const { return this->device_id_; } + void set_device_id(const uint32_t device_id) { this->device_id_ = device_id; } +#endif // Check if this entity has state bool has_state() const { return this->flags_.has_state; } @@ -71,7 +73,9 @@ class EntityBase { const char *object_id_c_str_{nullptr}; const char *icon_c_str_{nullptr}; uint32_t object_id_hash_{}; - uint32_t device_uid_{}; +#ifdef USE_SUB_DEVICE + uint32_t device_id_{}; +#endif // Bit-packed flags to save memory (1 byte instead of 5) struct EntityFlags { diff --git a/esphome/core/sub_area.h b/esphome/core/sub_area.h new file mode 100644 index 0000000000..55ea4b4541 --- /dev/null +++ b/esphome/core/sub_area.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +namespace esphome { + +class SubArea { + public: + void set_area_id(uint32_t area_id) { area_id_ = area_id; } + uint32_t get_area_id() { return area_id_; } + void set_name(std::string name) { name_ = std::move(name); } + std::string get_name() { return name_; } + + protected: + uint32_t area_id_{}; + std::string name_ = ""; +}; + +} // namespace esphome \ No newline at end of file diff --git a/esphome/core/sub_device.h b/esphome/core/sub_device.h index 9e7c4d2261..f17f882dfd 100644 --- a/esphome/core/sub_device.h +++ b/esphome/core/sub_device.h @@ -6,17 +6,17 @@ namespace esphome { class SubDevice { public: - void set_uid(uint32_t uid) { uid_ = uid; } - uint32_t get_uid() { return uid_; } + void set_device_id(uint32_t device_id) { device_id_ = device_id; } + uint32_t get_device_id() { return device_id_; } void set_name(std::string name) { name_ = std::move(name); } std::string get_name() { return name_; } - void set_area(std::string area) { area_ = std::move(area); } - std::string get_area() { return area_; } + void set_area_id(uint32_t area_id) { area_id_ = area_id; } + uint32_t get_area_id() { return area_id_; } protected: - uint32_t uid_{}; + uint32_t device_id_{}; + uint32_t area_id_{}; std::string name_ = ""; - std::string area_ = ""; }; } // namespace esphome diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 66ff58f4a7..cef7b31020 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -113,7 +113,7 @@ async def setup_entity(var, config): add(var.set_entity_category(config[CONF_ENTITY_CATEGORY])) if CONF_DEVICE_ID in config: device = await get_variable(config[CONF_DEVICE_ID]) - add(var.set_device_uid(fnv1a_32bit_hash(str(device)))) + add(var.set_device_id(fnv1a_32bit_hash(str(device)))) def extract_registry_entry_config( diff --git a/tests/components/esphome/common.yaml b/tests/components/esphome/common.yaml index 3754390e89..aa1ce9e111 100644 --- a/tests/components/esphome/common.yaml +++ b/tests/components/esphome/common.yaml @@ -17,10 +17,13 @@ esphome: version: "1.1" on_update: logger.log: on_update + sub_areas: + - id: another_area + name: Another area sub_devices: - id: other_device name: Another device - area: Another area + area_id: another_area binary_sensor: - platform: template From 02e922b56f1b49fdd324b7cfb2e1d80225574b62 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 13:16:42 +0200 Subject: [PATCH 35/99] cleanups to address review comments --- esphome/core/sub_area.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/sub_area.h b/esphome/core/sub_area.h index 55ea4b4541..2a70086c1c 100644 --- a/esphome/core/sub_area.h +++ b/esphome/core/sub_area.h @@ -17,4 +17,4 @@ class SubArea { std::string name_ = ""; }; -} // namespace esphome \ No newline at end of file +} // namespace esphome From 8937ed226957429317ea81e7ebf57511fe09d754 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 13:18:25 +0200 Subject: [PATCH 36/99] cleanups to address review comments --- esphome/core/application.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/esphome/core/application.h b/esphome/core/application.h index ee1f5db726..0e3869800f 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -114,8 +114,6 @@ class Application { #ifdef USE_SUB_DEVICE void register_sub_device(SubDevice *sub_device) { this->sub_devices_.push_back(sub_device); } -#endif -#ifdef USE_SUB_DEVICE void register_area(SubArea *area) { this->areas_.push_back(area); } #endif From 153a6440dcb8e470964b11b4c85fe700423b7733 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 13:20:59 +0200 Subject: [PATCH 37/99] cleanups to address review comments --- esphome/core/config.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index fbbdf1217a..2c33ad1df0 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -462,9 +462,10 @@ async def to_code(config): cg.add(dev.set_device_id(fnv1a_32bit_hash(str(dev_conf[CONF_ID])))) cg.add(dev.set_name(dev_conf[CONF_NAME])) if CONF_AREA_ID in dev_conf: - # The area_id in dev_conf is already the ID reference from cv.use_id - # We need to get the hash of that area's ID - area_id = fnv1a_32bit_hash(str(dev_conf[CONF_AREA_ID])) + # The area_id in dev_conf is the ID reference from cv.use_id + # We need to get the same hash that was used when creating the area + area_id_str = str(dev_conf[CONF_AREA_ID].id) + area_id = fnv1a_32bit_hash(area_id_str) cg.add(dev.set_area_id(area_id)) cg.add(cg.App.register_sub_device(dev)) cg.add_define("USE_SUB_DEVICE") From 63de88dd57963dbbc495001634ed16336d5db3b9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 13:33:05 +0200 Subject: [PATCH 38/99] fixes --- esphome/components/api/api_connection.h | 3 --- esphome/core/config.py | 8 +++----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 9166dbbc94..66b7ce38a7 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -301,9 +301,6 @@ class APIConnection : public APIServerConnection { response.icon = entity->get_icon(); response.disabled_by_default = entity->is_disabled_by_default(); response.entity_category = static_cast(entity->get_entity_category()); -#ifdef USE_SUB_DEVICE - response.device_id = entity->get_device_id(); -#endif } // Helper function to fill common entity state fields diff --git a/esphome/core/config.py b/esphome/core/config.py index 2c33ad1df0..76c7505393 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -462,10 +462,8 @@ async def to_code(config): cg.add(dev.set_device_id(fnv1a_32bit_hash(str(dev_conf[CONF_ID])))) cg.add(dev.set_name(dev_conf[CONF_NAME])) if CONF_AREA_ID in dev_conf: - # The area_id in dev_conf is the ID reference from cv.use_id - # We need to get the same hash that was used when creating the area - area_id_str = str(dev_conf[CONF_AREA_ID].id) - area_id = fnv1a_32bit_hash(area_id_str) - cg.add(dev.set_area_id(area_id)) + # Get the area variable and use its area_id + area = await cg.get_variable(dev_conf[CONF_AREA_ID]) + cg.add(dev.set_area_id(area.get_area_id())) cg.add(cg.App.register_sub_device(dev)) cg.add_define("USE_SUB_DEVICE") From 32088d5ef7f1ead6a6e86947bbf57b8dfa72c1cd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 13:35:32 +0200 Subject: [PATCH 39/99] revert --- esphome/components/api/api_connection.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 66b7ce38a7..9166dbbc94 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -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(entity->get_entity_category()); +#ifdef USE_SUB_DEVICE + response.device_id = entity->get_device_id(); +#endif } // Helper function to fill common entity state fields From 86fb0e317f5be639e658f1b9ad02acfdc8c276e5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 15:22:35 +0200 Subject: [PATCH 40/99] fixes --- esphome/components/api/api.proto | 1 + esphome/components/api/api_pb2.cpp | 11 +++++++++++ esphome/components/api/api_pb2.h | 25 ++----------------------- 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 850ca4a575..29e26bc0e5 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1106,6 +1106,7 @@ message ListEntitiesSirenResponse { bool supports_duration = 8; bool supports_volume = 9; EntityCategory entity_category = 10; + uint32 device_id = 11; } message SirenStateResponse { option (id) = 56; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index baa78f4358..501b8bd91d 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -5624,6 +5624,10 @@ bool ListEntitiesSirenResponse::decode_varint(uint32_t field_id, ProtoVarInt val this->entity_category = value.as_enum(); return true; } + case 11: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -5677,6 +5681,7 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(8, this->supports_duration); buffer.encode_bool(9, this->supports_volume); buffer.encode_enum(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); @@ -5693,6 +5698,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(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 { @@ -5740,6 +5746,11 @@ void ListEntitiesSirenResponse::dump_to(std::string &out) const { out.append(" entity_category: "); out.append(proto_enum_to_string(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 diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 7dedaa032d..2e4e32f038 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -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: }; @@ -530,7 +531,6 @@ class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage { #endif std::string device_class{}; bool is_status_binary_sensor{false}; - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -573,7 +573,6 @@ class ListEntitiesCoverResponse : public InfoResponseProtoMessage { bool supports_tilt{false}; std::string device_class{}; bool supports_stop{false}; - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -643,7 +642,6 @@ class ListEntitiesFanResponse : public InfoResponseProtoMessage { bool supports_direction{false}; int32_t supported_speed_count{0}; std::vector supported_preset_modes{}; - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -725,7 +723,6 @@ class ListEntitiesLightResponse : public InfoResponseProtoMessage { float min_mireds{0.0f}; float max_mireds{0.0f}; std::vector effects{}; - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -825,7 +822,6 @@ class ListEntitiesSensorResponse : public InfoResponseProtoMessage { std::string device_class{}; enums::SensorStateClass state_class{}; enums::SensorLastResetType legacy_last_reset_type{}; - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -865,7 +861,6 @@ class ListEntitiesSwitchResponse : public InfoResponseProtoMessage { #endif bool assumed_state{false}; std::string device_class{}; - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -922,7 +917,6 @@ class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage { static constexpr const char *message_name() { return "list_entities_text_sensor_response"; } #endif std::string device_class{}; - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1238,7 +1232,6 @@ class ListEntitiesCameraResponse : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "list_entities_camera_response"; } #endif - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1314,7 +1307,6 @@ class ListEntitiesClimateResponse : public InfoResponseProtoMessage { bool supports_target_humidity{false}; float visual_min_humidity{0.0f}; float visual_max_humidity{0.0f}; - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1412,7 +1404,6 @@ class ListEntitiesNumberResponse : public InfoResponseProtoMessage { std::string unit_of_measurement{}; enums::NumberMode mode{}; std::string device_class{}; - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1469,7 +1460,6 @@ class ListEntitiesSelectResponse : public InfoResponseProtoMessage { static constexpr const char *message_name() { return "list_entities_select_response"; } #endif std::vector options{}; - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1523,7 +1513,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 @@ -1597,7 +1587,6 @@ class ListEntitiesLockResponse : public InfoResponseProtoMessage { bool supports_open{false}; bool requires_code{false}; std::string code_format{}; - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1657,7 +1646,6 @@ class ListEntitiesButtonResponse : public InfoResponseProtoMessage { static constexpr const char *message_name() { return "list_entities_button_response"; } #endif std::string device_class{}; - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1712,7 +1700,6 @@ class ListEntitiesMediaPlayerResponse : public InfoResponseProtoMessage { #endif bool supports_pause{false}; std::vector supported_formats{}; - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2584,7 +2571,6 @@ class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage { uint32_t supported_features{0}; bool requires_code{false}; bool requires_code_to_arm{false}; - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2646,7 +2632,6 @@ class ListEntitiesTextResponse : public InfoResponseProtoMessage { uint32_t max_length{0}; std::string pattern{}; enums::TextMode mode{}; - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2704,7 +2689,6 @@ class ListEntitiesDateResponse : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "list_entities_date_response"; } #endif - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2765,7 +2749,6 @@ class ListEntitiesTimeResponse : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "list_entities_time_response"; } #endif - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2828,7 +2811,6 @@ class ListEntitiesEventResponse : public InfoResponseProtoMessage { #endif std::string device_class{}; std::vector event_types{}; - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2869,7 +2851,6 @@ class ListEntitiesValveResponse : public InfoResponseProtoMessage { bool assumed_state{false}; bool supports_position{false}; bool supports_stop{false}; - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2928,7 +2909,6 @@ class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "list_entities_date_time_response"; } #endif - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2985,7 +2965,6 @@ class ListEntitiesUpdateResponse : public InfoResponseProtoMessage { static constexpr const char *message_name() { return "list_entities_update_response"; } #endif std::string device_class{}; - uint32_t device_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP From 7d84f0e65036098d730dabee54d5728825f9960d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 16:37:21 +0200 Subject: [PATCH 41/99] migrate to using same area info for top level and sub devices --- esphome/components/api/api.proto | 7 ++- esphome/components/api/api_connection.cpp | 6 ++- esphome/components/api/api_pb2.cpp | 34 +++++++++------ esphome/components/api/api_pb2.h | 7 +-- esphome/core/application.h | 31 ++++++++++---- esphome/core/{sub_area.h => area.h} | 2 +- esphome/core/config.py | 52 ++++++++++++++++++++--- esphome/dashboard/util/text.py | 24 ++--------- esphome/helpers.py | 26 ++++++++++++ 9 files changed, 136 insertions(+), 53 deletions(-) rename esphome/core/{sub_area.h => area.h} (96%) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 29e26bc0e5..0ac9cd3aab 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -188,7 +188,7 @@ message DeviceInfoRequest { // Empty } -message SubAreaInfo { +message AreaInfo { uint32 area_id = 1; string name = 2; } @@ -249,7 +249,10 @@ message DeviceInfoResponse { bool api_encryption_supported = 19; repeated SubDeviceInfo sub_devices = 20; - repeated SubAreaInfo sub_areas = 21; + repeated AreaInfo areas = 21; + + // Top-level area info to phase out suggested_area + AreaInfo area = 22; } message ListEntitiesRequest { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 2e2e4ec003..799cd2f102 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1628,11 +1628,13 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { sub_device_info.area_id = sub_device->get_area_id(); resp.sub_devices.push_back(sub_device_info); } +#endif +#ifdef USE_AREAS for (auto const &area : App.get_areas()) { - SubAreaInfo area_info; + AreaInfo area_info; area_info.area_id = area->get_area_id(); area_info.name = area->get_name(); - resp.sub_areas.push_back(area_info); + resp.areas.push_back(area_info); } #endif return resp; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 501b8bd91d..cbe18e172e 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -812,7 +812,7 @@ 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 SubAreaInfo::decode_varint(uint32_t field_id, ProtoVarInt value) { +bool AreaInfo::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { this->area_id = value.as_uint32(); @@ -822,7 +822,7 @@ bool SubAreaInfo::decode_varint(uint32_t field_id, ProtoVarInt value) { return false; } } -bool SubAreaInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) { +bool AreaInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: { this->name = value.as_string(); @@ -832,18 +832,18 @@ bool SubAreaInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) { return false; } } -void SubAreaInfo::encode(ProtoWriteBuffer buffer) const { +void AreaInfo::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, this->area_id); buffer.encode_string(2, this->name); } -void SubAreaInfo::calculate_size(uint32_t &total_size) const { +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 SubAreaInfo::dump_to(std::string &out) const { +void AreaInfo::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; - out.append("SubAreaInfo {\n"); + out.append("AreaInfo {\n"); out.append(" area_id: "); sprintf(buffer, "%" PRIu32, this->area_id); out.append(buffer); @@ -998,7 +998,11 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v return true; } case 21: { - this->sub_areas.push_back(value.as_message()); + this->areas.push_back(value.as_message()); + return true; + } + case 22: { + this->area = value.as_message(); return true; } default: @@ -1028,9 +1032,10 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->sub_devices) { buffer.encode_message(20, it, true); } - for (auto &it : this->sub_areas) { - buffer.encode_message(21, it, true); + for (auto &it : this->areas) { + buffer.encode_message(21, it, true); } + buffer.encode_message(22, this->area); } void DeviceInfoResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_bool_field(total_size, 1, this->uses_password, false); @@ -1053,7 +1058,8 @@ void DeviceInfoResponse::calculate_size(uint32_t &total_size) const { 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->sub_devices); - ProtoSize::add_repeated_message(total_size, 2, this->sub_areas); + 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 { @@ -1146,11 +1152,15 @@ void DeviceInfoResponse::dump_to(std::string &out) const { out.append("\n"); } - for (const auto &it : this->sub_areas) { - out.append(" sub_areas: "); + 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 diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 2e4e32f038..e71fd23619 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -416,7 +416,7 @@ class DeviceInfoRequest : public ProtoMessage { protected: }; -class SubAreaInfo : public ProtoMessage { +class AreaInfo : public ProtoMessage { public: uint32_t area_id{0}; std::string name{}; @@ -448,7 +448,7 @@ class SubDeviceInfo : public ProtoMessage { class DeviceInfoResponse : public ProtoMessage { public: static constexpr uint16_t MESSAGE_TYPE = 10; - static constexpr uint16_t ESTIMATED_SIZE = 201; + static constexpr uint16_t ESTIMATED_SIZE = 219; #ifdef HAS_PROTO_MESSAGE_DUMP static constexpr const char *message_name() { return "device_info_response"; } #endif @@ -472,7 +472,8 @@ class DeviceInfoResponse : public ProtoMessage { std::string bluetooth_mac_address{}; bool api_encryption_supported{false}; std::vector sub_devices{}; - std::vector sub_areas{}; + std::vector areas{}; + AreaInfo area{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP diff --git a/esphome/core/application.h b/esphome/core/application.h index 0e3869800f..09e2cfefbf 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -11,7 +11,9 @@ #ifdef USE_SUB_DEVICE #include "esphome/core/sub_device.h" -#include "esphome/core/sub_area.h" +#endif +#ifdef USE_AREAS +#include "esphome/core/area.h" #endif #ifdef USE_SOCKET_SELECT_SUPPORT @@ -92,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; @@ -107,14 +109,16 @@ class Application { this->name_ = name; this->friendly_name_ = friendly_name; } - this->area_ = area; + // area is now handled through the areas system this->comment_ = comment; this->compilation_time_ = compilation_time; } #ifdef USE_SUB_DEVICE void register_sub_device(SubDevice *sub_device) { this->sub_devices_.push_back(sub_device); } - void register_area(SubArea *area) { this->areas_.push_back(area); } +#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; } @@ -295,7 +299,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_; } + std::string 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_; } @@ -346,7 +358,9 @@ class Application { #ifdef USE_SUB_DEVICE const std::vector &get_sub_devices() { return this->sub_devices_; } - const std::vector &get_areas() { return this->areas_; } +#endif +#ifdef USE_AREAS + const std::vector &get_areas() { return this->areas_; } #endif #ifdef USE_BINARY_SENSOR const std::vector &get_binary_sensors() { return this->binary_sensors_; } @@ -626,7 +640,9 @@ class Application { #ifdef USE_SUB_DEVICE std::vector sub_devices_{}; - std::vector areas_{}; +#endif +#ifdef USE_AREAS + std::vector areas_{}; #endif #ifdef USE_BINARY_SENSOR std::vector binary_sensors_{}; @@ -694,7 +710,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_; diff --git a/esphome/core/sub_area.h b/esphome/core/area.h similarity index 96% rename from esphome/core/sub_area.h rename to esphome/core/area.h index 2a70086c1c..f239983741 100644 --- a/esphome/core/sub_area.h +++ b/esphome/core/area.h @@ -5,7 +5,7 @@ namespace esphome { -class SubArea { +class Area { public: void set_area_id(uint32_t area_id) { area_id_ = area_id; } uint32_t get_area_id() { return area_id_; } diff --git a/esphome/core/config.py b/esphome/core/config.py index 76c7505393..921e7653a8 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -40,6 +40,7 @@ from esphome.helpers import ( copy_file_if_changed, fnv1a_32bit_hash, get_str_env, + slugify, walk_files, ) @@ -58,7 +59,7 @@ ProjectUpdateTrigger = cg.esphome_ns.class_( "ProjectUpdateTrigger", cg.Component, automation.Trigger.template(cg.std_string) ) SubDevice = cg.esphome_ns.class_("SubDevice") -SubArea = cg.esphome_ns.class_("SubArea") +Area = cg.esphome_ns.class_("Area") VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} @@ -127,7 +128,15 @@ CONFIG_SCHEMA = cv.All( { cv.Required(CONF_NAME): cv.valid_name, cv.Optional(CONF_FRIENDLY_NAME, ""): cv.string, - cv.Optional(CONF_AREA, ""): cv.string, + cv.Optional(CONF_AREA): cv.Any( + cv.string, # Old way: just a string + cv.Schema( # New way: structured area + { + cv.GenerateID(CONF_ID): cv.declare_id(Area), + cv.Required(CONF_NAME): cv.string, + } + ), + ), cv.Optional(CONF_COMMENT): cv.string, cv.Required(CONF_BUILD_PATH): cv.string, cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema( @@ -180,7 +189,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_SUB_AREAS, default=[]): cv.ensure_list( cv.Schema( { - cv.GenerateID(CONF_ID): cv.declare_id(SubArea), + cv.GenerateID(CONF_ID): cv.declare_id(Area), cv.Required(CONF_NAME): cv.string, } ), @@ -190,7 +199,7 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(CONF_ID): cv.declare_id(SubDevice), cv.Required(CONF_NAME): cv.string, - cv.Optional(CONF_AREA_ID): cv.use_id(SubArea), + cv.Optional(CONF_AREA_ID): cv.use_id(Area), } ), ), @@ -374,7 +383,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], @@ -445,6 +453,38 @@ async def to_code(config): if config[CONF_PLATFORMIO_OPTIONS]: CORE.add_job(_add_platformio_options, config[CONF_PLATFORMIO_OPTIONS]) + # Handle area configuration + if area_conf := config.get(CONF_AREA): + if isinstance(area_conf, dict): + # New way: structured area configuration + area_var = cg.new_Pvariable(area_conf[CONF_ID]) + area_id = fnv1a_32bit_hash(str(area_conf[CONF_ID])) + area_name = area_conf[CONF_NAME] + else: + # Old way: string-based area (deprecated) + area_slug = slugify(area_conf) + _LOGGER.warning( + "Using 'area' as a string is deprecated. Please use the new format:\n" + "area:\n" + " id: %s\n" + ' name: "%s"', + area_slug, + area_conf, + ) + # Create a synthetic area for backwards compatibility + area_var = cg.new_Pvariable( + cg.ID(f"area_{area_slug}", is_declaration=True, type=Area) + ) + area_id = fnv1a_32bit_hash(area_conf) + area_name = area_conf + + # Common setup for both ways + cg.add(area_var.set_area_id(area_id)) + cg.add(area_var.set_name(area_name)) + cg.add(cg.App.register_area(area_var)) + # Define USE_AREAS to enable area processing + cg.add_define("USE_AREAS") + # Process sub-devices and areas if sub_devices := config.get(CONF_SUB_DEVICES): # Process areas first @@ -455,6 +495,8 @@ async def to_code(config): cg.add(area.set_area_id(area_id)) cg.add(area.set_name(area_conf[CONF_NAME])) cg.add(cg.App.register_area(area)) + # Define USE_AREAS since we have areas + cg.add_define("USE_AREAS") # Process sub-devices for dev_conf in sub_devices: diff --git a/esphome/dashboard/util/text.py b/esphome/dashboard/util/text.py index 08d2df6abf..5c75061637 100644 --- a/esphome/dashboard/util/text.py +++ b/esphome/dashboard/util/text.py @@ -1,25 +1,9 @@ from __future__ import annotations -import unicodedata - -from esphome.const import ALLOWED_NAME_CHARS - - -def strip_accents(value): - return "".join( - c - for c in unicodedata.normalize("NFD", str(value)) - if unicodedata.category(c) != "Mn" - ) +from esphome.helpers import slugify 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) + """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("_", "-") diff --git a/esphome/helpers.py b/esphome/helpers.py index 242c05e892..c84d597999 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -38,6 +38,32 @@ def fnv1a_32bit_hash(string: str) -> int: 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: From 1589a131db8894f2487c5b791b0d22012160d13e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 16:39:07 +0200 Subject: [PATCH 42/99] migrate to using same area info for top level and sub devices --- esphome/components/api/api.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 0ac9cd3aab..b3ca1ce5c5 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -250,7 +250,7 @@ message DeviceInfoResponse { repeated SubDeviceInfo sub_devices = 20; repeated AreaInfo areas = 21; - + // Top-level area info to phase out suggested_area AreaInfo area = 22; } From e7a4eac8bdbc16d9a702482bc7cc81a01b069781 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 16:42:05 +0200 Subject: [PATCH 43/99] migrate to using same area info for top level and sub devices --- tests/components/esphome/common.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/components/esphome/common.yaml b/tests/components/esphome/common.yaml index aa1ce9e111..8597987708 100644 --- a/tests/components/esphome/common.yaml +++ b/tests/components/esphome/common.yaml @@ -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: @@ -24,6 +26,9 @@ esphome: - 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 sub_areas) binary_sensor: - platform: template From 41e11e9a0e849a82776de2869ffca5bf4ba3da52 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 16:43:48 +0200 Subject: [PATCH 44/99] migrate to using same area info for top level and sub devices --- tests/components/esphome/common.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/components/esphome/common.yaml b/tests/components/esphome/common.yaml index 8597987708..24f8eb9433 100644 --- a/tests/components/esphome/common.yaml +++ b/tests/components/esphome/common.yaml @@ -29,6 +29,8 @@ esphome: - id: test_device name: Test device in main area area_id: testing_area # Reference the main area (not in sub_areas) + - id: no_area_device + name: Device without area # This device has no area_id binary_sensor: - platform: template From 98de53f60ba02377d8cb0cb309aef26fdc09b87d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 16:47:03 +0200 Subject: [PATCH 45/99] migrate to using same area info for top level and sub devices --- esphome/components/api/api.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index b3ca1ce5c5..96ef93ef46 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -250,7 +250,7 @@ message DeviceInfoResponse { repeated SubDeviceInfo sub_devices = 20; repeated AreaInfo areas = 21; - + // Top-level area info to phase out suggested_area AreaInfo area = 22; } From 8714e809786aee5a4806135a3b6f2b1dfc23cea4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 17:05:46 +0200 Subject: [PATCH 46/99] make areas and devices consistant --- esphome/components/api/api.proto | 4 ++-- esphome/components/api/api_connection.cpp | 14 +++++++------- esphome/components/api/api_connection.h | 2 +- esphome/core/application.h | 18 +++++++++--------- esphome/core/area.h | 7 +++---- esphome/core/config.py | 8 ++++---- esphome/core/entity_base.h | 4 ++-- esphome/core/sub_device.h | 22 ---------------------- 8 files changed, 28 insertions(+), 51 deletions(-) delete mode 100644 esphome/core/sub_device.h diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 96ef93ef46..58a0b52555 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -193,7 +193,7 @@ message AreaInfo { string name = 2; } -message SubDeviceInfo { +message DeviceInfo { uint32 device_id = 1; string name = 2; uint32 area_id = 3; @@ -248,7 +248,7 @@ message DeviceInfoResponse { // Supports receiving and saving api encryption key bool api_encryption_supported = 19; - repeated SubDeviceInfo sub_devices = 20; + repeated DeviceInfo devices = 20; repeated AreaInfo areas = 21; // Top-level area info to phase out suggested_area diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 799cd2f102..948b67456b 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1620,13 +1620,13 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { #ifdef USE_API_NOISE resp.api_encryption_supported = true; #endif -#ifdef USE_SUB_DEVICE - for (auto const &sub_device : App.get_sub_devices()) { - SubDeviceInfo sub_device_info; - sub_device_info.device_id = sub_device->get_device_id(); - sub_device_info.name = sub_device->get_name(); - sub_device_info.area_id = sub_device->get_area_id(); - resp.sub_devices.push_back(sub_device_info); +#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 diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 9166dbbc94..da12a3e449 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -301,7 +301,7 @@ class APIConnection : public APIServerConnection { response.icon = entity->get_icon(); response.disabled_by_default = entity->is_disabled_by_default(); response.entity_category = static_cast(entity->get_entity_category()); -#ifdef USE_SUB_DEVICE +#ifdef USE_DEVICES response.device_id = entity->get_device_id(); #endif } diff --git a/esphome/core/application.h b/esphome/core/application.h index 09e2cfefbf..347cbca304 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -9,8 +9,8 @@ #include "esphome/core/preferences.h" #include "esphome/core/scheduler.h" -#ifdef USE_SUB_DEVICE -#include "esphome/core/sub_device.h" +#ifdef USE_DEVICES +#include "esphome/core/device.h" #endif #ifdef USE_AREAS #include "esphome/core/area.h" @@ -114,8 +114,8 @@ class Application { this->compilation_time_ = compilation_time; } -#ifdef USE_SUB_DEVICE - void register_sub_device(SubDevice *sub_device) { this->sub_devices_.push_back(sub_device); } +#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); } @@ -299,7 +299,7 @@ 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 { + 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) { @@ -356,8 +356,8 @@ class Application { uint8_t get_app_state() const { return this->app_state_; } -#ifdef USE_SUB_DEVICE - const std::vector &get_sub_devices() { return this->sub_devices_; } +#ifdef USE_DEVICES + const std::vector &get_devices() { return this->devices_; } #endif #ifdef USE_AREAS const std::vector &get_areas() { return this->areas_; } @@ -638,8 +638,8 @@ class Application { uint16_t current_loop_index_{0}; bool in_loop_{false}; -#ifdef USE_SUB_DEVICE - std::vector sub_devices_{}; +#ifdef USE_DEVICES + std::vector devices_{}; #endif #ifdef USE_AREAS std::vector areas_{}; diff --git a/esphome/core/area.h b/esphome/core/area.h index f239983741..30b82aad6d 100644 --- a/esphome/core/area.h +++ b/esphome/core/area.h @@ -1,6 +1,5 @@ #pragma once -#include #include namespace esphome { @@ -9,12 +8,12 @@ class Area { public: void set_area_id(uint32_t area_id) { area_id_ = area_id; } uint32_t get_area_id() { return area_id_; } - void set_name(std::string name) { name_ = std::move(name); } - std::string get_name() { return name_; } + void set_name(const char *name) { name_ = name; } + const char *get_name() { return name_; } protected: uint32_t area_id_{}; - std::string name_ = ""; + const char *name_ = ""; }; } // namespace esphome diff --git a/esphome/core/config.py b/esphome/core/config.py index 921e7653a8..ba7516d939 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -58,7 +58,7 @@ LoopTrigger = cg.esphome_ns.class_( ProjectUpdateTrigger = cg.esphome_ns.class_( "ProjectUpdateTrigger", cg.Component, automation.Trigger.template(cg.std_string) ) -SubDevice = cg.esphome_ns.class_("SubDevice") +Device = cg.esphome_ns.class_("Device") Area = cg.esphome_ns.class_("Area") VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} @@ -197,7 +197,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_SUB_DEVICES, default=[]): cv.ensure_list( cv.Schema( { - cv.GenerateID(CONF_ID): cv.declare_id(SubDevice), + cv.GenerateID(CONF_ID): cv.declare_id(Device), cv.Required(CONF_NAME): cv.string, cv.Optional(CONF_AREA_ID): cv.use_id(Area), } @@ -507,5 +507,5 @@ async def to_code(config): # Get the area variable and use its area_id area = await cg.get_variable(dev_conf[CONF_AREA_ID]) cg.add(dev.set_area_id(area.get_area_id())) - cg.add(cg.App.register_sub_device(dev)) - cg.add_define("USE_SUB_DEVICE") + cg.add(cg.App.register_device(dev)) + cg.add_define("USE_DEVICES") diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index b21ae196f1..4bd04a9b1c 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -51,7 +51,7 @@ class EntityBase { std::string get_icon() const; void set_icon(const char *icon); -#ifdef USE_SUB_DEVICE +#ifdef USE_DEVICES // Get/set this entity's device id uint32_t get_device_id() const { return this->device_id_; } void set_device_id(const uint32_t device_id) { this->device_id_ = device_id; } @@ -73,7 +73,7 @@ class EntityBase { const char *object_id_c_str_{nullptr}; const char *icon_c_str_{nullptr}; uint32_t object_id_hash_{}; -#ifdef USE_SUB_DEVICE +#ifdef USE_DEVICES uint32_t device_id_{}; #endif diff --git a/esphome/core/sub_device.h b/esphome/core/sub_device.h deleted file mode 100644 index f17f882dfd..0000000000 --- a/esphome/core/sub_device.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "esphome/core/string_ref.h" - -namespace esphome { - -class SubDevice { - public: - void set_device_id(uint32_t device_id) { device_id_ = device_id; } - uint32_t get_device_id() { return device_id_; } - void set_name(std::string name) { name_ = std::move(name); } - std::string get_name() { return name_; } - void set_area_id(uint32_t area_id) { area_id_ = area_id; } - uint32_t get_area_id() { return area_id_; } - - protected: - uint32_t device_id_{}; - uint32_t area_id_{}; - std::string name_ = ""; -}; - -} // namespace esphome From 65e3c6bfbbb775965bf42e9b525dc85804e1feb5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 17:12:00 +0200 Subject: [PATCH 47/99] make areas and devices consistant --- esphome/const.py | 4 ++-- esphome/core/config.py | 12 ++++++------ esphome/core/defines.h | 3 ++- tests/components/esphome/common.yaml | 6 +++--- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/esphome/const.py b/esphome/const.py index 47f20a71cb..577b9beae7 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -844,8 +844,8 @@ CONF_STILL_THRESHOLD = "still_threshold" CONF_STOP = "stop" CONF_STOP_ACTION = "stop_action" CONF_STORE_BASELINE = "store_baseline" -CONF_SUB_AREAS = "sub_areas" -CONF_SUB_DEVICES = "sub_devices" +CONF_AREAS = "areas" +CONF_DEVICES = "devices" CONF_SUBNET = "subnet" CONF_SUBSCRIBE_QOS = "subscribe_qos" CONF_SUBSTITUTIONS = "substitutions" diff --git a/esphome/core/config.py b/esphome/core/config.py index ba7516d939..46034575f9 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -8,10 +8,12 @@ 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, @@ -28,8 +30,6 @@ from esphome.const import ( CONF_PLATFORMIO_OPTIONS, CONF_PRIORITY, CONF_PROJECT, - CONF_SUB_AREAS, - CONF_SUB_DEVICES, CONF_TRIGGER_ID, CONF_VERSION, KEY_CORE, @@ -186,7 +186,7 @@ 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_SUB_AREAS, default=[]): cv.ensure_list( + cv.Optional(CONF_AREAS, default=[]): cv.ensure_list( cv.Schema( { cv.GenerateID(CONF_ID): cv.declare_id(Area), @@ -194,7 +194,7 @@ CONFIG_SCHEMA = cv.All( } ), ), - cv.Optional(CONF_SUB_DEVICES, default=[]): cv.ensure_list( + cv.Optional(CONF_DEVICES, default=[]): cv.ensure_list( cv.Schema( { cv.GenerateID(CONF_ID): cv.declare_id(Device), @@ -486,9 +486,9 @@ async def to_code(config): cg.add_define("USE_AREAS") # Process sub-devices and areas - if sub_devices := config.get(CONF_SUB_DEVICES): + if sub_devices := config.get(CONF_DEVICES): # Process areas first - if sub_areas := config.get(CONF_SUB_AREAS): + if sub_areas := config.get(CONF_AREAS): for area_conf in sub_areas: area = cg.new_Pvariable(area_conf[CONF_ID]) area_id = fnv1a_32bit_hash(str(area_conf[CONF_ID])) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 32625a6a04..b1ee597942 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -84,7 +84,8 @@ #define USE_SELECT #define USE_SENSOR #define USE_STATUS_LED -#define USE_SUB_DEVICE +#define USE_DEVICES +#define USE_AREAS #define USE_SWITCH #define USE_TEXT #define USE_TEXT_SENSOR diff --git a/tests/components/esphome/common.yaml b/tests/components/esphome/common.yaml index 24f8eb9433..a4b309b69d 100644 --- a/tests/components/esphome/common.yaml +++ b/tests/components/esphome/common.yaml @@ -19,16 +19,16 @@ esphome: version: "1.1" on_update: logger.log: on_update - sub_areas: + areas: - id: another_area name: Another area - sub_devices: + 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 sub_areas) + 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 From 66cce6a2f2306a2fe65d5c4a7f69182a53ac73c3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 17:12:25 +0200 Subject: [PATCH 48/99] make areas and devices consistant --- esphome/components/api/api_pb2.cpp | 24 ++++++++++++------------ esphome/components/api/api_pb2.h | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index cbe18e172e..9793565ee5 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -855,7 +855,7 @@ void AreaInfo::dump_to(std::string &out) const { out.append("}"); } #endif -bool SubDeviceInfo::decode_varint(uint32_t field_id, ProtoVarInt value) { +bool DeviceInfo::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { this->device_id = value.as_uint32(); @@ -869,7 +869,7 @@ bool SubDeviceInfo::decode_varint(uint32_t field_id, ProtoVarInt value) { return false; } } -bool SubDeviceInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) { +bool DeviceInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: { this->name = value.as_string(); @@ -879,20 +879,20 @@ bool SubDeviceInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) return false; } } -void SubDeviceInfo::encode(ProtoWriteBuffer buffer) const { +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 SubDeviceInfo::calculate_size(uint32_t &total_size) const { +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 SubDeviceInfo::dump_to(std::string &out) const { +void DeviceInfo::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; - out.append("SubDeviceInfo {\n"); + out.append("DeviceInfo {\n"); out.append(" device_id: "); sprintf(buffer, "%" PRIu32, this->device_id); out.append(buffer); @@ -994,7 +994,7 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v return true; } case 20: { - this->sub_devices.push_back(value.as_message()); + this->devices.push_back(value.as_message()); return true; } case 21: { @@ -1029,8 +1029,8 @@ 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->sub_devices) { - buffer.encode_message(20, it, true); + for (auto &it : this->devices) { + buffer.encode_message(20, it, true); } for (auto &it : this->areas) { buffer.encode_message(21, it, true); @@ -1057,7 +1057,7 @@ 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->sub_devices); + 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); } @@ -1146,8 +1146,8 @@ void DeviceInfoResponse::dump_to(std::string &out) const { out.append(YESNO(this->api_encryption_supported)); out.append("\n"); - for (const auto &it : this->sub_devices) { - out.append(" sub_devices: "); + for (const auto &it : this->devices) { + out.append(" devices: "); it.dump_to(out); out.append("\n"); } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index e71fd23619..6a5b51d3a1 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -430,7 +430,7 @@ class AreaInfo : public ProtoMessage { bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; -class SubDeviceInfo : public ProtoMessage { +class DeviceInfo : public ProtoMessage { public: uint32_t device_id{0}; std::string name{}; @@ -471,7 +471,7 @@ class DeviceInfoResponse : public ProtoMessage { std::string suggested_area{}; std::string bluetooth_mac_address{}; bool api_encryption_supported{false}; - std::vector sub_devices{}; + std::vector devices{}; std::vector areas{}; AreaInfo area{}; void encode(ProtoWriteBuffer buffer) const override; From d300d2605b7999a66dc6238eaab297bd4949b9c6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 17:13:04 +0200 Subject: [PATCH 49/99] make areas and devices consistant --- esphome/core/config.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index 46034575f9..8374a3d3be 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -485,11 +485,11 @@ async def to_code(config): # Define USE_AREAS to enable area processing cg.add_define("USE_AREAS") - # Process sub-devices and areas - if sub_devices := config.get(CONF_DEVICES): + # Process devices and areas + if devices := config.get(CONF_DEVICES): # Process areas first - if sub_areas := config.get(CONF_AREAS): - for area_conf in sub_areas: + if areas := config.get(CONF_AREAS): + for area_conf in areas: area = cg.new_Pvariable(area_conf[CONF_ID]) area_id = fnv1a_32bit_hash(str(area_conf[CONF_ID])) cg.add(area.set_area_id(area_id)) @@ -498,8 +498,8 @@ async def to_code(config): # Define USE_AREAS since we have areas cg.add_define("USE_AREAS") - # Process sub-devices - for dev_conf in sub_devices: + # Process devices + for dev_conf in devices: dev = cg.new_Pvariable(dev_conf[CONF_ID]) cg.add(dev.set_device_id(fnv1a_32bit_hash(str(dev_conf[CONF_ID])))) cg.add(dev.set_name(dev_conf[CONF_NAME])) From 3d0392d668f35b1133c7479ed0d6e41886d73b03 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 17:17:29 +0200 Subject: [PATCH 50/99] make areas and devices consistant --- esphome/components/usb_host/__init__.py | 3 +-- esphome/const.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/esphome/components/usb_host/__init__.py b/esphome/components/usb_host/__init__.py index 3204562dc8..0fe3310127 100644 --- a/esphome/components/usb_host/__init__.py +++ b/esphome/components/usb_host/__init__.py @@ -6,7 +6,7 @@ from esphome.components.esp32 import ( only_on_variant, ) import esphome.config_validation as cv -from esphome.const import CONF_ID +from esphome.const import CONF_DEVICES, CONF_ID from esphome.cpp_types import Component AUTO_LOAD = ["bytebuffer"] @@ -16,7 +16,6 @@ usb_host_ns = cg.esphome_ns.namespace("usb_host") USBHost = usb_host_ns.class_("USBHost", Component) USBClient = usb_host_ns.class_("USBClient", Component) -CONF_DEVICES = "devices" CONF_VID = "vid" CONF_PID = "pid" CONF_ENABLE_HUBS = "enable_hubs" diff --git a/esphome/const.py b/esphome/const.py index 577b9beae7..6d7d9c0c1b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -57,6 +57,7 @@ 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" @@ -219,6 +220,7 @@ 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" CONF_DIO_PIN = "dio_pin" @@ -844,8 +846,6 @@ CONF_STILL_THRESHOLD = "still_threshold" CONF_STOP = "stop" CONF_STOP_ACTION = "stop_action" CONF_STORE_BASELINE = "store_baseline" -CONF_AREAS = "areas" -CONF_DEVICES = "devices" CONF_SUBNET = "subnet" CONF_SUBSCRIBE_QOS = "subscribe_qos" CONF_SUBSTITUTIONS = "substitutions" From f44ecd08913b8f3ac3c9e87222cab6aa5c7acd8f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 17:18:23 +0200 Subject: [PATCH 51/99] make areas and devices consistant --- esphome/config_validation.py | 4 ++-- esphome/core/config.py | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 072b4d69d1..a3627efe7b 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -350,9 +350,9 @@ def icon(value): def sub_device_id(value): # Lazy import to avoid circular imports - from esphome.core.config import SubDevice + from esphome.core.config import Device - validator = use_id(SubDevice) + validator = use_id(Device) return validator(value) diff --git a/esphome/core/config.py b/esphome/core/config.py index 8374a3d3be..95419fee70 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -472,9 +472,8 @@ async def to_code(config): area_conf, ) # Create a synthetic area for backwards compatibility - area_var = cg.new_Pvariable( - cg.ID(f"area_{area_slug}", is_declaration=True, type=Area) - ) + area_id_obj = cv.ID(f"area_{area_slug}") + area_var = cg.new_Pvariable(area_id_obj, type_=Area) area_id = fnv1a_32bit_hash(area_conf) area_name = area_conf From 4a7958586ecac97a0622de973c2ffff823967faa Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 17:19:16 +0200 Subject: [PATCH 52/99] make areas and devices consistant --- esphome/core/config.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index 95419fee70..544fba4aba 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -472,8 +472,7 @@ async def to_code(config): area_conf, ) # Create a synthetic area for backwards compatibility - area_id_obj = cv.ID(f"area_{area_slug}") - area_var = cg.new_Pvariable(area_id_obj, type_=Area) + area_var = cg.Pvariable(f"area_{area_slug}", Area) area_id = fnv1a_32bit_hash(area_conf) area_name = area_conf From fad86c655eedb790d791adc37d115ed21e31e840 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 17:30:17 +0200 Subject: [PATCH 53/99] make areas and devices consistant --- esphome/core/device.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 esphome/core/device.h diff --git a/esphome/core/device.h b/esphome/core/device.h new file mode 100644 index 0000000000..29f78b023e --- /dev/null +++ b/esphome/core/device.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/string_ref.h" + +namespace esphome { + +class Device { + public: + void set_device_id(uint32_t device_id) { device_id_ = device_id; } + uint32_t get_device_id() { return device_id_; } + void set_name(const char *name) { name_ = name; } + const char *get_name() { return name_; } + void set_area_id(uint32_t area_id) { area_id_ = area_id; } + uint32_t get_area_id() { return area_id_; } + + protected: + uint32_t device_id_{}; + uint32_t area_id_{}; + const char *name_ = ""; +}; + +} // namespace esphome From be37178ef8b7c7dc251a5dfb5d67cd22fefd7b57 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 17:32:11 +0200 Subject: [PATCH 54/99] make areas and devices consistant --- esphome/core/defines.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index b1ee597942..b064653ca3 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -83,9 +83,9 @@ #define USE_QR_CODE #define USE_SELECT #define USE_SENSOR -#define USE_STATUS_LED -#define USE_DEVICES #define USE_AREAS +#define USE_DEVICES +#define USE_STATUS_LED #define USE_SWITCH #define USE_TEXT #define USE_TEXT_SENSOR From 1f99d18982eabcb6741366fa8b0719e087ce5fca Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 17:34:08 +0200 Subject: [PATCH 55/99] reverse space in vectors --- esphome/core/application.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/esphome/core/application.h b/esphome/core/application.h index 347cbca304..160a7b35ca 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -278,6 +278,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 C *register_component(C *c) { From aa4c3996574715c51296e4cde94412583d7fdea0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 17:36:25 +0200 Subject: [PATCH 56/99] reverse space in vectors --- esphome/core/config.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index 544fba4aba..00c739b079 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -453,6 +453,18 @@ async def to_code(config): if config[CONF_PLATFORMIO_OPTIONS]: CORE.add_job(_add_platformio_options, config[CONF_PLATFORMIO_OPTIONS]) + # Count total areas for reservation + total_areas = 0 + if config.get(CONF_AREA): + total_areas += 1 + if areas_list := config.get(CONF_AREAS): + total_areas += len(areas_list) + + # Reserve space for areas if any are defined + if total_areas > 0: + cg.add(cg.RawStatement(f"App.reserve_area({total_areas});")) + cg.add_define("USE_AREAS") + # Handle area configuration if area_conf := config.get(CONF_AREA): if isinstance(area_conf, dict): @@ -480,12 +492,13 @@ async def to_code(config): cg.add(area_var.set_area_id(area_id)) cg.add(area_var.set_name(area_name)) cg.add(cg.App.register_area(area_var)) - # Define USE_AREAS to enable area processing - cg.add_define("USE_AREAS") # Process devices and areas if devices := config.get(CONF_DEVICES): - # Process areas first + # Reserve space for devices + cg.add(cg.RawStatement(f"App.reserve_device({len(devices)});")) + + # Process additional areas if areas := config.get(CONF_AREAS): for area_conf in areas: area = cg.new_Pvariable(area_conf[CONF_ID]) @@ -493,8 +506,6 @@ async def to_code(config): cg.add(area.set_area_id(area_id)) cg.add(area.set_name(area_conf[CONF_NAME])) cg.add(cg.App.register_area(area)) - # Define USE_AREAS since we have areas - cg.add_define("USE_AREAS") # Process devices for dev_conf in devices: From 4d231953f4d793a1f4fd12861eb8532cc66ada37 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 17:57:10 +0200 Subject: [PATCH 57/99] preen --- esphome/core/config.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index 00c739b079..23201788ab 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -454,14 +454,12 @@ async def to_code(config): CORE.add_job(_add_platformio_options, config[CONF_PLATFORMIO_OPTIONS]) # Count total areas for reservation - total_areas = 0 + total_areas = len(config[CONF_AREAS]) if config.get(CONF_AREA): total_areas += 1 - if areas_list := config.get(CONF_AREAS): - total_areas += len(areas_list) # Reserve space for areas if any are defined - if total_areas > 0: + if total_areas: cg.add(cg.RawStatement(f"App.reserve_area({total_areas});")) cg.add_define("USE_AREAS") From 1873490b24d51927c1b1c5e4280db8f8cf82a18b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 17:57:36 +0200 Subject: [PATCH 58/99] preen --- esphome/core/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index 23201788ab..63f2ad4f3b 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -495,6 +495,7 @@ async def to_code(config): if devices := config.get(CONF_DEVICES): # Reserve space for devices cg.add(cg.RawStatement(f"App.reserve_device({len(devices)});")) + cg.add_define("USE_DEVICES") # Process additional areas if areas := config.get(CONF_AREAS): @@ -515,4 +516,3 @@ async def to_code(config): area = await cg.get_variable(dev_conf[CONF_AREA_ID]) cg.add(dev.set_area_id(area.get_area_id())) cg.add(cg.App.register_device(dev)) - cg.add_define("USE_DEVICES") From 8e7841c880ceb192adbeb2a6cec11f448428bdee Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 18:00:17 +0200 Subject: [PATCH 59/99] preen --- esphome/core/config.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index 63f2ad4f3b..b8288d534d 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging import os from pathlib import Path @@ -43,6 +45,7 @@ from esphome.helpers import ( slugify, walk_files, ) +from esphome.types import ConfigType _LOGGER = logging.getLogger(__name__) @@ -372,7 +375,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")) @@ -464,6 +467,7 @@ async def to_code(config): cg.add_define("USE_AREAS") # Handle area configuration + area_conf: dict[str, str] | str | None if area_conf := config.get(CONF_AREA): if isinstance(area_conf, dict): # New way: structured area configuration From f2b04a077eaf84ebb8d4f00abb182c031379f135 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 18:01:12 +0200 Subject: [PATCH 60/99] preen --- esphome/core/config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/core/config.py b/esphome/core/config.py index b8288d534d..a84f2d85ab 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -496,12 +496,14 @@ async def to_code(config: ConfigType) -> None: cg.add(cg.App.register_area(area_var)) # Process devices and areas + devices: dict[str, str] | None if devices := config.get(CONF_DEVICES): # Reserve space for devices cg.add(cg.RawStatement(f"App.reserve_device({len(devices)});")) cg.add_define("USE_DEVICES") # Process additional areas + areas: dict[str, str] | None if areas := config.get(CONF_AREAS): for area_conf in areas: area = cg.new_Pvariable(area_conf[CONF_ID]) From c19065f112b70288989773fd9f3c64adda142ae6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 18:02:32 +0200 Subject: [PATCH 61/99] preen --- esphome/core/config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index a84f2d85ab..1947f46e80 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -496,15 +496,15 @@ async def to_code(config: ConfigType) -> None: cg.add(cg.App.register_area(area_var)) # Process devices and areas - devices: dict[str, str] | None - if devices := config.get(CONF_DEVICES): + devices: list[dict[str, str]] + if devices := config[CONF_DEVICES]: # Reserve space for devices cg.add(cg.RawStatement(f"App.reserve_device({len(devices)});")) cg.add_define("USE_DEVICES") # Process additional areas - areas: dict[str, str] | None - if areas := config.get(CONF_AREAS): + areas: list[dict[str, str]] + if areas := config[CONF_AREAS]: for area_conf in areas: area = cg.new_Pvariable(area_conf[CONF_ID]) area_id = fnv1a_32bit_hash(str(area_conf[CONF_ID])) From fb1679d5726b88d55196105b138d257c75b11035 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 18:07:45 +0200 Subject: [PATCH 62/99] preen --- esphome/core/defines.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index b064653ca3..c9fea90386 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -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 @@ -83,8 +85,6 @@ #define USE_QR_CODE #define USE_SELECT #define USE_SENSOR -#define USE_AREAS -#define USE_DEVICES #define USE_STATUS_LED #define USE_SWITCH #define USE_TEXT From 221e3c6c9c641a4d90123eb7c2be3fd671df9237 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Jun 2025 18:09:16 +0200 Subject: [PATCH 63/99] preen --- esphome/core/device.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/esphome/core/device.h b/esphome/core/device.h index 29f78b023e..de25963110 100644 --- a/esphome/core/device.h +++ b/esphome/core/device.h @@ -1,7 +1,5 @@ #pragma once -#include "esphome/core/string_ref.h" - namespace esphome { class Device { From ffccce7ffcde0e54a3605eac98c4bf99ef75de19 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 09:58:12 +0200 Subject: [PATCH 64/99] handle collisions --- esphome/core/config.py | 52 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index 1947f46e80..6489c21826 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -4,6 +4,8 @@ import logging import os from pathlib import Path +import voluptuous as vol + from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv @@ -374,6 +376,17 @@ async def _add_platform_reserves() -> None: cg.add(cg.RawStatement(f"App.reserve_{platform_name}({count});"), prepend=True) +def _verify_no_collisions( + hashes: dict[int, str], id: str, id_hash: int, conf_key: str +) -> None: + """Verify that the given id and name do not collide with existing ones.""" + if id_hash in hashes: + raise vol.Invalid( + f"ID '{id}' with hash {id_hash} collides with existing ID '{hashes[id_hash]}'", + path=[conf_key], + ) + + @coroutine_with_priority(100.0) async def to_code(config: ConfigType) -> None: cg.add_global(cg.global_ns.namespace("esphome").using) @@ -467,6 +480,9 @@ async def to_code(config: ConfigType) -> None: cg.add_define("USE_AREAS") # Handle area configuration + area_hashes = dict[int, str] = {} + area_ids = set[str] = set() + device_hashes = dict[int, str] = {} area_conf: dict[str, str] | str | None if area_conf := config.get(CONF_AREA): if isinstance(area_conf, dict): @@ -491,6 +507,8 @@ async def to_code(config: ConfigType) -> None: area_name = area_conf # Common setup for both ways + area_hashes[area_id] = area_name + area_ids.add(area_id) cg.add(area_var.set_area_id(area_id)) cg.add(area_var.set_name(area_name)) cg.add(cg.App.register_area(area_var)) @@ -506,19 +524,35 @@ async def to_code(config: ConfigType) -> None: areas: list[dict[str, str]] if areas := config[CONF_AREAS]: for area_conf in areas: - area = cg.new_Pvariable(area_conf[CONF_ID]) - area_id = fnv1a_32bit_hash(str(area_conf[CONF_ID])) - cg.add(area.set_area_id(area_id)) - cg.add(area.set_name(area_conf[CONF_NAME])) + area_id = area_conf[CONF_ID] + area_ids.add(area_id) + area = cg.new_Pvariable(area_id) + area_id_hash = fnv1a_32bit_hash(area_id) + area_name = area_conf[CONF_NAME] + _verify_no_collisions(area_hashes, area_id, area_id_hash, CONF_AREAS) + cg.add(area.set_area_id(area_id_hash)) + cg.add(area.set_name(name)) cg.add(cg.App.register_area(area)) # Process devices for dev_conf in devices: - dev = cg.new_Pvariable(dev_conf[CONF_ID]) - cg.add(dev.set_device_id(fnv1a_32bit_hash(str(dev_conf[CONF_ID])))) - cg.add(dev.set_name(dev_conf[CONF_NAME])) + device_id = dev_conf[CONF_ID] + device_id_hash = fnv1a_32bit_hash(device_id) + device_name = dev_conf[CONF_NAME] + _verify_no_collisions( + device_hashes, device_id, device_id_hash, CONF_DEVICES + ) + dev = cg.new_Pvariable(device_id) + cg.add(dev.set_device_id(device_id_hash)) + cg.add(dev.set_name(device_name)) if CONF_AREA_ID in dev_conf: # Get the area variable and use its area_id - area = await cg.get_variable(dev_conf[CONF_AREA_ID]) - cg.add(dev.set_area_id(area.get_area_id())) + area_id = dev_conf[CONF_AREA_ID] + area_id_hash = fnv1a_32bit_hash(area_id) + if area_id not in area_ids: + raise vol.Invalid( + f"Device '{device_name}' has an area_id '{area_id}' that does not exist.", + path=[CONF_DEVICES, dev_conf[CONF_ID], CONF_AREA_ID], + ) + cg.add(dev.set_area_id(area_id_hash)) cg.add(cg.App.register_device(dev)) From 57599f7a98bfa2035c81994126eb4bc1088badc2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 10:00:31 +0200 Subject: [PATCH 65/99] handle collisions --- esphome/core/config.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index 6489c21826..cb8d2100db 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -487,12 +487,14 @@ async def to_code(config: ConfigType) -> None: if area_conf := config.get(CONF_AREA): if isinstance(area_conf, dict): # New way: structured area configuration - area_var = cg.new_Pvariable(area_conf[CONF_ID]) - area_id = fnv1a_32bit_hash(str(area_conf[CONF_ID])) + area_id_str = area_conf[CONF_ID] + area_var = cg.new_Pvariable(area_id_str) + area_id = fnv1a_32bit_hash(area_id_str) area_name = area_conf[CONF_NAME] else: # Old way: string-based area (deprecated) area_slug = slugify(area_conf) + area_id_str = area_slug _LOGGER.warning( "Using 'area' as a string is deprecated. Please use the new format:\n" "area:\n" @@ -502,13 +504,13 @@ async def to_code(config: ConfigType) -> None: area_conf, ) # Create a synthetic area for backwards compatibility - area_var = cg.Pvariable(f"area_{area_slug}", Area) + area_var = cg.Pvariable(area_slug, Area) area_id = fnv1a_32bit_hash(area_conf) area_name = area_conf # Common setup for both ways area_hashes[area_id] = area_name - area_ids.add(area_id) + area_ids.add(area_id_str) cg.add(area_var.set_area_id(area_id)) cg.add(area_var.set_name(area_name)) cg.add(cg.App.register_area(area_var)) @@ -531,7 +533,7 @@ async def to_code(config: ConfigType) -> None: area_name = area_conf[CONF_NAME] _verify_no_collisions(area_hashes, area_id, area_id_hash, CONF_AREAS) cg.add(area.set_area_id(area_id_hash)) - cg.add(area.set_name(name)) + cg.add(area.set_name(area_name)) cg.add(cg.App.register_area(area)) # Process devices From bf8d8b6e630c98ca25a99698eeb59df399e83a19 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 10:01:53 +0200 Subject: [PATCH 66/99] handle collisions --- esphome/core/config.py | 78 +++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index cb8d2100db..0e2a127942 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -517,44 +517,44 @@ async def to_code(config: ConfigType) -> None: # Process devices and areas devices: list[dict[str, str]] - if devices := config[CONF_DEVICES]: - # Reserve space for devices - cg.add(cg.RawStatement(f"App.reserve_device({len(devices)});")) - cg.add_define("USE_DEVICES") + if not (devices := config[CONF_DEVICES]): + return - # Process additional areas - areas: list[dict[str, str]] - if areas := config[CONF_AREAS]: - for area_conf in areas: - area_id = area_conf[CONF_ID] - area_ids.add(area_id) - area = cg.new_Pvariable(area_id) - area_id_hash = fnv1a_32bit_hash(area_id) - area_name = area_conf[CONF_NAME] - _verify_no_collisions(area_hashes, area_id, area_id_hash, CONF_AREAS) - cg.add(area.set_area_id(area_id_hash)) - cg.add(area.set_name(area_name)) - cg.add(cg.App.register_area(area)) + # Reserve space for devices + cg.add(cg.RawStatement(f"App.reserve_device({len(devices)});")) + cg.add_define("USE_DEVICES") - # Process devices - for dev_conf in devices: - device_id = dev_conf[CONF_ID] - device_id_hash = fnv1a_32bit_hash(device_id) - device_name = dev_conf[CONF_NAME] - _verify_no_collisions( - device_hashes, device_id, device_id_hash, CONF_DEVICES - ) - dev = cg.new_Pvariable(device_id) - cg.add(dev.set_device_id(device_id_hash)) - cg.add(dev.set_name(device_name)) - if CONF_AREA_ID in dev_conf: - # Get the area variable and use its area_id - area_id = dev_conf[CONF_AREA_ID] - area_id_hash = fnv1a_32bit_hash(area_id) - if area_id not in area_ids: - raise vol.Invalid( - f"Device '{device_name}' has an area_id '{area_id}' that does not exist.", - path=[CONF_DEVICES, dev_conf[CONF_ID], CONF_AREA_ID], - ) - cg.add(dev.set_area_id(area_id_hash)) - cg.add(cg.App.register_device(dev)) + # Process additional areas + areas: list[dict[str, str]] + if areas := config[CONF_AREAS]: + for area_conf in areas: + area_id = area_conf[CONF_ID] + area_ids.add(area_id) + area = cg.new_Pvariable(area_id) + area_id_hash = fnv1a_32bit_hash(area_id) + area_name = area_conf[CONF_NAME] + _verify_no_collisions(area_hashes, area_id, area_id_hash, CONF_AREAS) + cg.add(area.set_area_id(area_id_hash)) + cg.add(area.set_name(area_name)) + cg.add(cg.App.register_area(area)) + + # Process devices + for dev_conf in devices: + device_id = dev_conf[CONF_ID] + device_id_hash = fnv1a_32bit_hash(device_id) + device_name = dev_conf[CONF_NAME] + _verify_no_collisions(device_hashes, device_id, device_id_hash, CONF_DEVICES) + dev = cg.new_Pvariable(device_id) + cg.add(dev.set_device_id(device_id_hash)) + cg.add(dev.set_name(device_name)) + if CONF_AREA_ID in dev_conf: + # Get the area variable and use its area_id + area_id = dev_conf[CONF_AREA_ID] + area_id_hash = fnv1a_32bit_hash(area_id) + if area_id not in area_ids: + raise vol.Invalid( + f"Device '{device_name}' has an area_id '{area_id}' that does not exist.", + path=[CONF_DEVICES, dev_conf[CONF_ID], CONF_AREA_ID], + ) + cg.add(dev.set_area_id(area_id_hash)) + cg.add(cg.App.register_device(dev)) From a98e34d1906402ed1ad504a13c1d16cdb7028b2f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 10:02:59 +0200 Subject: [PATCH 67/99] handle collisions --- esphome/core/config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/core/config.py b/esphome/core/config.py index 0e2a127942..d870513cf9 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -534,6 +534,7 @@ async def to_code(config: ConfigType) -> None: area_id_hash = fnv1a_32bit_hash(area_id) area_name = area_conf[CONF_NAME] _verify_no_collisions(area_hashes, area_id, area_id_hash, CONF_AREAS) + area_hashes[area_id_hash] = area_name cg.add(area.set_area_id(area_id_hash)) cg.add(area.set_name(area_name)) cg.add(cg.App.register_area(area)) @@ -544,6 +545,7 @@ async def to_code(config: ConfigType) -> None: device_id_hash = fnv1a_32bit_hash(device_id) device_name = dev_conf[CONF_NAME] _verify_no_collisions(device_hashes, device_id, device_id_hash, CONF_DEVICES) + device_hashes[device_id_hash] = device_name dev = cg.new_Pvariable(device_id) cg.add(dev.set_device_id(device_id_hash)) cg.add(dev.set_name(device_name)) From b03e3b8d4abe67ee406fbfbb2aebd203a888b93d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 10:07:05 +0200 Subject: [PATCH 68/99] fixes --- esphome/core/config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index d870513cf9..cd8c0d7420 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -480,9 +480,9 @@ async def to_code(config: ConfigType) -> None: cg.add_define("USE_AREAS") # Handle area configuration - area_hashes = dict[int, str] = {} - area_ids = set[str] = set() - device_hashes = dict[int, str] = {} + area_hashes: dict[int, str] = {} + area_ids: set[str] = set() + device_hashes: dict[int, str] = {} area_conf: dict[str, str] | str | None if area_conf := config.get(CONF_AREA): if isinstance(area_conf, dict): @@ -524,7 +524,7 @@ async def to_code(config: ConfigType) -> None: cg.add(cg.RawStatement(f"App.reserve_device({len(devices)});")) cg.add_define("USE_DEVICES") - # Process additional areas + # Process additional areas from the areas list areas: list[dict[str, str]] if areas := config[CONF_AREAS]: for area_conf in areas: From 502b8a6073c8e64aef15d6907da1d0df17422048 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 12:32:25 +0200 Subject: [PATCH 69/99] fixes --- tests/dummy_main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/dummy_main.cpp b/tests/dummy_main.cpp index 3ba4c8bd07..afd393c095 100644 --- a/tests/dummy_main.cpp +++ b/tests/dummy_main.cpp @@ -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); From 61c29213a7a90794c6b11cf81391ddf589a730d8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 13:29:41 +0200 Subject: [PATCH 70/99] fixes --- esphome/core/config.py | 38 +++--- .../fixtures/areas_and_devices.yaml | 56 +++++++++ tests/integration/test_areas_and_devices.py | 116 ++++++++++++++++++ 3 files changed, 192 insertions(+), 18 deletions(-) create mode 100644 tests/integration/fixtures/areas_and_devices.yaml create mode 100644 tests/integration/test_areas_and_devices.py diff --git a/esphome/core/config.py b/esphome/core/config.py index cd8c0d7420..3a238e0453 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -6,7 +6,7 @@ from pathlib import Path import voluptuous as vol -from esphome import automation +from esphome import automation, core import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import ( @@ -483,17 +483,19 @@ async def to_code(config: ConfigType) -> None: area_hashes: dict[int, str] = {} area_ids: set[str] = set() device_hashes: dict[int, str] = {} - area_conf: dict[str, str] | str | None + area_conf: dict[str, str | core.ID] | str | None if area_conf := config.get(CONF_AREA): if isinstance(area_conf, dict): # New way: structured area configuration - area_id_str = area_conf[CONF_ID] - area_var = cg.new_Pvariable(area_id_str) - area_id = fnv1a_32bit_hash(area_id_str) + area_id: core.ID = area_conf[CONF_ID] + area_id_str: str = area_id.id + area_var = cg.new_Pvariable(area_id) + area_id_hash = fnv1a_32bit_hash(area_id_str) area_name = area_conf[CONF_NAME] else: # Old way: string-based area (deprecated) area_slug = slugify(area_conf) + area_id: core.ID = cv.declare_id(Area) area_id_str = area_slug _LOGGER.warning( "Using 'area' as a string is deprecated. Please use the new format:\n" @@ -504,19 +506,19 @@ async def to_code(config: ConfigType) -> None: area_conf, ) # Create a synthetic area for backwards compatibility - area_var = cg.Pvariable(area_slug, Area) - area_id = fnv1a_32bit_hash(area_conf) + area_var = cg.Pvariable(area_id) + area_id_hash = fnv1a_32bit_hash(area_conf) area_name = area_conf # Common setup for both ways - area_hashes[area_id] = area_name + area_hashes[area_id_hash] = area_name area_ids.add(area_id_str) - cg.add(area_var.set_area_id(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 and areas - devices: list[dict[str, str]] + devices: list[dict[str, str | core.ID]] if not (devices := config[CONF_DEVICES]): return @@ -528,11 +530,11 @@ async def to_code(config: ConfigType) -> None: areas: list[dict[str, str]] if areas := config[CONF_AREAS]: for area_conf in areas: - area_id = area_conf[CONF_ID] - area_ids.add(area_id) + area_id: core.ID = area_conf[CONF_ID] + area_ids.add(area_id.id) area = cg.new_Pvariable(area_id) - area_id_hash = fnv1a_32bit_hash(area_id) - area_name = area_conf[CONF_NAME] + area_id_hash = fnv1a_32bit_hash(area_id.id) + area_name: str = area_conf[CONF_NAME] _verify_no_collisions(area_hashes, area_id, area_id_hash, CONF_AREAS) area_hashes[area_id_hash] = area_name cg.add(area.set_area_id(area_id_hash)) @@ -542,7 +544,7 @@ async def to_code(config: ConfigType) -> None: # Process devices for dev_conf in devices: device_id = dev_conf[CONF_ID] - device_id_hash = fnv1a_32bit_hash(device_id) + device_id_hash = fnv1a_32bit_hash(device_id.id) device_name = dev_conf[CONF_NAME] _verify_no_collisions(device_hashes, device_id, device_id_hash, CONF_DEVICES) device_hashes[device_id_hash] = device_name @@ -552,10 +554,10 @@ async def to_code(config: ConfigType) -> None: if CONF_AREA_ID in dev_conf: # Get the area variable and use its area_id area_id = dev_conf[CONF_AREA_ID] - area_id_hash = fnv1a_32bit_hash(area_id) - if area_id not in area_ids: + area_id_hash = fnv1a_32bit_hash(area_id.id) + if area_id.id not in area_ids: raise vol.Invalid( - f"Device '{device_name}' has an area_id '{area_id}' that does not exist.", + f"Device '{device_name}' has an area_id '{area_id.id}' that does not exist.", path=[CONF_DEVICES, dev_conf[CONF_ID], CONF_AREA_ID], ) cg.add(dev.set_area_id(area_id_hash)) diff --git a/tests/integration/fixtures/areas_and_devices.yaml b/tests/integration/fixtures/areas_and_devices.yaml new file mode 100644 index 0000000000..6bf1519c79 --- /dev/null +++ b/tests/integration/fixtures/areas_and_devices.yaml @@ -0,0 +1,56 @@ +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 diff --git a/tests/integration/test_areas_and_devices.py b/tests/integration/test_areas_and_devices.py new file mode 100644 index 0000000000..32361f2844 --- /dev/null +++ b/tests/integration/test_areas_and_devices.py @@ -0,0 +1,116 @@ +"""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" + ) + + # 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}" + ) From f4f14a75070a2d758d6908f9fa92d7f0d113d4f1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 13:29:49 +0200 Subject: [PATCH 71/99] fixes --- tests/integration/fixtures/areas_and_devices.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/fixtures/areas_and_devices.yaml b/tests/integration/fixtures/areas_and_devices.yaml index 6bf1519c79..4a327b73a1 100644 --- a/tests/integration/fixtures/areas_and_devices.yaml +++ b/tests/integration/fixtures/areas_and_devices.yaml @@ -54,3 +54,4 @@ sensor: device_id: smart_switch_device lambda: return 4.0; update_interval: 0.1s + From 41b1bfc5043d0c8e294131cb3dfa5bc953fb15da Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 13:37:01 +0200 Subject: [PATCH 72/99] legacy test --- esphome/core/config.py | 6 ++- tests/integration/fixtures/legacy_area.yaml | 15 ++++++++ tests/integration/test_legacy_area.py | 41 +++++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 tests/integration/fixtures/legacy_area.yaml create mode 100644 tests/integration/test_legacy_area.py diff --git a/esphome/core/config.py b/esphome/core/config.py index 3a238e0453..1f40d1608e 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -495,7 +495,9 @@ async def to_code(config: ConfigType) -> None: else: # Old way: string-based area (deprecated) area_slug = slugify(area_conf) - area_id: core.ID = cv.declare_id(Area) + area_id = core.ID( + cv.validate_id_name(area_slug), is_declaration=True, type=Area + ) area_id_str = area_slug _LOGGER.warning( "Using 'area' as a string is deprecated. Please use the new format:\n" @@ -506,7 +508,7 @@ async def to_code(config: ConfigType) -> None: area_conf, ) # Create a synthetic area for backwards compatibility - area_var = cg.Pvariable(area_id) + area_var = cg.new_Pvariable(area_id) area_id_hash = fnv1a_32bit_hash(area_conf) area_name = area_conf diff --git a/tests/integration/fixtures/legacy_area.yaml b/tests/integration/fixtures/legacy_area.yaml new file mode 100644 index 0000000000..4d1617c395 --- /dev/null +++ b/tests/integration/fixtures/legacy_area.yaml @@ -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 diff --git a/tests/integration/test_legacy_area.py b/tests/integration/test_legacy_area.py new file mode 100644 index 0000000000..d10a01ec6a --- /dev/null +++ b/tests/integration/test_legacy_area.py @@ -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) From b30b527ff9ba3c65ec0c109237bfaf42a36a4bba Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 13:37:30 +0200 Subject: [PATCH 73/99] one more place to check --- tests/integration/test_areas_and_devices.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/integration/test_areas_and_devices.py b/tests/integration/test_areas_and_devices.py index 32361f2844..4ce55a30a7 100644 --- a/tests/integration/test_areas_and_devices.py +++ b/tests/integration/test_areas_and_devices.py @@ -68,6 +68,11 @@ async def test_areas_and_devices( "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() From 46b419ea8b70f5f55e5a0c847273cf91f020135c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 13:38:14 +0200 Subject: [PATCH 74/99] preen --- esphome/core/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index 1f40d1608e..be6e2cae95 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -559,7 +559,8 @@ async def to_code(config: ConfigType) -> None: area_id_hash = fnv1a_32bit_hash(area_id.id) if area_id.id not in area_ids: raise vol.Invalid( - f"Device '{device_name}' has an area_id '{area_id.id}' that does not exist.", + f"Device '{device_name}' has an area_id '{area_id.id}'" + " that does not exist.", path=[CONF_DEVICES, dev_conf[CONF_ID], CONF_AREA_ID], ) cg.add(dev.set_area_id(area_id_hash)) From 7f2d97925542eff51eb4b70de1a120ae55216876 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 13:39:12 +0200 Subject: [PATCH 75/99] preen --- esphome/core/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index be6e2cae95..c246e8dc2e 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -529,7 +529,7 @@ async def to_code(config: ConfigType) -> None: cg.add_define("USE_DEVICES") # Process additional areas from the areas list - areas: list[dict[str, str]] + areas: list[dict[str, str | core.ID]] if areas := config[CONF_AREAS]: for area_conf in areas: area_id: core.ID = area_conf[CONF_ID] From d7eae1c1a055035b0fac01a604936cceada929b9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 13:43:52 +0200 Subject: [PATCH 76/99] simplify --- esphome/core/config.py | 62 +++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index c246e8dc2e..74b2d0daa4 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -69,6 +69,27 @@ Area = cg.esphome_ns.class_("Area") VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} +def validate_area_config(value): + """Convert legacy string area to structured format.""" + if isinstance(value, str): + # Legacy string format - convert to structured format + _LOGGER.warning( + "Using 'area' as a string is deprecated. Please use the new format:\n" + "area:\n" + " id: %s\n" + ' name: "%s"', + slugify(value), + value, + ) + # Return a structured area config with the ID generated here + return { + CONF_ID: cv.declare_id(Area)(slugify(value)), + CONF_NAME: value, + } + # Already structured format + return value + + def validate_hostname(config): max_length = 31 if config[CONF_NAME_ADD_MAC_SUFFIX]: @@ -133,9 +154,9 @@ CONFIG_SCHEMA = cv.All( { cv.Required(CONF_NAME): cv.valid_name, cv.Optional(CONF_FRIENDLY_NAME, ""): cv.string, - cv.Optional(CONF_AREA): cv.Any( - cv.string, # Old way: just a string - cv.Schema( # New way: structured area + cv.Optional(CONF_AREA): cv.All( + validate_area_config, + cv.Schema( { cv.GenerateID(CONF_ID): cv.declare_id(Area), cv.Required(CONF_NAME): cv.string, @@ -483,36 +504,15 @@ async def to_code(config: ConfigType) -> None: area_hashes: dict[int, str] = {} area_ids: set[str] = set() device_hashes: dict[int, str] = {} - area_conf: dict[str, str | core.ID] | str | None + area_conf: dict[str, str | core.ID] | None if area_conf := config.get(CONF_AREA): - if isinstance(area_conf, dict): - # New way: structured area configuration - area_id: core.ID = area_conf[CONF_ID] - area_id_str: str = area_id.id - area_var = cg.new_Pvariable(area_id) - area_id_hash = fnv1a_32bit_hash(area_id_str) - area_name = area_conf[CONF_NAME] - else: - # Old way: string-based area (deprecated) - area_slug = slugify(area_conf) - area_id = core.ID( - cv.validate_id_name(area_slug), is_declaration=True, type=Area - ) - area_id_str = area_slug - _LOGGER.warning( - "Using 'area' as a string is deprecated. Please use the new format:\n" - "area:\n" - " id: %s\n" - ' name: "%s"', - area_slug, - area_conf, - ) - # Create a synthetic area for backwards compatibility - area_var = cg.new_Pvariable(area_id) - area_id_hash = fnv1a_32bit_hash(area_conf) - area_name = area_conf + # At this point, validation has already converted string to structured format + area_id: core.ID = area_conf[CONF_ID] + area_id_str: str = area_id.id + area_var = cg.new_Pvariable(area_id) + area_id_hash = fnv1a_32bit_hash(area_id_str) + area_name = area_conf[CONF_NAME] - # Common setup for both ways area_hashes[area_id_hash] = area_name area_ids.add(area_id_str) cg.add(area_var.set_area_id(area_id_hash)) From 17bf533ed7955c3dca702b9a1282b7f3d54ac5b8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 13:44:05 +0200 Subject: [PATCH 77/99] simplify --- esphome/core/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index 74b2d0daa4..a232746e19 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -69,7 +69,7 @@ Area = cg.esphome_ns.class_("Area") VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} -def validate_area_config(value): +def validate_area_config(value: dict | str) -> dict[str, str | core.ID]: """Convert legacy string area to structured format.""" if isinstance(value, str): # Legacy string format - convert to structured format From 0764fa729269ba21046495de4a655799eaa34c6b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 13:48:27 +0200 Subject: [PATCH 78/99] simplify --- esphome/core/config.py | 95 +++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 44 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index a232746e19..93fabe1495 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -490,78 +490,85 @@ async def to_code(config: ConfigType) -> None: if config[CONF_PLATFORMIO_OPTIONS]: CORE.add_job(_add_platformio_options, config[CONF_PLATFORMIO_OPTIONS]) - # Count total areas for reservation - total_areas = len(config[CONF_AREAS]) - if config.get(CONF_AREA): - total_areas += 1 - - # Reserve space for areas if any are defined - if total_areas: - cg.add(cg.RawStatement(f"App.reserve_area({total_areas});")) - cg.add_define("USE_AREAS") - - # Handle area configuration - area_hashes: dict[int, str] = {} - area_ids: set[str] = set() - device_hashes: dict[int, str] = {} - area_conf: dict[str, str | core.ID] | None - if area_conf := config.get(CONF_AREA): - # At this point, validation has already converted string to structured format + # Helper function to process an area configuration + def process_area( + area_conf: dict[str, str | core.ID], + area_hashes: dict[int, str], + area_ids: set[str], + conf_path: str | None = None, + ) -> None: + """Process and register an area configuration.""" area_id: core.ID = area_conf[CONF_ID] area_id_str: str = area_id.id - area_var = cg.new_Pvariable(area_id) area_id_hash = fnv1a_32bit_hash(area_id_str) - area_name = area_conf[CONF_NAME] + area_name: str = area_conf[CONF_NAME] + + if conf_path: # Only verify collisions for areas from CONF_AREAS list + _verify_no_collisions(area_hashes, area_id, area_id_hash, conf_path) area_hashes[area_id_hash] = area_name area_ids.add(area_id_str) + + 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 and areas - devices: list[dict[str, str | core.ID]] - if not (devices := config[CONF_DEVICES]): + # Initialize tracking structures + area_hashes: dict[int, str] = {} + area_ids: set[str] = set() + device_hashes: dict[int, str] = {} + + # Collect all areas to process + all_areas: list[tuple[dict[str, str | core.ID], str | None]] = [] + + # Add top-level area if present + if area_conf := config.get(CONF_AREA): + all_areas.append((area_conf, None)) + + # Add areas from CONF_AREAS list + all_areas.extend((area, CONF_AREAS) for area in config[CONF_AREAS]) + + # Reserve space for areas and process them + if all_areas: + cg.add(cg.RawStatement(f"App.reserve_area({len(all_areas)});")) + cg.add_define("USE_AREAS") + + for area_conf, conf_path in all_areas: + process_area(area_conf, area_hashes, area_ids, conf_path) + + # 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 additional areas from the areas list - areas: list[dict[str, str | core.ID]] - if areas := config[CONF_AREAS]: - for area_conf in areas: - area_id: core.ID = area_conf[CONF_ID] - area_ids.add(area_id.id) - area = cg.new_Pvariable(area_id) - area_id_hash = fnv1a_32bit_hash(area_id.id) - area_name: str = area_conf[CONF_NAME] - _verify_no_collisions(area_hashes, area_id, area_id_hash, CONF_AREAS) - area_hashes[area_id_hash] = area_name - cg.add(area.set_area_id(area_id_hash)) - cg.add(area.set_name(area_name)) - cg.add(cg.App.register_area(area)) - - # Process devices + # Process each device for dev_conf in devices: - device_id = dev_conf[CONF_ID] + device_id: core.ID = dev_conf[CONF_ID] device_id_hash = fnv1a_32bit_hash(device_id.id) - device_name = dev_conf[CONF_NAME] + device_name: str = dev_conf[CONF_NAME] + _verify_no_collisions(device_hashes, device_id, device_id_hash, CONF_DEVICES) device_hashes[device_id_hash] = device_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: - # Get the area variable and use its area_id - area_id = dev_conf[CONF_AREA_ID] - area_id_hash = fnv1a_32bit_hash(area_id.id) + area_id: core.ID = dev_conf[CONF_AREA_ID] if area_id.id not in area_ids: raise vol.Invalid( f"Device '{device_name}' has an area_id '{area_id.id}'" " that does not exist.", - path=[CONF_DEVICES, dev_conf[CONF_ID], CONF_AREA_ID], + path=[CONF_DEVICES, device_id, 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)) From 180aeb7d8e2c79dd78f5681a7eb833087b52337c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 13:50:29 +0200 Subject: [PATCH 79/99] simplify --- esphome/core/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index 93fabe1495..45ba214e44 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -566,7 +566,7 @@ async def to_code(config: ConfigType) -> None: raise vol.Invalid( f"Device '{device_name}' has an area_id '{area_id.id}'" " that does not exist.", - path=[CONF_DEVICES, device_id, CONF_AREA_ID], + path=[CONF_DEVICES, dev_conf[CONF_ID], CONF_AREA_ID], ) area_id_hash = fnv1a_32bit_hash(area_id.id) cg.add(dev.set_area_id(area_id_hash)) From 818a978dfc0e8b1190183791692b552cd8e5625a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 19:40:53 +0200 Subject: [PATCH 80/99] units --- tests/unit_tests/core/test_config.py | 187 +++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 tests/unit_tests/core/test_config.py diff --git a/tests/unit_tests/core/test_config.py b/tests/unit_tests/core/test_config.py new file mode 100644 index 0000000000..35245b82d3 --- /dev/null +++ b/tests/unit_tests/core/test_config.py @@ -0,0 +1,187 @@ +"""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, config_validation as cv +from esphome.config import Config +from esphome.const import CONF_AREA, CONF_AREAS, CONF_DEVICES +from esphome.core import CORE +from esphome.core.config import Area, validate_area_config + +FIXTURES_DIR = Path(__file__).parent.parent / "fixtures" / "core" / "config" + + +@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 + + +@pytest.fixture(autouse=True) +def reset_core(): + """Reset CORE after each test.""" + yield + CORE.reset() + + +def load_config_from_yaml( + yaml_file: Callable[[str], str], yaml_content: str +) -> Config | None: + """Load configuration from YAML content.""" + CORE.config_path = yaml_file(yaml_content) + return config.read_config({}) + + +def load_config_from_fixture( + yaml_file: Callable[[str], str], fixture_name: str +) -> 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) + + +def test_validate_area_config_with_string() -> None: + """Test that string area config is converted to structured format.""" + result: dict[str, Any] = validate_area_config("Living Room") + + assert isinstance(result, dict) + assert "id" in result + assert "name" in result + assert result["name"] == "Living Room" + # ID should be based on slugified name + assert result["id"].id == "living_room" + + +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: dict[str, Any] = 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") + 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") + 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") + 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 area["id"].id == "living_room" + + # Check for deprecation warning + assert "Using 'area' as a string is deprecated" in caplog.text + + +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") + assert result is None + + # Check for the specific error message in stdout + captured = capsys.readouterr() + 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") + 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 From a37bac1956fee10a1491523f080976c9495d98fc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 19:46:48 +0200 Subject: [PATCH 81/99] add files --- .../core/config/area_id_collision.yaml | 10 +++++++++ .../core/config/device_without_area.yaml | 7 ++++++ .../core/config/legacy_string_area.yaml | 5 +++++ .../core/config/multiple_areas_devices.yaml | 22 +++++++++++++++++++ .../core/config/valid_area_device.yaml | 11 ++++++++++ 5 files changed, 55 insertions(+) create mode 100644 tests/unit_tests/fixtures/core/config/area_id_collision.yaml create mode 100644 tests/unit_tests/fixtures/core/config/device_without_area.yaml create mode 100644 tests/unit_tests/fixtures/core/config/legacy_string_area.yaml create mode 100644 tests/unit_tests/fixtures/core/config/multiple_areas_devices.yaml create mode 100644 tests/unit_tests/fixtures/core/config/valid_area_device.yaml diff --git a/tests/unit_tests/fixtures/core/config/area_id_collision.yaml b/tests/unit_tests/fixtures/core/config/area_id_collision.yaml new file mode 100644 index 0000000000..985db073da --- /dev/null +++ b/tests/unit_tests/fixtures/core/config/area_id_collision.yaml @@ -0,0 +1,10 @@ +esphome: + name: test-collision + area: + id: duplicate_id + name: Area 1 + areas: + - id: duplicate_id + name: Area 2 + +host: \ No newline at end of file diff --git a/tests/unit_tests/fixtures/core/config/device_without_area.yaml b/tests/unit_tests/fixtures/core/config/device_without_area.yaml new file mode 100644 index 0000000000..cc81953d42 --- /dev/null +++ b/tests/unit_tests/fixtures/core/config/device_without_area.yaml @@ -0,0 +1,7 @@ +esphome: + name: test-device-no-area + devices: + - id: test_device + name: Test Device + +host: \ No newline at end of file diff --git a/tests/unit_tests/fixtures/core/config/legacy_string_area.yaml b/tests/unit_tests/fixtures/core/config/legacy_string_area.yaml new file mode 100644 index 0000000000..136c2aafac --- /dev/null +++ b/tests/unit_tests/fixtures/core/config/legacy_string_area.yaml @@ -0,0 +1,5 @@ +esphome: + name: test-legacy-area + area: Living Room + +host: \ No newline at end of file diff --git a/tests/unit_tests/fixtures/core/config/multiple_areas_devices.yaml b/tests/unit_tests/fixtures/core/config/multiple_areas_devices.yaml new file mode 100644 index 0000000000..0ffee3177c --- /dev/null +++ b/tests/unit_tests/fixtures/core/config/multiple_areas_devices.yaml @@ -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: \ No newline at end of file diff --git a/tests/unit_tests/fixtures/core/config/valid_area_device.yaml b/tests/unit_tests/fixtures/core/config/valid_area_device.yaml new file mode 100644 index 0000000000..54e1262819 --- /dev/null +++ b/tests/unit_tests/fixtures/core/config/valid_area_device.yaml @@ -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: \ No newline at end of file From 85e3b63f059c1e5728304cb937bf5789a268e804 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 19:49:12 +0200 Subject: [PATCH 82/99] adjust --- tests/unit_tests/fixtures/core/config/area_id_collision.yaml | 2 +- tests/unit_tests/fixtures/core/config/device_without_area.yaml | 2 +- tests/unit_tests/fixtures/core/config/legacy_string_area.yaml | 2 +- .../unit_tests/fixtures/core/config/multiple_areas_devices.yaml | 2 +- tests/unit_tests/fixtures/core/config/valid_area_device.yaml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/unit_tests/fixtures/core/config/area_id_collision.yaml b/tests/unit_tests/fixtures/core/config/area_id_collision.yaml index 985db073da..fb2e930e61 100644 --- a/tests/unit_tests/fixtures/core/config/area_id_collision.yaml +++ b/tests/unit_tests/fixtures/core/config/area_id_collision.yaml @@ -7,4 +7,4 @@ esphome: - id: duplicate_id name: Area 2 -host: \ No newline at end of file +host: diff --git a/tests/unit_tests/fixtures/core/config/device_without_area.yaml b/tests/unit_tests/fixtures/core/config/device_without_area.yaml index cc81953d42..8464cf37df 100644 --- a/tests/unit_tests/fixtures/core/config/device_without_area.yaml +++ b/tests/unit_tests/fixtures/core/config/device_without_area.yaml @@ -4,4 +4,4 @@ esphome: - id: test_device name: Test Device -host: \ No newline at end of file +host: diff --git a/tests/unit_tests/fixtures/core/config/legacy_string_area.yaml b/tests/unit_tests/fixtures/core/config/legacy_string_area.yaml index 136c2aafac..fe2dc3db17 100644 --- a/tests/unit_tests/fixtures/core/config/legacy_string_area.yaml +++ b/tests/unit_tests/fixtures/core/config/legacy_string_area.yaml @@ -2,4 +2,4 @@ esphome: name: test-legacy-area area: Living Room -host: \ No newline at end of file +host: diff --git a/tests/unit_tests/fixtures/core/config/multiple_areas_devices.yaml b/tests/unit_tests/fixtures/core/config/multiple_areas_devices.yaml index 0ffee3177c..ef3b4f6e67 100644 --- a/tests/unit_tests/fixtures/core/config/multiple_areas_devices.yaml +++ b/tests/unit_tests/fixtures/core/config/multiple_areas_devices.yaml @@ -19,4 +19,4 @@ esphome: name: Device 3 area_id: area2 -host: \ No newline at end of file +host: diff --git a/tests/unit_tests/fixtures/core/config/valid_area_device.yaml b/tests/unit_tests/fixtures/core/config/valid_area_device.yaml index 54e1262819..fc97894586 100644 --- a/tests/unit_tests/fixtures/core/config/valid_area_device.yaml +++ b/tests/unit_tests/fixtures/core/config/valid_area_device.yaml @@ -8,4 +8,4 @@ esphome: name: Test Device area_id: bedroom_area -host: \ No newline at end of file +host: From 25ed7c890b821bc84431024cd4b62083c68d34d4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 20:03:02 +0200 Subject: [PATCH 83/99] cleanups --- tests/unit_tests/core/test_config.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/unit_tests/core/test_config.py b/tests/unit_tests/core/test_config.py index 35245b82d3..d31a66bdf6 100644 --- a/tests/unit_tests/core/test_config.py +++ b/tests/unit_tests/core/test_config.py @@ -3,10 +3,11 @@ from collections.abc import Callable from pathlib import Path from typing import Any +from unittest.mock import patch import pytest -from esphome import config, config_validation as cv +from esphome import config, config_validation as cv, yaml_util from esphome.config import Config from esphome.const import CONF_AREA, CONF_AREAS, CONF_DEVICES from esphome.core import CORE @@ -38,8 +39,15 @@ def load_config_from_yaml( yaml_file: Callable[[str], str], yaml_content: str ) -> Config | None: """Load configuration from YAML content.""" - CORE.config_path = yaml_file(yaml_content) - return config.read_config({}) + 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( From a90d59b6ba3a894385c4e5a3d6e4e9f0c630f217 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 20:59:07 +0200 Subject: [PATCH 84/99] validate sooner --- esphome/core/config.py | 77 ++++++++++++++++++---------- tests/unit_tests/core/test_config.py | 50 +++++++++++++++++- 2 files changed, 99 insertions(+), 28 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index 45ba214e44..4d28a81229 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -108,6 +108,50 @@ def validate_hostname(config): return config +def validate_id_hash_collisions(config: dict) -> dict: + """Validate that there are no hash collisions between IDs of the same type.""" + from esphome.helpers import fnv1a_32bit_hash + + # Check area hash collisions + area_hashes: dict[int, str] = {} + + # Check main area if present + if CONF_AREA in config: + area_id: core.ID = config[CONF_AREA][CONF_ID] + if area_id.id: + area_hash = fnv1a_32bit_hash(area_id.id) + area_hashes[area_hash] = area_id.id + + # Check areas list + for area in config.get(CONF_AREAS, []): + area_id: core.ID = area[CONF_ID] + if area_id.id: + area_hash = fnv1a_32bit_hash(area_id.id) + if area_hash in area_hashes: + raise cv.Invalid( + f"Area ID '{area_id.id}' with hash {area_hash} collides with " + f"existing area ID '{area_hashes[area_hash]}'", + path=[CONF_AREAS, area_id.id], + ) + area_hashes[area_hash] = area_id.id + + # Check device hash collisions + device_hashes: dict[int, str] = {} + for device in config.get(CONF_DEVICES, []): + device_id: core.ID = device[CONF_ID] + if device_id.id: + device_hash = fnv1a_32bit_hash(device_id.id) + if device_hash in device_hashes: + raise cv.Invalid( + f"Device ID '{device_id.id}' with hash {device_hash} collides with " + f"existing device ID '{device_hashes[device_hash]}'", + path=[CONF_DEVICES, device_id.id], + ) + device_hashes[device_hash] = device_id.id + + return config + + def valid_include(value): # Look for "<...>" includes if value.startswith("<") and value.endswith(">"): @@ -232,6 +276,7 @@ CONFIG_SCHEMA = cv.All( } ), validate_hostname, + validate_id_hash_collisions, ) PRELOAD_CONFIG_SCHEMA = cv.Schema( @@ -397,17 +442,6 @@ async def _add_platform_reserves() -> None: cg.add(cg.RawStatement(f"App.reserve_{platform_name}({count});"), prepend=True) -def _verify_no_collisions( - hashes: dict[int, str], id: str, id_hash: int, conf_key: str -) -> None: - """Verify that the given id and name do not collide with existing ones.""" - if id_hash in hashes: - raise vol.Invalid( - f"ID '{id}' with hash {id_hash} collides with existing ID '{hashes[id_hash]}'", - path=[conf_key], - ) - - @coroutine_with_priority(100.0) async def to_code(config: ConfigType) -> None: cg.add_global(cg.global_ns.namespace("esphome").using) @@ -493,9 +527,7 @@ async def to_code(config: ConfigType) -> None: # Helper function to process an area configuration def process_area( area_conf: dict[str, str | core.ID], - area_hashes: dict[int, str], area_ids: set[str], - conf_path: str | None = None, ) -> None: """Process and register an area configuration.""" area_id: core.ID = area_conf[CONF_ID] @@ -503,10 +535,6 @@ async def to_code(config: ConfigType) -> None: area_id_hash = fnv1a_32bit_hash(area_id_str) area_name: str = area_conf[CONF_NAME] - if conf_path: # Only verify collisions for areas from CONF_AREAS list - _verify_no_collisions(area_hashes, area_id, area_id_hash, conf_path) - - area_hashes[area_id_hash] = area_name area_ids.add(area_id_str) area_var = cg.new_Pvariable(area_id) @@ -515,27 +543,25 @@ async def to_code(config: ConfigType) -> None: cg.add(cg.App.register_area(area_var)) # Initialize tracking structures - area_hashes: dict[int, str] = {} area_ids: set[str] = set() - device_hashes: dict[int, str] = {} # Collect all areas to process - all_areas: list[tuple[dict[str, str | core.ID], str | None]] = [] + all_areas: list[dict[str, str | core.ID]] = [] # Add top-level area if present if area_conf := config.get(CONF_AREA): - all_areas.append((area_conf, None)) + all_areas.append(area_conf) # Add areas from CONF_AREAS list - all_areas.extend((area, CONF_AREAS) for area in config[CONF_AREAS]) + all_areas.extend(config[CONF_AREAS]) # Reserve space for areas and process them if all_areas: cg.add(cg.RawStatement(f"App.reserve_area({len(all_areas)});")) cg.add_define("USE_AREAS") - for area_conf, conf_path in all_areas: - process_area(area_conf, area_hashes, area_ids, conf_path) + for area_conf in all_areas: + process_area(area_conf, area_ids) # Process devices devices: list[dict[str, str | core.ID]] = config[CONF_DEVICES] @@ -552,9 +578,6 @@ async def to_code(config: ConfigType) -> None: device_id_hash = fnv1a_32bit_hash(device_id.id) device_name: str = dev_conf[CONF_NAME] - _verify_no_collisions(device_hashes, device_id, device_id_hash, CONF_DEVICES) - device_hashes[device_id_hash] = device_name - dev = cg.new_Pvariable(device_id) cg.add(dev.set_device_id(device_id_hash)) cg.add(dev.set_name(device_name)) diff --git a/tests/unit_tests/core/test_config.py b/tests/unit_tests/core/test_config.py index d31a66bdf6..11a80e4cc5 100644 --- a/tests/unit_tests/core/test_config.py +++ b/tests/unit_tests/core/test_config.py @@ -172,7 +172,11 @@ def test_area_id_collision( # Check for the specific error message in stdout captured = capsys.readouterr() - assert "ID duplicate_id redefined! Check esphome->area->id." in captured.out + # Since duplicate IDs have the same hash, our hash collision detection catches this + assert ( + "Area ID 'duplicate_id' with hash 1805131238 collides with existing area ID 'duplicate_id'" + in captured.out + ) def test_device_without_area(yaml_file: Callable[[str], str]) -> None: @@ -193,3 +197,47 @@ def test_device_without_area(yaml_file: Callable[[str], str]) -> None: # 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") + assert result is None + + # Check for the specific error message in stdout + captured = capsys.readouterr() + assert "Couldn't find ID 'nonexistent_area'" 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") + 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") + 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 + ) From 7be12f5ff6d43d075c797c315f8399a976aadaa5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 20:59:54 +0200 Subject: [PATCH 85/99] validate sooner --- .../fixtures/core/config/area_id_hash_collision.yaml | 10 ++++++++++ .../fixtures/core/config/device_id_collision.yaml | 10 ++++++++++ .../fixtures/core/config/device_invalid_area.yaml | 12 ++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 tests/unit_tests/fixtures/core/config/area_id_hash_collision.yaml create mode 100644 tests/unit_tests/fixtures/core/config/device_id_collision.yaml create mode 100644 tests/unit_tests/fixtures/core/config/device_invalid_area.yaml diff --git a/tests/unit_tests/fixtures/core/config/area_id_hash_collision.yaml b/tests/unit_tests/fixtures/core/config/area_id_hash_collision.yaml new file mode 100644 index 0000000000..0fb932494d --- /dev/null +++ b/tests/unit_tests/fixtures/core/config/area_id_hash_collision.yaml @@ -0,0 +1,10 @@ +esphome: + name: test + areas: + - id: test_2258 + name: "Area 1" + - id: d6ka + name: "Area 2" + +esp32: + board: esp32dev \ No newline at end of file diff --git a/tests/unit_tests/fixtures/core/config/device_id_collision.yaml b/tests/unit_tests/fixtures/core/config/device_id_collision.yaml new file mode 100644 index 0000000000..a34454fc26 --- /dev/null +++ b/tests/unit_tests/fixtures/core/config/device_id_collision.yaml @@ -0,0 +1,10 @@ +esphome: + name: test + devices: + - id: test_2258 + name: "Device 1" + - id: d6ka + name: "Device 2" + +esp32: + board: esp32dev \ No newline at end of file diff --git a/tests/unit_tests/fixtures/core/config/device_invalid_area.yaml b/tests/unit_tests/fixtures/core/config/device_invalid_area.yaml new file mode 100644 index 0000000000..e27976cbbc --- /dev/null +++ b/tests/unit_tests/fixtures/core/config/device_invalid_area.yaml @@ -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 \ No newline at end of file From 02019dd16c60fd0cbb0ec3eb80b85bbda42fc4e9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 21:04:42 +0200 Subject: [PATCH 86/99] validate sooner --- esphome/core/config.py | 55 +++++++++++++++++----------- tests/unit_tests/core/test_config.py | 19 +++++++--- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index 4d28a81229..7358276754 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -109,13 +109,10 @@ def validate_hostname(config): def validate_id_hash_collisions(config: dict) -> dict: - """Validate that there are no hash collisions between IDs of the same type.""" - from esphome.helpers import fnv1a_32bit_hash - - # Check area hash collisions + """Validate that there are no hash collisions between IDs.""" area_hashes: dict[int, str] = {} - # Check main area if present + # Check main area if CONF_AREA in config: area_id: core.ID = config[CONF_AREA][CONF_ID] if area_id.id: @@ -125,29 +122,45 @@ def validate_id_hash_collisions(config: dict) -> dict: # Check areas list for area in config.get(CONF_AREAS, []): area_id: core.ID = area[CONF_ID] - if area_id.id: - area_hash = fnv1a_32bit_hash(area_id.id) - if area_hash in area_hashes: - raise cv.Invalid( - f"Area ID '{area_id.id}' with hash {area_hash} collides with " - f"existing area ID '{area_hashes[area_hash]}'", - path=[CONF_AREAS, area_id.id], - ) + if not area_id.id: + continue + + area_hash = fnv1a_32bit_hash(area_id.id) + if area_hash not in area_hashes: area_hashes[area_hash] = area_id.id + continue + + # Skip exact duplicates (handled by IDPassValidationStep) + if area_id.id == area_hashes[area_hash]: + continue + + raise cv.Invalid( + f"Area ID '{area_id.id}' with hash {area_hash} collides with " + f"existing area ID '{area_hashes[area_hash]}'", + path=[CONF_AREAS, area_id.id], + ) # Check device hash collisions device_hashes: dict[int, str] = {} for device in config.get(CONF_DEVICES, []): device_id: core.ID = device[CONF_ID] - if device_id.id: - device_hash = fnv1a_32bit_hash(device_id.id) - if device_hash in device_hashes: - raise cv.Invalid( - f"Device ID '{device_id.id}' with hash {device_hash} collides with " - f"existing device ID '{device_hashes[device_hash]}'", - path=[CONF_DEVICES, device_id.id], - ) + if not device_id.id: + continue + + device_hash = fnv1a_32bit_hash(device_id.id) + if device_hash not in device_hashes: device_hashes[device_hash] = device_id.id + continue + + # Skip exact duplicates (handled by IDPassValidationStep) + if device_id.id == device_hashes[device_hash]: + continue + + raise cv.Invalid( + f"Device ID '{device_id.id}' with hash {device_hash} collides " + f"with existing device ID '{device_hashes[device_hash]}'", + path=[CONF_DEVICES, device_id.id], + ) return config diff --git a/tests/unit_tests/core/test_config.py b/tests/unit_tests/core/test_config.py index 11a80e4cc5..ed442b93fa 100644 --- a/tests/unit_tests/core/test_config.py +++ b/tests/unit_tests/core/test_config.py @@ -172,11 +172,8 @@ def test_area_id_collision( # Check for the specific error message in stdout captured = capsys.readouterr() - # Since duplicate IDs have the same hash, our hash collision detection catches this - assert ( - "Area ID 'duplicate_id' with hash 1805131238 collides with existing area ID 'duplicate_id'" - in captured.out - ) + # 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: @@ -241,3 +238,15 @@ def test_area_id_hash_collision( "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") + assert result is None + + # Check for the specific error message from IDPassValidationStep + captured = capsys.readouterr() + assert "ID duplicate_device redefined!" in captured.out From b01eb28d4248b435f6736feafc29750d4f1f78a4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 21:05:15 +0200 Subject: [PATCH 87/99] validate sooner --- .../fixtures/core/config/device_duplicate_id.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tests/unit_tests/fixtures/core/config/device_duplicate_id.yaml diff --git a/tests/unit_tests/fixtures/core/config/device_duplicate_id.yaml b/tests/unit_tests/fixtures/core/config/device_duplicate_id.yaml new file mode 100644 index 0000000000..345d05502f --- /dev/null +++ b/tests/unit_tests/fixtures/core/config/device_duplicate_id.yaml @@ -0,0 +1,10 @@ +esphome: + name: test + devices: + - id: duplicate_device + name: "Device 1" + - id: duplicate_device + name: "Device 2" + +esp32: + board: esp32dev \ No newline at end of file From d3b18debf9dc237622890e5c07779d850b8854e5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 21:06:33 +0200 Subject: [PATCH 88/99] validate sooner --- esphome/core/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index 7358276754..d08441d3fd 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -120,7 +120,7 @@ def validate_id_hash_collisions(config: dict) -> dict: area_hashes[area_hash] = area_id.id # Check areas list - for area in config.get(CONF_AREAS, []): + for area in config[CONF_AREAS]: area_id: core.ID = area[CONF_ID] if not area_id.id: continue @@ -142,7 +142,7 @@ def validate_id_hash_collisions(config: dict) -> dict: # Check device hash collisions device_hashes: dict[int, str] = {} - for device in config.get(CONF_DEVICES, []): + for device in config[CONF_DEVICES]: device_id: core.ID = device[CONF_ID] if not device_id.id: continue From 2b9b7e285379096911829fef9ea4a6981fdb0c33 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 21:18:04 +0200 Subject: [PATCH 89/99] validation should happen sooner --- esphome/core/config.py | 142 +++++++++++---------------- tests/unit_tests/core/test_config.py | 5 +- 2 files changed, 62 insertions(+), 85 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index d08441d3fd..fb658de6b9 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -4,8 +4,6 @@ import logging import os from pathlib import Path -import voluptuous as vol - from esphome import automation, core import esphome.codegen as cg import esphome.config_validation as cv @@ -108,60 +106,61 @@ def validate_hostname(config): return config -def validate_id_hash_collisions(config: dict) -> dict: - """Validate that there are no hash collisions between IDs.""" - area_hashes: dict[int, str] = {} +def validate_ids_and_references(config: ConfigType) -> ConfigType: + """Validate that there are no hash collisions between IDs and that area_id references are valid.""" - # Check main area + # 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], + ) -> bool: + if not id_obj.id: + return False + + 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 + return True + + # Collect all areas + all_areas: list[dict[str, str | core.ID]] = [] if CONF_AREA in config: - area_id: core.ID = config[CONF_AREA][CONF_ID] - if area_id.id: - area_hash = fnv1a_32bit_hash(area_id.id) - area_hashes[area_hash] = area_id.id + all_areas.append(config[CONF_AREA]) + all_areas.extend(config[CONF_AREAS]) - # Check areas list - for area in 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] - if not area_id.id: - continue + if check_hash_collision(area_id, area_hashes, "Area", [CONF_AREAS, area_id.id]): + area_ids.add(area_id.id) - area_hash = fnv1a_32bit_hash(area_id.id) - if area_hash not in area_hashes: - area_hashes[area_hash] = area_id.id - continue - - # Skip exact duplicates (handled by IDPassValidationStep) - if area_id.id == area_hashes[area_hash]: - continue - - raise cv.Invalid( - f"Area ID '{area_id.id}' with hash {area_hash} collides with " - f"existing area ID '{area_hashes[area_hash]}'", - path=[CONF_AREAS, area_id.id], - ) - - # Check device hash collisions + # Validate device hash collisions and area references device_hashes: dict[int, str] = {} - for device in config[CONF_DEVICES]: + for i, device in enumerate(config[CONF_DEVICES]): device_id: core.ID = device[CONF_ID] - if not device_id.id: - continue - - device_hash = fnv1a_32bit_hash(device_id.id) - if device_hash not in device_hashes: - device_hashes[device_hash] = device_id.id - continue - - # Skip exact duplicates (handled by IDPassValidationStep) - if device_id.id == device_hashes[device_hash]: - continue - - raise cv.Invalid( - f"Device ID '{device_id.id}' with hash {device_hash} collides " - f"with existing device ID '{device_hashes[device_hash]}'", - path=[CONF_DEVICES, device_id.id], + check_hash_collision( + device_id, device_hashes, "Device", [CONF_DEVICES, device_id.id] ) + # Validate area_id reference if present + if CONF_AREA_ID in device: + area_ref_id: core.ID = device[CONF_AREA_ID] + if area_ref_id.id not in area_ids: + raise cv.Invalid( + f"Device '{device[CONF_NAME]}' has an area_id '{area_ref_id.id}'" + " that does not exist.", + path=[CONF_DEVICES, i, CONF_AREA_ID], + ) + return config @@ -289,7 +288,7 @@ CONFIG_SCHEMA = cv.All( } ), validate_hostname, - validate_id_hash_collisions, + validate_ids_and_references, ) PRELOAD_CONFIG_SCHEMA = cv.Schema( @@ -537,44 +536,25 @@ async def to_code(config: ConfigType) -> None: if config[CONF_PLATFORMIO_OPTIONS]: CORE.add_job(_add_platformio_options, config[CONF_PLATFORMIO_OPTIONS]) - # Helper function to process an area configuration - def process_area( - area_conf: dict[str, str | core.ID], - area_ids: set[str], - ) -> None: - """Process and register an area configuration.""" - area_id: core.ID = area_conf[CONF_ID] - area_id_str: str = area_id.id - area_id_hash = fnv1a_32bit_hash(area_id_str) - area_name: str = area_conf[CONF_NAME] - - area_ids.add(area_id_str) - - 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)) - - # Initialize tracking structures - area_ids: set[str] = set() - - # Collect all areas to process + # Process areas all_areas: list[dict[str, str | core.ID]] = [] - - # Add top-level area if present - if area_conf := config.get(CONF_AREA): - all_areas.append(area_conf) - - # Add areas from CONF_AREAS list + if CONF_AREA in config: + all_areas.append(config[CONF_AREA]) all_areas.extend(config[CONF_AREAS]) - # Reserve space for areas and process them 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: - process_area(area_conf, area_ids) + 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] @@ -598,12 +578,6 @@ async def to_code(config: ConfigType) -> None: # Set area if specified if CONF_AREA_ID in dev_conf: area_id: core.ID = dev_conf[CONF_AREA_ID] - if area_id.id not in area_ids: - raise vol.Invalid( - f"Device '{device_name}' has an area_id '{area_id.id}'" - " that does not exist.", - path=[CONF_DEVICES, dev_conf[CONF_ID], CONF_AREA_ID], - ) area_id_hash = fnv1a_32bit_hash(area_id.id) cg.add(dev.set_area_id(area_id_hash)) diff --git a/tests/unit_tests/core/test_config.py b/tests/unit_tests/core/test_config.py index ed442b93fa..6a28925dd3 100644 --- a/tests/unit_tests/core/test_config.py +++ b/tests/unit_tests/core/test_config.py @@ -205,7 +205,10 @@ def test_device_with_invalid_area_id( # Check for the specific error message in stdout captured = capsys.readouterr() - assert "Couldn't find ID 'nonexistent_area'" in captured.out + assert ( + "Device 'Test Device' has an area_id 'nonexistent_area' that does not exist." + in captured.out + ) def test_device_id_hash_collision( From c1853f8b84098e4de7fb35193da66451d68d4e7c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 21:21:29 +0200 Subject: [PATCH 90/99] document design decisions --- esphome/core/config.py | 8 +++++++- esphome/helpers.py | 14 +++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index fb658de6b9..23a18e4c2e 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -107,7 +107,13 @@ def validate_hostname(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.""" + """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( diff --git a/esphome/helpers.py b/esphome/helpers.py index c84d597999..bf0e3b5cf7 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -30,7 +30,19 @@ def ensure_unique_string(preferred_string, current_strings): def fnv1a_32bit_hash(string: str) -> int: - """FNV-1a 32-bit hash function.""" + """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) From 8831999ea6a5da7aa6ecb64778f21fcc5add903c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 21:23:41 +0200 Subject: [PATCH 91/99] lint --- .../fixtures/core/config/area_id_hash_collision.yaml | 9 --------- .../fixtures/core/config/device_duplicate_id.yaml | 9 --------- .../fixtures/core/config/device_invalid_area.yaml | 11 ----------- 3 files changed, 29 deletions(-) diff --git a/tests/unit_tests/fixtures/core/config/area_id_hash_collision.yaml b/tests/unit_tests/fixtures/core/config/area_id_hash_collision.yaml index 0fb932494d..8b13789179 100644 --- a/tests/unit_tests/fixtures/core/config/area_id_hash_collision.yaml +++ b/tests/unit_tests/fixtures/core/config/area_id_hash_collision.yaml @@ -1,10 +1 @@ -esphome: - name: test - areas: - - id: test_2258 - name: "Area 1" - - id: d6ka - name: "Area 2" -esp32: - board: esp32dev \ No newline at end of file diff --git a/tests/unit_tests/fixtures/core/config/device_duplicate_id.yaml b/tests/unit_tests/fixtures/core/config/device_duplicate_id.yaml index 345d05502f..8b13789179 100644 --- a/tests/unit_tests/fixtures/core/config/device_duplicate_id.yaml +++ b/tests/unit_tests/fixtures/core/config/device_duplicate_id.yaml @@ -1,10 +1 @@ -esphome: - name: test - devices: - - id: duplicate_device - name: "Device 1" - - id: duplicate_device - name: "Device 2" -esp32: - board: esp32dev \ No newline at end of file diff --git a/tests/unit_tests/fixtures/core/config/device_invalid_area.yaml b/tests/unit_tests/fixtures/core/config/device_invalid_area.yaml index e27976cbbc..8b13789179 100644 --- a/tests/unit_tests/fixtures/core/config/device_invalid_area.yaml +++ b/tests/unit_tests/fixtures/core/config/device_invalid_area.yaml @@ -1,12 +1 @@ -esphome: - name: test - areas: - - id: valid_area - name: "Valid Area" - devices: - - id: test_device - name: "Test Device" - area_id: nonexistent_area -esp32: - board: esp32dev \ No newline at end of file From 68b13340fb5b866de26c2b72fd459bbebaba4fc6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 21:24:17 +0200 Subject: [PATCH 92/99] lint --- esphome/config_validation.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index a3627efe7b..0665ffe39c 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -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 @@ -348,7 +350,7 @@ def icon(value): ) -def sub_device_id(value): +def sub_device_id(value) -> core.ID: # Lazy import to avoid circular imports from esphome.core.config import Device @@ -1931,7 +1933,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}") From c34ba3deb593be97d58ed04b78ddb5134f90804d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 21:25:55 +0200 Subject: [PATCH 93/99] lint --- esphome/config_validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 0665ffe39c..ec17ec986d 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -350,7 +350,7 @@ def icon(value): ) -def sub_device_id(value) -> core.ID: +def sub_device_id(value: str | None) -> core.ID: # Lazy import to avoid circular imports from esphome.core.config import Device From b725bb3dd199eadded51bf00377c1daf21bcd6db Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 21:28:16 +0200 Subject: [PATCH 94/99] lint --- esphome/cpp_helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index cef7b31020..8d5440f591 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -112,8 +112,8 @@ async def setup_entity(var, config): if CONF_ENTITY_CATEGORY in config: add(var.set_entity_category(config[CONF_ENTITY_CATEGORY])) if CONF_DEVICE_ID in config: - device = await get_variable(config[CONF_DEVICE_ID]) - add(var.set_device_id(fnv1a_32bit_hash(str(device)))) + device_id: ID = config[CONF_DEVICE_ID] + add(var.set_device_id(fnv1a_32bit_hash(device_id.id))) def extract_registry_entry_config( From ba87a0b63c0845942a5b466831caf0ffc1bf20c9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 21:32:20 +0200 Subject: [PATCH 95/99] cleanups --- esphome/config_validation.py | 3 +-- esphome/dashboard/util/text.py | 2 +- .../fixtures/core/config/area_id_hash_collision.yaml | 9 +++++++++ .../fixtures/core/config/device_duplicate_id.yaml | 9 +++++++++ .../fixtures/core/config/device_id_collision.yaml | 2 +- .../fixtures/core/config/device_invalid_area.yaml | 11 +++++++++++ 6 files changed, 32 insertions(+), 4 deletions(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index ec17ec986d..27f9a5b83f 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -354,8 +354,7 @@ def sub_device_id(value: str | None) -> core.ID: # Lazy import to avoid circular imports from esphome.core.config import Device - validator = use_id(Device) - return validator(value) + return use_id(Device)(value) def boolean(value): diff --git a/esphome/dashboard/util/text.py b/esphome/dashboard/util/text.py index 5c75061637..2a3b9042e6 100644 --- a/esphome/dashboard/util/text.py +++ b/esphome/dashboard/util/text.py @@ -3,7 +3,7 @@ from __future__ import annotations from esphome.helpers import slugify -def friendly_name_slugify(value): +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("_", "-") diff --git a/tests/unit_tests/fixtures/core/config/area_id_hash_collision.yaml b/tests/unit_tests/fixtures/core/config/area_id_hash_collision.yaml index 8b13789179..3a2e8ab8a9 100644 --- a/tests/unit_tests/fixtures/core/config/area_id_hash_collision.yaml +++ b/tests/unit_tests/fixtures/core/config/area_id_hash_collision.yaml @@ -1 +1,10 @@ +esphome: + name: test + areas: + - id: test_2258 + name: "Area 1" + - id: d6ka + name: "Area 2" +esp32: + board: esp32dev diff --git a/tests/unit_tests/fixtures/core/config/device_duplicate_id.yaml b/tests/unit_tests/fixtures/core/config/device_duplicate_id.yaml index 8b13789179..2aa3055686 100644 --- a/tests/unit_tests/fixtures/core/config/device_duplicate_id.yaml +++ b/tests/unit_tests/fixtures/core/config/device_duplicate_id.yaml @@ -1 +1,10 @@ +esphome: + name: test + devices: + - id: duplicate_device + name: "Device 1" + - id: duplicate_device + name: "Device 2" +esp32: + board: esp32dev diff --git a/tests/unit_tests/fixtures/core/config/device_id_collision.yaml b/tests/unit_tests/fixtures/core/config/device_id_collision.yaml index a34454fc26..9cf04e0595 100644 --- a/tests/unit_tests/fixtures/core/config/device_id_collision.yaml +++ b/tests/unit_tests/fixtures/core/config/device_id_collision.yaml @@ -7,4 +7,4 @@ esphome: name: "Device 2" esp32: - board: esp32dev \ No newline at end of file + board: esp32dev diff --git a/tests/unit_tests/fixtures/core/config/device_invalid_area.yaml b/tests/unit_tests/fixtures/core/config/device_invalid_area.yaml index 8b13789179..9a8ec0a1eb 100644 --- a/tests/unit_tests/fixtures/core/config/device_invalid_area.yaml +++ b/tests/unit_tests/fixtures/core/config/device_invalid_area.yaml @@ -1 +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 From a5ea0cd41f4800d7bf48123c6c14087c08b39b84 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Jun 2025 21:55:23 +0200 Subject: [PATCH 96/99] remove unreachable code --- esphome/core/config.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index 23a18e4c2e..bc7d31534b 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -121,10 +121,7 @@ def validate_ids_and_references(config: ConfigType) -> ConfigType: hash_dict: dict[int, str], item_type: str, path: list[str | int], - ) -> bool: - if not id_obj.id: - return False - + ) -> 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( @@ -133,7 +130,6 @@ def validate_ids_and_references(config: ConfigType) -> ConfigType: path=path, ) hash_dict[hash_val] = id_obj.id - return True # Collect all areas all_areas: list[dict[str, str | core.ID]] = [] @@ -146,8 +142,8 @@ def validate_ids_and_references(config: ConfigType) -> ConfigType: area_ids: set[str] = set() for area in all_areas: area_id: core.ID = area[CONF_ID] - if check_hash_collision(area_id, area_hashes, "Area", [CONF_AREAS, area_id.id]): - area_ids.add(area_id.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] = {} From 06de58ff8b0a0b210e289fc978473456df567eb3 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 23 Jun 2025 09:20:53 +1200 Subject: [PATCH 97/99] Dont need to warning about simple string area A single device in a single area can have a simple string as the area --- esphome/core/config.py | 66 ++++++++++++------------------------------ 1 file changed, 18 insertions(+), 48 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index bc7d31534b..00b36e7899 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -42,7 +42,6 @@ from esphome.helpers import ( copy_file_if_changed, fnv1a_32bit_hash, get_str_env, - slugify, walk_files, ) from esphome.types import ConfigType @@ -67,27 +66,6 @@ Area = cg.esphome_ns.class_("Area") VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} -def validate_area_config(value: dict | str) -> dict[str, str | core.ID]: - """Convert legacy string area to structured format.""" - if isinstance(value, str): - # Legacy string format - convert to structured format - _LOGGER.warning( - "Using 'area' as a string is deprecated. Please use the new format:\n" - "area:\n" - " id: %s\n" - ' name: "%s"', - slugify(value), - value, - ) - # Return a structured area config with the ID generated here - return { - CONF_ID: cv.declare_id(Area)(slugify(value)), - CONF_NAME: value, - } - # Already structured format - return value - - def validate_hostname(config): max_length = 31 if config[CONF_NAME_ADD_MAC_SUFFIX]: @@ -206,21 +184,28 @@ 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), + } +) + 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.All( - validate_area_config, - cv.Schema( - { - cv.GenerateID(CONF_ID): cv.declare_id(Area), - cv.Required(CONF_NAME): cv.string, - } - ), - ), + cv.Optional(CONF_AREA): cv.maybe_simple_value(AREA_SCHEMA, key=CONF_NAME), cv.Optional(CONF_COMMENT): cv.string, cv.Required(CONF_BUILD_PATH): cv.string, cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema( @@ -270,23 +255,8 @@ 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( - cv.Schema( - { - cv.GenerateID(CONF_ID): cv.declare_id(Area), - cv.Required(CONF_NAME): cv.string, - } - ), - ), - cv.Optional(CONF_DEVICES, default=[]): cv.ensure_list( - 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), - } - ), - ), + cv.Optional(CONF_AREAS, default=[]): cv.ensure_list(AREA_SCHEMA), + cv.Optional(CONF_DEVICES, default=[]): cv.ensure_list(DEVICE_SCHEMA), } ), validate_hostname, From 754d2874e7903c8a49f4f74795516d559d382a5e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 23 Jun 2025 09:21:29 +1200 Subject: [PATCH 98/99] ``this->`` --- esphome/core/area.h | 8 ++++---- esphome/core/device.h | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/esphome/core/area.h b/esphome/core/area.h index 30b82aad6d..f6d88fe703 100644 --- a/esphome/core/area.h +++ b/esphome/core/area.h @@ -6,10 +6,10 @@ namespace esphome { class Area { public: - void set_area_id(uint32_t area_id) { area_id_ = area_id; } - uint32_t get_area_id() { return area_id_; } - void set_name(const char *name) { name_ = name; } - const char *get_name() { return name_; } + 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_{}; diff --git a/esphome/core/device.h b/esphome/core/device.h index de25963110..3d0d1e7c23 100644 --- a/esphome/core/device.h +++ b/esphome/core/device.h @@ -4,12 +4,12 @@ namespace esphome { class Device { public: - void set_device_id(uint32_t device_id) { device_id_ = device_id; } - uint32_t get_device_id() { return device_id_; } - void set_name(const char *name) { name_ = name; } - const char *get_name() { return name_; } - void set_area_id(uint32_t area_id) { area_id_ = area_id; } - uint32_t get_area_id() { return area_id_; } + 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_{}; From e5e972231cda6624ea5667b738d9536a71e2a9cc Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 23 Jun 2025 10:26:31 +1200 Subject: [PATCH 99/99] Update testing --- esphome/core/config.py | 23 ++++++++++------------- tests/unit_tests/core/test_config.py | 17 +++++++++-------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index 00b36e7899..641c73a292 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -125,22 +125,12 @@ def validate_ids_and_references(config: ConfigType) -> ConfigType: # Validate device hash collisions and area references device_hashes: dict[int, str] = {} - for i, device in enumerate(config[CONF_DEVICES]): + 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] ) - # Validate area_id reference if present - if CONF_AREA_ID in device: - area_ref_id: core.ID = device[CONF_AREA_ID] - if area_ref_id.id not in area_ids: - raise cv.Invalid( - f"Device '{device[CONF_NAME]}' has an area_id '{area_ref_id.id}'" - " that does not exist.", - path=[CONF_DEVICES, i, CONF_AREA_ID], - ) - return config @@ -200,12 +190,16 @@ DEVICE_SCHEMA = cv.Schema( ) +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.maybe_simple_value(AREA_SCHEMA, key=CONF_NAME), + 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( @@ -260,9 +254,12 @@ CONFIG_SCHEMA = cv.All( } ), validate_hostname, - validate_ids_and_references, ) + +FINAL_VALIDATE_SCHEMA = cv.All(validate_ids_and_references) + + PRELOAD_CONFIG_SCHEMA = cv.Schema( { cv.Required(CONF_NAME): cv.valid_name, diff --git a/tests/unit_tests/core/test_config.py b/tests/unit_tests/core/test_config.py index 6a28925dd3..372c1df7ee 100644 --- a/tests/unit_tests/core/test_config.py +++ b/tests/unit_tests/core/test_config.py @@ -7,7 +7,7 @@ from unittest.mock import patch import pytest -from esphome import config, config_validation as cv, yaml_util +from esphome import config, config_validation as cv, core, yaml_util from esphome.config import Config from esphome.const import CONF_AREA, CONF_AREAS, CONF_DEVICES from esphome.core import CORE @@ -67,8 +67,9 @@ def test_validate_area_config_with_string() -> None: assert "id" in result assert "name" in result assert result["name"] == "Living Room" - # ID should be based on slugified name - assert result["id"].id == "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: @@ -157,10 +158,9 @@ def test_legacy_string_area( area = esphome_config[CONF_AREA] assert isinstance(area, dict) assert area["name"] == "Living Room" - assert area["id"].id == "living_room" - - # Check for deprecation warning - assert "Using 'area' as a string is deprecated" in caplog.text + assert isinstance(area["id"], core.ID) + assert area["id"].is_declaration + assert not area["id"].is_manual def test_area_id_collision( @@ -205,8 +205,9 @@ def test_device_with_invalid_area_id( # Check for the specific error message in stdout captured = capsys.readouterr() + print(captured.out) assert ( - "Device 'Test Device' has an area_id 'nonexistent_area' that does not exist." + "Couldn't find ID 'nonexistent_area'. Please check you have defined an ID with that name in your configuration." in captured.out )