mirror of
https://github.com/esphome/esphome.git
synced 2026-02-21 00:45:35 -07:00
Merge branch 'dev' into integration
This commit is contained in:
@@ -108,7 +108,7 @@ LV_CONF_H_FORMAT = """\
|
||||
|
||||
|
||||
def generate_lv_conf_h():
|
||||
definitions = [as_macro(m, v) for m, v in df.lv_defines.items()]
|
||||
definitions = [as_macro(m, v) for m, v in df.get_data(df.KEY_LV_DEFINES).items()]
|
||||
definitions.sort()
|
||||
return LV_CONF_H_FORMAT.format("\n".join(definitions))
|
||||
|
||||
@@ -140,11 +140,11 @@ def multi_conf_validate(configs: list[dict]):
|
||||
)
|
||||
|
||||
|
||||
def final_validation(configs):
|
||||
if len(configs) != 1:
|
||||
multi_conf_validate(configs)
|
||||
def final_validation(config_list):
|
||||
if len(config_list) != 1:
|
||||
multi_conf_validate(config_list)
|
||||
global_config = full_config.get()
|
||||
for config in configs:
|
||||
for config in config_list:
|
||||
if (pages := config.get(CONF_PAGES)) and all(p[df.CONF_SKIP] for p in pages):
|
||||
raise cv.Invalid("At least one page must not be skipped")
|
||||
for display_id in config[df.CONF_DISPLAYS]:
|
||||
@@ -190,6 +190,14 @@ def final_validation(configs):
|
||||
raise cv.Invalid(
|
||||
f"Widget '{w}' does not have any dynamic properties to refresh",
|
||||
)
|
||||
# Do per-widget type final validation for update actions
|
||||
for widget_type, update_configs in df.get_data(df.KEY_UPDATED_WIDGETS).items():
|
||||
for conf in update_configs:
|
||||
for id_conf in conf.get(CONF_ID, ()):
|
||||
name = id_conf[CONF_ID]
|
||||
path = global_config.get_path_for_id(name)
|
||||
widget_conf = global_config.get_config_for_path(path[:-1])
|
||||
widget_type.final_validate(name, conf, widget_conf, path[1:])
|
||||
|
||||
|
||||
async def to_code(configs):
|
||||
@@ -276,6 +284,7 @@ async def to_code(configs):
|
||||
config[df.CONF_FULL_REFRESH],
|
||||
config[CONF_DRAW_ROUNDING],
|
||||
config[df.CONF_RESUME_ON_INPUT],
|
||||
config[df.CONF_UPDATE_WHEN_DISPLAY_IDLE],
|
||||
)
|
||||
await cg.register_component(lv_component, config)
|
||||
Widget.create(config[CONF_ID], lv_component, LvScrActType(), config)
|
||||
@@ -373,6 +382,9 @@ LVGL_SCHEMA = cv.All(
|
||||
df.CONF_DEFAULT_FONT, default="montserrat_14"
|
||||
): lvalid.lv_font,
|
||||
cv.Optional(df.CONF_FULL_REFRESH, default=False): cv.boolean,
|
||||
cv.Optional(
|
||||
df.CONF_UPDATE_WHEN_DISPLAY_IDLE, default=False
|
||||
): cv.boolean,
|
||||
cv.Optional(CONF_DRAW_ROUNDING, default=2): cv.positive_int,
|
||||
cv.Optional(CONF_BUFFER_SIZE, default=0): cv.percentage,
|
||||
cv.Optional(CONF_LOG_LEVEL, default="WARN"): cv.one_of(
|
||||
|
||||
@@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Any
|
||||
|
||||
from esphome import codegen as cg, config_validation as cv
|
||||
from esphome.const import CONF_ITEMS
|
||||
from esphome.core import ID, Lambda
|
||||
from esphome.core import CORE, ID, Lambda
|
||||
from esphome.cpp_generator import LambdaExpression, MockObj
|
||||
from esphome.cpp_types import uint32
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
@@ -20,11 +20,27 @@ from .helpers import requires_component
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
lvgl_ns = cg.esphome_ns.namespace("lvgl")
|
||||
|
||||
lv_defines = {} # Dict of #defines to provide as build flags
|
||||
DOMAIN = "lvgl"
|
||||
KEY_LV_DEFINES = "lv_defines"
|
||||
KEY_UPDATED_WIDGETS = "updated_widgets"
|
||||
|
||||
|
||||
def get_data(key, default=None):
|
||||
"""
|
||||
Get a data structure from the global data store by key
|
||||
:param key: A key for the data
|
||||
:param default: The default data - the default is an empty dict
|
||||
:return:
|
||||
"""
|
||||
return CORE.data.setdefault(DOMAIN, {}).setdefault(
|
||||
key, default if default is not None else {}
|
||||
)
|
||||
|
||||
|
||||
def add_define(macro, value="1"):
|
||||
if macro in lv_defines and lv_defines[macro] != value:
|
||||
lv_defines = get_data(KEY_LV_DEFINES)
|
||||
value = str(value)
|
||||
if lv_defines.setdefault(macro, value) != value:
|
||||
LOGGER.error(
|
||||
"Redefinition of %s - was %s now %s", macro, lv_defines[macro], value
|
||||
)
|
||||
@@ -542,6 +558,7 @@ CONF_TOUCHSCREENS = "touchscreens"
|
||||
CONF_TRANSPARENCY_KEY = "transparency_key"
|
||||
CONF_THEME = "theme"
|
||||
CONF_UPDATE_ON_RELEASE = "update_on_release"
|
||||
CONF_UPDATE_WHEN_DISPLAY_IDLE = "update_when_display_idle"
|
||||
CONF_VISIBLE_ROW_COUNT = "visible_row_count"
|
||||
CONF_WIDGET = "widget"
|
||||
CONF_WIDGETS = "widgets"
|
||||
|
||||
@@ -106,6 +106,7 @@ void LvglComponent::dump_config() {
|
||||
this->disp_drv_.hor_res, this->disp_drv_.ver_res, 100 / this->buffer_frac_, this->rotation,
|
||||
(int) this->draw_rounding);
|
||||
}
|
||||
|
||||
void LvglComponent::set_paused(bool paused, bool show_snow) {
|
||||
this->paused_ = paused;
|
||||
this->show_snow_ = show_snow;
|
||||
@@ -124,32 +125,38 @@ void LvglComponent::esphome_lvgl_init() {
|
||||
lv_update_event = static_cast<lv_event_code_t>(lv_event_register_id());
|
||||
lv_api_event = static_cast<lv_event_code_t>(lv_event_register_id());
|
||||
}
|
||||
|
||||
void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event) {
|
||||
lv_obj_add_event_cb(obj, callback, event, nullptr);
|
||||
}
|
||||
|
||||
void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1,
|
||||
lv_event_code_t event2) {
|
||||
add_event_cb(obj, callback, event1);
|
||||
add_event_cb(obj, callback, event2);
|
||||
}
|
||||
|
||||
void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1,
|
||||
lv_event_code_t event2, lv_event_code_t event3) {
|
||||
add_event_cb(obj, callback, event1);
|
||||
add_event_cb(obj, callback, event2);
|
||||
add_event_cb(obj, callback, event3);
|
||||
}
|
||||
|
||||
void LvglComponent::add_page(LvPageType *page) {
|
||||
this->pages_.push_back(page);
|
||||
page->set_parent(this);
|
||||
lv_disp_set_default(this->disp_);
|
||||
page->setup(this->pages_.size() - 1);
|
||||
}
|
||||
|
||||
void LvglComponent::show_page(size_t index, lv_scr_load_anim_t anim, uint32_t time) {
|
||||
if (index >= this->pages_.size())
|
||||
return;
|
||||
this->current_page_ = index;
|
||||
lv_scr_load_anim(this->pages_[this->current_page_]->obj, anim, time, 0, false);
|
||||
}
|
||||
|
||||
void LvglComponent::show_next_page(lv_scr_load_anim_t anim, uint32_t time) {
|
||||
if (this->pages_.empty() || (this->current_page_ == this->pages_.size() - 1 && !this->page_wrap_))
|
||||
return;
|
||||
@@ -158,6 +165,7 @@ void LvglComponent::show_next_page(lv_scr_load_anim_t anim, uint32_t time) {
|
||||
} while (this->pages_[this->current_page_]->skip); // skip empty pages()
|
||||
this->show_page(this->current_page_, anim, time);
|
||||
}
|
||||
|
||||
void LvglComponent::show_prev_page(lv_scr_load_anim_t anim, uint32_t time) {
|
||||
if (this->pages_.empty() || (this->current_page_ == 0 && !this->page_wrap_))
|
||||
return;
|
||||
@@ -166,8 +174,10 @@ void LvglComponent::show_prev_page(lv_scr_load_anim_t anim, uint32_t time) {
|
||||
} while (this->pages_[this->current_page_]->skip); // skip empty pages()
|
||||
this->show_page(this->current_page_, anim, time);
|
||||
}
|
||||
|
||||
size_t LvglComponent::get_current_page() const { return this->current_page_; }
|
||||
bool LvPageType::is_showing() const { return this->parent_->get_current_page() == this->index; }
|
||||
|
||||
void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) {
|
||||
auto width = lv_area_get_width(area);
|
||||
auto height = lv_area_get_height(area);
|
||||
@@ -222,7 +232,7 @@ void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) {
|
||||
}
|
||||
|
||||
void LvglComponent::flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) {
|
||||
if (!this->paused_) {
|
||||
if (!this->is_paused()) {
|
||||
auto now = millis();
|
||||
this->draw_buffer_(area, color_p);
|
||||
ESP_LOGVV(TAG, "flush_cb, area=%d/%d, %d/%d took %dms", area->x1, area->y1, lv_area_get_width(area),
|
||||
@@ -230,6 +240,7 @@ void LvglComponent::flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv
|
||||
}
|
||||
lv_disp_flush_ready(disp_drv);
|
||||
}
|
||||
|
||||
IdleTrigger::IdleTrigger(LvglComponent *parent, TemplatableValue<uint32_t> timeout) : timeout_(std::move(timeout)) {
|
||||
parent->add_on_idle_callback([this](uint32_t idle_time) {
|
||||
if (!this->is_idle_ && idle_time > this->timeout_.value()) {
|
||||
@@ -377,6 +388,27 @@ void LvKeyboardType::set_obj(lv_obj_t *lv_obj) {
|
||||
}
|
||||
#endif // USE_LVGL_KEYBOARD
|
||||
|
||||
void LvglComponent::draw_end_() {
|
||||
if (this->draw_end_callback_ != nullptr)
|
||||
this->draw_end_callback_->trigger();
|
||||
if (this->update_when_display_idle_) {
|
||||
for (auto *disp : this->displays_)
|
||||
disp->update();
|
||||
}
|
||||
}
|
||||
|
||||
bool LvglComponent::is_paused() const {
|
||||
if (this->paused_)
|
||||
return true;
|
||||
if (this->update_when_display_idle_) {
|
||||
for (auto *disp : this->displays_) {
|
||||
if (!disp->is_idle())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void LvglComponent::write_random_() {
|
||||
int iterations = 6 - lv_disp_get_inactive_time(this->disp_) / 60000;
|
||||
if (iterations <= 0)
|
||||
@@ -426,12 +458,13 @@ void LvglComponent::write_random_() {
|
||||
* presses a key or clicks on the screen.
|
||||
*/
|
||||
LvglComponent::LvglComponent(std::vector<display::Display *> displays, float buffer_frac, bool full_refresh,
|
||||
int draw_rounding, bool resume_on_input)
|
||||
int draw_rounding, bool resume_on_input, bool update_when_display_idle)
|
||||
: draw_rounding(draw_rounding),
|
||||
displays_(std::move(displays)),
|
||||
buffer_frac_(buffer_frac),
|
||||
full_refresh_(full_refresh),
|
||||
resume_on_input_(resume_on_input) {
|
||||
resume_on_input_(resume_on_input),
|
||||
update_when_display_idle_(update_when_display_idle) {
|
||||
lv_disp_draw_buf_init(&this->draw_buf_, nullptr, nullptr, 0);
|
||||
lv_disp_drv_init(&this->disp_drv_);
|
||||
this->disp_drv_.draw_buf = &this->draw_buf_;
|
||||
@@ -487,7 +520,7 @@ void LvglComponent::setup() {
|
||||
if (this->draw_start_callback_ != nullptr) {
|
||||
this->disp_drv_.render_start_cb = render_start_cb;
|
||||
}
|
||||
if (this->draw_end_callback_ != nullptr) {
|
||||
if (this->draw_end_callback_ != nullptr || this->update_when_display_idle_) {
|
||||
this->disp_drv_.monitor_cb = monitor_cb;
|
||||
}
|
||||
#if LV_USE_LOG
|
||||
@@ -509,14 +542,15 @@ void LvglComponent::setup() {
|
||||
|
||||
void LvglComponent::update() {
|
||||
// update indicators
|
||||
if (this->paused_) {
|
||||
if (this->is_paused()) {
|
||||
return;
|
||||
}
|
||||
this->idle_callbacks_.call(lv_disp_get_inactive_time(this->disp_));
|
||||
}
|
||||
|
||||
void LvglComponent::loop() {
|
||||
if (this->paused_) {
|
||||
if (this->show_snow_)
|
||||
if (this->is_paused()) {
|
||||
if (this->paused_ && this->show_snow_)
|
||||
this->write_random_();
|
||||
} else {
|
||||
lv_timer_handler_run_in_period(5);
|
||||
|
||||
@@ -151,7 +151,7 @@ class LvglComponent : public PollingComponent {
|
||||
|
||||
public:
|
||||
LvglComponent(std::vector<display::Display *> displays, float buffer_frac, bool full_refresh, int draw_rounding,
|
||||
bool resume_on_input);
|
||||
bool resume_on_input, bool update_when_display_idle);
|
||||
static void static_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p);
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::PROCESSOR; }
|
||||
@@ -171,7 +171,9 @@ class LvglComponent : public PollingComponent {
|
||||
// @param paused If true, pause the display. If false, resume the display.
|
||||
// @param show_snow If true, show the snow effect when paused.
|
||||
void set_paused(bool paused, bool show_snow);
|
||||
bool is_paused() const { return this->paused_; }
|
||||
|
||||
// Returns true if the display is explicitly paused, or a blocking display update is in progress.
|
||||
bool is_paused() const;
|
||||
// If the display is paused and we have resume_on_input_ set to true, resume the display.
|
||||
void maybe_wakeup() {
|
||||
if (this->paused_ && this->resume_on_input_) {
|
||||
@@ -210,10 +212,10 @@ class LvglComponent : public PollingComponent {
|
||||
void set_draw_end_trigger(Trigger<> *trigger) { this->draw_end_callback_ = trigger; }
|
||||
|
||||
protected:
|
||||
// these functions are never called unless the callbacks are non-null since the
|
||||
// LVGL callbacks that call them are not set unless the start/end callbacks are non-null
|
||||
void draw_end_();
|
||||
// Not checking for non-null callback since the
|
||||
// LVGL callback that calls it is not set in that case
|
||||
void draw_start_() const { this->draw_start_callback_->trigger(); }
|
||||
void draw_end_() const { this->draw_end_callback_->trigger(); }
|
||||
|
||||
void write_random_();
|
||||
void draw_buffer_(const lv_area_t *area, lv_color_t *ptr);
|
||||
@@ -222,6 +224,7 @@ class LvglComponent : public PollingComponent {
|
||||
size_t buffer_frac_{1};
|
||||
bool full_refresh_{};
|
||||
bool resume_on_input_{};
|
||||
bool update_when_display_idle_{};
|
||||
|
||||
lv_disp_draw_buf_t draw_buf_{};
|
||||
lv_disp_drv_t disp_drv_{};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from collections.abc import Callable
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.automation import Trigger, validate_automation
|
||||
from esphome.components.time import RealTimeClock
|
||||
@@ -311,19 +313,36 @@ def automation_schema(typ: LvType):
|
||||
}
|
||||
|
||||
|
||||
def base_update_schema(widget_type, parts):
|
||||
def _update_widget(widget_type: WidgetType) -> Callable[[dict], dict]:
|
||||
"""
|
||||
Create a schema for updating a widgets style properties, states and flags
|
||||
During validation of update actions, create a map of action types to affected widgets
|
||||
for use in final validation.
|
||||
:param widget_type:
|
||||
:return:
|
||||
"""
|
||||
|
||||
def validator(value: dict) -> dict:
|
||||
df.get_data(df.KEY_UPDATED_WIDGETS).setdefault(widget_type, []).append(value)
|
||||
return value
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
def base_update_schema(widget_type: WidgetType | LvType, parts):
|
||||
"""
|
||||
Create a schema for updating a widget's style properties, states and flags.
|
||||
:param widget_type: The type of the ID
|
||||
:param parts: The allowable parts to specify
|
||||
:return:
|
||||
"""
|
||||
return part_schema(parts).extend(
|
||||
|
||||
w_type = widget_type.w_type if isinstance(widget_type, WidgetType) else widget_type
|
||||
schema = part_schema(parts).extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.ensure_list(
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(widget_type),
|
||||
cv.Required(CONF_ID): cv.use_id(w_type),
|
||||
},
|
||||
key=CONF_ID,
|
||||
)
|
||||
@@ -332,11 +351,9 @@ def base_update_schema(widget_type, parts):
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def create_modify_schema(widget_type):
|
||||
return base_update_schema(widget_type.w_type, widget_type.parts).extend(
|
||||
widget_type.modify_schema
|
||||
)
|
||||
if isinstance(widget_type, WidgetType):
|
||||
schema.add_extra(_update_widget(widget_type))
|
||||
return schema
|
||||
|
||||
|
||||
def obj_schema(widget_type: WidgetType):
|
||||
|
||||
@@ -152,18 +152,18 @@ class WidgetType:
|
||||
|
||||
# Local import to avoid circular import
|
||||
from .automation import update_to_code
|
||||
from .schemas import WIDGET_TYPES, create_modify_schema
|
||||
from .schemas import WIDGET_TYPES, base_update_schema
|
||||
|
||||
if not is_mock:
|
||||
if self.name in WIDGET_TYPES:
|
||||
raise EsphomeError(f"Duplicate definition of widget type '{self.name}'")
|
||||
WIDGET_TYPES[self.name] = self
|
||||
|
||||
# Register the update action automatically
|
||||
# Register the update action automatically, adding widget-specific properties
|
||||
register_action(
|
||||
f"lvgl.{self.name}.update",
|
||||
ObjUpdateAction,
|
||||
create_modify_schema(self),
|
||||
base_update_schema(self, self.parts).extend(self.modify_schema),
|
||||
)(update_to_code)
|
||||
|
||||
@property
|
||||
@@ -182,7 +182,6 @@ class WidgetType:
|
||||
Generate code for a given widget
|
||||
:param w: The widget
|
||||
:param config: Its configuration
|
||||
:return: Generated code as a list of text lines
|
||||
"""
|
||||
|
||||
async def obj_creator(self, parent: MockObjClass, config: dict):
|
||||
@@ -228,6 +227,15 @@ class WidgetType:
|
||||
"""
|
||||
return value
|
||||
|
||||
def final_validate(self, widget, update_config, widget_config, path):
|
||||
"""
|
||||
Allow final validation for a given widget type update action
|
||||
:param widget: A widget
|
||||
:param update_config: The configuration for the update action
|
||||
:param widget_config: The configuration for the widget itself
|
||||
:param path: The path to the widget, for error reporting
|
||||
"""
|
||||
|
||||
|
||||
class NumberType(WidgetType):
|
||||
def get_max(self, config: dict):
|
||||
|
||||
@@ -382,7 +382,7 @@ async def set_obj_properties(w: Widget, config):
|
||||
clrs = join_enums(flag_clr, "LV_OBJ_FLAG_")
|
||||
w.clear_flag(clrs)
|
||||
for key, value in lambs.items():
|
||||
lamb = await cg.process_lambda(value, [], return_type=cg.bool_)
|
||||
lamb = await cg.process_lambda(value, [], capture="=", return_type=cg.bool_)
|
||||
flag = f"LV_OBJ_FLAG_{key.upper()}"
|
||||
with LvConditional(call_lambda(lamb)) as cond:
|
||||
w.add_flag(flag)
|
||||
@@ -407,7 +407,7 @@ async def set_obj_properties(w: Widget, config):
|
||||
clears = join_enums(clears, "LV_STATE_")
|
||||
w.clear_state(clears)
|
||||
for key, value in lambs.items():
|
||||
lamb = await cg.process_lambda(value, [], return_type=cg.bool_)
|
||||
lamb = await cg.process_lambda(value, [], capture="=", return_type=cg.bool_)
|
||||
state = f"LV_STATE_{key.upper()}"
|
||||
with LvConditional(call_lambda(lamb)) as cond:
|
||||
w.add_state(state)
|
||||
|
||||
@@ -1,20 +1,52 @@
|
||||
from esphome.const import CONF_BUTTON
|
||||
from esphome import config_validation as cv
|
||||
from esphome.const import CONF_BUTTON, CONF_TEXT
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
from ..defines import CONF_MAIN
|
||||
from ..defines import CONF_MAIN, CONF_WIDGETS
|
||||
from ..helpers import add_lv_use
|
||||
from ..lv_validation import lv_text
|
||||
from ..lvcode import lv, lv_expr
|
||||
from ..schemas import TEXT_SCHEMA
|
||||
from ..types import LvBoolean, WidgetType
|
||||
from . import Widget
|
||||
from .label import label_spec
|
||||
|
||||
lv_button_t = LvBoolean("lv_btn_t")
|
||||
|
||||
|
||||
class ButtonType(WidgetType):
|
||||
def __init__(self):
|
||||
super().__init__(CONF_BUTTON, lv_button_t, (CONF_MAIN,), lv_name="btn")
|
||||
super().__init__(
|
||||
CONF_BUTTON, lv_button_t, (CONF_MAIN,), schema=TEXT_SCHEMA, lv_name="btn"
|
||||
)
|
||||
|
||||
def validate(self, value):
|
||||
if CONF_TEXT in value:
|
||||
if CONF_WIDGETS in value:
|
||||
raise cv.Invalid("Cannot use both text and widgets in a button")
|
||||
add_lv_use("label")
|
||||
return value
|
||||
|
||||
def get_uses(self):
|
||||
return ("btn",)
|
||||
|
||||
async def to_code(self, w, config):
|
||||
return []
|
||||
def on_create(self, var: MockObj, config: dict):
|
||||
if CONF_TEXT in config:
|
||||
lv.label_create(var)
|
||||
return var
|
||||
|
||||
async def to_code(self, w: Widget, config):
|
||||
if text := config.get(CONF_TEXT):
|
||||
label_widget = Widget.create(
|
||||
None, lv_expr.obj_get_child(w.obj, 0), label_spec
|
||||
)
|
||||
await label_widget.set_property(CONF_TEXT, await lv_text.process(text))
|
||||
|
||||
def final_validate(self, widget, update_config, widget_config, path):
|
||||
if CONF_TEXT in update_config and CONF_TEXT not in widget_config:
|
||||
raise cv.Invalid(
|
||||
"Button must have 'text:' configured to allow updating text", path
|
||||
)
|
||||
|
||||
|
||||
button_spec = ButtonType()
|
||||
|
||||
@@ -141,6 +141,24 @@ void PrometheusHandler::add_friendly_name_label_(AsyncResponseStream *stream, st
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
void PrometheusHandler::print_metric_labels_(AsyncResponseStream *stream, const __FlashStringHelper *metric_name,
|
||||
EntityBase *obj, std::string &area, std::string &node,
|
||||
std::string &friendly_name) {
|
||||
#else
|
||||
void PrometheusHandler::print_metric_labels_(AsyncResponseStream *stream, const char *metric_name, EntityBase *obj,
|
||||
std::string &area, std::string &node, std::string &friendly_name) {
|
||||
#endif
|
||||
stream->print(metric_name);
|
||||
stream->print(ESPHOME_F("{id=\""));
|
||||
stream->print(relabel_id_(obj).c_str());
|
||||
add_area_label_(stream, area);
|
||||
add_node_label_(stream, node);
|
||||
add_friendly_name_label_(stream, friendly_name);
|
||||
stream->print(ESPHOME_F("\",name=\""));
|
||||
stream->print(relabel_name_(obj).c_str());
|
||||
}
|
||||
|
||||
// Type-specific implementation
|
||||
#ifdef USE_SENSOR
|
||||
void PrometheusHandler::sensor_type_(AsyncResponseStream *stream) {
|
||||
@@ -303,13 +321,7 @@ void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightStat
|
||||
if (obj->is_internal() && !this->include_internal_)
|
||||
return;
|
||||
// State
|
||||
stream->print(ESPHOME_F("esphome_light_state{id=\""));
|
||||
stream->print(relabel_id_(obj).c_str());
|
||||
add_area_label_(stream, area);
|
||||
add_node_label_(stream, node);
|
||||
add_friendly_name_label_(stream, friendly_name);
|
||||
stream->print(ESPHOME_F("\",name=\""));
|
||||
stream->print(relabel_name_(obj).c_str());
|
||||
print_metric_labels_(stream, ESPHOME_F("esphome_light_state"), obj, area, node, friendly_name);
|
||||
stream->print(ESPHOME_F("\"} "));
|
||||
stream->print(obj->remote_values.is_on());
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
@@ -318,78 +330,45 @@ void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightStat
|
||||
float brightness, r, g, b, w;
|
||||
color.as_brightness(&brightness);
|
||||
color.as_rgbw(&r, &g, &b, &w);
|
||||
stream->print(ESPHOME_F("esphome_light_color{id=\""));
|
||||
stream->print(relabel_id_(obj).c_str());
|
||||
add_area_label_(stream, area);
|
||||
add_node_label_(stream, node);
|
||||
add_friendly_name_label_(stream, friendly_name);
|
||||
stream->print(ESPHOME_F("\",name=\""));
|
||||
stream->print(relabel_name_(obj).c_str());
|
||||
stream->print(ESPHOME_F("\",channel=\"brightness\"} "));
|
||||
stream->print(brightness);
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
stream->print(ESPHOME_F("esphome_light_color{id=\""));
|
||||
stream->print(relabel_id_(obj).c_str());
|
||||
add_area_label_(stream, area);
|
||||
add_node_label_(stream, node);
|
||||
add_friendly_name_label_(stream, friendly_name);
|
||||
stream->print(ESPHOME_F("\",name=\""));
|
||||
stream->print(relabel_name_(obj).c_str());
|
||||
stream->print(ESPHOME_F("\",channel=\"r\"} "));
|
||||
stream->print(r);
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
stream->print(ESPHOME_F("esphome_light_color{id=\""));
|
||||
stream->print(relabel_id_(obj).c_str());
|
||||
add_area_label_(stream, area);
|
||||
add_node_label_(stream, node);
|
||||
add_friendly_name_label_(stream, friendly_name);
|
||||
stream->print(ESPHOME_F("\",name=\""));
|
||||
stream->print(relabel_name_(obj).c_str());
|
||||
stream->print(ESPHOME_F("\",channel=\"g\"} "));
|
||||
stream->print(g);
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
stream->print(ESPHOME_F("esphome_light_color{id=\""));
|
||||
stream->print(relabel_id_(obj).c_str());
|
||||
add_area_label_(stream, area);
|
||||
add_node_label_(stream, node);
|
||||
add_friendly_name_label_(stream, friendly_name);
|
||||
stream->print(ESPHOME_F("\",name=\""));
|
||||
stream->print(relabel_name_(obj).c_str());
|
||||
stream->print(ESPHOME_F("\",channel=\"b\"} "));
|
||||
stream->print(b);
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
stream->print(ESPHOME_F("esphome_light_color{id=\""));
|
||||
stream->print(relabel_id_(obj).c_str());
|
||||
add_area_label_(stream, area);
|
||||
add_node_label_(stream, node);
|
||||
add_friendly_name_label_(stream, friendly_name);
|
||||
stream->print(ESPHOME_F("\",name=\""));
|
||||
stream->print(relabel_name_(obj).c_str());
|
||||
stream->print(ESPHOME_F("\",channel=\"w\"} "));
|
||||
stream->print(w);
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
// Effect
|
||||
std::string effect = obj->get_effect_name();
|
||||
if (effect == "None") {
|
||||
stream->print(ESPHOME_F("esphome_light_effect_active{id=\""));
|
||||
stream->print(relabel_id_(obj).c_str());
|
||||
add_area_label_(stream, area);
|
||||
add_node_label_(stream, node);
|
||||
add_friendly_name_label_(stream, friendly_name);
|
||||
stream->print(ESPHOME_F("\",name=\""));
|
||||
stream->print(relabel_name_(obj).c_str());
|
||||
stream->print(ESPHOME_F("\",effect=\"None\"} 0\n"));
|
||||
} else {
|
||||
stream->print(ESPHOME_F("esphome_light_effect_active{id=\""));
|
||||
stream->print(relabel_id_(obj).c_str());
|
||||
add_area_label_(stream, area);
|
||||
add_node_label_(stream, node);
|
||||
add_friendly_name_label_(stream, friendly_name);
|
||||
stream->print(ESPHOME_F("\",name=\""));
|
||||
stream->print(relabel_name_(obj).c_str());
|
||||
if (obj->get_traits().supports_color_capability(light::ColorCapability::BRIGHTNESS)) {
|
||||
print_metric_labels_(stream, ESPHOME_F("esphome_light_color"), obj, area, node, friendly_name);
|
||||
stream->print(ESPHOME_F("\",channel=\"brightness\"} "));
|
||||
stream->print(brightness);
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
}
|
||||
if (obj->get_traits().supports_color_capability(light::ColorCapability::RGB)) {
|
||||
print_metric_labels_(stream, ESPHOME_F("esphome_light_color"), obj, area, node, friendly_name);
|
||||
stream->print(ESPHOME_F("\",channel=\"r\"} "));
|
||||
stream->print(r);
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
print_metric_labels_(stream, ESPHOME_F("esphome_light_color"), obj, area, node, friendly_name);
|
||||
stream->print(ESPHOME_F("\",channel=\"g\"} "));
|
||||
stream->print(g);
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
print_metric_labels_(stream, ESPHOME_F("esphome_light_color"), obj, area, node, friendly_name);
|
||||
stream->print(ESPHOME_F("\",channel=\"b\"} "));
|
||||
stream->print(b);
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
}
|
||||
if (obj->get_traits().supports_color_capability(light::ColorCapability::WHITE)) {
|
||||
print_metric_labels_(stream, ESPHOME_F("esphome_light_color"), obj, area, node, friendly_name);
|
||||
stream->print(ESPHOME_F("\",channel=\"w\"} "));
|
||||
stream->print(w);
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
}
|
||||
// Skip effect metrics if light has no effects
|
||||
if (!obj->get_effects().empty()) {
|
||||
// Effect
|
||||
std::string effect = obj->get_effect_name();
|
||||
print_metric_labels_(stream, ESPHOME_F("esphome_light_effect_active"), obj, area, node, friendly_name);
|
||||
stream->print(ESPHOME_F("\",effect=\""));
|
||||
stream->print(effect.c_str());
|
||||
stream->print(ESPHOME_F("\"} 1\n"));
|
||||
// Only vary based on effect
|
||||
if (effect == "None") {
|
||||
stream->print(ESPHOME_F("None\"} 0\n"));
|
||||
} else {
|
||||
stream->print(effect.c_str());
|
||||
stream->print(ESPHOME_F("\"} 1\n"));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -66,6 +66,14 @@ class PrometheusHandler : public AsyncWebHandler, public Component {
|
||||
void add_area_label_(AsyncResponseStream *stream, std::string &area);
|
||||
void add_node_label_(AsyncResponseStream *stream, std::string &node);
|
||||
void add_friendly_name_label_(AsyncResponseStream *stream, std::string &friendly_name);
|
||||
/// Print metric name and common labels (id, area, node, friendly_name, name)
|
||||
#ifdef USE_ESP8266
|
||||
void print_metric_labels_(AsyncResponseStream *stream, const __FlashStringHelper *metric_name, EntityBase *obj,
|
||||
std::string &area, std::string &node, std::string &friendly_name);
|
||||
#else
|
||||
void print_metric_labels_(AsyncResponseStream *stream, const char *metric_name, EntityBase *obj, std::string &area,
|
||||
std::string &node, std::string &friendly_name);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
/// Return the type for prometheus
|
||||
|
||||
@@ -654,7 +654,7 @@ void ThermostatClimate::trigger_supplemental_action_() {
|
||||
|
||||
void ThermostatClimate::switch_to_humidity_control_action_(HumidificationAction action) {
|
||||
// setup_complete_ helps us ensure an action is called immediately after boot
|
||||
if ((action == this->humidification_action_) && this->setup_complete_) {
|
||||
if ((action == this->humidification_action) && this->setup_complete_) {
|
||||
// already in target mode
|
||||
return;
|
||||
}
|
||||
@@ -683,7 +683,7 @@ void ThermostatClimate::switch_to_humidity_control_action_(HumidificationAction
|
||||
this->prev_humidity_control_trigger_->stop_action();
|
||||
this->prev_humidity_control_trigger_ = nullptr;
|
||||
}
|
||||
this->humidification_action_ = action;
|
||||
this->humidification_action = action;
|
||||
this->prev_humidity_control_trigger_ = trig;
|
||||
if (trig != nullptr) {
|
||||
trig->trigger();
|
||||
@@ -1114,7 +1114,7 @@ bool ThermostatClimate::dehumidification_required_() {
|
||||
}
|
||||
// if we get here, the current humidity is between target + hysteresis and target - hysteresis,
|
||||
// so the action should not change
|
||||
return this->humidification_action_ == THERMOSTAT_HUMIDITY_CONTROL_ACTION_DEHUMIDIFY;
|
||||
return this->humidification_action == THERMOSTAT_HUMIDITY_CONTROL_ACTION_DEHUMIDIFY;
|
||||
}
|
||||
|
||||
bool ThermostatClimate::humidification_required_() {
|
||||
@@ -1127,7 +1127,7 @@ bool ThermostatClimate::humidification_required_() {
|
||||
}
|
||||
// if we get here, the current humidity is between target - hysteresis and target + hysteresis,
|
||||
// so the action should not change
|
||||
return this->humidification_action_ == THERMOSTAT_HUMIDITY_CONTROL_ACTION_HUMIDIFY;
|
||||
return this->humidification_action == THERMOSTAT_HUMIDITY_CONTROL_ACTION_HUMIDIFY;
|
||||
}
|
||||
|
||||
void ThermostatClimate::dump_preset_config_(const char *preset_name, const ThermostatClimateTargetTempConfig &config) {
|
||||
|
||||
@@ -207,6 +207,9 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
void validate_target_temperature_high();
|
||||
void validate_target_humidity();
|
||||
|
||||
/// The current humidification action
|
||||
HumidificationAction humidification_action{THERMOSTAT_HUMIDITY_CONTROL_ACTION_NONE};
|
||||
|
||||
protected:
|
||||
/// Override control to change settings of the climate device.
|
||||
void control(const climate::ClimateCall &call) override;
|
||||
@@ -301,9 +304,6 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
/// The current supplemental action
|
||||
climate::ClimateAction supplemental_action_{climate::CLIMATE_ACTION_OFF};
|
||||
|
||||
/// The current humidification action
|
||||
HumidificationAction humidification_action_{THERMOSTAT_HUMIDITY_CONTROL_ACTION_NONE};
|
||||
|
||||
/// Default standard preset to use on start up
|
||||
climate::ClimatePreset default_preset_{};
|
||||
|
||||
|
||||
@@ -659,7 +659,7 @@ async def get_variable_with_full_id(id_: ID) -> tuple[ID, "MockObj"]:
|
||||
async def process_lambda(
|
||||
value: Lambda,
|
||||
parameters: TemplateArgsType,
|
||||
capture: str = "=",
|
||||
capture: str = "",
|
||||
return_type: SafeExpType = None,
|
||||
) -> LambdaExpression | None:
|
||||
"""Process the given lambda value into a LambdaExpression.
|
||||
@@ -702,12 +702,6 @@ async def process_lambda(
|
||||
parts[i * 3 + 1] = var
|
||||
parts[i * 3 + 2] = ""
|
||||
|
||||
# All id() references are global variables in generated C++ code.
|
||||
# Global variables should not be captured - they're accessible everywhere.
|
||||
# Use empty capture instead of capture-by-value.
|
||||
if capture == "=":
|
||||
capture = ""
|
||||
|
||||
if isinstance(value, ESPHomeDataBase) and value.esp_range is not None:
|
||||
location = value.esp_range.start_mark
|
||||
location.line += value.content_offset
|
||||
|
||||
@@ -16,6 +16,18 @@ binary_sensor:
|
||||
platform: template
|
||||
- id: left_sensor
|
||||
platform: template
|
||||
- platform: lvgl
|
||||
id: button_checker
|
||||
name: LVGL button
|
||||
widget: button_button
|
||||
on_state:
|
||||
then:
|
||||
- lvgl.checkbox.update:
|
||||
id: checkbox_id
|
||||
state:
|
||||
checked: !lambda |-
|
||||
auto y = x; // block inlining of one line return
|
||||
return y;
|
||||
|
||||
lvgl:
|
||||
log_level: debug
|
||||
@@ -414,6 +426,14 @@ lvgl:
|
||||
logger.log: Long pressed repeated
|
||||
- buttons:
|
||||
- id: button_e
|
||||
- button:
|
||||
id: button_with_text
|
||||
text: Button
|
||||
on_click:
|
||||
lvgl.button.update:
|
||||
id: button_with_text
|
||||
text: Clicked
|
||||
|
||||
- button:
|
||||
layout: 2x1
|
||||
id: button_button
|
||||
|
||||
@@ -60,6 +60,7 @@ display:
|
||||
update_interval: never
|
||||
|
||||
lvgl:
|
||||
update_when_display_idle: true
|
||||
displays:
|
||||
- tft_display
|
||||
- second_display
|
||||
|
||||
@@ -112,6 +112,25 @@ cover:
|
||||
}
|
||||
return COVER_CLOSED;
|
||||
|
||||
light:
|
||||
- platform: binary
|
||||
name: "Binary Light"
|
||||
output: test_output
|
||||
- platform: monochromatic
|
||||
name: "Brightness Light"
|
||||
output: test_output
|
||||
- platform: rgb
|
||||
name: "RGB Light"
|
||||
red: test_output
|
||||
green: test_output
|
||||
blue: test_output
|
||||
- platform: rgbw
|
||||
name: "RGBW Light"
|
||||
red: test_output
|
||||
green: test_output
|
||||
blue: test_output
|
||||
white: test_output
|
||||
|
||||
lock:
|
||||
- platform: template
|
||||
id: template_lock1
|
||||
@@ -122,6 +141,14 @@ lock:
|
||||
return LOCK_STATE_UNLOCKED;
|
||||
optimistic: true
|
||||
|
||||
output:
|
||||
- platform: template
|
||||
id: test_output
|
||||
type: float
|
||||
write_action:
|
||||
- lambda: |-
|
||||
// no-op for CI/build tests
|
||||
(void)state;
|
||||
select:
|
||||
- platform: template
|
||||
id: template_select1
|
||||
|
||||
Reference in New Issue
Block a user