mirror of
https://github.com/Threnklyn/esphome-dev.git
synced 2026-05-21 05:23:27 +02:00
3920029aff
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
264 lines
6.8 KiB
Python
264 lines
6.8 KiB
Python
import abc
|
|
import logging
|
|
from typing import Union
|
|
|
|
from esphome import codegen as cg
|
|
from esphome.core import ID, Lambda
|
|
from esphome.cpp_generator import (
|
|
AssignmentExpression,
|
|
CallExpression,
|
|
Expression,
|
|
ExpressionStatement,
|
|
LambdaExpression,
|
|
MockObj,
|
|
RawExpression,
|
|
RawStatement,
|
|
SafeExpType,
|
|
Statement,
|
|
VariableDeclarationExpression,
|
|
statement,
|
|
)
|
|
|
|
from .defines import ConstantLiteral
|
|
from .helpers import get_line_marks
|
|
from .types import lv_group_t
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
class CodeContext(abc.ABC):
|
|
"""
|
|
A class providing a context for code generation. Generated code will be added to the
|
|
current context. A new context will stack on the current context, and restore it
|
|
when done. Used with the `with` statement.
|
|
"""
|
|
|
|
code_context = None
|
|
|
|
@abc.abstractmethod
|
|
def add(self, expression: Union[Expression, Statement]):
|
|
pass
|
|
|
|
@staticmethod
|
|
def append(expression: Union[Expression, Statement]):
|
|
if CodeContext.code_context is not None:
|
|
CodeContext.code_context.add(expression)
|
|
return expression
|
|
|
|
def __init__(self):
|
|
self.previous: Union[CodeContext | None] = None
|
|
|
|
def __enter__(self):
|
|
self.previous = CodeContext.code_context
|
|
CodeContext.code_context = self
|
|
|
|
def __exit__(self, *args):
|
|
CodeContext.code_context = self.previous
|
|
|
|
|
|
class MainContext(CodeContext):
|
|
"""
|
|
Code generation into the main() function
|
|
"""
|
|
|
|
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}", "")
|
|
|
|
|
|
class LambdaContext(CodeContext):
|
|
"""
|
|
A context that will accumlate code for use in a lambda.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
parameters: list[tuple[SafeExpType, str]] = None,
|
|
return_type: SafeExpType = cg.void,
|
|
capture: str = "",
|
|
):
|
|
super().__init__()
|
|
self.code_list: list[Statement] = []
|
|
self.parameters = parameters
|
|
self.return_type = return_type
|
|
self.capture = capture
|
|
|
|
def add(self, expression: Union[Expression, Statement]):
|
|
self.code_list.append(expression)
|
|
return expression
|
|
|
|
async def get_lambda(self) -> LambdaExpression:
|
|
code_text = self.get_code()
|
|
return await cg.process_lambda(
|
|
Lambda("\n".join(code_text) + "\n\n"),
|
|
self.parameters,
|
|
capture=self.capture,
|
|
return_type=self.return_type,
|
|
)
|
|
|
|
def get_code(self):
|
|
code_text = []
|
|
for exp in self.code_list:
|
|
text = str(statement(exp))
|
|
text = text.rstrip()
|
|
code_text.append(text)
|
|
return code_text
|
|
|
|
def __enter__(self):
|
|
super().__enter__()
|
|
return self
|
|
|
|
|
|
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)
|
|
super().__init__(base, "")
|
|
self.modifier = modifier
|
|
self.rhs = rhs
|
|
|
|
def __enter__(self):
|
|
CodeContext.append(RawStatement("{"))
|
|
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
|
|
|
|
def __exit__(self, *args):
|
|
CodeContext.append(RawStatement("}"))
|
|
|
|
|
|
class MockLv:
|
|
"""
|
|
A mock object that can be used to generate LVGL calls.
|
|
"""
|
|
|
|
def __init__(self, base):
|
|
self.base = base
|
|
|
|
def __getattr__(self, attr: str) -> "MockLv":
|
|
return MockLv(f"{self.base}{attr}")
|
|
|
|
def append(self, expression):
|
|
CodeContext.append(expression)
|
|
|
|
def __call__(self, *args: SafeExpType) -> "MockObj":
|
|
call = CallExpression(self.base, *args)
|
|
result = MockObj(call, "")
|
|
self.append(result)
|
|
return result
|
|
|
|
def __str__(self):
|
|
return str(self.base)
|
|
|
|
def __repr__(self):
|
|
return f"MockLv<{str(self.base)}>"
|
|
|
|
def call(self, prop, *args):
|
|
call = CallExpression(RawExpression(f"{self.base}{prop}"), *args)
|
|
result = MockObj(call, "")
|
|
self.append(result)
|
|
return result
|
|
|
|
def cond_if(self, expression: Expression):
|
|
CodeContext.append(RawStatement(f"if {expression} {{"))
|
|
|
|
def cond_else(self):
|
|
CodeContext.append(RawStatement("} else {"))
|
|
|
|
def cond_endif(self):
|
|
CodeContext.append(RawStatement("}"))
|
|
|
|
|
|
class ReturnStatement(ExpressionStatement):
|
|
def __str__(self):
|
|
return f"return {self.expression};"
|
|
|
|
|
|
class LvExpr(MockLv):
|
|
def __getattr__(self, attr: str) -> "MockLv":
|
|
return LvExpr(f"{self.base}{attr}")
|
|
|
|
def append(self, expression):
|
|
pass
|
|
|
|
|
|
# Top level mock for generic lv_ calls to be recorded
|
|
lv = MockLv("lv_")
|
|
# Just generate an expression
|
|
lv_expr = LvExpr("lv_")
|
|
# Mock for lv_obj_ calls
|
|
lv_obj = MockLv("lv_obj_")
|
|
lvgl_comp = MockObj("lvgl_comp", "->")
|
|
|
|
|
|
# equivalent to cg.add() for the lvgl init context
|
|
def lv_add(expression: Union[Expression, Statement]):
|
|
return CodeContext.append(expression)
|
|
|
|
|
|
def add_line_marks(where):
|
|
for mark in get_line_marks(where):
|
|
lv_add(cg.RawStatement(mark))
|
|
|
|
|
|
def lv_assign(target, expression):
|
|
lv_add(RawExpression(f"{target} = {expression}"))
|
|
|
|
|
|
lv_groups = {} # Widget group names
|
|
|
|
|
|
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]
|