mirror of
https://github.com/esphome/esphome.git
synced 2024-12-01 19:24:14 +01: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:
parent
4f5e4f3b86
commit
3dfc8d4291
6 changed files with 373 additions and 5 deletions
|
@ -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};
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue