From d8746266625c5a4c9127577297876e445cffe859 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 17 Nov 2022 20:01:28 +0100 Subject: [PATCH] Add number device class support (#4042) --- esphome/components/api/api.proto | 1 + esphome/components/api/api_connection.cpp | 1 + esphome/components/api/api_pb2.cpp | 9 +++ esphome/components/api/api_pb2.h | 1 + esphome/components/mqtt/mqtt_number.cpp | 2 + esphome/components/number/__init__.py | 81 +++++++++++++++++++++ esphome/components/number/number.h | 3 + esphome/components/number/number_traits.cpp | 8 ++ esphome/components/number/number_traits.h | 5 ++ tests/test5.yaml | 1 + 10 files changed, 112 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 5df35f7978..623ab6b009 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -915,6 +915,7 @@ message ListEntitiesNumberResponse { EntityCategory entity_category = 10; string unit_of_measurement = 11; NumberMode mode = 12; + string device_class = 13; } message NumberStateResponse { option (id) = 50; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 5227750cc0..d56f4e7a40 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -616,6 +616,7 @@ bool APIConnection::send_number_info(number::Number *number) { msg.entity_category = static_cast(number->get_entity_category()); msg.unit_of_measurement = number->traits.get_unit_of_measurement(); msg.mode = static_cast(number->traits.get_mode()); + msg.device_class = number->traits.get_device_class(); msg.min_value = number->traits.get_min_value(); msg.max_value = number->traits.get_max_value(); diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 03d09a0913..bbe1da3209 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -3942,6 +3942,10 @@ bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDel this->unit_of_measurement = value.as_string(); return true; } + case 13: { + this->device_class = value.as_string(); + return true; + } default: return false; } @@ -3981,6 +3985,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_enum(10, this->entity_category); buffer.encode_string(11, this->unit_of_measurement); buffer.encode_enum(12, this->mode); + buffer.encode_string(13, this->device_class); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { @@ -4037,6 +4042,10 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { out.append(" mode: "); out.append(proto_enum_to_string(this->mode)); out.append("\n"); + + out.append(" device_class: "); + out.append("'").append(this->device_class).append("'"); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 2ca0853951..3aee31b1bf 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1004,6 +1004,7 @@ class ListEntitiesNumberResponse : public ProtoMessage { enums::EntityCategory entity_category{}; std::string unit_of_measurement{}; enums::NumberMode mode{}; + std::string device_class{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 7018792283..3a6ea97967 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -55,6 +55,8 @@ void MQTTNumberComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon root[MQTT_MODE] = "slider"; break; } + if (!this->number_->traits.get_device_class().empty()) + root[MQTT_DEVICE_CLASS] = this->number_->traits.get_device_class(); config.command_topic = true; } diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index f809fff529..ec4dead42e 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -6,6 +6,7 @@ from esphome.components import mqtt from esphome.const import ( CONF_ABOVE, CONF_BELOW, + CONF_DEVICE_CLASS, CONF_ID, CONF_MODE, CONF_ON_VALUE, @@ -16,11 +17,87 @@ from esphome.const import ( CONF_VALUE, CONF_OPERATION, CONF_CYCLE, + DEVICE_CLASS_DISTANCE, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_APPARENT_POWER, + DEVICE_CLASS_AQI, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_CARBON_DIOXIDE, + DEVICE_CLASS_CARBON_MONOXIDE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_FREQUENCY, + DEVICE_CLASS_GAS, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_MOISTURE, + DEVICE_CLASS_MONETARY, + DEVICE_CLASS_NITROGEN_DIOXIDE, + DEVICE_CLASS_NITROGEN_MONOXIDE, + DEVICE_CLASS_NITROUS_OXIDE, + DEVICE_CLASS_OZONE, + DEVICE_CLASS_PM1, + DEVICE_CLASS_PM10, + DEVICE_CLASS_PM25, + DEVICE_CLASS_POWER, + DEVICE_CLASS_POWER_FACTOR, + DEVICE_CLASS_PRECIPITATION_INTENSITY, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_REACTIVE_POWER, + DEVICE_CLASS_SIGNAL_STRENGTH, + DEVICE_CLASS_SPEED, + DEVICE_CLASS_SULPHUR_DIOXIDE, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, + DEVICE_CLASS_VOLTAGE, + DEVICE_CLASS_VOLUME, + DEVICE_CLASS_WATER, + DEVICE_CLASS_WIND_SPEED, + DEVICE_CLASS_WEIGHT, ) from esphome.core import CORE, coroutine_with_priority from esphome.cpp_helpers import setup_entity CODEOWNERS = ["@esphome/core"] +DEVICE_CLASSES = [ + DEVICE_CLASS_APPARENT_POWER, + DEVICE_CLASS_AQI, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_CARBON_DIOXIDE, + DEVICE_CLASS_CARBON_MONOXIDE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_DISTANCE, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_FREQUENCY, + DEVICE_CLASS_GAS, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_MOISTURE, + DEVICE_CLASS_MONETARY, + DEVICE_CLASS_NITROGEN_DIOXIDE, + DEVICE_CLASS_NITROGEN_MONOXIDE, + DEVICE_CLASS_NITROUS_OXIDE, + DEVICE_CLASS_OZONE, + DEVICE_CLASS_PM1, + DEVICE_CLASS_PM10, + DEVICE_CLASS_PM25, + DEVICE_CLASS_POWER_FACTOR, + DEVICE_CLASS_POWER, + DEVICE_CLASS_PRECIPITATION_INTENSITY, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_REACTIVE_POWER, + DEVICE_CLASS_SIGNAL_STRENGTH, + DEVICE_CLASS_SPEED, + DEVICE_CLASS_SULPHUR_DIOXIDE, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, + DEVICE_CLASS_VOLTAGE, + DEVICE_CLASS_VOLUME, + DEVICE_CLASS_WATER, + DEVICE_CLASS_WEIGHT, + DEVICE_CLASS_WIND_SPEED, +] IS_PLATFORM_COMPONENT = True number_ns = cg.esphome_ns.namespace("number") @@ -62,6 +139,7 @@ NUMBER_OPERATION_OPTIONS = { } icon = cv.icon +validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { @@ -82,6 +160,7 @@ NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e ), cv.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string_strict, cv.Optional(CONF_MODE, default="AUTO"): cv.enum(NUMBER_MODES, upper=True), + cv.Optional(CONF_DEVICE_CLASS): validate_device_class, } ) @@ -117,6 +196,8 @@ async def setup_number_core_( if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) await mqtt.register_mqtt_component(mqtt_, config) + if CONF_DEVICE_CLASS in config: + cg.add(var.traits.set_device_class(config[CONF_DEVICE_CLASS])) async def register_number( diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index 7f360cad7b..4f63e0480c 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -18,6 +18,9 @@ namespace number { if (!(obj)->traits.get_unit_of_measurement().empty()) { \ ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->traits.get_unit_of_measurement().c_str()); \ } \ + if (!(obj)->traits.get_device_class().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->traits.get_device_class().c_str()); \ + } \ } class Number; diff --git a/esphome/components/number/number_traits.cpp b/esphome/components/number/number_traits.cpp index dcd05daa2a..1554f8d9c9 100644 --- a/esphome/components/number/number_traits.cpp +++ b/esphome/components/number/number_traits.cpp @@ -16,5 +16,13 @@ std::string NumberTraits::get_unit_of_measurement() { return ""; } +void NumberTraits::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } + +std::string NumberTraits::get_device_class() { + if (this->device_class_.has_value()) + return *this->device_class_; + return ""; +} + } // namespace number } // namespace esphome diff --git a/esphome/components/number/number_traits.h b/esphome/components/number/number_traits.h index 47756ff66f..ee10b0010c 100644 --- a/esphome/components/number/number_traits.h +++ b/esphome/components/number/number_traits.h @@ -32,12 +32,17 @@ class NumberTraits { void set_mode(NumberMode mode) { this->mode_ = mode; } NumberMode get_mode() const { return this->mode_; } + // Set/get the device class. + void set_device_class(const std::string &device_class); + std::string get_device_class(); + protected: float min_value_ = NAN; float max_value_ = NAN; float step_ = NAN; optional unit_of_measurement_; ///< Unit of measurement override NumberMode mode_{NUMBER_MODE_AUTO}; + optional device_class_; }; } // namespace number diff --git a/tests/test5.yaml b/tests/test5.yaml index 131d2f5b7f..30416fad01 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -240,6 +240,7 @@ number: step: 5 unit_of_measurement: "%" mode: slider + device_class: humidity on_value: - logger.log: format: Number changed to %f