aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--components/template/__init__.py3
-rw-r--r--components/template/binary_sensor/__init__.py47
-rw-r--r--components/template/binary_sensor/template_binary_sensor.cpp21
-rw-r--r--components/template/binary_sensor/template_binary_sensor.h23
-rw-r--r--components/template/button/__init__.py13
-rw-r--r--components/template/cover/__init__.py131
-rw-r--r--components/template/cover/template_cover.cpp129
-rw-r--r--components/template/cover/template_cover.h60
-rw-r--r--components/template/number/__init__.py93
-rw-r--r--components/template/number/template_number.cpp62
-rw-r--r--components/template/number/template_number.h37
-rw-r--r--components/template/output/__init__.py47
-rw-r--r--components/template/output/template_output.h31
-rw-r--r--components/template/select/__init__.py84
-rw-r--r--components/template/select/template_select.cpp80
-rw-r--r--components/template/select/template_select.h37
-rw-r--r--components/template/sensor/__init__.py59
-rw-r--r--components/template/sensor/template_sensor.cpp28
-rw-r--r--components/template/sensor/template_sensor.h24
-rw-r--r--components/template/switch/__init__.py91
-rw-r--r--components/template/switch/template_switch.cpp66
-rw-r--r--components/template/switch/template_switch.h42
-rw-r--r--components/template/text_sensor/__init__.py48
-rw-r--r--components/template/text_sensor/template_text_sensor.cpp24
-rw-r--r--components/template/text_sensor/template_text_sensor.h25
-rw-r--r--components/total_daily_energy/sensor.py100
-rw-r--r--components/total_daily_energy/total_daily_energy.cpp86
-rw-r--r--components/total_daily_energy/total_daily_energy.h57
28 files changed, 1548 insertions, 0 deletions
diff --git a/components/template/__init__.py b/components/template/__init__.py
new file mode 100644
index 0000000..6253af9
--- /dev/null
+++ b/components/template/__init__.py
@@ -0,0 +1,3 @@
+import esphome.codegen as cg
+
+template_ns = cg.esphome_ns.namespace("template_")
diff --git a/components/template/binary_sensor/__init__.py b/components/template/binary_sensor/__init__.py
new file mode 100644
index 0000000..8f551e3
--- /dev/null
+++ b/components/template/binary_sensor/__init__.py
@@ -0,0 +1,47 @@
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome import automation
+from esphome.components import binary_sensor
+from esphome.const import CONF_ID, CONF_LAMBDA, CONF_STATE
+from .. import template_ns
+
+TemplateBinarySensor = template_ns.class_(
+ "TemplateBinarySensor", binary_sensor.BinarySensor, cg.Component
+)
+
+CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
+ {
+ cv.GenerateID(): cv.declare_id(TemplateBinarySensor),
+ cv.Optional(CONF_LAMBDA): cv.returning_lambda,
+ }
+).extend(cv.COMPONENT_SCHEMA)
+
+
+async def to_code(config):
+ var = cg.new_Pvariable(config[CONF_ID])
+ await cg.register_component(var, config)
+ await binary_sensor.register_binary_sensor(var, config)
+
+ if CONF_LAMBDA in config:
+ template_ = await cg.process_lambda(
+ config[CONF_LAMBDA], [], return_type=cg.optional.template(bool)
+ )
+ cg.add(var.set_template(template_))
+
+
+ "binary_sensor.template.publish",
+ binary_sensor.BinarySensorPublishAction,
+ cv.Schema(
+ {
+ cv.Required(CONF_ID): cv.use_id(binary_sensor.BinarySensor),
+ cv.Required(CONF_STATE): cv.templatable(cv.boolean),
+ }
+ ),
+)
+async def binary_sensor_template_publish_to_code(config, action_id, template_arg, args):
+ paren = await cg.get_variable(config[CONF_ID])
+ var = cg.new_Pvariable(action_id, template_arg, paren)
+ template_ = await cg.templatable(config[CONF_STATE], args, bool)
+ cg.add(var.set_state(template_))
+ return var
diff --git a/components/template/binary_sensor/template_binary_sensor.cpp b/components/template/binary_sensor/template_binary_sensor.cpp
new file mode 100644
index 0000000..66ff4be
--- /dev/null
+++ b/components/template/binary_sensor/template_binary_sensor.cpp
@@ -0,0 +1,21 @@
+#include "template_binary_sensor.h"
+#include "esphome/core/log.h"
+
+namespace esphome {
+namespace template_ {
+
+static const char *const TAG = "template.binary_sensor";
+
+void TemplateBinarySensor::loop() {
+ if (!this->f_.has_value())
+ return;
+
+ auto s = (*this->f_)();
+ if (s.has_value()) {
+ this->publish_state(*s);
+ }
+}
+void TemplateBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Template Binary Sensor", this); }
+
+} // namespace template_
+} // namespace esphome
diff --git a/components/template/binary_sensor/template_binary_sensor.h b/components/template/binary_sensor/template_binary_sensor.h
new file mode 100644
index 0000000..a28929b
--- /dev/null
+++ b/components/template/binary_sensor/template_binary_sensor.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "esphome/core/component.h"
+#include "esphome/components/binary_sensor/binary_sensor.h"
+
+namespace esphome {
+namespace template_ {
+
+class TemplateBinarySensor : public Component, public binary_sensor::BinarySensor {
+ public:
+ void set_template(std::function<optional<bool>()> &&f) { this->f_ = f; }
+
+ void loop() override;
+ void dump_config() override;
+
+ float get_setup_priority() const override { return setup_priority::HARDWARE; }
+
+ protected:
+ optional<std::function<optional<bool>()>> f_{};
+};
+
+} // namespace template_
+} // namespace esphome
diff --git a/components/template/button/__init__.py b/components/template/button/__init__.py
new file mode 100644
index 0000000..aa192d1
--- /dev/null
+++ b/components/template/button/__init__.py
@@ -0,0 +1,13 @@
+import esphome.config_validation as cv
+from esphome.components import button
+
+
+CONFIG_SCHEMA = button.BUTTON_SCHEMA.extend(
+ {
+ cv.GenerateID(): cv.declare_id(button.Button),
+ }
+).extend(cv.COMPONENT_SCHEMA)
+
+
+async def to_code(config):
+ await button.new_button(config)
diff --git a/components/template/cover/__init__.py b/components/template/cover/__init__.py
new file mode 100644
index 0000000..a628da7
--- /dev/null
+++ b/components/template/cover/__init__.py
@@ -0,0 +1,131 @@
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome import automation
+from esphome.components import cover
+from esphome.const import (
+ CONF_ASSUMED_STATE,
+ CONF_CLOSE_ACTION,
+ CONF_CURRENT_OPERATION,
+ CONF_ID,
+ CONF_LAMBDA,
+ CONF_OPEN_ACTION,
+ CONF_OPTIMISTIC,
+ CONF_POSITION,
+ CONF_RESTORE_MODE,
+ CONF_STATE,
+ CONF_STOP_ACTION,
+ CONF_TILT,
+ CONF_TILT_ACTION,
+ CONF_TILT_LAMBDA,
+ CONF_POSITION_ACTION,
+)
+from .. import template_ns
+
+TemplateCover = template_ns.class_("TemplateCover", cover.Cover, cg.Component)
+
+TemplateCoverRestoreMode = template_ns.enum("TemplateCoverRestoreMode")
+RESTORE_MODES = {
+ "NO_RESTORE": TemplateCoverRestoreMode.COVER_NO_RESTORE,
+ "RESTORE": TemplateCoverRestoreMode.COVER_RESTORE,
+ "RESTORE_AND_CALL": TemplateCoverRestoreMode.COVER_RESTORE_AND_CALL,
+}
+
+CONF_HAS_POSITION = "has_position"
+
+CONFIG_SCHEMA = cover.COVER_SCHEMA.extend(
+ {
+ cv.GenerateID(): cv.declare_id(TemplateCover),
+ cv.Optional(CONF_LAMBDA): cv.returning_lambda,
+ cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
+ cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean,
+ cv.Optional(CONF_HAS_POSITION, default=False): cv.boolean,
+ cv.Optional(CONF_OPEN_ACTION): automation.validate_automation(single=True),
+ cv.Optional(CONF_CLOSE_ACTION): automation.validate_automation(single=True),
+ cv.Optional(CONF_STOP_ACTION): automation.validate_automation(single=True),
+ cv.Optional(CONF_TILT_ACTION): automation.validate_automation(single=True),
+ cv.Optional(CONF_TILT_LAMBDA): cv.returning_lambda,
+ cv.Optional(CONF_POSITION_ACTION): automation.validate_automation(single=True),
+ cv.Optional(CONF_RESTORE_MODE, default="RESTORE"): cv.enum(
+ RESTORE_MODES, upper=True
+ ),
+ }
+).extend(cv.COMPONENT_SCHEMA)
+
+
+async def to_code(config):
+ var = cg.new_Pvariable(config[CONF_ID])
+ await cg.register_component(var, config)
+ await cover.register_cover(var, config)
+ if CONF_LAMBDA in config:
+ template_ = await cg.process_lambda(
+ config[CONF_LAMBDA], [], return_type=cg.optional.template(float)
+ )
+ cg.add(var.set_state_lambda(template_))
+ if CONF_OPEN_ACTION in config:
+ await automation.build_automation(
+ var.get_open_trigger(), [], config[CONF_OPEN_ACTION]
+ )
+ if CONF_CLOSE_ACTION in config:
+ await automation.build_automation(
+ var.get_close_trigger(), [], config[CONF_CLOSE_ACTION]
+ )
+ if CONF_STOP_ACTION in config:
+ await automation.build_automation(
+ var.get_stop_trigger(), [], config[CONF_STOP_ACTION]
+ )
+ if CONF_TILT_ACTION in config:
+ await automation.build_automation(
+ var.get_tilt_trigger(), [(float, "tilt")], config[CONF_TILT_ACTION]
+ )
+ cg.add(var.set_has_tilt(True))
+ if CONF_TILT_LAMBDA in config:
+ tilt_template_ = await cg.process_lambda(
+ config[CONF_TILT_LAMBDA], [], return_type=cg.optional.template(float)
+ )
+ cg.add(var.set_tilt_lambda(tilt_template_))
+ if CONF_POSITION_ACTION in config:
+ await automation.build_automation(
+ var.get_position_trigger(), [(float, "pos")], config[CONF_POSITION_ACTION]
+ )
+ cg.add(var.set_has_position(True))
+ else:
+ cg.add(var.set_has_position(config[CONF_HAS_POSITION]))
+ cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
+ cg.add(var.set_assumed_state(config[CONF_ASSUMED_STATE]))
+ cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE]))
+ cg.add(var.set_has_position(config[CONF_HAS_POSITION]))
+
+
+ "cover.template.publish",
+ cover.CoverPublishAction,
+ cv.Schema(
+ {
+ cv.Required(CONF_ID): cv.use_id(cover.Cover),
+ cv.Exclusive(CONF_STATE, "pos"): cv.templatable(cover.validate_cover_state),
+ cv.Exclusive(CONF_POSITION, "pos"): cv.templatable(cv.zero_to_one_float),
+ cv.Optional(CONF_CURRENT_OPERATION): cv.templatable(
+ cover.validate_cover_operation
+ ),
+ cv.Optional(CONF_TILT): cv.templatable(cv.zero_to_one_float),
+ }
+ ),
+)
+async def cover_template_publish_to_code(config, action_id, template_arg, args):
+ paren = await cg.get_variable(config[CONF_ID])
+ var = cg.new_Pvariable(action_id, template_arg, paren)
+ if CONF_STATE in config:
+ template_ = await cg.templatable(config[CONF_STATE], args, float)
+ cg.add(var.set_position(template_))
+ if CONF_POSITION in config:
+ template_ = await cg.templatable(config[CONF_POSITION], args, float)
+ cg.add(var.set_position(template_))
+ if CONF_TILT in config:
+ template_ = await cg.templatable(config[CONF_TILT], args, float)
+ cg.add(var.set_tilt(template_))
+ if CONF_CURRENT_OPERATION in config:
+ template_ = await cg.templatable(
+ config[CONF_CURRENT_OPERATION], args, cover.CoverOperation
+ )
+ cg.add(var.set_current_operation(template_))
+ return var
diff --git a/components/template/cover/template_cover.cpp b/components/template/cover/template_cover.cpp
new file mode 100644
index 0000000..47c651e
--- /dev/null
+++ b/components/template/cover/template_cover.cpp
@@ -0,0 +1,129 @@
+#include "template_cover.h"
+#include "esphome/core/log.h"
+
+namespace esphome {
+namespace template_ {
+
+using namespace esphome::cover;
+
+static const char *const TAG = "template.cover";
+
+TemplateCover::TemplateCover()
+ : open_trigger_(new Trigger<>()),
+ close_trigger_(new Trigger<>),
+ stop_trigger_(new Trigger<>()),
+ position_trigger_(new Trigger<float>()),
+ tilt_trigger_(new Trigger<float>()) {}
+void TemplateCover::setup() {
+ ESP_LOGCONFIG(TAG, "Setting up template cover '%s'...", this->name_.c_str());
+ switch (this->restore_mode_) {
+ case COVER_NO_RESTORE:
+ break;
+ case COVER_RESTORE: {
+ auto restore = this->restore_state_();
+ if (restore.has_value())
+ restore->apply(this);
+ break;
+ }
+ case COVER_RESTORE_AND_CALL: {
+ auto restore = this->restore_state_();
+ if (restore.has_value()) {
+ restore->to_call(this).perform();
+ }
+ break;
+ }
+ }
+}
+void TemplateCover::loop() {
+ bool changed = false;
+
+ if (this->state_f_.has_value()) {
+ auto s = (*this->state_f_)();
+ if (s.has_value()) {
+ auto pos = clamp(*s, 0.0f, 1.0f);
+ if (pos != this->position) {
+ this->position = pos;
+ changed = true;
+ }
+ }
+ }
+ if (this->tilt_f_.has_value()) {
+ auto s = (*this->tilt_f_)();
+ if (s.has_value()) {
+ auto tilt = clamp(*s, 0.0f, 1.0f);
+ if (tilt != this->tilt) {
+ this->tilt = tilt;
+ changed = true;
+ }
+ }
+ }
+
+ if (changed)
+ this->publish_state();
+}
+void TemplateCover::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
+void TemplateCover::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
+void TemplateCover::set_state_lambda(std::function<optional<float>()> &&f) { this->state_f_ = f; }
+float TemplateCover::get_setup_priority() const { return setup_priority::HARDWARE; }
+Trigger<> *TemplateCover::get_open_trigger() const { return this->open_trigger_; }
+Trigger<> *TemplateCover::get_close_trigger() const { return this->close_trigger_; }
+Trigger<> *TemplateCover::get_stop_trigger() const { return this->stop_trigger_; }
+void TemplateCover::dump_config() { LOG_COVER("", "Template Cover", this); }
+void TemplateCover::control(const CoverCall &call) {
+ if (call.get_stop()) {
+ this->stop_prev_trigger_();
+ this->stop_trigger_->trigger();
+ this->prev_command_trigger_ = this->stop_trigger_;
+ this->publish_state();
+ }
+ if (call.get_position().has_value()) {
+ auto pos = *call.get_position();
+ this->stop_prev_trigger_();
+
+ if (pos == COVER_OPEN) {
+ this->open_trigger_->trigger();
+ this->prev_command_trigger_ = this->open_trigger_;
+ } else if (pos == COVER_CLOSED) {
+ this->close_trigger_->trigger();
+ this->prev_command_trigger_ = this->close_trigger_;
+ } else {
+ this->position_trigger_->trigger(pos);
+ }
+
+ if (this->optimistic_) {
+ this->position = pos;
+ }
+ }
+
+ if (call.get_tilt().has_value()) {
+ auto tilt = *call.get_tilt();
+ this->tilt_trigger_->trigger(tilt);
+
+ if (this->optimistic_) {
+ this->tilt = tilt;
+ }
+ }
+
+ this->publish_state();
+}
+CoverTraits TemplateCover::get_traits() {
+ auto traits = CoverTraits();
+ traits.set_is_assumed_state(this->assumed_state_);
+ traits.set_supports_position(this->has_position_);
+ traits.set_supports_tilt(this->has_tilt_);
+ return traits;
+}
+Trigger<float> *TemplateCover::get_position_trigger() const { return this->position_trigger_; }
+Trigger<float> *TemplateCover::get_tilt_trigger() const { return this->tilt_trigger_; }
+void TemplateCover::set_tilt_lambda(std::function<optional<float>()> &&tilt_f) { this->tilt_f_ = tilt_f; }
+void TemplateCover::set_has_position(bool has_position) { this->has_position_ = has_position; }
+void TemplateCover::set_has_tilt(bool has_tilt) { this->has_tilt_ = has_tilt; }
+void TemplateCover::stop_prev_trigger_() {
+ if (this->prev_command_trigger_ != nullptr) {
+ this->prev_command_trigger_->stop_action();
+ this->prev_command_trigger_ = nullptr;
+ }
+}
+
+} // namespace template_
+} // namespace esphome
diff --git a/components/template/cover/template_cover.h b/components/template/cover/template_cover.h
new file mode 100644
index 0000000..3b9dcea
--- /dev/null
+++ b/components/template/cover/template_cover.h
@@ -0,0 +1,60 @@
+#pragma once
+
+#include "esphome/core/component.h"
+#include "esphome/core/automation.h"
+#include "esphome/components/cover/cover.h"
+
+namespace esphome {
+namespace template_ {
+
+enum TemplateCoverRestoreMode {
+ COVER_NO_RESTORE,
+ COVER_RESTORE,
+ COVER_RESTORE_AND_CALL,
+};
+
+class TemplateCover : public cover::Cover, public Component {
+ public:
+ TemplateCover();
+
+ void set_state_lambda(std::function<optional<float>()> &&f);
+ Trigger<> *get_open_trigger() const;
+ Trigger<> *get_close_trigger() const;
+ Trigger<> *get_stop_trigger() const;
+ Trigger<float> *get_position_trigger() const;
+ Trigger<float> *get_tilt_trigger() const;
+ void set_optimistic(bool optimistic);
+ void set_assumed_state(bool assumed_state);
+ void set_tilt_lambda(std::function<optional<float>()> &&tilt_f);
+ void set_has_position(bool has_position);
+ void set_has_tilt(bool has_tilt);
+ void set_restore_mode(TemplateCoverRestoreMode restore_mode) { restore_mode_ = restore_mode; }
+
+ void setup() override;
+ void loop() override;
+ void dump_config() override;
+
+ float get_setup_priority() const override;
+
+ protected:
+ void control(const cover::CoverCall &call) override;
+ cover::CoverTraits get_traits() override;
+ void stop_prev_trigger_();
+
+ TemplateCoverRestoreMode restore_mode_{COVER_RESTORE};
+ optional<std::function<optional<float>()>> state_f_;
+ optional<std::function<optional<float>()>> tilt_f_;
+ bool assumed_state_{false};
+ bool optimistic_{false};
+ Trigger<> *open_trigger_;
+ Trigger<> *close_trigger_;
+ Trigger<> *stop_trigger_;
+ Trigger<> *prev_command_trigger_{nullptr};
+ Trigger<float> *position_trigger_;
+ bool has_position_{false};
+ Trigger<float> *tilt_trigger_;
+ bool has_tilt_{false};
+};
+
+} // namespace template_
+} // namespace esphome
diff --git a/components/template/number/__init__.py b/components/template/number/__init__.py
new file mode 100644
index 0000000..3dec706
--- /dev/null
+++ b/components/template/number/__init__.py
@@ -0,0 +1,93 @@
+from esphome import automation
+import esphome.codegen as cg
+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,
+ CONF_OPTIMISTIC,
+ CONF_RESTORE_VALUE,
+ CONF_STEP,
+)
+from .. import template_ns
+
+TemplateNumber = template_ns.class_(
+ "TemplateNumber", number.Number, cg.PollingComponent
+)
+
+CONF_SET_ACTION = "set_action"
+
+
+def validate_min_max(config):
+ if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]:
+ raise cv.Invalid("max_value must be greater than min_value")
+ return config
+
+
+def validate(config):
+ if CONF_LAMBDA in config:
+ if config[CONF_OPTIMISTIC]:
+ raise cv.Invalid("optimistic cannot be used with lambda")
+ if CONF_INITIAL_VALUE in config:
+ raise cv.Invalid("initial_value cannot be used with lambda")
+ if CONF_RESTORE_VALUE in config:
+ raise cv.Invalid("restore_value cannot be used with lambda")
+ elif CONF_INITIAL_VALUE not in config:
+ config[CONF_INITIAL_VALUE] = config[CONF_MIN_VALUE]
+
+ if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config:
+ raise cv.Invalid(
+ "Either optimistic mode must be enabled, or set_action must be set, to handle the number being set."
+ )
+ return config
+
+
+CONFIG_SCHEMA = cv.All(
+ number.NUMBER_SCHEMA.extend(
+ {
+ cv.GenerateID(): cv.declare_id(TemplateNumber),
+ 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.Optional(CONF_SET_ACTION): automation.validate_automation(single=True),
+ cv.Optional(CONF_INITIAL_VALUE): cv.float_,
+ cv.Optional(CONF_RESTORE_VALUE): cv.boolean,
+ }
+ ).extend(cv.polling_component_schema("60s")),
+ validate_min_max,
+ validate,
+)
+
+
+async def to_code(config):
+ var = cg.new_Pvariable(config[CONF_ID])
+ await cg.register_component(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_))
+
+ else:
+ cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
+ cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE]))
+ if CONF_RESTORE_VALUE in config:
+ cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE]))
+
+ if CONF_SET_ACTION in config:
+ await automation.build_automation(
+ var.get_set_trigger(), [(float, "x")], config[CONF_SET_ACTION]
+ )
diff --git a/components/template/number/template_number.cpp b/components/template/number/template_number.cpp
new file mode 100644
index 0000000..90157b7
--- /dev/null
+++ b/components/template/number/template_number.cpp
@@ -0,0 +1,62 @@
+#include "template_number.h"
+#include "esphome/core/log.h"
+
+namespace esphome {
+namespace template_ {
+
+static const char *const TAG = "template.number";
+
+void TemplateNumber::setup() {
+ if (this->f_.has_value())
+ return;
+
+ float value;
+ if (!this->restore_value_) {
+ value = this->initial_value_;
+ } else {
+
+ if ( this->has_forced_hash ) {
+ this->pref_ = global_preferences->make_preference<float>(this->forced_hash);
+ } else {
+ this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash());
+ }
+
+
+ if (!this->pref_.load(&value)) {
+ if (!std::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())
+ return;
+
+ this->publish_state(*val);
+}
+
+void TemplateNumber::control(float value) {
+ this->set_trigger_->trigger(value);
+
+ if (this->optimistic_)
+ this->publish_state(value);
+
+ if (this->restore_value_)
+ this->pref_.save(&value);
+}
+void TemplateNumber::dump_config() {
+ LOG_NUMBER("", "Template Number", this);
+ ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
+ LOG_UPDATE_INTERVAL(this);
+}
+
+} // namespace template_
+} // namespace esphome
diff --git a/components/template/number/template_number.h b/components/template/number/template_number.h
new file mode 100644
index 0000000..9a82e44
--- /dev/null
+++ b/components/template/number/template_number.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#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:
+ 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 { return setup_priority::HARDWARE; }
+
+ 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; }
+ void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
+
+ protected:
+ void control(float value) override;
+ bool optimistic_{false};
+ float initial_value_{NAN};
+ bool restore_value_{false};
+ Trigger<float> *set_trigger_ = new Trigger<float>();
+ optional<std::function<optional<float>()>> f_;
+
+ ESPPreferenceObject pref_;
+};
+
+} // namespace template_
+} // namespace esphome
diff --git a/components/template/output/__init__.py b/components/template/output/__init__.py
new file mode 100644
index 0000000..b42a4be
--- /dev/null
+++ b/components/template/output/__init__.py
@@ -0,0 +1,47 @@
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome import automation
+from esphome.components import output
+from esphome.const import CONF_ID, CONF_TYPE, CONF_BINARY
+from .. import template_ns
+
+TemplateBinaryOutput = template_ns.class_("TemplateBinaryOutput", output.BinaryOutput)
+TemplateFloatOutput = template_ns.class_("TemplateFloatOutput", output.FloatOutput)
+
+CONF_FLOAT = "float"
+CONF_WRITE_ACTION = "write_action"
+
+CONFIG_SCHEMA = cv.typed_schema(
+ {
+ CONF_BINARY: output.BINARY_OUTPUT_SCHEMA.extend(
+ {
+ cv.GenerateID(): cv.declare_id(TemplateBinaryOutput),
+ cv.Required(CONF_WRITE_ACTION): automation.validate_automation(
+ single=True
+ ),
+ }
+ ),
+ CONF_FLOAT: output.FLOAT_OUTPUT_SCHEMA.extend(
+ {
+ cv.GenerateID(): cv.declare_id(TemplateFloatOutput),
+ cv.Required(CONF_WRITE_ACTION): automation.validate_automation(
+ single=True
+ ),
+ }
+ ),
+ },
+ lower=True,
+)
+
+
+async def to_code(config):
+ var = cg.new_Pvariable(config[CONF_ID])
+ if config[CONF_TYPE] == CONF_BINARY:
+ await automation.build_automation(
+ var.get_trigger(), [(bool, "state")], config[CONF_WRITE_ACTION]
+ )
+ else:
+ await automation.build_automation(
+ var.get_trigger(), [(float, "state")], config[CONF_WRITE_ACTION]
+ )
+ await output.register_output(var, config)
diff --git a/components/template/output/template_output.h b/components/template/output/template_output.h
new file mode 100644
index 0000000..90de801
--- /dev/null
+++ b/components/template/output/template_output.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "esphome/core/automation.h"
+#include "esphome/components/output/binary_output.h"
+#include "esphome/components/output/float_output.h"
+
+namespace esphome {
+namespace template_ {
+
+class TemplateBinaryOutput : public output::BinaryOutput {
+ public:
+ Trigger<bool> *get_trigger() const { return trigger_; }
+
+ protected:
+ void write_state(bool state) override { this->trigger_->trigger(state); }
+
+ Trigger<bool> *trigger_ = new Trigger<bool>();
+};
+
+class TemplateFloatOutput : public output::FloatOutput {
+ public:
+ Trigger<float> *get_trigger() const { return trigger_; }
+
+ protected:
+ void write_state(float state) override { this->trigger_->trigger(state); }
+
+ Trigger<float> *trigger_ = new Trigger<float>();
+};
+
+} // namespace template_
+} // namespace esphome
diff --git a/components/template/select/__init__.py b/components/template/select/__init__.py
new file mode 100644
index 0000000..4eba771
--- /dev/null
+++ b/components/template/select/__init__.py
@@ -0,0 +1,84 @@
+from esphome import automation
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome.components import select
+from esphome.const import (
+ CONF_ID,
+ CONF_INITIAL_OPTION,
+ CONF_LAMBDA,
+ CONF_OPTIONS,
+ CONF_OPTIMISTIC,
+ CONF_RESTORE_VALUE,
+)
+from .. import template_ns
+
+TemplateSelect = template_ns.class_(
+ "TemplateSelect", select.Select, cg.PollingComponent
+)
+
+CONF_SET_ACTION = "set_action"
+
+
+def validate(config):
+ if CONF_LAMBDA in config:
+ if config[CONF_OPTIMISTIC]:
+ raise cv.Invalid("optimistic cannot be used with lambda")
+ if CONF_INITIAL_OPTION in config:
+ raise cv.Invalid("initial_value cannot be used with lambda")
+ if CONF_RESTORE_VALUE in config:
+ raise cv.Invalid("restore_value cannot be used with lambda")
+ elif CONF_INITIAL_OPTION in config:
+ if config[CONF_INITIAL_OPTION] not in config[CONF_OPTIONS]:
+ raise cv.Invalid(
+ f"initial_option '{config[CONF_INITIAL_OPTION]}' is not a valid option [{', '.join(config[CONF_OPTIONS])}]"
+ )
+ else:
+ config[CONF_INITIAL_OPTION] = config[CONF_OPTIONS][0]
+
+ if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config:
+ raise cv.Invalid(
+ "Either optimistic mode must be enabled, or set_action must be set, to handle the option being set."
+ )
+ return config
+
+
+CONFIG_SCHEMA = cv.All(
+ select.SELECT_SCHEMA.extend(
+ {
+ cv.GenerateID(): cv.declare_id(TemplateSelect),
+ cv.Required(CONF_OPTIONS): cv.All(
+ cv.ensure_list(cv.string_strict), cv.Length(min=1)
+ ),
+ cv.Optional(CONF_LAMBDA): cv.returning_lambda,
+ cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
+ cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True),
+ cv.Optional(CONF_INITIAL_OPTION): cv.string_strict,
+ cv.Optional(CONF_RESTORE_VALUE): cv.boolean,
+ }
+ ).extend(cv.polling_component_schema("60s")),
+ validate,
+)
+
+
+async def to_code(config):
+ var = cg.new_Pvariable(config[CONF_ID])
+ await cg.register_component(var, config)
+ await select.register_select(var, config, options=config[CONF_OPTIONS])
+
+ if CONF_LAMBDA in config:
+ template_ = await cg.process_lambda(
+ config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.std_string)
+ )
+ cg.add(var.set_template(template_))
+
+ else:
+ cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
+ cg.add(var.set_initial_option(config[CONF_INITIAL_OPTION]))
+
+ if CONF_RESTORE_VALUE in config:
+ cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE]))
+
+ if CONF_SET_ACTION in config:
+ await automation.build_automation(
+ var.get_set_trigger(), [(cg.std_string, "x")], config[CONF_SET_ACTION]
+ )
diff --git a/components/template/select/template_select.cpp b/components/template/select/template_select.cpp
new file mode 100644
index 0000000..4efa306
--- /dev/null
+++ b/components/template/select/template_select.cpp
@@ -0,0 +1,80 @@
+#include "template_select.h"
+#include "esphome/core/log.h"
+
+namespace esphome {
+namespace template_ {
+
+static const char *const TAG = "template.select";
+
+void TemplateSelect::setup() {
+ if (this->f_.has_value())
+ return;
+
+ std::string value;
+ ESP_LOGD(TAG, "Setting up Template Select");
+ if (!this->restore_value_) {
+ value = this->initial_option_;
+ ESP_LOGD(TAG, "State from initial: %s", value.c_str());
+ } else {
+ size_t index;
+
+ if ( this->has_forced_hash ) {
+ this->pref_ = global_preferences->make_preference<size_t>(this->forced_hash);
+ } else {
+ this->pref_ = global_preferences->make_preference<size_t>(this->get_object_id_hash());
+ }
+
+ if (!this->pref_.load(&index)) {
+ value = this->initial_option_;
+ ESP_LOGD(TAG, "State from initial (could not load): %s", value.c_str());
+ } else {
+ value = this->traits.get_options().at(index);
+ ESP_LOGD(TAG, "State from restore: %s", value.c_str());
+ }
+ }
+
+ this->publish_state(value);
+}
+
+void TemplateSelect::update() {
+ if (!this->f_.has_value())
+ return;
+
+ auto val = (*this->f_)();
+ if (!val.has_value())
+ return;
+
+ auto options = this->traits.get_options();
+ if (std::find(options.begin(), options.end(), *val) == options.end()) {
+ ESP_LOGE(TAG, "lambda returned an invalid option %s", (*val).c_str());
+ return;
+ }
+
+ this->publish_state(*val);
+}
+
+void TemplateSelect::control(const std::string &value) {
+ this->set_trigger_->trigger(value);
+
+ if (this->optimistic_)
+ this->publish_state(value);
+
+ if (this->restore_value_) {
+ auto options = this->traits.get_options();
+ size_t index = std::find(options.begin(), options.end(), value) - options.begin();
+
+ this->pref_.save(&index);
+ }
+}
+void TemplateSelect::dump_config() {
+ LOG_SELECT("", "Template Select", this);
+ LOG_UPDATE_INTERVAL(this);
+ if (this->f_.has_value())
+ return;
+ ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
+ ESP_LOGCONFIG(TAG, " Initial Option: %s", this->initial_option_.c_str());
+ ESP_LOGCONFIG(TAG, " Restore Value: %s", YESNO(this->restore_value_));
+}
+
+} // namespace template_
+} // namespace esphome
diff --git a/components/template/select/template_select.h b/components/template/select/template_select.h
new file mode 100644
index 0000000..2f00765
--- /dev/null
+++ b/components/template/select/template_select.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "esphome/components/select/select.h"
+#include "esphome/core/automation.h"
+#include "esphome/core/component.h"
+#include "esphome/core/preferences.h"
+
+namespace esphome {
+namespace template_ {
+
+class TemplateSelect : public select::Select, public PollingComponent {
+ public:
+ void set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; }
+
+ void setup() override;
+ void update() override;
+ void dump_config() override;
+ float get_setup_priority() const override { return setup_priority::HARDWARE; }
+
+ Trigger<std::string> *get_set_trigger() const { return this->set_trigger_; }
+ void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
+ void set_initial_option(const std::string &initial_option) { this->initial_option_ = initial_option; }
+ void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
+
+ protected:
+ void control(const std::string &value) override;
+ bool optimistic_ = false;
+ std::string initial_option_;
+ bool restore_value_ = false;
+ Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
+ optional<std::function<optional<std::string>()>> f_;
+
+ ESPPreferenceObject pref_;
+};
+
+} // namespace template_
+} // namespace esphome
diff --git a/components/template/sensor/__init__.py b/components/template/sensor/__init__.py
new file mode 100644
index 0000000..75fb505
--- /dev/null
+++ b/components/template/sensor/__init__.py
@@ -0,0 +1,59 @@
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome import automation
+from esphome.components import sensor
+from esphome.const import (
+ CONF_ID,
+ CONF_LAMBDA,
+ CONF_STATE,
+ STATE_CLASS_NONE,
+)
+from .. import template_ns
+
+TemplateSensor = template_ns.class_(
+ "TemplateSensor", sensor.Sensor, cg.PollingComponent
+)
+
+CONFIG_SCHEMA = (
+ sensor.sensor_schema(
+ accuracy_decimals=1,
+ state_class=STATE_CLASS_NONE,
+ )
+ .extend(
+ {
+ cv.GenerateID(): cv.declare_id(TemplateSensor),
+ cv.Optional(CONF_LAMBDA): cv.returning_lambda,
+ }
+ )
+ .extend(cv.polling_component_schema("60s"))
+)
+
+
+async def to_code(config):
+ var = cg.new_Pvariable(config[CONF_ID])
+ await cg.register_component(var, config)
+ await sensor.register_sensor(var, config)
+
+ 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_))
+
+
+ "sensor.template.publish",
+ sensor.SensorPublishAction,
+ cv.Schema(
+ {
+ cv.Required(CONF_ID): cv.use_id(sensor.Sensor),
+ cv.Required(CONF_STATE): cv.templatable(cv.float_),
+ }
+ ),
+)
+async def sensor_template_publish_to_code(config, action_id, template_arg, args):
+ paren = await cg.get_variable(config[CONF_ID])
+ var = cg.new_Pvariable(action_id, template_arg, paren)
+ template_ = await cg.templatable(config[CONF_STATE], args, float)
+ cg.add(var.set_state(template_))
+ return var
diff --git a/components/template/sensor/template_sensor.cpp b/components/template/sensor/template_sensor.cpp
new file mode 100644
index 0000000..b28eb3f
--- /dev/null
+++ b/components/template/sensor/template_sensor.cpp
@@ -0,0 +1,28 @@
+#include "template_sensor.h"
+#include "esphome/core/log.h"
+#include <cmath>
+
+namespace esphome {
+namespace template_ {
+
+static const char *const TAG = "template.sensor";
+
+void TemplateSensor::update() {
+ if (this->f_.has_value()) {
+ auto val = (*this->f_)();
+ if (val.has_value()) {
+ this->publish_state(*val);
+ }
+ } else if (!std::isnan(this->get_raw_state())) {
+ this->publish_state(this->get_raw_state());
+ }
+}
+float TemplateSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
+void TemplateSensor::set_template(std::function<optional<float>()> &&f) { this->f_ = f; }
+void TemplateSensor::dump_config() {
+ LOG_SENSOR("", "Template Sensor", this);
+ LOG_UPDATE_INTERVAL(this);
+}
+
+} // namespace template_
+} // namespace esphome
diff --git a/components/template/sensor/template_sensor.h b/components/template/sensor/template_sensor.h
new file mode 100644
index 0000000..2630cb0
--- /dev/null
+++ b/components/template/sensor/template_sensor.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "esphome/core/component.h"
+#include "esphome/components/sensor/sensor.h"
+
+namespace esphome {
+namespace template_ {
+
+class TemplateSensor : public sensor::Sensor, public PollingComponent {
+ public:
+ void set_template(std::function<optional<float>()> &&f);
+
+ void update() override;
+
+ void dump_config() override;
+
+ float get_setup_priority() const override;
+
+ protected:
+ optional<std::function<optional<float>()>> f_;
+};
+
+} // namespace template_
+} // namespace esphome
diff --git a/components/template/switch/__init__.py b/components/template/switch/__init__.py
new file mode 100644
index 0000000..6095a7c
--- /dev/null
+++ b/components/template/switch/__init__.py
@@ -0,0 +1,91 @@
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome import automation
+from esphome.components import switch
+from esphome.const import (
+ CONF_ASSUMED_STATE,
+ CONF_ID,
+ CONF_LAMBDA,
+ CONF_OPTIMISTIC,
+ CONF_RESTORE_STATE,
+ CONF_STATE,
+ CONF_TURN_OFF_ACTION,
+ CONF_TURN_ON_ACTION,
+)
+from .. import template_ns
+
+TemplateSwitch = template_ns.class_("TemplateSwitch", switch.Switch, cg.Component)
+
+
+def validate(config):
+ if (
+ not config[CONF_OPTIMISTIC]
+ and CONF_TURN_ON_ACTION not in config
+ and CONF_TURN_OFF_ACTION not in config
+ ):
+ raise cv.Invalid(
+ "Either optimistic mode must be enabled, or turn_on_action or turn_off_action must be set, "
+ "to handle the switch being set."
+ )
+ return config
+
+
+CONFIG_SCHEMA = cv.All(
+ switch.SWITCH_SCHEMA.extend(
+ {
+ cv.GenerateID(): cv.declare_id(TemplateSwitch),
+ cv.Optional(CONF_LAMBDA): cv.returning_lambda,
+ cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
+ cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean,
+ cv.Optional(CONF_TURN_OFF_ACTION): automation.validate_automation(
+ single=True
+ ),
+ cv.Optional(CONF_TURN_ON_ACTION): automation.validate_automation(
+ single=True
+ ),
+ cv.Optional(CONF_RESTORE_STATE, default=False): cv.boolean,
+ }
+ ).extend(cv.COMPONENT_SCHEMA),
+ validate,
+)
+
+
+async def to_code(config):
+ var = cg.new_Pvariable(config[CONF_ID])
+ await cg.register_component(var, config)
+ await switch.register_switch(var, config)
+
+ if CONF_LAMBDA in config:
+ template_ = await cg.process_lambda(
+ config[CONF_LAMBDA], [], return_type=cg.optional.template(bool)
+ )
+ cg.add(var.set_state_lambda(template_))
+ if CONF_TURN_OFF_ACTION in config:
+ await automation.build_automation(
+ var.get_turn_off_trigger(), [], config[CONF_TURN_OFF_ACTION]
+ )
+ if CONF_TURN_ON_ACTION in config:
+ await automation.build_automation(
+ var.get_turn_on_trigger(), [], config[CONF_TURN_ON_ACTION]
+ )
+ cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
+ cg.add(var.set_assumed_state(config[CONF_ASSUMED_STATE]))
+ cg.add(var.set_restore_state(config[CONF_RESTORE_STATE]))
+
+
+ "switch.template.publish",
+ switch.SwitchPublishAction,
+ cv.Schema(
+ {
+ cv.Required(CONF_ID): cv.use_id(switch.Switch),
+ cv.Required(CONF_STATE): cv.templatable(cv.boolean),
+ }
+ ),
+)
+async def switch_template_publish_to_code(config, action_id, template_arg, args):
+ paren = await cg.get_variable(config[CONF_ID])
+ var = cg.new_Pvariable(action_id, template_arg, paren)
+ template_ = await cg.templatable(config[CONF_STATE], args, bool)
+ cg.add(var.set_state(template_))
+ return var
diff --git a/components/template/switch/template_switch.cpp b/components/template/switch/template_switch.cpp
new file mode 100644
index 0000000..b3e545d
--- /dev/null
+++ b/components/template/switch/template_switch.cpp
@@ -0,0 +1,66 @@
+#include "template_switch.h"
+#include "esphome/core/log.h"
+
+namespace esphome {
+namespace template_ {
+
+static const char *const TAG = "template.switch";
+
+TemplateSwitch::TemplateSwitch() : turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {}
+
+void TemplateSwitch::loop() {
+ if (!this->f_.has_value())
+ return;
+ auto s = (*this->f_)();
+ if (!s.has_value())
+ return;
+
+ this->publish_state(*s);
+}
+void TemplateSwitch::write_state(bool state) {
+ if (this->prev_trigger_ != nullptr) {
+ this->prev_trigger_->stop_action();
+ }
+
+ if (state) {
+ this->prev_trigger_ = this->turn_on_trigger_;
+ this->turn_on_trigger_->trigger();
+ } else {
+ this->prev_trigger_ = this->turn_off_trigger_;
+ this->turn_off_trigger_->trigger();
+ }
+
+ if (this->optimistic_)
+ this->publish_state(state);
+}
+void TemplateSwitch::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
+bool TemplateSwitch::assumed_state() { return this->assumed_state_; }
+void TemplateSwitch::set_state_lambda(std::function<optional<bool>()> &&f) { this->f_ = f; }
+float TemplateSwitch::get_setup_priority() const { return setup_priority::HARDWARE; }
+Trigger<> *TemplateSwitch::get_turn_on_trigger() const { return this->turn_on_trigger_; }
+Trigger<> *TemplateSwitch::get_turn_off_trigger() const { return this->turn_off_trigger_; }
+void TemplateSwitch::setup() {
+ if (!this->restore_state_)
+ return;
+
+ auto restored = this->get_initial_state();
+ if (!restored.has_value())
+ return;
+
+ ESP_LOGD(TAG, " Restored state %s", ONOFF(*restored));
+ if (*restored) {
+ this->turn_on();
+ } else {
+ this->turn_off();
+ }
+}
+void TemplateSwitch::dump_config() {
+ LOG_SWITCH("", "Template Switch", this);
+ ESP_LOGCONFIG(TAG, " Restore State: %s", YESNO(this->restore_state_));
+ ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
+}
+void TemplateSwitch::set_restore_state(bool restore_state) { this->restore_state_ = restore_state; }
+void TemplateSwitch::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
+
+} // namespace template_
+} // namespace esphome
diff --git a/components/template/switch/template_switch.h b/components/template/switch/template_switch.h
new file mode 100644
index 0000000..ef9b567
--- /dev/null
+++ b/components/template/switch/template_switch.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "esphome/core/component.h"
+#include "esphome/core/automation.h"
+#include "esphome/components/switch/switch.h"
+
+namespace esphome {
+namespace template_ {
+
+class TemplateSwitch : public switch_::Switch, public Component {
+ public:
+ TemplateSwitch();
+
+ void setup() override;
+ void dump_config() override;
+
+ void set_state_lambda(std::function<optional<bool>()> &&f);
+ void set_restore_state(bool restore_state);
+ Trigger<> *get_turn_on_trigger() const;
+ Trigger<> *get_turn_off_trigger() const;
+ void set_optimistic(bool optimistic);
+ void set_assumed_state(bool assumed_state);
+ void loop() override;
+
+ float get_setup_priority() const override;
+
+ protected:
+ bool assumed_state() override;
+
+ void write_state(bool state) override;
+
+ optional<std::function<optional<bool>()>> f_;
+ bool optimistic_{false};
+ bool assumed_state_{false};
+ Trigger<> *turn_on_trigger_;
+ Trigger<> *turn_off_trigger_;
+ Trigger<> *prev_trigger_{nullptr};
+ bool restore_state_{false};
+};
+
+} // namespace template_
+} // namespace esphome
diff --git a/components/template/text_sensor/__init__.py b/components/template/text_sensor/__init__.py
new file mode 100644
index 0000000..2e098a7
--- /dev/null
+++ b/components/template/text_sensor/__init__.py
@@ -0,0 +1,48 @@
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome import automation
+from esphome.components import text_sensor
+from esphome.components.text_sensor import TextSensorPublishAction
+from esphome.const import CONF_ID, CONF_LAMBDA, CONF_STATE
+from .. import template_ns
+
+TemplateTextSensor = template_ns.class_(
+ "TemplateTextSensor", text_sensor.TextSensor, cg.PollingComponent
+)
+
+CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend(
+ {
+ cv.GenerateID(): cv.declare_id(TemplateTextSensor),
+ cv.Optional(CONF_LAMBDA): cv.returning_lambda,
+ }
+).extend(cv.polling_component_schema("60s"))
+
+
+async def to_code(config):
+ var = cg.new_Pvariable(config[CONF_ID])
+ await cg.register_component(var, config)
+ await text_sensor.register_text_sensor(var, config)
+
+ if CONF_LAMBDA in config:
+ template_ = await cg.process_lambda(
+ config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.std_string)
+ )
+ cg.add(var.set_template(template_))
+
+
+ "text_sensor.template.publish",
+ TextSensorPublishAction,
+ cv.Schema(
+ {
+ cv.Required(CONF_ID): cv.use_id(text_sensor.TextSensor),
+ cv.Required(CONF_STATE): cv.templatable(cv.string_strict),
+ }
+ ),
+)
+async def text_sensor_template_publish_to_code(config, action_id, template_arg, args):
+ paren = await cg.get_variable(config[CONF_ID])
+ var = cg.new_Pvariable(action_id, template_arg, paren)
+ template_ = await cg.templatable(config[CONF_STATE], args, cg.std_string)
+ cg.add(var.set_state(template_))
+ return var
diff --git a/components/template/text_sensor/template_text_sensor.cpp b/components/template/text_sensor/template_text_sensor.cpp
new file mode 100644
index 0000000..83bebb5
--- /dev/null
+++ b/components/template/text_sensor/template_text_sensor.cpp
@@ -0,0 +1,24 @@
+#include "template_text_sensor.h"
+#include "esphome/core/log.h"
+
+namespace esphome {
+namespace template_ {
+
+static const char *const TAG = "template.text_sensor";
+
+void TemplateTextSensor::update() {
+ if (this->f_.has_value()) {
+ auto val = (*this->f_)();
+ if (val.has_value()) {
+ this->publish_state(*val);
+ }
+ } else if (this->has_state()) {
+ this->publish_state(this->state);
+ }
+}
+float TemplateTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
+void TemplateTextSensor::set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; }
+void TemplateTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Template Sensor", this); }
+
+} // namespace template_
+} // namespace esphome
diff --git a/components/template/text_sensor/template_text_sensor.h b/components/template/text_sensor/template_text_sensor.h
new file mode 100644
index 0000000..07a2bd9
--- /dev/null
+++ b/components/template/text_sensor/template_text_sensor.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "esphome/core/component.h"
+#include "esphome/core/automation.h"
+#include "esphome/components/text_sensor/text_sensor.h"
+
+namespace esphome {
+namespace template_ {
+
+class TemplateTextSensor : public text_sensor::TextSensor, public PollingComponent {
+ public:
+ void set_template(std::function<optional<std::string>()> &&f);
+
+ void update() override;
+
+ float get_setup_priority() const override;
+
+ void dump_config() override;
+
+ protected:
+ optional<std::function<optional<std::string>()>> f_{};
+};
+
+} // namespace template_
+} // namespace esphome
diff --git a/components/total_daily_energy/sensor.py b/components/total_daily_energy/sensor.py
new file mode 100644
index 0000000..9ee1476
--- /dev/null
+++ b/components/total_daily_energy/sensor.py
@@ -0,0 +1,100 @@
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome.components import sensor, time
+from esphome.const import (
+ CONF_ICON,
+ CONF_ID,
+ CONF_RESTORE,
+ CONF_TIME_ID,
+ DEVICE_CLASS_ENERGY,
+ CONF_METHOD,
+ STATE_CLASS_TOTAL_INCREASING,
+ CONF_UNIT_OF_MEASUREMENT,
+ CONF_ACCURACY_DECIMALS,
+)
+from esphome.core.entity_helpers import inherit_property_from
+
+DEPENDENCIES = ["time"]
+
+CONF_POWER_ID = "power_id"
+CONF_MIN_SAVE_INTERVAL = "min_save_interval"
+total_daily_energy_ns = cg.esphome_ns.namespace("total_daily_energy")
+TotalDailyEnergyMethod = total_daily_energy_ns.enum("TotalDailyEnergyMethod")
+TOTAL_DAILY_ENERGY_METHODS = {
+ "trapezoid": TotalDailyEnergyMethod.TOTAL_DAILY_ENERGY_METHOD_TRAPEZOID,
+ "left": TotalDailyEnergyMethod.TOTAL_DAILY_ENERGY_METHOD_LEFT,
+ "right": TotalDailyEnergyMethod.TOTAL_DAILY_ENERGY_METHOD_RIGHT,
+}
+TotalDailyEnergy = total_daily_energy_ns.class_(
+ "TotalDailyEnergy", sensor.Sensor, cg.Component
+)
+
+
+def inherit_unit_of_measurement(uom, config):
+ return uom + "h"
+
+
+def inherit_accuracy_decimals(decimals, config):
+ return decimals + 2
+
+
+CONFIG_SCHEMA = (
+ sensor.sensor_schema(
+ device_class=DEVICE_CLASS_ENERGY,
+ state_class=STATE_CLASS_TOTAL_INCREASING,
+ )
+ .extend(
+ {
+ cv.GenerateID(): cv.declare_id(TotalDailyEnergy),
+ cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
+ cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor),
+ cv.Optional(CONF_RESTORE, default=True): cv.boolean,
+ cv.Optional(
+ CONF_MIN_SAVE_INTERVAL, default="0s"
+ ): cv.positive_time_period_milliseconds,
+ cv.Optional(CONF_METHOD, default="right"): cv.enum(
+ TOTAL_DAILY_ENERGY_METHODS, lower=True
+ ),
+ cv.Optional("forced_hash"): cv.int_,
+ }
+ )
+ .extend(cv.COMPONENT_SCHEMA)
+)
+
+FINAL_VALIDATE_SCHEMA = cv.All(
+ cv.Schema(
+ {
+ cv.Required(CONF_ID): cv.use_id(TotalDailyEnergy),
+ cv.Optional(CONF_ICON): cv.icon,
+ cv.Optional(CONF_UNIT_OF_MEASUREMENT): sensor.validate_unit_of_measurement,
+ cv.Optional(CONF_ACCURACY_DECIMALS): sensor.validate_accuracy_decimals,
+ cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor),
+ },
+ extra=cv.ALLOW_EXTRA,
+ ),
+ inherit_property_from(CONF_ICON, CONF_POWER_ID),
+ inherit_property_from(
+ CONF_UNIT_OF_MEASUREMENT, CONF_POWER_ID, transform=inherit_unit_of_measurement
+ ),
+ inherit_property_from(
+ CONF_ACCURACY_DECIMALS, CONF_POWER_ID, transform=inherit_accuracy_decimals
+ ),
+)
+
+
+async def to_code(config):
+ var = cg.new_Pvariable(config[CONF_ID])
+
+ await cg.register_component(var, config)
+ await sensor.register_sensor(var, config)
+
+ sens = await cg.get_variable(config[CONF_POWER_ID])
+ cg.add(var.set_parent(sens))
+ time_ = await cg.get_variable(config[CONF_TIME_ID])
+ cg.add(var.set_time(time_))
+ cg.add(var.set_restore(config[CONF_RESTORE]))
+ cg.add(var.set_min_save_interval(config[CONF_MIN_SAVE_INTERVAL]))
+ cg.add(var.set_method(config[CONF_METHOD]))
+
+ if "forced_hash" in config:
+ cg.add(var.set_forced_hash(config["forced_hash"]))
diff --git a/components/total_daily_energy/total_daily_energy.cpp b/components/total_daily_energy/total_daily_energy.cpp
new file mode 100644
index 0000000..c88d8c2
--- /dev/null
+++ b/components/total_daily_energy/total_daily_energy.cpp
@@ -0,0 +1,86 @@
+#include "total_daily_energy.h"
+#include "esphome/core/log.h"
+
+namespace esphome {
+namespace total_daily_energy {
+
+static const char *const TAG = "total_daily_energy";
+
+void TotalDailyEnergy::setup() {
+ float initial_value = 0;
+
+ if (this->restore_) {
+
+
+ if ( this->has_forced_hash ) {
+ this->pref_ = global_preferences->make_preference<float>(this->forced_hash);
+ } else {
+ this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash());
+ }
+
+ this->pref_.load(&initial_value);
+ }
+ this->publish_state_and_save(initial_value);
+
+ this->last_update_ = millis();
+ this->last_save_ = this->last_update_;
+
+ this->parent_->add_on_state_callback([this](float state) { this->process_new_state_(state); });
+}
+
+void TotalDailyEnergy::dump_config() { LOG_SENSOR("", "Total Daily Energy", this); }
+
+void TotalDailyEnergy::loop() {
+ auto t = this->time_->now();
+ if (!t.is_valid())
+ return;
+
+ if (this->last_day_of_year_ == 0) {
+ this->last_day_of_year_ = t.day_of_year;
+ return;
+ }
+
+ if (t.day_of_year != this->last_day_of_year_) {
+ this->last_day_of_year_ = t.day_of_year;
+ this->total_energy_ = 0;
+ this->publish_state_and_save(0);
+ }
+}
+
+void TotalDailyEnergy::publish_state_and_save(float state) {
+ this->total_energy_ = state;
+ this->publish_state(state);
+ const uint32_t now = millis();
+ if (now - this->last_save_ < this->min_save_interval_) {
+ return;
+ }
+ this->last_save_ = now;
+ this->pref_.save(&state);
+}
+
+void TotalDailyEnergy::process_new_state_(float state) {
+ if (std::isnan(state))
+ return;
+ const uint32_t now = millis();
+ const float old_state = this->last_power_state_;
+ const float new_state = state;
+ float delta_hours = (now - this->last_update_) / 1000.0f / 60.0f / 60.0f;
+ float delta_energy = 0.0f;
+ switch (this->method_) {
+ case TOTAL_DAILY_ENERGY_METHOD_TRAPEZOID:
+ delta_energy = delta_hours * (old_state + new_state) / 2.0;
+ break;
+ case TOTAL_DAILY_ENERGY_METHOD_LEFT:
+ delta_energy = delta_hours * old_state;
+ break;
+ case TOTAL_DAILY_ENERGY_METHOD_RIGHT:
+ delta_energy = delta_hours * new_state;
+ break;
+ }
+ this->last_power_state_ = new_state;
+ this->last_update_ = now;
+ this->publish_state_and_save(this->total_energy_ + delta_energy);
+}
+
+} // namespace total_daily_energy
+} // namespace esphome
diff --git a/components/total_daily_energy/total_daily_energy.h b/components/total_daily_energy/total_daily_energy.h
new file mode 100644
index 0000000..f091bf9
--- /dev/null
+++ b/components/total_daily_energy/total_daily_energy.h
@@ -0,0 +1,57 @@
+#pragma once
+
+#include "esphome/core/component.h"
+#include "esphome/core/preferences.h"
+#include "esphome/core/hal.h"
+#include "esphome/components/sensor/sensor.h"
+#include "esphome/components/time/real_time_clock.h"
+
+namespace esphome {
+namespace total_daily_energy {
+
+enum TotalDailyEnergyMethod {
+ TOTAL_DAILY_ENERGY_METHOD_TRAPEZOID = 0,
+ TOTAL_DAILY_ENERGY_METHOD_LEFT,
+ TOTAL_DAILY_ENERGY_METHOD_RIGHT,
+};
+
+class TotalDailyEnergy : public sensor::Sensor, public Component {
+ public:
+ void set_restore(bool restore) { restore_ = restore; }
+ void set_min_save_interval(uint32_t min_interval) { this->min_save_interval_ = min_interval; }
+ void set_time(time::RealTimeClock *time) { time_ = time; }
+ void set_parent(Sensor *parent) { parent_ = parent; }
+ void set_method(TotalDailyEnergyMethod method) { method_ = method; }
+ void setup() override;
+ void dump_config() override;
+ float get_setup_priority() const override { return setup_priority::DATA; }
+ void loop() override;
+
+ void publish_state_and_save(float state);
+
+
+ bool has_forced_hash = false;
+ uint32_t forced_hash = 0;
+ void set_forced_hash(uint32_t hash_value) {
+ forced_hash = hash_value;
+ has_forced_hash = true;
+ }
+
+ protected:
+ void process_new_state_(float state);
+
+ ESPPreferenceObject pref_;
+ time::RealTimeClock *time_;
+ Sensor *parent_;
+ TotalDailyEnergyMethod method_;
+ uint16_t last_day_of_year_{};
+ uint32_t last_update_{0};
+ uint32_t last_save_{0};
+ uint32_t min_save_interval_{0};
+ bool restore_;
+ float total_energy_{0.0f};
+ float last_power_state_{0.0f};
+};
+
+} // namespace total_daily_energy
+} // namespace esphome