mirror of
https://github.com/esphome/esphome.git
synced 2025-11-12 12:55:46 +00:00
Compare commits
20 Commits
platformio
...
2025.10.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47a7f729dd | ||
|
|
a59888224c | ||
|
|
58ad4759f0 | ||
|
|
87f79290ba | ||
|
|
9326d78439 | ||
|
|
a93887a790 | ||
|
|
00155989af | ||
|
|
a3583da17d | ||
|
|
0f6fd91304 | ||
|
|
2f5f1da16f | ||
|
|
51745d1d5e | ||
|
|
fecc8399a5 | ||
|
|
db395a662d | ||
|
|
641dd24b21 | ||
|
|
57f2e32b00 | ||
|
|
6a478b9070 | ||
|
|
a32a1d11fb | ||
|
|
daeb8ef88c | ||
|
|
febee437d6 | ||
|
|
de2f475dbd |
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
|||||||
# could be handy for archiving the generated documentation or if some version
|
# could be handy for archiving the generated documentation or if some version
|
||||||
# control system is used.
|
# control system is used.
|
||||||
|
|
||||||
PROJECT_NUMBER = 2025.10.2
|
PROJECT_NUMBER = 2025.10.5
|
||||||
|
|
||||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||||
# for a project that appears at the top of each page and should give viewer a
|
# for a project that appears at the top of each page and should give viewer a
|
||||||
|
|||||||
@@ -11,4 +11,5 @@ CONF_DRAW_ROUNDING = "draw_rounding"
|
|||||||
CONF_ON_RECEIVE = "on_receive"
|
CONF_ON_RECEIVE = "on_receive"
|
||||||
CONF_ON_STATE_CHANGE = "on_state_change"
|
CONF_ON_STATE_CHANGE = "on_state_change"
|
||||||
CONF_REQUEST_HEADERS = "request_headers"
|
CONF_REQUEST_HEADERS = "request_headers"
|
||||||
|
CONF_ROWS = "rows"
|
||||||
CONF_USE_PSRAM = "use_psram"
|
CONF_USE_PSRAM = "use_psram"
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ void HDC1080Component::setup() {
|
|||||||
|
|
||||||
// if configuration fails - there is a problem
|
// if configuration fails - there is a problem
|
||||||
if (this->write_register(HDC1080_CMD_CONFIGURATION, config, 2) != i2c::ERROR_OK) {
|
if (this->write_register(HDC1080_CMD_CONFIGURATION, config, 2) != i2c::ERROR_OK) {
|
||||||
this->mark_failed();
|
ESP_LOGW(TAG, "Failed to configure HDC1080");
|
||||||
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -671,18 +671,33 @@ async def write_image(config, all_frames=False):
|
|||||||
resize = config.get(CONF_RESIZE)
|
resize = config.get(CONF_RESIZE)
|
||||||
if is_svg_file(path):
|
if is_svg_file(path):
|
||||||
# Local import so use of non-SVG files needn't require cairosvg installed
|
# Local import so use of non-SVG files needn't require cairosvg installed
|
||||||
|
from pyexpat import ExpatError
|
||||||
|
from xml.etree.ElementTree import ParseError
|
||||||
|
|
||||||
from cairosvg import svg2png
|
from cairosvg import svg2png
|
||||||
|
from cairosvg.helpers import PointError
|
||||||
|
|
||||||
if not resize:
|
if not resize:
|
||||||
resize = (None, None)
|
resize = (None, None)
|
||||||
with open(path, "rb") as file:
|
try:
|
||||||
image = svg2png(
|
with open(path, "rb") as file:
|
||||||
file_obj=file,
|
image = svg2png(
|
||||||
output_width=resize[0],
|
file_obj=file,
|
||||||
output_height=resize[1],
|
output_width=resize[0],
|
||||||
)
|
output_height=resize[1],
|
||||||
image = Image.open(io.BytesIO(image))
|
)
|
||||||
width, height = image.size
|
image = Image.open(io.BytesIO(image))
|
||||||
|
width, height = image.size
|
||||||
|
except (
|
||||||
|
ValueError,
|
||||||
|
ParseError,
|
||||||
|
IndexError,
|
||||||
|
ExpatError,
|
||||||
|
AttributeError,
|
||||||
|
TypeError,
|
||||||
|
PointError,
|
||||||
|
) as e:
|
||||||
|
raise core.EsphomeError(f"Could not load SVG image {path}: {e}") from e
|
||||||
else:
|
else:
|
||||||
image = Image.open(path)
|
image = Image.open(path)
|
||||||
width, height = image.size
|
width, height = image.size
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ async def to_code(config):
|
|||||||
lvgl_static.add_event_cb(
|
lvgl_static.add_event_cb(
|
||||||
widget.obj,
|
widget.obj,
|
||||||
await pressed_ctx.get_lambda(),
|
await pressed_ctx.get_lambda(),
|
||||||
LV_EVENT.PRESSING,
|
LV_EVENT.PRESSED,
|
||||||
LV_EVENT.RELEASED,
|
LV_EVENT.RELEASED,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Constants already defined in esphome.const are not duplicated here and must be i
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from esphome import codegen as cg, config_validation as cv
|
from esphome import codegen as cg, config_validation as cv
|
||||||
from esphome.const import CONF_ITEMS
|
from esphome.const import CONF_ITEMS
|
||||||
@@ -12,6 +13,7 @@ from esphome.core import ID, Lambda
|
|||||||
from esphome.cpp_generator import LambdaExpression, MockObj
|
from esphome.cpp_generator import LambdaExpression, MockObj
|
||||||
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 esphome.types import Expression, SafeExpType
|
||||||
|
|
||||||
from .helpers import requires_component
|
from .helpers import requires_component
|
||||||
|
|
||||||
@@ -42,7 +44,13 @@ def 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(";"):
|
||||||
return expr[6:][:-1].strip()
|
return expr[6:-1].strip()
|
||||||
|
# If lambda has parameters, call it with those parameter names
|
||||||
|
# Parameter names come from hardcoded component code (like "x", "it", "event")
|
||||||
|
# not from user input, so they're safe to use directly
|
||||||
|
if lamb.parameters and lamb.parameters.parameters:
|
||||||
|
param_names = ", ".join(str(param.id) for param in lamb.parameters.parameters)
|
||||||
|
return f"{lamb}({param_names})"
|
||||||
return f"{lamb}()"
|
return f"{lamb}()"
|
||||||
|
|
||||||
|
|
||||||
@@ -65,10 +73,20 @@ class LValidator:
|
|||||||
return cv.returning_lambda(value)
|
return cv.returning_lambda(value)
|
||||||
return self.validator(value)
|
return self.validator(value)
|
||||||
|
|
||||||
async def process(self, value, args=()):
|
async def process(
|
||||||
|
self, value: Any, args: list[tuple[SafeExpType, str]] | None = None
|
||||||
|
) -> Expression:
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
if isinstance(value, Lambda):
|
if isinstance(value, Lambda):
|
||||||
|
# Local import to avoid circular import
|
||||||
|
from .lvcode import CodeContext, LambdaContext
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
# CodeContext does not have get_automation_parameters
|
||||||
|
# so we need to assert the type here
|
||||||
|
assert isinstance(CodeContext.code_context, LambdaContext)
|
||||||
|
args = args or CodeContext.code_context.get_automation_parameters()
|
||||||
return cg.RawExpression(
|
return cg.RawExpression(
|
||||||
call_lambda(
|
call_lambda(
|
||||||
await cg.process_lambda(value, args, return_type=self.rtype)
|
await cg.process_lambda(value, args, return_type=self.rtype)
|
||||||
@@ -486,7 +504,6 @@ CONF_RESUME_ON_INPUT = "resume_on_input"
|
|||||||
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_ROWS = "rows"
|
|
||||||
CONF_SCALE_LINES = "scale_lines"
|
CONF_SCALE_LINES = "scale_lines"
|
||||||
CONF_SCROLLBAR_MODE = "scrollbar_mode"
|
CONF_SCROLLBAR_MODE = "scrollbar_mode"
|
||||||
CONF_SELECTED_INDEX = "selected_index"
|
CONF_SELECTED_INDEX = "selected_index"
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import image
|
from esphome.components import image
|
||||||
from esphome.components.color import CONF_HEX, ColorStruct, from_rgbw
|
from esphome.components.color import CONF_HEX, ColorStruct, from_rgbw
|
||||||
@@ -17,6 +19,7 @@ from esphome.cpp_generator import MockObj
|
|||||||
from esphome.cpp_types import ESPTime, int32, uint32
|
from esphome.cpp_types import ESPTime, int32, uint32
|
||||||
from esphome.helpers import cpp_string_escape
|
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 esphome.types import Expression, SafeExpType
|
||||||
|
|
||||||
from . import types as ty
|
from . import types as ty
|
||||||
from .defines import (
|
from .defines import (
|
||||||
@@ -388,11 +391,23 @@ class TextValidator(LValidator):
|
|||||||
return value
|
return value
|
||||||
return super().__call__(value)
|
return super().__call__(value)
|
||||||
|
|
||||||
async def process(self, value, args=()):
|
async def process(
|
||||||
|
self, value: Any, args: list[tuple[SafeExpType, str]] | None = None
|
||||||
|
) -> Expression:
|
||||||
|
# Local import to avoid circular import at module level
|
||||||
|
|
||||||
|
from .lvcode import CodeContext, LambdaContext
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
# CodeContext does not have get_automation_parameters
|
||||||
|
# so we need to assert the type here
|
||||||
|
assert isinstance(CodeContext.code_context, LambdaContext)
|
||||||
|
args = args or CodeContext.code_context.get_automation_parameters()
|
||||||
|
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
if format_str := value.get(CONF_FORMAT):
|
if format_str := value.get(CONF_FORMAT):
|
||||||
args = [str(x) for x in value[CONF_ARGS]]
|
str_args = [str(x) for x in value[CONF_ARGS]]
|
||||||
arg_expr = cg.RawExpression(",".join(args))
|
arg_expr = cg.RawExpression(",".join(str_args))
|
||||||
format_str = cpp_string_escape(format_str)
|
format_str = cpp_string_escape(format_str)
|
||||||
return literal(f"str_sprintf({format_str}, {arg_expr}).c_str()")
|
return literal(f"str_sprintf({format_str}, {arg_expr}).c_str()")
|
||||||
if time_format := value.get(CONF_TIME_FORMAT):
|
if time_format := value.get(CONF_TIME_FORMAT):
|
||||||
|
|||||||
@@ -164,6 +164,9 @@ class LambdaContext(CodeContext):
|
|||||||
code_text.append(text)
|
code_text.append(text)
|
||||||
return code_text
|
return code_text
|
||||||
|
|
||||||
|
def get_automation_parameters(self) -> list[tuple[SafeExpType, str]]:
|
||||||
|
return self.parameters
|
||||||
|
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
await super().__aenter__()
|
await super().__aenter__()
|
||||||
add_line_marks(self.where)
|
add_line_marks(self.where)
|
||||||
@@ -178,9 +181,8 @@ class LvContext(LambdaContext):
|
|||||||
|
|
||||||
added_lambda_count = 0
|
added_lambda_count = 0
|
||||||
|
|
||||||
def __init__(self, args=None):
|
def __init__(self):
|
||||||
self.args = args or LVGL_COMP_ARG
|
super().__init__(parameters=LVGL_COMP_ARG)
|
||||||
super().__init__(parameters=self.args)
|
|
||||||
|
|
||||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||||
await super().__aexit__(exc_type, exc_val, exc_tb)
|
await super().__aexit__(exc_type, exc_val, exc_tb)
|
||||||
@@ -189,6 +191,11 @@ class LvContext(LambdaContext):
|
|||||||
cg.add(expression)
|
cg.add(expression)
|
||||||
return expression
|
return expression
|
||||||
|
|
||||||
|
def get_automation_parameters(self) -> list[tuple[SafeExpType, str]]:
|
||||||
|
# When generating automations, we don't want the `lv_component` parameter to be passed
|
||||||
|
# to the lambda.
|
||||||
|
return []
|
||||||
|
|
||||||
def __call__(self, *args):
|
def __call__(self, *args):
|
||||||
return self.add(*args)
|
return self.add(*args)
|
||||||
|
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ bool LvPageType::is_showing() const { return this->parent_->get_current_page() =
|
|||||||
void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) {
|
void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) {
|
||||||
auto width = lv_area_get_width(area);
|
auto width = lv_area_get_width(area);
|
||||||
auto height = lv_area_get_height(area);
|
auto height = lv_area_get_height(area);
|
||||||
|
auto height_rounded = (height + this->draw_rounding - 1) / this->draw_rounding * this->draw_rounding;
|
||||||
auto x1 = area->x1;
|
auto x1 = area->x1;
|
||||||
auto y1 = area->y1;
|
auto y1 = area->y1;
|
||||||
lv_color_t *dst = this->rotate_buf_;
|
lv_color_t *dst = this->rotate_buf_;
|
||||||
@@ -163,13 +164,13 @@ void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) {
|
|||||||
case display::DISPLAY_ROTATION_90_DEGREES:
|
case display::DISPLAY_ROTATION_90_DEGREES:
|
||||||
for (lv_coord_t x = height; x-- != 0;) {
|
for (lv_coord_t x = height; x-- != 0;) {
|
||||||
for (lv_coord_t y = 0; y != width; y++) {
|
for (lv_coord_t y = 0; y != width; y++) {
|
||||||
dst[y * height + x] = *ptr++;
|
dst[y * height_rounded + x] = *ptr++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
y1 = x1;
|
y1 = x1;
|
||||||
x1 = this->disp_drv_.ver_res - area->y1 - height;
|
x1 = this->disp_drv_.ver_res - area->y1 - height;
|
||||||
width = height;
|
height = width;
|
||||||
height = lv_area_get_width(area);
|
width = height_rounded;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case display::DISPLAY_ROTATION_180_DEGREES:
|
case display::DISPLAY_ROTATION_180_DEGREES:
|
||||||
@@ -185,13 +186,13 @@ void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) {
|
|||||||
case display::DISPLAY_ROTATION_270_DEGREES:
|
case display::DISPLAY_ROTATION_270_DEGREES:
|
||||||
for (lv_coord_t x = 0; x != height; x++) {
|
for (lv_coord_t x = 0; x != height; x++) {
|
||||||
for (lv_coord_t y = width; y-- != 0;) {
|
for (lv_coord_t y = width; y-- != 0;) {
|
||||||
dst[y * height + x] = *ptr++;
|
dst[y * height_rounded + x] = *ptr++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
x1 = y1;
|
x1 = y1;
|
||||||
y1 = this->disp_drv_.hor_res - area->x1 - width;
|
y1 = this->disp_drv_.hor_res - area->x1 - width;
|
||||||
width = height;
|
height = width;
|
||||||
height = lv_area_get_width(area);
|
width = height_rounded;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -435,8 +436,10 @@ LvglComponent::LvglComponent(std::vector<display::Display *> displays, float buf
|
|||||||
|
|
||||||
void LvglComponent::setup() {
|
void LvglComponent::setup() {
|
||||||
auto *display = this->displays_[0];
|
auto *display = this->displays_[0];
|
||||||
auto width = display->get_width();
|
auto rounding = this->draw_rounding;
|
||||||
auto height = display->get_height();
|
// cater for displays with dimensions that don't divide by the required rounding
|
||||||
|
auto width = (display->get_width() + rounding - 1) / rounding * rounding;
|
||||||
|
auto height = (display->get_height() + rounding - 1) / rounding * rounding;
|
||||||
auto frac = this->buffer_frac_;
|
auto frac = this->buffer_frac_;
|
||||||
if (frac == 0)
|
if (frac == 0)
|
||||||
frac = 1;
|
frac = 1;
|
||||||
@@ -461,9 +464,8 @@ void LvglComponent::setup() {
|
|||||||
}
|
}
|
||||||
this->buffer_frac_ = frac;
|
this->buffer_frac_ = frac;
|
||||||
lv_disp_draw_buf_init(&this->draw_buf_, buffer, nullptr, buffer_pixels);
|
lv_disp_draw_buf_init(&this->draw_buf_, buffer, nullptr, buffer_pixels);
|
||||||
this->disp_drv_.hor_res = width;
|
this->disp_drv_.hor_res = display->get_width();
|
||||||
this->disp_drv_.ver_res = height;
|
this->disp_drv_.ver_res = display->get_height();
|
||||||
// this->setup_driver_(display->get_width(), display->get_height());
|
|
||||||
lv_disp_drv_update(this->disp_, &this->disp_drv_);
|
lv_disp_drv_update(this->disp_, &this->disp_drv_);
|
||||||
this->rotation = display->get_rotation();
|
this->rotation = display->get_rotation();
|
||||||
if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) {
|
if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from ..defines import CONF_WIDGET
|
|||||||
from ..lvcode import (
|
from ..lvcode import (
|
||||||
API_EVENT,
|
API_EVENT,
|
||||||
EVENT_ARG,
|
EVENT_ARG,
|
||||||
LVGL_COMP_ARG,
|
|
||||||
UPDATE_EVENT,
|
UPDATE_EVENT,
|
||||||
LambdaContext,
|
LambdaContext,
|
||||||
LvContext,
|
LvContext,
|
||||||
@@ -30,7 +29,7 @@ async def to_code(config):
|
|||||||
await wait_for_widgets()
|
await wait_for_widgets()
|
||||||
async with LambdaContext(EVENT_ARG) as lamb:
|
async with LambdaContext(EVENT_ARG) as lamb:
|
||||||
lv_add(sensor.publish_state(widget.get_value()))
|
lv_add(sensor.publish_state(widget.get_value()))
|
||||||
async with LvContext(LVGL_COMP_ARG):
|
async with LvContext():
|
||||||
lv_add(
|
lv_add(
|
||||||
lvgl_static.add_event_cb(
|
lvgl_static.add_event_cb(
|
||||||
widget.obj,
|
widget.obj,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from esphome import automation
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
from esphome.components.const import CONF_ROWS
|
||||||
from esphome.components.key_provider import KeyProvider
|
from esphome.components.key_provider import KeyProvider
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID, CONF_ITEMS, CONF_TEXT, CONF_WIDTH
|
from esphome.const import CONF_ID, CONF_ITEMS, CONF_TEXT, CONF_WIDTH
|
||||||
@@ -15,7 +16,6 @@ from ..defines import (
|
|||||||
CONF_ONE_CHECKED,
|
CONF_ONE_CHECKED,
|
||||||
CONF_PAD_COLUMN,
|
CONF_PAD_COLUMN,
|
||||||
CONF_PAD_ROW,
|
CONF_PAD_ROW,
|
||||||
CONF_ROWS,
|
|
||||||
CONF_SELECTED,
|
CONF_SELECTED,
|
||||||
)
|
)
|
||||||
from ..helpers import lvgl_components_required
|
from ..helpers import lvgl_components_required
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from esphome import automation, pins
|
from esphome import automation, pins
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import key_provider
|
from esphome.components import key_provider
|
||||||
|
from esphome.components.const import CONF_ROWS
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID, CONF_ON_KEY, CONF_PIN, CONF_TRIGGER_ID
|
from esphome.const import CONF_ID, CONF_ON_KEY, CONF_PIN, CONF_TRIGGER_ID
|
||||||
|
|
||||||
@@ -19,7 +20,6 @@ MatrixKeyTrigger = matrix_keypad_ns.class_(
|
|||||||
)
|
)
|
||||||
|
|
||||||
CONF_KEYPAD_ID = "keypad_id"
|
CONF_KEYPAD_ID = "keypad_id"
|
||||||
CONF_ROWS = "rows"
|
|
||||||
CONF_COLUMNS = "columns"
|
CONF_COLUMNS = "columns"
|
||||||
CONF_KEYS = "keys"
|
CONF_KEYS = "keys"
|
||||||
CONF_DEBOUNCE_TIME = "debounce_time"
|
CONF_DEBOUNCE_TIME = "debounce_time"
|
||||||
|
|||||||
@@ -384,6 +384,18 @@ class DriverChip:
|
|||||||
transform[CONF_TRANSFORM] = True
|
transform[CONF_TRANSFORM] = True
|
||||||
return transform
|
return transform
|
||||||
|
|
||||||
|
def swap_xy_schema(self):
|
||||||
|
uses_swap = self.get_default(CONF_SWAP_XY, None) != cv.UNDEFINED
|
||||||
|
|
||||||
|
def validator(value):
|
||||||
|
if value:
|
||||||
|
raise cv.Invalid("Axis swapping not supported by this model")
|
||||||
|
return cv.boolean(value)
|
||||||
|
|
||||||
|
if uses_swap:
|
||||||
|
return {cv.Required(CONF_SWAP_XY): cv.boolean}
|
||||||
|
return {cv.Optional(CONF_SWAP_XY, default=False): validator}
|
||||||
|
|
||||||
def add_madctl(self, sequence: list, config: dict):
|
def add_madctl(self, sequence: list, config: dict):
|
||||||
# Add the MADCTL command to the sequence based on the configuration.
|
# Add the MADCTL command to the sequence based on the configuration.
|
||||||
use_flip = config.get(CONF_USE_AXIS_FLIPS)
|
use_flip = config.get(CONF_USE_AXIS_FLIPS)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ from esphome.const import (
|
|||||||
CONF_DATA_RATE,
|
CONF_DATA_RATE,
|
||||||
CONF_DC_PIN,
|
CONF_DC_PIN,
|
||||||
CONF_DIMENSIONS,
|
CONF_DIMENSIONS,
|
||||||
|
CONF_DISABLED,
|
||||||
CONF_ENABLE_PIN,
|
CONF_ENABLE_PIN,
|
||||||
CONF_GREEN,
|
CONF_GREEN,
|
||||||
CONF_HSYNC_PIN,
|
CONF_HSYNC_PIN,
|
||||||
@@ -117,16 +118,16 @@ def data_pin_set(length):
|
|||||||
|
|
||||||
def model_schema(config):
|
def model_schema(config):
|
||||||
model = MODELS[config[CONF_MODEL].upper()]
|
model = MODELS[config[CONF_MODEL].upper()]
|
||||||
if transforms := model.transforms:
|
transform = cv.Any(
|
||||||
transform = cv.Schema({cv.Required(x): cv.boolean for x in transforms})
|
cv.Schema(
|
||||||
for x in (CONF_SWAP_XY, CONF_MIRROR_X, CONF_MIRROR_Y):
|
{
|
||||||
if x not in transforms:
|
cv.Required(CONF_MIRROR_X): cv.boolean,
|
||||||
transform = transform.extend(
|
cv.Required(CONF_MIRROR_Y): cv.boolean,
|
||||||
{cv.Optional(x): cv.invalid(f"{x} not supported by this model")}
|
**model.swap_xy_schema(),
|
||||||
)
|
}
|
||||||
else:
|
),
|
||||||
transform = cv.invalid("This model does not support transforms")
|
cv.one_of(CONF_DISABLED, lower=True),
|
||||||
|
)
|
||||||
# RPI model does not use an init sequence, indicates with empty list
|
# RPI model does not use an init sequence, indicates with empty list
|
||||||
if model.initsequence is None:
|
if model.initsequence is None:
|
||||||
# Custom model requires an init sequence
|
# Custom model requires an init sequence
|
||||||
@@ -135,12 +136,16 @@ def model_schema(config):
|
|||||||
else:
|
else:
|
||||||
iseqconf = cv.Optional(CONF_INIT_SEQUENCE)
|
iseqconf = cv.Optional(CONF_INIT_SEQUENCE)
|
||||||
uses_spi = CONF_INIT_SEQUENCE in config or len(model.initsequence) != 0
|
uses_spi = CONF_INIT_SEQUENCE in config or len(model.initsequence) != 0
|
||||||
swap_xy = config.get(CONF_TRANSFORM, {}).get(CONF_SWAP_XY, False)
|
# Dimensions are optional if the model has a default width and the x-y transform is not overridden
|
||||||
|
transform_config = config.get(CONF_TRANSFORM, {})
|
||||||
# Dimensions are optional if the model has a default width and the swap_xy transform is not overridden
|
is_swapped = (
|
||||||
cv_dimensions = (
|
isinstance(transform_config, dict)
|
||||||
cv.Optional if model.get_default(CONF_WIDTH) and not swap_xy else cv.Required
|
and transform_config.get(CONF_SWAP_XY, False) is True
|
||||||
)
|
)
|
||||||
|
cv_dimensions = (
|
||||||
|
cv.Optional if model.get_default(CONF_WIDTH) and not is_swapped else cv.Required
|
||||||
|
)
|
||||||
|
|
||||||
pixel_modes = (PIXEL_MODE_16BIT, PIXEL_MODE_18BIT, "16", "18")
|
pixel_modes = (PIXEL_MODE_16BIT, PIXEL_MODE_18BIT, "16", "18")
|
||||||
schema = display.FULL_DISPLAY_SCHEMA.extend(
|
schema = display.FULL_DISPLAY_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
@@ -157,7 +162,7 @@ def model_schema(config):
|
|||||||
model.option(CONF_PIXEL_MODE, PIXEL_MODE_16BIT): cv.one_of(
|
model.option(CONF_PIXEL_MODE, PIXEL_MODE_16BIT): cv.one_of(
|
||||||
*pixel_modes, lower=True
|
*pixel_modes, lower=True
|
||||||
),
|
),
|
||||||
model.option(CONF_TRANSFORM, cv.UNDEFINED): transform,
|
cv.Optional(CONF_TRANSFORM): transform,
|
||||||
cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True),
|
cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True),
|
||||||
model.option(CONF_INVERT_COLORS, False): cv.boolean,
|
model.option(CONF_INVERT_COLORS, False): cv.boolean,
|
||||||
model.option(CONF_USE_AXIS_FLIPS, True): cv.boolean,
|
model.option(CONF_USE_AXIS_FLIPS, True): cv.boolean,
|
||||||
@@ -270,7 +275,6 @@ async def to_code(config):
|
|||||||
cg.add(var.set_vsync_front_porch(config[CONF_VSYNC_FRONT_PORCH]))
|
cg.add(var.set_vsync_front_porch(config[CONF_VSYNC_FRONT_PORCH]))
|
||||||
cg.add(var.set_pclk_inverted(config[CONF_PCLK_INVERTED]))
|
cg.add(var.set_pclk_inverted(config[CONF_PCLK_INVERTED]))
|
||||||
cg.add(var.set_pclk_frequency(config[CONF_PCLK_FREQUENCY]))
|
cg.add(var.set_pclk_frequency(config[CONF_PCLK_FREQUENCY]))
|
||||||
index = 0
|
|
||||||
dpins = []
|
dpins = []
|
||||||
if CONF_RED in config[CONF_DATA_PINS]:
|
if CONF_RED in config[CONF_DATA_PINS]:
|
||||||
red_pins = config[CONF_DATA_PINS][CONF_RED]
|
red_pins = config[CONF_DATA_PINS][CONF_RED]
|
||||||
|
|||||||
@@ -131,19 +131,6 @@ def denominator(config):
|
|||||||
) from StopIteration
|
) from StopIteration
|
||||||
|
|
||||||
|
|
||||||
def swap_xy_schema(model):
|
|
||||||
uses_swap = model.get_default(CONF_SWAP_XY, None) != cv.UNDEFINED
|
|
||||||
|
|
||||||
def validator(value):
|
|
||||||
if value:
|
|
||||||
raise cv.Invalid("Axis swapping not supported by this model")
|
|
||||||
return cv.boolean(value)
|
|
||||||
|
|
||||||
if uses_swap:
|
|
||||||
return {cv.Required(CONF_SWAP_XY): cv.boolean}
|
|
||||||
return {cv.Optional(CONF_SWAP_XY, default=False): validator}
|
|
||||||
|
|
||||||
|
|
||||||
def model_schema(config):
|
def model_schema(config):
|
||||||
model = MODELS[config[CONF_MODEL]]
|
model = MODELS[config[CONF_MODEL]]
|
||||||
bus_mode = config[CONF_BUS_MODE]
|
bus_mode = config[CONF_BUS_MODE]
|
||||||
@@ -152,7 +139,7 @@ def model_schema(config):
|
|||||||
{
|
{
|
||||||
cv.Required(CONF_MIRROR_X): cv.boolean,
|
cv.Required(CONF_MIRROR_X): cv.boolean,
|
||||||
cv.Required(CONF_MIRROR_Y): cv.boolean,
|
cv.Required(CONF_MIRROR_Y): cv.boolean,
|
||||||
**swap_xy_schema(model),
|
**model.swap_xy_schema(),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
cv.one_of(CONF_DISABLED, lower=True),
|
cv.one_of(CONF_DISABLED, lower=True),
|
||||||
|
|||||||
@@ -12,241 +12,256 @@ CODEOWNERS = ["@bdm310"]
|
|||||||
|
|
||||||
STATE_ARG = "state"
|
STATE_ARG = "state"
|
||||||
|
|
||||||
SDL_KEYMAP = {
|
SDL_KeyCode = cg.global_ns.enum("SDL_KeyCode")
|
||||||
"SDLK_UNKNOWN": 0,
|
|
||||||
"SDLK_FIRST": 0,
|
SDL_KEYS = (
|
||||||
"SDLK_BACKSPACE": 8,
|
"SDLK_UNKNOWN",
|
||||||
"SDLK_TAB": 9,
|
"SDLK_RETURN",
|
||||||
"SDLK_CLEAR": 12,
|
"SDLK_ESCAPE",
|
||||||
"SDLK_RETURN": 13,
|
"SDLK_BACKSPACE",
|
||||||
"SDLK_PAUSE": 19,
|
"SDLK_TAB",
|
||||||
"SDLK_ESCAPE": 27,
|
"SDLK_SPACE",
|
||||||
"SDLK_SPACE": 32,
|
"SDLK_EXCLAIM",
|
||||||
"SDLK_EXCLAIM": 33,
|
"SDLK_QUOTEDBL",
|
||||||
"SDLK_QUOTEDBL": 34,
|
"SDLK_HASH",
|
||||||
"SDLK_HASH": 35,
|
"SDLK_PERCENT",
|
||||||
"SDLK_DOLLAR": 36,
|
"SDLK_DOLLAR",
|
||||||
"SDLK_AMPERSAND": 38,
|
"SDLK_AMPERSAND",
|
||||||
"SDLK_QUOTE": 39,
|
"SDLK_QUOTE",
|
||||||
"SDLK_LEFTPAREN": 40,
|
"SDLK_LEFTPAREN",
|
||||||
"SDLK_RIGHTPAREN": 41,
|
"SDLK_RIGHTPAREN",
|
||||||
"SDLK_ASTERISK": 42,
|
"SDLK_ASTERISK",
|
||||||
"SDLK_PLUS": 43,
|
"SDLK_PLUS",
|
||||||
"SDLK_COMMA": 44,
|
"SDLK_COMMA",
|
||||||
"SDLK_MINUS": 45,
|
"SDLK_MINUS",
|
||||||
"SDLK_PERIOD": 46,
|
"SDLK_PERIOD",
|
||||||
"SDLK_SLASH": 47,
|
"SDLK_SLASH",
|
||||||
"SDLK_0": 48,
|
"SDLK_0",
|
||||||
"SDLK_1": 49,
|
"SDLK_1",
|
||||||
"SDLK_2": 50,
|
"SDLK_2",
|
||||||
"SDLK_3": 51,
|
"SDLK_3",
|
||||||
"SDLK_4": 52,
|
"SDLK_4",
|
||||||
"SDLK_5": 53,
|
"SDLK_5",
|
||||||
"SDLK_6": 54,
|
"SDLK_6",
|
||||||
"SDLK_7": 55,
|
"SDLK_7",
|
||||||
"SDLK_8": 56,
|
"SDLK_8",
|
||||||
"SDLK_9": 57,
|
"SDLK_9",
|
||||||
"SDLK_COLON": 58,
|
"SDLK_COLON",
|
||||||
"SDLK_SEMICOLON": 59,
|
"SDLK_SEMICOLON",
|
||||||
"SDLK_LESS": 60,
|
"SDLK_LESS",
|
||||||
"SDLK_EQUALS": 61,
|
"SDLK_EQUALS",
|
||||||
"SDLK_GREATER": 62,
|
"SDLK_GREATER",
|
||||||
"SDLK_QUESTION": 63,
|
"SDLK_QUESTION",
|
||||||
"SDLK_AT": 64,
|
"SDLK_AT",
|
||||||
"SDLK_LEFTBRACKET": 91,
|
"SDLK_LEFTBRACKET",
|
||||||
"SDLK_BACKSLASH": 92,
|
"SDLK_BACKSLASH",
|
||||||
"SDLK_RIGHTBRACKET": 93,
|
"SDLK_RIGHTBRACKET",
|
||||||
"SDLK_CARET": 94,
|
"SDLK_CARET",
|
||||||
"SDLK_UNDERSCORE": 95,
|
"SDLK_UNDERSCORE",
|
||||||
"SDLK_BACKQUOTE": 96,
|
"SDLK_BACKQUOTE",
|
||||||
"SDLK_a": 97,
|
"SDLK_a",
|
||||||
"SDLK_b": 98,
|
"SDLK_b",
|
||||||
"SDLK_c": 99,
|
"SDLK_c",
|
||||||
"SDLK_d": 100,
|
"SDLK_d",
|
||||||
"SDLK_e": 101,
|
"SDLK_e",
|
||||||
"SDLK_f": 102,
|
"SDLK_f",
|
||||||
"SDLK_g": 103,
|
"SDLK_g",
|
||||||
"SDLK_h": 104,
|
"SDLK_h",
|
||||||
"SDLK_i": 105,
|
"SDLK_i",
|
||||||
"SDLK_j": 106,
|
"SDLK_j",
|
||||||
"SDLK_k": 107,
|
"SDLK_k",
|
||||||
"SDLK_l": 108,
|
"SDLK_l",
|
||||||
"SDLK_m": 109,
|
"SDLK_m",
|
||||||
"SDLK_n": 110,
|
"SDLK_n",
|
||||||
"SDLK_o": 111,
|
"SDLK_o",
|
||||||
"SDLK_p": 112,
|
"SDLK_p",
|
||||||
"SDLK_q": 113,
|
"SDLK_q",
|
||||||
"SDLK_r": 114,
|
"SDLK_r",
|
||||||
"SDLK_s": 115,
|
"SDLK_s",
|
||||||
"SDLK_t": 116,
|
"SDLK_t",
|
||||||
"SDLK_u": 117,
|
"SDLK_u",
|
||||||
"SDLK_v": 118,
|
"SDLK_v",
|
||||||
"SDLK_w": 119,
|
"SDLK_w",
|
||||||
"SDLK_x": 120,
|
"SDLK_x",
|
||||||
"SDLK_y": 121,
|
"SDLK_y",
|
||||||
"SDLK_z": 122,
|
"SDLK_z",
|
||||||
"SDLK_DELETE": 127,
|
"SDLK_CAPSLOCK",
|
||||||
"SDLK_WORLD_0": 160,
|
"SDLK_F1",
|
||||||
"SDLK_WORLD_1": 161,
|
"SDLK_F2",
|
||||||
"SDLK_WORLD_2": 162,
|
"SDLK_F3",
|
||||||
"SDLK_WORLD_3": 163,
|
"SDLK_F4",
|
||||||
"SDLK_WORLD_4": 164,
|
"SDLK_F5",
|
||||||
"SDLK_WORLD_5": 165,
|
"SDLK_F6",
|
||||||
"SDLK_WORLD_6": 166,
|
"SDLK_F7",
|
||||||
"SDLK_WORLD_7": 167,
|
"SDLK_F8",
|
||||||
"SDLK_WORLD_8": 168,
|
"SDLK_F9",
|
||||||
"SDLK_WORLD_9": 169,
|
"SDLK_F10",
|
||||||
"SDLK_WORLD_10": 170,
|
"SDLK_F11",
|
||||||
"SDLK_WORLD_11": 171,
|
"SDLK_F12",
|
||||||
"SDLK_WORLD_12": 172,
|
"SDLK_PRINTSCREEN",
|
||||||
"SDLK_WORLD_13": 173,
|
"SDLK_SCROLLLOCK",
|
||||||
"SDLK_WORLD_14": 174,
|
"SDLK_PAUSE",
|
||||||
"SDLK_WORLD_15": 175,
|
"SDLK_INSERT",
|
||||||
"SDLK_WORLD_16": 176,
|
"SDLK_HOME",
|
||||||
"SDLK_WORLD_17": 177,
|
"SDLK_PAGEUP",
|
||||||
"SDLK_WORLD_18": 178,
|
"SDLK_DELETE",
|
||||||
"SDLK_WORLD_19": 179,
|
"SDLK_END",
|
||||||
"SDLK_WORLD_20": 180,
|
"SDLK_PAGEDOWN",
|
||||||
"SDLK_WORLD_21": 181,
|
"SDLK_RIGHT",
|
||||||
"SDLK_WORLD_22": 182,
|
"SDLK_LEFT",
|
||||||
"SDLK_WORLD_23": 183,
|
"SDLK_DOWN",
|
||||||
"SDLK_WORLD_24": 184,
|
"SDLK_UP",
|
||||||
"SDLK_WORLD_25": 185,
|
"SDLK_NUMLOCKCLEAR",
|
||||||
"SDLK_WORLD_26": 186,
|
"SDLK_KP_DIVIDE",
|
||||||
"SDLK_WORLD_27": 187,
|
"SDLK_KP_MULTIPLY",
|
||||||
"SDLK_WORLD_28": 188,
|
"SDLK_KP_MINUS",
|
||||||
"SDLK_WORLD_29": 189,
|
"SDLK_KP_PLUS",
|
||||||
"SDLK_WORLD_30": 190,
|
"SDLK_KP_ENTER",
|
||||||
"SDLK_WORLD_31": 191,
|
"SDLK_KP_1",
|
||||||
"SDLK_WORLD_32": 192,
|
"SDLK_KP_2",
|
||||||
"SDLK_WORLD_33": 193,
|
"SDLK_KP_3",
|
||||||
"SDLK_WORLD_34": 194,
|
"SDLK_KP_4",
|
||||||
"SDLK_WORLD_35": 195,
|
"SDLK_KP_5",
|
||||||
"SDLK_WORLD_36": 196,
|
"SDLK_KP_6",
|
||||||
"SDLK_WORLD_37": 197,
|
"SDLK_KP_7",
|
||||||
"SDLK_WORLD_38": 198,
|
"SDLK_KP_8",
|
||||||
"SDLK_WORLD_39": 199,
|
"SDLK_KP_9",
|
||||||
"SDLK_WORLD_40": 200,
|
"SDLK_KP_0",
|
||||||
"SDLK_WORLD_41": 201,
|
"SDLK_KP_PERIOD",
|
||||||
"SDLK_WORLD_42": 202,
|
"SDLK_APPLICATION",
|
||||||
"SDLK_WORLD_43": 203,
|
"SDLK_POWER",
|
||||||
"SDLK_WORLD_44": 204,
|
"SDLK_KP_EQUALS",
|
||||||
"SDLK_WORLD_45": 205,
|
"SDLK_F13",
|
||||||
"SDLK_WORLD_46": 206,
|
"SDLK_F14",
|
||||||
"SDLK_WORLD_47": 207,
|
"SDLK_F15",
|
||||||
"SDLK_WORLD_48": 208,
|
"SDLK_F16",
|
||||||
"SDLK_WORLD_49": 209,
|
"SDLK_F17",
|
||||||
"SDLK_WORLD_50": 210,
|
"SDLK_F18",
|
||||||
"SDLK_WORLD_51": 211,
|
"SDLK_F19",
|
||||||
"SDLK_WORLD_52": 212,
|
"SDLK_F20",
|
||||||
"SDLK_WORLD_53": 213,
|
"SDLK_F21",
|
||||||
"SDLK_WORLD_54": 214,
|
"SDLK_F22",
|
||||||
"SDLK_WORLD_55": 215,
|
"SDLK_F23",
|
||||||
"SDLK_WORLD_56": 216,
|
"SDLK_F24",
|
||||||
"SDLK_WORLD_57": 217,
|
"SDLK_EXECUTE",
|
||||||
"SDLK_WORLD_58": 218,
|
"SDLK_HELP",
|
||||||
"SDLK_WORLD_59": 219,
|
"SDLK_MENU",
|
||||||
"SDLK_WORLD_60": 220,
|
"SDLK_SELECT",
|
||||||
"SDLK_WORLD_61": 221,
|
"SDLK_STOP",
|
||||||
"SDLK_WORLD_62": 222,
|
"SDLK_AGAIN",
|
||||||
"SDLK_WORLD_63": 223,
|
"SDLK_UNDO",
|
||||||
"SDLK_WORLD_64": 224,
|
"SDLK_CUT",
|
||||||
"SDLK_WORLD_65": 225,
|
"SDLK_COPY",
|
||||||
"SDLK_WORLD_66": 226,
|
"SDLK_PASTE",
|
||||||
"SDLK_WORLD_67": 227,
|
"SDLK_FIND",
|
||||||
"SDLK_WORLD_68": 228,
|
"SDLK_MUTE",
|
||||||
"SDLK_WORLD_69": 229,
|
"SDLK_VOLUMEUP",
|
||||||
"SDLK_WORLD_70": 230,
|
"SDLK_VOLUMEDOWN",
|
||||||
"SDLK_WORLD_71": 231,
|
"SDLK_KP_COMMA",
|
||||||
"SDLK_WORLD_72": 232,
|
"SDLK_KP_EQUALSAS400",
|
||||||
"SDLK_WORLD_73": 233,
|
"SDLK_ALTERASE",
|
||||||
"SDLK_WORLD_74": 234,
|
"SDLK_SYSREQ",
|
||||||
"SDLK_WORLD_75": 235,
|
"SDLK_CANCEL",
|
||||||
"SDLK_WORLD_76": 236,
|
"SDLK_CLEAR",
|
||||||
"SDLK_WORLD_77": 237,
|
"SDLK_PRIOR",
|
||||||
"SDLK_WORLD_78": 238,
|
"SDLK_RETURN2",
|
||||||
"SDLK_WORLD_79": 239,
|
"SDLK_SEPARATOR",
|
||||||
"SDLK_WORLD_80": 240,
|
"SDLK_OUT",
|
||||||
"SDLK_WORLD_81": 241,
|
"SDLK_OPER",
|
||||||
"SDLK_WORLD_82": 242,
|
"SDLK_CLEARAGAIN",
|
||||||
"SDLK_WORLD_83": 243,
|
"SDLK_CRSEL",
|
||||||
"SDLK_WORLD_84": 244,
|
"SDLK_EXSEL",
|
||||||
"SDLK_WORLD_85": 245,
|
"SDLK_KP_00",
|
||||||
"SDLK_WORLD_86": 246,
|
"SDLK_KP_000",
|
||||||
"SDLK_WORLD_87": 247,
|
"SDLK_THOUSANDSSEPARATOR",
|
||||||
"SDLK_WORLD_88": 248,
|
"SDLK_DECIMALSEPARATOR",
|
||||||
"SDLK_WORLD_89": 249,
|
"SDLK_CURRENCYUNIT",
|
||||||
"SDLK_WORLD_90": 250,
|
"SDLK_CURRENCYSUBUNIT",
|
||||||
"SDLK_WORLD_91": 251,
|
"SDLK_KP_LEFTPAREN",
|
||||||
"SDLK_WORLD_92": 252,
|
"SDLK_KP_RIGHTPAREN",
|
||||||
"SDLK_WORLD_93": 253,
|
"SDLK_KP_LEFTBRACE",
|
||||||
"SDLK_WORLD_94": 254,
|
"SDLK_KP_RIGHTBRACE",
|
||||||
"SDLK_WORLD_95": 255,
|
"SDLK_KP_TAB",
|
||||||
"SDLK_KP0": 256,
|
"SDLK_KP_BACKSPACE",
|
||||||
"SDLK_KP1": 257,
|
"SDLK_KP_A",
|
||||||
"SDLK_KP2": 258,
|
"SDLK_KP_B",
|
||||||
"SDLK_KP3": 259,
|
"SDLK_KP_C",
|
||||||
"SDLK_KP4": 260,
|
"SDLK_KP_D",
|
||||||
"SDLK_KP5": 261,
|
"SDLK_KP_E",
|
||||||
"SDLK_KP6": 262,
|
"SDLK_KP_F",
|
||||||
"SDLK_KP7": 263,
|
"SDLK_KP_XOR",
|
||||||
"SDLK_KP8": 264,
|
"SDLK_KP_POWER",
|
||||||
"SDLK_KP9": 265,
|
"SDLK_KP_PERCENT",
|
||||||
"SDLK_KP_PERIOD": 266,
|
"SDLK_KP_LESS",
|
||||||
"SDLK_KP_DIVIDE": 267,
|
"SDLK_KP_GREATER",
|
||||||
"SDLK_KP_MULTIPLY": 268,
|
"SDLK_KP_AMPERSAND",
|
||||||
"SDLK_KP_MINUS": 269,
|
"SDLK_KP_DBLAMPERSAND",
|
||||||
"SDLK_KP_PLUS": 270,
|
"SDLK_KP_VERTICALBAR",
|
||||||
"SDLK_KP_ENTER": 271,
|
"SDLK_KP_DBLVERTICALBAR",
|
||||||
"SDLK_KP_EQUALS": 272,
|
"SDLK_KP_COLON",
|
||||||
"SDLK_UP": 273,
|
"SDLK_KP_HASH",
|
||||||
"SDLK_DOWN": 274,
|
"SDLK_KP_SPACE",
|
||||||
"SDLK_RIGHT": 275,
|
"SDLK_KP_AT",
|
||||||
"SDLK_LEFT": 276,
|
"SDLK_KP_EXCLAM",
|
||||||
"SDLK_INSERT": 277,
|
"SDLK_KP_MEMSTORE",
|
||||||
"SDLK_HOME": 278,
|
"SDLK_KP_MEMRECALL",
|
||||||
"SDLK_END": 279,
|
"SDLK_KP_MEMCLEAR",
|
||||||
"SDLK_PAGEUP": 280,
|
"SDLK_KP_MEMADD",
|
||||||
"SDLK_PAGEDOWN": 281,
|
"SDLK_KP_MEMSUBTRACT",
|
||||||
"SDLK_F1": 282,
|
"SDLK_KP_MEMMULTIPLY",
|
||||||
"SDLK_F2": 283,
|
"SDLK_KP_MEMDIVIDE",
|
||||||
"SDLK_F3": 284,
|
"SDLK_KP_PLUSMINUS",
|
||||||
"SDLK_F4": 285,
|
"SDLK_KP_CLEAR",
|
||||||
"SDLK_F5": 286,
|
"SDLK_KP_CLEARENTRY",
|
||||||
"SDLK_F6": 287,
|
"SDLK_KP_BINARY",
|
||||||
"SDLK_F7": 288,
|
"SDLK_KP_OCTAL",
|
||||||
"SDLK_F8": 289,
|
"SDLK_KP_DECIMAL",
|
||||||
"SDLK_F9": 290,
|
"SDLK_KP_HEXADECIMAL",
|
||||||
"SDLK_F10": 291,
|
"SDLK_LCTRL",
|
||||||
"SDLK_F11": 292,
|
"SDLK_LSHIFT",
|
||||||
"SDLK_F12": 293,
|
"SDLK_LALT",
|
||||||
"SDLK_F13": 294,
|
"SDLK_LGUI",
|
||||||
"SDLK_F14": 295,
|
"SDLK_RCTRL",
|
||||||
"SDLK_F15": 296,
|
"SDLK_RSHIFT",
|
||||||
"SDLK_NUMLOCK": 300,
|
"SDLK_RALT",
|
||||||
"SDLK_CAPSLOCK": 301,
|
"SDLK_RGUI",
|
||||||
"SDLK_SCROLLOCK": 302,
|
"SDLK_MODE",
|
||||||
"SDLK_RSHIFT": 303,
|
"SDLK_AUDIONEXT",
|
||||||
"SDLK_LSHIFT": 304,
|
"SDLK_AUDIOPREV",
|
||||||
"SDLK_RCTRL": 305,
|
"SDLK_AUDIOSTOP",
|
||||||
"SDLK_LCTRL": 306,
|
"SDLK_AUDIOPLAY",
|
||||||
"SDLK_RALT": 307,
|
"SDLK_AUDIOMUTE",
|
||||||
"SDLK_LALT": 308,
|
"SDLK_MEDIASELECT",
|
||||||
"SDLK_RMETA": 309,
|
"SDLK_WWW",
|
||||||
"SDLK_LMETA": 310,
|
"SDLK_MAIL",
|
||||||
"SDLK_LSUPER": 311,
|
"SDLK_CALCULATOR",
|
||||||
"SDLK_RSUPER": 312,
|
"SDLK_COMPUTER",
|
||||||
"SDLK_MODE": 313,
|
"SDLK_AC_SEARCH",
|
||||||
"SDLK_COMPOSE": 314,
|
"SDLK_AC_HOME",
|
||||||
"SDLK_HELP": 315,
|
"SDLK_AC_BACK",
|
||||||
"SDLK_PRINT": 316,
|
"SDLK_AC_FORWARD",
|
||||||
"SDLK_SYSREQ": 317,
|
"SDLK_AC_STOP",
|
||||||
"SDLK_BREAK": 318,
|
"SDLK_AC_REFRESH",
|
||||||
"SDLK_MENU": 319,
|
"SDLK_AC_BOOKMARKS",
|
||||||
"SDLK_POWER": 320,
|
"SDLK_BRIGHTNESSDOWN",
|
||||||
"SDLK_EURO": 321,
|
"SDLK_BRIGHTNESSUP",
|
||||||
"SDLK_UNDO": 322,
|
"SDLK_DISPLAYSWITCH",
|
||||||
}
|
"SDLK_KBDILLUMTOGGLE",
|
||||||
|
"SDLK_KBDILLUMDOWN",
|
||||||
|
"SDLK_KBDILLUMUP",
|
||||||
|
"SDLK_EJECT",
|
||||||
|
"SDLK_SLEEP",
|
||||||
|
"SDLK_APP1",
|
||||||
|
"SDLK_APP2",
|
||||||
|
"SDLK_AUDIOREWIND",
|
||||||
|
"SDLK_AUDIOFASTFORWARD",
|
||||||
|
"SDLK_SOFTLEFT",
|
||||||
|
"SDLK_SOFTRIGHT",
|
||||||
|
"SDLK_CALL",
|
||||||
|
"SDLK_ENDCALL",
|
||||||
|
)
|
||||||
|
|
||||||
|
SDL_KEYMAP = {key: getattr(SDL_KeyCode, key) for key in SDL_KEYS}
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
binary_sensor.binary_sensor_schema(BinarySensor)
|
binary_sensor.binary_sensor_schema(BinarySensor)
|
||||||
|
|||||||
@@ -56,6 +56,13 @@ uint32_t ESP8266UartComponent::get_config() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ESP8266UartComponent::setup() {
|
void ESP8266UartComponent::setup() {
|
||||||
|
if (this->rx_pin_) {
|
||||||
|
this->rx_pin_->setup();
|
||||||
|
}
|
||||||
|
if (this->tx_pin_ && this->rx_pin_ != this->tx_pin_) {
|
||||||
|
this->tx_pin_->setup();
|
||||||
|
}
|
||||||
|
|
||||||
// Use Arduino HardwareSerial UARTs if all used pins match the ones
|
// Use Arduino HardwareSerial UARTs if all used pins match the ones
|
||||||
// preconfigured by the platform. For example if RX disabled but TX pin
|
// preconfigured by the platform. For example if RX disabled but TX pin
|
||||||
// is 1 we still want to use Serial.
|
// is 1 we still want to use Serial.
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/gpio.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "soc/gpio_num.h"
|
||||||
|
|
||||||
#ifdef USE_LOGGER
|
#ifdef USE_LOGGER
|
||||||
#include "esphome/components/logger/logger.h"
|
#include "esphome/components/logger/logger.h"
|
||||||
@@ -96,23 +99,48 @@ void IDFUARTComponent::setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void IDFUARTComponent::load_settings(bool dump_config) {
|
void IDFUARTComponent::load_settings(bool dump_config) {
|
||||||
uart_config_t uart_config = this->get_config_();
|
esp_err_t err;
|
||||||
esp_err_t err = uart_param_config(this->uart_num_, &uart_config);
|
|
||||||
|
if (uart_is_driver_installed(this->uart_num_)) {
|
||||||
|
err = uart_driver_delete(this->uart_num_);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err));
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = uart_driver_install(this->uart_num_, // UART number
|
||||||
|
this->rx_buffer_size_, // RX ring buffer size
|
||||||
|
0, // TX ring buffer size. If zero, driver will not use a TX buffer and TX function will
|
||||||
|
// block task until all data has been sent out
|
||||||
|
20, // event queue size/depth
|
||||||
|
&this->uart_event_queue_, // event queue
|
||||||
|
0 // Flags used to allocate the interrupt
|
||||||
|
);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "uart_param_config failed: %s", esp_err_to_name(err));
|
ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err));
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this->rx_pin_) {
|
||||||
|
this->rx_pin_->setup();
|
||||||
|
}
|
||||||
|
if (this->tx_pin_ && this->rx_pin_ != this->tx_pin_) {
|
||||||
|
this->tx_pin_->setup();
|
||||||
|
}
|
||||||
|
|
||||||
int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1;
|
int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1;
|
||||||
int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1;
|
int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1;
|
||||||
int8_t flow_control = this->flow_control_pin_ != nullptr ? this->flow_control_pin_->get_pin() : -1;
|
int8_t flow_control = this->flow_control_pin_ != nullptr ? this->flow_control_pin_->get_pin() : -1;
|
||||||
|
|
||||||
uint32_t invert = 0;
|
uint32_t invert = 0;
|
||||||
if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted())
|
if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) {
|
||||||
invert |= UART_SIGNAL_TXD_INV;
|
invert |= UART_SIGNAL_TXD_INV;
|
||||||
if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted())
|
}
|
||||||
|
if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) {
|
||||||
invert |= UART_SIGNAL_RXD_INV;
|
invert |= UART_SIGNAL_RXD_INV;
|
||||||
|
}
|
||||||
|
|
||||||
err = uart_set_line_inverse(this->uart_num_, invert);
|
err = uart_set_line_inverse(this->uart_num_, invert);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
@@ -128,26 +156,6 @@ void IDFUARTComponent::load_settings(bool dump_config) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uart_is_driver_installed(this->uart_num_)) {
|
|
||||||
uart_driver_delete(this->uart_num_);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err));
|
|
||||||
this->mark_failed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = uart_driver_install(this->uart_num_, /* UART RX ring buffer size. */ this->rx_buffer_size_,
|
|
||||||
/* UART TX ring buffer size. If set to zero, driver will not use TX buffer, TX function will
|
|
||||||
block task until all data have been sent out.*/
|
|
||||||
0,
|
|
||||||
/* UART event queue size/depth. */ 20, &(this->uart_event_queue_),
|
|
||||||
/* Flags used to allocate the interrupt. */ 0);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err));
|
|
||||||
this->mark_failed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = uart_set_rx_full_threshold(this->uart_num_, this->rx_full_threshold_);
|
err = uart_set_rx_full_threshold(this->uart_num_, this->rx_full_threshold_);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "uart_set_rx_full_threshold failed: %s", esp_err_to_name(err));
|
ESP_LOGW(TAG, "uart_set_rx_full_threshold failed: %s", esp_err_to_name(err));
|
||||||
@@ -163,24 +171,32 @@ void IDFUARTComponent::load_settings(bool dump_config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto mode = this->flow_control_pin_ != nullptr ? UART_MODE_RS485_HALF_DUPLEX : UART_MODE_UART;
|
auto mode = this->flow_control_pin_ != nullptr ? UART_MODE_RS485_HALF_DUPLEX : UART_MODE_UART;
|
||||||
err = uart_set_mode(this->uart_num_, mode);
|
err = uart_set_mode(this->uart_num_, mode); // per docs, must be called only after uart_driver_install()
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "uart_set_mode failed: %s", esp_err_to_name(err));
|
ESP_LOGW(TAG, "uart_set_mode failed: %s", esp_err_to_name(err));
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uart_config_t uart_config = this->get_config_();
|
||||||
|
err = uart_param_config(this->uart_num_, &uart_config);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "uart_param_config failed: %s", esp_err_to_name(err));
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (dump_config) {
|
if (dump_config) {
|
||||||
ESP_LOGCONFIG(TAG, "UART %u was reloaded.", this->uart_num_);
|
ESP_LOGCONFIG(TAG, "Reloaded UART %u", this->uart_num_);
|
||||||
this->dump_config();
|
this->dump_config();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IDFUARTComponent::dump_config() {
|
void IDFUARTComponent::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "UART Bus %u:", this->uart_num_);
|
ESP_LOGCONFIG(TAG, "UART Bus %u:", this->uart_num_);
|
||||||
LOG_PIN(" TX Pin: ", tx_pin_);
|
LOG_PIN(" TX Pin: ", this->tx_pin_);
|
||||||
LOG_PIN(" RX Pin: ", rx_pin_);
|
LOG_PIN(" RX Pin: ", this->rx_pin_);
|
||||||
LOG_PIN(" Flow Control Pin: ", flow_control_pin_);
|
LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_);
|
||||||
if (this->rx_pin_ != nullptr) {
|
if (this->rx_pin_ != nullptr) {
|
||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
" RX Buffer Size: %u\n"
|
" RX Buffer Size: %u\n"
|
||||||
|
|||||||
@@ -51,28 +51,53 @@ void LibreTinyUARTComponent::setup() {
|
|||||||
bool tx_inverted = tx_pin_ != nullptr && tx_pin_->is_inverted();
|
bool tx_inverted = tx_pin_ != nullptr && tx_pin_->is_inverted();
|
||||||
bool rx_inverted = rx_pin_ != nullptr && rx_pin_->is_inverted();
|
bool rx_inverted = rx_pin_ != nullptr && rx_pin_->is_inverted();
|
||||||
|
|
||||||
|
auto shouldFallbackToSoftwareSerial = [&]() -> bool {
|
||||||
|
auto hasFlags = [](InternalGPIOPin *pin, const gpio::Flags mask) -> bool {
|
||||||
|
return pin && pin->get_flags() & mask != gpio::Flags::FLAG_NONE;
|
||||||
|
};
|
||||||
|
if (hasFlags(this->tx_pin_, gpio::Flags::FLAG_OPEN_DRAIN | gpio::Flags::FLAG_PULLUP | gpio::Flags::FLAG_PULLDOWN) ||
|
||||||
|
hasFlags(this->rx_pin_, gpio::Flags::FLAG_OPEN_DRAIN | gpio::Flags::FLAG_PULLUP | gpio::Flags::FLAG_PULLDOWN)) {
|
||||||
|
#if LT_ARD_HAS_SOFTSERIAL
|
||||||
|
ESP_LOGI(TAG, "Pins has flags set. Using Software Serial");
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
ESP_LOGW(TAG, "Pin flags are set but not supported for hardware serial. Ignoring");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
if (false)
|
if (false)
|
||||||
return;
|
return;
|
||||||
#if LT_HW_UART0
|
#if LT_HW_UART0
|
||||||
else if ((tx_pin == -1 || tx_pin == PIN_SERIAL0_TX) && (rx_pin == -1 || rx_pin == PIN_SERIAL0_RX)) {
|
else if ((tx_pin == -1 || tx_pin == PIN_SERIAL0_TX) && (rx_pin == -1 || rx_pin == PIN_SERIAL0_RX) &&
|
||||||
|
!shouldFallbackToSoftwareSerial()) {
|
||||||
this->serial_ = &Serial0;
|
this->serial_ = &Serial0;
|
||||||
this->hardware_idx_ = 0;
|
this->hardware_idx_ = 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if LT_HW_UART1
|
#if LT_HW_UART1
|
||||||
else if ((tx_pin == -1 || tx_pin == PIN_SERIAL1_TX) && (rx_pin == -1 || rx_pin == PIN_SERIAL1_RX)) {
|
else if ((tx_pin == -1 || tx_pin == PIN_SERIAL1_TX) && (rx_pin == -1 || rx_pin == PIN_SERIAL1_RX) &&
|
||||||
|
!shouldFallbackToSoftwareSerial()) {
|
||||||
this->serial_ = &Serial1;
|
this->serial_ = &Serial1;
|
||||||
this->hardware_idx_ = 1;
|
this->hardware_idx_ = 1;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if LT_HW_UART2
|
#if LT_HW_UART2
|
||||||
else if ((tx_pin == -1 || tx_pin == PIN_SERIAL2_TX) && (rx_pin == -1 || rx_pin == PIN_SERIAL2_RX)) {
|
else if ((tx_pin == -1 || tx_pin == PIN_SERIAL2_TX) && (rx_pin == -1 || rx_pin == PIN_SERIAL2_RX) &&
|
||||||
|
!shouldFallbackToSoftwareSerial()) {
|
||||||
this->serial_ = &Serial2;
|
this->serial_ = &Serial2;
|
||||||
this->hardware_idx_ = 2;
|
this->hardware_idx_ = 2;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
else {
|
else {
|
||||||
#if LT_ARD_HAS_SOFTSERIAL
|
#if LT_ARD_HAS_SOFTSERIAL
|
||||||
|
if (this->rx_pin_) {
|
||||||
|
this->rx_pin_->setup();
|
||||||
|
}
|
||||||
|
if (this->tx_pin_ && this->rx_pin_ != this->tx_pin_) {
|
||||||
|
this->tx_pin_->setup();
|
||||||
|
}
|
||||||
this->serial_ = new SoftwareSerial(rx_pin, tx_pin, rx_inverted || tx_inverted);
|
this->serial_ = new SoftwareSerial(rx_pin, tx_pin, rx_inverted || tx_inverted);
|
||||||
#else
|
#else
|
||||||
this->serial_ = &Serial;
|
this->serial_ = &Serial;
|
||||||
|
|||||||
@@ -52,6 +52,13 @@ uint16_t RP2040UartComponent::get_config() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RP2040UartComponent::setup() {
|
void RP2040UartComponent::setup() {
|
||||||
|
if (this->rx_pin_) {
|
||||||
|
this->rx_pin_->setup();
|
||||||
|
}
|
||||||
|
if (this->tx_pin_ && this->rx_pin_ != this->tx_pin_) {
|
||||||
|
this->tx_pin_->setup();
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t config = get_config();
|
uint16_t config = get_config();
|
||||||
|
|
||||||
constexpr uint32_t valid_tx_uart_0 = __bitset({0, 12, 16, 28});
|
constexpr uint32_t valid_tx_uart_0 = __bitset({0, 12, 16, 28});
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ static const uint8_t USB_DIR_IN = 1 << 7;
|
|||||||
static const uint8_t USB_DIR_OUT = 0;
|
static const uint8_t USB_DIR_OUT = 0;
|
||||||
static const size_t SETUP_PACKET_SIZE = 8;
|
static const size_t SETUP_PACKET_SIZE = 8;
|
||||||
|
|
||||||
static const size_t MAX_REQUESTS = USB_HOST_MAX_REQUESTS; // maximum number of outstanding requests possible.
|
static constexpr size_t MAX_REQUESTS = USB_HOST_MAX_REQUESTS; // maximum number of outstanding requests possible.
|
||||||
static_assert(MAX_REQUESTS >= 1 && MAX_REQUESTS <= 32, "MAX_REQUESTS must be between 1 and 32");
|
static_assert(MAX_REQUESTS >= 1 && MAX_REQUESTS <= 32, "MAX_REQUESTS must be between 1 and 32");
|
||||||
|
|
||||||
// Select appropriate bitmask type for tracking allocation of TransferRequest slots.
|
// Select appropriate bitmask type for tracking allocation of TransferRequest slots.
|
||||||
@@ -65,6 +65,7 @@ static_assert(MAX_REQUESTS >= 1 && MAX_REQUESTS <= 32, "MAX_REQUESTS must be bet
|
|||||||
// This is tied to the static_assert above, which enforces MAX_REQUESTS is between 1 and 32.
|
// This is tied to the static_assert above, which enforces MAX_REQUESTS is between 1 and 32.
|
||||||
// If MAX_REQUESTS is increased above 32, this logic and the static_assert must be updated.
|
// If MAX_REQUESTS is increased above 32, this logic and the static_assert must be updated.
|
||||||
using trq_bitmask_t = std::conditional<(MAX_REQUESTS <= 16), uint16_t, uint32_t>::type;
|
using trq_bitmask_t = std::conditional<(MAX_REQUESTS <= 16), uint16_t, uint32_t>::type;
|
||||||
|
static constexpr trq_bitmask_t ALL_REQUESTS_IN_USE = MAX_REQUESTS == 32 ? ~0 : (1 << MAX_REQUESTS) - 1;
|
||||||
|
|
||||||
static constexpr size_t USB_EVENT_QUEUE_SIZE = 32; // Size of event queue between USB task and main loop
|
static constexpr size_t USB_EVENT_QUEUE_SIZE = 32; // Size of event queue between USB task and main loop
|
||||||
static constexpr size_t USB_TASK_STACK_SIZE = 4096; // Stack size for USB task (same as ESP-IDF USB examples)
|
static constexpr size_t USB_TASK_STACK_SIZE = 4096; // Stack size for USB task (same as ESP-IDF USB examples)
|
||||||
@@ -133,11 +134,11 @@ class USBClient : public Component {
|
|||||||
float get_setup_priority() const override { return setup_priority::IO; }
|
float get_setup_priority() const override { return setup_priority::IO; }
|
||||||
void on_opened(uint8_t addr);
|
void on_opened(uint8_t addr);
|
||||||
void on_removed(usb_device_handle_t handle);
|
void on_removed(usb_device_handle_t handle);
|
||||||
void control_transfer_callback(const usb_transfer_t *xfer) const;
|
bool transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length);
|
||||||
void transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length);
|
bool transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length);
|
||||||
void transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length);
|
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
void release_trq(TransferRequest *trq);
|
void release_trq(TransferRequest *trq);
|
||||||
|
trq_bitmask_t get_trq_in_use() const { return trq_in_use_; }
|
||||||
bool control_transfer(uint8_t type, uint8_t request, uint16_t value, uint16_t index, const transfer_cb_t &callback,
|
bool control_transfer(uint8_t type, uint8_t request, uint16_t value, uint16_t index, const transfer_cb_t &callback,
|
||||||
const std::vector<uint8_t> &data = {});
|
const std::vector<uint8_t> &data = {});
|
||||||
|
|
||||||
@@ -147,7 +148,6 @@ class USBClient : public Component {
|
|||||||
EventPool<UsbEvent, USB_EVENT_QUEUE_SIZE> event_pool;
|
EventPool<UsbEvent, USB_EVENT_QUEUE_SIZE> event_pool;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool register_();
|
|
||||||
TransferRequest *get_trq_(); // Lock-free allocation using atomic bitmask (multi-consumer safe)
|
TransferRequest *get_trq_(); // Lock-free allocation using atomic bitmask (multi-consumer safe)
|
||||||
virtual void disconnect();
|
virtual void disconnect();
|
||||||
virtual void on_connected() {}
|
virtual void on_connected() {}
|
||||||
@@ -158,7 +158,7 @@ class USBClient : public Component {
|
|||||||
|
|
||||||
// USB task management
|
// USB task management
|
||||||
static void usb_task_fn(void *arg);
|
static void usb_task_fn(void *arg);
|
||||||
void usb_task_loop();
|
[[noreturn]] void usb_task_loop() const;
|
||||||
|
|
||||||
TaskHandle_t usb_task_handle_{nullptr};
|
TaskHandle_t usb_task_handle_{nullptr};
|
||||||
|
|
||||||
|
|||||||
@@ -188,9 +188,9 @@ void USBClient::setup() {
|
|||||||
}
|
}
|
||||||
// Pre-allocate USB transfer buffers for all slots at startup
|
// Pre-allocate USB transfer buffers for all slots at startup
|
||||||
// This avoids any dynamic allocation during runtime
|
// This avoids any dynamic allocation during runtime
|
||||||
for (size_t i = 0; i < MAX_REQUESTS; i++) {
|
for (auto &request : this->requests_) {
|
||||||
usb_host_transfer_alloc(64, 0, &this->requests_[i].transfer);
|
usb_host_transfer_alloc(64, 0, &request.transfer);
|
||||||
this->requests_[i].client = this; // Set once, never changes
|
request.client = this; // Set once, never changes
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and start USB task
|
// Create and start USB task
|
||||||
@@ -210,8 +210,7 @@ void USBClient::usb_task_fn(void *arg) {
|
|||||||
auto *client = static_cast<USBClient *>(arg);
|
auto *client = static_cast<USBClient *>(arg);
|
||||||
client->usb_task_loop();
|
client->usb_task_loop();
|
||||||
}
|
}
|
||||||
|
void USBClient::usb_task_loop() const {
|
||||||
void USBClient::usb_task_loop() {
|
|
||||||
while (true) {
|
while (true) {
|
||||||
usb_host_client_handle_events(this->handle_, portMAX_DELAY);
|
usb_host_client_handle_events(this->handle_, portMAX_DELAY);
|
||||||
}
|
}
|
||||||
@@ -334,22 +333,23 @@ static void control_callback(const usb_transfer_t *xfer) {
|
|||||||
// This multi-threaded access is intentional for performance - USB task can
|
// This multi-threaded access is intentional for performance - USB task can
|
||||||
// immediately restart transfers without waiting for main loop scheduling.
|
// immediately restart transfers without waiting for main loop scheduling.
|
||||||
TransferRequest *USBClient::get_trq_() {
|
TransferRequest *USBClient::get_trq_() {
|
||||||
trq_bitmask_t mask = this->trq_in_use_.load(std::memory_order_relaxed);
|
trq_bitmask_t mask = this->trq_in_use_.load(std::memory_order_acquire);
|
||||||
|
|
||||||
// Find first available slot (bit = 0) and try to claim it atomically
|
// Find first available slot (bit = 0) and try to claim it atomically
|
||||||
// We use a while loop to allow retrying the same slot after CAS failure
|
// We use a while loop to allow retrying the same slot after CAS failure
|
||||||
size_t i = 0;
|
for (;;) {
|
||||||
while (i != MAX_REQUESTS) {
|
if (mask == ALL_REQUESTS_IN_USE) {
|
||||||
if (mask & (static_cast<trq_bitmask_t>(1) << i)) {
|
ESP_LOGE(TAG, "All %zu transfer slots in use", MAX_REQUESTS);
|
||||||
// Slot is in use, move to next slot
|
return nullptr;
|
||||||
i++;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
// find the least significant zero bit
|
||||||
|
trq_bitmask_t lsb = ~mask & (mask + 1);
|
||||||
|
|
||||||
// Slot i appears available, try to claim it atomically
|
// Slot i appears available, try to claim it atomically
|
||||||
trq_bitmask_t desired = mask | (static_cast<trq_bitmask_t>(1) << i); // Set bit i to mark as in-use
|
trq_bitmask_t desired = mask | lsb;
|
||||||
|
|
||||||
if (this->trq_in_use_.compare_exchange_weak(mask, desired, std::memory_order_acquire, std::memory_order_relaxed)) {
|
if (this->trq_in_use_.compare_exchange_weak(mask, desired, std::memory_order::acquire)) {
|
||||||
|
auto i = __builtin_ctz(lsb); // count trailing zeroes
|
||||||
// Successfully claimed slot i - prepare the TransferRequest
|
// Successfully claimed slot i - prepare the TransferRequest
|
||||||
auto *trq = &this->requests_[i];
|
auto *trq = &this->requests_[i];
|
||||||
trq->transfer->context = trq;
|
trq->transfer->context = trq;
|
||||||
@@ -358,13 +358,9 @@ TransferRequest *USBClient::get_trq_() {
|
|||||||
}
|
}
|
||||||
// CAS failed - another thread modified the bitmask
|
// CAS failed - another thread modified the bitmask
|
||||||
// mask was already updated by compare_exchange_weak with the current value
|
// mask was already updated by compare_exchange_weak with the current value
|
||||||
// No need to reload - the CAS already did that for us
|
|
||||||
i = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGE(TAG, "All %zu transfer slots in use", MAX_REQUESTS);
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void USBClient::disconnect() {
|
void USBClient::disconnect() {
|
||||||
this->on_disconnected();
|
this->on_disconnected();
|
||||||
auto err = usb_host_device_close(this->handle_, this->device_handle_);
|
auto err = usb_host_device_close(this->handle_, this->device_handle_);
|
||||||
@@ -446,11 +442,11 @@ static void transfer_callback(usb_transfer_t *xfer) {
|
|||||||
*
|
*
|
||||||
* @throws None.
|
* @throws None.
|
||||||
*/
|
*/
|
||||||
void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length) {
|
bool USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length) {
|
||||||
auto *trq = this->get_trq_();
|
auto *trq = this->get_trq_();
|
||||||
if (trq == nullptr) {
|
if (trq == nullptr) {
|
||||||
ESP_LOGE(TAG, "Too many requests queued");
|
ESP_LOGE(TAG, "Too many requests queued");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
trq->callback = callback;
|
trq->callback = callback;
|
||||||
trq->transfer->callback = transfer_callback;
|
trq->transfer->callback = transfer_callback;
|
||||||
@@ -460,7 +456,9 @@ void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, u
|
|||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err);
|
ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err);
|
||||||
this->release_trq(trq);
|
this->release_trq(trq);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -476,11 +474,11 @@ void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, u
|
|||||||
*
|
*
|
||||||
* @throws None.
|
* @throws None.
|
||||||
*/
|
*/
|
||||||
void USBClient::transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length) {
|
bool USBClient::transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length) {
|
||||||
auto *trq = this->get_trq_();
|
auto *trq = this->get_trq_();
|
||||||
if (trq == nullptr) {
|
if (trq == nullptr) {
|
||||||
ESP_LOGE(TAG, "Too many requests queued");
|
ESP_LOGE(TAG, "Too many requests queued");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
trq->callback = callback;
|
trq->callback = callback;
|
||||||
trq->transfer->callback = transfer_callback;
|
trq->transfer->callback = transfer_callback;
|
||||||
@@ -491,7 +489,9 @@ void USBClient::transfer_out(uint8_t ep_address, const transfer_cb_t &callback,
|
|||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err);
|
ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err);
|
||||||
this->release_trq(trq);
|
this->release_trq(trq);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
void USBClient::dump_config() {
|
void USBClient::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
@@ -505,7 +505,7 @@ void USBClient::dump_config() {
|
|||||||
// - Main loop: When transfer submission fails
|
// - Main loop: When transfer submission fails
|
||||||
//
|
//
|
||||||
// THREAD SAFETY: Lock-free using atomic AND to clear bit
|
// THREAD SAFETY: Lock-free using atomic AND to clear bit
|
||||||
// Thread-safe atomic operation allows multi-threaded deallocation
|
// Thread-safe atomic operation allows multithreaded deallocation
|
||||||
void USBClient::release_trq(TransferRequest *trq) {
|
void USBClient::release_trq(TransferRequest *trq) {
|
||||||
if (trq == nullptr)
|
if (trq == nullptr)
|
||||||
return;
|
return;
|
||||||
@@ -517,10 +517,10 @@ void USBClient::release_trq(TransferRequest *trq) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Atomically clear bit i to mark slot as available
|
// Atomically clear the bit to mark slot as available
|
||||||
// fetch_and with inverted bitmask clears the bit atomically
|
// fetch_and with inverted bitmask clears the bit atomically
|
||||||
trq_bitmask_t bit = static_cast<trq_bitmask_t>(1) << index;
|
trq_bitmask_t mask = ~(static_cast<trq_bitmask_t>(1) << index);
|
||||||
this->trq_in_use_.fetch_and(static_cast<trq_bitmask_t>(~bit), std::memory_order_release);
|
this->trq_in_use_.fetch_and(mask, std::memory_order_release);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace usb_host
|
} // namespace usb_host
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ void USBUartComponent::dump_config() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
void USBUartComponent::start_input(USBUartChannel *channel) {
|
void USBUartComponent::start_input(USBUartChannel *channel) {
|
||||||
if (!channel->initialised_.load() || channel->input_started_.load())
|
if (!channel->initialised_.load())
|
||||||
return;
|
return;
|
||||||
// THREAD CONTEXT: Called from both USB task and main loop threads
|
// THREAD CONTEXT: Called from both USB task and main loop threads
|
||||||
// - USB task: Immediate restart after successful transfer for continuous data flow
|
// - USB task: Immediate restart after successful transfer for continuous data flow
|
||||||
@@ -226,12 +226,18 @@ void USBUartComponent::start_input(USBUartChannel *channel) {
|
|||||||
//
|
//
|
||||||
// The underlying transfer_in() uses lock-free atomic allocation from the
|
// The underlying transfer_in() uses lock-free atomic allocation from the
|
||||||
// TransferRequest pool, making this multi-threaded access safe
|
// TransferRequest pool, making this multi-threaded access safe
|
||||||
|
|
||||||
|
// if already started, don't restart. A spurious failure in compare_exchange_weak
|
||||||
|
// is not a problem, as it will be retried on the next read_array()
|
||||||
|
auto started = false;
|
||||||
|
if (!channel->input_started_.compare_exchange_weak(started, true))
|
||||||
|
return;
|
||||||
const auto *ep = channel->cdc_dev_.in_ep;
|
const auto *ep = channel->cdc_dev_.in_ep;
|
||||||
// CALLBACK CONTEXT: This lambda is executed in USB task via transfer_callback
|
// CALLBACK CONTEXT: This lambda is executed in USB task via transfer_callback
|
||||||
auto callback = [this, channel](const usb_host::TransferStatus &status) {
|
auto callback = [this, channel](const usb_host::TransferStatus &status) {
|
||||||
ESP_LOGV(TAG, "Transfer result: length: %u; status %X", status.data_len, status.error_code);
|
ESP_LOGV(TAG, "Transfer result: length: %u; status %X", status.data_len, status.error_code);
|
||||||
if (!status.success) {
|
if (!status.success) {
|
||||||
ESP_LOGE(TAG, "Control transfer failed, status=%s", esp_err_to_name(status.error_code));
|
ESP_LOGE(TAG, "Input transfer failed, status=%s", esp_err_to_name(status.error_code));
|
||||||
// On failure, don't restart - let next read_array() trigger it
|
// On failure, don't restart - let next read_array() trigger it
|
||||||
channel->input_started_.store(false);
|
channel->input_started_.store(false);
|
||||||
return;
|
return;
|
||||||
@@ -263,8 +269,9 @@ void USBUartComponent::start_input(USBUartChannel *channel) {
|
|||||||
channel->input_started_.store(false);
|
channel->input_started_.store(false);
|
||||||
this->start_input(channel);
|
this->start_input(channel);
|
||||||
};
|
};
|
||||||
channel->input_started_.store(true);
|
if (!this->transfer_in(ep->bEndpointAddress, callback, ep->wMaxPacketSize)) {
|
||||||
this->transfer_in(ep->bEndpointAddress, callback, ep->wMaxPacketSize);
|
channel->input_started_.store(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void USBUartComponent::start_output(USBUartChannel *channel) {
|
void USBUartComponent::start_output(USBUartChannel *channel) {
|
||||||
@@ -357,11 +364,12 @@ void USBUartTypeCdcAcm::on_disconnected() {
|
|||||||
usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
|
usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
|
||||||
}
|
}
|
||||||
usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.bulk_interface_number);
|
usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.bulk_interface_number);
|
||||||
channel->initialised_.store(false);
|
// Reset the input and output started flags to their initial state to avoid the possibility of spurious restarts
|
||||||
channel->input_started_.store(false);
|
channel->input_started_.store(true);
|
||||||
channel->output_started_.store(false);
|
channel->output_started_.store(true);
|
||||||
channel->input_buffer_.clear();
|
channel->input_buffer_.clear();
|
||||||
channel->output_buffer_.clear();
|
channel->output_buffer_.clear();
|
||||||
|
channel->initialised_.store(false);
|
||||||
}
|
}
|
||||||
USBClient::on_disconnected();
|
USBClient::on_disconnected();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from enum import Enum
|
|||||||
|
|
||||||
from esphome.enum import StrEnum
|
from esphome.enum import StrEnum
|
||||||
|
|
||||||
__version__ = "2025.10.2"
|
__version__ = "2025.10.5"
|
||||||
|
|
||||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||||
|
|||||||
@@ -224,36 +224,37 @@ def resolve_ip_address(
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
# Process hosts
|
# Process hosts
|
||||||
cached_addresses: list[str] = []
|
|
||||||
uncached_hosts: list[str] = []
|
uncached_hosts: list[str] = []
|
||||||
has_cache = address_cache is not None
|
|
||||||
|
|
||||||
for h in hosts:
|
for h in hosts:
|
||||||
if is_ip_address(h):
|
if is_ip_address(h):
|
||||||
if has_cache:
|
_add_ip_addresses_to_addrinfo([h], port, res)
|
||||||
# If we have a cache, treat IPs as cached
|
|
||||||
cached_addresses.append(h)
|
|
||||||
else:
|
|
||||||
# If no cache, pass IPs through to resolver with hostnames
|
|
||||||
uncached_hosts.append(h)
|
|
||||||
elif address_cache and (cached := address_cache.get_addresses(h)):
|
elif address_cache and (cached := address_cache.get_addresses(h)):
|
||||||
# Found in cache
|
_add_ip_addresses_to_addrinfo(cached, port, res)
|
||||||
cached_addresses.extend(cached)
|
|
||||||
else:
|
else:
|
||||||
# Not cached, need to resolve
|
# Not cached, need to resolve
|
||||||
if address_cache and address_cache.has_cache():
|
if address_cache and address_cache.has_cache():
|
||||||
_LOGGER.info("Host %s not in cache, will need to resolve", h)
|
_LOGGER.info("Host %s not in cache, will need to resolve", h)
|
||||||
uncached_hosts.append(h)
|
uncached_hosts.append(h)
|
||||||
|
|
||||||
# Process cached addresses (includes direct IPs and cached lookups)
|
|
||||||
_add_ip_addresses_to_addrinfo(cached_addresses, port, res)
|
|
||||||
|
|
||||||
# If we have uncached hosts (only non-IP hostnames), resolve them
|
# If we have uncached hosts (only non-IP hostnames), resolve them
|
||||||
if uncached_hosts:
|
if uncached_hosts:
|
||||||
|
from aioesphomeapi.host_resolver import AddrInfo as AioAddrInfo
|
||||||
|
|
||||||
|
from esphome.core import EsphomeError
|
||||||
from esphome.resolver import AsyncResolver
|
from esphome.resolver import AsyncResolver
|
||||||
|
|
||||||
resolver = AsyncResolver(uncached_hosts, port)
|
resolver = AsyncResolver(uncached_hosts, port)
|
||||||
addr_infos = resolver.resolve()
|
addr_infos: list[AioAddrInfo] = []
|
||||||
|
try:
|
||||||
|
addr_infos = resolver.resolve()
|
||||||
|
except EsphomeError as err:
|
||||||
|
if not res:
|
||||||
|
# No pre-resolved addresses available, DNS resolution is fatal
|
||||||
|
raise
|
||||||
|
_LOGGER.info("%s (using %d already resolved IP addresses)", err, len(res))
|
||||||
|
|
||||||
# Convert aioesphomeapi AddrInfo to our format
|
# Convert aioesphomeapi AddrInfo to our format
|
||||||
for addr_info in addr_infos:
|
for addr_info in addr_infos:
|
||||||
sockaddr = addr_info.sockaddr
|
sockaddr = addr_info.sockaddr
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ classifiers = [
|
|||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Topic :: Home Automation",
|
"Topic :: Home Automation",
|
||||||
]
|
]
|
||||||
requires-python = ">=3.11.0"
|
|
||||||
|
# Python 3.14 is currently not supported by IDF <= 5.5.1, see https://github.com/esphome/esphome/issues/11502
|
||||||
|
requires-python = ">=3.11.0,<3.14"
|
||||||
|
|
||||||
dynamic = ["dependencies", "optional-dependencies", "version"]
|
dynamic = ["dependencies", "optional-dependencies", "version"]
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,19 @@ number:
|
|||||||
widget: spinbox_id
|
widget: spinbox_id
|
||||||
id: lvgl_spinbox_number
|
id: lvgl_spinbox_number
|
||||||
name: LVGL Spinbox Number
|
name: LVGL Spinbox Number
|
||||||
|
- platform: template
|
||||||
|
id: test_brightness
|
||||||
|
name: "Test Brightness"
|
||||||
|
min_value: 0
|
||||||
|
max_value: 255
|
||||||
|
step: 1
|
||||||
|
optimistic: true
|
||||||
|
# Test lambda in automation accessing x parameter directly
|
||||||
|
# This is a real-world pattern from user configs
|
||||||
|
on_value:
|
||||||
|
- lambda: !lambda |-
|
||||||
|
// Direct use of x parameter in automation
|
||||||
|
ESP_LOGD("test", "Brightness: %.0f", x);
|
||||||
|
|
||||||
light:
|
light:
|
||||||
- platform: lvgl
|
- platform: lvgl
|
||||||
@@ -110,3 +123,21 @@ text:
|
|||||||
platform: lvgl
|
platform: lvgl
|
||||||
widget: hello_label
|
widget: hello_label
|
||||||
mode: text
|
mode: text
|
||||||
|
|
||||||
|
text_sensor:
|
||||||
|
- platform: template
|
||||||
|
id: test_text_sensor
|
||||||
|
name: "Test Text Sensor"
|
||||||
|
# Test nested lambdas in LVGL actions can access automation parameters
|
||||||
|
on_value:
|
||||||
|
- lvgl.label.update:
|
||||||
|
id: hello_label
|
||||||
|
text: !lambda return x.c_str();
|
||||||
|
- lvgl.label.update:
|
||||||
|
id: hello_label
|
||||||
|
text: !lambda |-
|
||||||
|
// Test complex lambda with conditionals accessing x parameter
|
||||||
|
if (x == "*") {
|
||||||
|
return "WILDCARD";
|
||||||
|
}
|
||||||
|
return x.c_str();
|
||||||
|
|||||||
@@ -257,7 +257,30 @@ lvgl:
|
|||||||
text: "Hello shiny day"
|
text: "Hello shiny day"
|
||||||
text_color: 0xFFFFFF
|
text_color: 0xFFFFFF
|
||||||
align: bottom_mid
|
align: bottom_mid
|
||||||
|
- label:
|
||||||
|
id: setup_lambda_label
|
||||||
|
# Test lambda in widget property during setup (LvContext)
|
||||||
|
# Should NOT receive lv_component parameter
|
||||||
|
text: !lambda |-
|
||||||
|
char buf[32];
|
||||||
|
snprintf(buf, sizeof(buf), "Setup: %d", 42);
|
||||||
|
return std::string(buf);
|
||||||
|
align: top_mid
|
||||||
text_font: space16
|
text_font: space16
|
||||||
|
- label:
|
||||||
|
id: chip_info_label
|
||||||
|
# Test complex setup lambda (real-world pattern)
|
||||||
|
# Should NOT receive lv_component parameter
|
||||||
|
text: !lambda |-
|
||||||
|
// Test conditional compilation and string formatting
|
||||||
|
char buf[64];
|
||||||
|
#ifdef USE_ESP_IDF
|
||||||
|
snprintf(buf, sizeof(buf), "IDF: v%d.%d", ESP_IDF_VERSION_MAJOR, ESP_IDF_VERSION_MINOR);
|
||||||
|
#else
|
||||||
|
snprintf(buf, sizeof(buf), "Arduino");
|
||||||
|
#endif
|
||||||
|
return std::string(buf);
|
||||||
|
align: top_left
|
||||||
- obj:
|
- obj:
|
||||||
align: center
|
align: center
|
||||||
arc_opa: COVER
|
arc_opa: COVER
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ display:
|
|||||||
binary_sensor:
|
binary_sensor:
|
||||||
- platform: sdl
|
- platform: sdl
|
||||||
id: key_up
|
id: key_up
|
||||||
key: SDLK_a
|
key: SDLK_UP
|
||||||
- platform: sdl
|
- platform: sdl
|
||||||
id: key_down
|
id: key_down
|
||||||
key: SDLK_d
|
key: SDLK_DOWN
|
||||||
- platform: sdl
|
- platform: sdl
|
||||||
id: key_enter
|
id: key_enter
|
||||||
key: SDLK_s
|
key: SDLK_RETURN
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
usb_host:
|
||||||
|
max_transfer_requests: 32
|
||||||
|
|
||||||
usb_uart:
|
usb_uart:
|
||||||
- id: uart_0
|
- id: uart_0
|
||||||
type: cdc_acm
|
type: cdc_acm
|
||||||
|
|||||||
@@ -454,9 +454,27 @@ def test_resolve_ip_address_mixed_list() -> None:
|
|||||||
# Mix of IP and hostname - should use async resolver
|
# Mix of IP and hostname - should use async resolver
|
||||||
result = helpers.resolve_ip_address(["192.168.1.100", "test.local"], 6053)
|
result = helpers.resolve_ip_address(["192.168.1.100", "test.local"], 6053)
|
||||||
|
|
||||||
|
assert len(result) == 2
|
||||||
|
assert result[0][4][0] == "192.168.1.100"
|
||||||
|
assert result[1][4][0] == "192.168.1.200"
|
||||||
|
MockResolver.assert_called_once_with(["test.local"], 6053)
|
||||||
|
mock_resolver.resolve.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_ip_address_mixed_list_fail() -> None:
|
||||||
|
"""Test resolving a mix of IPs and hostnames with resolve failed."""
|
||||||
|
with patch("esphome.resolver.AsyncResolver") as MockResolver:
|
||||||
|
mock_resolver = MockResolver.return_value
|
||||||
|
mock_resolver.resolve.side_effect = EsphomeError(
|
||||||
|
"Error resolving IP address: [test.local]"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mix of IP and hostname - should use async resolver
|
||||||
|
result = helpers.resolve_ip_address(["192.168.1.100", "test.local"], 6053)
|
||||||
|
|
||||||
assert len(result) == 1
|
assert len(result) == 1
|
||||||
assert result[0][4][0] == "192.168.1.200"
|
assert result[0][4][0] == "192.168.1.100"
|
||||||
MockResolver.assert_called_once_with(["192.168.1.100", "test.local"], 6053)
|
MockResolver.assert_called_once_with(["test.local"], 6053)
|
||||||
mock_resolver.resolve.assert_called_once()
|
mock_resolver.resolve.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user