diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 3e2c806135..0f7a5839ab 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -885,6 +885,7 @@ message ListEntitiesNumberResponse { float step = 8; bool disabled_by_default = 9; EntityCategory entity_category = 10; + string unit_of_measurement = 11; } message NumberStateResponse { option (id) = 50; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 8367afc042..b41a7633a8 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -619,6 +619,7 @@ bool APIConnection::send_number_info(number::Number *number) { msg.icon = number->get_icon(); msg.disabled_by_default = number->is_disabled_by_default(); msg.entity_category = static_cast(number->get_entity_category()); + msg.unit_of_measurement = number->traits.get_unit_of_measurement(); 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 b6974de08e..62cecb7818 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -3780,6 +3780,10 @@ bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDel this->icon = value.as_string(); return true; } + case 11: { + this->unit_of_measurement = value.as_string(); + return true; + } default: return false; } @@ -3817,6 +3821,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(8, this->step); buffer.encode_bool(9, this->disabled_by_default); buffer.encode_enum(10, this->entity_category); + buffer.encode_string(11, this->unit_of_measurement); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { @@ -3865,6 +3870,10 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { out.append(" entity_category: "); out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); + + out.append(" unit_of_measurement: "); + out.append("'").append(this->unit_of_measurement).append("'"); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 4d1f658910..4866f50c9b 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -957,6 +957,7 @@ class ListEntitiesNumberResponse : public ProtoMessage { float step{0.0f}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; + std::string unit_of_measurement{}; 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 337013055a..bf8b6b39c5 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -43,6 +43,8 @@ void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo root[MQTT_MIN] = traits.get_min_value(); root[MQTT_MAX] = traits.get_max_value(); root[MQTT_STEP] = traits.get_step(); + if (!this->number_->traits.get_unit_of_measurement().empty()) + root[MQTT_UNIT_OF_MEASUREMENT] = this->number_->traits.get_unit_of_measurement(); config.command_topic = true; } diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index 1da25caafe..ae15704a91 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_ON_VALUE, CONF_ON_VALUE_RANGE, CONF_TRIGGER_ID, + CONF_UNIT_OF_MEASUREMENT, CONF_MQTT_ID, CONF_VALUE, ) @@ -58,6 +59,7 @@ NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e }, cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW), ), + cv.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string_strict, } ) @@ -86,6 +88,8 @@ async def setup_number_core_( cg.add(trigger.set_max(template_)) await automation.build_automation(trigger, [(float, "x")], conf) + if CONF_UNIT_OF_MEASUREMENT in config: + cg.add(var.traits.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT])) if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) await mqtt.register_mqtt_component(mqtt_, config) diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp index 57a5c7c4bd..99a2c04a22 100644 --- a/esphome/components/number/number.cpp +++ b/esphome/components/number/number.cpp @@ -41,6 +41,15 @@ void Number::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } +std::string NumberTraits::get_unit_of_measurement() { + if (this->unit_of_measurement_.has_value()) + return *this->unit_of_measurement_; + return ""; +} +void NumberTraits::set_unit_of_measurement(const std::string &unit_of_measurement) { + this->unit_of_measurement_ = unit_of_measurement; +} + uint32_t Number::hash_base() { return 2282307003UL; } } // namespace number diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index ed104fb477..b3214913d9 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -13,6 +13,9 @@ namespace number { if (!(obj)->get_icon().empty()) { \ ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ } \ + 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()); \ + } \ } class Number; @@ -42,10 +45,16 @@ class NumberTraits { void set_step(float step) { step_ = step; } float get_step() const { return step_; } + /// Get the unit of measurement, using the manual override if set. + std::string get_unit_of_measurement(); + /// Manually set the unit of measurement. + void set_unit_of_measurement(const std::string &unit_of_measurement); + protected: float min_value_ = NAN; float max_value_ = NAN; float step_ = NAN; + optional unit_of_measurement_; ///< Unit of measurement override }; /** Base-class for all numbers. diff --git a/tests/test5.yaml b/tests/test5.yaml index f1fb786fe5..708db55044 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -97,6 +97,7 @@ number: max_value: 100 min_value: 0 step: 5 + unit_of_measurement: '%' select: - platform: template