mirror of
https://github.com/esphome/esphome.git
synced 2024-11-27 17:27:59 +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/
|
.tests/
|
||||||
|
|
||||||
/components
|
/components
|
||||||
|
/managed_components
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ bool AdE7953Spi::ade_read_16(uint16_t reg, uint16_t *value) {
|
||||||
this->write_byte16(reg);
|
this->write_byte16(reg);
|
||||||
this->transfer_byte(0x80);
|
this->transfer_byte(0x80);
|
||||||
uint8_t recv[2];
|
uint8_t recv[2];
|
||||||
this->read_array(recv, 4);
|
this->read_array(recv, 2);
|
||||||
*value = encode_uint16(recv[0], recv[1]);
|
*value = encode_uint16(recv[0], recv[1]);
|
||||||
this->disable();
|
this->disable();
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -1,25 +1,27 @@
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
import esphome.codegen as cg
|
|
||||||
import esphome.config_validation as cv
|
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
from esphome.automation import Condition
|
from esphome.automation import Condition
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
|
CONF_ACTION,
|
||||||
|
CONF_ACTIONS,
|
||||||
CONF_DATA,
|
CONF_DATA,
|
||||||
CONF_DATA_TEMPLATE,
|
CONF_DATA_TEMPLATE,
|
||||||
|
CONF_EVENT,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_KEY,
|
CONF_KEY,
|
||||||
|
CONF_ON_CLIENT_CONNECTED,
|
||||||
|
CONF_ON_CLIENT_DISCONNECTED,
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
CONF_PORT,
|
CONF_PORT,
|
||||||
CONF_REBOOT_TIMEOUT,
|
CONF_REBOOT_TIMEOUT,
|
||||||
CONF_SERVICE,
|
CONF_SERVICE,
|
||||||
CONF_VARIABLES,
|
|
||||||
CONF_SERVICES,
|
CONF_SERVICES,
|
||||||
CONF_TRIGGER_ID,
|
|
||||||
CONF_EVENT,
|
|
||||||
CONF_TAG,
|
CONF_TAG,
|
||||||
CONF_ON_CLIENT_CONNECTED,
|
CONF_TRIGGER_ID,
|
||||||
CONF_ON_CLIENT_DISCONNECTED,
|
CONF_VARIABLES,
|
||||||
)
|
)
|
||||||
from esphome.core import coroutine_with_priority
|
from esphome.core import coroutine_with_priority
|
||||||
|
|
||||||
|
@ -63,7 +65,25 @@ def validate_encryption_key(value):
|
||||||
return 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.GenerateID(): cv.declare_id(APIServer),
|
||||||
cv.Optional(CONF_PORT, default=6053): cv.port,
|
cv.Optional(CONF_PORT, default=6053): cv.port,
|
||||||
|
@ -71,19 +91,10 @@ CONFIG_SCHEMA = cv.Schema(
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_REBOOT_TIMEOUT, default="15min"
|
CONF_REBOOT_TIMEOUT, default="15min"
|
||||||
): cv.positive_time_period_milliseconds,
|
): cv.positive_time_period_milliseconds,
|
||||||
cv.Optional(CONF_SERVICES): automation.validate_automation(
|
cv.Exclusive(
|
||||||
{
|
CONF_SERVICES, group_of_exclusion=CONF_ACTIONS
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
|
): ACTIONS_SCHEMA,
|
||||||
cv.Required(CONF_SERVICE): cv.valid_name,
|
cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA,
|
||||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
|
|
||||||
{
|
|
||||||
cv.validate_id_name: cv.one_of(
|
|
||||||
*SERVICE_ARG_NATIVE_TYPES, lower=True
|
|
||||||
),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_ENCRYPTION): cv.Schema(
|
cv.Optional(CONF_ENCRYPTION): cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_KEY): validate_encryption_key,
|
cv.Required(CONF_KEY): validate_encryption_key,
|
||||||
|
@ -96,7 +107,9 @@ CONFIG_SCHEMA = cv.Schema(
|
||||||
single=True
|
single=True
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA)
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
|
cv.rename_key(CONF_SERVICES, CONF_ACTIONS),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(40.0)
|
@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_password(config[CONF_PASSWORD]))
|
||||||
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
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 = []
|
template_args = []
|
||||||
func_args = []
|
func_args = []
|
||||||
service_arg_names = []
|
service_arg_names = []
|
||||||
|
@ -119,7 +132,7 @@ async def to_code(config):
|
||||||
service_arg_names.append(name)
|
service_arg_names.append(name)
|
||||||
templ = cg.TemplateArguments(*template_args)
|
templ = cg.TemplateArguments(*template_args)
|
||||||
trigger = cg.new_Pvariable(
|
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))
|
cg.add(var.register_user_service(trigger))
|
||||||
await automation.build_automation(trigger, func_args, conf)
|
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)})
|
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.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, default={}): KEY_VALUE_SCHEMA,
|
||||||
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
|
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
|
||||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
|
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
|
||||||
{cv.string: cv.returning_lambda}
|
{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(
|
@automation.register_action(
|
||||||
"homeassistant.service",
|
"homeassistant.service",
|
||||||
HomeAssistantServiceCallAction,
|
HomeAssistantServiceCallAction,
|
||||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA,
|
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
|
||||||
)
|
)
|
||||||
async def homeassistant_service_to_code(config, action_id, template_arg, args):
|
async def homeassistant_service_to_code(config, action_id, template_arg, args):
|
||||||
serv = await cg.get_variable(config[CONF_ID])
|
serv = await cg.get_variable(config[CONF_ID])
|
||||||
var = cg.new_Pvariable(action_id, template_arg, serv, False)
|
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))
|
cg.add(var.set_service(templ))
|
||||||
for key, value in config[CONF_DATA].items():
|
for key, value in config[CONF_DATA].items():
|
||||||
templ = await cg.templatable(value, args, None)
|
templ = await cg.templatable(value, args, None)
|
||||||
|
|
|
@ -138,8 +138,8 @@ void HttpRequestUpdate::update() {
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpRequestUpdate::perform() {
|
void HttpRequestUpdate::perform(bool force) {
|
||||||
if (this->state_ != update::UPDATE_STATE_AVAILABLE) {
|
if (this->state_ != update::UPDATE_STATE_AVAILABLE && !force) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent {
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void update() 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; }
|
void set_source_url(const std::string &source_url) { this->source_url_ = source_url; }
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import esphome.config_validation as cv
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
from esphome.const import __version__
|
from esphome.const import __version__
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
|
@ -39,4 +38,4 @@ def _process_next_url(url: str):
|
||||||
async def setup_improv_core(var, config):
|
async def setup_improv_core(var, config):
|
||||||
if CONF_NEXT_URL in config:
|
if CONF_NEXT_URL in config:
|
||||||
cg.add(var.set_next_url(_process_next_url(config[CONF_NEXT_URL])))
|
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
|
import logging
|
||||||
|
|
||||||
|
from esphome.automation import build_automation, register_action, validate_automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components.display import Display
|
from esphome.components.display import Display
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
|
@ -8,7 +9,11 @@ from esphome.const import (
|
||||||
CONF_BUFFER_SIZE,
|
CONF_BUFFER_SIZE,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_LAMBDA,
|
CONF_LAMBDA,
|
||||||
|
CONF_ON_IDLE,
|
||||||
CONF_PAGES,
|
CONF_PAGES,
|
||||||
|
CONF_TIMEOUT,
|
||||||
|
CONF_TRIGGER_ID,
|
||||||
|
CONF_TYPE,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, ID, Lambda
|
from esphome.core import CORE, ID, Lambda
|
||||||
from esphome.cpp_generator import MockObj
|
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 esphome.helpers import write_file_if_changed
|
||||||
|
|
||||||
from . import defines as df, helpers, lv_validation as lvalid
|
from . import defines as df, helpers, lv_validation as lvalid
|
||||||
|
from .automation import update_to_code
|
||||||
from .btn import btn_spec
|
from .btn import btn_spec
|
||||||
from .label import label_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 .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 .touchscreens import touchscreen_schema, touchscreens_to_code
|
||||||
|
from .trigger import generate_triggers
|
||||||
from .types import (
|
from .types import (
|
||||||
WIDGET_TYPES,
|
WIDGET_TYPES,
|
||||||
FontEngine,
|
FontEngine,
|
||||||
|
IdleTrigger,
|
||||||
LvglComponent,
|
LvglComponent,
|
||||||
lv_disp_t_ptr,
|
ObjUpdateAction,
|
||||||
lv_font_t,
|
lv_font_t,
|
||||||
lvgl_ns,
|
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"
|
DOMAIN = "lvgl"
|
||||||
DEPENDENCIES = ("display",)
|
DEPENDENCIES = ("display",)
|
||||||
|
@ -41,17 +51,21 @@ LOGGER = logging.getLogger(__name__)
|
||||||
for w_type in (label_spec, obj_spec, btn_spec):
|
for w_type in (label_spec, obj_spec, btn_spec):
|
||||||
WIDGET_TYPES[w_type.name] = w_type
|
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()
|
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):
|
async def add_init_lambda(lv_component, init):
|
||||||
if 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))
|
cg.add(lv_component.add_init_lambda(lamb))
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,6 +113,13 @@ def final_validation(config):
|
||||||
buffer_frac = config[CONF_BUFFER_SIZE]
|
buffer_frac = config[CONF_BUFFER_SIZE]
|
||||||
if CORE.is_esp32 and buffer_frac > 0.5 and "psram" not in global_config:
|
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")
|
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):
|
async def to_code(config):
|
||||||
|
@ -174,9 +195,15 @@ async def to_code(config):
|
||||||
|
|
||||||
with LvContext():
|
with LvContext():
|
||||||
await touchscreens_to_code(lv_component, config)
|
await touchscreens_to_code(lv_component, config)
|
||||||
|
await rotary_encoders_to_code(lv_component, config)
|
||||||
await set_obj_properties(lv_scr_act, config)
|
await set_obj_properties(lv_scr_act, config)
|
||||||
await add_widgets(lv_scr_act, config)
|
await add_widgets(lv_scr_act, config)
|
||||||
Widget.set_completed()
|
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())
|
await add_init_lambda(lv_component, LvContext.get_code())
|
||||||
for comp in helpers.lvgl_components_required:
|
for comp in helpers.lvgl_components_required:
|
||||||
CORE.add_define(f"USE_LVGL_{comp.upper()}")
|
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(
|
cv.Optional(df.CONF_BYTE_ORDER, default="big_endian"): cv.one_of(
|
||||||
"big_endian", "little_endian"
|
"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_WIDGETS): cv.ensure_list(WIDGET_SCHEMA),
|
||||||
cv.Optional(df.CONF_TRANSPARENCY_KEY, default=0x000400): lvalid.lv_color,
|
cv.Optional(df.CONF_TRANSPARENCY_KEY, default=0x000400): lvalid.lv_color,
|
||||||
cv.GenerateID(df.CONF_TOUCHSCREENS): touchscreen_schema,
|
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))
|
).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):
|
def __init__(self):
|
||||||
super().__init__(CONF_BUTTON, LvBoolean("lv_btn_t"), (CONF_MAIN,))
|
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):
|
def obj_creator(self, parent: MockObjClass, config: dict):
|
||||||
"""
|
"""
|
||||||
LVGL 8 calls buttons `btn`
|
LVGL 8 calls buttons `btn`
|
||||||
|
@ -21,5 +18,8 @@ class BtnType(WidgetType):
|
||||||
def get_uses(self):
|
def get_uses(self):
|
||||||
return ("btn",)
|
return ("btn",)
|
||||||
|
|
||||||
|
async def to_code(self, w, config):
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
btn_spec = BtnType()
|
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 import codegen as cg, config_validation as cv
|
||||||
from esphome.core import ID, Lambda
|
from esphome.core import ID, Lambda
|
||||||
|
from esphome.cpp_generator import Literal
|
||||||
from esphome.cpp_types import uint32
|
from esphome.cpp_types import uint32
|
||||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
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:
|
class LValidator:
|
||||||
|
@ -18,14 +38,19 @@ class LValidator:
|
||||||
has `process()` to convert a value during code generation
|
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.validator = validator
|
||||||
self.rtype = rtype
|
self.rtype = rtype
|
||||||
self.idtype = idtype
|
self.idtype = idtype
|
||||||
self.idexpr = idexpr
|
self.idexpr = idexpr
|
||||||
self.retmapper = retmapper
|
self.retmapper = retmapper
|
||||||
|
self.requires = requires
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
|
if self.requires:
|
||||||
|
value = requires_component(self.requires)(value)
|
||||||
if isinstance(value, cv.Lambda):
|
if isinstance(value, cv.Lambda):
|
||||||
return cv.returning_lambda(value)
|
return cv.returning_lambda(value)
|
||||||
if self.idtype is not None and isinstance(value, ID):
|
if self.idtype is not None and isinstance(value, ID):
|
||||||
|
@ -422,6 +447,7 @@ CONF_RECOLOR = "recolor"
|
||||||
CONF_RIGHT_BUTTON = "right_button"
|
CONF_RIGHT_BUTTON = "right_button"
|
||||||
CONF_ROLLOVER = "rollover"
|
CONF_ROLLOVER = "rollover"
|
||||||
CONF_ROOT_BACK_BTN = "root_back_btn"
|
CONF_ROOT_BACK_BTN = "root_back_btn"
|
||||||
|
CONF_ROTARY_ENCODERS = "rotary_encoders"
|
||||||
CONF_ROWS = "rows"
|
CONF_ROWS = "rows"
|
||||||
CONF_SCALES = "scales"
|
CONF_SCALES = "scales"
|
||||||
CONF_SCALE_LINES = "scale_lines"
|
CONF_SCALE_LINES = "scale_lines"
|
||||||
|
|
|
@ -2,6 +2,7 @@ import esphome.codegen as cg
|
||||||
from esphome.components.binary_sensor import BinarySensor
|
from esphome.components.binary_sensor import BinarySensor
|
||||||
from esphome.components.color import ColorStruct
|
from esphome.components.color import ColorStruct
|
||||||
from esphome.components.font import Font
|
from esphome.components.font import Font
|
||||||
|
from esphome.components.image import Image_
|
||||||
from esphome.components.sensor import Sensor
|
from esphome.components.sensor import Sensor
|
||||||
from esphome.components.text_sensor import TextSensor
|
from esphome.components.text_sensor import TextSensor
|
||||||
import esphome.config_validation as cv
|
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 esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||||
|
|
||||||
from . import types as ty
|
from . import types as ty
|
||||||
from .defines import LV_FONTS, LValidator, LvConstant
|
from .defines import LV_FONTS, ConstantLiteral, LValidator, LvConstant, literal
|
||||||
from .helpers import (
|
from .helpers import (
|
||||||
esphome_fonts_used,
|
esphome_fonts_used,
|
||||||
lv_fonts_used,
|
lv_fonts_used,
|
||||||
lvgl_components_required,
|
lvgl_components_required,
|
||||||
requires_component,
|
requires_component,
|
||||||
)
|
)
|
||||||
from .lvcode import ConstantLiteral, lv_expr
|
from .lvcode import lv_expr
|
||||||
from .types import lv_font_t
|
from .types import lv_font_t, lv_img_t
|
||||||
|
|
||||||
|
|
||||||
def literal_mapper(value, args=()):
|
|
||||||
if isinstance(value, str):
|
|
||||||
return ConstantLiteral(value)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER")
|
opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER")
|
||||||
|
|
||||||
|
@ -43,7 +37,7 @@ def opacity_validator(value):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
opacity = LValidator(opacity_validator, uint32, retmapper=literal_mapper)
|
opacity = LValidator(opacity_validator, uint32, retmapper=literal)
|
||||||
|
|
||||||
|
|
||||||
@schema_extractor("one_of")
|
@schema_extractor("one_of")
|
||||||
|
@ -79,9 +73,7 @@ def pixels_or_percent_validator(value):
|
||||||
return f"lv_pct({int(cv.percentage(value) * 100)})"
|
return f"lv_pct({int(cv.percentage(value) * 100)})"
|
||||||
|
|
||||||
|
|
||||||
pixels_or_percent = LValidator(
|
pixels_or_percent = LValidator(pixels_or_percent_validator, uint32, retmapper=literal)
|
||||||
pixels_or_percent_validator, uint32, retmapper=literal_mapper
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def zoom(value):
|
def zoom(value):
|
||||||
|
@ -115,7 +107,7 @@ def size_validator(value):
|
||||||
return f"lv_pct({int(cv.percentage(value) * 100)})"
|
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")
|
radius_consts = LvConstant("LV_RADIUS_", "CIRCLE")
|
||||||
|
|
||||||
|
@ -130,21 +122,37 @@ def radius_validator(value):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
radius = LValidator(radius_validator, uint32, retmapper=literal)
|
||||||
|
|
||||||
|
|
||||||
def id_name(value):
|
def id_name(value):
|
||||||
if value == SCHEMA_EXTRACT:
|
if value == SCHEMA_EXTRACT:
|
||||||
return "id"
|
return "id"
|
||||||
return cv.validate_id_name(value)
|
return cv.validate_id_name(value)
|
||||||
|
|
||||||
|
|
||||||
radius = LValidator(radius_validator, uint32, retmapper=literal_mapper)
|
|
||||||
|
|
||||||
|
|
||||||
def stop_value(value):
|
def stop_value(value):
|
||||||
return cv.int_range(0, 255)(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(
|
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,
|
AssignmentExpression,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
Expression,
|
Expression,
|
||||||
|
ExpressionStatement,
|
||||||
LambdaExpression,
|
LambdaExpression,
|
||||||
Literal,
|
|
||||||
MockObj,
|
MockObj,
|
||||||
RawExpression,
|
RawExpression,
|
||||||
RawStatement,
|
RawStatement,
|
||||||
|
@ -19,7 +19,9 @@ from esphome.cpp_generator import (
|
||||||
statement,
|
statement,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from .defines import ConstantLiteral
|
||||||
from .helpers import get_line_marks
|
from .helpers import get_line_marks
|
||||||
|
from .types import lv_group_t
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -105,29 +107,40 @@ class LambdaContext(CodeContext):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
parameters: list[tuple[SafeExpType, str]],
|
parameters: list[tuple[SafeExpType, str]] = None,
|
||||||
return_type: SafeExpType = None,
|
return_type: SafeExpType = cg.void,
|
||||||
|
capture: str = "",
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.code_list: list[Statement] = []
|
self.code_list: list[Statement] = []
|
||||||
self.parameters = parameters
|
self.parameters = parameters
|
||||||
self.return_type = return_type
|
self.return_type = return_type
|
||||||
|
self.capture = capture
|
||||||
|
|
||||||
def add(self, expression: Union[Expression, Statement]):
|
def add(self, expression: Union[Expression, Statement]):
|
||||||
self.code_list.append(expression)
|
self.code_list.append(expression)
|
||||||
return 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 = []
|
code_text = []
|
||||||
for exp in self.code_list:
|
for exp in self.code_list:
|
||||||
text = str(statement(exp))
|
text = str(statement(exp))
|
||||||
text = text.rstrip()
|
text = text.rstrip()
|
||||||
code_text.append(text)
|
code_text.append(text)
|
||||||
return await cg.process_lambda(
|
return code_text
|
||||||
Lambda("\n".join(code_text) + "\n\n"),
|
|
||||||
self.parameters,
|
def __enter__(self):
|
||||||
return_type=self.return_type,
|
super().__enter__()
|
||||||
)
|
return self
|
||||||
|
|
||||||
|
|
||||||
class LocalVariable(MockObj):
|
class LocalVariable(MockObj):
|
||||||
|
@ -187,13 +200,18 @@ class MockLv:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def cond_if(self, expression: Expression):
|
def cond_if(self, expression: Expression):
|
||||||
CodeContext.append(RawExpression(f"if({expression}) {{"))
|
CodeContext.append(RawStatement(f"if {expression} {{"))
|
||||||
|
|
||||||
def cond_else(self):
|
def cond_else(self):
|
||||||
CodeContext.append(RawExpression("} else {"))
|
CodeContext.append(RawStatement("} else {"))
|
||||||
|
|
||||||
def cond_endif(self):
|
def cond_endif(self):
|
||||||
CodeContext.append(RawExpression("}"))
|
CodeContext.append(RawStatement("}"))
|
||||||
|
|
||||||
|
|
||||||
|
class ReturnStatement(ExpressionStatement):
|
||||||
|
def __str__(self):
|
||||||
|
return f"return {self.expression};"
|
||||||
|
|
||||||
|
|
||||||
class LvExpr(MockLv):
|
class LvExpr(MockLv):
|
||||||
|
@ -210,6 +228,7 @@ lv = MockLv("lv_")
|
||||||
lv_expr = LvExpr("lv_")
|
lv_expr = LvExpr("lv_")
|
||||||
# Mock for lv_obj_ calls
|
# Mock for lv_obj_ calls
|
||||||
lv_obj = MockLv("lv_obj_")
|
lv_obj = MockLv("lv_obj_")
|
||||||
|
lvgl_comp = MockObj("lvgl_comp", "->")
|
||||||
|
|
||||||
|
|
||||||
# equivalent to cg.add() for the lvgl init context
|
# equivalent to cg.add() for the lvgl init context
|
||||||
|
@ -226,12 +245,19 @@ def lv_assign(target, expression):
|
||||||
lv_add(RawExpression(f"{target} = {expression}"))
|
lv_add(RawExpression(f"{target} = {expression}"))
|
||||||
|
|
||||||
|
|
||||||
class ConstantLiteral(Literal):
|
lv_groups = {} # Widget group names
|
||||||
__slots__ = ("constant",)
|
|
||||||
|
|
||||||
def __init__(self, constant: str):
|
|
||||||
super().__init__()
|
|
||||||
self.constant = constant
|
|
||||||
|
|
||||||
def __str__(self):
|
def add_group(name):
|
||||||
return self.constant
|
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) {
|
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();
|
auto now = millis();
|
||||||
this->draw_buffer_(area, (const uint8_t *) color_p);
|
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),
|
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_area_get_height(area), (int) (millis() - now));
|
||||||
|
}
|
||||||
lv_disp_flush_ready(disp_drv);
|
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() {
|
void LvglComponent::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "LVGL Setup starts");
|
ESP_LOGCONFIG(TAG, "LVGL Setup starts");
|
||||||
#if LV_USE_LOG
|
#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);
|
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_);
|
this->disp_ = lv_disp_drv_register(&this->disp_drv_);
|
||||||
for (const auto &v : this->init_lambdas_)
|
for (const auto &v : this->init_lambdas_)
|
||||||
v(this->disp_);
|
v(this);
|
||||||
lv_disp_trig_activity(this->disp_);
|
lv_disp_trig_activity(this->disp_);
|
||||||
ESP_LOGCONFIG(TAG, "LVGL Setup complete");
|
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 lvgl
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,32 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "esphome/core/defines.h"
|
#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
|
// required for clang-tidy
|
||||||
#ifndef LV_CONF_H
|
#ifndef LV_CONF_H
|
||||||
#define LV_CONF_SKIP 1 // NOLINT
|
#define LV_CONF_SKIP 1 // NOLINT
|
||||||
#endif
|
#endif // LV_CONF_H
|
||||||
|
|
||||||
#include "esphome/components/display/display.h"
|
#include "esphome/components/display/display.h"
|
||||||
#include "esphome/components/display/display_color_utils.h"
|
#include "esphome/components/display/display_color_utils.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/hal.h"
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include <lvgl.h>
|
#include <lvgl.h>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#ifdef USE_LVGL_IMAGE
|
||||||
|
#include "esphome/components/image/image.h"
|
||||||
|
#endif // USE_LVGL_IMAGE
|
||||||
|
|
||||||
#ifdef USE_LVGL_FONT
|
#ifdef USE_LVGL_FONT
|
||||||
#include "esphome/components/font/font.h"
|
#include "esphome/components/font/font.h"
|
||||||
#endif
|
#endif // USE_LVGL_FONT
|
||||||
#ifdef USE_LVGL_TOUCHSCREEN
|
#ifdef USE_LVGL_TOUCHSCREEN
|
||||||
#include "esphome/components/touchscreen/touchscreen.h"
|
#include "esphome/components/touchscreen/touchscreen.h"
|
||||||
#endif // USE_LVGL_TOUCHSCREEN
|
#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
|
// Parent class for things that wrap an LVGL object
|
||||||
class LvCompound final {
|
class LvCompound final {
|
||||||
public:
|
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{};
|
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 event_callback_t = void(_lv_event_t *);
|
||||||
using text_lambda_t = std::function<const char *()>;
|
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
|
#ifdef USE_LVGL_FONT
|
||||||
class FontEngine {
|
class FontEngine {
|
||||||
public:
|
public:
|
||||||
|
@ -67,6 +85,9 @@ class FontEngine {
|
||||||
lv_font_t lv_font_{};
|
lv_font_t lv_font_{};
|
||||||
};
|
};
|
||||||
#endif // USE_LVGL_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 {
|
class LvglComponent : public PollingComponent {
|
||||||
constexpr static const char *const TAG = "lvgl";
|
constexpr static const char *const TAG = "lvgl";
|
||||||
|
@ -92,27 +113,54 @@ class LvglComponent : public PollingComponent {
|
||||||
area->y2++;
|
area->y2++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() override { lv_timer_handler_run_in_period(5); }
|
|
||||||
void setup() override;
|
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_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 dump_config() override;
|
||||||
void set_full_refresh(bool full_refresh) { this->full_refresh_ = full_refresh; }
|
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; }
|
void set_buffer_frac(size_t frac) { this->buffer_frac_ = frac; }
|
||||||
lv_disp_t *get_disp() { return this->disp_; }
|
lv_disp_t *get_disp() { return this->disp_; }
|
||||||
void set_paused(bool paused, bool show_snow) {
|
void set_paused(bool paused, bool show_snow) {
|
||||||
this->paused_ = paused;
|
this->paused_ = paused;
|
||||||
|
this->show_snow_ = show_snow;
|
||||||
|
this->snow_line_ = 0;
|
||||||
if (!paused && lv_scr_act() != nullptr) {
|
if (!paused && lv_scr_act() != nullptr) {
|
||||||
lv_disp_trig_activity(this->disp_); // resets the inactivity time
|
lv_disp_trig_activity(this->disp_); // resets the inactivity time
|
||||||
lv_obj_invalidate(lv_scr_act());
|
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_; }
|
bool is_paused() const { return this->paused_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void write_random_();
|
||||||
void draw_buffer_(const lv_area_t *area, const uint8_t *ptr);
|
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);
|
void flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p);
|
||||||
std::vector<display::Display *> displays_{};
|
std::vector<display::Display *> displays_{};
|
||||||
|
@ -120,12 +168,52 @@ class LvglComponent : public PollingComponent {
|
||||||
lv_disp_drv_t disp_drv_{};
|
lv_disp_drv_t disp_drv_{};
|
||||||
lv_disp_t *disp_{};
|
lv_disp_t *disp_{};
|
||||||
bool paused_{};
|
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};
|
size_t buffer_frac_{1};
|
||||||
bool full_refresh_{};
|
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
|
#ifdef USE_LVGL_TOUCHSCREEN
|
||||||
class LVTouchListener : public touchscreen::TouchListener, public Parented<LvglComponent> {
|
class LVTouchListener : public touchscreen::TouchListener, public Parented<LvglComponent> {
|
||||||
public:
|
public:
|
||||||
|
@ -160,7 +248,62 @@ class LVTouchListener : public touchscreen::TouchListener, public Parented<LvglC
|
||||||
bool touch_pressed_{};
|
bool touch_pressed_{};
|
||||||
};
|
};
|
||||||
#endif // USE_LVGL_TOUCHSCREEN
|
#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 lvgl
|
||||||
} // namespace esphome
|
} // 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 .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):
|
class ObjType(WidgetType):
|
||||||
|
@ -15,3 +19,10 @@ class ObjType(WidgetType):
|
||||||
|
|
||||||
|
|
||||||
obj_spec = ObjType()
|
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 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 esphome.schema_extractors import SCHEMA_EXTRACT
|
||||||
|
|
||||||
from . import defines as df, lv_validation as lvalid, types as ty
|
from . import defines as df, lv_validation as lvalid, types as ty
|
||||||
from .helpers import add_lv_use, requires_component, validate_printf
|
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
|
from .types import WIDGET_TYPES, WidgetType
|
||||||
|
|
||||||
# A schema for text properties
|
# 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
|
# All LVGL styles and their validators
|
||||||
STYLE_PROPS = {
|
STYLE_PROPS = {
|
||||||
"align": df.CHILD_ALIGNMENTS.one_of,
|
"align": df.CHILD_ALIGNMENTS.one_of,
|
||||||
|
@ -43,6 +76,7 @@ STYLE_PROPS = {
|
||||||
"bg_image_opa": lvalid.opacity,
|
"bg_image_opa": lvalid.opacity,
|
||||||
"bg_image_recolor": lvalid.lv_color,
|
"bg_image_recolor": lvalid.lv_color,
|
||||||
"bg_image_recolor_opa": lvalid.opacity,
|
"bg_image_recolor_opa": lvalid.opacity,
|
||||||
|
"bg_image_src": lvalid.lv_image,
|
||||||
"bg_main_stop": lvalid.stop_value,
|
"bg_main_stop": lvalid.stop_value,
|
||||||
"bg_opa": lvalid.opacity,
|
"bg_opa": lvalid.opacity,
|
||||||
"border_color": lvalid.lv_color,
|
"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):
|
def obj_schema(widget_type: WidgetType):
|
||||||
"""
|
"""
|
||||||
Create a schema for a widget type itself i.e. no allowance for children
|
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)
|
part_schema(widget_type)
|
||||||
.extend(FLAG_SCHEMA)
|
.extend(FLAG_SCHEMA)
|
||||||
.extend(ALIGN_TO_SCHEMA)
|
.extend(ALIGN_TO_SCHEMA)
|
||||||
|
.extend(automation_schema(widget_type.w_type))
|
||||||
.extend(
|
.extend(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Optional(CONF_STATE): SET_STATE_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
|
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 = {
|
ALL_STYLES = {
|
||||||
**STYLE_PROPS,
|
**STYLE_PROPS,
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import esphome.codegen as cg
|
||||||
from esphome.components.touchscreen import CONF_TOUCHSCREEN_ID, Touchscreen
|
from esphome.components.touchscreen import CONF_TOUCHSCREEN_ID, Touchscreen
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_ID
|
||||||
from esphome.core import CORE, TimePeriod
|
from esphome.core import CORE
|
||||||
|
|
||||||
from .defines import (
|
from .defines import (
|
||||||
CONF_LONG_PRESS_REPEAT_TIME,
|
CONF_LONG_PRESS_REPEAT_TIME,
|
||||||
|
@ -10,11 +10,10 @@ from .defines import (
|
||||||
CONF_TOUCHSCREENS,
|
CONF_TOUCHSCREENS,
|
||||||
)
|
)
|
||||||
from .helpers import lvgl_components_required
|
from .helpers import lvgl_components_required
|
||||||
from .lv_validation import lv_milliseconds
|
|
||||||
from .lvcode import lv
|
from .lvcode import lv
|
||||||
|
from .schemas import PRESS_TIME
|
||||||
from .types import LVTouchListener
|
from .types import LVTouchListener
|
||||||
|
|
||||||
PRESS_TIME = cv.All(lv_milliseconds, cv.Range(max=TimePeriod(milliseconds=65535)))
|
|
||||||
CONF_TOUCHSCREEN = "touchscreen"
|
CONF_TOUCHSCREEN = "touchscreen"
|
||||||
TOUCHSCREENS_CONFIG = cv.maybe_simple_value(
|
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.core import ID
|
||||||
from esphome.cpp_generator import MockObjClass
|
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")
|
char_ptr = cg.global_ns.namespace("char").operator("ptr")
|
||||||
void_ptr = cg.void.operator("ptr")
|
void_ptr = cg.void.operator("ptr")
|
||||||
LvglComponent = lvgl_ns.class_("LvglComponent", cg.PollingComponent)
|
LvglComponent = lvgl_ns.class_("LvglComponent", cg.PollingComponent)
|
||||||
|
LvglComponentPtr = LvglComponent.operator("ptr")
|
||||||
lv_event_code_t = cg.global_ns.namespace("lv_event_code_t")
|
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")
|
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")
|
LvCompound = lvgl_ns.class_("LvCompound")
|
||||||
lv_font_t = cg.global_ns.class_("lv_font_t")
|
lv_font_t = cg.global_ns.class_("lv_font_t")
|
||||||
lv_style_t = cg.global_ns.struct("lv_style_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_obj_t_ptr = lv_obj_base_t.operator("ptr")
|
||||||
lv_disp_t_ptr = cg.global_ns.struct("lv_disp_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_color_t = cg.global_ns.struct("lv_color_t")
|
||||||
|
lv_group_t = cg.global_ns.struct("lv_group_t")
|
||||||
LVTouchListener = lvgl_ns.class_("LVTouchListener")
|
LVTouchListener = lvgl_ns.class_("LVTouchListener")
|
||||||
LVEncoderListener = lvgl_ns.class_("LVEncoderListener")
|
LVEncoderListener = lvgl_ns.class_("LVEncoderListener")
|
||||||
lv_obj_t = LvType("lv_obj_t")
|
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.
|
# this will be populated later, in __init__.py to avoid circular imports.
|
||||||
|
@ -58,7 +66,7 @@ class LvBoolean(LvType):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
*args,
|
*args,
|
||||||
largs=[(cg.bool_, "x")],
|
largs=[(cg.bool_, "x")],
|
||||||
lvalue=lambda w: w.is_checked(),
|
lvalue=lambda w: w.has_state("LV_STATE_CHECKED"),
|
||||||
has_on_value=True,
|
has_on_value=True,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
@ -83,11 +91,14 @@ class WidgetType:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.w_type = w_type
|
self.w_type = w_type
|
||||||
self.parts = parts
|
self.parts = parts
|
||||||
self.schema = schema or {}
|
if schema is None:
|
||||||
if modify_schema is None:
|
self.schema = {}
|
||||||
self.modify_schema = schema
|
|
||||||
else:
|
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
|
@property
|
||||||
def animated(self):
|
def animated(self):
|
||||||
|
|
|
@ -4,9 +4,9 @@ from typing import Any
|
||||||
from esphome import codegen as cg, config_validation as cv
|
from esphome import codegen as cg, config_validation as cv
|
||||||
from esphome.config_validation import Invalid
|
from esphome.config_validation import Invalid
|
||||||
from esphome.const import CONF_GROUP, CONF_ID, CONF_STATE
|
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.coroutine import FakeAwaitable
|
||||||
from esphome.cpp_generator import MockObjClass
|
from esphome.cpp_generator import MockObj, MockObjClass, VariableDeclarationExpression
|
||||||
|
|
||||||
from .defines import (
|
from .defines import (
|
||||||
CONF_DEFAULT,
|
CONF_DEFAULT,
|
||||||
|
@ -16,13 +16,15 @@ from .defines import (
|
||||||
OBJ_FLAGS,
|
OBJ_FLAGS,
|
||||||
PARTS,
|
PARTS,
|
||||||
STATES,
|
STATES,
|
||||||
|
ConstantLiteral,
|
||||||
LValidator,
|
LValidator,
|
||||||
join_enums,
|
join_enums,
|
||||||
|
literal,
|
||||||
)
|
)
|
||||||
from .helpers import add_lv_use
|
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 .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__"
|
EVENT_LAMB = "event_lamb__"
|
||||||
|
|
||||||
|
@ -76,17 +78,20 @@ class Widget:
|
||||||
return f"{self.var}->obj"
|
return f"{self.var}->obj"
|
||||||
return self.var
|
return self.var
|
||||||
|
|
||||||
def add_state(self, *args):
|
def add_state(self, state):
|
||||||
return lv_obj.add_state(self.obj, *args)
|
return lv_obj.add_state(self.obj, literal(state))
|
||||||
|
|
||||||
def clear_state(self, *args):
|
def clear_state(self, state):
|
||||||
return lv_obj.clear_state(self.obj, *args)
|
return lv_obj.clear_state(self.obj, literal(state))
|
||||||
|
|
||||||
def add_flag(self, *args):
|
def has_state(self, state):
|
||||||
return lv_obj.add_flag(self.obj, *args)
|
return lv_expr.obj_get_state(self.obj) & literal(state) != 0
|
||||||
|
|
||||||
def clear_flag(self, *args):
|
def add_flag(self, flag):
|
||||||
return lv_obj.clear_flag(self.obj, *args)
|
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):
|
def set_property(self, prop, value, animated: bool = None, ltype=None):
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
|
@ -125,6 +130,16 @@ class Widget:
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"({self.var}, {self.type})"
|
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
|
# Map of widgets to their config, used for trigger generation
|
||||||
widget_map: dict[Any, Widget] = {}
|
widget_map: dict[Any, Widget] = {}
|
||||||
|
@ -146,7 +161,8 @@ def get_widget_generator(wid):
|
||||||
yield
|
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):
|
if obj := widget_map.get(wid):
|
||||||
return obj
|
return obj
|
||||||
return await FakeAwaitable(get_widget_generator(wid))
|
return await FakeAwaitable(get_widget_generator(wid))
|
||||||
|
@ -204,9 +220,10 @@ async def set_obj_properties(w: Widget, config):
|
||||||
}.items():
|
}.items():
|
||||||
if isinstance(ALL_STYLES[prop], LValidator):
|
if isinstance(ALL_STYLES[prop], LValidator):
|
||||||
value = await ALL_STYLES[prop].process(value)
|
value = await ALL_STYLES[prop].process(value)
|
||||||
# Remapping for backwards compatibility of style names
|
|
||||||
prop_r = STYLE_REMAP.get(prop, prop)
|
prop_r = STYLE_REMAP.get(prop, prop)
|
||||||
w.set_style(prop_r, value, lv_state)
|
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_clr = set()
|
||||||
flag_set = set()
|
flag_set = set()
|
||||||
props = parts[CONF_MAIN][CONF_DEFAULT]
|
props = parts[CONF_MAIN][CONF_DEFAULT]
|
||||||
|
@ -241,7 +258,7 @@ async def set_obj_properties(w: Widget, config):
|
||||||
w.clear_state(clears)
|
w.clear_state(clears)
|
||||||
for key, value in lambs.items():
|
for key, value in lambs.items():
|
||||||
lamb = await cg.process_lambda(value, [], return_type=cg.bool_)
|
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)
|
lv.cond_if(lamb)
|
||||||
w.add_state(state)
|
w.add_state(state)
|
||||||
lv.cond_else()
|
lv.cond_else()
|
||||||
|
@ -281,10 +298,19 @@ async def widget_to_code(w_cnfig, w_type, parent):
|
||||||
var = cg.new_Pvariable(wid)
|
var = cg.new_Pvariable(wid)
|
||||||
lv_add(var.set_obj(creator))
|
lv_add(var.set_obj(creator))
|
||||||
else:
|
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)
|
lv_assign(var, creator)
|
||||||
|
|
||||||
widget = Widget.create(wid, var, spec, w_cnfig, parent)
|
widget = Widget.create(wid, var, spec, w_cnfig, parent)
|
||||||
await set_obj_properties(widget, w_cnfig)
|
await set_obj_properties(widget, w_cnfig)
|
||||||
await add_widgets(widget, w_cnfig)
|
await add_widgets(widget, w_cnfig)
|
||||||
await spec.to_code(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() {
|
void MatrixKeypad::setup() {
|
||||||
for (auto *pin : this->rows_) {
|
for (auto *pin : this->rows_) {
|
||||||
|
pin->setup();
|
||||||
if (!has_diodes_) {
|
if (!has_diodes_) {
|
||||||
pin->pin_mode(gpio::FLAG_INPUT);
|
pin->pin_mode(gpio::FLAG_INPUT);
|
||||||
} else {
|
} else {
|
||||||
|
@ -15,6 +16,7 @@ void MatrixKeypad::setup() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (auto *pin : this->columns_) {
|
for (auto *pin : this->columns_) {
|
||||||
|
pin->setup();
|
||||||
if (has_pulldowns_) {
|
if (has_pulldowns_) {
|
||||||
pin->pin_mode(gpio::FLAG_INPUT);
|
pin->pin_mode(gpio::FLAG_INPUT);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -148,7 +148,7 @@ WakeWordModel::WakeWordModel(const uint8_t *model_start, float probability_cutof
|
||||||
};
|
};
|
||||||
|
|
||||||
bool WakeWordModel::determine_detected() {
|
bool WakeWordModel::determine_detected() {
|
||||||
int32_t sum = 0;
|
uint32_t sum = 0;
|
||||||
for (auto &prob : this->recent_streaming_probabilities_) {
|
for (auto &prob : this->recent_streaming_probabilities_) {
|
||||||
sum += prob;
|
sum += prob;
|
||||||
}
|
}
|
||||||
|
@ -175,12 +175,14 @@ VADModel::VADModel(const uint8_t *model_start, float probability_cutoff, size_t
|
||||||
};
|
};
|
||||||
|
|
||||||
bool VADModel::determine_detected() {
|
bool VADModel::determine_detected() {
|
||||||
uint8_t max = 0;
|
uint32_t sum = 0;
|
||||||
for (auto &prob : this->recent_streaming_probabilities_) {
|
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
|
} // namespace micro_wake_word
|
||||||
|
|
|
@ -17,6 +17,7 @@ from esphome.const import (
|
||||||
CONF_ICON,
|
CONF_ICON,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_IGNORE_OUT_OF_RANGE,
|
CONF_IGNORE_OUT_OF_RANGE,
|
||||||
|
CONF_MULTIPLE,
|
||||||
CONF_ON_RAW_VALUE,
|
CONF_ON_RAW_VALUE,
|
||||||
CONF_ON_VALUE,
|
CONF_ON_VALUE,
|
||||||
CONF_ON_VALUE_RANGE,
|
CONF_ON_VALUE_RANGE,
|
||||||
|
@ -249,6 +250,7 @@ CalibratePolynomialFilter = sensor_ns.class_("CalibratePolynomialFilter", Filter
|
||||||
SensorInRangeCondition = sensor_ns.class_("SensorInRangeCondition", Filter)
|
SensorInRangeCondition = sensor_ns.class_("SensorInRangeCondition", Filter)
|
||||||
ClampFilter = sensor_ns.class_("ClampFilter", Filter)
|
ClampFilter = sensor_ns.class_("ClampFilter", Filter)
|
||||||
RoundFilter = sensor_ns.class_("RoundFilter", Filter)
|
RoundFilter = sensor_ns.class_("RoundFilter", Filter)
|
||||||
|
RoundMultipleFilter = sensor_ns.class_("RoundMultipleFilter", Filter)
|
||||||
|
|
||||||
validate_unit_of_measurement = cv.string_strict
|
validate_unit_of_measurement = cv.string_strict
|
||||||
validate_accuracy_decimals = cv.int_
|
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):
|
async def build_filters(config):
|
||||||
return await cg.build_registry_list(FILTER_REGISTRY, config)
|
return await cg.build_registry_list(FILTER_REGISTRY, config)
|
||||||
|
|
||||||
|
|
|
@ -472,5 +472,13 @@ optional<float> RoundFilter::new_value(float value) {
|
||||||
return 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 sensor
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -431,5 +431,14 @@ class RoundFilter : public Filter {
|
||||||
uint8_t precision_;
|
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 sensor
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
|
import esphome.codegen as cg
|
||||||
from esphome.components import mqtt, web_server
|
from esphome.components import mqtt, web_server
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
import esphome.codegen as cg
|
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_DEVICE_CLASS,
|
CONF_DEVICE_CLASS,
|
||||||
CONF_ENTITY_CATEGORY,
|
CONF_ENTITY_CATEGORY,
|
||||||
|
CONF_FORCE_UPDATE,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_MQTT_ID,
|
CONF_MQTT_ID,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER_ID,
|
||||||
|
@ -23,8 +24,12 @@ UpdateEntity = update_ns.class_("UpdateEntity", cg.EntityBase)
|
||||||
|
|
||||||
UpdateInfo = update_ns.struct("UpdateInfo")
|
UpdateInfo = update_ns.struct("UpdateInfo")
|
||||||
|
|
||||||
PerformAction = update_ns.class_("PerformAction", automation.Action)
|
PerformAction = update_ns.class_(
|
||||||
IsAvailableCondition = update_ns.class_("IsAvailableCondition", automation.Condition)
|
"PerformAction", automation.Action, cg.Parented.template(UpdateEntity)
|
||||||
|
)
|
||||||
|
IsAvailableCondition = update_ns.class_(
|
||||||
|
"IsAvailableCondition", automation.Condition, cg.Parented.template(UpdateEntity)
|
||||||
|
)
|
||||||
|
|
||||||
DEVICE_CLASSES = [
|
DEVICE_CLASSES = [
|
||||||
DEVICE_CLASS_EMPTY,
|
DEVICE_CLASS_EMPTY,
|
||||||
|
@ -92,24 +97,37 @@ async def to_code(config):
|
||||||
cg.add_global(update_ns.using)
|
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.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):
|
async def update_perform_action_to_code(config, action_id, template_arg, args):
|
||||||
paren = await cg.get_variable(config[CONF_ID])
|
var = cg.new_Pvariable(action_id, template_arg)
|
||||||
return cg.new_Pvariable(action_id, paren, paren)
|
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(
|
@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(
|
async def update_is_available_condition_to_code(
|
||||||
config, condition_id, template_arg, args
|
config, condition_id, template_arg, args
|
||||||
):
|
):
|
||||||
paren = await cg.get_variable(config[CONF_ID])
|
var = cg.new_Pvariable(condition_id, template_arg)
|
||||||
return cg.new_Pvariable(condition_id, paren, paren)
|
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();
|
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 UpdateInfo &update_info = update_info_;
|
||||||
const UpdateState &state = state_;
|
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)
|
negative_one_to_one_float = float_range(min=-1, max=1)
|
||||||
positive_int = int_range(min=0)
|
positive_int = int_range(min=0)
|
||||||
positive_not_null_int = int_range(min=0, min_included=False)
|
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):
|
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_ACCELERATION_Z = "acceleration_z"
|
||||||
CONF_ACCURACY = "accuracy"
|
CONF_ACCURACY = "accuracy"
|
||||||
CONF_ACCURACY_DECIMALS = "accuracy_decimals"
|
CONF_ACCURACY_DECIMALS = "accuracy_decimals"
|
||||||
|
CONF_ACTION = "action"
|
||||||
CONF_ACTION_ID = "action_id"
|
CONF_ACTION_ID = "action_id"
|
||||||
CONF_ACTION_STATE_TOPIC = "action_state_topic"
|
CONF_ACTION_STATE_TOPIC = "action_state_topic"
|
||||||
|
CONF_ACTIONS = "actions"
|
||||||
CONF_ACTIVE = "active"
|
CONF_ACTIVE = "active"
|
||||||
CONF_ACTIVE_POWER = "active_power"
|
CONF_ACTIVE_POWER = "active_power"
|
||||||
CONF_ACTUAL_GAIN = "actual_gain"
|
CONF_ACTUAL_GAIN = "actual_gain"
|
||||||
|
@ -501,6 +503,7 @@ CONF_MOTION = "motion"
|
||||||
CONF_MOVEMENT_COUNTER = "movement_counter"
|
CONF_MOVEMENT_COUNTER = "movement_counter"
|
||||||
CONF_MQTT = "mqtt"
|
CONF_MQTT = "mqtt"
|
||||||
CONF_MQTT_ID = "mqtt_id"
|
CONF_MQTT_ID = "mqtt_id"
|
||||||
|
CONF_MULTIPLE = "multiple"
|
||||||
CONF_MULTIPLEXER = "multiplexer"
|
CONF_MULTIPLEXER = "multiplexer"
|
||||||
CONF_MULTIPLY = "multiply"
|
CONF_MULTIPLY = "multiply"
|
||||||
CONF_NAME = "name"
|
CONF_NAME = "name"
|
||||||
|
|
|
@ -39,9 +39,12 @@
|
||||||
#define USE_LOCK
|
#define USE_LOCK
|
||||||
#define USE_LOGGER
|
#define USE_LOGGER
|
||||||
#define USE_LVGL
|
#define USE_LVGL
|
||||||
|
#define USE_LVGL_BINARY_SENSOR
|
||||||
#define USE_LVGL_FONT
|
#define USE_LVGL_FONT
|
||||||
#define USE_LVGL_IMAGE
|
#define USE_LVGL_IMAGE
|
||||||
|
#define USE_LVGL_KEY_LISTENER
|
||||||
#define USE_LVGL_TOUCHSCREEN
|
#define USE_LVGL_TOUCHSCREEN
|
||||||
|
#define USE_LVGL_ROTARY_ENCODER
|
||||||
#define USE_MDNS
|
#define USE_MDNS
|
||||||
#define USE_MEDIA_PLAYER
|
#define USE_MEDIA_PLAYER
|
||||||
#define USE_MQTT
|
#define USE_MQTT
|
||||||
|
|
|
@ -35,7 +35,7 @@ build_flags =
|
||||||
lib_deps =
|
lib_deps =
|
||||||
esphome/noise-c@0.1.4 ; api
|
esphome/noise-c@0.1.4 ; api
|
||||||
makuna/NeoPixelBus@2.7.3 ; neopixelbus
|
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
|
bblanchon/ArduinoJson@6.18.5 ; json
|
||||||
wjtje/qr-code-generator-library@1.7.0 ; qr_code
|
wjtje/qr-code-generator-library@1.7.0 ; qr_code
|
||||||
functionpointer/arduino-MLX90393@1.0.0 ; mlx90393
|
functionpointer/arduino-MLX90393@1.0.0 ; mlx90393
|
||||||
|
|
|
@ -5,8 +5,8 @@ esphome:
|
||||||
event: esphome.button_pressed
|
event: esphome.button_pressed
|
||||||
data:
|
data:
|
||||||
message: Button was pressed
|
message: Button was pressed
|
||||||
- homeassistant.service:
|
- homeassistant.action:
|
||||||
service: notify.html5
|
action: notify.html5
|
||||||
data:
|
data:
|
||||||
message: Button was pressed
|
message: Button was pressed
|
||||||
- homeassistant.tag_scanned: pulse
|
- homeassistant.tag_scanned: pulse
|
||||||
|
@ -21,8 +21,8 @@ api:
|
||||||
reboot_timeout: 0min
|
reboot_timeout: 0min
|
||||||
encryption:
|
encryption:
|
||||||
key: bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU=
|
key: bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU=
|
||||||
services:
|
actions:
|
||||||
- service: hello_world
|
- action: hello_world
|
||||||
variables:
|
variables:
|
||||||
name: string
|
name: string
|
||||||
then:
|
then:
|
||||||
|
@ -30,10 +30,10 @@ api:
|
||||||
format: Hello World %s!
|
format: Hello World %s!
|
||||||
args:
|
args:
|
||||||
- name.c_str()
|
- name.c_str()
|
||||||
- service: empty_service
|
- action: empty_action
|
||||||
then:
|
then:
|
||||||
- logger.log: Service Called
|
- logger.log: Action Called
|
||||||
- service: all_types
|
- action: all_types
|
||||||
variables:
|
variables:
|
||||||
bool_: bool
|
bool_: bool
|
||||||
int_: int
|
int_: int
|
||||||
|
@ -41,7 +41,7 @@ api:
|
||||||
string_: string
|
string_: string
|
||||||
then:
|
then:
|
||||||
- logger.log: Something happened
|
- logger.log: Something happened
|
||||||
- service: array_types
|
- action: array_types
|
||||||
variables:
|
variables:
|
||||||
bool_arr: bool[]
|
bool_arr: bool[]
|
||||||
int_arr: int[]
|
int_arr: int[]
|
||||||
|
|
|
@ -13,12 +13,12 @@ esphome:
|
||||||
message: The humidity is {{ my_variable }}%.
|
message: The humidity is {{ my_variable }}%.
|
||||||
variables:
|
variables:
|
||||||
my_variable: "return id(ha_hello_world_temperature).state;"
|
my_variable: "return id(ha_hello_world_temperature).state;"
|
||||||
- homeassistant.service:
|
- homeassistant.action:
|
||||||
service: notify.html5
|
action: notify.html5
|
||||||
data:
|
data:
|
||||||
message: Button was pressed
|
message: Button was pressed
|
||||||
- homeassistant.service:
|
- homeassistant.action:
|
||||||
service: notify.html5
|
action: notify.html5
|
||||||
data:
|
data:
|
||||||
title: New Humidity
|
title: New Humidity
|
||||||
data_template:
|
data_template:
|
||||||
|
|
|
@ -7,6 +7,7 @@ lvgl:
|
||||||
long_press_time: 500ms
|
long_press_time: 500ms
|
||||||
widgets:
|
widgets:
|
||||||
- label:
|
- label:
|
||||||
|
id: hello_label
|
||||||
text: Hello world
|
text: Hello world
|
||||||
text_color: 0xFF8000
|
text_color: 0xFF8000
|
||||||
align: center
|
align: center
|
||||||
|
@ -95,9 +96,43 @@ lvgl:
|
||||||
height: 10%
|
height: 10%
|
||||||
pressed:
|
pressed:
|
||||||
bg_color: light_blue
|
bg_color: light_blue
|
||||||
|
checkable: true
|
||||||
|
checked:
|
||||||
|
bg_color: 0x000000
|
||||||
widgets:
|
widgets:
|
||||||
- label:
|
- label:
|
||||||
text: Button
|
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:
|
font:
|
||||||
- file: "gfonts://Roboto"
|
- file: "gfonts://Roboto"
|
||||||
|
|
|
@ -6,6 +6,23 @@ i2c:
|
||||||
sda: GPIO18
|
sda: GPIO18
|
||||||
scl: GPIO19
|
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:
|
display:
|
||||||
- platform: ili9xxx
|
- platform: ili9xxx
|
||||||
model: st7789v
|
model: st7789v
|
||||||
|
@ -50,5 +67,9 @@ lvgl:
|
||||||
displays:
|
displays:
|
||||||
- tft_display
|
- tft_display
|
||||||
- second_display
|
- second_display
|
||||||
|
rotary_encoders:
|
||||||
|
sensor: encoder
|
||||||
|
enter_button: pushbutton
|
||||||
|
group: general
|
||||||
|
|
||||||
<<: !include common.yaml
|
<<: !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:
|
update:
|
||||||
|
- platform: http_request
|
||||||
|
name: Firmware Update
|
||||||
|
source: http://example.com/manifest.json
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
|
substitutions:
|
||||||
|
verify_ssl: "false"
|
||||||
|
|
||||||
<<: !include common.yaml
|
<<: !include common.yaml
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
|
substitutions:
|
||||||
|
verify_ssl: "false"
|
||||||
|
|
||||||
<<: !include common.yaml
|
<<: !include common.yaml
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
|
substitutions:
|
||||||
|
verify_ssl: "false"
|
||||||
|
|
||||||
<<: !include common.yaml
|
<<: !include common.yaml
|
||||||
|
|
Loading…
Reference in a new issue