From 4e37a093104dd0632cefa4c018dd64ec327955f3 Mon Sep 17 00:00:00 2001 From: Michael Davidson Date: Tue, 2 Jan 2024 17:32:41 +1100 Subject: [PATCH] Add support for TextPanels grabbing their content from Sensors and TextSensors --- .../graphical_layout/text_panel.cpp | 41 +++++++++++++- .../components/graphical_layout/text_panel.h | 12 ++++- .../components/graphical_layout/text_panel.py | 53 +++++++++++++------ 3 files changed, 89 insertions(+), 17 deletions(-) diff --git a/esphome/components/graphical_layout/text_panel.cpp b/esphome/components/graphical_layout/text_panel.cpp index 9ef02451a7..09aeaa5d08 100644 --- a/esphome/components/graphical_layout/text_panel.cpp +++ b/esphome/components/graphical_layout/text_panel.cpp @@ -3,6 +3,8 @@ #include "esphome/components/display/display.h" #include "esphome/components/display/rect.h" #include "esphome/core/log.h" +#include +#include namespace esphome { namespace graphical_layout { @@ -14,10 +16,47 @@ static const int TEXT_ALIGN_Y_MASK = void TextPanel::dump_config(int indent_depth, int additional_level_depth) { this->dump_config_base_properties(TAG, indent_depth); - std::string text = this->text_.value(); + std::string text = this->text_input_.value(); ESP_LOGCONFIG(TAG, "%*sText Align: %s", indent_depth, "", LOG_STR_ARG(display::text_align_to_string(this->text_align_))); ESP_LOGCONFIG(TAG, "%*sText: %s", indent_depth, "", text.c_str()); + if (this->sensor_ != nullptr) { + ESP_LOGCONFIG(TAG, "%*sSensor: %s", indent_depth, "", this->sensor_->get_name()); + } + if (this->text_sensor_ != nullptr) { + ESP_LOGCONFIG(TAG, "%*sText Sensor: %s", indent_depth, "", this->text_sensor_->get_name()); + } + ESP_LOGCONFIG(TAG, "%*sHas Text Formatter: %s", indent_depth, "", YESNO(!this->text_formatter_.has_value())); +} + +void TextPanel::setup_complete() { + if (!this->text_formatter_.has_value()) { + this->text_formatter_ = [this](const std::string string) { + return string; + }; + } + + if (this->sensor_ != nullptr) { + // Need to setup the text callback for the sensor + this->text_ = [this]() { + std::stringstream stream; + stream << std::fixed << std::setprecision(this->sensor_->get_accuracy_decimals()) << this->sensor_->get_state(); + return this->text_formatter_.value(stream.str()); + }; + } + + if (this->text_sensor_ != nullptr) { + // Need to setup the text callback to the TextSensor + this->text_ = [this]() { + return this->text_formatter_.value(this->text_sensor_->get_state()); + }; + } + + if (this->text_input_.has_value()) { + this->text_ = [this]() { + return this->text_formatter_.value(this->text_input_.value()); + }; + } } display::Rect TextPanel::measure_item_internal(display::Display *display) { diff --git a/esphome/components/graphical_layout/text_panel.h b/esphome/components/graphical_layout/text_panel.h index 0d45a3b71b..6598ef976b 100644 --- a/esphome/components/graphical_layout/text_panel.h +++ b/esphome/components/graphical_layout/text_panel.h @@ -5,6 +5,8 @@ #include "esphome/components/graphical_layout/graphical_layout.h" #include "esphome/components/font/font.h" #include "esphome/core/automation.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/text_sensor/text_sensor.h" namespace esphome { namespace graphical_layout { @@ -15,8 +17,12 @@ class TextPanel : public LayoutItem { display::Rect measure_item_internal(display::Display *display) override; void render_internal(display::Display *display, display::Rect bounds) override; void dump_config(int indent_depth, int additional_level_depth) override; + void setup_complete() override; - template void set_text(V text) { this->text_ = text; }; + template void set_text(V text) { this->text_input_ = text; }; + void set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }; + void set_text_sensor(text_sensor::TextSensor *text_sensor) { this->text_sensor_ = text_sensor; }; + template void set_text_formatter(V text_formatter) { this->text_formatter_ = text_formatter; }; void set_font(display::BaseFont *font) { this->font_ = font; }; void set_foreground_color(Color foreground_color) { this->foreground_color_ = foreground_color; }; void set_background_color(Color background_color) { this->background_color_ = background_color; }; @@ -24,6 +30,10 @@ class TextPanel : public LayoutItem { protected: TemplatableValue text_{}; + sensor::Sensor *sensor_{nullptr}; + text_sensor::TextSensor *text_sensor_{nullptr}; + TemplatableValue text_formatter_{}; + TemplatableValue text_input_{}; display::BaseFont *font_{nullptr}; display::TextAlign text_align_{display::TextAlign::TOP_LEFT}; Color foreground_color_{COLOR_ON}; diff --git a/esphome/components/graphical_layout/text_panel.py b/esphome/components/graphical_layout/text_panel.py index 995a435b6b..4cdc8695bf 100644 --- a/esphome/components/graphical_layout/text_panel.py +++ b/esphome/components/graphical_layout/text_panel.py @@ -1,8 +1,8 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import font, color +from esphome.components import font, color, sensor, text_sensor from esphome.components.display import display_ns -from esphome.const import CONF_FOREGROUND_COLOR, CONF_BACKGROUND_COLOR +from esphome.const import CONF_FOREGROUND_COLOR, CONF_BACKGROUND_COLOR, CONST_SENSOR graphical_layout_ns = cg.esphome_ns.namespace("graphical_layout") TextPanel = graphical_layout_ns.class_("TextPanel") @@ -12,6 +12,8 @@ CONF_TEXT_PANEL = "text_panel" CONF_FONT = "font" CONF_TEXT = "text" CONF_TEXT_ALIGN = "text_align" +CONF_TEXT_SENSOR = "text_sensor" +CONF_TEXT_FORMATTER = "text_formatter" TEXT_ALIGN = { "TOP_LEFT": TextAlign.TOP_LEFT, @@ -30,15 +32,21 @@ TEXT_ALIGN = { def get_config_schema(base_item_schema, item_type_schema): - return base_item_schema.extend( - { - cv.GenerateID(): cv.declare_id(TextPanel), - cv.Required(CONF_FONT): cv.use_id(font.Font), - cv.Optional(CONF_FOREGROUND_COLOR): cv.use_id(color.ColorStruct), - cv.Optional(CONF_BACKGROUND_COLOR): cv.use_id(color.ColorStruct), - cv.Required(CONF_TEXT): cv.templatable(cv.string), - cv.Optional(CONF_TEXT_ALIGN): cv.enum(TEXT_ALIGN, upper=True), - } + return cv.All( + base_item_schema.extend( + { + cv.GenerateID(): cv.declare_id(TextPanel), + cv.Required(CONF_FONT): cv.use_id(font.Font), + cv.Optional(CONF_FOREGROUND_COLOR): cv.use_id(color.ColorStruct), + cv.Optional(CONF_BACKGROUND_COLOR): cv.use_id(color.ColorStruct), + cv.Optional(CONF_TEXT): cv.templatable(cv.string), + cv.Optional(CONF_TEXT_ALIGN): cv.enum(TEXT_ALIGN, upper=True), + cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor), + cv.Optional(CONF_TEXT_SENSOR): cv.use_id(text_sensor.TextSensor), + cv.Optional(CONF_TEXT_FORMATTER): cv.returning_lambda, + } + ), + cv.has_exactly_one_key(CONF_TEXT, CONF_SENSOR, CONF_TEXT_SENSOR), ) @@ -56,10 +64,25 @@ async def config_to_layout_item(pvariable_builder, item_config, child_item_build background_color = await cg.get_variable(background_color_config) cg.add(var.set_background_color(background_color)) - text = await cg.templatable( - item_config[CONF_TEXT], args=[], output_type=cg.std_string - ) - cg.add(var.set_text(text)) + if sensor_config := item_config.get(CONF_SENSOR): + sens = await cg.get_variable(sensor_config) + cg.add(var.set_sensor(sens)) + elif text_sensor_config := item_config.get(CONF_TEXT_SENSOR): + text_sens = await cg.get_variable(text_sensor_config) + cg.add(var.set_text_sensor(text_sens)) + else: + text = await cg.templatable( + item_config[CONF_TEXT], args=[], output_type=cg.std_string + ) + cg.add(var.set_text(text)) + + if text_formatter_config := item_config.get(CONF_TEXT_FORMATTER): + text_formatter = await cg.process_lambda( + text_formatter_config, + [(cg.std_string, "it")], + return_type=cg.std_string, + ) + cg.add(var.set_text_formatter(text_formatter)) if text_align := item_config.get(CONF_TEXT_ALIGN): cg.add(var.set_text_align(text_align))