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(