mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	String manipulation filters for text sensors (#2393)
* initial text sensor filter POC * fixed verbose logging * add append, prepend, substitute filters * add to lower, get to upper working without dummy * clang lint * more linting... * std::move append and prepend filters * fix verbose filter::input logging * value.c_str() in input print * lambda filter verbose log fix * correct log tag, neaten to upper and to lower * add on_raw_value automation/trigger
This commit is contained in:
		| @@ -4,16 +4,22 @@ from esphome import automation | |||||||
| from esphome.components import mqtt | from esphome.components import mqtt | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_DISABLED_BY_DEFAULT, |     CONF_DISABLED_BY_DEFAULT, | ||||||
|  |     CONF_FILTERS, | ||||||
|     CONF_ICON, |     CONF_ICON, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_INTERNAL, |     CONF_INTERNAL, | ||||||
|     CONF_ON_VALUE, |     CONF_ON_VALUE, | ||||||
|  |     CONF_ON_RAW_VALUE, | ||||||
|     CONF_TRIGGER_ID, |     CONF_TRIGGER_ID, | ||||||
|     CONF_MQTT_ID, |     CONF_MQTT_ID, | ||||||
|     CONF_NAME, |     CONF_NAME, | ||||||
|     CONF_STATE, |     CONF_STATE, | ||||||
|  |     CONF_FROM, | ||||||
|  |     CONF_TO, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.util import Registry | ||||||
|  |  | ||||||
|  |  | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
|  |  | ||||||
| @@ -25,6 +31,9 @@ TextSensorPtr = TextSensor.operator("ptr") | |||||||
| TextSensorStateTrigger = text_sensor_ns.class_( | TextSensorStateTrigger = text_sensor_ns.class_( | ||||||
|     "TextSensorStateTrigger", automation.Trigger.template(cg.std_string) |     "TextSensorStateTrigger", automation.Trigger.template(cg.std_string) | ||||||
| ) | ) | ||||||
|  | TextSensorStateRawTrigger = text_sensor_ns.class_( | ||||||
|  |     "TextSensorStateRawTrigger", automation.Trigger.template(cg.std_string) | ||||||
|  | ) | ||||||
| TextSensorPublishAction = text_sensor_ns.class_( | TextSensorPublishAction = text_sensor_ns.class_( | ||||||
|     "TextSensorPublishAction", automation.Action |     "TextSensorPublishAction", automation.Action | ||||||
| ) | ) | ||||||
| @@ -32,21 +41,101 @@ TextSensorStateCondition = text_sensor_ns.class_( | |||||||
|     "TextSensorStateCondition", automation.Condition |     "TextSensorStateCondition", automation.Condition | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | FILTER_REGISTRY = Registry() | ||||||
|  | validate_filters = cv.validate_registry("filter", FILTER_REGISTRY) | ||||||
|  |  | ||||||
|  | # Filters | ||||||
|  | Filter = text_sensor_ns.class_("Filter") | ||||||
|  | LambdaFilter = text_sensor_ns.class_("LambdaFilter", Filter) | ||||||
|  | ToUpperFilter = text_sensor_ns.class_("ToUpperFilter", Filter) | ||||||
|  | ToLowerFilter = text_sensor_ns.class_("ToLowerFilter", Filter) | ||||||
|  | AppendFilter = text_sensor_ns.class_("AppendFilter", Filter) | ||||||
|  | PrependFilter = text_sensor_ns.class_("PrependFilter", Filter) | ||||||
|  | SubstituteFilter = text_sensor_ns.class_("SubstituteFilter", Filter) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda) | ||||||
|  | async def lambda_filter_to_code(config, filter_id): | ||||||
|  |     lambda_ = await cg.process_lambda( | ||||||
|  |         config, [(cg.std_string, "x")], return_type=cg.optional.template(cg.std_string) | ||||||
|  |     ) | ||||||
|  |     return cg.new_Pvariable(filter_id, lambda_) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @FILTER_REGISTRY.register("to_upper", ToUpperFilter, {}) | ||||||
|  | async def to_upper_filter_to_code(config, filter_id): | ||||||
|  |     return cg.new_Pvariable(filter_id) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @FILTER_REGISTRY.register("to_lower", ToLowerFilter, {}) | ||||||
|  | async def to_lower_filter_to_code(config, filter_id): | ||||||
|  |     return cg.new_Pvariable(filter_id) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @FILTER_REGISTRY.register("append", AppendFilter, cv.string) | ||||||
|  | async def append_filter_to_code(config, filter_id): | ||||||
|  |     return cg.new_Pvariable(filter_id, config) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @FILTER_REGISTRY.register("prepend", PrependFilter, cv.string) | ||||||
|  | async def prepend_filter_to_code(config, filter_id): | ||||||
|  |     return cg.new_Pvariable(filter_id, config) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_substitute(value): | ||||||
|  |     if isinstance(value, dict): | ||||||
|  |         return cv.Schema( | ||||||
|  |             { | ||||||
|  |                 cv.Required(CONF_FROM): cv.string, | ||||||
|  |                 cv.Required(CONF_TO): cv.string, | ||||||
|  |             } | ||||||
|  |         )(value) | ||||||
|  |     value = cv.string(value) | ||||||
|  |     if "->" not in value: | ||||||
|  |         raise cv.Invalid("Substitute mapping must contain '->'") | ||||||
|  |     a, b = value.split("->", 1) | ||||||
|  |     a, b = a.strip(), b.strip() | ||||||
|  |     return validate_substitute({CONF_FROM: cv.string(a), CONF_TO: cv.string(b)}) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @FILTER_REGISTRY.register( | ||||||
|  |     "substitute", | ||||||
|  |     SubstituteFilter, | ||||||
|  |     cv.All(cv.ensure_list(validate_substitute), cv.Length(min=2)), | ||||||
|  | ) | ||||||
|  | async def substitute_filter_to_code(config, filter_id): | ||||||
|  |     from_strings = [conf[CONF_FROM] for conf in config] | ||||||
|  |     to_strings = [conf[CONF_TO] for conf in config] | ||||||
|  |     return cg.new_Pvariable(filter_id, from_strings, to_strings) | ||||||
|  |  | ||||||
|  |  | ||||||
| icon = cv.icon | icon = cv.icon | ||||||
|  |  | ||||||
| TEXT_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( | TEXT_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( | ||||||
|     { |     { | ||||||
|         cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor), |         cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor), | ||||||
|         cv.Optional(CONF_ICON): icon, |         cv.Optional(CONF_ICON): icon, | ||||||
|  |         cv.Optional(CONF_FILTERS): validate_filters, | ||||||
|         cv.Optional(CONF_ON_VALUE): automation.validate_automation( |         cv.Optional(CONF_ON_VALUE): automation.validate_automation( | ||||||
|             { |             { | ||||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TextSensorStateTrigger), |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TextSensorStateTrigger), | ||||||
|             } |             } | ||||||
|         ), |         ), | ||||||
|  |         cv.Optional(CONF_ON_RAW_VALUE): automation.validate_automation( | ||||||
|  |             { | ||||||
|  |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||||
|  |                     TextSensorStateRawTrigger | ||||||
|  |                 ), | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def build_filters(config): | ||||||
|  |     return await cg.build_registry_list(FILTER_REGISTRY, config) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_text_sensor_core_(var, config): | async def setup_text_sensor_core_(var, config): | ||||||
|     cg.add(var.set_name(config[CONF_NAME])) |     cg.add(var.set_name(config[CONF_NAME])) | ||||||
|     cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) |     cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) | ||||||
| @@ -55,10 +144,18 @@ async def setup_text_sensor_core_(var, config): | |||||||
|     if CONF_ICON in config: |     if CONF_ICON in config: | ||||||
|         cg.add(var.set_icon(config[CONF_ICON])) |         cg.add(var.set_icon(config[CONF_ICON])) | ||||||
|  |  | ||||||
|  |     if config.get(CONF_FILTERS):  # must exist and not be empty | ||||||
|  |         filters = await build_filters(config[CONF_FILTERS]) | ||||||
|  |         cg.add(var.set_filters(filters)) | ||||||
|  |  | ||||||
|     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) | ||||||
|         await automation.build_automation(trigger, [(cg.std_string, "x")], conf) |         await automation.build_automation(trigger, [(cg.std_string, "x")], conf) | ||||||
|  |  | ||||||
|  |     for conf in config.get(CONF_ON_RAW_VALUE, []): | ||||||
|  |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|  |         await automation.build_automation(trigger, [(cg.std_string, "x")], conf) | ||||||
|  |  | ||||||
|     if CONF_MQTT_ID in config: |     if CONF_MQTT_ID in config: | ||||||
|         mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) |         mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) | ||||||
|         await mqtt.register_mqtt_component(mqtt_, config) |         await mqtt.register_mqtt_component(mqtt_, config) | ||||||
|   | |||||||
| @@ -16,6 +16,13 @@ class TextSensorStateTrigger : public Trigger<std::string> { | |||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | class TextSensorStateRawTrigger : public Trigger<std::string> { | ||||||
|  |  public: | ||||||
|  |   explicit TextSensorStateRawTrigger(TextSensor *parent) { | ||||||
|  |     parent->add_on_raw_state_callback([this](std::string value) { this->trigger(std::move(value)); }); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
| template<typename... Ts> class TextSensorStateCondition : public Condition<Ts...> { | template<typename... Ts> class TextSensorStateCondition : public Condition<Ts...> { | ||||||
|  public: |  public: | ||||||
|   explicit TextSensorStateCondition(TextSensor *parent) : parent_(parent) {} |   explicit TextSensorStateCondition(TextSensor *parent) : parent_(parent) {} | ||||||
|   | |||||||
							
								
								
									
										74
									
								
								esphome/components/text_sensor/filter.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								esphome/components/text_sensor/filter.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | |||||||
|  | #include "filter.h" | ||||||
|  | #include "text_sensor.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace text_sensor { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "text_sensor.filter"; | ||||||
|  |  | ||||||
|  | // Filter | ||||||
|  | void Filter::input(const std::string &value) { | ||||||
|  |   ESP_LOGVV(TAG, "Filter(%p)::input(%s)", this, value.c_str()); | ||||||
|  |   optional<std::string> out = this->new_value(value); | ||||||
|  |   if (out.has_value()) | ||||||
|  |     this->output(*out); | ||||||
|  | } | ||||||
|  | void Filter::output(const std::string &value) { | ||||||
|  |   if (this->next_ == nullptr) { | ||||||
|  |     ESP_LOGVV(TAG, "Filter(%p)::output(%s) -> SENSOR", this, value.c_str()); | ||||||
|  |     this->parent_->internal_send_state_to_frontend(value); | ||||||
|  |   } else { | ||||||
|  |     ESP_LOGVV(TAG, "Filter(%p)::output(%s) -> %p", this, value.c_str(), this->next_); | ||||||
|  |     this->next_->input(value); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void Filter::initialize(TextSensor *parent, Filter *next) { | ||||||
|  |   ESP_LOGVV(TAG, "Filter(%p)::initialize(parent=%p next=%p)", this, parent, next); | ||||||
|  |   this->parent_ = parent; | ||||||
|  |   this->next_ = next; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // LambdaFilter | ||||||
|  | LambdaFilter::LambdaFilter(lambda_filter_t lambda_filter) : lambda_filter_(std::move(lambda_filter)) {} | ||||||
|  | const lambda_filter_t &LambdaFilter::get_lambda_filter() const { return this->lambda_filter_; } | ||||||
|  | void LambdaFilter::set_lambda_filter(const lambda_filter_t &lambda_filter) { this->lambda_filter_ = lambda_filter; } | ||||||
|  |  | ||||||
|  | optional<std::string> LambdaFilter::new_value(std::string value) { | ||||||
|  |   auto it = this->lambda_filter_(value); | ||||||
|  |   ESP_LOGVV(TAG, "LambdaFilter(%p)::new_value(%s) -> %s", this, value.c_str(), it.value_or("").c_str()); | ||||||
|  |   return it; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ToUpperFilter | ||||||
|  | optional<std::string> ToUpperFilter::new_value(std::string value) { | ||||||
|  |   for (char &c : value) | ||||||
|  |     c = ::toupper(c); | ||||||
|  |   return value; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ToLowerFilter | ||||||
|  | optional<std::string> ToLowerFilter::new_value(std::string value) { | ||||||
|  |   for (char &c : value) | ||||||
|  |     c = ::toupper(c); | ||||||
|  |   return value; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Append | ||||||
|  | optional<std::string> AppendFilter::new_value(std::string value) { return value + this->suffix_; } | ||||||
|  |  | ||||||
|  | // Prepend | ||||||
|  | optional<std::string> PrependFilter::new_value(std::string value) { return this->prefix_ + value; } | ||||||
|  |  | ||||||
|  | // Substitute | ||||||
|  | optional<std::string> SubstituteFilter::new_value(std::string value) { | ||||||
|  |   std::size_t pos; | ||||||
|  |   for (int i = 0; i < this->from_strings_.size(); i++) | ||||||
|  |     while ((pos = value.find(this->from_strings_[i])) != std::string::npos) | ||||||
|  |       value.replace(pos, this->from_strings_[i].size(), this->to_strings_[i]); | ||||||
|  |   return value; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace text_sensor | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										112
									
								
								esphome/components/text_sensor/filter.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								esphome/components/text_sensor/filter.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include <queue> | ||||||
|  | #include <utility> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace text_sensor { | ||||||
|  |  | ||||||
|  | class TextSensor; | ||||||
|  |  | ||||||
|  | /** Apply a filter to text sensor values such as to_upper. | ||||||
|  |  * | ||||||
|  |  * This class is purposefully kept quite simple, since more complicated | ||||||
|  |  * filters should really be done with the filter sensor in Home Assistant. | ||||||
|  |  */ | ||||||
|  | class Filter { | ||||||
|  |  public: | ||||||
|  |   /** This will be called every time the filter receives a new value. | ||||||
|  |    * | ||||||
|  |    * It can return an empty optional to indicate that the filter chain | ||||||
|  |    * should stop, otherwise the value in the filter will be passed down | ||||||
|  |    * the chain. | ||||||
|  |    * | ||||||
|  |    * @param value The new value. | ||||||
|  |    * @return An optional string, the new value that should be pushed out. | ||||||
|  |    */ | ||||||
|  |   virtual optional<std::string> new_value(std::string value); | ||||||
|  |  | ||||||
|  |   /// Initialize this filter, please note this can be called more than once. | ||||||
|  |   virtual void initialize(TextSensor *parent, Filter *next); | ||||||
|  |  | ||||||
|  |   void input(const std::string &value); | ||||||
|  |  | ||||||
|  |   void output(const std::string &value); | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   friend TextSensor; | ||||||
|  |  | ||||||
|  |   Filter *next_{nullptr}; | ||||||
|  |   TextSensor *parent_{nullptr}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | using lambda_filter_t = std::function<optional<std::string>(std::string)>; | ||||||
|  |  | ||||||
|  | /** This class allows for creation of simple template filters. | ||||||
|  |  * | ||||||
|  |  * The constructor accepts a lambda of the form std::string -> optional<std::string>. | ||||||
|  |  * It will be called with each new value in the filter chain and returns the modified | ||||||
|  |  * value that shall be passed down the filter chain. Returning an empty Optional | ||||||
|  |  * means that the value shall be discarded. | ||||||
|  |  */ | ||||||
|  | class LambdaFilter : public Filter { | ||||||
|  |  public: | ||||||
|  |   explicit LambdaFilter(lambda_filter_t lambda_filter); | ||||||
|  |  | ||||||
|  |   optional<std::string> new_value(std::string value) override; | ||||||
|  |  | ||||||
|  |   const lambda_filter_t &get_lambda_filter() const; | ||||||
|  |   void set_lambda_filter(const lambda_filter_t &lambda_filter); | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   lambda_filter_t lambda_filter_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /// A simple filter that converts all text to uppercase | ||||||
|  | class ToUpperFilter : public Filter { | ||||||
|  |  public: | ||||||
|  |   optional<std::string> new_value(std::string value) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /// A simple filter that converts all text to lowercase | ||||||
|  | class ToLowerFilter : public Filter { | ||||||
|  |  public: | ||||||
|  |   optional<std::string> new_value(std::string value) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /// A simple filter that adds a string to the end of another string | ||||||
|  | class AppendFilter : public Filter { | ||||||
|  |  public: | ||||||
|  |   AppendFilter(std::string suffix) : suffix_(std::move(suffix)) {} | ||||||
|  |   optional<std::string> new_value(std::string value) override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   std::string suffix_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /// A simple filter that adds a string to the start of another string | ||||||
|  | class PrependFilter : public Filter { | ||||||
|  |  public: | ||||||
|  |   PrependFilter(std::string prefix) : prefix_(std::move(prefix)) {} | ||||||
|  |   optional<std::string> new_value(std::string value) override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   std::string prefix_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /// A simple filter that replaces a substring with another substring | ||||||
|  | class SubstituteFilter : public Filter { | ||||||
|  |  public: | ||||||
|  |   SubstituteFilter(std::vector<std::string> from_strings, std::vector<std::string> to_strings) | ||||||
|  |       : from_strings_(std::move(from_strings)), to_strings_(std::move(to_strings)) {} | ||||||
|  |   optional<std::string> new_value(std::string value) override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   std::vector<std::string> from_strings_; | ||||||
|  |   std::vector<std::string> to_strings_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace text_sensor | ||||||
|  | }  // namespace esphome | ||||||
| @@ -10,21 +10,72 @@ TextSensor::TextSensor() : TextSensor("") {} | |||||||
| TextSensor::TextSensor(const std::string &name) : Nameable(name) {} | TextSensor::TextSensor(const std::string &name) : Nameable(name) {} | ||||||
|  |  | ||||||
| void TextSensor::publish_state(const std::string &state) { | void TextSensor::publish_state(const std::string &state) { | ||||||
|   this->state = state; |   this->raw_state = state; | ||||||
|  |   this->raw_callback_.call(state); | ||||||
|  |  | ||||||
|  |   ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), state.c_str()); | ||||||
|  |  | ||||||
|  |   if (this->filter_list_ == nullptr) { | ||||||
|  |     this->internal_send_state_to_frontend(state); | ||||||
|  |   } else { | ||||||
|  |     this->filter_list_->input(state); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TextSensor::add_filter(Filter *filter) { | ||||||
|  |   // inefficient, but only happens once on every sensor setup and nobody's going to have massive amounts of | ||||||
|  |   // filters | ||||||
|  |   ESP_LOGVV(TAG, "TextSensor(%p)::add_filter(%p)", this, filter); | ||||||
|  |   if (this->filter_list_ == nullptr) { | ||||||
|  |     this->filter_list_ = filter; | ||||||
|  |   } else { | ||||||
|  |     Filter *last_filter = this->filter_list_; | ||||||
|  |     while (last_filter->next_ != nullptr) | ||||||
|  |       last_filter = last_filter->next_; | ||||||
|  |     last_filter->initialize(this, filter); | ||||||
|  |   } | ||||||
|  |   filter->initialize(this, nullptr); | ||||||
|  | } | ||||||
|  | void TextSensor::add_filters(const std::vector<Filter *> &filters) { | ||||||
|  |   for (Filter *filter : filters) { | ||||||
|  |     this->add_filter(filter); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void TextSensor::set_filters(const std::vector<Filter *> &filters) { | ||||||
|  |   this->clear_filters(); | ||||||
|  |   this->add_filters(filters); | ||||||
|  | } | ||||||
|  | void TextSensor::clear_filters() { | ||||||
|  |   if (this->filter_list_ != nullptr) { | ||||||
|  |     ESP_LOGVV(TAG, "TextSensor(%p)::clear_filters()", this); | ||||||
|  |   } | ||||||
|  |   this->filter_list_ = nullptr; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TextSensor::add_on_state_callback(std::function<void(std::string)> callback) { | ||||||
|  |   this->callback_.add(std::move(callback)); | ||||||
|  | } | ||||||
|  | void TextSensor::add_on_raw_state_callback(std::function<void(std::string)> callback) { | ||||||
|  |   this->raw_callback_.add(std::move(callback)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::string TextSensor::get_state() const { return this->state; } | ||||||
|  | std::string TextSensor::get_raw_state() const { return this->raw_state; } | ||||||
|  | void TextSensor::internal_send_state_to_frontend(const std::string &state) { | ||||||
|  |   this->state = this->raw_state; | ||||||
|   this->has_state_ = true; |   this->has_state_ = true; | ||||||
|   ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), state.c_str()); |   ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), state.c_str()); | ||||||
|   this->callback_.call(state); |   this->callback_.call(state); | ||||||
| } | } | ||||||
|  |  | ||||||
| void TextSensor::set_icon(const std::string &icon) { this->icon_ = icon; } | void TextSensor::set_icon(const std::string &icon) { this->icon_ = icon; } | ||||||
| void TextSensor::add_on_state_callback(std::function<void(std::string)> callback) { |  | ||||||
|   this->callback_.add(std::move(callback)); |  | ||||||
| } |  | ||||||
| std::string TextSensor::get_icon() { | std::string TextSensor::get_icon() { | ||||||
|   if (this->icon_.has_value()) |   if (this->icon_.has_value()) | ||||||
|     return *this->icon_; |     return *this->icon_; | ||||||
|   return this->icon(); |   return this->icon(); | ||||||
| } | } | ||||||
| std::string TextSensor::icon() { return ""; } | std::string TextSensor::icon() { return ""; } | ||||||
|  |  | ||||||
| std::string TextSensor::unique_id() { return ""; } | std::string TextSensor::unique_id() { return ""; } | ||||||
| bool TextSensor::has_state() { return this->has_state_; } | bool TextSensor::has_state() { return this->has_state_; } | ||||||
| uint32_t TextSensor::hash_base() { return 334300109UL; } | uint32_t TextSensor::hash_base() { return 334300109UL; } | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
|  |  | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/components/text_sensor/filter.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace text_sensor { | namespace text_sensor { | ||||||
| @@ -22,13 +23,33 @@ class TextSensor : public Nameable { | |||||||
|   explicit TextSensor(); |   explicit TextSensor(); | ||||||
|   explicit TextSensor(const std::string &name); |   explicit TextSensor(const std::string &name); | ||||||
|  |  | ||||||
|  |   /// Getter-syntax for .state. | ||||||
|  |   std::string get_state() const; | ||||||
|  |   /// Getter-syntax for .raw_state | ||||||
|  |   std::string get_raw_state() const; | ||||||
|  |  | ||||||
|   void publish_state(const std::string &state); |   void publish_state(const std::string &state); | ||||||
|  |  | ||||||
|   void set_icon(const std::string &icon); |   void set_icon(const std::string &icon); | ||||||
|  |  | ||||||
|  |   /// Add a filter to the filter chain. Will be appended to the back. | ||||||
|  |   void add_filter(Filter *filter); | ||||||
|  |  | ||||||
|  |   /// Add a list of vectors to the back of the filter chain. | ||||||
|  |   void add_filters(const std::vector<Filter *> &filters); | ||||||
|  |  | ||||||
|  |   /// Clear the filters and replace them by filters. | ||||||
|  |   void set_filters(const std::vector<Filter *> &filters); | ||||||
|  |  | ||||||
|  |   /// Clear the entire filter chain. | ||||||
|  |   void clear_filters(); | ||||||
|  |  | ||||||
|   void add_on_state_callback(std::function<void(std::string)> callback); |   void add_on_state_callback(std::function<void(std::string)> callback); | ||||||
|  |   /// Add a callback that will be called every time the sensor sends a raw value. | ||||||
|  |   void add_on_raw_state_callback(std::function<void(std::string)> callback); | ||||||
|  |  | ||||||
|   std::string state; |   std::string state; | ||||||
|  |   std::string raw_state; | ||||||
|  |  | ||||||
|   // ========== INTERNAL METHODS ========== |   // ========== INTERNAL METHODS ========== | ||||||
|   // (In most use cases you won't need these) |   // (In most use cases you won't need these) | ||||||
| @@ -40,10 +61,16 @@ class TextSensor : public Nameable { | |||||||
|  |  | ||||||
|   bool has_state(); |   bool has_state(); | ||||||
|  |  | ||||||
|  |   void internal_send_state_to_frontend(const std::string &state); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   uint32_t hash_base() override; |   uint32_t hash_base() override; | ||||||
|  |  | ||||||
|   CallbackManager<void(std::string)> callback_; |   CallbackManager<void(std::string)> raw_callback_;  ///< Storage for raw state callbacks. | ||||||
|  |   CallbackManager<void(std::string)> callback_;      ///< Storage for filtered state callbacks. | ||||||
|  |  | ||||||
|  |   Filter *filter_list_{nullptr};  ///< Store all active filters. | ||||||
|  |  | ||||||
|   optional<std::string> icon_; |   optional<std::string> icon_; | ||||||
|   bool has_state_{false}; |   bool has_state_{false}; | ||||||
| }; | }; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user