1
0
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:
Clyde Stubbs
2024-08-05 15:07:05 +10:00
committed by GitHub
parent 87944f0c1b
commit d18bb34f87
28 changed files with 2002 additions and 579 deletions

View File

@@ -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