mirror of
https://github.com/esphome/esphome.git
synced 2025-09-22 05:02:23 +01:00
Merge branch 'dev' into multi_device
This commit is contained in:
@@ -893,8 +893,28 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
|||||||
|
|
||||||
ParsedFrame frame;
|
ParsedFrame frame;
|
||||||
aerr = try_read_frame_(&frame);
|
aerr = try_read_frame_(&frame);
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK) {
|
||||||
|
if (aerr == APIError::BAD_INDICATOR) {
|
||||||
|
// Make sure to tell the remote that we don't
|
||||||
|
// understand the indicator byte so it knows
|
||||||
|
// we do not support it.
|
||||||
|
struct iovec iov[1];
|
||||||
|
// The \x00 first byte is the marker for plaintext.
|
||||||
|
//
|
||||||
|
// The remote will know how to handle the indicator byte,
|
||||||
|
// but it likely won't understand the rest of the message.
|
||||||
|
//
|
||||||
|
// We must send at least 3 bytes to be read, so we add
|
||||||
|
// a message after the indicator byte to ensures its long
|
||||||
|
// enough and can aid in debugging.
|
||||||
|
const char msg[] = "\x00"
|
||||||
|
"Bad indicator byte";
|
||||||
|
iov[0].iov_base = (void *) msg;
|
||||||
|
iov[0].iov_len = 19;
|
||||||
|
write_raw_(iov, 1);
|
||||||
|
}
|
||||||
return aerr;
|
return aerr;
|
||||||
|
}
|
||||||
|
|
||||||
buffer->container = std::move(frame.msg);
|
buffer->container = std::move(frame.msg);
|
||||||
buffer->data_offset = 0;
|
buffer->data_offset = 0;
|
||||||
|
@@ -10,6 +10,7 @@ from esphome.const import (
|
|||||||
CONF_GROUP,
|
CONF_GROUP,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_LAMBDA,
|
CONF_LAMBDA,
|
||||||
|
CONF_ON_BOOT,
|
||||||
CONF_ON_IDLE,
|
CONF_ON_IDLE,
|
||||||
CONF_PAGES,
|
CONF_PAGES,
|
||||||
CONF_TIMEOUT,
|
CONF_TIMEOUT,
|
||||||
@@ -38,19 +39,18 @@ from .lvcode import LvContext, LvglComponent, lvgl_static
|
|||||||
from .schemas import (
|
from .schemas import (
|
||||||
DISP_BG_SCHEMA,
|
DISP_BG_SCHEMA,
|
||||||
FLEX_OBJ_SCHEMA,
|
FLEX_OBJ_SCHEMA,
|
||||||
|
FULL_STYLE_SCHEMA,
|
||||||
GRID_CELL_SCHEMA,
|
GRID_CELL_SCHEMA,
|
||||||
LAYOUT_SCHEMAS,
|
LAYOUT_SCHEMAS,
|
||||||
STYLE_SCHEMA,
|
|
||||||
WIDGET_TYPES,
|
WIDGET_TYPES,
|
||||||
any_widget_schema,
|
any_widget_schema,
|
||||||
container_schema,
|
container_schema,
|
||||||
create_modify_schema,
|
create_modify_schema,
|
||||||
grid_alignments,
|
|
||||||
obj_schema,
|
obj_schema,
|
||||||
)
|
)
|
||||||
from .styles import add_top_layer, styles_to_code, theme_to_code
|
from .styles import add_top_layer, styles_to_code, theme_to_code
|
||||||
from .touchscreens import touchscreen_schema, touchscreens_to_code
|
from .touchscreens import touchscreen_schema, touchscreens_to_code
|
||||||
from .trigger import generate_triggers
|
from .trigger import add_on_boot_triggers, generate_triggers
|
||||||
from .types import (
|
from .types import (
|
||||||
FontEngine,
|
FontEngine,
|
||||||
IdleTrigger,
|
IdleTrigger,
|
||||||
@@ -73,6 +73,7 @@ from .widgets.animimg import animimg_spec
|
|||||||
from .widgets.arc import arc_spec
|
from .widgets.arc import arc_spec
|
||||||
from .widgets.button import button_spec
|
from .widgets.button import button_spec
|
||||||
from .widgets.buttonmatrix import buttonmatrix_spec
|
from .widgets.buttonmatrix import buttonmatrix_spec
|
||||||
|
from .widgets.canvas import canvas_spec
|
||||||
from .widgets.checkbox import checkbox_spec
|
from .widgets.checkbox import checkbox_spec
|
||||||
from .widgets.dropdown import dropdown_spec
|
from .widgets.dropdown import dropdown_spec
|
||||||
from .widgets.img import img_spec
|
from .widgets.img import img_spec
|
||||||
@@ -125,6 +126,7 @@ for w_type in (
|
|||||||
keyboard_spec,
|
keyboard_spec,
|
||||||
tileview_spec,
|
tileview_spec,
|
||||||
qr_code_spec,
|
qr_code_spec,
|
||||||
|
canvas_spec,
|
||||||
):
|
):
|
||||||
WIDGET_TYPES[w_type.name] = w_type
|
WIDGET_TYPES[w_type.name] = w_type
|
||||||
|
|
||||||
@@ -365,6 +367,7 @@ async def to_code(configs):
|
|||||||
conf[CONF_TRIGGER_ID], lv_component, False
|
conf[CONF_TRIGGER_ID], lv_component, False
|
||||||
)
|
)
|
||||||
await build_automation(resume_trigger, [], conf)
|
await build_automation(resume_trigger, [], conf)
|
||||||
|
await add_on_boot_triggers(config.get(CONF_ON_BOOT, ()))
|
||||||
|
|
||||||
# This must be done after all widgets are created
|
# This must be done after all widgets are created
|
||||||
for comp in helpers.lvgl_components_required:
|
for comp in helpers.lvgl_components_required:
|
||||||
@@ -373,6 +376,7 @@ async def to_code(configs):
|
|||||||
add_define("LV_COLOR_SCREEN_TRANSP", "1")
|
add_define("LV_COLOR_SCREEN_TRANSP", "1")
|
||||||
for use in helpers.lv_uses:
|
for use in helpers.lv_uses:
|
||||||
add_define(f"LV_USE_{use.upper()}")
|
add_define(f"LV_USE_{use.upper()}")
|
||||||
|
cg.add_define(f"USE_LVGL_{use.upper()}")
|
||||||
lv_conf_h_file = CORE.relative_src_path(LV_CONF_FILENAME)
|
lv_conf_h_file = CORE.relative_src_path(LV_CONF_FILENAME)
|
||||||
write_file_if_changed(lv_conf_h_file, generate_lv_conf_h())
|
write_file_if_changed(lv_conf_h_file, generate_lv_conf_h())
|
||||||
cg.add_build_flag("-DLV_CONF_H=1")
|
cg.add_build_flag("-DLV_CONF_H=1")
|
||||||
@@ -418,15 +422,8 @@ LVGL_SCHEMA = cv.All(
|
|||||||
"big_endian", "little_endian"
|
"big_endian", "little_endian"
|
||||||
),
|
),
|
||||||
cv.Optional(df.CONF_STYLE_DEFINITIONS): cv.ensure_list(
|
cv.Optional(df.CONF_STYLE_DEFINITIONS): cv.ensure_list(
|
||||||
cv.Schema({cv.Required(CONF_ID): cv.declare_id(lv_style_t)})
|
cv.Schema({cv.Required(CONF_ID): cv.declare_id(lv_style_t)}).extend(
|
||||||
.extend(STYLE_SCHEMA)
|
FULL_STYLE_SCHEMA
|
||||||
.extend(
|
|
||||||
{
|
|
||||||
cv.Optional(df.CONF_GRID_CELL_X_ALIGN): grid_alignments,
|
|
||||||
cv.Optional(df.CONF_GRID_CELL_Y_ALIGN): grid_alignments,
|
|
||||||
cv.Optional(df.CONF_PAD_ROW): lvalid.pixels,
|
|
||||||
cv.Optional(df.CONF_PAD_COLUMN): lvalid.pixels,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_ON_IDLE): validate_automation(
|
cv.Optional(CONF_ON_IDLE): validate_automation(
|
||||||
|
@@ -17,6 +17,7 @@ from .defines import (
|
|||||||
CONF_SHOW_SNOW,
|
CONF_SHOW_SNOW,
|
||||||
PARTS,
|
PARTS,
|
||||||
literal,
|
literal,
|
||||||
|
static_cast,
|
||||||
)
|
)
|
||||||
from .lv_validation import lv_bool, lv_color, lv_image, opacity
|
from .lv_validation import lv_bool, lv_color, lv_image, opacity
|
||||||
from .lvcode import (
|
from .lvcode import (
|
||||||
@@ -32,7 +33,6 @@ from .lvcode import (
|
|||||||
lv_expr,
|
lv_expr,
|
||||||
lv_obj,
|
lv_obj,
|
||||||
lvgl_comp,
|
lvgl_comp,
|
||||||
static_cast,
|
|
||||||
)
|
)
|
||||||
from .schemas import DISP_BG_SCHEMA, LIST_ACTION_SCHEMA, LVGL_SCHEMA, base_update_schema
|
from .schemas import DISP_BG_SCHEMA, LIST_ACTION_SCHEMA, LVGL_SCHEMA, base_update_schema
|
||||||
from .types import (
|
from .types import (
|
||||||
|
@@ -29,12 +29,16 @@ def add_define(macro, value="1"):
|
|||||||
lv_defines[macro] = value
|
lv_defines[macro] = value
|
||||||
|
|
||||||
|
|
||||||
def literal(arg):
|
def literal(arg) -> MockObj:
|
||||||
if isinstance(arg, str):
|
if isinstance(arg, str):
|
||||||
return MockObj(arg)
|
return MockObj(arg)
|
||||||
return arg
|
return arg
|
||||||
|
|
||||||
|
|
||||||
|
def static_cast(type, value):
|
||||||
|
return literal(f"static_cast<{type}>({value})")
|
||||||
|
|
||||||
|
|
||||||
def call_lambda(lamb: LambdaExpression):
|
def call_lambda(lamb: LambdaExpression):
|
||||||
expr = lamb.content.strip()
|
expr = lamb.content.strip()
|
||||||
if expr.startswith("return") and expr.endswith(";"):
|
if expr.startswith("return") and expr.endswith(";"):
|
||||||
|
@@ -254,11 +254,27 @@ def pixels_or_percent_validator(value):
|
|||||||
pixels_or_percent = LValidator(pixels_or_percent_validator, uint32, retmapper=literal)
|
pixels_or_percent = LValidator(pixels_or_percent_validator, uint32, retmapper=literal)
|
||||||
|
|
||||||
|
|
||||||
def zoom(value):
|
def pixels_validator(value):
|
||||||
|
if isinstance(value, str) and value.lower().endswith("px"):
|
||||||
|
value = value[:-2]
|
||||||
|
return cv.positive_int(value)
|
||||||
|
|
||||||
|
|
||||||
|
pixels = LValidator(pixels_validator, uint32, retmapper=literal)
|
||||||
|
|
||||||
|
|
||||||
|
def zoom_validator(value):
|
||||||
value = cv.float_range(0.1, 10.0)(value)
|
value = cv.float_range(0.1, 10.0)(value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def zoom_retmapper(value):
|
||||||
return int(value * 256)
|
return int(value * 256)
|
||||||
|
|
||||||
|
|
||||||
|
zoom = LValidator(zoom_validator, uint32, retmapper=zoom_retmapper)
|
||||||
|
|
||||||
|
|
||||||
def angle(value):
|
def angle(value):
|
||||||
"""
|
"""
|
||||||
Validation for an angle in degrees, converted to an integer representing 0.1deg units
|
Validation for an angle in degrees, converted to an integer representing 0.1deg units
|
||||||
@@ -286,14 +302,6 @@ def size_validator(value):
|
|||||||
size = LValidator(size_validator, uint32, retmapper=literal)
|
size = LValidator(size_validator, uint32, retmapper=literal)
|
||||||
|
|
||||||
|
|
||||||
def pixels_validator(value):
|
|
||||||
if isinstance(value, str) and value.lower().endswith("px"):
|
|
||||||
return cv.int_(value[:-2])
|
|
||||||
return cv.int_(value)
|
|
||||||
|
|
||||||
|
|
||||||
pixels = LValidator(pixels_validator, uint32, retmapper=literal)
|
|
||||||
|
|
||||||
radius_consts = LvConstant("LV_RADIUS_", "CIRCLE")
|
radius_consts = LvConstant("LV_RADIUS_", "CIRCLE")
|
||||||
|
|
||||||
|
|
||||||
|
@@ -206,11 +206,16 @@ class LocalVariable(MockObj):
|
|||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
CodeContext.start_block()
|
CodeContext.start_block()
|
||||||
CodeContext.append(
|
|
||||||
VariableDeclarationExpression(self.base.type, self.modifier, self.base.id)
|
|
||||||
)
|
|
||||||
if self.rhs is not None:
|
if self.rhs is not None:
|
||||||
CodeContext.append(AssignmentExpression(None, "", self.base, self.rhs))
|
CodeContext.append(
|
||||||
|
AssignmentExpression(self.base.type, self.modifier, self.base, self.rhs)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
CodeContext.append(
|
||||||
|
VariableDeclarationExpression(
|
||||||
|
self.base.type, self.modifier, self.base.id
|
||||||
|
)
|
||||||
|
)
|
||||||
return MockObj(self.base)
|
return MockObj(self.base)
|
||||||
|
|
||||||
def __exit__(self, *args):
|
def __exit__(self, *args):
|
||||||
@@ -285,10 +290,6 @@ class LvExpr(MockLv):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def static_cast(type, value):
|
|
||||||
return literal(f"static_cast<{type}>({value})")
|
|
||||||
|
|
||||||
|
|
||||||
# Top level mock for generic lv_ calls to be recorded
|
# Top level mock for generic lv_ calls to be recorded
|
||||||
lv = MockLv("lv_")
|
lv = MockLv("lv_")
|
||||||
# Just generate an expression
|
# Just generate an expression
|
||||||
|
@@ -63,6 +63,13 @@ inline void lv_disp_set_bg_image(lv_disp_t *disp, esphome::image::Image *image)
|
|||||||
inline void lv_obj_set_style_bg_img_src(lv_obj_t *obj, esphome::image::Image *image, lv_style_selector_t selector) {
|
inline void lv_obj_set_style_bg_img_src(lv_obj_t *obj, esphome::image::Image *image, lv_style_selector_t selector) {
|
||||||
lv_obj_set_style_bg_img_src(obj, image->get_lv_img_dsc(), selector);
|
lv_obj_set_style_bg_img_src(obj, image->get_lv_img_dsc(), selector);
|
||||||
}
|
}
|
||||||
|
#ifdef USE_LVGL_CANVAS
|
||||||
|
inline void lv_canvas_draw_img(lv_obj_t *canvas, lv_coord_t x, lv_coord_t y, image::Image *image,
|
||||||
|
lv_draw_img_dsc_t *dsc) {
|
||||||
|
lv_canvas_draw_img(canvas, x, y, image->get_lv_img_dsc(), dsc);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_LVGL_METER
|
#ifdef USE_LVGL_METER
|
||||||
inline lv_meter_indicator_t *lv_meter_add_needle_img(lv_obj_t *obj, lv_meter_scale_t *scale, esphome::image::Image *src,
|
inline lv_meter_indicator_t *lv_meter_add_needle_img(lv_obj_t *obj, lv_meter_scale_t *scale, esphome::image::Image *src,
|
||||||
lv_coord_t pivot_x, lv_coord_t pivot_y) {
|
lv_coord_t pivot_x, lv_coord_t pivot_y) {
|
||||||
@@ -90,6 +97,7 @@ inline void lv_animimg_set_src(lv_obj_t *img, std::vector<image::Image *> images
|
|||||||
// Parent class for things that wrap an LVGL object
|
// Parent class for things that wrap an LVGL object
|
||||||
class LvCompound {
|
class LvCompound {
|
||||||
public:
|
public:
|
||||||
|
virtual ~LvCompound() = default;
|
||||||
virtual void set_obj(lv_obj_t *lv_obj) { this->obj = lv_obj; }
|
virtual void set_obj(lv_obj_t *lv_obj) { this->obj = lv_obj; }
|
||||||
lv_obj_t *obj{};
|
lv_obj_t *obj{};
|
||||||
};
|
};
|
||||||
@@ -330,6 +338,19 @@ class LVEncoderListener : public Parented<LvglComponent> {
|
|||||||
};
|
};
|
||||||
#endif // USE_LVGL_KEY_LISTENER
|
#endif // USE_LVGL_KEY_LISTENER
|
||||||
|
|
||||||
|
#ifdef USE_LVGL_LINE
|
||||||
|
class LvLineType : public LvCompound {
|
||||||
|
public:
|
||||||
|
std::vector<lv_point_t> get_points() { return this->points_; }
|
||||||
|
void set_points(std::vector<lv_point_t> points) {
|
||||||
|
this->points_ = std::move(points);
|
||||||
|
lv_line_set_points(this->obj, this->points_.data(), this->points_.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<lv_point_t> points_{};
|
||||||
|
};
|
||||||
|
#endif
|
||||||
#if defined(USE_LVGL_DROPDOWN) || defined(LV_USE_ROLLER)
|
#if defined(USE_LVGL_DROPDOWN) || defined(LV_USE_ROLLER)
|
||||||
class LvSelectable : public LvCompound {
|
class LvSelectable : public LvCompound {
|
||||||
public:
|
public:
|
||||||
|
@@ -6,6 +6,7 @@ from esphome.const import (
|
|||||||
CONF_FORMAT,
|
CONF_FORMAT,
|
||||||
CONF_GROUP,
|
CONF_GROUP,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
|
CONF_ON_BOOT,
|
||||||
CONF_ON_VALUE,
|
CONF_ON_VALUE,
|
||||||
CONF_STATE,
|
CONF_STATE,
|
||||||
CONF_TEXT,
|
CONF_TEXT,
|
||||||
@@ -14,10 +15,11 @@ from esphome.const import (
|
|||||||
CONF_TYPE,
|
CONF_TYPE,
|
||||||
)
|
)
|
||||||
from esphome.core import TimePeriod
|
from esphome.core import TimePeriod
|
||||||
|
from esphome.core.config import StartupTrigger
|
||||||
from esphome.schema_extractors import SCHEMA_EXTRACT
|
from esphome.schema_extractors import SCHEMA_EXTRACT
|
||||||
|
|
||||||
from . import defines as df, lv_validation as lvalid
|
from . import defines as df, lv_validation as lvalid
|
||||||
from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR
|
from .defines import CONF_TIME_FORMAT, CONF_X, CONF_Y, LV_GRAD_DIR
|
||||||
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_color, lv_font, lv_gradient, lv_image, opacity
|
from .lv_validation import lv_color, lv_font, lv_gradient, lv_image, opacity
|
||||||
from .lvcode import LvglComponent, lv_event_t_ptr
|
from .lvcode import LvglComponent, lv_event_t_ptr
|
||||||
@@ -85,6 +87,31 @@ ENCODER_SCHEMA = cv.Schema(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
POINT_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_X): cv.templatable(cv.int_),
|
||||||
|
cv.Required(CONF_Y): cv.templatable(cv.int_),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def point_schema(value):
|
||||||
|
"""
|
||||||
|
A shorthand for a point in the form of x,y
|
||||||
|
:param value: The value to check
|
||||||
|
:return: The value as a tuple of x,y
|
||||||
|
"""
|
||||||
|
if isinstance(value, dict):
|
||||||
|
return POINT_SCHEMA(value)
|
||||||
|
try:
|
||||||
|
x, y = map(int, value.split(","))
|
||||||
|
return {CONF_X: x, CONF_Y: y}
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
# not raising this in the catch block because pylint doesn't like it
|
||||||
|
raise cv.Invalid("Invalid point format, should be <x_int>, <y_int>")
|
||||||
|
|
||||||
|
|
||||||
# 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,
|
||||||
@@ -103,6 +130,7 @@ STYLE_PROPS = {
|
|||||||
"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_image_src": lvalid.lv_image,
|
||||||
|
"bg_image_tiled": lvalid.lv_bool,
|
||||||
"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,
|
||||||
@@ -117,9 +145,9 @@ STYLE_PROPS = {
|
|||||||
"height": lvalid.size,
|
"height": lvalid.size,
|
||||||
"image_recolor": lvalid.lv_color,
|
"image_recolor": lvalid.lv_color,
|
||||||
"image_recolor_opa": lvalid.opacity,
|
"image_recolor_opa": lvalid.opacity,
|
||||||
"line_width": cv.positive_int,
|
"line_width": lvalid.lv_positive_int,
|
||||||
"line_dash_width": cv.positive_int,
|
"line_dash_width": lvalid.lv_positive_int,
|
||||||
"line_dash_gap": cv.positive_int,
|
"line_dash_gap": lvalid.lv_positive_int,
|
||||||
"line_rounded": lvalid.lv_bool,
|
"line_rounded": lvalid.lv_bool,
|
||||||
"line_color": lvalid.lv_color,
|
"line_color": lvalid.lv_color,
|
||||||
"opa": lvalid.opacity,
|
"opa": lvalid.opacity,
|
||||||
@@ -147,8 +175,8 @@ STYLE_PROPS = {
|
|||||||
"LV_TEXT_DECOR_", "NONE", "UNDERLINE", "STRIKETHROUGH"
|
"LV_TEXT_DECOR_", "NONE", "UNDERLINE", "STRIKETHROUGH"
|
||||||
).several_of,
|
).several_of,
|
||||||
"text_font": lv_font,
|
"text_font": lv_font,
|
||||||
"text_letter_space": cv.positive_int,
|
"text_letter_space": lvalid.lv_positive_int,
|
||||||
"text_line_space": cv.positive_int,
|
"text_line_space": lvalid.lv_positive_int,
|
||||||
"text_opa": lvalid.opacity,
|
"text_opa": lvalid.opacity,
|
||||||
"transform_angle": lvalid.lv_angle,
|
"transform_angle": lvalid.lv_angle,
|
||||||
"transform_height": lvalid.pixels_or_percent,
|
"transform_height": lvalid.pixels_or_percent,
|
||||||
@@ -172,10 +200,15 @@ STYLE_REMAP = {
|
|||||||
"bg_image_recolor": "bg_img_recolor",
|
"bg_image_recolor": "bg_img_recolor",
|
||||||
"bg_image_recolor_opa": "bg_img_recolor_opa",
|
"bg_image_recolor_opa": "bg_img_recolor_opa",
|
||||||
"bg_image_src": "bg_img_src",
|
"bg_image_src": "bg_img_src",
|
||||||
|
"bg_image_tiled": "bg_img_tiled",
|
||||||
"image_recolor": "img_recolor",
|
"image_recolor": "img_recolor",
|
||||||
"image_recolor_opa": "img_recolor_opa",
|
"image_recolor_opa": "img_recolor_opa",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cell_alignments = df.LV_CELL_ALIGNMENTS.one_of
|
||||||
|
grid_alignments = df.LV_GRID_ALIGNMENTS.one_of
|
||||||
|
flex_alignments = df.LV_FLEX_ALIGNMENTS.one_of
|
||||||
|
|
||||||
# Complete object style schema
|
# Complete object style schema
|
||||||
STYLE_SCHEMA = cv.Schema({cv.Optional(k): v for k, v in STYLE_PROPS.items()}).extend(
|
STYLE_SCHEMA = cv.Schema({cv.Optional(k): v for k, v in STYLE_PROPS.items()}).extend(
|
||||||
{
|
{
|
||||||
@@ -186,6 +219,16 @@ STYLE_SCHEMA = cv.Schema({cv.Optional(k): v for k, v in STYLE_PROPS.items()}).ex
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Also allow widget specific properties for use in style definitions
|
||||||
|
FULL_STYLE_SCHEMA = STYLE_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.Optional(df.CONF_GRID_CELL_X_ALIGN): grid_alignments,
|
||||||
|
cv.Optional(df.CONF_GRID_CELL_Y_ALIGN): grid_alignments,
|
||||||
|
cv.Optional(df.CONF_PAD_ROW): lvalid.pixels,
|
||||||
|
cv.Optional(df.CONF_PAD_COLUMN): lvalid.pixels,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Object states. Top level properties apply to MAIN
|
# Object states. Top level properties apply to MAIN
|
||||||
STATE_SCHEMA = cv.Schema(
|
STATE_SCHEMA = cv.Schema(
|
||||||
{cv.Optional(state): STYLE_SCHEMA for state in df.STATES}
|
{cv.Optional(state): STYLE_SCHEMA for state in df.STATES}
|
||||||
@@ -216,14 +259,24 @@ def automation_schema(typ: LvType):
|
|||||||
events = events + (CONF_ON_VALUE,)
|
events = events + (CONF_ON_VALUE,)
|
||||||
args = typ.get_arg_type() if isinstance(typ, LvType) else []
|
args = typ.get_arg_type() if isinstance(typ, LvType) else []
|
||||||
args.append(lv_event_t_ptr)
|
args.append(lv_event_t_ptr)
|
||||||
return {
|
return cv.Schema(
|
||||||
cv.Optional(event): validate_automation(
|
{
|
||||||
{
|
cv.Optional(event): validate_automation(
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger.template(*args)),
|
{
|
||||||
}
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||||
)
|
Trigger.template(*args)
|
||||||
for event in events
|
),
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
for event in events
|
||||||
|
}
|
||||||
|
).extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_ON_BOOT): validate_automation(
|
||||||
|
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StartupTrigger)}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def base_update_schema(widget_type, parts):
|
def base_update_schema(widget_type, parts):
|
||||||
@@ -307,10 +360,6 @@ grid_spec = cv.Any(
|
|||||||
lvalid.size, df.LvConstant("LV_GRID_", "CONTENT").one_of, grid_free_space
|
lvalid.size, df.LvConstant("LV_GRID_", "CONTENT").one_of, grid_free_space
|
||||||
)
|
)
|
||||||
|
|
||||||
cell_alignments = df.LV_CELL_ALIGNMENTS.one_of
|
|
||||||
grid_alignments = df.LV_GRID_ALIGNMENTS.one_of
|
|
||||||
flex_alignments = df.LV_FLEX_ALIGNMENTS.one_of
|
|
||||||
|
|
||||||
LAYOUT_SCHEMA = {
|
LAYOUT_SCHEMA = {
|
||||||
cv.Optional(df.CONF_LAYOUT): cv.typed_schema(
|
cv.Optional(df.CONF_LAYOUT): cv.typed_schema(
|
||||||
{
|
{
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_ID
|
||||||
from esphome.core import ID
|
from esphome.core import ID
|
||||||
from esphome.cpp_generator import MockObj
|
from esphome.cpp_generator import MockObj
|
||||||
@@ -12,25 +14,54 @@ from .defines import (
|
|||||||
)
|
)
|
||||||
from .helpers import add_lv_use
|
from .helpers import add_lv_use
|
||||||
from .lvcode import LambdaContext, LocalVariable, lv, lv_assign, lv_variable
|
from .lvcode import LambdaContext, LocalVariable, lv, lv_assign, lv_variable
|
||||||
from .schemas import ALL_STYLES, STYLE_REMAP
|
from .schemas import ALL_STYLES, FULL_STYLE_SCHEMA, STYLE_REMAP
|
||||||
from .types import lv_lambda_t, lv_obj_t, lv_obj_t_ptr
|
from .types import ObjUpdateAction, lv_lambda_t, lv_obj_t, lv_obj_t_ptr, lv_style_t
|
||||||
from .widgets import Widget, add_widgets, set_obj_properties, theme_widget_map
|
from .widgets import (
|
||||||
|
Widget,
|
||||||
|
add_widgets,
|
||||||
|
set_obj_properties,
|
||||||
|
theme_widget_map,
|
||||||
|
wait_for_widgets,
|
||||||
|
)
|
||||||
from .widgets.obj import obj_spec
|
from .widgets.obj import obj_spec
|
||||||
|
|
||||||
|
|
||||||
|
async def style_set(svar, style):
|
||||||
|
for prop, validator in ALL_STYLES.items():
|
||||||
|
if (value := style.get(prop)) is not None:
|
||||||
|
if isinstance(validator, LValidator):
|
||||||
|
value = await validator.process(value)
|
||||||
|
if isinstance(value, list):
|
||||||
|
value = "|".join(value)
|
||||||
|
remapped_prop = STYLE_REMAP.get(prop, prop)
|
||||||
|
lv.call(f"style_set_{remapped_prop}", svar, literal(value))
|
||||||
|
|
||||||
|
|
||||||
async def styles_to_code(config):
|
async def styles_to_code(config):
|
||||||
"""Convert styles to C__ code."""
|
"""Convert styles to C__ code."""
|
||||||
for style in config.get(CONF_STYLE_DEFINITIONS, ()):
|
for style in config.get(CONF_STYLE_DEFINITIONS, ()):
|
||||||
svar = cg.new_Pvariable(style[CONF_ID])
|
svar = cg.new_Pvariable(style[CONF_ID])
|
||||||
lv.style_init(svar)
|
lv.style_init(svar)
|
||||||
for prop, validator in ALL_STYLES.items():
|
await style_set(svar, style)
|
||||||
if (value := style.get(prop)) is not None:
|
|
||||||
if isinstance(validator, LValidator):
|
|
||||||
value = await validator.process(value)
|
@automation.register_action(
|
||||||
if isinstance(value, list):
|
"lvgl.style.update",
|
||||||
value = "|".join(value)
|
ObjUpdateAction,
|
||||||
remapped_prop = STYLE_REMAP.get(prop, prop)
|
FULL_STYLE_SCHEMA.extend(
|
||||||
lv.call(f"style_set_{remapped_prop}", svar, literal(value))
|
{
|
||||||
|
cv.Required(CONF_ID): cv.use_id(lv_style_t),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async def style_update_to_code(config, action_id, template_arg, args):
|
||||||
|
await wait_for_widgets()
|
||||||
|
style = await cg.get_variable(config[CONF_ID])
|
||||||
|
async with LambdaContext(parameters=args, where=action_id) as context:
|
||||||
|
await style_set(style, config)
|
||||||
|
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda())
|
||||||
|
return var
|
||||||
|
|
||||||
|
|
||||||
async def theme_to_code(config):
|
async def theme_to_code(config):
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
from esphome import automation
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.const import CONF_ID, CONF_ON_VALUE, CONF_TRIGGER_ID
|
from esphome.const import CONF_ID, CONF_ON_BOOT, CONF_ON_VALUE, CONF_TRIGGER_ID
|
||||||
|
|
||||||
from .defines import (
|
from .defines import (
|
||||||
CONF_ALIGN,
|
CONF_ALIGN,
|
||||||
@@ -28,6 +28,13 @@ from .types import LV_EVENT
|
|||||||
from .widgets import LvScrActType, get_scr_act, widget_map
|
from .widgets import LvScrActType, get_scr_act, widget_map
|
||||||
|
|
||||||
|
|
||||||
|
async def add_on_boot_triggers(triggers):
|
||||||
|
for conf in triggers:
|
||||||
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], 390)
|
||||||
|
await cg.register_component(trigger, conf)
|
||||||
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
|
||||||
|
|
||||||
async def generate_triggers():
|
async def generate_triggers():
|
||||||
"""
|
"""
|
||||||
Generate LVGL triggers for all defined widgets
|
Generate LVGL triggers for all defined widgets
|
||||||
@@ -75,6 +82,8 @@ async def generate_triggers():
|
|||||||
UPDATE_EVENT,
|
UPDATE_EVENT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await add_on_boot_triggers(w.config.get(CONF_ON_BOOT, ()))
|
||||||
|
|
||||||
# Generate align to directives while we're here
|
# Generate align to directives while we're here
|
||||||
if align_to := w.config.get(CONF_ALIGN_TO):
|
if align_to := w.config.get(CONF_ALIGN_TO):
|
||||||
target = widget_map[align_to[CONF_ID]].obj
|
target = widget_map[align_to[CONF_ID]].obj
|
||||||
|
403
esphome/components/lvgl/widgets/canvas.py
Normal file
403
esphome/components/lvgl/widgets/canvas.py
Normal file
@@ -0,0 +1,403 @@
|
|||||||
|
from esphome import automation, codegen as cg, config_validation as cv
|
||||||
|
from esphome.components.display_menu_base import CONF_LABEL
|
||||||
|
from esphome.const import CONF_COLOR, CONF_HEIGHT, CONF_ID, CONF_TEXT, CONF_WIDTH
|
||||||
|
from esphome.cpp_generator import Literal, MockObj
|
||||||
|
|
||||||
|
from ..automation import action_to_code
|
||||||
|
from ..defines import (
|
||||||
|
CONF_END_ANGLE,
|
||||||
|
CONF_MAIN,
|
||||||
|
CONF_OPA,
|
||||||
|
CONF_PIVOT_X,
|
||||||
|
CONF_PIVOT_Y,
|
||||||
|
CONF_POINTS,
|
||||||
|
CONF_SRC,
|
||||||
|
CONF_START_ANGLE,
|
||||||
|
CONF_X,
|
||||||
|
CONF_Y,
|
||||||
|
literal,
|
||||||
|
)
|
||||||
|
from ..lv_validation import (
|
||||||
|
lv_angle,
|
||||||
|
lv_bool,
|
||||||
|
lv_color,
|
||||||
|
lv_image,
|
||||||
|
lv_text,
|
||||||
|
opacity,
|
||||||
|
pixels,
|
||||||
|
size,
|
||||||
|
)
|
||||||
|
from ..lvcode import LocalVariable, lv, lv_assign
|
||||||
|
from ..schemas import STYLE_PROPS, STYLE_REMAP, TEXT_SCHEMA, point_schema
|
||||||
|
from ..types import LvType, ObjUpdateAction, WidgetType
|
||||||
|
from . import Widget, get_widgets
|
||||||
|
from .line import lv_point_t, process_coord
|
||||||
|
|
||||||
|
CONF_CANVAS = "canvas"
|
||||||
|
CONF_BUFFER_ID = "buffer_id"
|
||||||
|
CONF_MAX_WIDTH = "max_width"
|
||||||
|
CONF_TRANSPARENT = "transparent"
|
||||||
|
CONF_RADIUS = "radius"
|
||||||
|
|
||||||
|
lv_canvas_t = LvType("lv_canvas_t")
|
||||||
|
|
||||||
|
|
||||||
|
class CanvasType(WidgetType):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(
|
||||||
|
CONF_CANVAS,
|
||||||
|
lv_canvas_t,
|
||||||
|
(CONF_MAIN,),
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_WIDTH): size,
|
||||||
|
cv.Required(CONF_HEIGHT): size,
|
||||||
|
cv.Optional(CONF_TRANSPARENT, default=False): cv.boolean,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_uses(self):
|
||||||
|
return "img", CONF_LABEL
|
||||||
|
|
||||||
|
async def to_code(self, w: Widget, config):
|
||||||
|
width = config[CONF_WIDTH]
|
||||||
|
height = config[CONF_HEIGHT]
|
||||||
|
use_alpha = "_ALPHA" if config[CONF_TRANSPARENT] else ""
|
||||||
|
lv.canvas_set_buffer(
|
||||||
|
w.obj,
|
||||||
|
lv.custom_mem_alloc(
|
||||||
|
literal(f"LV_CANVAS_BUF_SIZE_TRUE_COLOR{use_alpha}({width}, {height})")
|
||||||
|
),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
literal(f"LV_IMG_CF_TRUE_COLOR{use_alpha}"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
canvas_spec = CanvasType()
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"lvgl.canvas.fill",
|
||||||
|
ObjUpdateAction,
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_ID): cv.use_id(lv_canvas_t),
|
||||||
|
cv.Required(CONF_COLOR): lv_color,
|
||||||
|
cv.Optional(CONF_OPA, default="COVER"): opacity,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async def canvas_fill(config, action_id, template_arg, args):
|
||||||
|
widget = await get_widgets(config)
|
||||||
|
color = await lv_color.process(config[CONF_COLOR])
|
||||||
|
opa = await opacity.process(config[CONF_OPA])
|
||||||
|
|
||||||
|
async def do_fill(w: Widget):
|
||||||
|
lv.canvas_fill_bg(w.obj, color, opa)
|
||||||
|
|
||||||
|
return await action_to_code(widget, do_fill, action_id, template_arg, args)
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"lvgl.canvas.set_pixels",
|
||||||
|
ObjUpdateAction,
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_ID): cv.use_id(lv_canvas_t),
|
||||||
|
cv.Required(CONF_COLOR): lv_color,
|
||||||
|
cv.Optional(CONF_OPA): opacity,
|
||||||
|
cv.Required(CONF_POINTS): cv.ensure_list(point_schema),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async def canvas_set_pixel(config, action_id, template_arg, args):
|
||||||
|
widget = await get_widgets(config)
|
||||||
|
color = await lv_color.process(config[CONF_COLOR])
|
||||||
|
opa = await opacity.process(config.get(CONF_OPA))
|
||||||
|
points = [
|
||||||
|
(
|
||||||
|
await pixels.process(p[CONF_X]),
|
||||||
|
await pixels.process(p[CONF_Y]),
|
||||||
|
)
|
||||||
|
for p in config[CONF_POINTS]
|
||||||
|
]
|
||||||
|
|
||||||
|
async def do_set_pixels(w: Widget):
|
||||||
|
if isinstance(color, MockObj):
|
||||||
|
for point in points:
|
||||||
|
x, y = point
|
||||||
|
lv.canvas_set_px_color(w.obj, x, y, color)
|
||||||
|
else:
|
||||||
|
with LocalVariable("color", "lv_color_t", color, modifier="") as color_var:
|
||||||
|
for point in points:
|
||||||
|
x, y = point
|
||||||
|
lv.canvas_set_px_color(w.obj, x, y, color_var)
|
||||||
|
if opa:
|
||||||
|
if isinstance(opa, Literal):
|
||||||
|
for point in points:
|
||||||
|
x, y = point
|
||||||
|
lv.canvas_set_px_opa(w.obj, x, y, opa)
|
||||||
|
else:
|
||||||
|
with LocalVariable("opa", "lv_opa_t", opa, modifier="") as opa_var:
|
||||||
|
for point in points:
|
||||||
|
x, y = point
|
||||||
|
lv.canvas_set_px_opa(w.obj, x, y, opa_var)
|
||||||
|
|
||||||
|
return await action_to_code(widget, do_set_pixels, action_id, template_arg, args)
|
||||||
|
|
||||||
|
|
||||||
|
DRAW_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_ID): cv.use_id(lv_canvas_t),
|
||||||
|
cv.Required(CONF_X): pixels,
|
||||||
|
cv.Required(CONF_Y): pixels,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
DRAW_OPA_SCHEMA = DRAW_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_OPA): opacity,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def draw_to_code(config, dsc_type, props, do_draw, action_id, template_arg, args):
|
||||||
|
widget = await get_widgets(config)
|
||||||
|
x = await pixels.process(config.get(CONF_X))
|
||||||
|
y = await pixels.process(config.get(CONF_Y))
|
||||||
|
|
||||||
|
async def action_func(w: Widget):
|
||||||
|
with LocalVariable("dsc", f"lv_draw_{dsc_type}_dsc_t", modifier="") as dsc:
|
||||||
|
dsc_addr = literal(f"&{dsc}")
|
||||||
|
lv.call(f"draw_{dsc_type}_dsc_init", dsc_addr)
|
||||||
|
if CONF_OPA in config:
|
||||||
|
opa = await opacity.process(config[CONF_OPA])
|
||||||
|
lv_assign(dsc.opa, opa)
|
||||||
|
for prop, validator in props.items():
|
||||||
|
if prop in config:
|
||||||
|
value = await validator.process(config[prop])
|
||||||
|
mapped_prop = STYLE_REMAP.get(prop, prop)
|
||||||
|
lv_assign(getattr(dsc, mapped_prop), value)
|
||||||
|
await do_draw(w, x, y, dsc_addr)
|
||||||
|
|
||||||
|
return await action_to_code(widget, action_func, action_id, template_arg, args)
|
||||||
|
|
||||||
|
|
||||||
|
RECT_PROPS = {
|
||||||
|
p: STYLE_PROPS[p]
|
||||||
|
for p in (
|
||||||
|
"radius",
|
||||||
|
"bg_opa",
|
||||||
|
"bg_color",
|
||||||
|
"bg_grad",
|
||||||
|
"border_color",
|
||||||
|
"border_width",
|
||||||
|
"border_opa",
|
||||||
|
"outline_color",
|
||||||
|
"outline_width",
|
||||||
|
"outline_pad",
|
||||||
|
"outline_opa",
|
||||||
|
"shadow_color",
|
||||||
|
"shadow_width",
|
||||||
|
"shadow_ofs_x",
|
||||||
|
"shadow_ofs_y",
|
||||||
|
"shadow_spread",
|
||||||
|
"shadow_opa",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"lvgl.canvas.draw_rectangle",
|
||||||
|
ObjUpdateAction,
|
||||||
|
DRAW_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_WIDTH): cv.templatable(cv.int_),
|
||||||
|
cv.Required(CONF_HEIGHT): cv.templatable(cv.int_),
|
||||||
|
},
|
||||||
|
).extend({cv.Optional(prop): STYLE_PROPS[prop] for prop in RECT_PROPS}),
|
||||||
|
)
|
||||||
|
async def canvas_draw_rect(config, action_id, template_arg, args):
|
||||||
|
width = await pixels.process(config[CONF_WIDTH])
|
||||||
|
height = await pixels.process(config[CONF_HEIGHT])
|
||||||
|
|
||||||
|
async def do_draw_rect(w: Widget, x, y, dsc_addr):
|
||||||
|
lv.canvas_draw_rect(w.obj, x, y, width, height, dsc_addr)
|
||||||
|
|
||||||
|
return await draw_to_code(
|
||||||
|
config, "rect", RECT_PROPS, do_draw_rect, action_id, template_arg, args
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
TEXT_PROPS = {
|
||||||
|
p: STYLE_PROPS[f"text_{p}"]
|
||||||
|
for p in (
|
||||||
|
"font",
|
||||||
|
"color",
|
||||||
|
# "sel_color",
|
||||||
|
# "sel_bg_color",
|
||||||
|
"line_space",
|
||||||
|
"letter_space",
|
||||||
|
"align",
|
||||||
|
"decor",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"lvgl.canvas.draw_text",
|
||||||
|
ObjUpdateAction,
|
||||||
|
TEXT_SCHEMA.extend(DRAW_OPA_SCHEMA)
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_MAX_WIDTH): cv.templatable(cv.int_),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.extend({cv.Optional(prop): STYLE_PROPS[f"text_{prop}"] for prop in TEXT_PROPS}),
|
||||||
|
)
|
||||||
|
async def canvas_draw_text(config, action_id, template_arg, args):
|
||||||
|
text = await lv_text.process(config[CONF_TEXT])
|
||||||
|
max_width = await pixels.process(config[CONF_MAX_WIDTH])
|
||||||
|
|
||||||
|
async def do_draw_text(w: Widget, x, y, dsc_addr):
|
||||||
|
lv.canvas_draw_text(w.obj, x, y, max_width, dsc_addr, text)
|
||||||
|
|
||||||
|
return await draw_to_code(
|
||||||
|
config, "label", TEXT_PROPS, do_draw_text, action_id, template_arg, args
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
IMG_PROPS = {
|
||||||
|
"angle": STYLE_PROPS["transform_angle"],
|
||||||
|
"zoom": STYLE_PROPS["transform_zoom"],
|
||||||
|
"recolor": STYLE_PROPS["image_recolor"],
|
||||||
|
"recolor_opa": STYLE_PROPS["image_recolor_opa"],
|
||||||
|
"opa": STYLE_PROPS["opa"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"lvgl.canvas.draw_image",
|
||||||
|
ObjUpdateAction,
|
||||||
|
DRAW_OPA_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_SRC): lv_image,
|
||||||
|
cv.Optional(CONF_PIVOT_X, default=0): pixels,
|
||||||
|
cv.Optional(CONF_PIVOT_Y, default=0): pixels,
|
||||||
|
},
|
||||||
|
).extend({cv.Optional(prop): validator for prop, validator in IMG_PROPS.items()}),
|
||||||
|
)
|
||||||
|
async def canvas_draw_image(config, action_id, template_arg, args):
|
||||||
|
src = await lv_image.process(config[CONF_SRC])
|
||||||
|
pivot_x = await pixels.process(config[CONF_PIVOT_X])
|
||||||
|
pivot_y = await pixels.process(config[CONF_PIVOT_Y])
|
||||||
|
|
||||||
|
async def do_draw_image(w: Widget, x, y, dsc_addr):
|
||||||
|
dsc = MockObj(f"(*{dsc_addr})")
|
||||||
|
if pivot_x or pivot_y:
|
||||||
|
# pylint :disable=no-member
|
||||||
|
lv_assign(dsc.pivot, literal(f"{{{pivot_x}, {pivot_y}}}"))
|
||||||
|
lv.canvas_draw_img(w.obj, x, y, src, dsc_addr)
|
||||||
|
|
||||||
|
return await draw_to_code(
|
||||||
|
config, "img", IMG_PROPS, do_draw_image, action_id, template_arg, args
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
LINE_PROPS = {
|
||||||
|
"width": STYLE_PROPS["line_width"],
|
||||||
|
"color": STYLE_PROPS["line_color"],
|
||||||
|
"dash-width": STYLE_PROPS["line_dash_width"],
|
||||||
|
"dash-gap": STYLE_PROPS["line_dash_gap"],
|
||||||
|
"round_start": lv_bool,
|
||||||
|
"round_end": lv_bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"lvgl.canvas.draw_line",
|
||||||
|
ObjUpdateAction,
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_ID): cv.use_id(lv_canvas_t),
|
||||||
|
cv.Optional(CONF_OPA): opacity,
|
||||||
|
cv.Required(CONF_POINTS): cv.ensure_list(point_schema),
|
||||||
|
},
|
||||||
|
).extend({cv.Optional(prop): validator for prop, validator in LINE_PROPS.items()}),
|
||||||
|
)
|
||||||
|
async def canvas_draw_line(config, action_id, template_arg, args):
|
||||||
|
points = [
|
||||||
|
[await process_coord(p[CONF_X]), await process_coord(p[CONF_Y])]
|
||||||
|
for p in config[CONF_POINTS]
|
||||||
|
]
|
||||||
|
|
||||||
|
async def do_draw_line(w: Widget, x, y, dsc_addr):
|
||||||
|
with LocalVariable(
|
||||||
|
"points", cg.std_vector.template(lv_point_t), points, modifier=""
|
||||||
|
) as points_var:
|
||||||
|
lv.canvas_draw_line(w.obj, points_var.data(), points_var.size(), dsc_addr)
|
||||||
|
|
||||||
|
return await draw_to_code(
|
||||||
|
config, "line", LINE_PROPS, do_draw_line, action_id, template_arg, args
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"lvgl.canvas.draw_polygon",
|
||||||
|
ObjUpdateAction,
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_ID): cv.use_id(lv_canvas_t),
|
||||||
|
cv.Required(CONF_POINTS): cv.ensure_list(point_schema),
|
||||||
|
},
|
||||||
|
).extend({cv.Optional(prop): STYLE_PROPS[prop] for prop in RECT_PROPS}),
|
||||||
|
)
|
||||||
|
async def canvas_draw_polygon(config, action_id, template_arg, args):
|
||||||
|
points = [
|
||||||
|
[await process_coord(p[CONF_X]), await process_coord(p[CONF_Y])]
|
||||||
|
for p in config[CONF_POINTS]
|
||||||
|
]
|
||||||
|
|
||||||
|
async def do_draw_polygon(w: Widget, x, y, dsc_addr):
|
||||||
|
with LocalVariable(
|
||||||
|
"points", cg.std_vector.template(lv_point_t), points, modifier=""
|
||||||
|
) as points_var:
|
||||||
|
lv.canvas_draw_polygon(
|
||||||
|
w.obj, points_var.data(), points_var.size(), dsc_addr
|
||||||
|
)
|
||||||
|
|
||||||
|
return await draw_to_code(
|
||||||
|
config, "rect", RECT_PROPS, do_draw_polygon, action_id, template_arg, args
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
ARC_PROPS = {
|
||||||
|
"width": STYLE_PROPS["arc_width"],
|
||||||
|
"color": STYLE_PROPS["arc_color"],
|
||||||
|
"rounded": STYLE_PROPS["arc_rounded"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"lvgl.canvas.draw_arc",
|
||||||
|
ObjUpdateAction,
|
||||||
|
DRAW_OPA_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_RADIUS): pixels,
|
||||||
|
cv.Required(CONF_START_ANGLE): lv_angle,
|
||||||
|
cv.Required(CONF_END_ANGLE): lv_angle,
|
||||||
|
}
|
||||||
|
).extend({cv.Optional(prop): validator for prop, validator in ARC_PROPS.items()}),
|
||||||
|
)
|
||||||
|
async def canvas_draw_arc(config, action_id, template_arg, args):
|
||||||
|
radius = await size.process(config[CONF_RADIUS])
|
||||||
|
start_angle = await lv_angle.process(config[CONF_START_ANGLE])
|
||||||
|
end_angle = await lv_angle.process(config[CONF_END_ANGLE])
|
||||||
|
|
||||||
|
async def do_draw_arc(w: Widget, x, y, dsc_addr):
|
||||||
|
lv.canvas_draw_arc(w.obj, x, y, radius, start_angle, end_angle, dsc_addr)
|
||||||
|
|
||||||
|
return await draw_to_code(
|
||||||
|
config, "arc", ARC_PROPS, do_draw_arc, action_id, template_arg, args
|
||||||
|
)
|
@@ -1,11 +1,11 @@
|
|||||||
import functools
|
|
||||||
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
|
from esphome.core import Lambda
|
||||||
|
|
||||||
from ..defines import CONF_MAIN
|
from ..defines import CONF_MAIN, CONF_X, CONF_Y, call_lambda
|
||||||
from ..lvcode import lv
|
from ..lvcode import lv_add
|
||||||
from ..types import LvType
|
from ..schemas import point_schema
|
||||||
|
from ..types import LvCompound, LvType
|
||||||
from . import Widget, WidgetType
|
from . import Widget, WidgetType
|
||||||
|
|
||||||
CONF_LINE = "line"
|
CONF_LINE = "line"
|
||||||
@@ -15,47 +15,37 @@ CONF_POINT_LIST_ID = "point_list_id"
|
|||||||
lv_point_t = cg.global_ns.struct("lv_point_t")
|
lv_point_t = cg.global_ns.struct("lv_point_t")
|
||||||
|
|
||||||
|
|
||||||
def point_list(il):
|
|
||||||
il = cv.string(il)
|
|
||||||
nl = il.replace(" ", "").split(",")
|
|
||||||
return [int(n) for n in nl]
|
|
||||||
|
|
||||||
|
|
||||||
def cv_point_list(value):
|
|
||||||
if not isinstance(value, list):
|
|
||||||
raise cv.Invalid("List of points required")
|
|
||||||
values = [point_list(v) for v in value]
|
|
||||||
if not functools.reduce(lambda f, v: f and len(v) == 2, values, True):
|
|
||||||
raise cv.Invalid("Points must be a list of x,y integer pairs")
|
|
||||||
return values
|
|
||||||
|
|
||||||
|
|
||||||
LINE_SCHEMA = {
|
LINE_SCHEMA = {
|
||||||
cv.Required(CONF_POINTS): cv_point_list,
|
cv.Required(CONF_POINTS): cv.ensure_list(point_schema),
|
||||||
cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LINE_MODIFY_SCHEMA = {
|
|
||||||
cv.Optional(CONF_POINTS): cv_point_list,
|
async def process_coord(coord):
|
||||||
cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t),
|
if isinstance(coord, Lambda):
|
||||||
}
|
coord = call_lambda(
|
||||||
|
await cg.process_lambda(coord, [], return_type="lv_coord_t")
|
||||||
|
)
|
||||||
|
if not coord.endswith("()"):
|
||||||
|
coord = f"static_cast<lv_coord_t>({coord})"
|
||||||
|
return cg.RawExpression(coord)
|
||||||
|
return cg.safe_exp(coord)
|
||||||
|
|
||||||
|
|
||||||
class LineType(WidgetType):
|
class LineType(WidgetType):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
CONF_LINE,
|
CONF_LINE,
|
||||||
LvType("lv_line_t"),
|
LvType("LvLineType", parents=(LvCompound,)),
|
||||||
(CONF_MAIN,),
|
(CONF_MAIN,),
|
||||||
LINE_SCHEMA,
|
LINE_SCHEMA,
|
||||||
modify_schema=LINE_MODIFY_SCHEMA,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def to_code(self, w: Widget, config):
|
async def to_code(self, w: Widget, config):
|
||||||
"""For a line object, create and add the points"""
|
points = [
|
||||||
if data := config.get(CONF_POINTS):
|
[await process_coord(p[CONF_X]), await process_coord(p[CONF_Y])]
|
||||||
points = cg.static_const_array(config[CONF_POINT_LIST_ID], data)
|
for p in config[CONF_POINTS]
|
||||||
lv.line_set_points(w.obj, points, len(data))
|
]
|
||||||
|
lv_add(w.var.set_points(points))
|
||||||
|
|
||||||
|
|
||||||
line_spec = LineType()
|
line_spec = LineType()
|
||||||
|
@@ -113,7 +113,7 @@ BASE_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend(
|
|||||||
cs_pin_required=False,
|
cs_pin_required=False,
|
||||||
default_mode="MODE0",
|
default_mode="MODE0",
|
||||||
default_data_rate=10e6,
|
default_data_rate=10e6,
|
||||||
quad=True,
|
mode=spi.TYPE_QUAD,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@@ -37,6 +37,7 @@ CODEOWNERS = ["@esphome/core", "@clydebarrow"]
|
|||||||
spi_ns = cg.esphome_ns.namespace("spi")
|
spi_ns = cg.esphome_ns.namespace("spi")
|
||||||
SPIComponent = spi_ns.class_("SPIComponent", cg.Component)
|
SPIComponent = spi_ns.class_("SPIComponent", cg.Component)
|
||||||
QuadSPIComponent = spi_ns.class_("QuadSPIComponent", cg.Component)
|
QuadSPIComponent = spi_ns.class_("QuadSPIComponent", cg.Component)
|
||||||
|
OctalSPIComponent = spi_ns.class_("OctalSPIComponent", cg.Component)
|
||||||
SPIDevice = spi_ns.class_("SPIDevice")
|
SPIDevice = spi_ns.class_("SPIDevice")
|
||||||
SPIDataRate = spi_ns.enum("SPIDataRate")
|
SPIDataRate = spi_ns.enum("SPIDataRate")
|
||||||
SPIMode = spi_ns.enum("SPIMode")
|
SPIMode = spi_ns.enum("SPIMode")
|
||||||
@@ -78,6 +79,13 @@ CONF_INTERFACE = "interface"
|
|||||||
CONF_INTERFACE_INDEX = "interface_index"
|
CONF_INTERFACE_INDEX = "interface_index"
|
||||||
TYPE_SINGLE = "single"
|
TYPE_SINGLE = "single"
|
||||||
TYPE_QUAD = "quad"
|
TYPE_QUAD = "quad"
|
||||||
|
TYPE_OCTAL = "octal"
|
||||||
|
|
||||||
|
TYPE_CLASS = {
|
||||||
|
TYPE_SINGLE: SPIComponent,
|
||||||
|
TYPE_QUAD: QuadSPIComponent,
|
||||||
|
TYPE_OCTAL: OctalSPIComponent,
|
||||||
|
}
|
||||||
|
|
||||||
# RP2040 SPI pin assignments are complicated;
|
# RP2040 SPI pin assignments are complicated;
|
||||||
# refer to GPIO function select table in https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf
|
# refer to GPIO function select table in https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf
|
||||||
@@ -230,7 +238,7 @@ def validate_spi_config(config):
|
|||||||
):
|
):
|
||||||
raise cv.Invalid("Invalid pin selections for hardware SPI interface")
|
raise cv.Invalid("Invalid pin selections for hardware SPI interface")
|
||||||
if CONF_DATA_PINS in spi and CONF_INTERFACE_INDEX not in spi:
|
if CONF_DATA_PINS in spi and CONF_INTERFACE_INDEX not in spi:
|
||||||
raise cv.Invalid("Quad mode requires a hardware interface")
|
raise cv.Invalid("Quad and octal modes requires a hardware interface")
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@@ -251,7 +259,7 @@ def get_spi_interface(index):
|
|||||||
return "new SPIClass(HSPI)"
|
return "new SPIClass(HSPI)"
|
||||||
|
|
||||||
|
|
||||||
SPI_SCHEMA = cv.All(
|
SPI_SINGLE_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(SPIComponent),
|
cv.GenerateID(): cv.declare_id(SPIComponent),
|
||||||
@@ -266,7 +274,7 @@ SPI_SCHEMA = cv.All(
|
|||||||
lower=True,
|
lower=True,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_DATA_PINS): cv.invalid(
|
cv.Optional(CONF_DATA_PINS): cv.invalid(
|
||||||
"'data_pins' should be used with 'type: quad' only"
|
"'data_pins' should be used with 'type: quad or octal' only"
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
@@ -274,38 +282,41 @@ SPI_SCHEMA = cv.All(
|
|||||||
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]),
|
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]),
|
||||||
)
|
)
|
||||||
|
|
||||||
SPI_QUAD_SCHEMA = cv.All(
|
|
||||||
cv.Schema(
|
def spi_mode_schema(mode):
|
||||||
{
|
if mode == TYPE_SINGLE:
|
||||||
cv.GenerateID(): cv.declare_id(QuadSPIComponent),
|
return SPI_SINGLE_SCHEMA
|
||||||
cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema,
|
pin_count = 4 if mode == TYPE_QUAD else 8
|
||||||
cv.Required(CONF_DATA_PINS): cv.All(
|
return cv.All(
|
||||||
cv.ensure_list(pins.internal_gpio_output_pin_number),
|
cv.Schema(
|
||||||
cv.Length(min=4, max=4),
|
{
|
||||||
),
|
cv.GenerateID(): cv.declare_id(TYPE_CLASS[mode]),
|
||||||
cv.Optional(CONF_INTERFACE, default="hardware"): cv.one_of(
|
cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema,
|
||||||
*sum(get_hw_interface_list(), ["hardware"]),
|
cv.Required(CONF_DATA_PINS): cv.All(
|
||||||
lower=True,
|
cv.ensure_list(pins.internal_gpio_output_pin_number),
|
||||||
),
|
cv.Length(min=pin_count, max=pin_count),
|
||||||
cv.Optional(CONF_MISO_PIN): cv.invalid(
|
),
|
||||||
"'miso_pin' should not be used with quad SPI"
|
cv.Optional(CONF_INTERFACE, default="hardware"): cv.one_of(
|
||||||
),
|
*sum(get_hw_interface_list(), ["hardware"]),
|
||||||
cv.Optional(CONF_MOSI_PIN): cv.invalid(
|
lower=True,
|
||||||
"'mosi_pin' should not be used with quad SPI"
|
),
|
||||||
),
|
cv.Optional(CONF_MISO_PIN): cv.invalid(
|
||||||
}
|
f"'miso_pin' should not be used with {mode} SPI"
|
||||||
),
|
),
|
||||||
cv.only_on([PLATFORM_ESP32]),
|
cv.Optional(CONF_MOSI_PIN): cv.invalid(
|
||||||
cv.only_with_esp_idf,
|
f"'mosi_pin' should not be used with {mode} SPI"
|
||||||
)
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.only_on([PLATFORM_ESP32]),
|
||||||
|
cv.only_with_esp_idf,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.ensure_list(
|
cv.ensure_list(
|
||||||
cv.typed_schema(
|
cv.typed_schema(
|
||||||
{
|
{k: spi_mode_schema(k) for k in TYPE_CLASS},
|
||||||
TYPE_SINGLE: SPI_SCHEMA,
|
|
||||||
TYPE_QUAD: SPI_QUAD_SCHEMA,
|
|
||||||
},
|
|
||||||
default_type=TYPE_SINGLE,
|
default_type=TYPE_SINGLE,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@@ -344,19 +355,17 @@ def spi_device_schema(
|
|||||||
cs_pin_required=True,
|
cs_pin_required=True,
|
||||||
default_data_rate=cv.UNDEFINED,
|
default_data_rate=cv.UNDEFINED,
|
||||||
default_mode=cv.UNDEFINED,
|
default_mode=cv.UNDEFINED,
|
||||||
quad=False,
|
mode=TYPE_SINGLE,
|
||||||
):
|
):
|
||||||
"""Create a schema for an SPI device.
|
"""Create a schema for an SPI device.
|
||||||
:param cs_pin_required: If true, make the CS_PIN required in the config.
|
:param cs_pin_required: If true, make the CS_PIN required in the config.
|
||||||
:param default_data_rate: Optional data_rate to use as default
|
:param default_data_rate: Optional data_rate to use as default
|
||||||
:param default_mode Optional. The default SPI mode to use.
|
:param default_mode Optional. The default SPI mode to use.
|
||||||
:param quad If set, will require an SPI component configured as quad data bits.
|
:param mode Choose single, quad or octal mode.
|
||||||
:return: The SPI device schema, `extend` this in your config schema.
|
:return: The SPI device schema, `extend` this in your config schema.
|
||||||
"""
|
"""
|
||||||
schema = {
|
schema = {
|
||||||
cv.GenerateID(CONF_SPI_ID): cv.use_id(
|
cv.GenerateID(CONF_SPI_ID): cv.use_id(TYPE_CLASS[mode]),
|
||||||
QuadSPIComponent if quad else SPIComponent
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_DATA_RATE, default=default_data_rate): SPI_DATA_RATE_SCHEMA,
|
cv.Optional(CONF_DATA_RATE, default=default_data_rate): SPI_DATA_RATE_SCHEMA,
|
||||||
cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum(
|
cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum(
|
||||||
SPI_MODE_OPTIONS, upper=True
|
SPI_MODE_OPTIONS, upper=True
|
||||||
|
@@ -369,6 +369,7 @@ class SPIComponent : public Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
using QuadSPIComponent = SPIComponent;
|
using QuadSPIComponent = SPIComponent;
|
||||||
|
using OctalSPIComponent = SPIComponent;
|
||||||
/**
|
/**
|
||||||
* Base class for SPIDevice, un-templated.
|
* Base class for SPIDevice, un-templated.
|
||||||
*/
|
*/
|
||||||
|
@@ -211,11 +211,19 @@ class SPIBusHw : public SPIBus {
|
|||||||
buscfg.data1_io_num = data_pins[1];
|
buscfg.data1_io_num = data_pins[1];
|
||||||
buscfg.data2_io_num = data_pins[2];
|
buscfg.data2_io_num = data_pins[2];
|
||||||
buscfg.data3_io_num = data_pins[3];
|
buscfg.data3_io_num = data_pins[3];
|
||||||
buscfg.data4_io_num = -1;
|
if (data_pins.size() == 8) {
|
||||||
buscfg.data5_io_num = -1;
|
buscfg.data4_io_num = data_pins[4];
|
||||||
buscfg.data6_io_num = -1;
|
buscfg.data5_io_num = data_pins[5];
|
||||||
buscfg.data7_io_num = -1;
|
buscfg.data6_io_num = data_pins[6];
|
||||||
buscfg.flags |= SPICOMMON_BUSFLAG_QUAD;
|
buscfg.data7_io_num = data_pins[7];
|
||||||
|
buscfg.flags |= SPICOMMON_BUSFLAG_OCTAL;
|
||||||
|
} else {
|
||||||
|
buscfg.data4_io_num = -1;
|
||||||
|
buscfg.data5_io_num = -1;
|
||||||
|
buscfg.data6_io_num = -1;
|
||||||
|
buscfg.data7_io_num = -1;
|
||||||
|
buscfg.flags |= SPICOMMON_BUSFLAG_QUAD;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
buscfg.max_transfer_sz = MAX_TRANSFER_SIZE;
|
buscfg.max_transfer_sz = MAX_TRANSFER_SIZE;
|
||||||
auto err = spi_bus_initialize(channel, &buscfg, SPI_DMA_CH_AUTO);
|
auto err = spi_bus_initialize(channel, &buscfg, SPI_DMA_CH_AUTO);
|
||||||
|
@@ -21,10 +21,6 @@ namespace time {
|
|||||||
static const char *const TAG = "time";
|
static const char *const TAG = "time";
|
||||||
|
|
||||||
RealTimeClock::RealTimeClock() = default;
|
RealTimeClock::RealTimeClock() = default;
|
||||||
void RealTimeClock::call_setup() {
|
|
||||||
this->apply_timezone_();
|
|
||||||
PollingComponent::call_setup();
|
|
||||||
}
|
|
||||||
void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
|
void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
|
||||||
// Update UTC epoch time.
|
// Update UTC epoch time.
|
||||||
struct timeval timev {
|
struct timeval timev {
|
||||||
|
@@ -21,7 +21,10 @@ class RealTimeClock : public PollingComponent {
|
|||||||
explicit RealTimeClock();
|
explicit RealTimeClock();
|
||||||
|
|
||||||
/// Set the time zone.
|
/// Set the time zone.
|
||||||
void set_timezone(const std::string &tz) { this->timezone_ = tz; }
|
void set_timezone(const std::string &tz) {
|
||||||
|
this->timezone_ = tz;
|
||||||
|
this->apply_timezone_();
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the time zone currently in use.
|
/// Get the time zone currently in use.
|
||||||
std::string get_timezone() { return this->timezone_; }
|
std::string get_timezone() { return this->timezone_; }
|
||||||
@@ -35,8 +38,6 @@ class RealTimeClock : public PollingComponent {
|
|||||||
/// Get the current time as the UTC epoch since January 1st 1970.
|
/// Get the current time as the UTC epoch since January 1st 1970.
|
||||||
time_t timestamp_now() { return ::time(nullptr); }
|
time_t timestamp_now() { return ::time(nullptr); }
|
||||||
|
|
||||||
void call_setup() override;
|
|
||||||
|
|
||||||
void add_on_time_sync_callback(std::function<void()> callback) {
|
void add_on_time_sync_callback(std::function<void()> callback) {
|
||||||
this->time_sync_callback_.add(std::move(callback));
|
this->time_sync_callback_.add(std::move(callback));
|
||||||
};
|
};
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
"""Constants used by esphome."""
|
"""Constants used by esphome."""
|
||||||
|
|
||||||
__version__ = "2025.4.0-dev"
|
__version__ = "2025.5.0-dev"
|
||||||
|
|
||||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||||
|
@@ -13,7 +13,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
|
|||||||
esptool==4.8.1
|
esptool==4.8.1
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
esphome-dashboard==20250212.0
|
esphome-dashboard==20250212.0
|
||||||
aioesphomeapi==29.7.0
|
aioesphomeapi==29.9.0
|
||||||
zeroconf==0.146.3
|
zeroconf==0.146.3
|
||||||
puremagic==1.28
|
puremagic==1.28
|
||||||
ruamel.yaml==0.18.10 # dashboard_import
|
ruamel.yaml==0.18.10 # dashboard_import
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
pylint==3.3.6
|
pylint==3.3.6
|
||||||
flake8==7.2.0 # also change in .pre-commit-config.yaml when updating
|
flake8==7.2.0 # also change in .pre-commit-config.yaml when updating
|
||||||
ruff==0.11.2 # also change in .pre-commit-config.yaml when updating
|
ruff==0.11.4 # also change in .pre-commit-config.yaml when updating
|
||||||
pyupgrade==3.19.1 # also change in .pre-commit-config.yaml when updating
|
pyupgrade==3.19.1 # also change in .pre-commit-config.yaml when updating
|
||||||
pre-commit
|
pre-commit
|
||||||
|
|
||||||
# Unit tests
|
# Unit tests
|
||||||
pytest==8.2.0
|
pytest==8.3.5
|
||||||
pytest-cov==6.0.0
|
pytest-cov==6.1.1
|
||||||
pytest-mock==3.14.0
|
pytest-mock==3.14.0
|
||||||
pytest-asyncio==0.26.0
|
pytest-asyncio==0.26.0
|
||||||
asyncmock==0.4.2
|
asyncmock==0.4.2
|
||||||
|
@@ -24,6 +24,8 @@ lvgl:
|
|||||||
logger.log: LVGL is Paused
|
logger.log: LVGL is Paused
|
||||||
on_resume:
|
on_resume:
|
||||||
logger.log: LVGL has resumed
|
logger.log: LVGL has resumed
|
||||||
|
on_boot:
|
||||||
|
logger.log: LVGL has started
|
||||||
bg_color: light_blue
|
bg_color: light_blue
|
||||||
disp_bg_color: color_id
|
disp_bg_color: color_id
|
||||||
disp_bg_image: cat_image
|
disp_bg_image: cat_image
|
||||||
@@ -128,6 +130,10 @@ lvgl:
|
|||||||
on_click:
|
on_click:
|
||||||
then:
|
then:
|
||||||
- lvgl.widget.hide: message_box
|
- lvgl.widget.hide: message_box
|
||||||
|
- lvgl.style.update:
|
||||||
|
id: style_test
|
||||||
|
bg_color: blue
|
||||||
|
bg_opa: !lambda return 0.5;
|
||||||
- id: simple_msgbox
|
- id: simple_msgbox
|
||||||
title: Simple
|
title: Simple
|
||||||
|
|
||||||
@@ -210,6 +216,10 @@ lvgl:
|
|||||||
src: !lambda "return {dog_image, cat_image};"
|
src: !lambda "return {dog_image, cat_image};"
|
||||||
duration: 2s
|
duration: 2s
|
||||||
- label:
|
- label:
|
||||||
|
on_boot:
|
||||||
|
lvgl.label.update:
|
||||||
|
id: hello_label
|
||||||
|
text: Goodbye Cruel World
|
||||||
id: hello_label
|
id: hello_label
|
||||||
text: Hello world
|
text: Hello world
|
||||||
text_color: 0xFF8000
|
text_color: 0xFF8000
|
||||||
@@ -504,6 +514,110 @@ lvgl:
|
|||||||
|
|
||||||
- id: page2
|
- id: page2
|
||||||
widgets:
|
widgets:
|
||||||
|
- canvas:
|
||||||
|
id: canvas_id
|
||||||
|
align: center
|
||||||
|
width: 400
|
||||||
|
height: 400
|
||||||
|
transparent: true
|
||||||
|
on_boot:
|
||||||
|
- lvgl.canvas.fill:
|
||||||
|
color: blue
|
||||||
|
opa: 50%
|
||||||
|
- lvgl.canvas.draw_rectangle:
|
||||||
|
x: 20
|
||||||
|
y: 20
|
||||||
|
width: 150
|
||||||
|
height: 150
|
||||||
|
bg_color: green
|
||||||
|
bg_opa: cover
|
||||||
|
radius: 5
|
||||||
|
border_color: black
|
||||||
|
border_width: 4
|
||||||
|
border_opa: 80%
|
||||||
|
shadow_color: black
|
||||||
|
shadow_width: 10
|
||||||
|
shadow_ofs_x: 5
|
||||||
|
shadow_ofs_y: 5
|
||||||
|
shadow_spread: 4
|
||||||
|
shadow_opa: cover
|
||||||
|
outline_color: red
|
||||||
|
outline_width: 4
|
||||||
|
outline_pad: 4
|
||||||
|
outline_opa: cover
|
||||||
|
- lvgl.canvas.set_pixels:
|
||||||
|
color: red
|
||||||
|
points:
|
||||||
|
- x: 100
|
||||||
|
y: 100
|
||||||
|
- 100,101
|
||||||
|
- 100,102
|
||||||
|
- 100,103
|
||||||
|
- 100,104
|
||||||
|
- lvgl.canvas.set_pixels:
|
||||||
|
opa: 50%
|
||||||
|
color: !lambda return lv_color_make(255,255,255);
|
||||||
|
points:
|
||||||
|
- x: !lambda return random_uint32() % 200;
|
||||||
|
y: !lambda return random_uint32() % 200;
|
||||||
|
- 121,120
|
||||||
|
- 122,120
|
||||||
|
- 123,120
|
||||||
|
- 124,120
|
||||||
|
- 125,120
|
||||||
|
|
||||||
|
- lvgl.canvas.draw_text:
|
||||||
|
x: 100
|
||||||
|
y: 100
|
||||||
|
font: montserrat_18
|
||||||
|
color: white
|
||||||
|
opa: cover
|
||||||
|
decor: underline
|
||||||
|
letter_space: 1
|
||||||
|
line_space: 2
|
||||||
|
text: Canvas Text
|
||||||
|
align: center
|
||||||
|
max_width: 150
|
||||||
|
- lvgl.canvas.draw_image:
|
||||||
|
src: cat_image
|
||||||
|
x: 100
|
||||||
|
y: 100
|
||||||
|
angle: 90
|
||||||
|
zoom: 2.0
|
||||||
|
pivot_x: 25
|
||||||
|
pivot_y: 25
|
||||||
|
- lvgl.canvas.draw_line:
|
||||||
|
color: blue
|
||||||
|
width: 4
|
||||||
|
round_end: true
|
||||||
|
round_start: false
|
||||||
|
points:
|
||||||
|
- 50,50
|
||||||
|
- 50, 200
|
||||||
|
- 200, 200
|
||||||
|
- 200, 50
|
||||||
|
- 50,50
|
||||||
|
- lvgl.canvas.draw_polygon:
|
||||||
|
bg_color: teal
|
||||||
|
border_color: white
|
||||||
|
border_width: 2
|
||||||
|
border_opa: cover
|
||||||
|
points:
|
||||||
|
- 150,150
|
||||||
|
- 150, 300
|
||||||
|
- 300, 300
|
||||||
|
- 350, 250
|
||||||
|
- lvgl.canvas.draw_arc:
|
||||||
|
x: 200
|
||||||
|
y: 200
|
||||||
|
radius: 40
|
||||||
|
opa: 50%
|
||||||
|
color: purple
|
||||||
|
width: 6
|
||||||
|
rounded: true
|
||||||
|
start_angle: 10
|
||||||
|
end_angle: !lambda return 900;
|
||||||
|
|
||||||
- qrcode:
|
- qrcode:
|
||||||
id: lv_qr
|
id: lv_qr
|
||||||
align: left_mid
|
align: left_mid
|
||||||
@@ -608,6 +722,8 @@ lvgl:
|
|||||||
align: center
|
align: center
|
||||||
points:
|
points:
|
||||||
- 5, 5
|
- 5, 5
|
||||||
|
- x: !lambda return random_uint32() % 100;
|
||||||
|
y: !lambda return random_uint32() % 100;
|
||||||
- 70, 70
|
- 70, 70
|
||||||
- 120, 10
|
- 120, 10
|
||||||
- 180, 60
|
- 180, 60
|
||||||
@@ -616,6 +732,14 @@ lvgl:
|
|||||||
- lvgl.line.update:
|
- lvgl.line.update:
|
||||||
id: lv_line_id
|
id: lv_line_id
|
||||||
line_color: 0xFFFF
|
line_color: 0xFFFF
|
||||||
|
points:
|
||||||
|
- 5, 5
|
||||||
|
- x: !lambda return random_uint32() % 100;
|
||||||
|
y: !lambda return random_uint32() % 100;
|
||||||
|
- 70, 70
|
||||||
|
- 120, 10
|
||||||
|
- 180, 60
|
||||||
|
- 240, 10
|
||||||
- lvgl.page.next:
|
- lvgl.page.next:
|
||||||
- switch:
|
- switch:
|
||||||
align: right_mid
|
align: right_mid
|
||||||
|
@@ -1,22 +1,36 @@
|
|||||||
spi:
|
spi:
|
||||||
- id: spi_id_1
|
|
||||||
type: single
|
|
||||||
clk_pin:
|
|
||||||
number: GPIO0
|
|
||||||
ignore_strapping_warning: true
|
|
||||||
allow_other_uses: false
|
|
||||||
mosi_pin: GPIO6
|
|
||||||
interface: hardware
|
|
||||||
- id: quad_spi
|
- id: quad_spi
|
||||||
type: quad
|
type: quad
|
||||||
interface: spi3
|
interface: spi3
|
||||||
clk_pin: 47
|
clk_pin:
|
||||||
|
number: 47
|
||||||
data_pins:
|
data_pins:
|
||||||
- number: 40
|
- allow_other_uses: true
|
||||||
allow_other_uses: false
|
number: 40
|
||||||
- 41
|
- allow_other_uses: true
|
||||||
- 42
|
number: 41
|
||||||
- 43
|
- allow_other_uses: true
|
||||||
|
number: 42
|
||||||
|
- allow_other_uses: true
|
||||||
|
number: 43
|
||||||
|
- id: octal_spi
|
||||||
|
type: octal
|
||||||
|
interface: hardware
|
||||||
|
clk_pin:
|
||||||
|
number: 0
|
||||||
|
data_pins:
|
||||||
|
- 36
|
||||||
|
- 37
|
||||||
|
- 38
|
||||||
|
- 39
|
||||||
|
- allow_other_uses: true
|
||||||
|
number: 40
|
||||||
|
- allow_other_uses: true
|
||||||
|
number: 41
|
||||||
|
- allow_other_uses: true
|
||||||
|
number: 42
|
||||||
|
- allow_other_uses: true
|
||||||
|
number: 43
|
||||||
- id: spi_id_3
|
- id: spi_id_3
|
||||||
interface: any
|
interface: any
|
||||||
clk_pin: 8
|
clk_pin: 8
|
||||||
|
Reference in New Issue
Block a user