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.name = number->get_name(); | ||||
|   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.max_value = number->get_max_value(); | ||||
|   msg.step = number->get_step(); | ||||
|   msg.min_value = number->traits.get_min_value(); | ||||
|   msg.max_value = number->traits.get_max_value(); | ||||
|   msg.step = number->traits.get_step(); | ||||
|  | ||||
|   return this->send_list_entities_number_response(msg); | ||||
| } | ||||
|   | ||||
| @@ -3,10 +3,6 @@ | ||||
|  | ||||
| #ifdef USE_NUMBER | ||||
|  | ||||
| #ifdef USE_DEEP_SLEEP | ||||
| #include "esphome/components/deep_sleep/deep_sleep_component.h" | ||||
| #endif | ||||
|  | ||||
| namespace esphome { | ||||
| namespace mqtt { | ||||
|  | ||||
| @@ -20,7 +16,7 @@ void MQTTNumberComponent::setup() { | ||||
|   this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) { | ||||
|     auto val = parse_float(state); | ||||
|     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; | ||||
|     } | ||||
|     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(); } | ||||
| void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { | ||||
|   if (!this->number_->get_icon().empty()) | ||||
|     root["icon"] = this->number_->get_icon(); | ||||
|   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_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() { | ||||
|   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::publish_state(float value) { | ||||
|   int8_t accuracy = this->number_->get_accuracy_decimals(); | ||||
|   return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy)); | ||||
|   char buffer[64]; | ||||
|   snprintf(buffer, sizeof(buffer), "%f", value); | ||||
|   return this->publish(this->get_state_topic_(), buffer); | ||||
| } | ||||
|  | ||||
| }  // namespace mqtt | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| from typing import Optional | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| 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])) | ||||
|     if CONF_INTERNAL in config: | ||||
|         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, []): | ||||
|         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) | ||||
|  | ||||
|  | ||||
| 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]): | ||||
|         var = cg.Pvariable(config[CONF_ID], 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]) | ||||
|     await register_number(var, config) | ||||
|     await register_number( | ||||
|         var, config, min_value=min_value, max_value=max_value, step=step | ||||
|     ) | ||||
|     return var | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -8,67 +8,38 @@ static const char *const TAG = "number"; | ||||
|  | ||||
| void NumberCall::perform() { | ||||
|   ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); | ||||
|   if (this->value_.has_value()) { | ||||
|     auto value = *this->value_; | ||||
|     uint8_t accuracy = this->parent_->get_accuracy_decimals(); | ||||
|     float min_value = this->parent_->get_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(), | ||||
|                value_accuracy_to_string(min_value, accuracy).c_str()); | ||||
|       this->value_.reset(); | ||||
|       return; | ||||
|     } | ||||
|     float max_value = this->parent_->get_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(), | ||||
|                value_accuracy_to_string(max_value, accuracy).c_str()); | ||||
|       this->value_.reset(); | ||||
|       return; | ||||
|     } | ||||
|     ESP_LOGD(TAG, "  Value: %s", value_accuracy_to_string(*this->value_, accuracy).c_str()); | ||||
|     this->parent_->set(*this->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_; | ||||
|  | ||||
|   float min_value = traits.get_min_value(); | ||||
|   if (value < min_value) { | ||||
|     ESP_LOGW(TAG, "  Value %f must not be less than minimum %f", value, min_value); | ||||
|     return; | ||||
|   } | ||||
|   float max_value = traits.get_max_value(); | ||||
|   if (value > max_value) { | ||||
|     ESP_LOGW(TAG, "  Value %f must not be greater than maximum %f", value, max_value); | ||||
|     return; | ||||
|   } | ||||
|   ESP_LOGD(TAG, "  Value: %f", *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) { | ||||
|   this->has_state_ = true; | ||||
|   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); | ||||
| } | ||||
|  | ||||
| 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) { | ||||
|   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; } | ||||
|  | ||||
|   | ||||
| @@ -9,8 +9,8 @@ namespace number { | ||||
| #define LOG_NUMBER(prefix, type, obj) \ | ||||
|   if ((obj) != nullptr) { \ | ||||
|     ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ | ||||
|     if (!(obj)->get_icon().empty()) { \ | ||||
|       ESP_LOGCONFIG(TAG, "%s  Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ | ||||
|     if (!(obj)->traits.get_icon().empty()) { \ | ||||
|       ESP_LOGCONFIG(TAG, "%s  Icon: '%s'", prefix, (obj)->traits.get_icon().c_str()); \ | ||||
|     } \ | ||||
|   } | ||||
|  | ||||
| @@ -19,72 +19,56 @@ class Number; | ||||
| class NumberCall { | ||||
|  public: | ||||
|   explicit NumberCall(Number *parent) : parent_(parent) {} | ||||
|   NumberCall &set_value(float value); | ||||
|   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: | ||||
|   Number *const parent_; | ||||
|   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. | ||||
|  * | ||||
|  * A number can use publish_state to send out a new value. | ||||
|  */ | ||||
| class Number : public Nameable { | ||||
|  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; | ||||
|  | ||||
|   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. | ||||
|   bool has_state() const; | ||||
|  | ||||
|   /// 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_; } | ||||
|   bool has_state() const { return has_state_; } | ||||
|  | ||||
|  protected: | ||||
|   friend class NumberCall; | ||||
| @@ -95,17 +79,12 @@ class Number : public Nameable { | ||||
|    * | ||||
|    * @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; | ||||
|  | ||||
|   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}; | ||||
|   float step_{1.0}; | ||||
|   float min_value_{0}; | ||||
|   float max_value_{100}; | ||||
| }; | ||||
|  | ||||
| }  // namespace number | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import esphome.config_validation as cv | ||||
| from esphome.components import number | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_INITIAL_VALUE, | ||||
|     CONF_LAMBDA, | ||||
|     CONF_MAX_VALUE, | ||||
|     CONF_MIN_VALUE, | ||||
| @@ -32,9 +33,10 @@ CONFIG_SCHEMA = cv.All( | ||||
|             cv.Required(CONF_MAX_VALUE): cv.float_, | ||||
|             cv.Required(CONF_MIN_VALUE): cv.float_, | ||||
|             cv.Required(CONF_STEP): cv.positive_float, | ||||
|             cv.Optional(CONF_LAMBDA): cv.returning_lambda, | ||||
|             cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, | ||||
|             cv.Exclusive(CONF_LAMBDA, "lambda-optimistic"): cv.returning_lambda, | ||||
|             cv.Exclusive(CONF_OPTIMISTIC, "lambda-optimistic"): cv.boolean, | ||||
|             cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), | ||||
|             cv.Optional(CONF_INITIAL_VALUE): cv.float_, | ||||
|         } | ||||
|     ).extend(cv.polling_component_schema("60s")), | ||||
|     validate_min_max, | ||||
| @@ -44,20 +46,27 @@ CONFIG_SCHEMA = cv.All( | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     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: | ||||
|         template_ = await cg.process_lambda( | ||||
|             config[CONF_LAMBDA], [], return_type=cg.optional.template(float) | ||||
|         ) | ||||
|         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: | ||||
|         await automation.build_automation( | ||||
|             var.get_set_trigger(), [(float, "x")], config[CONF_SET_ACTION] | ||||
|         ) | ||||
|  | ||||
|     cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) | ||||
|  | ||||
|     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])) | ||||
|     if CONF_INITIAL_VALUE in config: | ||||
|         cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) | ||||
|   | ||||
| @@ -6,34 +6,45 @@ namespace template_ { | ||||
|  | ||||
| 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() { | ||||
|   if (!this->f_.has_value()) | ||||
|     return; | ||||
|  | ||||
|   auto val = (*this->f_)(); | ||||
|   if (val.has_value()) { | ||||
|     this->publish_state(*val); | ||||
|   } | ||||
|   if (!val.has_value()) | ||||
|     return; | ||||
|  | ||||
|   this->publish_state(*val); | ||||
| } | ||||
|  | ||||
| void TemplateNumber::set(float value) { | ||||
| void TemplateNumber::control(float value) { | ||||
|   this->set_trigger_->trigger(value); | ||||
|  | ||||
|   if (this->optimistic_) | ||||
|   if (this->optimistic_) { | ||||
|     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() { | ||||
|   LOG_NUMBER("", "Template Number", this); | ||||
|   ESP_LOGCONFIG(TAG, "  Optimistic: %s", YESNO(this->optimistic_)); | ||||
|   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 esphome | ||||
|   | ||||
| @@ -3,27 +3,32 @@ | ||||
| #include "esphome/components/number/number.h" | ||||
| #include "esphome/core/automation.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/preferences.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace template_ { | ||||
|  | ||||
| class TemplateNumber : public number::Number, public PollingComponent { | ||||
|  public: | ||||
|   TemplateNumber(); | ||||
|   void set_template(std::function<optional<float>()> &&f); | ||||
|   void set_template(std::function<optional<float>()> &&f) { this->f_ = f; } | ||||
|  | ||||
|   void setup() override; | ||||
|   void update() 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; | ||||
|   void set_optimistic(bool optimistic); | ||||
|   Trigger<float> *get_set_trigger() const { return set_trigger_; } | ||||
|   void set_optimistic(bool optimistic) { optimistic_ = optimistic; } | ||||
|   void set_initial_value(float initial_value) { initial_value_ = initial_value; } | ||||
|  | ||||
|  protected: | ||||
|   void set(float value) override; | ||||
|   void control(float value) override; | ||||
|   bool optimistic_{false}; | ||||
|   Trigger<float> *set_trigger_; | ||||
|   float initial_value_{NAN}; | ||||
|   Trigger<float> *set_trigger_ = new Trigger<float>(); | ||||
|   optional<std::function<optional<float>()>> f_; | ||||
|  | ||||
|   ESPPreferenceObject pref_; | ||||
| }; | ||||
|  | ||||
| }  // 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) { | ||||
|   return json::build_json([obj, value](JsonObject &root) { | ||||
|     root["id"] = "number-" + obj->get_object_id(); | ||||
|     std::string state = value_accuracy_to_string(value, obj->get_accuracy_decimals()); | ||||
|     root["state"] = state; | ||||
|     char buffer[64]; | ||||
|     snprintf(buffer, sizeof(buffer), "%f", value); | ||||
|     root["state"] = buffer; | ||||
|     root["value"] = value; | ||||
|   }); | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| """Constants used by esphome.""" | ||||
|  | ||||
| __version__ = "1.20.0b3" | ||||
| __version__ = "1.20.0b4" | ||||
|  | ||||
| ESP_PLATFORM_ESP32 = "ESP32" | ||||
| ESP_PLATFORM_ESP8266 = "ESP8266" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user