mirror of
https://github.com/esphome/esphome.git
synced 2025-10-06 03:43:49 +01:00
[lvgl] Stage 4 (#7166)
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import abc
|
||||
import logging
|
||||
from typing import Union
|
||||
|
||||
from esphome import codegen as cg
|
||||
from esphome.core import ID, Lambda
|
||||
from esphome.config import Config
|
||||
from esphome.core import CORE, ID, Lambda
|
||||
from esphome.cpp_generator import (
|
||||
AssignmentExpression,
|
||||
CallExpression,
|
||||
@@ -18,12 +18,47 @@ from esphome.cpp_generator import (
|
||||
VariableDeclarationExpression,
|
||||
statement,
|
||||
)
|
||||
from esphome.yaml_util import ESPHomeDataBase
|
||||
|
||||
from .defines import ConstantLiteral
|
||||
from .helpers import get_line_marks
|
||||
from .types import lv_group_t
|
||||
from .defines import literal, lvgl_ns
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
LVGL_COMP = "lv_component" # used as a lambda argument in lvgl_comp()
|
||||
|
||||
# Argument tuple for use in lambdas
|
||||
LvglComponent = lvgl_ns.class_("LvglComponent", cg.PollingComponent)
|
||||
LVGL_COMP_ARG = [(LvglComponent.operator("ptr"), LVGL_COMP)]
|
||||
lv_event_t_ptr = cg.global_ns.namespace("lv_event_t").operator("ptr")
|
||||
EVENT_ARG = [(lv_event_t_ptr, "ev")]
|
||||
CUSTOM_EVENT = literal("lvgl::lv_custom_event")
|
||||
|
||||
|
||||
def get_line_marks(value) -> list:
|
||||
"""
|
||||
If possible, return a preprocessor directive to identify the line number where the given id was defined.
|
||||
:param value: The id or other token to get the line number for
|
||||
:return: A list containing zero or more line directives
|
||||
"""
|
||||
path = None
|
||||
if isinstance(value, ESPHomeDataBase):
|
||||
path = value.esp_range
|
||||
elif isinstance(value, ID) and isinstance(CORE.config, Config):
|
||||
path = CORE.config.get_path_for_id(value)[:-1]
|
||||
path = CORE.config.get_deepest_document_range_for_path(path)
|
||||
if path is None:
|
||||
return []
|
||||
return [path.start_mark.as_line_directive]
|
||||
|
||||
|
||||
class IndentedStatement(Statement):
|
||||
def __init__(self, stmt: Statement, indent: int):
|
||||
self.statement = stmt
|
||||
self.indent = indent
|
||||
|
||||
def __str__(self):
|
||||
result = " " * self.indent * 4 + str(self.statement).strip()
|
||||
if not isinstance(self.statement, RawStatement):
|
||||
result += ";"
|
||||
return result
|
||||
|
||||
|
||||
class CodeContext(abc.ABC):
|
||||
@@ -39,6 +74,16 @@ class CodeContext(abc.ABC):
|
||||
def add(self, expression: Union[Expression, Statement]):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def start_block():
|
||||
CodeContext.append(RawStatement("{"))
|
||||
CodeContext.code_context.indent()
|
||||
|
||||
@staticmethod
|
||||
def end_block():
|
||||
CodeContext.code_context.detent()
|
||||
CodeContext.append(RawStatement("}"))
|
||||
|
||||
@staticmethod
|
||||
def append(expression: Union[Expression, Statement]):
|
||||
if CodeContext.code_context is not None:
|
||||
@@ -47,14 +92,25 @@ class CodeContext(abc.ABC):
|
||||
|
||||
def __init__(self):
|
||||
self.previous: Union[CodeContext | None] = None
|
||||
self.indent_level = 0
|
||||
|
||||
def __enter__(self):
|
||||
async def __aenter__(self):
|
||||
self.previous = CodeContext.code_context
|
||||
CodeContext.code_context = self
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
async def __aexit__(self, *args):
|
||||
CodeContext.code_context = self.previous
|
||||
|
||||
def indent(self):
|
||||
self.indent_level += 1
|
||||
|
||||
def detent(self):
|
||||
self.indent_level -= 1
|
||||
|
||||
def indented_statement(self, stmt):
|
||||
return IndentedStatement(stmt, self.indent_level)
|
||||
|
||||
|
||||
class MainContext(CodeContext):
|
||||
"""
|
||||
@@ -62,42 +118,7 @@ class MainContext(CodeContext):
|
||||
"""
|
||||
|
||||
def add(self, expression: Union[Expression, Statement]):
|
||||
return cg.add(expression)
|
||||
|
||||
|
||||
class LvContext(CodeContext):
|
||||
"""
|
||||
Code generation into the LVGL initialisation code (called in `setup()`)
|
||||
"""
|
||||
|
||||
lv_init_code: list["Statement"] = []
|
||||
|
||||
@staticmethod
|
||||
def lv_add(expression: Union[Expression, Statement]):
|
||||
if isinstance(expression, Expression):
|
||||
expression = statement(expression)
|
||||
if not isinstance(expression, Statement):
|
||||
raise ValueError(
|
||||
f"Add '{expression}' must be expression or statement, not {type(expression)}"
|
||||
)
|
||||
LvContext.lv_init_code.append(expression)
|
||||
_LOGGER.debug("LV Adding: %s", expression)
|
||||
return expression
|
||||
|
||||
@staticmethod
|
||||
def get_code():
|
||||
code = []
|
||||
for exp in LvContext.lv_init_code:
|
||||
text = str(statement(exp))
|
||||
text = text.rstrip()
|
||||
code.append(text)
|
||||
return "\n".join(code) + "\n\n"
|
||||
|
||||
def add(self, expression: Union[Expression, Statement]):
|
||||
return LvContext.lv_add(expression)
|
||||
|
||||
def set_style(self, prop):
|
||||
return MockObj("lv_set_style_{prop}", "")
|
||||
return cg.add(self.indented_statement(expression))
|
||||
|
||||
|
||||
class LambdaContext(CodeContext):
|
||||
@@ -110,21 +131,23 @@ class LambdaContext(CodeContext):
|
||||
parameters: list[tuple[SafeExpType, str]] = None,
|
||||
return_type: SafeExpType = cg.void,
|
||||
capture: str = "",
|
||||
where=None,
|
||||
):
|
||||
super().__init__()
|
||||
self.code_list: list[Statement] = []
|
||||
self.parameters = parameters
|
||||
self.parameters = parameters or []
|
||||
self.return_type = return_type
|
||||
self.capture = capture
|
||||
self.where = where
|
||||
|
||||
def add(self, expression: Union[Expression, Statement]):
|
||||
self.code_list.append(expression)
|
||||
self.code_list.append(self.indented_statement(expression))
|
||||
return expression
|
||||
|
||||
async def get_lambda(self) -> LambdaExpression:
|
||||
code_text = self.get_code()
|
||||
return await cg.process_lambda(
|
||||
Lambda("\n".join(code_text) + "\n\n"),
|
||||
Lambda("\n".join(code_text) + "\n"),
|
||||
self.parameters,
|
||||
capture=self.capture,
|
||||
return_type=self.return_type,
|
||||
@@ -138,33 +161,59 @@ class LambdaContext(CodeContext):
|
||||
code_text.append(text)
|
||||
return code_text
|
||||
|
||||
def __enter__(self):
|
||||
super().__enter__()
|
||||
async def __aenter__(self):
|
||||
await super().__aenter__()
|
||||
add_line_marks(self.where)
|
||||
return self
|
||||
|
||||
|
||||
class LvContext(LambdaContext):
|
||||
"""
|
||||
Code generation into the LVGL initialisation code (called in `setup()`)
|
||||
"""
|
||||
|
||||
def __init__(self, lv_component, args=None):
|
||||
self.args = args or LVGL_COMP_ARG
|
||||
super().__init__(parameters=self.args)
|
||||
self.lv_component = lv_component
|
||||
|
||||
async def add_init_lambda(self):
|
||||
cg.add(self.lv_component.add_init_lambda(await self.get_lambda()))
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
await super().__aexit__(exc_type, exc_val, exc_tb)
|
||||
await self.add_init_lambda()
|
||||
|
||||
def add(self, expression: Union[Expression, Statement]):
|
||||
self.code_list.append(self.indented_statement(expression))
|
||||
return expression
|
||||
|
||||
def __call__(self, *args):
|
||||
return self.add(*args)
|
||||
|
||||
|
||||
class LocalVariable(MockObj):
|
||||
"""
|
||||
Create a local variable and enclose the code using it within a block.
|
||||
"""
|
||||
|
||||
def __init__(self, name, type, modifier=None, rhs=None):
|
||||
base = ID(name, True, type)
|
||||
def __init__(self, name, type, rhs=None, modifier="*"):
|
||||
base = ID(name + "_VAR_", True, type)
|
||||
super().__init__(base, "")
|
||||
self.modifier = modifier
|
||||
self.rhs = rhs
|
||||
|
||||
def __enter__(self):
|
||||
CodeContext.append(RawStatement("{"))
|
||||
CodeContext.start_block()
|
||||
CodeContext.append(
|
||||
VariableDeclarationExpression(self.base.type, self.modifier, self.base.id)
|
||||
)
|
||||
if self.rhs is not None:
|
||||
CodeContext.append(AssignmentExpression(None, "", self.base, self.rhs))
|
||||
return self.base
|
||||
return MockObj(self.base)
|
||||
|
||||
def __exit__(self, *args):
|
||||
CodeContext.append(RawStatement("}"))
|
||||
CodeContext.end_block()
|
||||
|
||||
|
||||
class MockLv:
|
||||
@@ -199,14 +248,27 @@ class MockLv:
|
||||
self.append(result)
|
||||
return result
|
||||
|
||||
def cond_if(self, expression: Expression):
|
||||
CodeContext.append(RawStatement(f"if {expression} {{"))
|
||||
|
||||
def cond_else(self):
|
||||
class LvConditional:
|
||||
def __init__(self, condition):
|
||||
self.condition = condition
|
||||
|
||||
def __enter__(self):
|
||||
if self.condition is not None:
|
||||
CodeContext.append(RawStatement(f"if ({self.condition}) {{"))
|
||||
CodeContext.code_context.indent()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
if self.condition is not None:
|
||||
CodeContext.code_context.detent()
|
||||
CodeContext.append(RawStatement("}"))
|
||||
|
||||
def else_(self):
|
||||
assert self.condition is not None
|
||||
CodeContext.code_context.detent()
|
||||
CodeContext.append(RawStatement("} else {"))
|
||||
|
||||
def cond_endif(self):
|
||||
CodeContext.append(RawStatement("}"))
|
||||
CodeContext.code_context.indent()
|
||||
|
||||
|
||||
class ReturnStatement(ExpressionStatement):
|
||||
@@ -228,36 +290,56 @@ lv = MockLv("lv_")
|
||||
lv_expr = LvExpr("lv_")
|
||||
# Mock for lv_obj_ calls
|
||||
lv_obj = MockLv("lv_obj_")
|
||||
lvgl_comp = MockObj("lvgl_comp", "->")
|
||||
# Operations on the LVGL component
|
||||
lvgl_comp = MockObj(LVGL_COMP, "->")
|
||||
|
||||
|
||||
# equivalent to cg.add() for the lvgl init context
|
||||
# equivalent to cg.add() for the current code context
|
||||
def lv_add(expression: Union[Expression, Statement]):
|
||||
return CodeContext.append(expression)
|
||||
|
||||
|
||||
def add_line_marks(where):
|
||||
"""
|
||||
Add line marks for the current code context
|
||||
:param where: An object to identify the source of the line marks
|
||||
:return:
|
||||
"""
|
||||
for mark in get_line_marks(where):
|
||||
lv_add(cg.RawStatement(mark))
|
||||
|
||||
|
||||
def lv_assign(target, expression):
|
||||
lv_add(RawExpression(f"{target} = {expression}"))
|
||||
lv_add(AssignmentExpression("", "", target, expression))
|
||||
|
||||
|
||||
lv_groups = {} # Widget group names
|
||||
def lv_Pvariable(type, name):
|
||||
"""
|
||||
Create but do not initialise a pointer variable
|
||||
:param type: Type of the variable target
|
||||
:param name: name of the variable, or an ID
|
||||
:return: A MockObj of the variable
|
||||
"""
|
||||
if isinstance(name, str):
|
||||
name = ID(name, True, type)
|
||||
decl = VariableDeclarationExpression(type, "*", name)
|
||||
CORE.add_global(decl)
|
||||
var = MockObj(name, "->")
|
||||
CORE.register_variable(name, var)
|
||||
return var
|
||||
|
||||
|
||||
def add_group(name):
|
||||
if name is None:
|
||||
return None
|
||||
fullname = f"lv_esp_group_{name}"
|
||||
if name not in lv_groups:
|
||||
gid = ID(fullname, True, type=lv_group_t.operator("ptr"))
|
||||
lv_add(
|
||||
AssignmentExpression(
|
||||
type_=gid.type, modifier="", name=fullname, rhs=lv_expr.group_create()
|
||||
)
|
||||
)
|
||||
lv_groups[name] = ConstantLiteral(fullname)
|
||||
return lv_groups[name]
|
||||
def lv_variable(type, name):
|
||||
"""
|
||||
Create but do not initialise a variable
|
||||
:param type: Type of the variable target
|
||||
:param name: name of the variable, or an ID
|
||||
:return: A MockObj of the variable
|
||||
"""
|
||||
if isinstance(name, str):
|
||||
name = ID(name, True, type)
|
||||
decl = VariableDeclarationExpression(type, "", name)
|
||||
CORE.add_global(decl)
|
||||
var = MockObj(name, ".")
|
||||
CORE.register_variable(name, var)
|
||||
return var
|
||||
|
Reference in New Issue
Block a user