diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 0f7a5839ab..dca722dca5 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -869,6 +869,11 @@ message ClimateCommandRequest { } // ==================== NUMBER ==================== +enum NumberMode { + NUMBER_MODE_AUTO = 0; + NUMBER_MODE_BOX = 1; + NUMBER_MODE_SLIDER = 2; +} message ListEntitiesNumberResponse { option (id) = 49; option (source) = SOURCE_SERVER; @@ -886,6 +891,7 @@ message ListEntitiesNumberResponse { bool disabled_by_default = 9; EntityCategory entity_category = 10; string unit_of_measurement = 11; + NumberMode mode = 12; } message NumberStateResponse { option (id) = 50; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index b41a7633a8..92699df0da 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -620,6 +620,7 @@ bool APIConnection::send_number_info(number::Number *number) { 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.mode = static_cast(number->traits.get_mode()); 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 62cecb7818..9228be0860 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -266,6 +266,18 @@ template<> const char *proto_enum_to_string(enums::Climate return "UNKNOWN"; } } +template<> const char *proto_enum_to_string(enums::NumberMode value) { + switch (value) { + case enums::NUMBER_MODE_AUTO: + return "NUMBER_MODE_AUTO"; + case enums::NUMBER_MODE_BOX: + return "NUMBER_MODE_BOX"; + case enums::NUMBER_MODE_SLIDER: + return "NUMBER_MODE_SLIDER"; + default: + return "UNKNOWN"; + } +} bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -3758,6 +3770,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->entity_category = value.as_enum(); return true; } + case 12: { + this->mode = value.as_enum(); + return true; + } default: return false; } @@ -3822,6 +3838,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(9, this->disabled_by_default); buffer.encode_enum(10, this->entity_category); buffer.encode_string(11, this->unit_of_measurement); + buffer.encode_enum(12, this->mode); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { @@ -3874,6 +3891,10 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { out.append(" unit_of_measurement: "); out.append("'").append(this->unit_of_measurement).append("'"); out.append("\n"); + + out.append(" mode: "); + out.append(proto_enum_to_string(this->mode)); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 4866f50c9b..e92b2fa4b6 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -123,6 +123,11 @@ enum ClimatePreset : uint32_t { CLIMATE_PRESET_SLEEP = 6, CLIMATE_PRESET_ACTIVITY = 7, }; +enum NumberMode : uint32_t { + NUMBER_MODE_AUTO = 0, + NUMBER_MODE_BOX = 1, + NUMBER_MODE_SLIDER = 2, +}; } // namespace enums @@ -958,6 +963,7 @@ class ListEntitiesNumberResponse : public ProtoMessage { bool disabled_by_default{false}; enums::EntityCategory entity_category{}; std::string unit_of_measurement{}; + enums::NumberMode mode{}; 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_const.h b/esphome/components/mqtt/mqtt_const.h index 1d5e22efde..8134a6b53e 100644 --- a/esphome/components/mqtt/mqtt_const.h +++ b/esphome/components/mqtt/mqtt_const.h @@ -514,6 +514,7 @@ constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "suggested_area"; // Additional MQTT fields where no abbreviation is defined in HA source constexpr const char *const MQTT_ENTITY_CATEGORY = "entity_category"; +constexpr const char *const MQTT_MODE = "mode"; } // namespace mqtt } // namespace esphome diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index bf8b6b39c5..18e3a61417 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -45,6 +45,16 @@ void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo 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(); + switch (this->number_->traits.get_mode()) { + case NUMBER_MODE_AUTO: + break; + case NUMBER_MODE_BOX: + root[MQTT_MODE] = "box"; + break; + case NUMBER_MODE_SLIDER: + root[MQTT_MODE] = "slider"; + break; + } config.command_topic = true; } diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index ae15704a91..71e288a4cc 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -7,6 +7,7 @@ from esphome.const import ( CONF_ABOVE, CONF_BELOW, CONF_ID, + CONF_MODE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, CONF_TRIGGER_ID, @@ -40,6 +41,14 @@ NumberInRangeCondition = number_ns.class_( "NumberInRangeCondition", automation.Condition ) +NumberMode = number_ns.enum("NumberMode") + +NUMBER_MODES = { + "AUTO": NumberMode.NUMBER_MODE_AUTO, + "BOX": NumberMode.NUMBER_MODE_BOX, + "SLIDER": NumberMode.NUMBER_MODE_SLIDER, +} + icon = cv.icon NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( @@ -60,6 +69,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, + cv.Optional(CONF_MODE, default="AUTO"): cv.enum(NUMBER_MODES, upper=True), } ) @@ -74,6 +84,8 @@ async def setup_number_core_( if step is not None: cg.add(var.traits.set_step(step)) + cg.add(var.traits.set_mode(config[CONF_MODE])) + for conf in config.get(CONF_ON_VALUE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [(float, "x")], conf) diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index b3214913d9..40fdfceec1 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -36,6 +36,12 @@ class NumberCall { optional value_; }; +enum NumberMode : uint8_t { + NUMBER_MODE_AUTO = 0, + NUMBER_MODE_BOX = 1, + NUMBER_MODE_SLIDER = 2, +}; + class NumberTraits { public: void set_min_value(float min_value) { min_value_ = min_value; } @@ -50,11 +56,16 @@ class NumberTraits { /// Manually set the unit of measurement. void set_unit_of_measurement(const std::string &unit_of_measurement); + // Get/set the frontend mode. + NumberMode get_mode() const { return this->mode_; } + void set_mode(NumberMode mode) { this->mode_ = mode; } + 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}; }; /** Base-class for all numbers. diff --git a/tests/test5.yaml b/tests/test5.yaml index 708db55044..aa3d057252 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -10,7 +10,7 @@ esp32: framework: type: esp-idf advanced: - ignore_efuse_mac_crc: true + ignore_efuse_mac_crc: true wifi: networks: @@ -98,6 +98,7 @@ number: min_value: 0 step: 5 unit_of_measurement: '%' + mode: slider select: - platform: template