diff --git a/esphome/codegen.py b/esphome/codegen.py index 1b38fd9ed2..4f9f67245d 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -67,7 +67,7 @@ from esphome.cpp_types import ( # noqa NAN, esphome_ns, App, - Nameable, + EntityBase, Component, ComponentPtr, PollingComponent, diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 7648ffeaa2..5a6eba004c 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -215,6 +215,7 @@ message ListEntitiesBinarySensorResponse { string device_class = 5; bool is_status_binary_sensor = 6; bool disabled_by_default = 7; + string icon = 8; } message BinarySensorStateResponse { option (id) = 21; @@ -245,6 +246,7 @@ message ListEntitiesCoverResponse { bool supports_tilt = 7; string device_class = 8; bool disabled_by_default = 9; + string icon = 10; } enum LegacyCoverState { @@ -313,6 +315,7 @@ message ListEntitiesFanResponse { bool supports_direction = 7; int32 supported_speed_count = 8; bool disabled_by_default = 9; + string icon = 10; } enum FanSpeed { FAN_SPEED_LOW = 0; @@ -388,6 +391,7 @@ message ListEntitiesLightResponse { float max_mireds = 10; repeated string effects = 11; bool disabled_by_default = 13; + string icon = 14; } message LightStateResponse { option (id) = 24; @@ -790,6 +794,7 @@ message ListEntitiesClimateResponse { repeated ClimatePreset supported_presets = 16; repeated string supported_custom_presets = 17; bool disabled_by_default = 18; + string icon = 19; } message ClimateStateResponse { option (id) = 47; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 450375f7cd..47171ba50f 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1,4 +1,5 @@ #include "api_connection.h" +#include "esphome/core/entity_base.h" #include "esphome/core/log.h" #include "esphome/components/network/util.h" #include "esphome/core/version.h" @@ -143,8 +144,8 @@ void APIConnection::loop() { } } -std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) { - return App.get_name() + component_type + nameable->get_object_id(); +std::string get_default_unique_id(const std::string &component_type, EntityBase *entity) { + return App.get_name() + component_type + entity->get_object_id(); } DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) { @@ -180,6 +181,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_ msg.device_class = binary_sensor->get_device_class(); msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor(); msg.disabled_by_default = binary_sensor->is_disabled_by_default(); + msg.icon = binary_sensor->get_icon(); return this->send_list_entities_binary_sensor_response(msg); } #endif @@ -212,6 +214,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) { msg.supports_tilt = traits.get_supports_tilt(); msg.device_class = cover->get_device_class(); msg.disabled_by_default = cover->is_disabled_by_default(); + msg.icon = cover->get_icon(); return this->send_list_entities_cover_response(msg); } void APIConnection::cover_command(const CoverCommandRequest &msg) { @@ -277,6 +280,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) { msg.supports_direction = traits.supports_direction(); msg.supported_speed_count = traits.supported_speed_count(); msg.disabled_by_default = fan->is_disabled_by_default(); + msg.icon = fan->get_icon(); return this->send_list_entities_fan_response(msg); } void APIConnection::fan_command(const FanCommandRequest &msg) { @@ -339,6 +343,7 @@ bool APIConnection::send_light_info(light::LightState *light) { msg.unique_id = get_default_unique_id("light", light); msg.disabled_by_default = light->is_disabled_by_default(); + msg.icon = light->get_icon(); for (auto mode : traits.get_supported_color_modes()) msg.supported_color_modes.push_back(static_cast(mode)); @@ -529,6 +534,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) { msg.unique_id = get_default_unique_id("climate", climate); msg.disabled_by_default = climate->is_disabled_by_default(); + msg.icon = climate->get_icon(); msg.supports_current_temperature = traits.get_supports_current_temperature(); msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature(); @@ -601,7 +607,7 @@ bool APIConnection::send_number_info(number::Number *number) { msg.object_id = number->get_object_id(); msg.name = number->get_name(); msg.unique_id = get_default_unique_id("number", number); - msg.icon = number->traits.get_icon(); + msg.icon = number->get_icon(); msg.disabled_by_default = number->is_disabled_by_default(); msg.min_value = number->traits.get_min_value(); @@ -638,7 +644,7 @@ bool APIConnection::send_select_info(select::Select *select) { msg.object_id = select->get_object_id(); msg.name = select->get_name(); msg.unique_id = get_default_unique_id("select", select); - msg.icon = select->traits.get_icon(); + msg.icon = select->get_icon(); msg.disabled_by_default = select->is_disabled_by_default(); for (const auto &option : select->traits.get_options()) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 580e147245..6a87238186 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -2,7 +2,6 @@ // See scripts/api_protobuf/api_protobuf.py #include "api_pb2.h" #include "esphome/core/log.h" -#include namespace esphome { namespace api { @@ -532,6 +531,10 @@ bool ListEntitiesBinarySensorResponse::decode_length(uint32_t field_id, ProtoLen this->device_class = value.as_string(); return true; } + case 8: { + this->icon = value.as_string(); + return true; + } default: return false; } @@ -554,6 +557,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->device_class); buffer.encode_bool(6, this->is_status_binary_sensor); buffer.encode_bool(7, this->disabled_by_default); + buffer.encode_string(8, this->icon); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { @@ -587,6 +591,10 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -678,6 +686,10 @@ bool ListEntitiesCoverResponse::decode_length(uint32_t field_id, ProtoLengthDeli this->device_class = value.as_string(); return true; } + case 10: { + this->icon = value.as_string(); + return true; + } default: return false; } @@ -702,6 +714,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(7, this->supports_tilt); buffer.encode_string(8, this->device_class); buffer.encode_bool(9, this->disabled_by_default); + buffer.encode_string(10, this->icon); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCoverResponse::dump_to(std::string &out) const { @@ -743,6 +756,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -949,6 +966,10 @@ bool ListEntitiesFanResponse::decode_length(uint32_t field_id, ProtoLengthDelimi this->unique_id = value.as_string(); return true; } + case 10: { + this->icon = value.as_string(); + return true; + } default: return false; } @@ -973,6 +994,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(7, this->supports_direction); buffer.encode_int32(8, this->supported_speed_count); buffer.encode_bool(9, this->disabled_by_default); + buffer.encode_string(10, this->icon); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesFanResponse::dump_to(std::string &out) const { @@ -1015,6 +1037,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -1263,6 +1289,10 @@ bool ListEntitiesLightResponse::decode_length(uint32_t field_id, ProtoLengthDeli this->effects.push_back(value.as_string()); return true; } + case 14: { + this->icon = value.as_string(); + return true; + } default: return false; } @@ -1303,6 +1333,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(11, it, true); } buffer.encode_bool(13, this->disabled_by_default); + buffer.encode_string(14, this->icon); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesLightResponse::dump_to(std::string &out) const { @@ -1366,6 +1397,10 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -3073,6 +3108,10 @@ bool ListEntitiesClimateResponse::decode_length(uint32_t field_id, ProtoLengthDe this->supported_custom_presets.push_back(value.as_string()); return true; } + case 19: { + this->icon = value.as_string(); + return true; + } default: return false; } @@ -3130,6 +3169,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(17, it, true); } buffer.encode_bool(18, this->disabled_by_default); + buffer.encode_string(19, this->icon); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesClimateResponse::dump_to(std::string &out) const { @@ -3222,6 +3262,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 1371ab5248..13a21c4772 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -269,6 +269,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage { std::string device_class{}; bool is_status_binary_sensor{false}; bool disabled_by_default{false}; + std::string icon{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -304,6 +305,7 @@ class ListEntitiesCoverResponse : public ProtoMessage { bool supports_tilt{false}; std::string device_class{}; bool disabled_by_default{false}; + std::string icon{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -360,6 +362,7 @@ class ListEntitiesFanResponse : public ProtoMessage { bool supports_direction{false}; int32_t supported_speed_count{0}; bool disabled_by_default{false}; + std::string icon{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -424,6 +427,7 @@ class ListEntitiesLightResponse : public ProtoMessage { float max_mireds{0.0f}; std::vector effects{}; bool disabled_by_default{false}; + std::string icon{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -856,6 +860,7 @@ class ListEntitiesClimateResponse : public ProtoMessage { std::vector supported_presets{}; std::vector supported_custom_presets{}; bool disabled_by_default{false}; + std::string icon{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 60ac4303a7..ec199cc5fa 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -1,15 +1,14 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome.cpp_helpers import setup_entity from esphome import automation, core from esphome.automation import Condition, maybe_simple_id from esphome.components import mqtt from esphome.const import ( CONF_DELAY, CONF_DEVICE_CLASS, - CONF_DISABLED_BY_DEFAULT, CONF_FILTERS, CONF_ID, - CONF_INTERNAL, CONF_INVALID_COOLDOWN, CONF_INVERTED, CONF_MAX_LENGTH, @@ -88,7 +87,7 @@ DEVICE_CLASSES = [ IS_PLATFORM_COMPONENT = True binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor") -BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.Nameable) +BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.EntityBase) BinarySensorInitiallyOff = binary_sensor_ns.class_( "BinarySensorInitiallyOff", BinarySensor ) @@ -314,7 +313,7 @@ def validate_multi_click_timing(value): device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") -BINARY_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( +BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(BinarySensor), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id( @@ -375,10 +374,8 @@ BINARY_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).exten async def setup_binary_sensor_core_(var, config): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) + await setup_entity(var, config) + if CONF_DEVICE_CLASS in config: cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) if CONF_INVERTED in config: diff --git a/esphome/components/binary_sensor/binary_sensor.cpp b/esphome/components/binary_sensor/binary_sensor.cpp index 2e1f228be6..41da83aa3e 100644 --- a/esphome/components/binary_sensor/binary_sensor.cpp +++ b/esphome/components/binary_sensor/binary_sensor.cpp @@ -42,7 +42,7 @@ void BinarySensor::send_state_internal(bool state, bool is_initial) { } } std::string BinarySensor::device_class() { return ""; } -BinarySensor::BinarySensor(const std::string &name) : Nameable(name), state(false) {} +BinarySensor::BinarySensor(const std::string &name) : EntityBase(name), state(false) {} BinarySensor::BinarySensor() : BinarySensor("") {} void BinarySensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } std::string BinarySensor::get_device_class() { diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index 195badd798..9c0d43fa98 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/components/binary_sensor/filter.h" @@ -22,7 +23,7 @@ namespace binary_sensor { * The sub classes should notify the front-end of new states via the publish_state() method which * handles inverted inputs for you. */ -class BinarySensor : public Nameable { +class BinarySensor : public EntityBase { public: explicit BinarySensor(); /** Construct a binary sensor with the specified name diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index c2f07ce423..ca1ea6a756 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -1,14 +1,13 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome.cpp_helpers import setup_entity from esphome import automation from esphome.components import mqtt from esphome.const import ( CONF_AWAY, CONF_CUSTOM_FAN_MODE, CONF_CUSTOM_PRESET, - CONF_DISABLED_BY_DEFAULT, CONF_ID, - CONF_INTERNAL, CONF_MAX_TEMPERATURE, CONF_MIN_TEMPERATURE, CONF_MODE, @@ -19,7 +18,6 @@ from esphome.const import ( CONF_TEMPERATURE_STEP, CONF_VISUAL, CONF_MQTT_ID, - CONF_NAME, CONF_FAN_MODE, CONF_SWING_MODE, ) @@ -30,7 +28,7 @@ IS_PLATFORM_COMPONENT = True CODEOWNERS = ["@esphome/core"] climate_ns = cg.esphome_ns.namespace("climate") -Climate = climate_ns.class_("Climate", cg.Nameable) +Climate = climate_ns.class_("Climate", cg.EntityBase) ClimateCall = climate_ns.class_("ClimateCall") ClimateTraits = climate_ns.class_("ClimateTraits") @@ -88,7 +86,7 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True) # Actions ControlAction = climate_ns.class_("ControlAction", automation.Action) -CLIMATE_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( +CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(Climate), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent), @@ -105,10 +103,8 @@ CLIMATE_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).ext async def setup_climate_core_(var, config): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) + await setup_entity(var, config) + visual = config[CONF_VISUAL] if CONF_MIN_TEMPERATURE in visual: cg.add(var.set_visual_min_temperature_override(visual[CONF_MIN_TEMPERATURE])) diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index a365b933bb..34e6328d8a 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -440,7 +440,7 @@ void Climate::set_visual_max_temperature_override(float visual_max_temperature_o void Climate::set_visual_temperature_step_override(float visual_temperature_step_override) { this->visual_temperature_step_override_ = visual_temperature_step_override; } -Climate::Climate(const std::string &name) : Nameable(name) {} +Climate::Climate(const std::string &name) : EntityBase(name) {} Climate::Climate() : Climate("") {} ClimateCall Climate::make_call() { return ClimateCall(this); } diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index 418db02485..852b76686c 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" #include "esphome/core/log.h" @@ -163,7 +164,7 @@ struct ClimateDeviceRestoreState { * mode etc). These are read-only for the user and rw for integrations. The reason these are public * is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_HEAT_COOL) ...` */ -class Climate : public Nameable { +class Climate : public EntityBase { public: /// Construct a climate device with empty name (will be set later). Climate(); diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 46b8906adb..eb57637283 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -4,18 +4,16 @@ from esphome import automation from esphome.automation import maybe_simple_id, Condition from esphome.components import mqtt from esphome.const import ( - CONF_DISABLED_BY_DEFAULT, CONF_ID, - CONF_INTERNAL, CONF_DEVICE_CLASS, CONF_STATE, CONF_POSITION, CONF_TILT, CONF_STOP, CONF_MQTT_ID, - CONF_NAME, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity IS_PLATFORM_COMPONENT = True @@ -36,7 +34,7 @@ DEVICE_CLASSES = [ cover_ns = cg.esphome_ns.namespace("cover") -Cover = cover_ns.class_("Cover", cg.Nameable) +Cover = cover_ns.class_("Cover", cg.EntityBase) COVER_OPEN = cover_ns.COVER_OPEN COVER_CLOSED = cover_ns.COVER_CLOSED @@ -65,7 +63,7 @@ CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action) CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition) CoverIsClosedCondition = cover_ns.class_("CoverIsClosedCondition", Condition) -COVER_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( +COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(Cover), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent), @@ -76,10 +74,8 @@ COVER_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).exten async def setup_cover_core_(var, config): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) + await setup_entity(var, config) + if CONF_DEVICE_CLASS in config: cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index 863adb1d81..a8d3d691a4 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -31,7 +31,7 @@ const char *cover_operation_to_str(CoverOperation op) { } } -Cover::Cover(const std::string &name) : Nameable(name), position{COVER_OPEN} {} +Cover::Cover(const std::string &name) : EntityBase(name), position{COVER_OPEN} {} uint32_t Cover::hash_base() { return 1727367479UL; } diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index 8f98a88a42..a67f8d2393 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" #include "cover_traits.h" @@ -107,7 +108,7 @@ const char *cover_operation_to_str(CoverOperation op); * to control all values of the cover. Also implement get_traits() to return what operations * the cover supports. */ -class Cover : public Nameable { +class Cover : public EntityBase { public: explicit Cover(); explicit Cover(const std::string &name); diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index de61ab43cf..7f3aebe238 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -21,7 +21,7 @@ from esphome.components.esp32 import add_idf_sdkconfig_option DEPENDENCIES = ["esp32", "api"] esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") -ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.Nameable) +ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase) ESP32CameraFrameSize = esp32_camera_ns.enum("ESP32CameraFrameSize") FRAME_SIZES = { "160X120": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_160X120, diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 05445f024b..babfda4113 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -172,7 +172,7 @@ void ESP32Camera::framebuffer_task(void *pv) { esp_camera_fb_return(framebuffer); } } -ESP32Camera::ESP32Camera(const std::string &name) : Nameable(name) { +ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) { this->config_.pin_pwdn = -1; this->config_.pin_reset = -1; this->config_.pin_xclk = -1; diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index 6246dc2f12..d0445607a4 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -3,6 +3,7 @@ #ifdef USE_ESP32 #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include #include @@ -50,7 +51,7 @@ enum ESP32CameraFrameSize { ESP32_CAMERA_SIZE_1600X1200, // UXGA }; -class ESP32Camera : public Component, public Nameable { +class ESP32Camera : public Component, public EntityBase { public: ESP32Camera(const std::string &name); void set_data_pins(std::array pins); diff --git a/esphome/components/esp32_touch/binary_sensor.py b/esphome/components/esp32_touch/binary_sensor.py index 93640334cd..bd3e06545d 100644 --- a/esphome/components/esp32_touch/binary_sensor.py +++ b/esphome/components/esp32_touch/binary_sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor from esphome.const import ( - CONF_NAME, CONF_PIN, CONF_THRESHOLD, CONF_ID, @@ -55,7 +54,6 @@ async def to_code(config): hub = await cg.get_variable(config[CONF_ESP32_TOUCH_ID]) var = cg.new_Pvariable( config[CONF_ID], - config[CONF_NAME], TOUCH_PADS[config[CONF_PIN]], config[CONF_THRESHOLD], config[CONF_WAKEUP_THRESHOLD], diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 801106ab6c..cb72820900 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -159,9 +159,8 @@ void ESP32TouchComponent::on_shutdown() { } } -ESP32TouchBinarySensor::ESP32TouchBinarySensor(const std::string &name, touch_pad_t touch_pad, uint16_t threshold, - uint16_t wakeup_threshold) - : BinarySensor(name), touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {} +ESP32TouchBinarySensor::ESP32TouchBinarySensor(touch_pad_t touch_pad, uint16_t threshold, uint16_t wakeup_threshold) + : BinarySensor(), touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {} } // namespace esp32_touch } // namespace esphome diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index c584a6d9bc..d49e4703a7 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -64,7 +64,7 @@ class ESP32TouchComponent : public Component { /// Simple helper class to expose a touch pad value as a binary sensor. class ESP32TouchBinarySensor : public binary_sensor::BinarySensor { public: - ESP32TouchBinarySensor(const std::string &name, touch_pad_t touch_pad, uint16_t threshold, uint16_t wakeup_threshold); + ESP32TouchBinarySensor(touch_pad_t touch_pad, uint16_t threshold, uint16_t wakeup_threshold); touch_pad_t get_touch_pad() const { return touch_pad_; } uint16_t get_threshold() const { return threshold_; } diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index 15895976d1..52bec3b5b6 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -4,9 +4,7 @@ from esphome import automation from esphome.automation import maybe_simple_id from esphome.components import mqtt from esphome.const import ( - CONF_DISABLED_BY_DEFAULT, CONF_ID, - CONF_INTERNAL, CONF_MQTT_ID, CONF_OSCILLATING, CONF_OSCILLATION_COMMAND_TOPIC, @@ -16,7 +14,6 @@ from esphome.const import ( CONF_SPEED_LEVEL_STATE_TOPIC, CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC, - CONF_NAME, CONF_ON_SPEED_SET, CONF_ON_TURN_OFF, CONF_ON_TURN_ON, @@ -24,11 +21,12 @@ from esphome.const import ( CONF_DIRECTION, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity IS_PLATFORM_COMPONENT = True fan_ns = cg.esphome_ns.namespace("fan") -FanState = fan_ns.class_("FanState", cg.Nameable, cg.Component) +FanState = fan_ns.class_("FanState", cg.EntityBase, cg.Component) MakeFan = cg.Application.struct("MakeFan") FanDirection = fan_ns.enum("FanDirection") @@ -50,7 +48,7 @@ FanSpeedSetTrigger = fan_ns.class_("FanSpeedSetTrigger", automation.Trigger.temp FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template()) FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.template()) -FAN_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( +FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(FanState), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent), @@ -92,10 +90,7 @@ FAN_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( async def setup_fan_core_(var, config): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) + await setup_entity(var, config) if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) diff --git a/esphome/components/fan/fan_state.cpp b/esphome/components/fan/fan_state.cpp index 921b8d57e7..6ff4d3a833 100644 --- a/esphome/components/fan/fan_state.cpp +++ b/esphome/components/fan/fan_state.cpp @@ -12,7 +12,7 @@ void FanState::set_traits(const FanTraits &traits) { this->traits_ = traits; } void FanState::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } -FanState::FanState(const std::string &name) : Nameable(name) {} +FanState::FanState(const std::string &name) : EntityBase(name) {} FanStateCall FanState::turn_on() { return this->make_call().set_state(true); } FanStateCall FanState::turn_off() { return this->make_call().set_state(false); } diff --git a/esphome/components/fan/fan_state.h b/esphome/components/fan/fan_state.h index af00275df0..c5a6f59ac4 100644 --- a/esphome/components/fan/fan_state.h +++ b/esphome/components/fan/fan_state.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" #include "esphome/core/log.h" @@ -66,7 +67,7 @@ class FanStateCall { optional direction_{}; }; -class FanState : public Nameable, public Component { +class FanState : public EntityBase, public Component { public: FanState() = default; /// Construct the fan state with name. diff --git a/esphome/components/integration/integration_sensor.h b/esphome/components/integration/integration_sensor.h index a3bdfbbb2b..437649c1dd 100644 --- a/esphome/components/integration/integration_sensor.h +++ b/esphome/components/integration/integration_sensor.h @@ -64,7 +64,6 @@ class IntegrationSensor : public sensor::Sensor, public Component { this->rtc_.save(&result_f); } std::string unit_of_measurement() override; - std::string icon() override { return this->sensor_->get_icon(); } int8_t accuracy_decimals() override { return this->sensor_->get_accuracy_decimals() + 2; } sensor::Sensor *sensor_; diff --git a/esphome/components/integration/sensor.py b/esphome/components/integration/sensor.py index 460dd46619..26c7c2871a 100644 --- a/esphome/components/integration/sensor.py +++ b/esphome/components/integration/sensor.py @@ -2,7 +2,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation from esphome.components import sensor -from esphome.const import CONF_ID, CONF_SENSOR, CONF_RESTORE +from esphome.const import CONF_ICON, CONF_ID, CONF_SENSOR, CONF_RESTORE +from esphome.core.entity_helpers import inherit_property_from integration_ns = cg.esphome_ns.namespace("integration") IntegrationSensor = integration_ns.class_( @@ -29,7 +30,6 @@ CONF_TIME_UNIT = "time_unit" CONF_INTEGRATION_METHOD = "integration_method" CONF_MIN_SAVE_INTERVAL = "min_save_interval" - CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(IntegrationSensor), @@ -46,6 +46,19 @@ CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( ).extend(cv.COMPONENT_SCHEMA) +FINAL_VALIDATE_SCHEMA = cv.All( + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(IntegrationSensor), + cv.Optional(CONF_ICON): cv.icon, + cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), + }, + extra=cv.ALLOW_EXTRA, + ), + inherit_property_from(CONF_ICON, CONF_SENSOR), +) + + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index 69cb87e539..03224d4c10 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -5,13 +5,10 @@ from esphome.components import mqtt, power_supply from esphome.const import ( CONF_COLOR_CORRECT, CONF_DEFAULT_TRANSITION_LENGTH, - CONF_DISABLED_BY_DEFAULT, CONF_EFFECTS, CONF_FLASH_TRANSITION_LENGTH, CONF_GAMMA_CORRECT, CONF_ID, - CONF_INTERNAL, - CONF_NAME, CONF_MQTT_ID, CONF_POWER_SUPPLY, CONF_RESTORE_MODE, @@ -22,6 +19,7 @@ from esphome.const import ( CONF_WARM_WHITE_COLOR_TEMPERATURE, ) from esphome.core import coroutine_with_priority +from esphome.cpp_helpers import setup_entity from .automation import light_control_to_code # noqa from .effects import ( validate_effects, @@ -54,7 +52,7 @@ RESTORE_MODES = { "RESTORE_INVERTED_DEFAULT_ON": LightRestoreMode.LIGHT_RESTORE_INVERTED_DEFAULT_ON, } -LIGHT_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( +LIGHT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(LightState), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTJSONLightComponent), @@ -126,10 +124,10 @@ def validate_color_temperature_channels(value): async def setup_light_core_(light_var, output_var, config): - cg.add(light_var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) + await setup_entity(light_var, config) + cg.add(light_var.set_restore_mode(config[CONF_RESTORE_MODE])) - if CONF_INTERNAL in config: - cg.add(light_var.set_internal(config[CONF_INTERNAL])) + if CONF_DEFAULT_TRANSITION_LENGTH in config: cg.add( light_var.set_default_transition_length( @@ -167,7 +165,7 @@ async def setup_light_core_(light_var, output_var, config): async def register_light(output_var, config): - light_var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME], output_var) + light_var = cg.new_Pvariable(config[CONF_ID], output_var) cg.add(cg.App.register_light(light_var)) await cg.register_component(light_var, config) await setup_light_core_(light_var, output_var, config) diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index f888006b17..5f16585c36 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -8,7 +8,8 @@ namespace light { static const char *const TAG = "light"; -LightState::LightState(const std::string &name, LightOutput *output) : Nameable(name), output_(output) {} +LightState::LightState(const std::string &name, LightOutput *output) : EntityBase(name), output_(output) {} +LightState::LightState(LightOutput *output) : output_(output) {} LightTraits LightState::get_traits() { return this->output_->get_traits(); } LightCall LightState::turn_on() { return this->make_call().set_state(true); } diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index f73a4c3b17..ae3711234d 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/optional.h" #include "esphome/core/preferences.h" #include "light_call.h" @@ -26,11 +27,13 @@ enum LightRestoreMode { /** This class represents the communication layer between the front-end MQTT layer and the * hardware output layer. */ -class LightState : public Nameable, public Component { +class LightState : public EntityBase, public Component { public: /// Construct this LightState using the provided traits and name. LightState(const std::string &name, LightOutput *output); + LightState(LightOutput *output); + LightTraits get_traits(); /// Make a light state call diff --git a/esphome/components/light/types.py b/esphome/components/light/types.py index 66329f7cf9..cf544e5435 100644 --- a/esphome/components/light/types.py +++ b/esphome/components/light/types.py @@ -3,7 +3,7 @@ from esphome import automation # Base light_ns = cg.esphome_ns.namespace("light") -LightState = light_ns.class_("LightState", cg.Nameable, cg.Component) +LightState = light_ns.class_("LightState", cg.EntityBase, cg.Component) # Fake class for addressable lights AddressableLightState = light_ns.class_("LightState", LightState) LightOutput = light_ns.class_("LightOutput") diff --git a/esphome/components/mcp3008/mcp3008.cpp b/esphome/components/mcp3008/mcp3008.cpp index bea24b5c6a..81abc4f012 100644 --- a/esphome/components/mcp3008/mcp3008.cpp +++ b/esphome/components/mcp3008/mcp3008.cpp @@ -37,10 +37,8 @@ float MCP3008::read_data(uint8_t pin) { return data / 1023.0f; } -MCP3008Sensor::MCP3008Sensor(MCP3008 *parent, const std::string &name, uint8_t pin, float reference_voltage) - : PollingComponent(1000), parent_(parent), pin_(pin), reference_voltage_(reference_voltage) { - this->set_name(name); -} +MCP3008Sensor::MCP3008Sensor(MCP3008 *parent, uint8_t pin, float reference_voltage) + : PollingComponent(1000), parent_(parent), pin_(pin), reference_voltage_(reference_voltage) {} float MCP3008Sensor::get_setup_priority() const { return setup_priority::DATA; } diff --git a/esphome/components/mcp3008/mcp3008.h b/esphome/components/mcp3008/mcp3008.h index 6f3dc576ea..5d8b823111 100644 --- a/esphome/components/mcp3008/mcp3008.h +++ b/esphome/components/mcp3008/mcp3008.h @@ -26,7 +26,7 @@ class MCP3008 : public Component, class MCP3008Sensor : public PollingComponent, public sensor::Sensor, public voltage_sampler::VoltageSampler { public: - MCP3008Sensor(MCP3008 *parent, const std::string &name, uint8_t pin, float reference_voltage); + MCP3008Sensor(MCP3008 *parent, uint8_t pin, float reference_voltage); void set_reference_voltage(float reference_voltage) { reference_voltage_ = reference_voltage; } void setup() override; diff --git a/esphome/components/mcp3008/sensor.py b/esphome/components/mcp3008/sensor.py index 4fc9b83afb..d4b9e979ce 100644 --- a/esphome/components/mcp3008/sensor.py +++ b/esphome/components/mcp3008/sensor.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, voltage_sampler -from esphome.const import CONF_ID, CONF_NUMBER, CONF_NAME +from esphome.const import CONF_ID, CONF_NUMBER from . import mcp3008_ns, MCP3008 AUTO_LOAD = ["voltage_sampler"] @@ -29,7 +29,6 @@ async def to_code(config): var = cg.new_Pvariable( config[CONF_ID], parent, - config[CONF_NAME], config[CONF_NUMBER], config[CONF_REFERENCE_VOLTAGE], ) diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index d7322298bb..188df0f7b9 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -10,6 +10,7 @@ namespace mqtt { static const char *const TAG = "mqtt.binary_sensor"; std::string MQTTBinarySensorComponent::component_type() const { return "binary_sensor"; } +const EntityBase *MQTTBinarySensorComponent::get_entity() const { return this->binary_sensor_; } void MQTTBinarySensorComponent::setup() { this->binary_sensor_->add_on_state_callback([this](bool state) { this->publish_state(state); }); @@ -25,7 +26,6 @@ MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor this->set_custom_state_topic(mqtt::global_mqtt_client->get_availability().topic); } } -std::string MQTTBinarySensorComponent::friendly_name() const { return this->binary_sensor_->get_name(); } void MQTTBinarySensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (!this->binary_sensor_->get_device_class().empty()) @@ -43,7 +43,6 @@ bool MQTTBinarySensorComponent::send_initial_state() { return true; } } -bool MQTTBinarySensorComponent::is_internal() { return this->binary_sensor_->is_internal(); } bool MQTTBinarySensorComponent::publish_state(bool state) { if (this->binary_sensor_->is_status_binary_sensor()) return true; diff --git a/esphome/components/mqtt/mqtt_binary_sensor.h b/esphome/components/mqtt/mqtt_binary_sensor.h index c459bea1f7..0efb490367 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.h +++ b/esphome/components/mqtt/mqtt_binary_sensor.h @@ -28,11 +28,10 @@ class MQTTBinarySensorComponent : public mqtt::MQTTComponent { bool send_initial_state() override; bool publish_state(bool state); - bool is_internal() override; protected: - std::string friendly_name() const override; std::string component_type() const override; + const EntityBase *get_entity() const override; binary_sensor::BinarySensor *binary_sensor_; }; diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 8519e296b0..47b6684dec 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -212,9 +212,9 @@ void MQTTClimateComponent::setup() { } MQTTClimateComponent::MQTTClimateComponent(Climate *device) : device_(device) {} bool MQTTClimateComponent::send_initial_state() { return this->publish_state_(); } -bool MQTTClimateComponent::is_internal() { return this->device_->is_internal(); } std::string MQTTClimateComponent::component_type() const { return "climate"; } -std::string MQTTClimateComponent::friendly_name() const { return this->device_->get_name(); } +const EntityBase *MQTTClimateComponent::get_entity() const { return this->device_; } + bool MQTTClimateComponent::publish_state_() { auto traits = this->device_->get_traits(); // mode diff --git a/esphome/components/mqtt/mqtt_climate.h b/esphome/components/mqtt/mqtt_climate.h index 8b8a8e866e..40ac4c18c1 100644 --- a/esphome/components/mqtt/mqtt_climate.h +++ b/esphome/components/mqtt/mqtt_climate.h @@ -16,7 +16,6 @@ class MQTTClimateComponent : public mqtt::MQTTComponent { MQTTClimateComponent(climate::Climate *device); void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; bool send_initial_state() override; - bool is_internal() override; std::string component_type() const override; void setup() override; @@ -38,7 +37,7 @@ class MQTTClimateComponent : public mqtt::MQTTComponent { MQTT_COMPONENT_CUSTOM_TOPIC(swing_mode, command) protected: - std::string friendly_name() const override; + const EntityBase *get_entity() const override; bool publish_state_(); diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 96cda57914..0ece4b3501 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -68,8 +68,13 @@ bool MQTTComponent::send_discovery_() { this->send_discovery(root, config); - std::string name = this->friendly_name(); - root["name"] = name; + // Fields from EntityBase + root["name"] = this->friendly_name(); + if (this->is_disabled_by_default()) + root["enabled_by_default"] = false; + if (!this->get_icon().empty()) + root["icon"] = this->get_icon(); + if (config.state_topic) root["state_topic"] = this->get_state_topic_(); if (config.command_topic) @@ -199,6 +204,12 @@ void MQTTComponent::schedule_resend_state() { this->resend_state_ = true; } std::string MQTTComponent::unique_id() { return ""; } bool MQTTComponent::is_connected_() const { return global_mqtt_client->is_connected(); } +// Pull these properties from EntityBase if not overridden +std::string MQTTComponent::friendly_name() const { return this->get_entity()->get_name(); } +std::string MQTTComponent::get_icon() const { return this->get_entity()->get_icon(); } +bool MQTTComponent::is_disabled_by_default() const { return this->get_entity()->is_disabled_by_default(); } +bool MQTTComponent::is_internal() { return this->get_entity()->is_internal(); } + } // namespace mqtt } // namespace esphome diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index f07e752e02..657ab7b608 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -7,6 +7,7 @@ #include #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "mqtt_client.h" namespace esphome { @@ -73,7 +74,7 @@ class MQTTComponent : public Component { virtual bool send_initial_state() = 0; - virtual bool is_internal() = 0; + virtual bool is_internal(); /// Set whether state message should be retained. void set_retain(bool retain); @@ -148,8 +149,10 @@ class MQTTComponent : public Component { */ std::string get_default_topic_for_(const std::string &suffix) const; - /// Get the friendly name of this MQTT component. - virtual std::string friendly_name() const = 0; + /** + * Gets the Entity served by this MQTT component. + */ + virtual const EntityBase *get_entity() const = 0; /** A unique ID for this MQTT component, empty for no unique id. See unique ID requirements: * https://developers.home-assistant.io/docs/en/entity_registry_index.html#unique-id-requirements @@ -158,6 +161,15 @@ class MQTTComponent : public Component { */ virtual std::string unique_id(); + /// Get the friendly name of this MQTT component. + virtual std::string friendly_name() const; + + /// Get the icon field of this component + virtual std::string get_icon() const; + + /// Get whether the underlying Entity is disabled by default + virtual bool is_disabled_by_default() const; + /// Get the MQTT topic that new states will be shared to. const std::string get_state_topic_() const; diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index 61c8fa2d94..e8bc7f0e30 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -84,9 +84,9 @@ void MQTTCoverComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCon } std::string MQTTCoverComponent::component_type() const { return "cover"; } -std::string MQTTCoverComponent::friendly_name() const { return this->cover_->get_name(); } +const EntityBase *MQTTCoverComponent::get_entity() const { return this->cover_; } + bool MQTTCoverComponent::send_initial_state() { return this->publish_state(); } -bool MQTTCoverComponent::is_internal() { return this->cover_->is_internal(); } bool MQTTCoverComponent::publish_state() { auto traits = this->cover_->get_traits(); bool success = true; diff --git a/esphome/components/mqtt/mqtt_cover.h b/esphome/components/mqtt/mqtt_cover.h index b8c9f2617c..149d46ac85 100644 --- a/esphome/components/mqtt/mqtt_cover.h +++ b/esphome/components/mqtt/mqtt_cover.h @@ -24,7 +24,6 @@ class MQTTCoverComponent : public mqtt::MQTTComponent { MQTT_COMPONENT_CUSTOM_TOPIC(tilt, state) bool send_initial_state() override; - bool is_internal() override; bool publish_state(); @@ -32,7 +31,7 @@ class MQTTCoverComponent : public mqtt::MQTTComponent { protected: std::string component_type() const override; - std::string friendly_name() const override; + const EntityBase *get_entity() const override; cover::Cover *cover_; }; diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index ed1ab605aa..898183cc58 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -16,6 +16,7 @@ MQTTFanComponent::MQTTFanComponent(FanState *state) : MQTTComponent(), state_(st FanState *MQTTFanComponent::get_state() const { return this->state_; } std::string MQTTFanComponent::component_type() const { return "fan"; } +const EntityBase *MQTTFanComponent::get_entity() const { return this->state_; } void MQTTFanComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { @@ -113,7 +114,7 @@ void MQTTFanComponent::dump_config() { } bool MQTTFanComponent::send_initial_state() { return this->publish_state(); } -std::string MQTTFanComponent::friendly_name() const { return this->state_->get_name(); } + void MQTTFanComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (this->state_->get_traits().supports_oscillation()) { root["oscillation_command_topic"] = this->get_oscillation_command_topic(); @@ -126,7 +127,6 @@ void MQTTFanComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfi root["speed_state_topic"] = this->get_speed_state_topic(); } } -bool MQTTFanComponent::is_internal() { return this->state_->is_internal(); } bool MQTTFanComponent::publish_state() { const char *state_s = this->state_->state ? "ON" : "OFF"; ESP_LOGD(TAG, "'%s' Sending state %s.", this->state_->get_name().c_str(), state_s); diff --git a/esphome/components/mqtt/mqtt_fan.h b/esphome/components/mqtt/mqtt_fan.h index 00263e13eb..a160d5366b 100644 --- a/esphome/components/mqtt/mqtt_fan.h +++ b/esphome/components/mqtt/mqtt_fan.h @@ -39,10 +39,8 @@ class MQTTFanComponent : public mqtt::MQTTComponent { fan::FanState *get_state() const; - bool is_internal() override; - protected: - std::string friendly_name() const override; + const EntityBase *get_entity() const override; fan::FanState *state_; }; diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index d028b1b037..a88358a6b2 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -13,6 +13,7 @@ static const char *const TAG = "mqtt.light"; using namespace esphome::light; std::string MQTTJSONLightComponent::component_type() const { return "light"; } +const EntityBase *MQTTJSONLightComponent::get_entity() const { return this->state_; } void MQTTJSONLightComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject &root) { @@ -32,7 +33,7 @@ bool MQTTJSONLightComponent::publish_state_() { [this](JsonObject &root) { LightJSONSchema::dump_json(*this->state_, root); }); } LightState *MQTTJSONLightComponent::get_state() const { return this->state_; } -std::string MQTTJSONLightComponent::friendly_name() const { return this->state_->get_name(); } + void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { root["schema"] = "json"; auto traits = this->state_->get_traits(); @@ -70,7 +71,6 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover } } bool MQTTJSONLightComponent::send_initial_state() { return this->publish_state_(); } -bool MQTTJSONLightComponent::is_internal() { return this->state_->is_internal(); } void MQTTJSONLightComponent::dump_config() { ESP_LOGCONFIG(TAG, "MQTT Light '%s':", this->state_->get_name().c_str()); LOG_MQTT_COMPONENT(true, true) diff --git a/esphome/components/mqtt/mqtt_light.h b/esphome/components/mqtt/mqtt_light.h index b0f3145900..192cba39b6 100644 --- a/esphome/components/mqtt/mqtt_light.h +++ b/esphome/components/mqtt/mqtt_light.h @@ -25,11 +25,9 @@ class MQTTJSONLightComponent : public mqtt::MQTTComponent { bool send_initial_state() override; - bool is_internal() override; - protected: - std::string friendly_name() const override; std::string component_type() const override; + const EntityBase *get_entity() const override; bool publish_state_(); diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index faa38056a9..674fd77bdf 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -33,13 +33,11 @@ void MQTTNumberComponent::dump_config() { } std::string MQTTNumberComponent::component_type() const { return "number"; } +const EntityBase *MQTTNumberComponent::get_entity() const { return this->number_; } -std::string MQTTNumberComponent::friendly_name() const { return this->number_->get_name(); } void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { const auto &traits = number_->traits; // https://www.home-assistant.io/integrations/number.mqtt/ - if (!traits.get_icon().empty()) - root["icon"] = traits.get_icon(); root["min"] = traits.get_min_value(); root["max"] = traits.get_max_value(); root["step"] = traits.get_step(); @@ -53,7 +51,6 @@ bool MQTTNumberComponent::send_initial_state() { return true; } } -bool MQTTNumberComponent::is_internal() { return this->number_->is_internal(); } bool MQTTNumberComponent::publish_state(float value) { char buffer[64]; snprintf(buffer, sizeof(buffer), "%f", value); diff --git a/esphome/components/mqtt/mqtt_number.h b/esphome/components/mqtt/mqtt_number.h index 46dc221e9e..66622d7c29 100644 --- a/esphome/components/mqtt/mqtt_number.h +++ b/esphome/components/mqtt/mqtt_number.h @@ -28,15 +28,13 @@ class MQTTNumberComponent : public mqtt::MQTTComponent { void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; bool send_initial_state() override; - bool is_internal() override; bool publish_state(float value); protected: /// Override for MQTTComponent, returns "number". std::string component_type() const override; - - std::string friendly_name() const override; + const EntityBase *get_entity() const override; number::Number *number_; }; diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index 467a0cd84c..b499636006 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -28,13 +28,11 @@ void MQTTSelectComponent::dump_config() { } std::string MQTTSelectComponent::component_type() const { return "select"; } +const EntityBase *MQTTSelectComponent::get_entity() const { return this->select_; } -std::string MQTTSelectComponent::friendly_name() const { return this->select_->get_name(); } void MQTTSelectComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { const auto &traits = select_->traits; // https://www.home-assistant.io/integrations/select.mqtt/ - if (!traits.get_icon().empty()) - root["icon"] = traits.get_icon(); JsonArray &options = root.createNestedArray("options"); for (const auto &option : traits.get_options()) options.add(option); @@ -48,7 +46,6 @@ bool MQTTSelectComponent::send_initial_state() { return true; } } -bool MQTTSelectComponent::is_internal() { return this->select_->is_internal(); } bool MQTTSelectComponent::publish_state(const std::string &value) { return this->publish(this->get_state_topic_(), value); } diff --git a/esphome/components/mqtt/mqtt_select.h b/esphome/components/mqtt/mqtt_select.h index 0115c0a41e..d77d0cf513 100644 --- a/esphome/components/mqtt/mqtt_select.h +++ b/esphome/components/mqtt/mqtt_select.h @@ -28,15 +28,13 @@ class MQTTSelectComponent : public mqtt::MQTTComponent { void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; bool send_initial_state() override; - bool is_internal() override; bool publish_state(const std::string &value); protected: /// Override for MQTTComponent, returns "select". std::string component_type() const override; - - std::string friendly_name() const override; + const EntityBase *get_entity() const override; select::Select *select_; }; diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index 72ec7c54ee..78710ff403 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -30,6 +30,7 @@ void MQTTSensorComponent::dump_config() { } std::string MQTTSensorComponent::component_type() const { return "sensor"; } +const EntityBase *MQTTSensorComponent::get_entity() const { return this->sensor_; } uint32_t MQTTSensorComponent::get_expire_after() const { if (this->expire_after_.has_value()) @@ -38,7 +39,7 @@ uint32_t MQTTSensorComponent::get_expire_after() const { } void MQTTSensorComponent::set_expire_after(uint32_t expire_after) { this->expire_after_ = expire_after; } void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; } -std::string MQTTSensorComponent::friendly_name() const { return this->sensor_->get_name(); } + void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (!this->sensor_->get_device_class().empty()) root["device_class"] = this->sensor_->get_device_class(); @@ -49,9 +50,6 @@ void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo if (this->get_expire_after() > 0) root["expire_after"] = this->get_expire_after() / 1000; - if (!this->sensor_->get_icon().empty()) - root["icon"] = this->sensor_->get_icon(); - if (this->sensor_->get_force_update()) root["force_update"] = true; @@ -67,7 +65,6 @@ bool MQTTSensorComponent::send_initial_state() { return true; } } -bool MQTTSensorComponent::is_internal() { return this->sensor_->is_internal(); } bool MQTTSensorComponent::publish_state(float value) { int8_t accuracy = this->sensor_->get_accuracy_decimals(); return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy)); diff --git a/esphome/components/mqtt/mqtt_sensor.h b/esphome/components/mqtt/mqtt_sensor.h index 2385529f4f..22609fdfef 100644 --- a/esphome/components/mqtt/mqtt_sensor.h +++ b/esphome/components/mqtt/mqtt_sensor.h @@ -41,14 +41,11 @@ class MQTTSensorComponent : public mqtt::MQTTComponent { bool publish_state(float value); bool send_initial_state() override; - bool is_internal() override; protected: /// Override for MQTTComponent, returns "sensor". std::string component_type() const override; - - std::string friendly_name() const override; - + const EntityBase *get_entity() const override; std::string unique_id() override; sensor::Sensor *sensor_; diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp index b13ddd5d9d..16cf102f7e 100644 --- a/esphome/components/mqtt/mqtt_switch.cpp +++ b/esphome/components/mqtt/mqtt_switch.cpp @@ -41,15 +41,13 @@ void MQTTSwitchComponent::dump_config() { } std::string MQTTSwitchComponent::component_type() const { return "switch"; } +const EntityBase *MQTTSwitchComponent::get_entity() const { return this->switch_; } void MQTTSwitchComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { - if (!this->switch_->get_icon().empty()) - root["icon"] = this->switch_->get_icon(); if (this->switch_->assumed_state()) root["optimistic"] = true; } bool MQTTSwitchComponent::send_initial_state() { return this->publish_state(this->switch_->state); } -bool MQTTSwitchComponent::is_internal() { return this->switch_->is_internal(); } -std::string MQTTSwitchComponent::friendly_name() const { return this->switch_->get_name(); } + bool MQTTSwitchComponent::publish_state(bool state) { const char *state_s = state ? "ON" : "OFF"; return this->publish(this->get_state_topic_(), state_s); diff --git a/esphome/components/mqtt/mqtt_switch.h b/esphome/components/mqtt/mqtt_switch.h index 9959d21872..a0a7a23220 100644 --- a/esphome/components/mqtt/mqtt_switch.h +++ b/esphome/components/mqtt/mqtt_switch.h @@ -23,15 +23,13 @@ class MQTTSwitchComponent : public mqtt::MQTTComponent { void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override; bool send_initial_state() override; - bool is_internal() override; bool publish_state(bool state); protected: - std::string friendly_name() const override; - /// "switch" component type. std::string component_type() const override; + const EntityBase *get_entity() const override; switch_::Switch *switch_; }; diff --git a/esphome/components/mqtt/mqtt_text_sensor.cpp b/esphome/components/mqtt/mqtt_text_sensor.cpp index fd96cd0902..7b89915649 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.cpp +++ b/esphome/components/mqtt/mqtt_text_sensor.cpp @@ -13,9 +13,6 @@ using namespace esphome::text_sensor; MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : MQTTComponent(), sensor_(sensor) {} void MQTTTextSensor::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { - if (!this->sensor_->get_icon().empty()) - root["icon"] = this->sensor_->get_icon(); - config.command_topic = false; } void MQTTTextSensor::setup() { @@ -35,9 +32,8 @@ bool MQTTTextSensor::send_initial_state() { return true; } } -bool MQTTTextSensor::is_internal() { return this->sensor_->is_internal(); } std::string MQTTTextSensor::component_type() const { return "sensor"; } -std::string MQTTTextSensor::friendly_name() const { return this->sensor_->get_name(); } +const EntityBase *MQTTTextSensor::get_entity() const { return this->sensor_; } std::string MQTTTextSensor::unique_id() { return this->sensor_->unique_id(); } } // namespace mqtt diff --git a/esphome/components/mqtt/mqtt_text_sensor.h b/esphome/components/mqtt/mqtt_text_sensor.h index 1d3f95b894..83743245cc 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.h +++ b/esphome/components/mqtt/mqtt_text_sensor.h @@ -25,13 +25,9 @@ class MQTTTextSensor : public mqtt::MQTTComponent { bool send_initial_state() override; - bool is_internal() override; - protected: std::string component_type() const override; - - std::string friendly_name() const override; - + const EntityBase *get_entity() const override; std::string unique_id() override; text_sensor::TextSensor *sensor_; diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index 88153492d3..2856a25ee7 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -6,25 +6,21 @@ from esphome.components import mqtt from esphome.const import ( CONF_ABOVE, CONF_BELOW, - CONF_DISABLED_BY_DEFAULT, - CONF_ICON, CONF_ID, - CONF_INTERNAL, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, CONF_TRIGGER_ID, - CONF_NAME, CONF_MQTT_ID, CONF_VALUE, - ICON_EMPTY, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity CODEOWNERS = ["@esphome/core"] IS_PLATFORM_COMPONENT = True number_ns = cg.esphome_ns.namespace("number") -Number = number_ns.class_("Number", cg.Nameable) +Number = number_ns.class_("Number", cg.EntityBase) NumberPtr = Number.operator("ptr") # Triggers @@ -46,11 +42,10 @@ NumberInRangeCondition = number_ns.class_( icon = cv.icon -NUMBER_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( +NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent), cv.GenerateID(): cv.declare_id(Number), - cv.Optional(CONF_ICON, default=ICON_EMPTY): icon, cv.Optional(CONF_ON_VALUE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(NumberStateTrigger), @@ -71,12 +66,8 @@ NUMBER_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( async def setup_number_core_( var, config, *, min_value: float, max_value: float, step: Optional[float] ): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) + await setup_entity(var, config) - cg.add(var.traits.set_icon(config[CONF_ICON])) cg.add(var.traits.set_min_value(min_value)) cg.add(var.traits.set_max_value(max_value)) if step is not None: diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index 945f174510..ed104fb477 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" namespace esphome { @@ -9,8 +10,8 @@ namespace number { #define LOG_NUMBER(prefix, type, obj) \ if ((obj) != nullptr) { \ ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ - if (!(obj)->traits.get_icon().empty()) { \ - ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->traits.get_icon().c_str()); \ + if (!(obj)->get_icon().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ } \ } @@ -40,21 +41,18 @@ class NumberTraits { float get_max_value() const { return max_value_; } void set_step(float step) { step_ = step; } float get_step() const { return step_; } - void set_icon(std::string icon) { icon_ = std::move(icon); } - const std::string &get_icon() const { return icon_; } protected: float min_value_ = NAN; float max_value_ = NAN; float step_ = NAN; - std::string icon_; }; /** Base-class for all numbers. * * A number can use publish_state to send out a new value. */ -class Number : public Nameable { +class Number : public EntityBase { public: float state; diff --git a/esphome/components/remote_receiver/binary_sensor.py b/esphome/components/remote_receiver/binary_sensor.py index 62c1d9cb27..218b40d6cc 100644 --- a/esphome/components/remote_receiver/binary_sensor.py +++ b/esphome/components/remote_receiver/binary_sensor.py @@ -1,6 +1,4 @@ -import esphome.codegen as cg from esphome.components import binary_sensor, remote_base -from esphome.const import CONF_NAME DEPENDENCIES = ["remote_receiver"] @@ -9,5 +7,4 @@ CONFIG_SCHEMA = remote_base.validate_binary_sensor async def to_code(config): var = await remote_base.build_binary_sensor(config) - cg.add(var.set_name(config[CONF_NAME])) await binary_sensor.register_binary_sensor(var, config) diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index d3ab344926..c156a63a86 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -4,24 +4,20 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import mqtt from esphome.const import ( - CONF_DISABLED_BY_DEFAULT, - CONF_ICON, CONF_ID, - CONF_INTERNAL, CONF_ON_VALUE, CONF_OPTION, CONF_TRIGGER_ID, - CONF_NAME, CONF_MQTT_ID, - ICON_EMPTY, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity CODEOWNERS = ["@esphome/core"] IS_PLATFORM_COMPONENT = True select_ns = cg.esphome_ns.namespace("select") -Select = select_ns.class_("Select", cg.Nameable) +Select = select_ns.class_("Select", cg.EntityBase) SelectPtr = Select.operator("ptr") # Triggers @@ -35,11 +31,10 @@ SelectSetAction = select_ns.class_("SelectSetAction", automation.Action) icon = cv.icon -SELECT_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( +SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent), cv.GenerateID(): cv.declare_id(Select), - cv.Optional(CONF_ICON, default=ICON_EMPTY): icon, cv.Optional(CONF_ON_VALUE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SelectStateTrigger), @@ -50,12 +45,8 @@ SELECT_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( async def setup_select_core_(var, config, *, options: List[str]): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) + await setup_entity(var, config) - cg.add(var.traits.set_icon(config[CONF_ICON])) cg.add(var.traits.set_options(options)) for conf in config.get(CONF_ON_VALUE, []): diff --git a/esphome/components/select/select.h b/esphome/components/select/select.h index 0dec74b627..6113cca1fd 100644 --- a/esphome/components/select/select.h +++ b/esphome/components/select/select.h @@ -3,6 +3,7 @@ #include #include #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" namespace esphome { @@ -11,8 +12,8 @@ namespace select { #define LOG_SELECT(prefix, type, obj) \ if ((obj) != nullptr) { \ ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ - if (!(obj)->traits.get_icon().empty()) { \ - ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->traits.get_icon().c_str()); \ + if (!(obj)->get_icon().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ } \ } @@ -38,19 +39,16 @@ class SelectTraits { public: void set_options(std::vector options) { this->options_ = std::move(options); } const std::vector get_options() const { return this->options_; } - void set_icon(std::string icon) { icon_ = std::move(icon); } - const std::string &get_icon() const { return icon_; } protected: std::vector options_; - std::string icon_; }; /** Base-class for all selects. * * A select can use publish_state to send out a new value. */ -class Select : public Nameable { +class Select : public EntityBase { public: std::string state; diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index ce4aafb3b0..cb74a41119 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -10,13 +10,11 @@ from esphome.const import ( CONF_ACCURACY_DECIMALS, CONF_ALPHA, CONF_BELOW, - CONF_DISABLED_BY_DEFAULT, CONF_EXPIRE_AFTER, CONF_FILTERS, CONF_FROM, CONF_ICON, CONF_ID, - CONF_INTERNAL, CONF_ON_RAW_VALUE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, @@ -27,7 +25,6 @@ from esphome.const import ( CONF_TRIGGER_ID, CONF_UNIT_OF_MEASUREMENT, CONF_WINDOW_SIZE, - CONF_NAME, CONF_MQTT_ID, CONF_FORCE_UPDATE, DEVICE_CLASS_EMPTY, @@ -59,6 +56,7 @@ from esphome.const import ( DEVICE_CLASS_VOLTAGE, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity from esphome.util import Registry CODEOWNERS = ["@esphome/core"] @@ -136,7 +134,7 @@ def validate_datapoint(value): # Base sensor_ns = cg.esphome_ns.namespace("sensor") -Sensor = sensor_ns.class_("Sensor", cg.Nameable) +Sensor = sensor_ns.class_("Sensor", cg.EntityBase) SensorPtr = Sensor.operator("ptr") # Triggers @@ -180,12 +178,11 @@ validate_accuracy_decimals = cv.int_ validate_icon = cv.icon validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") -SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( +SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSensorComponent), cv.GenerateID(): cv.declare_id(Sensor), cv.Optional(CONF_UNIT_OF_MEASUREMENT): validate_unit_of_measurement, - cv.Optional(CONF_ICON): validate_icon, cv.Optional(CONF_ACCURACY_DECIMALS): validate_accuracy_decimals, cv.Optional(CONF_DEVICE_CLASS): validate_device_class, cv.Optional(CONF_STATE_CLASS): validate_state_class, @@ -495,18 +492,14 @@ async def build_filters(config): async def setup_sensor_core_(var, config): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) + await setup_entity(var, config) + if CONF_DEVICE_CLASS in config: cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) if CONF_STATE_CLASS in config: cg.add(var.set_state_class(config[CONF_STATE_CLASS])) if CONF_UNIT_OF_MEASUREMENT in config: cg.add(var.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT])) - if CONF_ICON in config: - cg.add(var.set_icon(config[CONF_ICON])) if CONF_ACCURACY_DECIMALS in config: cg.add(var.set_accuracy_decimals(config[CONF_ACCURACY_DECIMALS])) cg.add(var.set_force_update(config[CONF_FORCE_UPDATE])) diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 0dc0275715..793ae170c3 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -18,7 +18,7 @@ std::string state_class_to_string(StateClass state_class) { } } -Sensor::Sensor(const std::string &name) : Nameable(name), state(NAN), raw_state(NAN) {} +Sensor::Sensor(const std::string &name) : EntityBase(name), state(NAN), raw_state(NAN) {} Sensor::Sensor() : Sensor("") {} std::string Sensor::get_unit_of_measurement() { @@ -31,14 +31,6 @@ void Sensor::set_unit_of_measurement(const std::string &unit_of_measurement) { } std::string Sensor::unit_of_measurement() { return ""; } -std::string Sensor::get_icon() { - if (this->icon_.has_value()) - return *this->icon_; - return this->icon(); -} -void Sensor::set_icon(const std::string &icon) { this->icon_ = icon; } -std::string Sensor::icon() { return ""; } - int8_t Sensor::get_accuracy_decimals() { if (this->accuracy_decimals_.has_value()) return *this->accuracy_decimals_; diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index d284f931b1..6cab46f7f9 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -2,6 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/components/sensor/filter.h" @@ -43,7 +44,7 @@ std::string state_class_to_string(StateClass state_class); * * A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy. */ -class Sensor : public Nameable { +class Sensor : public EntityBase { public: explicit Sensor(); explicit Sensor(const std::string &name); @@ -53,11 +54,6 @@ class Sensor : public Nameable { /// Manually set the unit of measurement. void set_unit_of_measurement(const std::string &unit_of_measurement); - /// Get the icon. Uses the manual override if specified or the default value instead. - std::string get_icon(); - /// Manually set the icon, for example "mdi:flash". - void set_icon(const std::string &icon); - /// Get the accuracy in decimals, using the manual override if set. int8_t get_accuracy_decimals(); /// Manually set the accuracy in decimals. @@ -157,9 +153,6 @@ class Sensor : public Nameable { /// Override this to set the default unit of measurement. virtual std::string unit_of_measurement(); // NOLINT - /// Override this to set the default icon. - virtual std::string icon(); // NOLINT - /// Override this to set the default accuracy in decimals. virtual int8_t accuracy_decimals(); // NOLINT @@ -178,7 +171,6 @@ class Sensor : public Nameable { Filter *filter_list_{nullptr}; ///< Store all active filters. optional unit_of_measurement_; ///< Unit of measurement override - optional icon_; ///< Icon override optional accuracy_decimals_; ///< Accuracy in decimals override optional device_class_; ///< Device class override optional state_class_{STATE_CLASS_NONE}; ///< State class override diff --git a/esphome/components/switch/__init__.py b/esphome/components/switch/__init__.py index 8aa213a9f6..88341e0add 100644 --- a/esphome/components/switch/__init__.py +++ b/esphome/components/switch/__init__.py @@ -4,24 +4,21 @@ from esphome import automation from esphome.automation import Condition, maybe_simple_id from esphome.components import mqtt from esphome.const import ( - CONF_DISABLED_BY_DEFAULT, - CONF_ICON, CONF_ID, - CONF_INTERNAL, CONF_INVERTED, CONF_ON_TURN_OFF, CONF_ON_TURN_ON, CONF_TRIGGER_ID, CONF_MQTT_ID, - CONF_NAME, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity CODEOWNERS = ["@esphome/core"] IS_PLATFORM_COMPONENT = True switch_ns = cg.esphome_ns.namespace("switch_") -Switch = switch_ns.class_("Switch", cg.Nameable) +Switch = switch_ns.class_("Switch", cg.EntityBase) SwitchPtr = Switch.operator("ptr") ToggleAction = switch_ns.class_("ToggleAction", automation.Action) @@ -39,10 +36,9 @@ SwitchTurnOffTrigger = switch_ns.class_( icon = cv.icon -SWITCH_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( +SWITCH_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSwitchComponent), - cv.Optional(CONF_ICON): icon, cv.Optional(CONF_INVERTED): cv.boolean, cv.Optional(CONF_ON_TURN_ON): automation.validate_automation( { @@ -59,12 +55,8 @@ SWITCH_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).exte async def setup_switch_core_(var, config): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) - if CONF_ICON in config: - cg.add(var.set_icon(config[CONF_ICON])) + await setup_entity(var, config) + if CONF_INVERTED in config: cg.add(var.set_inverted(config[CONF_INVERTED])) for conf in config.get(CONF_ON_TURN_ON, []): diff --git a/esphome/components/switch/switch.cpp b/esphome/components/switch/switch.cpp index 0e12f5af0f..e4d20719e1 100644 --- a/esphome/components/switch/switch.cpp +++ b/esphome/components/switch/switch.cpp @@ -6,17 +6,9 @@ namespace switch_ { static const char *const TAG = "switch"; -std::string Switch::icon() { return ""; } -Switch::Switch(const std::string &name) : Nameable(name), state(false) {} +Switch::Switch(const std::string &name) : EntityBase(name), state(false) {} Switch::Switch() : Switch("") {} -std::string Switch::get_icon() { - if (this->icon_.has_value()) - return *this->icon_; - return this->icon(); -} - -void Switch::set_icon(const std::string &icon) { this->icon_ = icon; } void Switch::turn_on() { ESP_LOGD(TAG, "'%s' Turning ON.", this->get_name().c_str()); this->write_state(!this->inverted_); diff --git a/esphome/components/switch/switch.h b/esphome/components/switch/switch.h index 8cfae3b6f8..071393003a 100644 --- a/esphome/components/switch/switch.h +++ b/esphome/components/switch/switch.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/preferences.h" #include "esphome/core/helpers.h" @@ -26,7 +27,7 @@ namespace switch_ { * A switch is basically just a combination of a binary sensor (for reporting switch values) * and a write_state method that writes a state to the hardware. */ -class Switch : public Nameable { +class Switch : public EntityBase { public: explicit Switch(); explicit Switch(const std::string &name); @@ -70,12 +71,6 @@ class Switch : public Nameable { */ void set_inverted(bool inverted); - /// Set the icon for this switch. "" for no icon. - void set_icon(const std::string &icon); - - /// Get the icon for this switch. Using icon() if not manually set - std::string get_icon(); - /** Set callback for state changes. * * @param callback The void(bool) callback. @@ -104,18 +99,8 @@ class Switch : public Nameable { */ virtual void write_state(bool state) = 0; - /** Override this to set the Home Assistant icon for this switch. - * - * Return "" to disable this feature. - * - * @return The icon of this switch, for example "mdi:fan". - */ - virtual std::string icon(); // NOLINT - uint32_t hash_base() override; - optional icon_{}; ///< The icon shown here. Not set means use default from switch. Empty means no icon. - CallbackManager state_callback_{}; bool inverted_{false}; Deduplicator publish_dedup_; diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index cdb4b85e9a..5c739e1d0a 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -3,21 +3,18 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import mqtt from esphome.const import ( - CONF_DISABLED_BY_DEFAULT, CONF_FILTERS, - CONF_ICON, CONF_ID, - CONF_INTERNAL, CONF_ON_VALUE, CONF_ON_RAW_VALUE, CONF_TRIGGER_ID, CONF_MQTT_ID, - CONF_NAME, CONF_STATE, CONF_FROM, CONF_TO, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity from esphome.util import Registry @@ -25,7 +22,7 @@ IS_PLATFORM_COMPONENT = True # pylint: disable=invalid-name text_sensor_ns = cg.esphome_ns.namespace("text_sensor") -TextSensor = text_sensor_ns.class_("TextSensor", cg.Nameable) +TextSensor = text_sensor_ns.class_("TextSensor", cg.EntityBase) TextSensorPtr = TextSensor.operator("ptr") TextSensorStateTrigger = text_sensor_ns.class_( @@ -111,10 +108,9 @@ async def substitute_filter_to_code(config, filter_id): icon = cv.icon -TEXT_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( +TEXT_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor), - cv.Optional(CONF_ICON): icon, cv.Optional(CONF_FILTERS): validate_filters, cv.Optional(CONF_ON_VALUE): automation.validate_automation( { @@ -137,12 +133,7 @@ async def build_filters(config): async def setup_text_sensor_core_(var, config): - cg.add(var.set_name(config[CONF_NAME])) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) - if CONF_INTERNAL in config: - cg.add(var.set_internal(config[CONF_INTERNAL])) - if CONF_ICON in config: - cg.add(var.set_icon(config[CONF_ICON])) + await setup_entity(var, config) if config.get(CONF_FILTERS): # must exist and not be empty filters = await build_filters(config[CONF_FILTERS]) diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index 774f3a8cb6..0bcab90843 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -7,7 +7,7 @@ namespace text_sensor { static const char *const TAG = "text_sensor"; TextSensor::TextSensor() : TextSensor("") {} -TextSensor::TextSensor(const std::string &name) : Nameable(name) {} +TextSensor::TextSensor(const std::string &name) : EntityBase(name) {} void TextSensor::publish_state(const std::string &state) { this->raw_state = state; @@ -68,14 +68,6 @@ void TextSensor::internal_send_state_to_frontend(const std::string &state) { this->callback_.call(state); } -void TextSensor::set_icon(const std::string &icon) { this->icon_ = icon; } -std::string TextSensor::get_icon() { - if (this->icon_.has_value()) - return *this->icon_; - return this->icon(); -} -std::string TextSensor::icon() { return ""; } - std::string TextSensor::unique_id() { return ""; } bool TextSensor::has_state() { return this->has_state_; } uint32_t TextSensor::hash_base() { return 334300109UL; } diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index 7804deedb6..4bd77131d7 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/components/text_sensor/filter.h" @@ -18,7 +19,7 @@ namespace text_sensor { } \ } -class TextSensor : public Nameable { +class TextSensor : public EntityBase { public: explicit TextSensor(); explicit TextSensor(const std::string &name); @@ -30,8 +31,6 @@ class TextSensor : public Nameable { void publish_state(const std::string &state); - void set_icon(const std::string &icon); - /// Add a filter to the filter chain. Will be appended to the back. void add_filter(Filter *filter); @@ -53,10 +52,6 @@ class TextSensor : public Nameable { // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) - std::string get_icon(); - - virtual std::string icon(); - virtual std::string unique_id(); bool has_state(); @@ -71,7 +66,6 @@ class TextSensor : public Nameable { Filter *filter_list_{nullptr}; ///< Store all active filters. - optional icon_; bool has_state_{false}; }; diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index 46eaac98eb..6a8a416b81 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -2,12 +2,14 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, time from esphome.const import ( + CONF_ICON, CONF_ID, CONF_TIME_ID, DEVICE_CLASS_ENERGY, CONF_METHOD, STATE_CLASS_TOTAL_INCREASING, ) +from esphome.core.entity_helpers import inherit_property_from DEPENDENCIES = ["time"] @@ -45,6 +47,18 @@ CONFIG_SCHEMA = ( .extend(cv.COMPONENT_SCHEMA) ) +FINAL_VALIDATE_SCHEMA = cv.All( + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(TotalDailyEnergy), + cv.Optional(CONF_ICON): cv.icon, + cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor), + }, + extra=cv.ALLOW_EXTRA, + ), + inherit_property_from(CONF_ICON, CONF_POWER_ID), +) + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) diff --git a/esphome/components/total_daily_energy/total_daily_energy.h b/esphome/components/total_daily_energy/total_daily_energy.h index 9d2396d6e3..fedceafbd3 100644 --- a/esphome/components/total_daily_energy/total_daily_energy.h +++ b/esphome/components/total_daily_energy/total_daily_energy.h @@ -25,7 +25,6 @@ class TotalDailyEnergy : public sensor::Sensor, public Component { void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } std::string unit_of_measurement() override { return this->parent_->get_unit_of_measurement() + "h"; } - std::string icon() override { return this->parent_->get_icon(); } int8_t accuracy_decimals() override { return this->parent_->get_accuracy_decimals() + 2; } void loop() override; diff --git a/esphome/components/tsl2591/tsl2591.h b/esphome/components/tsl2591/tsl2591.h index d377d082a8..19352a15c5 100644 --- a/esphome/components/tsl2591/tsl2591.h +++ b/esphome/components/tsl2591/tsl2591.h @@ -224,7 +224,7 @@ class TSL2591Component : public PollingComponent, public i2c::I2CDevice { float get_setup_priority() const override; protected: - const char *name_; // TODO: extend esphome::Nameable + const char *name_; sensor::Sensor *full_spectrum_sensor_; sensor::Sensor *infrared_sensor_; sensor::Sensor *visible_sensor_; diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index d72262b4d3..e99431be36 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -3,6 +3,7 @@ #include "web_server.h" #include "esphome/core/log.h" #include "esphome/core/application.h" +#include "esphome/core/entity_base.h" #include "esphome/core/util.h" #include "esphome/components/json/json_util.h" #include "esphome/components/network/util.h" @@ -28,8 +29,8 @@ namespace web_server { static const char *const TAG = "web_server"; -void write_row(AsyncResponseStream *stream, Nameable *obj, const std::string &klass, const std::string &action, - const std::function &action_func = nullptr) { +void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &klass, const std::string &action, + const std::function &action_func = nullptr) { if (obj->is_internal()) return; stream->print(""); stream.print(""); diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 0d8f4f3b64..fcec74b245 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -18,6 +18,7 @@ from esphome.const import ( CONF_COMMAND_TOPIC, CONF_DISABLED_BY_DEFAULT, CONF_DISCOVERY, + CONF_ICON, CONF_ID, CONF_INTERNAL, CONF_NAME, @@ -1476,7 +1477,7 @@ class OnlyWith(Optional): pass -def _nameable_validator(config): +def _entity_base_validator(config): if CONF_NAME not in config and CONF_ID not in config: raise Invalid("At least one of 'id:' or 'name:' is required!") if CONF_NAME not in config: @@ -1587,15 +1588,16 @@ MQTT_COMMAND_COMPONENT_SCHEMA = MQTT_COMPONENT_SCHEMA.extend( } ) -NAMEABLE_SCHEMA = Schema( +ENTITY_BASE_SCHEMA = Schema( { Optional(CONF_NAME): string, Optional(CONF_INTERNAL): boolean, Optional(CONF_DISABLED_BY_DEFAULT, default=False): boolean, + Optional(CONF_ICON): icon, } ) -NAMEABLE_SCHEMA.add_extra(_nameable_validator) +ENTITY_BASE_SCHEMA.add_extra(_entity_base_validator) COMPONENT_SCHEMA = Schema({Optional(CONF_SETUP_PRIORITY): float_}) diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index c85b445b08..5692194a91 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -177,26 +177,6 @@ void PollingComponent::call_setup() { uint32_t PollingComponent::get_update_interval() const { return this->update_interval_; } void PollingComponent::set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; } -const std::string &Nameable::get_name() const { return this->name_; } -void Nameable::set_name(const std::string &name) { - this->name_ = name; - this->calc_object_id_(); -} -Nameable::Nameable(std::string name) : name_(std::move(name)) { this->calc_object_id_(); } - -const std::string &Nameable::get_object_id() { return this->object_id_; } -bool Nameable::is_internal() const { return this->internal_; } -void Nameable::set_internal(bool internal) { this->internal_ = internal; } -void Nameable::calc_object_id_() { - this->object_id_ = sanitize_string_allowlist(to_lowercase_underscore(this->name_), HOSTNAME_CHARACTER_ALLOWLIST); - // FNV-1 hash - this->object_id_hash_ = fnv1_hash(this->object_id_); -} -uint32_t Nameable::get_object_id_hash() { return this->object_id_hash_; } - -bool Nameable::is_disabled_by_default() const { return this->disabled_by_default_; } -void Nameable::set_disabled_by_default(bool disabled_by_default) { this->disabled_by_default_ = disabled_by_default; } - WarnIfComponentBlockingGuard::WarnIfComponentBlockingGuard(Component *component) : started_(millis()), component_(component) {} WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() { diff --git a/esphome/core/component.h b/esphome/core/component.h index 85256c0f0f..a1afc17c2c 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -264,40 +264,6 @@ class PollingComponent : public Component { uint32_t update_interval_; }; -/// Helper class that enables naming of objects so that it doesn't have to be re-implement every time. -class Nameable { - public: - Nameable() : Nameable("") {} - explicit Nameable(std::string name); - const std::string &get_name() const; - void set_name(const std::string &name); - /// Get the sanitized name of this nameable as an ID. Caching it internally. - const std::string &get_object_id(); - uint32_t get_object_id_hash(); - - bool is_internal() const; - void set_internal(bool internal); - - /** Check if this object is declared to be disabled by default. - * - * That means that when the device gets added to Home Assistant (or other clients) it should - * not be added to the default view by default, and a user action is necessary to manually add it. - */ - bool is_disabled_by_default() const; - void set_disabled_by_default(bool disabled_by_default); - - protected: - virtual uint32_t hash_base() = 0; - - void calc_object_id_(); - - std::string name_; - std::string object_id_; - uint32_t object_id_hash_; - bool internal_{false}; - bool disabled_by_default_{false}; -}; - class WarnIfComponentBlockingGuard { public: WarnIfComponentBlockingGuard(Component *component); diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp new file mode 100644 index 0000000000..bc94da85fe --- /dev/null +++ b/esphome/core/entity_base.cpp @@ -0,0 +1,40 @@ +#include "esphome/core/entity_base.h" +#include "esphome/core/helpers.h" + +namespace esphome { + +static const char *const TAG = "entity_base"; + +EntityBase::EntityBase(std::string name) : name_(std::move(name)) { this->calc_object_id_(); } + +// Entity Name +const std::string &EntityBase::get_name() const { return this->name_; } +void EntityBase::set_name(const std::string &name) { + this->name_ = name; + this->calc_object_id_(); +} + +// Entity Internal +bool EntityBase::is_internal() const { return this->internal_; } +void EntityBase::set_internal(bool internal) { this->internal_ = internal; } + +// Entity Disabled by Default +bool EntityBase::is_disabled_by_default() const { return this->disabled_by_default_; } +void EntityBase::set_disabled_by_default(bool disabled_by_default) { this->disabled_by_default_ = disabled_by_default; } + +// Entity Icon +const std::string &EntityBase::get_icon() const { return this->icon_; } +void EntityBase::set_icon(const std::string &name) { this->icon_ = name; } + +// Entity Object ID +const std::string &EntityBase::get_object_id() { return this->object_id_; } + +// Calculate Object ID Hash from Entity Name +void EntityBase::calc_object_id_() { + this->object_id_ = sanitize_string_allowlist(to_lowercase_underscore(this->name_), HOSTNAME_CHARACTER_ALLOWLIST); + // FNV-1 hash + this->object_id_hash_ = fnv1_hash(this->object_id_); +} +uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; } + +} // namespace esphome diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h new file mode 100644 index 0000000000..263747b721 --- /dev/null +++ b/esphome/core/entity_base.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +namespace esphome { + +// The generic Entity base class that provides an interface common to all Entities. +class EntityBase { + public: + EntityBase() : EntityBase("") {} + explicit EntityBase(std::string name); + + // Get/set the name of this Entity + const std::string &get_name() const; + void set_name(const std::string &name); + + // Get the sanitized name of this Entity as an ID. Caching it internally. + const std::string &get_object_id(); + + // Get the unique Object ID of this Entity + uint32_t get_object_id_hash(); + + // Get/set whether this Entity should be hidden from outside of ESPHome + bool is_internal() const; + void set_internal(bool internal); + + // Check if this object is declared to be disabled by default. + // That means that when the device gets added to Home Assistant (or other clients) it should + // not be added to the default view by default, and a user action is necessary to manually add it. + bool is_disabled_by_default() const; + void set_disabled_by_default(bool disabled_by_default); + + // Get/set this entity's icon + const std::string &get_icon() const; + void set_icon(const std::string &name); + + protected: + virtual uint32_t hash_base() = 0; + void calc_object_id_(); + + std::string name_; + std::string object_id_; + std::string icon_; + uint32_t object_id_hash_; + bool internal_{false}; + bool disabled_by_default_{false}; +}; + +} // namespace esphome diff --git a/esphome/core/entity_helpers.py b/esphome/core/entity_helpers.py new file mode 100644 index 0000000000..b2dbe2116e --- /dev/null +++ b/esphome/core/entity_helpers.py @@ -0,0 +1,32 @@ +import esphome.final_validate as fv + +from esphome.const import CONF_ID + + +def inherit_property_from(property_to_inherit, parent_id_property): + """Validator that inherits a configuration property from another entity, for use with FINAL_VALIDATE_SCHEMA. + + If a property is already set, it will not be inherited. + + Keyword arguments: + property_to_inherit -- the name of the property to inherit, e.g. CONF_ICON + parent_id_property -- the name of the property that holds the ID of the parent, e.g. CONF_POWER_ID + """ + + def inherit_property(config): + if property_to_inherit not in config: + fconf = fv.full_config.get() + + # Get config for the parent entity + path = fconf.get_path_for_id(config[parent_id_property])[:-1] + parent_config = fconf.get_config_for_path(path) + + # If parent sensor has the property set, inherit it + if property_to_inherit in parent_config: + path = fconf.get_path_for_id(config[CONF_ID])[:-1] + this_config = fconf.get_config_for_path(path) + this_config[property_to_inherit] = parent_config[property_to_inherit] + + return config + + return inherit_property diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index a2eafaa0e8..5b081698ad 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -1,6 +1,10 @@ import logging from esphome.const import ( + CONF_DISABLED_BY_DEFAULT, + CONF_ICON, + CONF_INTERNAL, + CONF_NAME, CONF_SETUP_PRIORITY, CONF_UPDATE_INTERVAL, CONF_TYPE_ID, @@ -90,6 +94,16 @@ async def register_parented(var, value): add(var.set_parent(paren)) +async def setup_entity(var, config): + """Set up generic properties of an Entity""" + add(var.set_name(config[CONF_NAME])) + add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) + if CONF_INTERNAL in config: + add(var.set_internal(config[CONF_INTERNAL])) + if CONF_ICON in config: + add(var.set_icon(config[CONF_ICON])) + + def extract_registry_entry_config(registry, full_config): # type: (Registry, ConfigType) -> RegistryEntry key, config = next((k, v) for k, v in full_config.items() if k in registry) diff --git a/esphome/cpp_types.py b/esphome/cpp_types.py index 7a8eb8e04c..888c319024 100644 --- a/esphome/cpp_types.py +++ b/esphome/cpp_types.py @@ -19,7 +19,7 @@ const_char_ptr = global_ns.namespace("const char *") NAN = global_ns.namespace("NAN") esphome_ns = global_ns # using namespace esphome; App = esphome_ns.App -Nameable = esphome_ns.class_("Nameable") +EntityBase = esphome_ns.class_("EntityBase") Component = esphome_ns.class_("Component") ComponentPtr = Component.operator("ptr") PollingComponent = esphome_ns.class_("PollingComponent", Component) diff --git a/tests/test1.yaml b/tests/test1.yaml index 400cdb3b6b..c0bfbb8f0c 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1708,6 +1708,7 @@ climate: name: Anova cooker ble_client_id: ble_blah unit_of_measurement: c + icon: mdi:stove script: - id: climate_custom @@ -1986,6 +1987,7 @@ fan: direction_output: gpio_26 - platform: speed id: fan_speed + icon: mdi:weather-windy output: pca_6 speed_count: 10 name: 'Living Room Fan 2' @@ -2287,6 +2289,7 @@ cover: name: 'Test AM43' id: am43_test ble_client_id: ble_foo + icon: mdi:blinds debug: diff --git a/tests/test5.yaml b/tests/test5.yaml index aca8434fbf..72df3ed212 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -51,6 +51,7 @@ binary_sensor: - platform: gpio pin: GPIO0 id: io0_button + icon: mdi:gesture-tap-button tlc5947: data_pin: GPIO12 diff --git a/tests/unit_tests/test_codegen.py b/tests/unit_tests/test_codegen.py index 9f402465fa..32d82b3062 100644 --- a/tests/unit_tests/test_codegen.py +++ b/tests/unit_tests/test_codegen.py @@ -59,7 +59,7 @@ from esphome import codegen as cg "NAN", "esphome_ns", "App", - "Nameable", + "EntityBase", "Component", "ComponentPtr", # from cpp_types