mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	| @@ -570,11 +570,11 @@ bool APIConnection::send_number_info(number::Number *number) { | |||||||
|   msg.object_id = number->get_object_id(); |   msg.object_id = number->get_object_id(); | ||||||
|   msg.name = number->get_name(); |   msg.name = number->get_name(); | ||||||
|   msg.unique_id = get_default_unique_id("number", number); |   msg.unique_id = get_default_unique_id("number", number); | ||||||
|   msg.icon = number->get_icon(); |   msg.icon = number->traits.get_icon(); | ||||||
|  |  | ||||||
|   msg.min_value = number->get_min_value(); |   msg.min_value = number->traits.get_min_value(); | ||||||
|   msg.max_value = number->get_max_value(); |   msg.max_value = number->traits.get_max_value(); | ||||||
|   msg.step = number->get_step(); |   msg.step = number->traits.get_step(); | ||||||
|  |  | ||||||
|   return this->send_list_entities_number_response(msg); |   return this->send_list_entities_number_response(msg); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,10 +3,6 @@ | |||||||
|  |  | ||||||
| #ifdef USE_NUMBER | #ifdef USE_NUMBER | ||||||
|  |  | ||||||
| #ifdef USE_DEEP_SLEEP |  | ||||||
| #include "esphome/components/deep_sleep/deep_sleep_component.h" |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace mqtt { | namespace mqtt { | ||||||
|  |  | ||||||
| @@ -20,7 +16,7 @@ void MQTTNumberComponent::setup() { | |||||||
|   this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) { |   this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) { | ||||||
|     auto val = parse_float(state); |     auto val = parse_float(state); | ||||||
|     if (!val.has_value()) { |     if (!val.has_value()) { | ||||||
|       ESP_LOGE(TAG, "Can't convert '%s' to number!", state.c_str()); |       ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str()); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     auto call = this->number_->make_call(); |     auto call = this->number_->make_call(); | ||||||
| @@ -39,8 +35,15 @@ std::string MQTTNumberComponent::component_type() const { return "number"; } | |||||||
|  |  | ||||||
| std::string MQTTNumberComponent::friendly_name() const { return this->number_->get_name(); } | std::string MQTTNumberComponent::friendly_name() const { return this->number_->get_name(); } | ||||||
| void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { | void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { | ||||||
|   if (!this->number_->get_icon().empty()) |   const auto &traits = number_->traits; | ||||||
|     root["icon"] = this->number_->get_icon(); |   // https://www.home-assistant.io/integrations/number.mqtt/ | ||||||
|  |   if (!traits.get_icon().empty()) | ||||||
|  |     root["icon"] = traits.get_icon(); | ||||||
|  |   root["min_value"] = traits.get_min_value(); | ||||||
|  |   root["max_value"] = traits.get_max_value(); | ||||||
|  |   root["step"] = traits.get_step(); | ||||||
|  |  | ||||||
|  |   config.command_topic = true; | ||||||
| } | } | ||||||
| bool MQTTNumberComponent::send_initial_state() { | bool MQTTNumberComponent::send_initial_state() { | ||||||
|   if (this->number_->has_state()) { |   if (this->number_->has_state()) { | ||||||
| @@ -51,8 +54,9 @@ bool MQTTNumberComponent::send_initial_state() { | |||||||
| } | } | ||||||
| bool MQTTNumberComponent::is_internal() { return this->number_->is_internal(); } | bool MQTTNumberComponent::is_internal() { return this->number_->is_internal(); } | ||||||
| bool MQTTNumberComponent::publish_state(float value) { | bool MQTTNumberComponent::publish_state(float value) { | ||||||
|   int8_t accuracy = this->number_->get_accuracy_decimals(); |   char buffer[64]; | ||||||
|   return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy)); |   snprintf(buffer, sizeof(buffer), "%f", value); | ||||||
|  |   return this->publish(this->get_state_topic_(), buffer); | ||||||
| } | } | ||||||
|  |  | ||||||
| }  // namespace mqtt | }  // namespace mqtt | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | from typing import Optional | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome import automation | from esphome import automation | ||||||
| @@ -66,12 +67,18 @@ NUMBER_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_number_core_(var, config): | 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_name(config[CONF_NAME])) | ||||||
|     if CONF_INTERNAL in config: |     if CONF_INTERNAL in config: | ||||||
|         cg.add(var.set_internal(config[CONF_INTERNAL])) |         cg.add(var.set_internal(config[CONF_INTERNAL])) | ||||||
|  |  | ||||||
|     cg.add(var.set_icon(config[CONF_ICON])) |     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: | ||||||
|  |         cg.add(var.traits.set_step(step)) | ||||||
|  |  | ||||||
|     for conf in config.get(CONF_ON_VALUE, []): |     for conf in config.get(CONF_ON_VALUE, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
| @@ -92,16 +99,24 @@ async def setup_number_core_(var, config): | |||||||
|         await mqtt.register_mqtt_component(mqtt_, config) |         await mqtt.register_mqtt_component(mqtt_, config) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def register_number(var, config): | async def register_number( | ||||||
|  |     var, config, *, min_value: float, max_value: float, step: Optional[float] = None | ||||||
|  | ): | ||||||
|     if not CORE.has_id(config[CONF_ID]): |     if not CORE.has_id(config[CONF_ID]): | ||||||
|         var = cg.Pvariable(config[CONF_ID], var) |         var = cg.Pvariable(config[CONF_ID], var) | ||||||
|     cg.add(cg.App.register_number(var)) |     cg.add(cg.App.register_number(var)) | ||||||
|     await setup_number_core_(var, config) |     await setup_number_core_( | ||||||
|  |         var, config, min_value=min_value, max_value=max_value, step=step | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def new_number(config): | async def new_number( | ||||||
|  |     config, *, min_value: float, max_value: float, step: Optional[float] = None | ||||||
|  | ): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     await register_number(var, config) |     await register_number( | ||||||
|  |         var, config, min_value=min_value, max_value=max_value, step=step | ||||||
|  |     ) | ||||||
|     return var |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,67 +8,38 @@ static const char *const TAG = "number"; | |||||||
|  |  | ||||||
| void NumberCall::perform() { | void NumberCall::perform() { | ||||||
|   ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); |   ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); | ||||||
|   if (this->value_.has_value()) { |   if (!this->value_.has_value() || isnan(*this->value_)) { | ||||||
|  |     ESP_LOGW(TAG, "No value set for NumberCall"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   const auto &traits = this->parent_->traits; | ||||||
|   auto value = *this->value_; |   auto value = *this->value_; | ||||||
|     uint8_t accuracy = this->parent_->get_accuracy_decimals(); |  | ||||||
|     float min_value = this->parent_->get_min_value(); |   float min_value = traits.get_min_value(); | ||||||
|   if (value < min_value) { |   if (value < min_value) { | ||||||
|       ESP_LOGW(TAG, "  Value %s must not be less than minimum %s", value_accuracy_to_string(value, accuracy).c_str(), |     ESP_LOGW(TAG, "  Value %f must not be less than minimum %f", value, min_value); | ||||||
|                value_accuracy_to_string(min_value, accuracy).c_str()); |  | ||||||
|       this->value_.reset(); |  | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|     float max_value = this->parent_->get_max_value(); |   float max_value = traits.get_max_value(); | ||||||
|   if (value > max_value) { |   if (value > max_value) { | ||||||
|       ESP_LOGW(TAG, "  Value %s must not be larger than maximum %s", value_accuracy_to_string(value, accuracy).c_str(), |     ESP_LOGW(TAG, "  Value %f must not be greater than maximum %f", value, max_value); | ||||||
|                value_accuracy_to_string(max_value, accuracy).c_str()); |  | ||||||
|       this->value_.reset(); |  | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|     ESP_LOGD(TAG, "  Value: %s", value_accuracy_to_string(*this->value_, accuracy).c_str()); |   ESP_LOGD(TAG, "  Value: %f", *this->value_); | ||||||
|     this->parent_->set(*this->value_); |   this->parent_->control(*this->value_); | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| NumberCall &NumberCall::set_value(float value) { |  | ||||||
|   this->value_ = value; |  | ||||||
|   return *this; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const optional<float> &NumberCall::get_value() const { return this->value_; } |  | ||||||
|  |  | ||||||
| NumberCall Number::make_call() { return NumberCall(this); } |  | ||||||
|  |  | ||||||
| void Number::publish_state(float state) { | void Number::publish_state(float state) { | ||||||
|   this->has_state_ = true; |   this->has_state_ = true; | ||||||
|   this->state = state; |   this->state = state; | ||||||
|   ESP_LOGD(TAG, "'%s': Sending state %.5f", this->get_name().c_str(), state); |   ESP_LOGD(TAG, "'%s': Sending state %f", this->get_name().c_str(), state); | ||||||
|   this->state_callback_.call(state); |   this->state_callback_.call(state); | ||||||
| } | } | ||||||
|  |  | ||||||
| uint32_t Number::update_interval() { return 0; } |  | ||||||
| Number::Number(const std::string &name) : Nameable(name), state(NAN) {} |  | ||||||
| Number::Number() : Number("") {} |  | ||||||
|  |  | ||||||
| void Number::add_on_state_callback(std::function<void(float)> &&callback) { | void Number::add_on_state_callback(std::function<void(float)> &&callback) { | ||||||
|   this->state_callback_.add(std::move(callback)); |   this->state_callback_.add(std::move(callback)); | ||||||
| } | } | ||||||
| void Number::set_icon(const std::string &icon) { this->icon_ = icon; } |  | ||||||
| std::string Number::get_icon() { return *this->icon_; } |  | ||||||
| int8_t Number::get_accuracy_decimals() { |  | ||||||
|   // use printf %g to find number of digits based on step |  | ||||||
|   char buf[32]; |  | ||||||
|   sprintf(buf, "%.5g", this->step_); |  | ||||||
|   std::string str{buf}; |  | ||||||
|   size_t dot_pos = str.find('.'); |  | ||||||
|   if (dot_pos == std::string::npos) |  | ||||||
|     return 0; |  | ||||||
|  |  | ||||||
|   return str.length() - dot_pos - 1; |  | ||||||
| } |  | ||||||
| float Number::get_state() const { return this->state; } |  | ||||||
|  |  | ||||||
| bool Number::has_state() const { return this->has_state_; } |  | ||||||
|  |  | ||||||
| uint32_t Number::hash_base() { return 2282307003UL; } | uint32_t Number::hash_base() { return 2282307003UL; } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,8 +9,8 @@ namespace number { | |||||||
| #define LOG_NUMBER(prefix, type, obj) \ | #define LOG_NUMBER(prefix, type, obj) \ | ||||||
|   if ((obj) != nullptr) { \ |   if ((obj) != nullptr) { \ | ||||||
|     ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ |     ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ | ||||||
|     if (!(obj)->get_icon().empty()) { \ |     if (!(obj)->traits.get_icon().empty()) { \ | ||||||
|       ESP_LOGCONFIG(TAG, "%s  Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ |       ESP_LOGCONFIG(TAG, "%s  Icon: '%s'", prefix, (obj)->traits.get_icon().c_str()); \ | ||||||
|     } \ |     } \ | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -19,72 +19,56 @@ class Number; | |||||||
| class NumberCall { | class NumberCall { | ||||||
|  public: |  public: | ||||||
|   explicit NumberCall(Number *parent) : parent_(parent) {} |   explicit NumberCall(Number *parent) : parent_(parent) {} | ||||||
|   NumberCall &set_value(float value); |  | ||||||
|   void perform(); |   void perform(); | ||||||
|  |  | ||||||
|   const optional<float> &get_value() const; |   NumberCall &set_value(float value) { | ||||||
|  |     value_ = value; | ||||||
|  |     return *this; | ||||||
|  |   } | ||||||
|  |   const optional<float> &get_value() const { return value_; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   Number *const parent_; |   Number *const parent_; | ||||||
|   optional<float> value_; |   optional<float> value_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | class NumberTraits { | ||||||
|  |  public: | ||||||
|  |   void set_min_value(float min_value) { min_value_ = min_value; } | ||||||
|  |   float get_min_value() const { return min_value_; } | ||||||
|  |   void set_max_value(float max_value) { max_value_ = max_value; } | ||||||
|  |   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. | /** Base-class for all numbers. | ||||||
|  * |  * | ||||||
|  * A number can use publish_state to send out a new value. |  * A number can use publish_state to send out a new value. | ||||||
|  */ |  */ | ||||||
| class Number : public Nameable { | class Number : public Nameable { | ||||||
|  public: |  public: | ||||||
|   explicit Number(); |  | ||||||
|   explicit Number(const std::string &name); |  | ||||||
|  |  | ||||||
|   /** Manually set the icon of this number. By default the number's default defined by icon() is used. |  | ||||||
|    * |  | ||||||
|    * @param icon The icon, for example "mdi:flash". "" to disable. |  | ||||||
|    */ |  | ||||||
|   void set_icon(const std::string &icon); |  | ||||||
|   /// Get the Home Assistant Icon. Uses the manual override if specified or the default value instead. |  | ||||||
|   std::string get_icon(); |  | ||||||
|  |  | ||||||
|   /// Getter-syntax for .state. |  | ||||||
|   float get_state() const; |  | ||||||
|  |  | ||||||
|   /// Get the accuracy in decimals. Based on the step value. |  | ||||||
|   int8_t get_accuracy_decimals(); |  | ||||||
|  |  | ||||||
|   /** Publish the current state to the front-end. |  | ||||||
|    */ |  | ||||||
|   void publish_state(float state); |  | ||||||
|  |  | ||||||
|   NumberCall make_call(); |  | ||||||
|  |  | ||||||
|   // ========== INTERNAL METHODS ========== |  | ||||||
|   // (In most use cases you won't need these) |  | ||||||
|   /// Add a callback that will be called every time the state changes. |  | ||||||
|   void add_on_state_callback(std::function<void(float)> &&callback); |  | ||||||
|  |  | ||||||
|   /** This member variable stores the last state. |  | ||||||
|    * |  | ||||||
|    * On startup, when no state is available yet, this is NAN (not-a-number) and the validity |  | ||||||
|    * can be checked using has_state(). |  | ||||||
|    * |  | ||||||
|    * This is exposed through a member variable for ease of use in esphome lambdas. |  | ||||||
|    */ |  | ||||||
|   float state; |   float state; | ||||||
|  |  | ||||||
|  |   void publish_state(float state); | ||||||
|  |  | ||||||
|  |   NumberCall make_call() { return NumberCall(this); } | ||||||
|  |   void set(float value) { make_call().set_value(value).perform(); } | ||||||
|  |  | ||||||
|  |   void add_on_state_callback(std::function<void(float)> &&callback); | ||||||
|  |  | ||||||
|  |   NumberTraits traits; | ||||||
|  |  | ||||||
|   /// Return whether this number has gotten a full state yet. |   /// Return whether this number has gotten a full state yet. | ||||||
|   bool has_state() const; |   bool has_state() const { return has_state_; } | ||||||
|  |  | ||||||
|   /// Return with which interval the number is polled. Return 0 for non-polling mode. |  | ||||||
|   virtual uint32_t update_interval(); |  | ||||||
|  |  | ||||||
|   void set_min_value(float min_value) { this->min_value_ = min_value; } |  | ||||||
|   void set_max_value(float max_value) { this->max_value_ = max_value; } |  | ||||||
|   void set_step(float step) { this->step_ = step; } |  | ||||||
|  |  | ||||||
|   float get_min_value() const { return this->min_value_; } |  | ||||||
|   float get_max_value() const { return this->max_value_; } |  | ||||||
|   float get_step() const { return this->step_; } |  | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   friend class NumberCall; |   friend class NumberCall; | ||||||
| @@ -95,17 +79,12 @@ class Number : public Nameable { | |||||||
|    * |    * | ||||||
|    * @param value The value as validated by the NumberCall. |    * @param value The value as validated by the NumberCall. | ||||||
|    */ |    */ | ||||||
|   virtual void set(float value) = 0; |   virtual void control(float value) = 0; | ||||||
|  |  | ||||||
|   uint32_t hash_base() override; |   uint32_t hash_base() override; | ||||||
|  |  | ||||||
|   CallbackManager<void(float)> state_callback_; |   CallbackManager<void(float)> state_callback_; | ||||||
|   /// Override the icon advertised to Home Assistant, otherwise number's icon will be used. |  | ||||||
|   optional<std::string> icon_; |  | ||||||
|   bool has_state_{false}; |   bool has_state_{false}; | ||||||
|   float step_{1.0}; |  | ||||||
|   float min_value_{0}; |  | ||||||
|   float max_value_{100}; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace number | }  // namespace number | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import esphome.config_validation as cv | |||||||
| from esphome.components import number | from esphome.components import number | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|  |     CONF_INITIAL_VALUE, | ||||||
|     CONF_LAMBDA, |     CONF_LAMBDA, | ||||||
|     CONF_MAX_VALUE, |     CONF_MAX_VALUE, | ||||||
|     CONF_MIN_VALUE, |     CONF_MIN_VALUE, | ||||||
| @@ -32,9 +33,10 @@ CONFIG_SCHEMA = cv.All( | |||||||
|             cv.Required(CONF_MAX_VALUE): cv.float_, |             cv.Required(CONF_MAX_VALUE): cv.float_, | ||||||
|             cv.Required(CONF_MIN_VALUE): cv.float_, |             cv.Required(CONF_MIN_VALUE): cv.float_, | ||||||
|             cv.Required(CONF_STEP): cv.positive_float, |             cv.Required(CONF_STEP): cv.positive_float, | ||||||
|             cv.Optional(CONF_LAMBDA): cv.returning_lambda, |             cv.Exclusive(CONF_LAMBDA, "lambda-optimistic"): cv.returning_lambda, | ||||||
|             cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, |             cv.Exclusive(CONF_OPTIMISTIC, "lambda-optimistic"): cv.boolean, | ||||||
|             cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), |             cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), | ||||||
|  |             cv.Optional(CONF_INITIAL_VALUE): cv.float_, | ||||||
|         } |         } | ||||||
|     ).extend(cv.polling_component_schema("60s")), |     ).extend(cv.polling_component_schema("60s")), | ||||||
|     validate_min_max, |     validate_min_max, | ||||||
| @@ -44,20 +46,27 @@ CONFIG_SCHEMA = cv.All( | |||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|     await number.register_number(var, config) |     await number.register_number( | ||||||
|  |         var, | ||||||
|  |         config, | ||||||
|  |         min_value=config[CONF_MIN_VALUE], | ||||||
|  |         max_value=config[CONF_MAX_VALUE], | ||||||
|  |         step=config[CONF_STEP], | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     if CONF_LAMBDA in config: |     if CONF_LAMBDA in config: | ||||||
|         template_ = await cg.process_lambda( |         template_ = await cg.process_lambda( | ||||||
|             config[CONF_LAMBDA], [], return_type=cg.optional.template(float) |             config[CONF_LAMBDA], [], return_type=cg.optional.template(float) | ||||||
|         ) |         ) | ||||||
|         cg.add(var.set_template(template_)) |         cg.add(var.set_template(template_)) | ||||||
|  |  | ||||||
|  |     elif CONF_OPTIMISTIC in config: | ||||||
|  |         cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) | ||||||
|  |  | ||||||
|     if CONF_SET_ACTION in config: |     if CONF_SET_ACTION in config: | ||||||
|         await automation.build_automation( |         await automation.build_automation( | ||||||
|             var.get_set_trigger(), [(float, "x")], config[CONF_SET_ACTION] |             var.get_set_trigger(), [(float, "x")], config[CONF_SET_ACTION] | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) |     if CONF_INITIAL_VALUE in config: | ||||||
|  |         cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) | ||||||
|     cg.add(var.set_min_value(config[CONF_MIN_VALUE])) |  | ||||||
|     cg.add(var.set_max_value(config[CONF_MAX_VALUE])) |  | ||||||
|     cg.add(var.set_step(config[CONF_STEP])) |  | ||||||
|   | |||||||
| @@ -6,34 +6,45 @@ namespace template_ { | |||||||
|  |  | ||||||
| static const char *const TAG = "template.number"; | static const char *const TAG = "template.number"; | ||||||
|  |  | ||||||
| TemplateNumber::TemplateNumber() : set_trigger_(new Trigger<float>()) {} | void TemplateNumber::setup() { | ||||||
|  |   if (this->f_.has_value() || !this->optimistic_) | ||||||
|  |     return; | ||||||
|  |  | ||||||
|  |   this->pref_ = global_preferences.make_preference<float>(this->get_object_id_hash()); | ||||||
|  |   float value; | ||||||
|  |   if (!this->pref_.load(&value)) { | ||||||
|  |     if (!isnan(this->initial_value_)) | ||||||
|  |       value = this->initial_value_; | ||||||
|  |     else | ||||||
|  |       value = this->traits.get_min_value(); | ||||||
|  |   } | ||||||
|  |   this->publish_state(value); | ||||||
|  | } | ||||||
|  |  | ||||||
| void TemplateNumber::update() { | void TemplateNumber::update() { | ||||||
|   if (!this->f_.has_value()) |   if (!this->f_.has_value()) | ||||||
|     return; |     return; | ||||||
|  |  | ||||||
|   auto val = (*this->f_)(); |   auto val = (*this->f_)(); | ||||||
|   if (val.has_value()) { |   if (!val.has_value()) | ||||||
|  |     return; | ||||||
|  |  | ||||||
|   this->publish_state(*val); |   this->publish_state(*val); | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void TemplateNumber::set(float value) { | void TemplateNumber::control(float value) { | ||||||
|   this->set_trigger_->trigger(value); |   this->set_trigger_->trigger(value); | ||||||
|  |  | ||||||
|   if (this->optimistic_) |   if (this->optimistic_) { | ||||||
|     this->publish_state(value); |     this->publish_state(value); | ||||||
|  |     this->pref_.save(&value); | ||||||
|  |   } | ||||||
| } | } | ||||||
| float TemplateNumber::get_setup_priority() const { return setup_priority::HARDWARE; } |  | ||||||
| void TemplateNumber::set_template(std::function<optional<float>()> &&f) { this->f_ = f; } |  | ||||||
| void TemplateNumber::dump_config() { | void TemplateNumber::dump_config() { | ||||||
|   LOG_NUMBER("", "Template Number", this); |   LOG_NUMBER("", "Template Number", this); | ||||||
|   ESP_LOGCONFIG(TAG, "  Optimistic: %s", YESNO(this->optimistic_)); |   ESP_LOGCONFIG(TAG, "  Optimistic: %s", YESNO(this->optimistic_)); | ||||||
|   LOG_UPDATE_INTERVAL(this); |   LOG_UPDATE_INTERVAL(this); | ||||||
| } | } | ||||||
|  |  | ||||||
| void TemplateNumber::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } |  | ||||||
| Trigger<float> *TemplateNumber::get_set_trigger() const { return this->set_trigger_; }; |  | ||||||
|  |  | ||||||
| }  // namespace template_ | }  // namespace template_ | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -3,27 +3,32 @@ | |||||||
| #include "esphome/components/number/number.h" | #include "esphome/components/number/number.h" | ||||||
| #include "esphome/core/automation.h" | #include "esphome/core/automation.h" | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/preferences.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace template_ { | namespace template_ { | ||||||
|  |  | ||||||
| class TemplateNumber : public number::Number, public PollingComponent { | class TemplateNumber : public number::Number, public PollingComponent { | ||||||
|  public: |  public: | ||||||
|   TemplateNumber(); |   void set_template(std::function<optional<float>()> &&f) { this->f_ = f; } | ||||||
|   void set_template(std::function<optional<float>()> &&f); |  | ||||||
|  |  | ||||||
|  |   void setup() override; | ||||||
|   void update() override; |   void update() override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|   float get_setup_priority() const override; |   float get_setup_priority() const override { return setup_priority::HARDWARE; } | ||||||
|  |  | ||||||
|   Trigger<float> *get_set_trigger() const; |   Trigger<float> *get_set_trigger() const { return set_trigger_; } | ||||||
|   void set_optimistic(bool optimistic); |   void set_optimistic(bool optimistic) { optimistic_ = optimistic; } | ||||||
|  |   void set_initial_value(float initial_value) { initial_value_ = initial_value; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void set(float value) override; |   void control(float value) override; | ||||||
|   bool optimistic_{false}; |   bool optimistic_{false}; | ||||||
|   Trigger<float> *set_trigger_; |   float initial_value_{NAN}; | ||||||
|  |   Trigger<float> *set_trigger_ = new Trigger<float>(); | ||||||
|   optional<std::function<optional<float>()>> f_; |   optional<std::function<optional<float>()>> f_; | ||||||
|  |  | ||||||
|  |   ESPPreferenceObject pref_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace template_ | }  // namespace template_ | ||||||
|   | |||||||
| @@ -614,8 +614,9 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM | |||||||
| std::string WebServer::number_json(number::Number *obj, float value) { | std::string WebServer::number_json(number::Number *obj, float value) { | ||||||
|   return json::build_json([obj, value](JsonObject &root) { |   return json::build_json([obj, value](JsonObject &root) { | ||||||
|     root["id"] = "number-" + obj->get_object_id(); |     root["id"] = "number-" + obj->get_object_id(); | ||||||
|     std::string state = value_accuracy_to_string(value, obj->get_accuracy_decimals()); |     char buffer[64]; | ||||||
|     root["state"] = state; |     snprintf(buffer, sizeof(buffer), "%f", value); | ||||||
|  |     root["state"] = buffer; | ||||||
|     root["value"] = value; |     root["value"] = value; | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| """Constants used by esphome.""" | """Constants used by esphome.""" | ||||||
|  |  | ||||||
| __version__ = "1.20.0b3" | __version__ = "1.20.0b4" | ||||||
|  |  | ||||||
| ESP_PLATFORM_ESP32 = "ESP32" | ESP_PLATFORM_ESP32 = "ESP32" | ||||||
| ESP_PLATFORM_ESP8266 = "ESP8266" | ESP_PLATFORM_ESP8266 = "ESP8266" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user