Add support for time based Text Panel and Text Run Panels

Easy way to print times and dates in different formats
This commit is contained in:
Michael Davidson 2024-01-08 21:45:24 +11:00
parent ff4c918ceb
commit 17659e9783
No known key found for this signature in database
GPG key ID: B8D1A99712B8B0EB
6 changed files with 105 additions and 6 deletions

View file

@ -16,7 +16,7 @@ ContainerLayoutItem = graphical_layout_ns.class_("ContainerLayoutItem", LayoutIt
CODEOWNERS = ["@MrMDavidson"]
AUTO_LOAD = ["display", "sensor", "text_sensor"]
AUTO_LOAD = ["display", "sensor", "text_sensor", "time"]
MULTI_CONF = True

View file

@ -26,6 +26,11 @@ void TextPanel::dump_config(int indent_depth, int additional_level_depth) {
if (this->text_sensor_ != nullptr) {
ESP_LOGCONFIG(TAG, "%*sText Sensor: %s", indent_depth, "", this->text_sensor_->get_name().c_str());
}
if (this->time_ != nullptr) {
ESP_LOGCONFIG(TAG, "%*sTime: YES", indent_depth, "");
ESP_LOGCONFIG(TAG, "%*sUse UTC Time: %s", indent_depth, "", YESNO(this->use_utc_time_));
ESP_LOGCONFIG(TAG, "%*sTime Format: %s", indent_depth, "", this->time_format_.value().c_str());
}
ESP_LOGCONFIG(TAG, "%*sHas Text Formatter: %s", indent_depth, "", YESNO(!this->text_formatter_.has_value()));
}
@ -48,6 +53,19 @@ void TextPanel::setup_complete() {
this->text_ = [this]() { return this->text_formatter_.value(this->text_sensor_->get_state()); };
}
if (this->time_ != nullptr) {
this->text_ = [this]() {
ESPTime time;
if (this->use_utc_time_) {
time = this->time_->utcnow();
} else {
time = this->time_->now();
}
return this->text_formatter_.value(time.strftime(this->time_format_.value()));
};
}
if (this->text_input_.has_value()) {
this->text_ = [this]() { return this->text_formatter_.value(this->text_input_.value()); };
}

View file

@ -7,6 +7,7 @@
#include "esphome/core/automation.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/components/time/real_time_clock.h"
namespace esphome {
namespace graphical_layout {
@ -22,6 +23,9 @@ class TextPanel : public LayoutItem {
template<typename V> 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; };
void set_time(time::RealTimeClock *time) { this->time_ = time; };
void set_use_utc_time(bool use_utc_time) { this->use_utc_time_ = use_utc_time; };
template<typename V> void set_time_format(V time_format) { this->time_format_ = time_format; };
template<typename V> 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; };
@ -32,6 +36,9 @@ class TextPanel : public LayoutItem {
TemplatableValue<std::string> text_{};
sensor::Sensor *sensor_{nullptr};
text_sensor::TextSensor *text_sensor_{nullptr};
time::RealTimeClock *time_{nullptr};
bool use_utc_time_{false};
TemplatableValue<std::string> time_format_{};
TemplatableValue<std::string, const std::string> text_formatter_{};
TemplatableValue<std::string> text_input_{};
display::BaseFont *font_{nullptr};

View file

@ -1,8 +1,14 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import font, color, sensor, text_sensor
from esphome.components import font, color, sensor, text_sensor, time
from esphome.components.display import display_ns
from esphome.const import CONF_FOREGROUND_COLOR, CONF_BACKGROUND_COLOR, CONF_SENSOR
from esphome.const import (
CONF_FOREGROUND_COLOR,
CONF_BACKGROUND_COLOR,
CONF_SENSOR,
CONF_TIME_ID,
)
graphical_layout_ns = cg.esphome_ns.namespace("graphical_layout")
TextPanel = graphical_layout_ns.class_("TextPanel")
@ -14,6 +20,8 @@ CONF_TEXT = "text"
CONF_TEXT_ALIGN = "text_align"
CONF_TEXT_SENSOR = "text_sensor"
CONF_TEXT_FORMATTER = "text_formatter"
CONF_TIME_FORMAT = "time_format"
CONF_USE_UTC_TIME = "use_utc_time"
TEXT_ALIGN = {
"TOP_LEFT": TextAlign.TOP_LEFT,
@ -43,10 +51,15 @@ def get_config_schema(base_item_schema, item_type_schema):
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_TIME_ID): cv.use_id(time.RealTimeClock),
cv.Optional(CONF_TIME_FORMAT, default="%H:%M"): cv.templatable(
cv.string
),
cv.Optional(CONF_USE_UTC_TIME, default=False): cv.boolean,
cv.Optional(CONF_TEXT_FORMATTER): cv.returning_lambda,
}
),
cv.has_exactly_one_key(CONF_TEXT, CONF_SENSOR, CONF_TEXT_SENSOR),
cv.has_exactly_one_key(CONF_TEXT, CONF_SENSOR, CONF_TEXT_SENSOR, CONF_TIME_ID),
)
@ -70,6 +83,15 @@ async def config_to_layout_item(pvariable_builder, item_config, child_item_build
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))
elif time_id_config := item_config.get(CONF_TIME_ID):
time_sens = await cg.get_variable(time_id_config)
time_format = await cg.templatable(
item_config[CONF_TIME_FORMAT], args=[], output_type=cg.std_string
)
use_utc_time = item_config[CONF_USE_UTC_TIME]
cg.add(var.set_time(time_sens))
cg.add(var.set_time_format(time_format))
cg.add(var.set_use_utc_time(use_utc_time))
else:
text = await cg.templatable(
item_config[CONF_TEXT], args=[], output_type=cg.std_string

View file

@ -9,6 +9,7 @@
#include "esphome/core/automation.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/components/time/real_time_clock.h"
namespace esphome {
namespace graphical_layout {
@ -64,7 +65,7 @@ class TextRun : public TextRunBase, public FormattableTextRun {
this->text_ = std::move(text);
}
std::string get_text() override { return this->format_text(text_.value()); }
std::string get_text() override { return this->format_text(this->text_.value()); }
protected:
TemplatableValue<std::string> text_{};
@ -96,6 +97,33 @@ class TextSensorTextRun : public TextRunBase, public FormattableTextRun {
text_sensor::TextSensor *text_sensor_{nullptr};
};
class TimeTextRun : public TextRunBase, public FormattableTextRun {
public:
TimeTextRun(time::RealTimeClock *time, TemplatableValue<std::string> time_format, bool use_utc_time,
display::BaseFont *font)
: TextRunBase(font) {
this->time_ = time;
this->time_format_ = std::move(time_format);
this->use_utc_time_ = use_utc_time;
}
std::string get_text() override {
ESPTime time;
if (this->use_utc_time_) {
time = this->time_->utcnow();
} else {
time = this->time_->now();
}
return this->format_text(time.strftime(this->time_format_.value()));
}
protected:
time::RealTimeClock *time_{nullptr};
TemplatableValue<std::string> time_format_{""};
bool use_utc_time_{false};
};
class CalculatedTextRun {
public:
CalculatedTextRun(TextRunBase *run, std::string text) {

View file

@ -1,12 +1,13 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import font, color, sensor, text_sensor
from esphome.components import font, color, sensor, text_sensor, time
from esphome.components.display import display_ns
from esphome.const import (
CONF_ID,
CONF_FOREGROUND_COLOR,
CONF_BACKGROUND_COLOR,
CONF_SENSOR,
CONF_TIME_ID,
)
graphical_layout_ns = cg.esphome_ns.namespace("graphical_layout")
@ -16,6 +17,7 @@ TextRunBase = graphical_layout_ns.class_("TextRunBase")
TextRun = graphical_layout_ns.class_("TextRun", TextRunBase)
SensorTextRun = graphical_layout_ns.class_("SensorTextRun", TextRunBase)
TextSensorTextRun = graphical_layout_ns.class_("TextSensorTextRun", TextRunBase)
TimeTextRun = graphical_layout_ns.class_("TimeTextRun", TextRunBase)
CanWrapAtCharacterArguments = graphical_layout_ns.struct("CanWrapAtCharacterArguments")
CanWrapAtCharacterArgumentsConstRef = CanWrapAtCharacterArguments.operator(
"const"
@ -32,6 +34,8 @@ CONF_CAN_WRAP_AT_CHARACTER = "can_wrap_at_character"
CONF_DEBUG_OUTLINE_RUNS = "debug_outline_runs"
CONF_TEXT_SENSOR = "text_sensor"
CONF_TEXT_FORMATTER = "text_formatter"
CONF_TIME_FORMAT = "time_format"
CONF_USE_UTC_TIME = "use_utc_time"
TEXT_ALIGN = {
"TOP_LEFT": TextAlign.TOP_LEFT,
@ -80,10 +84,21 @@ TEXT_SENSOR_TEXT_RUN_SCHEMA = BASE_RUN_SCHEMA.extend(
}
)
TIME_TEXT_RUN_SCHEMA = BASE_RUN_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(TimeTextRun),
cv.Required(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
cv.Optional(CONF_TIME_FORMAT, default="%H:%M"): cv.templatable(cv.string),
cv.Optional(CONF_USE_UTC_TIME, default=False): cv.boolean,
cv.Optional(CONF_TEXT_FORMATTER): cv.returning_lambda,
}
)
RUN_SCHEMA = cv.Any(
SENSOR_TEXT_RUN_SCHEMA,
TEXT_RUN_SCHEMA,
TEXT_SENSOR_TEXT_RUN_SCHEMA,
TIME_TEXT_RUN_SCHEMA,
)
@ -136,6 +151,15 @@ async def config_to_layout_item(pvariable_builder, item_config, child_item_build
elif run_text_sensor_config := run_config.get(CONF_TEXT_SENSOR):
text_sens = await cg.get_variable(run_text_sensor_config)
run = cg.new_Pvariable(run_config[CONF_ID], text_sens, run_font)
elif run_time_id_config := run_config.get(CONF_TIME_ID):
time_sens = await cg.get_variable(run_time_id_config)
time_format = await cg.templatable(
run_config[CONF_TIME_FORMAT], args=[], output_type=cg.std_string
)
use_utc_time = run_config[CONF_USE_UTC_TIME]
run = cg.new_Pvariable(
run_config[CONF_ID], time_sens, time_format, use_utc_time, run_font
)
else:
run_text = await cg.templatable(
run_config[CONF_TEXT], args=[], output_type=cg.std_string