mirror of
https://github.com/esphome/esphome.git
synced 2024-11-23 23:48:11 +01:00
Merge branch 'dev' into gsm
This commit is contained in:
commit
5b4ccabc17
41 changed files with 1148 additions and 189 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -138,3 +138,5 @@ sdkconfig.*
|
|||
.tests/
|
||||
|
||||
/components
|
||||
/managed_components
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ bool AdE7953Spi::ade_read_16(uint16_t reg, uint16_t *value) {
|
|||
this->write_byte16(reg);
|
||||
this->transfer_byte(0x80);
|
||||
uint8_t recv[2];
|
||||
this->read_array(recv, 4);
|
||||
this->read_array(recv, 2);
|
||||
*value = encode_uint16(recv[0], recv[1]);
|
||||
this->disable();
|
||||
return false;
|
||||
|
|
|
@ -1,25 +1,27 @@
|
|||
import base64
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ACTION,
|
||||
CONF_ACTIONS,
|
||||
CONF_DATA,
|
||||
CONF_DATA_TEMPLATE,
|
||||
CONF_EVENT,
|
||||
CONF_ID,
|
||||
CONF_KEY,
|
||||
CONF_ON_CLIENT_CONNECTED,
|
||||
CONF_ON_CLIENT_DISCONNECTED,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_REBOOT_TIMEOUT,
|
||||
CONF_SERVICE,
|
||||
CONF_VARIABLES,
|
||||
CONF_SERVICES,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_EVENT,
|
||||
CONF_TAG,
|
||||
CONF_ON_CLIENT_CONNECTED,
|
||||
CONF_ON_CLIENT_DISCONNECTED,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_VARIABLES,
|
||||
)
|
||||
from esphome.core import coroutine_with_priority
|
||||
|
||||
|
@ -63,7 +65,25 @@ def validate_encryption_key(value):
|
|||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
ACTIONS_SCHEMA = automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
|
||||
cv.Exclusive(CONF_SERVICE, group_of_exclusion=CONF_ACTION): cv.valid_name,
|
||||
cv.Exclusive(CONF_ACTION, group_of_exclusion=CONF_ACTION): cv.valid_name,
|
||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
|
||||
{
|
||||
cv.validate_id_name: cv.one_of(*SERVICE_ARG_NATIVE_TYPES, lower=True),
|
||||
}
|
||||
),
|
||||
},
|
||||
cv.All(
|
||||
cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION),
|
||||
cv.rename_key(CONF_SERVICE, CONF_ACTION),
|
||||
),
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(APIServer),
|
||||
cv.Optional(CONF_PORT, default=6053): cv.port,
|
||||
|
@ -71,19 +91,10 @@ CONFIG_SCHEMA = cv.Schema(
|
|||
cv.Optional(
|
||||
CONF_REBOOT_TIMEOUT, default="15min"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_SERVICES): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
|
||||
cv.Required(CONF_SERVICE): cv.valid_name,
|
||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
|
||||
{
|
||||
cv.validate_id_name: cv.one_of(
|
||||
*SERVICE_ARG_NATIVE_TYPES, lower=True
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Exclusive(
|
||||
CONF_SERVICES, group_of_exclusion=CONF_ACTIONS
|
||||
): ACTIONS_SCHEMA,
|
||||
cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA,
|
||||
cv.Optional(CONF_ENCRYPTION): cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_KEY): validate_encryption_key,
|
||||
|
@ -96,7 +107,9 @@ CONFIG_SCHEMA = cv.Schema(
|
|||
single=True
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.rename_key(CONF_SERVICES, CONF_ACTIONS),
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(40.0)
|
||||
|
@ -108,7 +121,7 @@ async def to_code(config):
|
|||
cg.add(var.set_password(config[CONF_PASSWORD]))
|
||||
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||
|
||||
for conf in config.get(CONF_SERVICES, []):
|
||||
for conf in config.get(CONF_ACTIONS, []):
|
||||
template_args = []
|
||||
func_args = []
|
||||
service_arg_names = []
|
||||
|
@ -119,7 +132,7 @@ async def to_code(config):
|
|||
service_arg_names.append(name)
|
||||
templ = cg.TemplateArguments(*template_args)
|
||||
trigger = cg.new_Pvariable(
|
||||
conf[CONF_TRIGGER_ID], templ, conf[CONF_SERVICE], service_arg_names
|
||||
conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names
|
||||
)
|
||||
cg.add(var.register_user_service(trigger))
|
||||
await automation.build_automation(trigger, func_args, conf)
|
||||
|
@ -152,28 +165,43 @@ async def to_code(config):
|
|||
|
||||
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
|
||||
|
||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema(
|
||||
|
||||
HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(APIServer),
|
||||
cv.Required(CONF_SERVICE): cv.templatable(cv.string),
|
||||
cv.Exclusive(CONF_SERVICE, group_of_exclusion=CONF_ACTION): cv.templatable(
|
||||
cv.string
|
||||
),
|
||||
cv.Exclusive(CONF_ACTION, group_of_exclusion=CONF_ACTION): cv.templatable(
|
||||
cv.string
|
||||
),
|
||||
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
|
||||
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
|
||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
|
||||
{cv.string: cv.returning_lambda}
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION),
|
||||
cv.rename_key(CONF_SERVICE, CONF_ACTION),
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"homeassistant.action",
|
||||
HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
|
||||
)
|
||||
@automation.register_action(
|
||||
"homeassistant.service",
|
||||
HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA,
|
||||
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
|
||||
)
|
||||
async def homeassistant_service_to_code(config, action_id, template_arg, args):
|
||||
serv = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, serv, False)
|
||||
templ = await cg.templatable(config[CONF_SERVICE], args, None)
|
||||
templ = await cg.templatable(config[CONF_ACTION], args, None)
|
||||
cg.add(var.set_service(templ))
|
||||
for key, value in config[CONF_DATA].items():
|
||||
templ = await cg.templatable(value, args, None)
|
||||
|
|
|
@ -138,8 +138,8 @@ void HttpRequestUpdate::update() {
|
|||
this->publish_state();
|
||||
}
|
||||
|
||||
void HttpRequestUpdate::perform() {
|
||||
if (this->state_ != update::UPDATE_STATE_AVAILABLE) {
|
||||
void HttpRequestUpdate::perform(bool force) {
|
||||
if (this->state_ != update::UPDATE_STATE_AVAILABLE && !force) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent {
|
|||
void setup() override;
|
||||
void update() override;
|
||||
|
||||
void perform() override;
|
||||
void perform(bool force) override;
|
||||
|
||||
void set_source_url(const std::string &source_url) { this->source_url_ = source_url; }
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import re
|
||||
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import __version__
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
|
@ -39,4 +38,4 @@ def _process_next_url(url: str):
|
|||
async def setup_improv_core(var, config):
|
||||
if CONF_NEXT_URL in config:
|
||||
cg.add(var.set_next_url(_process_next_url(config[CONF_NEXT_URL])))
|
||||
cg.add_library("esphome/Improv", "1.2.3")
|
||||
cg.add_library("improv/Improv", "1.2.4")
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import logging
|
||||
|
||||
from esphome.automation import build_automation, register_action, validate_automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.display import Display
|
||||
import esphome.config_validation as cv
|
||||
|
@ -8,7 +9,11 @@ from esphome.const import (
|
|||
CONF_BUFFER_SIZE,
|
||||
CONF_ID,
|
||||
CONF_LAMBDA,
|
||||
CONF_ON_IDLE,
|
||||
CONF_PAGES,
|
||||
CONF_TIMEOUT,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_TYPE,
|
||||
)
|
||||
from esphome.core import CORE, ID, Lambda
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
@ -16,21 +21,26 @@ from esphome.final_validate import full_config
|
|||
from esphome.helpers import write_file_if_changed
|
||||
|
||||
from . import defines as df, helpers, lv_validation as lvalid
|
||||
from .automation import update_to_code
|
||||
from .btn import btn_spec
|
||||
from .label import label_spec
|
||||
from .lvcode import ConstantLiteral, LvContext
|
||||
from .lv_validation import lv_images_used
|
||||
from .lvcode import LvContext
|
||||
from .obj import obj_spec
|
||||
from .schemas import any_widget_schema, obj_schema
|
||||
from .rotary_encoders import ROTARY_ENCODER_CONFIG, rotary_encoders_to_code
|
||||
from .schemas import any_widget_schema, create_modify_schema, obj_schema
|
||||
from .touchscreens import touchscreen_schema, touchscreens_to_code
|
||||
from .trigger import generate_triggers
|
||||
from .types import (
|
||||
WIDGET_TYPES,
|
||||
FontEngine,
|
||||
IdleTrigger,
|
||||
LvglComponent,
|
||||
lv_disp_t_ptr,
|
||||
ObjUpdateAction,
|
||||
lv_font_t,
|
||||
lvgl_ns,
|
||||
)
|
||||
from .widget import LvScrActType, Widget, add_widgets, set_obj_properties
|
||||
from .widget import Widget, add_widgets, lv_scr_act, set_obj_properties
|
||||
|
||||
DOMAIN = "lvgl"
|
||||
DEPENDENCIES = ("display",)
|
||||
|
@ -41,17 +51,21 @@ LOGGER = logging.getLogger(__name__)
|
|||
for w_type in (label_spec, obj_spec, btn_spec):
|
||||
WIDGET_TYPES[w_type.name] = w_type
|
||||
|
||||
lv_scr_act_spec = LvScrActType()
|
||||
lv_scr_act = Widget.create(
|
||||
None, ConstantLiteral("lv_scr_act()"), lv_scr_act_spec, {}, parent=None
|
||||
)
|
||||
|
||||
WIDGET_SCHEMA = any_widget_schema()
|
||||
|
||||
for w_type in WIDGET_TYPES.values():
|
||||
register_action(
|
||||
f"lvgl.{w_type.name}.update",
|
||||
ObjUpdateAction,
|
||||
create_modify_schema(w_type),
|
||||
)(update_to_code)
|
||||
|
||||
|
||||
async def add_init_lambda(lv_component, init):
|
||||
if init:
|
||||
lamb = await cg.process_lambda(Lambda(init), [(lv_disp_t_ptr, "lv_disp")])
|
||||
lamb = await cg.process_lambda(
|
||||
Lambda(init), [(LvglComponent.operator("ptr"), "lv_component")]
|
||||
)
|
||||
cg.add(lv_component.add_init_lambda(lamb))
|
||||
|
||||
|
||||
|
@ -99,6 +113,13 @@ def final_validation(config):
|
|||
buffer_frac = config[CONF_BUFFER_SIZE]
|
||||
if CORE.is_esp32 and buffer_frac > 0.5 and "psram" not in global_config:
|
||||
LOGGER.warning("buffer_size: may need to be reduced without PSRAM")
|
||||
for image_id in lv_images_used:
|
||||
path = global_config.get_path_for_id(image_id)[:-1]
|
||||
image_conf = global_config.get_config_for_path(path)
|
||||
if image_conf[CONF_TYPE] in ("RGBA", "RGB24"):
|
||||
raise cv.Invalid(
|
||||
"Using RGBA or RGB24 in image config not compatible with LVGL", path
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
@ -174,9 +195,15 @@ async def to_code(config):
|
|||
|
||||
with LvContext():
|
||||
await touchscreens_to_code(lv_component, config)
|
||||
await rotary_encoders_to_code(lv_component, config)
|
||||
await set_obj_properties(lv_scr_act, config)
|
||||
await add_widgets(lv_scr_act, config)
|
||||
Widget.set_completed()
|
||||
await generate_triggers(lv_component)
|
||||
for conf in config.get(CONF_ON_IDLE, ()):
|
||||
templ = await cg.templatable(conf[CONF_TIMEOUT], [], cg.uint32)
|
||||
idle_trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], lv_component, templ)
|
||||
await build_automation(idle_trigger, [], conf)
|
||||
await add_init_lambda(lv_component, LvContext.get_code())
|
||||
for comp in helpers.lvgl_components_required:
|
||||
CORE.add_define(f"USE_LVGL_{comp.upper()}")
|
||||
|
@ -212,9 +239,18 @@ CONFIG_SCHEMA = (
|
|||
cv.Optional(df.CONF_BYTE_ORDER, default="big_endian"): cv.one_of(
|
||||
"big_endian", "little_endian"
|
||||
),
|
||||
cv.Optional(CONF_ON_IDLE): validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(IdleTrigger),
|
||||
cv.Required(CONF_TIMEOUT): cv.templatable(
|
||||
cv.positive_time_period_milliseconds
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(df.CONF_WIDGETS): cv.ensure_list(WIDGET_SCHEMA),
|
||||
cv.Optional(df.CONF_TRANSPARENCY_KEY, default=0x000400): lvalid.lv_color,
|
||||
cv.GenerateID(df.CONF_TOUCHSCREENS): touchscreen_schema,
|
||||
cv.GenerateID(df.CONF_ROTARY_ENCODERS): ROTARY_ENCODER_CONFIG,
|
||||
}
|
||||
)
|
||||
).add_extra(cv.has_at_least_one_key(CONF_PAGES, df.CONF_WIDGETS))
|
||||
|
|
188
esphome/components/lvgl/automation.py
Normal file
188
esphome/components/lvgl/automation.py
Normal file
|
@ -0,0 +1,188 @@
|
|||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_TIMEOUT
|
||||
from esphome.core import Lambda
|
||||
from esphome.cpp_generator import RawStatement
|
||||
from esphome.cpp_types import nullptr
|
||||
|
||||
from .defines import CONF_LVGL_ID, CONF_SHOW_SNOW, literal
|
||||
from .lv_validation import lv_bool
|
||||
from .lvcode import (
|
||||
LambdaContext,
|
||||
ReturnStatement,
|
||||
add_line_marks,
|
||||
lv,
|
||||
lv_add,
|
||||
lv_obj,
|
||||
lvgl_comp,
|
||||
)
|
||||
from .schemas import ACTION_SCHEMA, LVGL_SCHEMA
|
||||
from .types import (
|
||||
LvglAction,
|
||||
LvglComponent,
|
||||
LvglComponentPtr,
|
||||
LvglCondition,
|
||||
ObjUpdateAction,
|
||||
lv_obj_t,
|
||||
)
|
||||
from .widget import Widget, get_widget, lv_scr_act, set_obj_properties
|
||||
|
||||
|
||||
async def action_to_code(action: list, action_id, widget: Widget, template_arg, args):
|
||||
with LambdaContext() as context:
|
||||
lv.cond_if(widget.obj == nullptr)
|
||||
lv_add(RawStatement(" return;"))
|
||||
lv.cond_endif()
|
||||
code = context.get_code()
|
||||
code.extend(action)
|
||||
action = "\n".join(code) + "\n\n"
|
||||
lamb = await cg.process_lambda(Lambda(action), args)
|
||||
var = cg.new_Pvariable(action_id, template_arg, lamb)
|
||||
return var
|
||||
|
||||
|
||||
async def update_to_code(config, action_id, template_arg, args):
|
||||
if config is not None:
|
||||
widget = await get_widget(config)
|
||||
with LambdaContext() as context:
|
||||
add_line_marks(action_id)
|
||||
await set_obj_properties(widget, config)
|
||||
await widget.type.to_code(widget, config)
|
||||
if (
|
||||
widget.type.w_type.value_property is not None
|
||||
and widget.type.w_type.value_property in config
|
||||
):
|
||||
lv.event_send(widget.obj, literal("LV_EVENT_VALUE_CHANGED"), nullptr)
|
||||
return await action_to_code(
|
||||
context.get_code(), action_id, widget, template_arg, args
|
||||
)
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"lvgl.is_paused",
|
||||
LvglCondition,
|
||||
LVGL_SCHEMA,
|
||||
)
|
||||
async def lvgl_is_paused(config, condition_id, template_arg, args):
|
||||
lvgl = config[CONF_LVGL_ID]
|
||||
with LambdaContext(
|
||||
[(LvglComponentPtr, "lvgl_comp")], return_type=cg.bool_
|
||||
) as context:
|
||||
lv_add(ReturnStatement(lvgl_comp.is_paused()))
|
||||
var = cg.new_Pvariable(condition_id, template_arg, await context.get_lambda())
|
||||
await cg.register_parented(var, lvgl)
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"lvgl.is_idle",
|
||||
LvglCondition,
|
||||
LVGL_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_TIMEOUT): cv.templatable(
|
||||
cv.positive_time_period_milliseconds
|
||||
)
|
||||
}
|
||||
),
|
||||
)
|
||||
async def lvgl_is_idle(config, condition_id, template_arg, args):
|
||||
lvgl = config[CONF_LVGL_ID]
|
||||
timeout = await cg.templatable(config[CONF_TIMEOUT], [], cg.uint32)
|
||||
with LambdaContext(
|
||||
[(LvglComponentPtr, "lvgl_comp")], return_type=cg.bool_
|
||||
) as context:
|
||||
lv_add(ReturnStatement(lvgl_comp.is_idle(timeout)))
|
||||
var = cg.new_Pvariable(condition_id, template_arg, await context.get_lambda())
|
||||
await cg.register_parented(var, lvgl)
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"lvgl.widget.redraw",
|
||||
ObjUpdateAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ID): cv.use_id(lv_obj_t),
|
||||
cv.GenerateID(CONF_LVGL_ID): cv.use_id(LvglComponent),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def obj_invalidate_to_code(config, action_id, template_arg, args):
|
||||
if CONF_ID in config:
|
||||
w = await get_widget(config)
|
||||
else:
|
||||
w = lv_scr_act
|
||||
with LambdaContext() as context:
|
||||
add_line_marks(action_id)
|
||||
lv_obj.invalidate(w.obj)
|
||||
return await action_to_code(context.get_code(), action_id, w, template_arg, args)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"lvgl.pause",
|
||||
LvglAction,
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(LvglComponent),
|
||||
cv.Optional(CONF_SHOW_SNOW, default=False): lv_bool,
|
||||
},
|
||||
)
|
||||
async def pause_action_to_code(config, action_id, template_arg, args):
|
||||
with LambdaContext([(LvglComponentPtr, "lvgl_comp")]) as context:
|
||||
add_line_marks(action_id)
|
||||
lv_add(lvgl_comp.set_paused(True, config[CONF_SHOW_SNOW]))
|
||||
var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda())
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"lvgl.resume",
|
||||
LvglAction,
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(LvglComponent),
|
||||
},
|
||||
)
|
||||
async def resume_action_to_code(config, action_id, template_arg, args):
|
||||
with LambdaContext([(LvglComponentPtr, "lvgl_comp")]) as context:
|
||||
add_line_marks(action_id)
|
||||
lv_add(lvgl_comp.set_paused(False, False))
|
||||
var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda())
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action("lvgl.widget.disable", ObjUpdateAction, ACTION_SCHEMA)
|
||||
async def obj_disable_to_code(config, action_id, template_arg, args):
|
||||
w = await get_widget(config)
|
||||
with LambdaContext() as context:
|
||||
add_line_marks(action_id)
|
||||
w.add_state("LV_STATE_DISABLED")
|
||||
return await action_to_code(context.get_code(), action_id, w, template_arg, args)
|
||||
|
||||
|
||||
@automation.register_action("lvgl.widget.enable", ObjUpdateAction, ACTION_SCHEMA)
|
||||
async def obj_enable_to_code(config, action_id, template_arg, args):
|
||||
w = await get_widget(config)
|
||||
with LambdaContext() as context:
|
||||
add_line_marks(action_id)
|
||||
w.clear_state("LV_STATE_DISABLED")
|
||||
return await action_to_code(context.get_code(), action_id, w, template_arg, args)
|
||||
|
||||
|
||||
@automation.register_action("lvgl.widget.hide", ObjUpdateAction, ACTION_SCHEMA)
|
||||
async def obj_hide_to_code(config, action_id, template_arg, args):
|
||||
w = await get_widget(config)
|
||||
with LambdaContext() as context:
|
||||
add_line_marks(action_id)
|
||||
w.add_flag("LV_OBJ_FLAG_HIDDEN")
|
||||
return await action_to_code(context.get_code(), action_id, w, template_arg, args)
|
||||
|
||||
|
||||
@automation.register_action("lvgl.widget.show", ObjUpdateAction, ACTION_SCHEMA)
|
||||
async def obj_show_to_code(config, action_id, template_arg, args):
|
||||
w = await get_widget(config)
|
||||
with LambdaContext() as context:
|
||||
add_line_marks(action_id)
|
||||
w.clear_flag("LV_OBJ_FLAG_HIDDEN")
|
||||
return await action_to_code(context.get_code(), action_id, w, template_arg, args)
|
|
@ -9,9 +9,6 @@ class BtnType(WidgetType):
|
|||
def __init__(self):
|
||||
super().__init__(CONF_BUTTON, LvBoolean("lv_btn_t"), (CONF_MAIN,))
|
||||
|
||||
async def to_code(self, w, config):
|
||||
return []
|
||||
|
||||
def obj_creator(self, parent: MockObjClass, config: dict):
|
||||
"""
|
||||
LVGL 8 calls buttons `btn`
|
||||
|
@ -21,5 +18,8 @@ class BtnType(WidgetType):
|
|||
def get_uses(self):
|
||||
return ("btn",)
|
||||
|
||||
async def to_code(self, w, config):
|
||||
return []
|
||||
|
||||
|
||||
btn_spec = BtnType()
|
||||
|
|
|
@ -4,12 +4,32 @@ Constants already defined in esphome.const are not duplicated here and must be i
|
|||
|
||||
"""
|
||||
|
||||
from typing import Union
|
||||
|
||||
from esphome import codegen as cg, config_validation as cv
|
||||
from esphome.core import ID, Lambda
|
||||
from esphome.cpp_generator import Literal
|
||||
from esphome.cpp_types import uint32
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
|
||||
from .lvcode import ConstantLiteral
|
||||
from .helpers import requires_component
|
||||
|
||||
|
||||
class ConstantLiteral(Literal):
|
||||
__slots__ = ("constant",)
|
||||
|
||||
def __init__(self, constant: str):
|
||||
super().__init__()
|
||||
self.constant = constant
|
||||
|
||||
def __str__(self):
|
||||
return self.constant
|
||||
|
||||
|
||||
def literal(arg: Union[str, ConstantLiteral]):
|
||||
if isinstance(arg, str):
|
||||
return ConstantLiteral(arg)
|
||||
return arg
|
||||
|
||||
|
||||
class LValidator:
|
||||
|
@ -18,14 +38,19 @@ class LValidator:
|
|||
has `process()` to convert a value during code generation
|
||||
"""
|
||||
|
||||
def __init__(self, validator, rtype, idtype=None, idexpr=None, retmapper=None):
|
||||
def __init__(
|
||||
self, validator, rtype, idtype=None, idexpr=None, retmapper=None, requires=None
|
||||
):
|
||||
self.validator = validator
|
||||
self.rtype = rtype
|
||||
self.idtype = idtype
|
||||
self.idexpr = idexpr
|
||||
self.retmapper = retmapper
|
||||
self.requires = requires
|
||||
|
||||
def __call__(self, value):
|
||||
if self.requires:
|
||||
value = requires_component(self.requires)(value)
|
||||
if isinstance(value, cv.Lambda):
|
||||
return cv.returning_lambda(value)
|
||||
if self.idtype is not None and isinstance(value, ID):
|
||||
|
@ -422,6 +447,7 @@ CONF_RECOLOR = "recolor"
|
|||
CONF_RIGHT_BUTTON = "right_button"
|
||||
CONF_ROLLOVER = "rollover"
|
||||
CONF_ROOT_BACK_BTN = "root_back_btn"
|
||||
CONF_ROTARY_ENCODERS = "rotary_encoders"
|
||||
CONF_ROWS = "rows"
|
||||
CONF_SCALES = "scales"
|
||||
CONF_SCALE_LINES = "scale_lines"
|
||||
|
|
|
@ -2,6 +2,7 @@ import esphome.codegen as cg
|
|||
from esphome.components.binary_sensor import BinarySensor
|
||||
from esphome.components.color import ColorStruct
|
||||
from esphome.components.font import Font
|
||||
from esphome.components.image import Image_
|
||||
from esphome.components.sensor import Sensor
|
||||
from esphome.components.text_sensor import TextSensor
|
||||
import esphome.config_validation as cv
|
||||
|
@ -13,22 +14,15 @@ from esphome.helpers import cpp_string_escape
|
|||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
|
||||
from . import types as ty
|
||||
from .defines import LV_FONTS, LValidator, LvConstant
|
||||
from .defines import LV_FONTS, ConstantLiteral, LValidator, LvConstant, literal
|
||||
from .helpers import (
|
||||
esphome_fonts_used,
|
||||
lv_fonts_used,
|
||||
lvgl_components_required,
|
||||
requires_component,
|
||||
)
|
||||
from .lvcode import ConstantLiteral, lv_expr
|
||||
from .types import lv_font_t
|
||||
|
||||
|
||||
def literal_mapper(value, args=()):
|
||||
if isinstance(value, str):
|
||||
return ConstantLiteral(value)
|
||||
return value
|
||||
|
||||
from .lvcode import lv_expr
|
||||
from .types import lv_font_t, lv_img_t
|
||||
|
||||
opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER")
|
||||
|
||||
|
@ -43,7 +37,7 @@ def opacity_validator(value):
|
|||
return value
|
||||
|
||||
|
||||
opacity = LValidator(opacity_validator, uint32, retmapper=literal_mapper)
|
||||
opacity = LValidator(opacity_validator, uint32, retmapper=literal)
|
||||
|
||||
|
||||
@schema_extractor("one_of")
|
||||
|
@ -79,9 +73,7 @@ def pixels_or_percent_validator(value):
|
|||
return f"lv_pct({int(cv.percentage(value) * 100)})"
|
||||
|
||||
|
||||
pixels_or_percent = LValidator(
|
||||
pixels_or_percent_validator, uint32, retmapper=literal_mapper
|
||||
)
|
||||
pixels_or_percent = LValidator(pixels_or_percent_validator, uint32, retmapper=literal)
|
||||
|
||||
|
||||
def zoom(value):
|
||||
|
@ -115,7 +107,7 @@ def size_validator(value):
|
|||
return f"lv_pct({int(cv.percentage(value) * 100)})"
|
||||
|
||||
|
||||
size = LValidator(size_validator, uint32, retmapper=literal_mapper)
|
||||
size = LValidator(size_validator, uint32, retmapper=literal)
|
||||
|
||||
radius_consts = LvConstant("LV_RADIUS_", "CIRCLE")
|
||||
|
||||
|
@ -130,21 +122,37 @@ def radius_validator(value):
|
|||
return value
|
||||
|
||||
|
||||
radius = LValidator(radius_validator, uint32, retmapper=literal)
|
||||
|
||||
|
||||
def id_name(value):
|
||||
if value == SCHEMA_EXTRACT:
|
||||
return "id"
|
||||
return cv.validate_id_name(value)
|
||||
|
||||
|
||||
radius = LValidator(radius_validator, uint32, retmapper=literal_mapper)
|
||||
|
||||
|
||||
def stop_value(value):
|
||||
return cv.int_range(0, 255)(value)
|
||||
|
||||
|
||||
lv_images_used = set()
|
||||
|
||||
|
||||
def image_validator(value):
|
||||
value = requires_component("image")(value)
|
||||
value = cv.use_id(Image_)(value)
|
||||
lv_images_used.add(value)
|
||||
return value
|
||||
|
||||
|
||||
lv_image = LValidator(
|
||||
image_validator,
|
||||
lv_img_t,
|
||||
retmapper=lambda x: lv_expr.img_from(MockObj(x)),
|
||||
requires="image",
|
||||
)
|
||||
lv_bool = LValidator(
|
||||
cv.boolean, cg.bool_, BinarySensor, "get_state()", retmapper=literal_mapper
|
||||
cv.boolean, cg.bool_, BinarySensor, "get_state()", retmapper=literal
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@ from esphome.cpp_generator import (
|
|||
AssignmentExpression,
|
||||
CallExpression,
|
||||
Expression,
|
||||
ExpressionStatement,
|
||||
LambdaExpression,
|
||||
Literal,
|
||||
MockObj,
|
||||
RawExpression,
|
||||
RawStatement,
|
||||
|
@ -19,7 +19,9 @@ from esphome.cpp_generator import (
|
|||
statement,
|
||||
)
|
||||
|
||||
from .defines import ConstantLiteral
|
||||
from .helpers import get_line_marks
|
||||
from .types import lv_group_t
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -105,29 +107,40 @@ class LambdaContext(CodeContext):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
parameters: list[tuple[SafeExpType, str]],
|
||||
return_type: SafeExpType = None,
|
||||
parameters: list[tuple[SafeExpType, str]] = None,
|
||||
return_type: SafeExpType = cg.void,
|
||||
capture: str = "",
|
||||
):
|
||||
super().__init__()
|
||||
self.code_list: list[Statement] = []
|
||||
self.parameters = parameters
|
||||
self.return_type = return_type
|
||||
self.capture = capture
|
||||
|
||||
def add(self, expression: Union[Expression, Statement]):
|
||||
self.code_list.append(expression)
|
||||
return expression
|
||||
|
||||
async def code(self) -> LambdaExpression:
|
||||
async def get_lambda(self) -> LambdaExpression:
|
||||
code_text = self.get_code()
|
||||
return await cg.process_lambda(
|
||||
Lambda("\n".join(code_text) + "\n\n"),
|
||||
self.parameters,
|
||||
capture=self.capture,
|
||||
return_type=self.return_type,
|
||||
)
|
||||
|
||||
def get_code(self):
|
||||
code_text = []
|
||||
for exp in self.code_list:
|
||||
text = str(statement(exp))
|
||||
text = text.rstrip()
|
||||
code_text.append(text)
|
||||
return await cg.process_lambda(
|
||||
Lambda("\n".join(code_text) + "\n\n"),
|
||||
self.parameters,
|
||||
return_type=self.return_type,
|
||||
)
|
||||
return code_text
|
||||
|
||||
def __enter__(self):
|
||||
super().__enter__()
|
||||
return self
|
||||
|
||||
|
||||
class LocalVariable(MockObj):
|
||||
|
@ -187,13 +200,18 @@ class MockLv:
|
|||
return result
|
||||
|
||||
def cond_if(self, expression: Expression):
|
||||
CodeContext.append(RawExpression(f"if({expression}) {{"))
|
||||
CodeContext.append(RawStatement(f"if {expression} {{"))
|
||||
|
||||
def cond_else(self):
|
||||
CodeContext.append(RawExpression("} else {"))
|
||||
CodeContext.append(RawStatement("} else {"))
|
||||
|
||||
def cond_endif(self):
|
||||
CodeContext.append(RawExpression("}"))
|
||||
CodeContext.append(RawStatement("}"))
|
||||
|
||||
|
||||
class ReturnStatement(ExpressionStatement):
|
||||
def __str__(self):
|
||||
return f"return {self.expression};"
|
||||
|
||||
|
||||
class LvExpr(MockLv):
|
||||
|
@ -210,6 +228,7 @@ lv = MockLv("lv_")
|
|||
lv_expr = LvExpr("lv_")
|
||||
# Mock for lv_obj_ calls
|
||||
lv_obj = MockLv("lv_obj_")
|
||||
lvgl_comp = MockObj("lvgl_comp", "->")
|
||||
|
||||
|
||||
# equivalent to cg.add() for the lvgl init context
|
||||
|
@ -226,12 +245,19 @@ def lv_assign(target, expression):
|
|||
lv_add(RawExpression(f"{target} = {expression}"))
|
||||
|
||||
|
||||
class ConstantLiteral(Literal):
|
||||
__slots__ = ("constant",)
|
||||
lv_groups = {} # Widget group names
|
||||
|
||||
def __init__(self, constant: str):
|
||||
super().__init__()
|
||||
self.constant = constant
|
||||
|
||||
def __str__(self):
|
||||
return self.constant
|
||||
def add_group(name):
|
||||
if name is None:
|
||||
return None
|
||||
fullname = f"lv_esp_group_{name}"
|
||||
if name not in lv_groups:
|
||||
gid = ID(fullname, True, type=lv_group_t.operator("ptr"))
|
||||
lv_add(
|
||||
AssignmentExpression(
|
||||
type_=gid.type, modifier="", name=fullname, rhs=lv_expr.group_create()
|
||||
)
|
||||
)
|
||||
lv_groups[name] = ConstantLiteral(fullname)
|
||||
return lv_groups[name]
|
||||
|
|
|
@ -19,13 +19,35 @@ void LvglComponent::draw_buffer_(const lv_area_t *area, const uint8_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_) {
|
||||
auto now = millis();
|
||||
this->draw_buffer_(area, (const uint8_t *) color_p);
|
||||
ESP_LOGV(TAG, "flush_cb, area=%d/%d, %d/%d took %dms", area->x1, area->y1, lv_area_get_width(area),
|
||||
lv_area_get_height(area), (int) (millis() - now));
|
||||
}
|
||||
lv_disp_flush_ready(disp_drv);
|
||||
}
|
||||
|
||||
void LvglComponent::write_random_() {
|
||||
// length of 2 lines in 32 bit units
|
||||
// we write 2 lines for the benefit of displays that won't write one line at a time.
|
||||
size_t line_len = this->disp_drv_.hor_res * LV_COLOR_DEPTH / 8 / 4 * 2;
|
||||
for (size_t i = 0; i != line_len; i++) {
|
||||
((uint32_t *) (this->draw_buf_.buf1))[i] = random_uint32();
|
||||
}
|
||||
lv_area_t area;
|
||||
area.x1 = 0;
|
||||
area.x2 = this->disp_drv_.hor_res - 1;
|
||||
if (this->snow_line_ == this->disp_drv_.ver_res / 2) {
|
||||
area.y1 = static_cast<lv_coord_t>(random_uint32() % (this->disp_drv_.ver_res / 2) * 2);
|
||||
} else {
|
||||
area.y1 = this->snow_line_++ * 2;
|
||||
}
|
||||
// write 2 lines
|
||||
area.y2 = area.y1 + 1;
|
||||
this->draw_buffer_(&area, (const uint8_t *) this->draw_buf_.buf1);
|
||||
}
|
||||
|
||||
void LvglComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "LVGL Setup starts");
|
||||
#if LV_USE_LOG
|
||||
|
@ -74,10 +96,53 @@ void LvglComponent::setup() {
|
|||
ESP_LOGV(TAG, "sw_rotate = %d, rotated=%d", this->disp_drv_.sw_rotate, this->disp_drv_.rotated);
|
||||
this->disp_ = lv_disp_drv_register(&this->disp_drv_);
|
||||
for (const auto &v : this->init_lambdas_)
|
||||
v(this->disp_);
|
||||
v(this);
|
||||
lv_disp_trig_activity(this->disp_);
|
||||
ESP_LOGCONFIG(TAG, "LVGL Setup complete");
|
||||
}
|
||||
|
||||
#ifdef USE_LVGL_IMAGE
|
||||
lv_img_dsc_t *lv_img_from(image::Image *src, lv_img_dsc_t *img_dsc) {
|
||||
if (img_dsc == nullptr)
|
||||
img_dsc = new lv_img_dsc_t(); // NOLINT
|
||||
img_dsc->header.always_zero = 0;
|
||||
img_dsc->header.reserved = 0;
|
||||
img_dsc->header.w = src->get_width();
|
||||
img_dsc->header.h = src->get_height();
|
||||
img_dsc->data = src->get_data_start();
|
||||
img_dsc->data_size = image_type_to_width_stride(img_dsc->header.w * img_dsc->header.h, src->get_type());
|
||||
switch (src->get_type()) {
|
||||
case image::IMAGE_TYPE_BINARY:
|
||||
img_dsc->header.cf = LV_IMG_CF_ALPHA_1BIT;
|
||||
break;
|
||||
|
||||
case image::IMAGE_TYPE_GRAYSCALE:
|
||||
img_dsc->header.cf = LV_IMG_CF_ALPHA_8BIT;
|
||||
break;
|
||||
|
||||
case image::IMAGE_TYPE_RGB24:
|
||||
img_dsc->header.cf = LV_IMG_CF_RGB888;
|
||||
break;
|
||||
|
||||
case image::IMAGE_TYPE_RGB565:
|
||||
#if LV_COLOR_DEPTH == 16
|
||||
img_dsc->header.cf = src->has_transparency() ? LV_IMG_CF_TRUE_COLOR_CHROMA_KEYED : LV_IMG_CF_TRUE_COLOR;
|
||||
#else
|
||||
img_dsc->header.cf = LV_IMG_CF_RGB565;
|
||||
#endif
|
||||
break;
|
||||
|
||||
case image::IMAGE_TYPE_RGBA:
|
||||
#if LV_COLOR_DEPTH == 32
|
||||
img_dsc->header.cf = LV_IMG_CF_TRUE_COLOR;
|
||||
#else
|
||||
img_dsc->header.cf = LV_IMG_CF_RGBA8888;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
return img_dsc;
|
||||
}
|
||||
#endif
|
||||
} // namespace lvgl
|
||||
} // namespace esphome
|
||||
|
||||
|
|
|
@ -1,23 +1,32 @@
|
|||
#pragma once
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_LVGL
|
||||
|
||||
#ifdef USE_LVGL_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif // USE_LVGL_BINARY_SENSOR
|
||||
#ifdef USE_LVGL_ROTARY_ENCODER
|
||||
#include "esphome/components/rotary_encoder/rotary_encoder.h"
|
||||
#endif // USE_LVGL_ROTARY_ENCODER
|
||||
|
||||
// required for clang-tidy
|
||||
#ifndef LV_CONF_H
|
||||
#define LV_CONF_SKIP 1 // NOLINT
|
||||
#endif
|
||||
#endif // LV_CONF_H
|
||||
|
||||
#include "esphome/components/display/display.h"
|
||||
#include "esphome/components/display/display_color_utils.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <lvgl.h>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#ifdef USE_LVGL_IMAGE
|
||||
#include "esphome/components/image/image.h"
|
||||
#endif // USE_LVGL_IMAGE
|
||||
|
||||
#ifdef USE_LVGL_FONT
|
||||
#include "esphome/components/font/font.h"
|
||||
#endif
|
||||
#endif // USE_LVGL_FONT
|
||||
#ifdef USE_LVGL_TOUCHSCREEN
|
||||
#include "esphome/components/touchscreen/touchscreen.h"
|
||||
#endif // USE_LVGL_TOUCHSCREEN
|
||||
|
@ -40,7 +49,7 @@ static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BIT
|
|||
// Parent class for things that wrap an LVGL object
|
||||
class LvCompound final {
|
||||
public:
|
||||
virtual void set_obj(lv_obj_t *lv_obj) { this->obj = lv_obj; }
|
||||
void set_obj(lv_obj_t *lv_obj) { this->obj = lv_obj; }
|
||||
lv_obj_t *obj{};
|
||||
};
|
||||
|
||||
|
@ -49,6 +58,15 @@ using set_value_lambda_t = std::function<void(float)>;
|
|||
using event_callback_t = void(_lv_event_t *);
|
||||
using text_lambda_t = std::function<const char *()>;
|
||||
|
||||
template<typename... Ts> class ObjUpdateAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit ObjUpdateAction(std::function<void(Ts...)> &&lamb) : lamb_(std::move(lamb)) {}
|
||||
|
||||
void play(Ts... x) override { this->lamb_(x...); }
|
||||
|
||||
protected:
|
||||
std::function<void(Ts...)> lamb_;
|
||||
};
|
||||
#ifdef USE_LVGL_FONT
|
||||
class FontEngine {
|
||||
public:
|
||||
|
@ -67,6 +85,9 @@ class FontEngine {
|
|||
lv_font_t lv_font_{};
|
||||
};
|
||||
#endif // USE_LVGL_FONT
|
||||
#ifdef USE_LVGL_IMAGE
|
||||
lv_img_dsc_t *lv_img_from(image::Image *src, lv_img_dsc_t *img_dsc = nullptr);
|
||||
#endif // USE_LVGL_IMAGE
|
||||
|
||||
class LvglComponent : public PollingComponent {
|
||||
constexpr static const char *const TAG = "lvgl";
|
||||
|
@ -92,27 +113,54 @@ class LvglComponent : public PollingComponent {
|
|||
area->y2++;
|
||||
}
|
||||
|
||||
void loop() override { lv_timer_handler_run_in_period(5); }
|
||||
void setup() override;
|
||||
|
||||
void update() override {}
|
||||
void update() override {
|
||||
// update indicators
|
||||
if (this->paused_) {
|
||||
return;
|
||||
}
|
||||
this->idle_callbacks_.call(lv_disp_get_inactive_time(this->disp_));
|
||||
}
|
||||
|
||||
void loop() override {
|
||||
if (this->paused_) {
|
||||
if (this->show_snow_)
|
||||
this->write_random_();
|
||||
}
|
||||
lv_timer_handler_run_in_period(5);
|
||||
}
|
||||
|
||||
void add_on_idle_callback(std::function<void(uint32_t)> &&callback) {
|
||||
this->idle_callbacks_.add(std::move(callback));
|
||||
}
|
||||
void add_display(display::Display *display) { this->displays_.push_back(display); }
|
||||
void add_init_lambda(const std::function<void(lv_disp_t *)> &lamb) { this->init_lambdas_.push_back(lamb); }
|
||||
void add_init_lambda(const std::function<void(LvglComponent *)> &lamb) { this->init_lambdas_.push_back(lamb); }
|
||||
void dump_config() override;
|
||||
void set_full_refresh(bool full_refresh) { this->full_refresh_ = full_refresh; }
|
||||
bool is_idle(uint32_t idle_ms) { return lv_disp_get_inactive_time(this->disp_) > idle_ms; }
|
||||
void set_buffer_frac(size_t frac) { this->buffer_frac_ = frac; }
|
||||
lv_disp_t *get_disp() { return this->disp_; }
|
||||
void set_paused(bool paused, bool show_snow) {
|
||||
this->paused_ = paused;
|
||||
this->show_snow_ = show_snow;
|
||||
this->snow_line_ = 0;
|
||||
if (!paused && lv_scr_act() != nullptr) {
|
||||
lv_disp_trig_activity(this->disp_); // resets the inactivity time
|
||||
lv_obj_invalidate(lv_scr_act());
|
||||
}
|
||||
}
|
||||
|
||||
void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event) {
|
||||
lv_obj_add_event_cb(obj, callback, event, this);
|
||||
if (event == LV_EVENT_VALUE_CHANGED) {
|
||||
lv_obj_add_event_cb(obj, callback, lv_custom_event, this);
|
||||
}
|
||||
}
|
||||
bool is_paused() const { return this->paused_; }
|
||||
|
||||
protected:
|
||||
void write_random_();
|
||||
void draw_buffer_(const lv_area_t *area, const uint8_t *ptr);
|
||||
void flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p);
|
||||
std::vector<display::Display *> displays_{};
|
||||
|
@ -120,12 +168,52 @@ class LvglComponent : public PollingComponent {
|
|||
lv_disp_drv_t disp_drv_{};
|
||||
lv_disp_t *disp_{};
|
||||
bool paused_{};
|
||||
bool show_snow_{};
|
||||
lv_coord_t snow_line_{};
|
||||
|
||||
std::vector<std::function<void(lv_disp_t *)>> init_lambdas_;
|
||||
std::vector<std::function<void(LvglComponent *lv_component)>> init_lambdas_;
|
||||
CallbackManager<void(uint32_t)> idle_callbacks_{};
|
||||
size_t buffer_frac_{1};
|
||||
bool full_refresh_{};
|
||||
};
|
||||
|
||||
class IdleTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit 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()) {
|
||||
this->is_idle_ = true;
|
||||
this->trigger();
|
||||
} else if (this->is_idle_ && idle_time < this->timeout_.value()) {
|
||||
this->is_idle_ = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
TemplatableValue<uint32_t> timeout_;
|
||||
bool is_idle_{};
|
||||
};
|
||||
|
||||
template<typename... Ts> class LvglAction : public Action<Ts...>, public Parented<LvglComponent> {
|
||||
public:
|
||||
explicit LvglAction(std::function<void(LvglComponent *)> &&lamb) : action_(std::move(lamb)) {}
|
||||
void play(Ts... x) override { this->action_(this->parent_); }
|
||||
|
||||
protected:
|
||||
std::function<void(LvglComponent *)> action_{};
|
||||
};
|
||||
|
||||
template<typename... Ts> class LvglCondition : public Condition<Ts...>, public Parented<LvglComponent> {
|
||||
public:
|
||||
LvglCondition(std::function<bool(LvglComponent *)> &&condition_lambda)
|
||||
: condition_lambda_(std::move(condition_lambda)) {}
|
||||
bool check(Ts... x) override { return this->condition_lambda_(this->parent_); }
|
||||
|
||||
protected:
|
||||
std::function<bool(LvglComponent *)> condition_lambda_{};
|
||||
};
|
||||
|
||||
#ifdef USE_LVGL_TOUCHSCREEN
|
||||
class LVTouchListener : public touchscreen::TouchListener, public Parented<LvglComponent> {
|
||||
public:
|
||||
|
@ -160,7 +248,62 @@ class LVTouchListener : public touchscreen::TouchListener, public Parented<LvglC
|
|||
bool touch_pressed_{};
|
||||
};
|
||||
#endif // USE_LVGL_TOUCHSCREEN
|
||||
|
||||
#ifdef USE_LVGL_KEY_LISTENER
|
||||
class LVEncoderListener : public Parented<LvglComponent> {
|
||||
public:
|
||||
LVEncoderListener(lv_indev_type_t type, uint16_t lpt, uint16_t lprt) {
|
||||
lv_indev_drv_init(&this->drv_);
|
||||
this->drv_.type = type;
|
||||
this->drv_.user_data = this;
|
||||
this->drv_.long_press_time = lpt;
|
||||
this->drv_.long_press_repeat_time = lprt;
|
||||
this->drv_.read_cb = [](lv_indev_drv_t *d, lv_indev_data_t *data) {
|
||||
auto *l = static_cast<LVEncoderListener *>(d->user_data);
|
||||
data->state = l->pressed_ ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
|
||||
data->key = l->key_;
|
||||
data->enc_diff = (int16_t) (l->count_ - l->last_count_);
|
||||
l->last_count_ = l->count_;
|
||||
data->continue_reading = false;
|
||||
};
|
||||
}
|
||||
|
||||
void set_left_button(binary_sensor::BinarySensor *left_button) {
|
||||
left_button->add_on_state_callback([this](bool state) { this->event(LV_KEY_LEFT, state); });
|
||||
}
|
||||
void set_right_button(binary_sensor::BinarySensor *right_button) {
|
||||
right_button->add_on_state_callback([this](bool state) { this->event(LV_KEY_RIGHT, state); });
|
||||
}
|
||||
|
||||
void set_enter_button(binary_sensor::BinarySensor *enter_button) {
|
||||
enter_button->add_on_state_callback([this](bool state) { this->event(LV_KEY_ENTER, state); });
|
||||
}
|
||||
|
||||
void set_sensor(rotary_encoder::RotaryEncoderSensor *sensor) {
|
||||
sensor->register_listener([this](int32_t count) { this->set_count(count); });
|
||||
}
|
||||
|
||||
void event(int key, bool pressed) {
|
||||
if (!this->parent_->is_paused()) {
|
||||
this->pressed_ = pressed;
|
||||
this->key_ = key;
|
||||
}
|
||||
}
|
||||
|
||||
void set_count(int32_t count) {
|
||||
if (!this->parent_->is_paused())
|
||||
this->count_ = count;
|
||||
}
|
||||
|
||||
lv_indev_drv_t *get_drv() { return &this->drv_; }
|
||||
|
||||
protected:
|
||||
lv_indev_drv_t drv_{};
|
||||
bool pressed_{};
|
||||
int32_t count_{};
|
||||
int32_t last_count_{};
|
||||
int key_{};
|
||||
};
|
||||
#endif // USE_LVGL_KEY_LISTENER
|
||||
} // namespace lvgl
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_LVGL
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
from esphome import automation
|
||||
|
||||
from .automation import update_to_code
|
||||
from .defines import CONF_MAIN, CONF_OBJ
|
||||
from .types import WidgetType, lv_obj_t
|
||||
from .schemas import create_modify_schema
|
||||
from .types import ObjUpdateAction, WidgetType, lv_obj_t
|
||||
|
||||
|
||||
class ObjType(WidgetType):
|
||||
|
@ -15,3 +19,10 @@ class ObjType(WidgetType):
|
|||
|
||||
|
||||
obj_spec = ObjType()
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"lvgl.widget.update", ObjUpdateAction, create_modify_schema(obj_spec)
|
||||
)
|
||||
async def obj_update_to_code(config, action_id, template_arg, args):
|
||||
return await update_to_code(config, action_id, template_arg, args)
|
||||
|
|
62
esphome/components/lvgl/rotary_encoders.py
Normal file
62
esphome/components/lvgl/rotary_encoders.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
import esphome.codegen as cg
|
||||
from esphome.components.binary_sensor import BinarySensor
|
||||
from esphome.components.rotary_encoder.sensor import RotaryEncoderSensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_GROUP, CONF_ID, CONF_SENSOR
|
||||
|
||||
from .defines import (
|
||||
CONF_ENTER_BUTTON,
|
||||
CONF_LEFT_BUTTON,
|
||||
CONF_LONG_PRESS_REPEAT_TIME,
|
||||
CONF_LONG_PRESS_TIME,
|
||||
CONF_RIGHT_BUTTON,
|
||||
CONF_ROTARY_ENCODERS,
|
||||
)
|
||||
from .helpers import lvgl_components_required
|
||||
from .lvcode import add_group, lv, lv_add, lv_expr
|
||||
from .schemas import ENCODER_SCHEMA
|
||||
from .types import lv_indev_type_t
|
||||
|
||||
ROTARY_ENCODER_CONFIG = cv.ensure_list(
|
||||
ENCODER_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ENTER_BUTTON): cv.use_id(BinarySensor),
|
||||
cv.Required(CONF_SENSOR): cv.Any(
|
||||
cv.use_id(RotaryEncoderSensor),
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_LEFT_BUTTON): cv.use_id(BinarySensor),
|
||||
cv.Required(CONF_RIGHT_BUTTON): cv.use_id(BinarySensor),
|
||||
}
|
||||
),
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def rotary_encoders_to_code(var, config):
|
||||
for enc_conf in config.get(CONF_ROTARY_ENCODERS, ()):
|
||||
lvgl_components_required.add("KEY_LISTENER")
|
||||
lvgl_components_required.add("ROTARY_ENCODER")
|
||||
lpt = enc_conf[CONF_LONG_PRESS_TIME].total_milliseconds
|
||||
lprt = enc_conf[CONF_LONG_PRESS_REPEAT_TIME].total_milliseconds
|
||||
listener = cg.new_Pvariable(
|
||||
enc_conf[CONF_ID], lv_indev_type_t.LV_INDEV_TYPE_ENCODER, lpt, lprt
|
||||
)
|
||||
await cg.register_parented(listener, var)
|
||||
if sensor_config := enc_conf.get(CONF_SENSOR):
|
||||
if isinstance(sensor_config, dict):
|
||||
b_sensor = await cg.get_variable(sensor_config[CONF_LEFT_BUTTON])
|
||||
cg.add(listener.set_left_button(b_sensor))
|
||||
b_sensor = await cg.get_variable(sensor_config[CONF_RIGHT_BUTTON])
|
||||
cg.add(listener.set_right_button(b_sensor))
|
||||
else:
|
||||
sensor_config = await cg.get_variable(sensor_config)
|
||||
lv_add(listener.set_sensor(sensor_config))
|
||||
b_sensor = await cg.get_variable(enc_conf[CONF_ENTER_BUTTON])
|
||||
cg.add(listener.set_enter_button(b_sensor))
|
||||
if group := add_group(enc_conf.get(CONF_GROUP)):
|
||||
lv.indev_set_group(lv_expr.indev_drv_register(listener.get_drv()), group)
|
||||
else:
|
||||
lv.indev_drv_register(listener.get_drv())
|
|
@ -1,10 +1,21 @@
|
|||
from esphome import config_validation as cv
|
||||
from esphome.const import CONF_ARGS, CONF_FORMAT, CONF_ID, CONF_STATE, CONF_TYPE
|
||||
from esphome.automation import Trigger, validate_automation
|
||||
from esphome.const import (
|
||||
CONF_ARGS,
|
||||
CONF_FORMAT,
|
||||
CONF_GROUP,
|
||||
CONF_ID,
|
||||
CONF_ON_VALUE,
|
||||
CONF_STATE,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_TYPE,
|
||||
)
|
||||
from esphome.core import TimePeriod
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT
|
||||
|
||||
from . import defines as df, lv_validation as lvalid, types as ty
|
||||
from .helpers import add_lv_use, requires_component, validate_printf
|
||||
from .lv_validation import lv_font
|
||||
from .lv_validation import id_name, lv_font
|
||||
from .types import WIDGET_TYPES, WidgetType
|
||||
|
||||
# A schema for text properties
|
||||
|
@ -27,6 +38,28 @@ TEXT_SCHEMA = cv.Schema(
|
|||
}
|
||||
)
|
||||
|
||||
ACTION_SCHEMA = cv.maybe_simple_value(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(ty.lv_pseudo_button_t),
|
||||
},
|
||||
key=CONF_ID,
|
||||
)
|
||||
|
||||
PRESS_TIME = cv.All(
|
||||
lvalid.lv_milliseconds, cv.Range(max=TimePeriod(milliseconds=65535))
|
||||
)
|
||||
|
||||
ENCODER_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.All(
|
||||
cv.declare_id(ty.LVEncoderListener), requires_component("binary_sensor")
|
||||
),
|
||||
cv.Optional(CONF_GROUP): lvalid.id_name,
|
||||
cv.Optional(df.CONF_LONG_PRESS_TIME, default="400ms"): PRESS_TIME,
|
||||
cv.Optional(df.CONF_LONG_PRESS_REPEAT_TIME, default="100ms"): PRESS_TIME,
|
||||
}
|
||||
)
|
||||
|
||||
# All LVGL styles and their validators
|
||||
STYLE_PROPS = {
|
||||
"align": df.CHILD_ALIGNMENTS.one_of,
|
||||
|
@ -43,6 +76,7 @@ STYLE_PROPS = {
|
|||
"bg_image_opa": lvalid.opacity,
|
||||
"bg_image_recolor": lvalid.lv_color,
|
||||
"bg_image_recolor_opa": lvalid.opacity,
|
||||
"bg_image_src": lvalid.lv_image,
|
||||
"bg_main_stop": lvalid.stop_value,
|
||||
"bg_opa": lvalid.opacity,
|
||||
"border_color": lvalid.lv_color,
|
||||
|
@ -151,6 +185,39 @@ def part_schema(widget_type: WidgetType):
|
|||
)
|
||||
|
||||
|
||||
def automation_schema(typ: ty.LvType):
|
||||
if typ.has_on_value:
|
||||
events = df.LV_EVENT_TRIGGERS + (CONF_ON_VALUE,)
|
||||
else:
|
||||
events = df.LV_EVENT_TRIGGERS
|
||||
if isinstance(typ, ty.LvType):
|
||||
template = Trigger.template(typ.get_arg_type())
|
||||
else:
|
||||
template = Trigger.template()
|
||||
return {
|
||||
cv.Optional(event): validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(template),
|
||||
}
|
||||
)
|
||||
for event in events
|
||||
}
|
||||
|
||||
|
||||
def create_modify_schema(widget_type):
|
||||
return (
|
||||
part_schema(widget_type)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(widget_type),
|
||||
cv.Optional(CONF_STATE): SET_STATE_SCHEMA,
|
||||
}
|
||||
)
|
||||
.extend(FLAG_SCHEMA)
|
||||
.extend(widget_type.modify_schema)
|
||||
)
|
||||
|
||||
|
||||
def obj_schema(widget_type: WidgetType):
|
||||
"""
|
||||
Create a schema for a widget type itself i.e. no allowance for children
|
||||
|
@ -161,10 +228,12 @@ def obj_schema(widget_type: WidgetType):
|
|||
part_schema(widget_type)
|
||||
.extend(FLAG_SCHEMA)
|
||||
.extend(ALIGN_TO_SCHEMA)
|
||||
.extend(automation_schema(widget_type.w_type))
|
||||
.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_STATE): SET_STATE_SCHEMA,
|
||||
cv.Optional(CONF_GROUP): id_name,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
@ -188,6 +257,13 @@ STYLED_TEXT_SCHEMA = cv.maybe_simple_value(
|
|||
STYLE_SCHEMA.extend(TEXT_SCHEMA), key=df.CONF_TEXT
|
||||
)
|
||||
|
||||
# For use by platform components
|
||||
LVGL_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(df.CONF_LVGL_ID): cv.use_id(ty.LvglComponent),
|
||||
}
|
||||
)
|
||||
|
||||
ALL_STYLES = {
|
||||
**STYLE_PROPS,
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import esphome.codegen as cg
|
|||
from esphome.components.touchscreen import CONF_TOUCHSCREEN_ID, Touchscreen
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.core import CORE, TimePeriod
|
||||
from esphome.core import CORE
|
||||
|
||||
from .defines import (
|
||||
CONF_LONG_PRESS_REPEAT_TIME,
|
||||
|
@ -10,11 +10,10 @@ from .defines import (
|
|||
CONF_TOUCHSCREENS,
|
||||
)
|
||||
from .helpers import lvgl_components_required
|
||||
from .lv_validation import lv_milliseconds
|
||||
from .lvcode import lv
|
||||
from .schemas import PRESS_TIME
|
||||
from .types import LVTouchListener
|
||||
|
||||
PRESS_TIME = cv.All(lv_milliseconds, cv.Range(max=TimePeriod(milliseconds=65535)))
|
||||
CONF_TOUCHSCREEN = "touchscreen"
|
||||
TOUCHSCREENS_CONFIG = cv.maybe_simple_value(
|
||||
{
|
||||
|
|
61
esphome/components/lvgl/trigger.py
Normal file
61
esphome/components/lvgl/trigger.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_ID, CONF_ON_VALUE, CONF_TRIGGER_ID
|
||||
|
||||
from .defines import (
|
||||
CONF_ALIGN,
|
||||
CONF_ALIGN_TO,
|
||||
CONF_X,
|
||||
CONF_Y,
|
||||
LV_EVENT,
|
||||
LV_EVENT_TRIGGERS,
|
||||
literal,
|
||||
)
|
||||
from .lvcode import LambdaContext, add_line_marks, lv, lv_add
|
||||
from .widget import widget_map
|
||||
|
||||
lv_event_t_ptr = cg.global_ns.namespace("lv_event_t").operator("ptr")
|
||||
|
||||
|
||||
async def generate_triggers(lv_component):
|
||||
"""
|
||||
Generate LVGL triggers for all defined widgets
|
||||
Must be done after all widgets completed
|
||||
:param lv_component: The parent component
|
||||
:return:
|
||||
"""
|
||||
|
||||
for w in widget_map.values():
|
||||
if w.config:
|
||||
for event, conf in {
|
||||
event: conf
|
||||
for event, conf in w.config.items()
|
||||
if event in LV_EVENT_TRIGGERS
|
||||
}.items():
|
||||
conf = conf[0]
|
||||
w.add_flag("LV_OBJ_FLAG_CLICKABLE")
|
||||
event = "LV_EVENT_" + LV_EVENT[event[3:].upper()]
|
||||
await add_trigger(conf, event, lv_component, w)
|
||||
for conf in w.config.get(CONF_ON_VALUE, ()):
|
||||
await add_trigger(conf, "LV_EVENT_VALUE_CHANGED", lv_component, w)
|
||||
|
||||
# Generate align to directives while we're here
|
||||
if align_to := w.config.get(CONF_ALIGN_TO):
|
||||
target = widget_map[align_to[CONF_ID]].obj
|
||||
align = align_to[CONF_ALIGN]
|
||||
x = align_to[CONF_X]
|
||||
y = align_to[CONF_Y]
|
||||
lv.obj_align_to(w.obj, target, align, x, y)
|
||||
|
||||
|
||||
async def add_trigger(conf, event, lv_component, w):
|
||||
tid = conf[CONF_TRIGGER_ID]
|
||||
add_line_marks(tid)
|
||||
trigger = cg.new_Pvariable(tid)
|
||||
args = w.get_args()
|
||||
value = w.get_value()
|
||||
await automation.build_automation(trigger, args, conf)
|
||||
with LambdaContext([(lv_event_t_ptr, "event_data")]) as context:
|
||||
add_line_marks(tid)
|
||||
lv_add(trigger.trigger(value))
|
||||
lv_add(lv_component.add_event_cb(w.obj, await context.get_lambda(), literal(event)))
|
|
@ -1,4 +1,4 @@
|
|||
from esphome import codegen as cg
|
||||
from esphome import automation, codegen as cg
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
|
||||
|
@ -23,8 +23,14 @@ lvgl_ns = cg.esphome_ns.namespace("lvgl")
|
|||
char_ptr = cg.global_ns.namespace("char").operator("ptr")
|
||||
void_ptr = cg.void.operator("ptr")
|
||||
LvglComponent = lvgl_ns.class_("LvglComponent", cg.PollingComponent)
|
||||
LvglComponentPtr = LvglComponent.operator("ptr")
|
||||
lv_event_code_t = cg.global_ns.namespace("lv_event_code_t")
|
||||
lv_indev_type_t = cg.global_ns.enum("lv_indev_type_t")
|
||||
FontEngine = lvgl_ns.class_("FontEngine")
|
||||
IdleTrigger = lvgl_ns.class_("IdleTrigger", automation.Trigger.template())
|
||||
ObjUpdateAction = lvgl_ns.class_("ObjUpdateAction", automation.Action)
|
||||
LvglCondition = lvgl_ns.class_("LvglCondition", automation.Condition)
|
||||
LvglAction = lvgl_ns.class_("LvglAction", automation.Action)
|
||||
LvCompound = lvgl_ns.class_("LvCompound")
|
||||
lv_font_t = cg.global_ns.class_("lv_font_t")
|
||||
lv_style_t = cg.global_ns.struct("lv_style_t")
|
||||
|
@ -33,9 +39,11 @@ lv_obj_base_t = cg.global_ns.class_("lv_obj_t", lv_pseudo_button_t)
|
|||
lv_obj_t_ptr = lv_obj_base_t.operator("ptr")
|
||||
lv_disp_t_ptr = cg.global_ns.struct("lv_disp_t").operator("ptr")
|
||||
lv_color_t = cg.global_ns.struct("lv_color_t")
|
||||
lv_group_t = cg.global_ns.struct("lv_group_t")
|
||||
LVTouchListener = lvgl_ns.class_("LVTouchListener")
|
||||
LVEncoderListener = lvgl_ns.class_("LVEncoderListener")
|
||||
lv_obj_t = LvType("lv_obj_t")
|
||||
lv_img_t = LvType("lv_img_t")
|
||||
|
||||
|
||||
# this will be populated later, in __init__.py to avoid circular imports.
|
||||
|
@ -58,7 +66,7 @@ class LvBoolean(LvType):
|
|||
super().__init__(
|
||||
*args,
|
||||
largs=[(cg.bool_, "x")],
|
||||
lvalue=lambda w: w.is_checked(),
|
||||
lvalue=lambda w: w.has_state("LV_STATE_CHECKED"),
|
||||
has_on_value=True,
|
||||
**kwargs,
|
||||
)
|
||||
|
@ -83,11 +91,14 @@ class WidgetType:
|
|||
self.name = name
|
||||
self.w_type = w_type
|
||||
self.parts = parts
|
||||
self.schema = schema or {}
|
||||
if modify_schema is None:
|
||||
self.modify_schema = schema
|
||||
if schema is None:
|
||||
self.schema = {}
|
||||
else:
|
||||
self.modify_schema = modify_schema
|
||||
self.schema = schema
|
||||
if modify_schema is None:
|
||||
self.modify_schema = self.schema
|
||||
else:
|
||||
self.modify_schema = self.schema
|
||||
|
||||
@property
|
||||
def animated(self):
|
||||
|
|
|
@ -4,9 +4,9 @@ from typing import Any
|
|||
from esphome import codegen as cg, config_validation as cv
|
||||
from esphome.config_validation import Invalid
|
||||
from esphome.const import CONF_GROUP, CONF_ID, CONF_STATE
|
||||
from esphome.core import ID, TimePeriod
|
||||
from esphome.core import CORE, TimePeriod
|
||||
from esphome.coroutine import FakeAwaitable
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_generator import MockObj, MockObjClass, VariableDeclarationExpression
|
||||
|
||||
from .defines import (
|
||||
CONF_DEFAULT,
|
||||
|
@ -16,13 +16,15 @@ from .defines import (
|
|||
OBJ_FLAGS,
|
||||
PARTS,
|
||||
STATES,
|
||||
ConstantLiteral,
|
||||
LValidator,
|
||||
join_enums,
|
||||
literal,
|
||||
)
|
||||
from .helpers import add_lv_use
|
||||
from .lvcode import ConstantLiteral, add_line_marks, lv, lv_add, lv_assign, lv_obj
|
||||
from .lvcode import add_group, add_line_marks, lv, lv_add, lv_assign, lv_expr, lv_obj
|
||||
from .schemas import ALL_STYLES, STYLE_REMAP
|
||||
from .types import WIDGET_TYPES, WidgetType, lv_obj_t
|
||||
from .types import WIDGET_TYPES, LvType, WidgetType, lv_obj_t, lv_obj_t_ptr
|
||||
|
||||
EVENT_LAMB = "event_lamb__"
|
||||
|
||||
|
@ -76,17 +78,20 @@ class Widget:
|
|||
return f"{self.var}->obj"
|
||||
return self.var
|
||||
|
||||
def add_state(self, *args):
|
||||
return lv_obj.add_state(self.obj, *args)
|
||||
def add_state(self, state):
|
||||
return lv_obj.add_state(self.obj, literal(state))
|
||||
|
||||
def clear_state(self, *args):
|
||||
return lv_obj.clear_state(self.obj, *args)
|
||||
def clear_state(self, state):
|
||||
return lv_obj.clear_state(self.obj, literal(state))
|
||||
|
||||
def add_flag(self, *args):
|
||||
return lv_obj.add_flag(self.obj, *args)
|
||||
def has_state(self, state):
|
||||
return lv_expr.obj_get_state(self.obj) & literal(state) != 0
|
||||
|
||||
def clear_flag(self, *args):
|
||||
return lv_obj.clear_flag(self.obj, *args)
|
||||
def add_flag(self, flag):
|
||||
return lv_obj.add_flag(self.obj, literal(flag))
|
||||
|
||||
def clear_flag(self, flag):
|
||||
return lv_obj.clear_flag(self.obj, literal(flag))
|
||||
|
||||
def set_property(self, prop, value, animated: bool = None, ltype=None):
|
||||
if isinstance(value, dict):
|
||||
|
@ -125,6 +130,16 @@ class Widget:
|
|||
def __str__(self):
|
||||
return f"({self.var}, {self.type})"
|
||||
|
||||
def get_args(self):
|
||||
if isinstance(self.type.w_type, LvType):
|
||||
return self.type.w_type.args
|
||||
return [(lv_obj_t_ptr, "obj")]
|
||||
|
||||
def get_value(self):
|
||||
if isinstance(self.type.w_type, LvType):
|
||||
return self.type.w_type.value(self)
|
||||
return self.obj
|
||||
|
||||
|
||||
# Map of widgets to their config, used for trigger generation
|
||||
widget_map: dict[Any, Widget] = {}
|
||||
|
@ -146,7 +161,8 @@ def get_widget_generator(wid):
|
|||
yield
|
||||
|
||||
|
||||
async def get_widget(wid: ID) -> Widget:
|
||||
async def get_widget(config: dict, id: str = CONF_ID) -> Widget:
|
||||
wid = config[id]
|
||||
if obj := widget_map.get(wid):
|
||||
return obj
|
||||
return await FakeAwaitable(get_widget_generator(wid))
|
||||
|
@ -204,9 +220,10 @@ async def set_obj_properties(w: Widget, config):
|
|||
}.items():
|
||||
if isinstance(ALL_STYLES[prop], LValidator):
|
||||
value = await ALL_STYLES[prop].process(value)
|
||||
# Remapping for backwards compatibility of style names
|
||||
prop_r = STYLE_REMAP.get(prop, prop)
|
||||
w.set_style(prop_r, value, lv_state)
|
||||
if group := add_group(config.get(CONF_GROUP)):
|
||||
lv.group_add_obj(group, w.obj)
|
||||
flag_clr = set()
|
||||
flag_set = set()
|
||||
props = parts[CONF_MAIN][CONF_DEFAULT]
|
||||
|
@ -241,7 +258,7 @@ async def set_obj_properties(w: Widget, config):
|
|||
w.clear_state(clears)
|
||||
for key, value in lambs.items():
|
||||
lamb = await cg.process_lambda(value, [], return_type=cg.bool_)
|
||||
state = ConstantLiteral(f"LV_STATE_{key.upper}")
|
||||
state = f"LV_STATE_{key.upper}"
|
||||
lv.cond_if(lamb)
|
||||
w.add_state(state)
|
||||
lv.cond_else()
|
||||
|
@ -281,10 +298,19 @@ async def widget_to_code(w_cnfig, w_type, parent):
|
|||
var = cg.new_Pvariable(wid)
|
||||
lv_add(var.set_obj(creator))
|
||||
else:
|
||||
var = cg.Pvariable(wid, cg.nullptr, type_=lv_obj_t)
|
||||
var = MockObj(wid, "->")
|
||||
decl = VariableDeclarationExpression(lv_obj_t, "*", wid)
|
||||
CORE.add_global(decl)
|
||||
CORE.register_variable(wid, var)
|
||||
lv_assign(var, creator)
|
||||
|
||||
widget = Widget.create(wid, var, spec, w_cnfig, parent)
|
||||
await set_obj_properties(widget, w_cnfig)
|
||||
await add_widgets(widget, w_cnfig)
|
||||
await spec.to_code(widget, w_cnfig)
|
||||
|
||||
|
||||
lv_scr_act_spec = LvScrActType()
|
||||
lv_scr_act = Widget.create(
|
||||
None, ConstantLiteral("lv_scr_act()"), lv_scr_act_spec, {}, parent=None
|
||||
)
|
||||
|
|
|
@ -8,6 +8,7 @@ static const char *const TAG = "matrix_keypad";
|
|||
|
||||
void MatrixKeypad::setup() {
|
||||
for (auto *pin : this->rows_) {
|
||||
pin->setup();
|
||||
if (!has_diodes_) {
|
||||
pin->pin_mode(gpio::FLAG_INPUT);
|
||||
} else {
|
||||
|
@ -15,6 +16,7 @@ void MatrixKeypad::setup() {
|
|||
}
|
||||
}
|
||||
for (auto *pin : this->columns_) {
|
||||
pin->setup();
|
||||
if (has_pulldowns_) {
|
||||
pin->pin_mode(gpio::FLAG_INPUT);
|
||||
} else {
|
||||
|
|
|
@ -148,7 +148,7 @@ WakeWordModel::WakeWordModel(const uint8_t *model_start, float probability_cutof
|
|||
};
|
||||
|
||||
bool WakeWordModel::determine_detected() {
|
||||
int32_t sum = 0;
|
||||
uint32_t sum = 0;
|
||||
for (auto &prob : this->recent_streaming_probabilities_) {
|
||||
sum += prob;
|
||||
}
|
||||
|
@ -175,12 +175,14 @@ VADModel::VADModel(const uint8_t *model_start, float probability_cutoff, size_t
|
|||
};
|
||||
|
||||
bool VADModel::determine_detected() {
|
||||
uint8_t max = 0;
|
||||
uint32_t sum = 0;
|
||||
for (auto &prob : this->recent_streaming_probabilities_) {
|
||||
max = std::max(prob, max);
|
||||
sum += prob;
|
||||
}
|
||||
|
||||
return max > this->probability_cutoff_;
|
||||
float sliding_window_average = static_cast<float>(sum) / static_cast<float>(255 * this->sliding_window_size_);
|
||||
|
||||
return sliding_window_average > this->probability_cutoff_;
|
||||
}
|
||||
|
||||
} // namespace micro_wake_word
|
||||
|
|
|
@ -17,6 +17,7 @@ from esphome.const import (
|
|||
CONF_ICON,
|
||||
CONF_ID,
|
||||
CONF_IGNORE_OUT_OF_RANGE,
|
||||
CONF_MULTIPLE,
|
||||
CONF_ON_RAW_VALUE,
|
||||
CONF_ON_VALUE,
|
||||
CONF_ON_VALUE_RANGE,
|
||||
|
@ -249,6 +250,7 @@ CalibratePolynomialFilter = sensor_ns.class_("CalibratePolynomialFilter", Filter
|
|||
SensorInRangeCondition = sensor_ns.class_("SensorInRangeCondition", Filter)
|
||||
ClampFilter = sensor_ns.class_("ClampFilter", Filter)
|
||||
RoundFilter = sensor_ns.class_("RoundFilter", Filter)
|
||||
RoundMultipleFilter = sensor_ns.class_("RoundMultipleFilter", Filter)
|
||||
|
||||
validate_unit_of_measurement = cv.string_strict
|
||||
validate_accuracy_decimals = cv.int_
|
||||
|
@ -734,6 +736,23 @@ async def round_filter_to_code(config, filter_id):
|
|||
)
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register(
|
||||
"round_to_multiple_of",
|
||||
RoundMultipleFilter,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.Required(CONF_MULTIPLE): cv.positive_not_null_float,
|
||||
},
|
||||
key=CONF_MULTIPLE,
|
||||
),
|
||||
)
|
||||
async def round_multiple_filter_to_code(config, filter_id):
|
||||
return cg.new_Pvariable(
|
||||
filter_id,
|
||||
config[CONF_MULTIPLE],
|
||||
)
|
||||
|
||||
|
||||
async def build_filters(config):
|
||||
return await cg.build_registry_list(FILTER_REGISTRY, config)
|
||||
|
||||
|
|
|
@ -472,5 +472,13 @@ optional<float> RoundFilter::new_value(float value) {
|
|||
return value;
|
||||
}
|
||||
|
||||
RoundMultipleFilter::RoundMultipleFilter(float multiple) : multiple_(multiple) {}
|
||||
optional<float> RoundMultipleFilter::new_value(float value) {
|
||||
if (std::isfinite(value)) {
|
||||
return value - remainderf(value, this->multiple_);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace sensor
|
||||
} // namespace esphome
|
||||
|
|
|
@ -431,5 +431,14 @@ class RoundFilter : public Filter {
|
|||
uint8_t precision_;
|
||||
};
|
||||
|
||||
class RoundMultipleFilter : public Filter {
|
||||
public:
|
||||
explicit RoundMultipleFilter(float multiple);
|
||||
optional<float> new_value(float value) override;
|
||||
|
||||
protected:
|
||||
float multiple_;
|
||||
};
|
||||
|
||||
} // namespace sensor
|
||||
} // namespace esphome
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import mqtt, web_server
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import (
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_FORCE_UPDATE,
|
||||
CONF_ID,
|
||||
CONF_MQTT_ID,
|
||||
CONF_WEB_SERVER_ID,
|
||||
|
@ -23,8 +24,12 @@ UpdateEntity = update_ns.class_("UpdateEntity", cg.EntityBase)
|
|||
|
||||
UpdateInfo = update_ns.struct("UpdateInfo")
|
||||
|
||||
PerformAction = update_ns.class_("PerformAction", automation.Action)
|
||||
IsAvailableCondition = update_ns.class_("IsAvailableCondition", automation.Condition)
|
||||
PerformAction = update_ns.class_(
|
||||
"PerformAction", automation.Action, cg.Parented.template(UpdateEntity)
|
||||
)
|
||||
IsAvailableCondition = update_ns.class_(
|
||||
"IsAvailableCondition", automation.Condition, cg.Parented.template(UpdateEntity)
|
||||
)
|
||||
|
||||
DEVICE_CLASSES = [
|
||||
DEVICE_CLASS_EMPTY,
|
||||
|
@ -92,24 +97,37 @@ async def to_code(config):
|
|||
cg.add_global(update_ns.using)
|
||||
|
||||
|
||||
UPDATE_AUTOMATION_SCHEMA = cv.Schema(
|
||||
@automation.register_action(
|
||||
"update.perform",
|
||||
PerformAction,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(UpdateEntity),
|
||||
cv.Optional(CONF_FORCE_UPDATE, default=False): cv.templatable(cv.boolean),
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action("update.perform", PerformAction, UPDATE_AUTOMATION_SCHEMA)
|
||||
async def update_perform_action_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, paren, paren)
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
|
||||
force = await cg.templatable(config[CONF_FORCE_UPDATE], args, cg.bool_)
|
||||
cg.add(var.set_force(force))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"update.is_available", IsAvailableCondition, UPDATE_AUTOMATION_SCHEMA
|
||||
"update.is_available",
|
||||
IsAvailableCondition,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(UpdateEntity),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def update_is_available_condition_to_code(
|
||||
config, condition_id, template_arg, args
|
||||
):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, paren, paren)
|
||||
var = cg.new_Pvariable(condition_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
|
23
esphome/components/update/automation.h
Normal file
23
esphome/components/update/automation.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include "update_entity.h"
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace update {
|
||||
|
||||
template<typename... Ts> class PerformAction : public Action<Ts...>, public Parented<UpdateEntity> {
|
||||
TEMPLATABLE_VALUE(bool, force)
|
||||
|
||||
public:
|
||||
void play(Ts... x) override { this->parent_->perform(this->force_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsAvailableCondition : public Condition<Ts...>, public Parented<UpdateEntity> {
|
||||
public:
|
||||
bool check(Ts... x) override { return this->parent_->state == UPDATE_STATE_AVAILABLE; }
|
||||
};
|
||||
|
||||
} // namespace update
|
||||
} // namespace esphome
|
|
@ -32,7 +32,9 @@ class UpdateEntity : public EntityBase, public EntityBase_DeviceClass {
|
|||
|
||||
void publish_state();
|
||||
|
||||
virtual void perform() = 0;
|
||||
void perform() { this->perform(false); }
|
||||
|
||||
virtual void perform(bool force) = 0;
|
||||
|
||||
const UpdateInfo &update_info = update_info_;
|
||||
const UpdateState &state = state_;
|
||||
|
|
|
@ -464,6 +464,7 @@ zero_to_one_float = float_range(min=0, max=1)
|
|||
negative_one_to_one_float = float_range(min=-1, max=1)
|
||||
positive_int = int_range(min=0)
|
||||
positive_not_null_int = int_range(min=0, min_included=False)
|
||||
positive_not_null_float = float_range(min=0, min_included=False)
|
||||
|
||||
|
||||
def validate_id_name(value):
|
||||
|
@ -2181,3 +2182,13 @@ SOURCE_SCHEMA = Any(
|
|||
}
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def rename_key(old_key, new_key):
|
||||
def validator(config: dict) -> dict:
|
||||
config = config.copy()
|
||||
if old_key in config:
|
||||
config[new_key] = config.pop(old_key)
|
||||
return config
|
||||
|
||||
return validator
|
||||
|
|
|
@ -37,8 +37,10 @@ CONF_ACCELERATION_Y = "acceleration_y"
|
|||
CONF_ACCELERATION_Z = "acceleration_z"
|
||||
CONF_ACCURACY = "accuracy"
|
||||
CONF_ACCURACY_DECIMALS = "accuracy_decimals"
|
||||
CONF_ACTION = "action"
|
||||
CONF_ACTION_ID = "action_id"
|
||||
CONF_ACTION_STATE_TOPIC = "action_state_topic"
|
||||
CONF_ACTIONS = "actions"
|
||||
CONF_ACTIVE = "active"
|
||||
CONF_ACTIVE_POWER = "active_power"
|
||||
CONF_ACTUAL_GAIN = "actual_gain"
|
||||
|
@ -501,6 +503,7 @@ CONF_MOTION = "motion"
|
|||
CONF_MOVEMENT_COUNTER = "movement_counter"
|
||||
CONF_MQTT = "mqtt"
|
||||
CONF_MQTT_ID = "mqtt_id"
|
||||
CONF_MULTIPLE = "multiple"
|
||||
CONF_MULTIPLEXER = "multiplexer"
|
||||
CONF_MULTIPLY = "multiply"
|
||||
CONF_NAME = "name"
|
||||
|
|
|
@ -39,9 +39,12 @@
|
|||
#define USE_LOCK
|
||||
#define USE_LOGGER
|
||||
#define USE_LVGL
|
||||
#define USE_LVGL_BINARY_SENSOR
|
||||
#define USE_LVGL_FONT
|
||||
#define USE_LVGL_IMAGE
|
||||
#define USE_LVGL_KEY_LISTENER
|
||||
#define USE_LVGL_TOUCHSCREEN
|
||||
#define USE_LVGL_ROTARY_ENCODER
|
||||
#define USE_MDNS
|
||||
#define USE_MEDIA_PLAYER
|
||||
#define USE_MQTT
|
||||
|
|
|
@ -35,7 +35,7 @@ build_flags =
|
|||
lib_deps =
|
||||
esphome/noise-c@0.1.4 ; api
|
||||
makuna/NeoPixelBus@2.7.3 ; neopixelbus
|
||||
esphome/Improv@1.2.3 ; improv_serial / esp32_improv
|
||||
improv/Improv@1.2.4 ; improv_serial / esp32_improv
|
||||
bblanchon/ArduinoJson@6.18.5 ; json
|
||||
wjtje/qr-code-generator-library@1.7.0 ; qr_code
|
||||
functionpointer/arduino-MLX90393@1.0.0 ; mlx90393
|
||||
|
|
|
@ -5,8 +5,8 @@ esphome:
|
|||
event: esphome.button_pressed
|
||||
data:
|
||||
message: Button was pressed
|
||||
- homeassistant.service:
|
||||
service: notify.html5
|
||||
- homeassistant.action:
|
||||
action: notify.html5
|
||||
data:
|
||||
message: Button was pressed
|
||||
- homeassistant.tag_scanned: pulse
|
||||
|
@ -21,8 +21,8 @@ api:
|
|||
reboot_timeout: 0min
|
||||
encryption:
|
||||
key: bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU=
|
||||
services:
|
||||
- service: hello_world
|
||||
actions:
|
||||
- action: hello_world
|
||||
variables:
|
||||
name: string
|
||||
then:
|
||||
|
@ -30,10 +30,10 @@ api:
|
|||
format: Hello World %s!
|
||||
args:
|
||||
- name.c_str()
|
||||
- service: empty_service
|
||||
- action: empty_action
|
||||
then:
|
||||
- logger.log: Service Called
|
||||
- service: all_types
|
||||
- logger.log: Action Called
|
||||
- action: all_types
|
||||
variables:
|
||||
bool_: bool
|
||||
int_: int
|
||||
|
@ -41,7 +41,7 @@ api:
|
|||
string_: string
|
||||
then:
|
||||
- logger.log: Something happened
|
||||
- service: array_types
|
||||
- action: array_types
|
||||
variables:
|
||||
bool_arr: bool[]
|
||||
int_arr: int[]
|
||||
|
|
|
@ -13,12 +13,12 @@ esphome:
|
|||
message: The humidity is {{ my_variable }}%.
|
||||
variables:
|
||||
my_variable: "return id(ha_hello_world_temperature).state;"
|
||||
- homeassistant.service:
|
||||
service: notify.html5
|
||||
- homeassistant.action:
|
||||
action: notify.html5
|
||||
data:
|
||||
message: Button was pressed
|
||||
- homeassistant.service:
|
||||
service: notify.html5
|
||||
- homeassistant.action:
|
||||
action: notify.html5
|
||||
data:
|
||||
title: New Humidity
|
||||
data_template:
|
||||
|
|
|
@ -7,6 +7,7 @@ lvgl:
|
|||
long_press_time: 500ms
|
||||
widgets:
|
||||
- label:
|
||||
id: hello_label
|
||||
text: Hello world
|
||||
text_color: 0xFF8000
|
||||
align: center
|
||||
|
@ -95,9 +96,43 @@ lvgl:
|
|||
height: 10%
|
||||
pressed:
|
||||
bg_color: light_blue
|
||||
checkable: true
|
||||
checked:
|
||||
bg_color: 0x000000
|
||||
widgets:
|
||||
- label:
|
||||
text: Button
|
||||
on_click:
|
||||
lvgl.label.update:
|
||||
id: hello_label
|
||||
bg_color: 0x123456
|
||||
text: clicked
|
||||
on_value:
|
||||
logger.log:
|
||||
format: "state now %d"
|
||||
args: [x]
|
||||
on_short_click:
|
||||
lvgl.widget.hide: hello_label
|
||||
on_long_press:
|
||||
lvgl.widget.show: hello_label
|
||||
on_cancel:
|
||||
lvgl.widget.enable: hello_label
|
||||
on_ready:
|
||||
lvgl.widget.disable: hello_label
|
||||
on_defocus:
|
||||
lvgl.widget.hide: hello_label
|
||||
on_focus:
|
||||
logger.log: Button clicked
|
||||
on_scroll:
|
||||
logger.log: Button clicked
|
||||
on_scroll_end:
|
||||
logger.log: Button clicked
|
||||
on_scroll_begin:
|
||||
logger.log: Button clicked
|
||||
on_release:
|
||||
logger.log: Button clicked
|
||||
on_long_press_repeat:
|
||||
logger.log: Button clicked
|
||||
|
||||
font:
|
||||
- file: "gfonts://Roboto"
|
||||
|
|
|
@ -6,6 +6,23 @@ i2c:
|
|||
sda: GPIO18
|
||||
scl: GPIO19
|
||||
|
||||
sensor:
|
||||
- platform: rotary_encoder
|
||||
name: "Rotary Encoder"
|
||||
id: encoder
|
||||
pin_a: 2
|
||||
pin_b: 1
|
||||
internal: true
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
id: pushbutton
|
||||
name: Pushbutton
|
||||
pin:
|
||||
number: 0
|
||||
inverted: true
|
||||
ignore_strapping_warning: true
|
||||
|
||||
display:
|
||||
- platform: ili9xxx
|
||||
model: st7789v
|
||||
|
@ -50,5 +67,9 @@ lvgl:
|
|||
displays:
|
||||
- tft_display
|
||||
- second_display
|
||||
rotary_encoders:
|
||||
sensor: encoder
|
||||
enter_button: pushbutton
|
||||
group: general
|
||||
|
||||
<<: !include common.yaml
|
||||
|
|
|
@ -1 +1,28 @@
|
|||
substitutions:
|
||||
verify_ssl: "true"
|
||||
|
||||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
update.is_available:
|
||||
then:
|
||||
- logger.log: "Update available"
|
||||
- update.perform:
|
||||
force_update: true
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
http_request:
|
||||
verify_ssl: ${verify_ssl}
|
||||
|
||||
ota:
|
||||
- platform: http_request
|
||||
|
||||
update:
|
||||
- platform: http_request
|
||||
name: Firmware Update
|
||||
source: http://example.com/manifest.json
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
substitutions:
|
||||
verify_ssl: "false"
|
||||
|
||||
<<: !include common.yaml
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
substitutions:
|
||||
verify_ssl: "false"
|
||||
|
||||
<<: !include common.yaml
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
substitutions:
|
||||
verify_ssl: "false"
|
||||
|
||||
<<: !include common.yaml
|
||||
|
|
Loading…
Reference in a new issue