mirror of
https://github.com/esphome/esphome.git
synced 2026-09-18 18:48:39 +00:00
Merge remote-tracking branch 'upstream/dev' into integration
This commit is contained in:
@@ -3,6 +3,9 @@ name: PR Title Check
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, synchronize, reopened]
|
||||
branches-ignore:
|
||||
- release
|
||||
- beta
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -132,7 +132,6 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_PHASE_ANGLE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_DEGREES,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_HARMONIC_POWER): sensor.sensor_schema(
|
||||
|
||||
@@ -390,7 +390,7 @@ def validate_multi_click_timing(value):
|
||||
new_state = v_.get(CONF_STATE, not state)
|
||||
if new_state == state:
|
||||
raise cv.Invalid(
|
||||
f"Timings must have alternating state. Indices {i} and {i + 1} have the same state {state}"
|
||||
f"Timings must have alternating state. Indices {i - 1} and {i} have the same state {state}"
|
||||
)
|
||||
if max_length is not None and max_length < min_length:
|
||||
raise cv.Invalid(
|
||||
|
||||
@@ -89,6 +89,6 @@ async def to_code(config):
|
||||
)
|
||||
await cg.register_component(var, conf)
|
||||
|
||||
if restore_value := config.get(CONF_RESTORE_VALUE):
|
||||
if restore_value := conf.get(CONF_RESTORE_VALUE):
|
||||
cg.add(var.set_restore_value(restore_value))
|
||||
cg.add(getattr(bl0940, setter_method)(var))
|
||||
|
||||
@@ -373,14 +373,14 @@ def bt_uuid(value):
|
||||
value = in_value.upper()
|
||||
|
||||
if len(value) == len(bt_uuid16_format):
|
||||
pattern = re.compile("^[A-F|0-9]{4,}$")
|
||||
pattern = re.compile("^[A-F0-9]{4,}$")
|
||||
if not pattern.match(value):
|
||||
raise cv.Invalid(
|
||||
f"Invalid hexadecimal value for 16 bit UUID format: '{in_value}'"
|
||||
)
|
||||
return value
|
||||
if len(value) == len(bt_uuid32_format):
|
||||
pattern = re.compile("^[A-F|0-9]{8,}$")
|
||||
pattern = re.compile("^[A-F0-9]{8,}$")
|
||||
if not pattern.match(value):
|
||||
raise cv.Invalid(
|
||||
f"Invalid hexadecimal value for 32 bit UUID format: '{in_value}'"
|
||||
@@ -388,7 +388,7 @@ def bt_uuid(value):
|
||||
return value
|
||||
if len(value) == len(bt_uuid128_format):
|
||||
pattern = re.compile(
|
||||
"^[A-F|0-9]{8,}-[A-F|0-9]{4,}-[A-F|0-9]{4,}-[A-F|0-9]{4,}-[A-F|0-9]{12,}$"
|
||||
"^[A-F0-9]{8,}-[A-F0-9]{4,}-[A-F0-9]{4,}-[A-F0-9]{4,}-[A-F0-9]{12,}$"
|
||||
)
|
||||
if not pattern.match(value):
|
||||
raise cv.Invalid(
|
||||
|
||||
@@ -10,11 +10,7 @@ AUTO_LOAD = ["esp32_ble"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
esp32_ble_beacon_ns = cg.esphome_ns.namespace("esp32_ble_beacon")
|
||||
ESP32BLEBeacon = esp32_ble_beacon_ns.class_(
|
||||
"ESP32BLEBeacon",
|
||||
cg.Component,
|
||||
cg.Parented.template(esp32_ble.ESP32BLE),
|
||||
)
|
||||
ESP32BLEBeacon = esp32_ble_beacon_ns.class_("ESP32BLEBeacon", cg.Component)
|
||||
CONF_MAJOR = "major"
|
||||
CONF_MINOR = "minor"
|
||||
CONF_MIN_INTERVAL = "min_interval"
|
||||
|
||||
@@ -35,7 +35,7 @@ using esp_ble_ibeacon_t = struct {
|
||||
|
||||
using namespace esp32_ble;
|
||||
|
||||
class ESP32BLEBeacon : public Component, public Parented<ESP32BLE> {
|
||||
class ESP32BLEBeacon : public Component {
|
||||
public:
|
||||
explicit ESP32BLEBeacon(const std::array<uint8_t, 16> &uuid) : uuid_(uuid) {}
|
||||
|
||||
|
||||
@@ -307,24 +307,30 @@ def final_validate_config(config):
|
||||
# Check if all characteristics that require notifications have the notify property set
|
||||
for char_id in CORE.data.get(DOMAIN, {}).get(KEY_NOTIFY_REQUIRED, set()):
|
||||
# Look for the characteristic in the configuration
|
||||
char_config = [
|
||||
matches = [
|
||||
char_conf
|
||||
for service_conf in config[CONF_SERVICES]
|
||||
for char_conf in service_conf[CONF_CHARACTERISTICS]
|
||||
if char_conf[CONF_ID] == char_id
|
||||
][0]
|
||||
]
|
||||
if not matches:
|
||||
continue
|
||||
char_config = matches[0]
|
||||
if not char_config[CONF_NOTIFY]:
|
||||
raise cv.Invalid(
|
||||
f"Characteristic {char_config[CONF_UUID]} has notify actions and the {CONF_NOTIFY} property is not set"
|
||||
)
|
||||
for char_id in CORE.data.get(DOMAIN, {}).get(KEY_SET_VALUE, set()):
|
||||
# Look for the characteristic in the configuration
|
||||
char_config = [
|
||||
matches = [
|
||||
char_conf
|
||||
for service_conf in config[CONF_SERVICES]
|
||||
for char_conf in service_conf[CONF_CHARACTERISTICS]
|
||||
if char_conf[CONF_ID] == char_id
|
||||
][0]
|
||||
]
|
||||
if not matches:
|
||||
continue
|
||||
char_config = matches[0]
|
||||
if isinstance(char_config.get(CONF_VALUE, {}).get(CONF_DATA), cv.Lambda):
|
||||
raise cv.Invalid(
|
||||
f"Characteristic {char_config[CONF_UUID]} has both a set_value action and a templated value"
|
||||
|
||||
@@ -155,7 +155,7 @@ ESP8266_PIN_SCHEMA = cv.All(
|
||||
|
||||
@dataclass
|
||||
class PinInitialState:
|
||||
mode = 255
|
||||
mode: int = 255
|
||||
level: int = 255
|
||||
|
||||
|
||||
|
||||
@@ -158,15 +158,15 @@ def validate_peer(value):
|
||||
|
||||
def _validate_raw_data(value):
|
||||
if isinstance(value, str):
|
||||
if len(value) >= MAX_ESPNOW_PACKET_SIZE:
|
||||
if len(value) > MAX_ESPNOW_PACKET_SIZE:
|
||||
raise cv.Invalid(
|
||||
f"'{CONF_DATA}' must be less than {MAX_ESPNOW_PACKET_SIZE} characters long, got {len(value)}"
|
||||
f"'{CONF_DATA}' must be at most {MAX_ESPNOW_PACKET_SIZE} characters long, got {len(value)}"
|
||||
)
|
||||
return value
|
||||
if isinstance(value, list):
|
||||
if len(value) > MAX_ESPNOW_PACKET_SIZE:
|
||||
raise cv.Invalid(
|
||||
f"'{CONF_DATA}' must be less than {MAX_ESPNOW_PACKET_SIZE} bytes long, got {len(value)}"
|
||||
f"'{CONF_DATA}' must be at most {MAX_ESPNOW_PACKET_SIZE} bytes long, got {len(value)}"
|
||||
)
|
||||
return cv.Schema([cv.hex_uint8_t])(value)
|
||||
raise cv.Invalid(
|
||||
@@ -245,7 +245,7 @@ async def send_action(
|
||||
|
||||
data = config.get(CONF_DATA, [])
|
||||
if isinstance(data, str):
|
||||
data = [cg.RawExpression(f"'{c}'") for c in data]
|
||||
data = list(data.encode())
|
||||
templ = await cg.templatable(data, args, byte_vector, byte_vector)
|
||||
cg.add(var.set_data(templ))
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ from esphome.const import (
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLUME,
|
||||
DEVICE_CLASS_VOLUME_FLOW_RATE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_CELSIUS,
|
||||
@@ -75,7 +76,7 @@ CONFIG_SCHEMA = (
|
||||
),
|
||||
cv.Optional(CONF_FLOW): sensor.sensor_schema(
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLUME,
|
||||
device_class=DEVICE_CLASS_VOLUME_FLOW_RATE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_LITRE_PER_HOUR,
|
||||
),
|
||||
|
||||
@@ -36,7 +36,7 @@ CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(lc709203f),
|
||||
cv.Optional(CONF_SIZE, default="500"): cv.int_range(100, 3000),
|
||||
cv.Optional(CONF_SIZE, default=500): cv.int_range(100, 3000),
|
||||
cv.Optional(CONF_VOLTAGE, default="3.7"): cv.enum(
|
||||
BATTERY_VOLTAGE_OPTIONS, upper=True
|
||||
),
|
||||
|
||||
@@ -2,7 +2,7 @@ import importlib
|
||||
from pathlib import Path
|
||||
import pkgutil
|
||||
|
||||
from esphome.automation import build_automation, validate_automation
|
||||
from esphome.automation import Trigger, build_automation, validate_automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import (
|
||||
CONF_BYTE_ORDER,
|
||||
@@ -34,7 +34,6 @@ from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_LAMBDA,
|
||||
CONF_LOG_LEVEL,
|
||||
CONF_ON_BOOT,
|
||||
CONF_ON_IDLE,
|
||||
CONF_PAGES,
|
||||
CONF_ROTATION,
|
||||
@@ -59,7 +58,7 @@ from .encoders import (
|
||||
from .gradient import GRADIENT_SCHEMA, gradients_to_code
|
||||
from .keypads import KEYPADS_CONFIG, keypads_to_code
|
||||
from .lv_validation import lv_bool, lv_images_used
|
||||
from .lvcode import LvContext, LvglComponent, lvgl_static
|
||||
from .lvcode import LvContext, LvglComponent, lv_event_t_ptr, lvgl_static
|
||||
from .schemas import (
|
||||
DISP_BG_SCHEMA,
|
||||
FULL_STYLE_SCHEMA,
|
||||
@@ -71,7 +70,7 @@ from .schemas import (
|
||||
)
|
||||
from .styles import styles_to_code, theme_to_code
|
||||
from .touchscreens import touchscreen_schema, touchscreens_to_code
|
||||
from .trigger import add_on_boot_triggers, generate_align_tos, generate_triggers
|
||||
from .trigger import generate_align_tos, generate_triggers
|
||||
from .types import (
|
||||
IdleTrigger,
|
||||
PlainTrigger,
|
||||
@@ -79,6 +78,7 @@ from .types import (
|
||||
lv_font_t,
|
||||
lv_group_t,
|
||||
lv_lambda_t,
|
||||
lv_obj_t_ptr,
|
||||
lv_style_t,
|
||||
lvgl_ns,
|
||||
)
|
||||
@@ -398,7 +398,6 @@ async def to_code(configs):
|
||||
f"set_{trigger_name.removeprefix('on_')}_trigger",
|
||||
)(trigger_var)
|
||||
)
|
||||
await add_on_boot_triggers(config.get(CONF_ON_BOOT, ()))
|
||||
|
||||
# This must be done after all widgets are created
|
||||
for comp in helpers.lvgl_components_required:
|
||||
@@ -502,6 +501,17 @@ LVGL_SCHEMA = cv.All(
|
||||
cv.polling_component_schema("1s")
|
||||
.extend(
|
||||
{
|
||||
**{
|
||||
cv.Optional(event): validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
Trigger.template(lv_obj_t_ptr, lv_event_t_ptr)
|
||||
),
|
||||
}
|
||||
)
|
||||
for event in df.LV_SCREEN_EVENT_TRIGGERS
|
||||
+ df.LV_DISPLAY_EVENT_TRIGGERS
|
||||
},
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(LvglComponent),
|
||||
cv.GenerateID(CONF_ALIGN_TO_LAMBDA_ID): cv.declare_id(lv_lambda_t),
|
||||
cv.GenerateID(df.CONF_DISPLAYS): display_schema,
|
||||
|
||||
@@ -276,10 +276,6 @@ LV_EVENT_MAP = {
|
||||
"DRAW_POST_BEGIN": "DRAW_POST_BEGIN",
|
||||
"DRAW_POST_END": "DRAW_POST_END",
|
||||
"DRAW_TASK_ADD": "DRAW_TASK_ADDED",
|
||||
"FLUSH_FINISH": "FLUSH_FINISH",
|
||||
"FLUSH_START": "FLUSH_START",
|
||||
"FLUSH_WAIT_FINISH": "FLUSH_WAIT_FINISH",
|
||||
"FLUSH_WAIT_START": "FLUSH_WAIT_START",
|
||||
"FOCUS": "FOCUSED",
|
||||
"GESTURE": "GESTURE",
|
||||
"GET_SELF_SIZE": "GET_SELF_SIZE",
|
||||
@@ -300,18 +296,8 @@ LV_EVENT_MAP = {
|
||||
"READY": "READY",
|
||||
"REFRESH": "REFRESH",
|
||||
"REFR_EXT_DRAW_SIZE": "REFR_EXT_DRAW_SIZE",
|
||||
"REFR_READY": "REFR_READY",
|
||||
"REFR_REQUEST": "REFR_REQUEST",
|
||||
"REFR_START": "REFR_START",
|
||||
"RELEASE": "RELEASED",
|
||||
"RENDER_READY": "RENDER_READY",
|
||||
"RENDER_START": "RENDER_START",
|
||||
"RESOLUTION_CHANGE": "RESOLUTION_CHANGED",
|
||||
"ROTARY": "ROTARY",
|
||||
"SCREEN_LOAD": "SCREEN_LOADED",
|
||||
"SCREEN_LOAD_START": "SCREEN_LOAD_START",
|
||||
"SCREEN_UNLOAD": "SCREEN_UNLOADED",
|
||||
"SCREEN_UNLOAD_START": "SCREEN_UNLOAD_START",
|
||||
"SCROLL": "SCROLL",
|
||||
"SCROLL_BEGIN": "SCROLL_BEGIN",
|
||||
"SCROLL_END": "SCROLL_END",
|
||||
@@ -322,12 +308,34 @@ LV_EVENT_MAP = {
|
||||
"STATE_CHANGE": "STATE_CHANGED",
|
||||
"STYLE_CHANGE": "STYLE_CHANGED",
|
||||
"TRIPLE_CLICK": "TRIPLE_CLICKED",
|
||||
}
|
||||
LV_SCREEN_EVENT_MAP = {
|
||||
"SCREEN_LOAD": "SCREEN_LOADED",
|
||||
"SCREEN_LOAD_START": "SCREEN_LOAD_START",
|
||||
"SCREEN_UNLOAD": "SCREEN_UNLOADED",
|
||||
"SCREEN_UNLOAD_START": "SCREEN_UNLOAD_START",
|
||||
}
|
||||
|
||||
LV_DISPLAY_EVENT_MAP = {
|
||||
"FLUSH_FINISH": "FLUSH_FINISH",
|
||||
"FLUSH_START": "FLUSH_START",
|
||||
"FLUSH_WAIT_FINISH": "FLUSH_WAIT_FINISH",
|
||||
"FLUSH_WAIT_START": "FLUSH_WAIT_START",
|
||||
"REFR_READY": "REFR_READY",
|
||||
"REFR_REQUEST": "REFR_REQUEST",
|
||||
"REFR_START": "REFR_START",
|
||||
"RENDER_READY": "RENDER_READY",
|
||||
"RENDER_START": "RENDER_START",
|
||||
"RESOLUTION_CHANGE": "RESOLUTION_CHANGED",
|
||||
"UPDATE_LAYOUT_COMPLETE": "UPDATE_LAYOUT_COMPLETED",
|
||||
"VSYNC": "VSYNC",
|
||||
"VSYNC_REQUEST": "VSYNC_REQUEST",
|
||||
}
|
||||
|
||||
LV_EVENT_TRIGGERS = tuple(f"on_{x.lower()}" for x in LV_EVENT_MAP)
|
||||
LV_DISPLAY_EVENT_TRIGGERS = tuple(f"on_{x.lower()}" for x in LV_DISPLAY_EVENT_MAP)
|
||||
LV_SCREEN_EVENT_TRIGGERS = tuple(f"on_{x.lower()}" for x in LV_SCREEN_EVENT_MAP)
|
||||
|
||||
SWIPE_TRIGGERS = tuple(
|
||||
f"on_swipe_{x.lower()}" for x in DIRECTIONS.choices + ("up", "down")
|
||||
)
|
||||
|
||||
@@ -8,16 +8,21 @@ from esphome.const import (
|
||||
CONF_X,
|
||||
CONF_Y,
|
||||
)
|
||||
from esphome.cpp_generator import new_Pvariable
|
||||
from esphome.cpp_generator import MockObj, new_Pvariable
|
||||
from esphome.cpp_helpers import register_component
|
||||
from esphome.cpp_types import nullptr
|
||||
|
||||
from .defines import (
|
||||
CONF_ALIGN,
|
||||
CONF_ALIGN_TO,
|
||||
CONF_ALIGN_TO_LAMBDA_ID,
|
||||
DIRECTIONS,
|
||||
LV_DISPLAY_EVENT_MAP,
|
||||
LV_DISPLAY_EVENT_TRIGGERS,
|
||||
LV_EVENT_MAP,
|
||||
LV_EVENT_TRIGGERS,
|
||||
LV_SCREEN_EVENT_MAP,
|
||||
LV_SCREEN_EVENT_TRIGGERS,
|
||||
SWIPE_TRIGGERS,
|
||||
literal,
|
||||
)
|
||||
@@ -30,6 +35,7 @@ from .lvcode import (
|
||||
lv,
|
||||
lv_add,
|
||||
lv_event_t_ptr,
|
||||
lv_expr,
|
||||
lvgl_static,
|
||||
)
|
||||
from .types import LV_EVENT
|
||||
@@ -49,25 +55,24 @@ async def generate_triggers():
|
||||
Must be done after all widgets completed
|
||||
"""
|
||||
|
||||
all_triggers = (
|
||||
LV_EVENT_TRIGGERS + LV_DISPLAY_EVENT_TRIGGERS + LV_SCREEN_EVENT_TRIGGERS
|
||||
)
|
||||
for w in widget_map.values():
|
||||
config = w.config
|
||||
if isinstance(w.type, LvScrActType):
|
||||
w = get_screen_active(w.var)
|
||||
|
||||
if w.config:
|
||||
if config:
|
||||
for event, conf in {
|
||||
event: conf
|
||||
for event, conf in w.config.items()
|
||||
if event in LV_EVENT_TRIGGERS
|
||||
event: conf for event, conf in config.items() if event in all_triggers
|
||||
}.items():
|
||||
conf = conf[0]
|
||||
w.add_flag("LV_OBJ_FLAG_CLICKABLE")
|
||||
event = literal("LV_EVENT_" + LV_EVENT_MAP[event[3:].upper()])
|
||||
await add_trigger(conf, w, event)
|
||||
|
||||
for event, conf in {
|
||||
event: conf
|
||||
for event, conf in w.config.items()
|
||||
if event in SWIPE_TRIGGERS
|
||||
event: conf for event, conf in config.items() if event in SWIPE_TRIGGERS
|
||||
}.items():
|
||||
conf = conf[0]
|
||||
dir = event[9:].upper()
|
||||
@@ -77,11 +82,9 @@ async def generate_triggers():
|
||||
selected = literal(
|
||||
f"lv_indev_get_gesture_dir(lv_indev_active()) == {dir}"
|
||||
)
|
||||
await add_trigger(
|
||||
conf, w, literal("LV_EVENT_GESTURE"), is_selected=selected
|
||||
)
|
||||
await add_trigger(conf, w, "GESTURE", is_selected=selected)
|
||||
|
||||
for conf in w.config.get(CONF_ON_VALUE, ()):
|
||||
for conf in config.get(CONF_ON_VALUE, ()):
|
||||
await add_trigger(
|
||||
conf,
|
||||
w,
|
||||
@@ -90,7 +93,7 @@ async def generate_triggers():
|
||||
UPDATE_EVENT,
|
||||
)
|
||||
|
||||
await add_on_boot_triggers(w.config.get(CONF_ON_BOOT, ()))
|
||||
await add_on_boot_triggers(config.get(CONF_ON_BOOT, ()))
|
||||
|
||||
|
||||
async def generate_align_tos(config: dict):
|
||||
@@ -119,6 +122,17 @@ async def generate_align_tos(config: dict):
|
||||
await register_component(var, {})
|
||||
|
||||
|
||||
TRIGGER_MAP = LV_EVENT_MAP | LV_DISPLAY_EVENT_MAP | LV_SCREEN_EVENT_MAP
|
||||
DISPLAY_TRIGGERS = set(LV_DISPLAY_EVENT_TRIGGERS)
|
||||
|
||||
|
||||
def _get_event_literal(trigger: str | MockObj) -> MockObj:
|
||||
if isinstance(trigger, MockObj):
|
||||
return trigger
|
||||
trigger = trigger.removeprefix("on_")
|
||||
return literal("LV_EVENT_" + TRIGGER_MAP[trigger.upper()])
|
||||
|
||||
|
||||
async def add_trigger(conf, w, *events, is_selected=None):
|
||||
is_selected = is_selected or w.is_selected()
|
||||
tid = conf[CONF_TRIGGER_ID]
|
||||
@@ -129,4 +143,14 @@ async def add_trigger(conf, w, *events, is_selected=None):
|
||||
async with LambdaContext(EVENT_ARG, where=tid) as context:
|
||||
with LvConditional(is_selected):
|
||||
lv_add(trigger.trigger(*value, literal("event")))
|
||||
lv_add(lvgl_static.add_event_cb(w.obj, await context.get_lambda(), *events))
|
||||
callback = await context.get_lambda()
|
||||
event_literals = [_get_event_literal(event) for event in events]
|
||||
if isinstance(events[0], str) and events[0] in DISPLAY_TRIGGERS:
|
||||
assert len(events) == 1
|
||||
lv.display_add_event_cb(
|
||||
lv_expr.obj_get_display(w.obj), callback, event_literals[0], nullptr
|
||||
)
|
||||
else:
|
||||
lv_add(
|
||||
lvgl_static.add_event_cb(w.obj, await context.get_lambda(), *event_literals)
|
||||
)
|
||||
|
||||
@@ -405,7 +405,7 @@ def _model_config_to_manifest_data(model_config):
|
||||
file = _compute_local_file_path(model_config) / "manifest.json"
|
||||
|
||||
else:
|
||||
raise ValueError("Unsupported config type: {model_config[CONF_TYPE]}")
|
||||
raise ValueError(f"Unsupported config type: {model_config[CONF_TYPE]}")
|
||||
|
||||
return _load_model_data(file)
|
||||
|
||||
|
||||
@@ -344,7 +344,7 @@ def _spi_extra_validate(config):
|
||||
if CORE.is_esp32:
|
||||
return
|
||||
|
||||
if config[CONF_DATA_PIN] != 13 and config[CONF_CLOCK_PIN] != 14:
|
||||
if config[CONF_DATA_PIN] != 13 or config[CONF_CLOCK_PIN] != 14:
|
||||
raise cv.Invalid(
|
||||
"SPI only supports pins GPIO13 for data and GPIO14 for clock on ESP8266"
|
||||
)
|
||||
|
||||
@@ -185,7 +185,7 @@ def validate_update_interval(value):
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(PMSX003Component),
|
||||
@@ -290,7 +290,8 @@ CONFIG_SCHEMA = (
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
.extend(uart.UART_DEVICE_SCHEMA),
|
||||
validate_pmsx003_sensors,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -10,12 +10,14 @@ AUTO_LOAD = ["web_server_base"]
|
||||
prometheus_ns = cg.esphome_ns.namespace("prometheus")
|
||||
PrometheusHandler = prometheus_ns.class_("PrometheusHandler", cg.Component)
|
||||
|
||||
CUSTOMIZED_ENTITY = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ID): cv.string_strict,
|
||||
cv.Optional(CONF_NAME): cv.string_strict,
|
||||
},
|
||||
cv.has_at_least_one_key,
|
||||
CUSTOMIZED_ENTITY = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ID): cv.string_strict,
|
||||
cv.Optional(CONF_NAME): cv.string_strict,
|
||||
},
|
||||
),
|
||||
cv.has_at_least_one_key(CONF_ID, CONF_NAME),
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
|
||||
@@ -470,7 +470,7 @@ CANALSATLD_SCHEMA = cv.Schema(
|
||||
)
|
||||
|
||||
|
||||
@register_binary_sensor("canalsatld", CanalSatLDBinarySensor, CANALSAT_SCHEMA)
|
||||
@register_binary_sensor("canalsatld", CanalSatLDBinarySensor, CANALSATLD_SCHEMA)
|
||||
def canalsatld_binary_sensor(var, config):
|
||||
cg.add(
|
||||
var.set_data(
|
||||
@@ -1130,7 +1130,7 @@ def sony_dumper(var, config):
|
||||
async def sony_action(var, config, args):
|
||||
template_ = await cg.templatable(config[CONF_DATA], args, cg.uint32)
|
||||
cg.add(var.set_data(template_))
|
||||
template_ = await cg.templatable(config[CONF_NBITS], args, cg.uint32)
|
||||
template_ = await cg.templatable(config[CONF_NBITS], args, cg.uint8)
|
||||
cg.add(var.set_nbits(template_))
|
||||
|
||||
|
||||
@@ -1174,7 +1174,7 @@ def symphony_dumper(var, config):
|
||||
async def symphony_action(var, config, args):
|
||||
template_ = await cg.templatable(config[CONF_DATA], args, cg.uint32)
|
||||
cg.add(var.set_data(template_))
|
||||
template_ = await cg.templatable(config[CONF_NBITS], args, cg.uint32)
|
||||
template_ = await cg.templatable(config[CONF_NBITS], args, cg.uint8)
|
||||
cg.add(var.set_nbits(template_))
|
||||
template_ = await cg.templatable(config[CONF_COMMAND_REPEATS], args, cg.uint8)
|
||||
cg.add(var.set_repeats(template_))
|
||||
@@ -1188,7 +1188,7 @@ def validate_raw_alternating(value):
|
||||
this_negative = val < 0
|
||||
if i != 0 and this_negative == last_negative:
|
||||
raise cv.Invalid(
|
||||
f"Values must alternate between being positive and negative, please see index {i} and {i + 1}",
|
||||
f"Values must alternate between being positive and negative, please see index {i - 1} and {i}",
|
||||
[i],
|
||||
)
|
||||
last_negative = this_negative
|
||||
@@ -2105,12 +2105,12 @@ async def abbwelcome_action(var, config, args):
|
||||
)
|
||||
cg.add(
|
||||
var.set_source_address(
|
||||
await cg.templatable(config[CONF_SOURCE_ADDRESS], args, cg.uint16)
|
||||
await cg.templatable(config[CONF_SOURCE_ADDRESS], args, cg.uint32)
|
||||
)
|
||||
)
|
||||
cg.add(
|
||||
var.set_destination_address(
|
||||
await cg.templatable(config[CONF_DESTINATION_ADDRESS], args, cg.uint16)
|
||||
await cg.templatable(config[CONF_DESTINATION_ADDRESS], args, cg.uint32)
|
||||
)
|
||||
)
|
||||
cg.add(
|
||||
|
||||
@@ -50,7 +50,7 @@ def validate_min_max_value(config):
|
||||
max_val = config[CONF_MAX_VALUE]
|
||||
if min_val >= max_val:
|
||||
raise cv.Invalid(
|
||||
f"Max value {max_val} must be smaller than min value {min_val}"
|
||||
f"Max value {max_val} must be greater than min value {min_val}"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
@@ -272,7 +272,7 @@ SPRINKLER_VALVE_SCHEMA = cv.Schema(
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_UNIT_OF_MEASUREMENT, default=UNIT_SECOND
|
||||
): cv.one_of(UNIT_MINUTE, UNIT_SECOND, lower="True"),
|
||||
): cv.one_of(UNIT_MINUTE, UNIT_SECOND, lower=True),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
|
||||
@@ -127,7 +127,7 @@ def validate_st7789v(config):
|
||||
|
||||
if model_data[REQUIRE_PS] and CONF_POWER_SUPPLY not in config:
|
||||
raise cv.Invalid(
|
||||
f'{CONF_POWER_SUPPLY} must be specified when {CONF_MODEL} is {config[CONF_MODEL]}"'
|
||||
f"{CONF_POWER_SUPPLY} must be specified when {CONF_MODEL} is {config[CONF_MODEL]}"
|
||||
)
|
||||
|
||||
if (
|
||||
|
||||
@@ -46,7 +46,7 @@ def validate_acceleration(value):
|
||||
|
||||
def validate_speed(value):
|
||||
value = cv.string(value)
|
||||
for suffix in ("steps/s", "steps/s"):
|
||||
for suffix in ("steps/s",):
|
||||
value = value.removesuffix(suffix)
|
||||
|
||||
if value == "inf":
|
||||
|
||||
@@ -1667,7 +1667,7 @@ def dimensions(value):
|
||||
match = re.match(r"\s*([0-9]+)\s*[xX]\s*([0-9]+)\s*", value)
|
||||
if not match:
|
||||
raise Invalid(
|
||||
"Invalid value '{}' for dimensions. Only WIDTHxHEIGHT is allowed."
|
||||
f"Invalid value '{value}' for dimensions. Only WIDTHxHEIGHT is allowed."
|
||||
)
|
||||
return dimensions([match.group(1), match.group(2)])
|
||||
|
||||
|
||||
@@ -22,6 +22,19 @@ namespace esphome {
|
||||
|
||||
static const char *const TAG = "helpers";
|
||||
|
||||
__attribute__((noinline, cold)) void *callback_manager_grow(void *data, uint16_t size, uint16_t &capacity,
|
||||
size_t elem_size) {
|
||||
ESPHOME_DEBUG_ASSERT(size < UINT16_MAX);
|
||||
uint16_t new_cap = size + 1;
|
||||
auto *new_data = ::operator new(new_cap *elem_size);
|
||||
if (data) {
|
||||
__builtin_memcpy(new_data, data, size * elem_size);
|
||||
::operator delete(data);
|
||||
}
|
||||
capacity = new_cap;
|
||||
return new_data;
|
||||
}
|
||||
|
||||
static const uint16_t CRC16_A001_LE_LUT_L[] = {0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241,
|
||||
0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440};
|
||||
static const uint16_t CRC16_A001_LE_LUT_H[] = {0x0000, 0xcc01, 0xd801, 0x1400, 0xf001, 0x3c00, 0x2800, 0xe401,
|
||||
|
||||
+56
-12
@@ -1801,33 +1801,77 @@ template<typename... Ts> struct Callback<void(Ts...)> {
|
||||
}
|
||||
};
|
||||
|
||||
/// Grow a CallbackManager's backing array to exactly size+1. Defined in helpers.cpp.
|
||||
void *callback_manager_grow(void *data, uint16_t size, uint16_t &capacity, size_t elem_size);
|
||||
|
||||
template<typename... X> class CallbackManager;
|
||||
|
||||
/** Helper class to allow having multiple subscribers to a callback.
|
||||
*
|
||||
* Uses a trivial-copyable-specialized container instead of std::vector to avoid
|
||||
* template bloat (_M_realloc_insert, exception-safe copies). Since Callback is
|
||||
* trivially copyable (just {fn_ptr, ctx_ptr}), reallocation is a plain memcpy.
|
||||
* Uses uint16_t for size/capacity (8 bytes on 32-bit vs 12 for std::vector).
|
||||
* Grows to exact size on each add — callbacks are registered during setup()
|
||||
* and most instances have only 1-2 callbacks, so slack capacity is wasteful.
|
||||
*
|
||||
* @tparam Ts The arguments for the callbacks, wrapped in void().
|
||||
*/
|
||||
template<typename... Ts> class CallbackManager<void(Ts...)> {
|
||||
using CbType = Callback<void(Ts...)>;
|
||||
static_assert(std::is_trivially_copyable_v<CbType>, "Callback must be trivially copyable");
|
||||
|
||||
public:
|
||||
CallbackManager() = default;
|
||||
~CallbackManager() { ::operator delete(this->data_); }
|
||||
|
||||
// Non-copyable (would alias data_), movable (for std::map support)
|
||||
CallbackManager(const CallbackManager &) = delete;
|
||||
CallbackManager &operator=(const CallbackManager &) = delete;
|
||||
CallbackManager(CallbackManager &&other) noexcept
|
||||
: data_(other.data_), size_(other.size_), capacity_(other.capacity_) {
|
||||
other.data_ = nullptr;
|
||||
other.size_ = 0;
|
||||
other.capacity_ = 0;
|
||||
}
|
||||
CallbackManager &operator=(CallbackManager &&other) noexcept {
|
||||
std::swap(this->data_, other.data_);
|
||||
std::swap(this->size_, other.size_);
|
||||
std::swap(this->capacity_, other.capacity_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Add any callable. Small trivially-copyable callables (like [this] lambdas)
|
||||
/// are stored inline without heap allocation or std::function.
|
||||
template<typename F> void add(F &&callback) { this->add_(Callback<void(Ts...)>::create(std::forward<F>(callback))); }
|
||||
|
||||
/// Call all callbacks in this manager. No null check on invoke.
|
||||
void call(Ts... args) {
|
||||
for (auto &cb : this->callbacks_)
|
||||
cb.call(args...);
|
||||
}
|
||||
size_t size() const { return this->callbacks_.size(); }
|
||||
template<typename F> void add(F &&callback) { this->add_(CbType::create(std::forward<F>(callback))); }
|
||||
|
||||
/// Call all callbacks in this manager.
|
||||
void operator()(Ts... args) { call(args...); }
|
||||
inline void ESPHOME_ALWAYS_INLINE call(Ts... args) {
|
||||
if (this->size_ != 0) {
|
||||
for (auto *it = this->data_, *end = it + this->size_; it != end; ++it) {
|
||||
it->call(args...);
|
||||
}
|
||||
}
|
||||
}
|
||||
uint16_t size() const { return this->size_; }
|
||||
|
||||
/// Call all callbacks in this manager.
|
||||
void operator()(Ts... args) { this->call(args...); }
|
||||
|
||||
protected:
|
||||
template<typename...> friend class LazyCallbackManager;
|
||||
/// Non-template core to avoid code duplication per lambda type.
|
||||
void add_(Callback<void(Ts...)> cb) { this->callbacks_.push_back(cb); }
|
||||
std::vector<Callback<void(Ts...)>> callbacks_;
|
||||
/// Inline fast path; cold growth path is in helpers.cpp via callback_manager_grow().
|
||||
void add_(CbType cb) {
|
||||
if (this->size_ == this->capacity_) {
|
||||
this->data_ =
|
||||
static_cast<CbType *>(callback_manager_grow(this->data_, this->size_, this->capacity_, sizeof(CbType)));
|
||||
}
|
||||
this->data_[this->size_++] = cb;
|
||||
}
|
||||
CbType *data_{nullptr};
|
||||
uint16_t size_{0};
|
||||
uint16_t capacity_{0};
|
||||
};
|
||||
|
||||
/** CallbackManager backed by StaticVector for compile-time-known callback counts.
|
||||
@@ -1871,7 +1915,7 @@ template<typename... X> class LazyCallbackManager;
|
||||
* from API and web_server components).
|
||||
*
|
||||
* Memory overhead comparison (32-bit systems):
|
||||
* - CallbackManager: 12 bytes (empty std::vector)
|
||||
* - CallbackManager: 8 bytes (pointer + uint16 size + uint16 capacity)
|
||||
* - LazyCallbackManager: 4 bytes (nullptr pointer)
|
||||
*
|
||||
* Uses plain pointer instead of unique_ptr to avoid template instantiation overhead.
|
||||
|
||||
+2
-2
@@ -130,7 +130,7 @@ def check_error(data: list[int] | bytes, expect: int | list[int] | None) -> None
|
||||
:param expect: Expected response code(s), None to skip validation.
|
||||
:raises OTAError: If an error code is detected or response doesn't match expected.
|
||||
"""
|
||||
if not expect:
|
||||
if expect is None:
|
||||
return
|
||||
if not data:
|
||||
raise OTAError(
|
||||
@@ -278,7 +278,7 @@ def perform_ota(
|
||||
raise OTAError("ESP requests password, but no password given!")
|
||||
|
||||
nonce_bytes = receive_exactly(
|
||||
sock, nonce_size, f"{hash_name} authentication nonce", [], decode=False
|
||||
sock, nonce_size, f"{hash_name} authentication nonce", None, decode=False
|
||||
)
|
||||
assert isinstance(nonce_bytes, bytes)
|
||||
nonce = nonce_bytes.decode()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import UTC, datetime
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
@@ -27,8 +27,8 @@ def has_remote_file_changed(url: str, local_file_path: Path) -> bool:
|
||||
_LOGGER.debug("has_remote_file_changed: File exists at %s", local_file_path)
|
||||
try:
|
||||
local_modification_time = local_file_path.stat().st_mtime
|
||||
local_modification_time_str = datetime.utcfromtimestamp(
|
||||
local_modification_time
|
||||
local_modification_time_str = datetime.fromtimestamp(
|
||||
local_modification_time, tz=UTC
|
||||
).strftime("%a, %d %b %Y %H:%M:%S GMT")
|
||||
|
||||
headers = {
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ _BACKGROUND_TASKS: set[asyncio.Task] = set()
|
||||
|
||||
|
||||
class DashboardStatus:
|
||||
def __init__(self, on_update: Callable[[dict[str, bool | None], []]]) -> None:
|
||||
def __init__(self, on_update: Callable[[dict[str, bool | None]], None]) -> None:
|
||||
"""Initialize the dashboard status."""
|
||||
self.on_update = on_update
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ espnow:
|
||||
data: !lambda 'return {0x01, 0x02, 0x03, 0x04, 0x05};'
|
||||
- espnow.broadcast:
|
||||
data: "Hello, World!"
|
||||
- espnow.broadcast:
|
||||
data: "it's a test"
|
||||
- espnow.broadcast:
|
||||
data: [0x01, 0x02, 0x03, 0x04, 0x05]
|
||||
- espnow.broadcast:
|
||||
|
||||
@@ -49,6 +49,44 @@ lvgl:
|
||||
id: meter_arc_indicator
|
||||
start_value: 0
|
||||
end_value: 180
|
||||
on_invalidate_area:
|
||||
logger.log: Invalidate area
|
||||
on_resolution_change:
|
||||
logger.log: Resolution changed
|
||||
on_color_format_change:
|
||||
logger.log: Color format changed
|
||||
on_refr_request:
|
||||
logger.log: Refresh request
|
||||
on_refr_start:
|
||||
logger.log: Refresh start
|
||||
on_refr_ready:
|
||||
logger.log: Refresh ready
|
||||
on_render_start:
|
||||
logger.log: Render start
|
||||
on_render_ready:
|
||||
logger.log: Render ready
|
||||
on_flush_start:
|
||||
logger.log: Flush start
|
||||
on_flush_finish:
|
||||
logger.log: Flush finish
|
||||
on_flush_wait_start:
|
||||
logger.log: Flush wait start
|
||||
on_flush_wait_finish:
|
||||
logger.log: Flush wait finish
|
||||
on_update_layout_complete:
|
||||
logger.log: Update layout complete
|
||||
on_vsync:
|
||||
logger.log: Vsync
|
||||
on_vsync_request:
|
||||
logger.log: Vsync request
|
||||
on_screen_load_start:
|
||||
logger.log: Screen load start
|
||||
on_screen_load:
|
||||
logger.log: Screen loaded
|
||||
on_screen_unload:
|
||||
logger.log: Screen unloaded
|
||||
on_screen_unload_start:
|
||||
logger.log: Screen unload start
|
||||
bg_color: light_blue
|
||||
bottom_layer:
|
||||
widgets:
|
||||
@@ -660,14 +698,6 @@ lvgl:
|
||||
logger.log: Child created
|
||||
on_child_delete:
|
||||
logger.log: Child deleted
|
||||
on_screen_unload_start:
|
||||
logger.log: Screen unload start
|
||||
on_screen_load_start:
|
||||
logger.log: Screen load start
|
||||
on_screen_load:
|
||||
logger.log: Screen loaded
|
||||
on_screen_unload:
|
||||
logger.log: Screen unloaded
|
||||
on_size_change:
|
||||
logger.log: Size changed
|
||||
on_style_change:
|
||||
@@ -676,36 +706,6 @@ lvgl:
|
||||
logger.log: Layout changed
|
||||
on_get_self_size:
|
||||
logger.log: Get self size
|
||||
on_invalidate_area:
|
||||
logger.log: Invalidate area
|
||||
on_resolution_change:
|
||||
logger.log: Resolution changed
|
||||
on_color_format_change:
|
||||
logger.log: Color format changed
|
||||
on_refr_request:
|
||||
logger.log: Refresh request
|
||||
on_refr_start:
|
||||
logger.log: Refresh start
|
||||
on_refr_ready:
|
||||
logger.log: Refresh ready
|
||||
on_render_start:
|
||||
logger.log: Render start
|
||||
on_render_ready:
|
||||
logger.log: Render ready
|
||||
on_flush_start:
|
||||
logger.log: Flush start
|
||||
on_flush_finish:
|
||||
logger.log: Flush finish
|
||||
on_flush_wait_start:
|
||||
logger.log: Flush wait start
|
||||
on_flush_wait_finish:
|
||||
logger.log: Flush wait finish
|
||||
on_update_layout_complete:
|
||||
logger.log: Update layout complete
|
||||
on_vsync:
|
||||
logger.log: Vsync
|
||||
on_vsync_request:
|
||||
logger.log: Vsync request
|
||||
- led:
|
||||
id: lv_led
|
||||
color: 0x00FF00
|
||||
|
||||
Reference in New Issue
Block a user