mirror of
https://github.com/esphome/esphome.git
synced 2026-10-27 16:32:11 +00:00
Compare commits
34
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
568161d3f2 | ||
|
|
d3f4f44c7b | ||
|
|
6df15be714 | ||
|
|
a03fe3c38e | ||
|
|
afc8c51f93 | ||
|
|
ef7ed69f9d | ||
|
|
ec32802662 | ||
|
|
433d8046b1 | ||
|
|
8ecb981656 | ||
|
|
8c990573d7 | ||
|
|
bdec0c1485 | ||
|
|
70e85d939d | ||
|
|
25b3c2820a | ||
|
|
0a3993639e | ||
|
|
b1342196c3 | ||
|
|
9f5ff8ef6e | ||
|
|
79c86833ee | ||
|
|
4ed6599b7e | ||
|
|
de1a854266 | ||
|
|
11b73fa6eb | ||
|
|
7f5898279c | ||
|
|
916fc01a6a | ||
|
|
de49ad67fb | ||
|
|
4ee6141b02 | ||
|
|
0a97ed5706 | ||
|
|
8359b10cf8 | ||
|
|
f00b11a48a | ||
|
|
9749698201 | ||
|
|
6f4bdf49fd | ||
|
|
772acacb9e | ||
|
|
c1e1555918 | ||
|
|
e647b2acc1 | ||
|
|
3af8fec562 | ||
|
|
ef53e675ff |
No files matched your search
@@ -13,19 +13,10 @@ from esphome.const import (
|
||||
from esphome.core.entity_helpers import inherit_property_from
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import copy_ns
|
||||
|
||||
CopySensor = copy_ns.class_("CopySensor", sensor.Sensor, cg.Component)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(CopySensor)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(sensor.Sensor),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(sensor.Sensor).extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(sensor.Sensor),
|
||||
}
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
@@ -40,6 +31,4 @@ FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = await sensor.new_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
await cg.add_variable(var.set_source, config[CONF_SOURCE_ID])
|
||||
sensor.follow_sensor(var, await cg.get_variable(config[CONF_SOURCE_ID]))
|
||||
@@ -1,16 +0,0 @@
|
||||
#include "copy_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::copy {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "copy.sensor");
|
||||
|
||||
void CopySensor::setup() {
|
||||
source_->add_on_state_callback([this](float value) { this->publish_state(value); });
|
||||
if (source_->has_state())
|
||||
this->publish_state(source_->state);
|
||||
}
|
||||
|
||||
void CopySensor::dump_config() { LOG_SENSOR("", "Copy Sensor", this); }
|
||||
|
||||
} // namespace esphome::copy
|
||||
@@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome::copy {
|
||||
|
||||
class CopySensor final : public sensor::Sensor, public Component {
|
||||
public:
|
||||
void set_source(sensor::Sensor *source) { source_ = source; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
sensor::Sensor *source_;
|
||||
};
|
||||
|
||||
} // namespace esphome::copy
|
||||
@@ -5,19 +5,10 @@ from esphome.const import CONF_ENTITY_CATEGORY, CONF_ICON, CONF_SOURCE_ID
|
||||
from esphome.core.entity_helpers import inherit_property_from
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import copy_ns
|
||||
|
||||
CopyTextSensor = copy_ns.class_("CopyTextSensor", text_sensor.TextSensor, cg.Component)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
text_sensor.text_sensor_schema(CopyTextSensor)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(text_sensor.TextSensor),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = text_sensor.text_sensor_schema(text_sensor.TextSensor).extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(text_sensor.TextSensor),
|
||||
}
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
@@ -28,6 +19,4 @@ FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = await text_sensor.new_text_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
await cg.add_variable(var.set_source, config[CONF_SOURCE_ID])
|
||||
text_sensor.follow_text_sensor(var, await cg.get_variable(config[CONF_SOURCE_ID]))
|
||||
@@ -1,16 +0,0 @@
|
||||
#include "copy_text_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::copy {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "copy.text_sensor");
|
||||
|
||||
void CopyTextSensor::setup() {
|
||||
source_->add_on_state_callback([this](const std::string &value) { this->publish_state(value); });
|
||||
if (source_->has_state())
|
||||
this->publish_state(source_->state);
|
||||
}
|
||||
|
||||
void CopyTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Copy Sensor", this); }
|
||||
|
||||
} // namespace esphome::copy
|
||||
@@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
|
||||
namespace esphome::copy {
|
||||
|
||||
class CopyTextSensor final : public text_sensor::TextSensor, public Component {
|
||||
public:
|
||||
void set_source(text_sensor::TextSensor *source) { source_ = source; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
text_sensor::TextSensor *source_;
|
||||
};
|
||||
|
||||
} // namespace esphome::copy
|
||||
@@ -1,6 +1,7 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Protocol
|
||||
|
||||
from esphome import automation, core
|
||||
from esphome import automation, core, pins
|
||||
from esphome.automation import maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import (
|
||||
@@ -13,6 +14,7 @@ import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_AUTO_CLEAR_ENABLED,
|
||||
CONF_DIMENSIONS,
|
||||
CONF_ENABLE_PIN,
|
||||
CONF_FROM,
|
||||
CONF_HEIGHT,
|
||||
CONF_ID,
|
||||
@@ -54,6 +56,33 @@ DISPLAY_ROTATIONS = {
|
||||
}
|
||||
|
||||
|
||||
class DisplayModel(Protocol):
|
||||
"""The parts of a display model used by enable_pin_schema()."""
|
||||
|
||||
def get_default(self, key: str, fallback: Any = ...) -> Any: ...
|
||||
|
||||
|
||||
def enable_pin_schema(
|
||||
model: DisplayModel, config: ConfigType
|
||||
) -> dict[cv.Optional, Any]:
|
||||
"""
|
||||
Get the schema entry for a display's enable pins.
|
||||
Pins given in the user config are used as they are. Otherwise the model's default enable
|
||||
pins are used, except any that is also used elsewhere in the config, so a user can take
|
||||
control of that pin, e.g. with a switch.
|
||||
:param config: The display config as given by the user
|
||||
"""
|
||||
schema = cv.ensure_list(pins.gpio_output_pin_schema)
|
||||
default = model.get_default(CONF_ENABLE_PIN, None)
|
||||
if default is None or CONF_ENABLE_PIN in config:
|
||||
return {cv.Optional(CONF_ENABLE_PIN): schema}
|
||||
return {
|
||||
cv.Optional(
|
||||
CONF_ENABLE_PIN, default=default
|
||||
): pins.PIN_SCHEMA_REGISTRY.optional(schema)
|
||||
}
|
||||
|
||||
|
||||
def validate_rotation(value):
|
||||
value = cv.string(value)
|
||||
value = value.removesuffix("°")
|
||||
|
||||
@@ -146,7 +146,7 @@ def model_schema(config):
|
||||
cv.GenerateID(): cv.declare_id(class_name),
|
||||
cv.GenerateID(CONF_INIT_SEQUENCE_ID): cv.declare_id(cg.uint8),
|
||||
cv_dimensions(CONF_DIMENSIONS): dimension_schema(model),
|
||||
model.option(CONF_ENABLE_PIN): cv.ensure_list(pins.gpio_output_pin_schema),
|
||||
**display.enable_pin_schema(model, config),
|
||||
model.option(CONF_INIT_SEQUENCE, cv.UNDEFINED): cv.ensure_list(
|
||||
map_sequence
|
||||
),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from typing import Any, Self
|
||||
|
||||
from esphome.components.mipi import check_model_requirements
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_DIMENSIONS, CONF_HEIGHT, CONF_OFFSET_HEIGHT, CONF_WIDTH
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import MockObj
|
||||
from esphome.types import ConfigType
|
||||
|
||||
@@ -128,24 +128,5 @@ class EpaperModel:
|
||||
return model
|
||||
|
||||
def check_requirements(self) -> None:
|
||||
"""
|
||||
Raise a friendly error if any component this model requires is not configured.
|
||||
|
||||
This runs during schema validation (before ID references are resolved) so that a
|
||||
model whose default pins live on a pin expander reports the missing expander clearly
|
||||
instead of a cryptic "Couldn't find ID" from the unresolved pin reference.
|
||||
"""
|
||||
if requirements := self.get_default("requires", set()):
|
||||
# ``raw_config`` is populated before any component schema runs during a real
|
||||
# validation, so presence of a required component is simply a top-level key.
|
||||
# When it is absent (e.g. a unit test that invokes the schema directly) there
|
||||
# is no config to check against, so skip.
|
||||
global_config = CORE.raw_config
|
||||
if global_config is None:
|
||||
return
|
||||
missing = {x for x in requirements if x not in global_config}
|
||||
if missing:
|
||||
reqstr = ", ".join(f"'{x}'" for x in sorted(missing))
|
||||
raise cv.Invalid(
|
||||
f"{self.name} requires component{'s' if len(missing) > 1 else ''} {reqstr} to be configured"
|
||||
)
|
||||
"""Check that the components this model needs are configured, see check_model_requirements()."""
|
||||
check_model_requirements(self.name, self.defaults)
|
||||
@@ -274,9 +274,7 @@ def _model_schema(config: ConfigType) -> cv.Schema:
|
||||
cv.Optional(CONF_UPDATE_MODE): update_mode,
|
||||
# One or more GPIOs driven high during setup to power on the panel
|
||||
# (e.g. board power-enable rails), before reset and init.
|
||||
cv.Optional(
|
||||
CONF_ENABLE_PIN, default=model.get_default(CONF_ENABLE_PIN, [])
|
||||
): cv.ensure_list(pins.gpio_output_pin_schema),
|
||||
**display.enable_pin_schema(model, config),
|
||||
cv.Optional(CONF_RESET_DURATION): cv.All(
|
||||
cv.positive_time_period_milliseconds,
|
||||
cv.Range(max=core.TimePeriod(milliseconds=500)),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import logging
|
||||
|
||||
from esphome import automation, pins
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
@@ -15,6 +17,8 @@ from esphome.core import CORE
|
||||
from esphome.cpp_helpers import gpio_pin_expression
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CODEOWNERS = ["@max246"]
|
||||
DOMAIN = "lightwaverf"
|
||||
|
||||
@@ -35,10 +39,20 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
).extend(cv.polling_component_schema("1s"))
|
||||
|
||||
|
||||
def _drop_unused_name(config: ConfigType) -> ConfigType:
|
||||
# Remove before 2027.5.0
|
||||
if config.pop(CONF_NAME, None) is not None:
|
||||
_LOGGER.warning(
|
||||
"[lightwaverf] 'name' on lightwaverf.send_raw has no effect; remove it. "
|
||||
"Removed in 2027.5.0"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
LIGHTWAVE_SEND_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(LIGHTWAVERFComponent),
|
||||
cv.Required(CONF_NAME): cv.string,
|
||||
cv.Optional(CONF_NAME): cv.string,
|
||||
cv.Required(CONF_CODE): cv.All(
|
||||
[cv.Any(cv.hex_uint8_t)],
|
||||
cv.Length(min=10),
|
||||
@@ -47,7 +61,7 @@ LIGHTWAVE_SEND_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PULSE_LENGTH, default=330): cv.int_,
|
||||
}
|
||||
)
|
||||
).add_extra(_drop_unused_name)
|
||||
|
||||
|
||||
automation.register_apply_action(
|
||||
|
||||
@@ -28,6 +28,7 @@ from esphome.const import (
|
||||
CONF_X,
|
||||
CONF_Y,
|
||||
)
|
||||
from esphome.cpp_generator import MockObj
|
||||
from esphome.cpp_types import FixedVector
|
||||
|
||||
from ..automation import action_to_code
|
||||
@@ -41,6 +42,7 @@ from ..defines import (
|
||||
CONF_RADIUS,
|
||||
CONF_SRC,
|
||||
CONF_START_ANGLE,
|
||||
LValidator,
|
||||
addr,
|
||||
literal,
|
||||
)
|
||||
@@ -57,7 +59,7 @@ from ..lv_validation import (
|
||||
)
|
||||
from ..lvcode import LocalVariable, lv, lv_assign, lv_expr
|
||||
from ..schemas import STYLE_PROPS, TEXT_SCHEMA, point_schema, remap_property
|
||||
from ..types import LvType, ObjUpdateAction, lv_point_precise_t
|
||||
from ..types import LvType, ObjUpdateAction, lv_gradient_t, lv_point_precise_t
|
||||
from . import Widget, WidgetType, get_widgets
|
||||
from .img import CONF_IMAGE
|
||||
|
||||
@@ -221,7 +223,6 @@ RECT_PROPS = {
|
||||
"radius",
|
||||
"bg_opa",
|
||||
"bg_color",
|
||||
"bg_grad",
|
||||
"border_color",
|
||||
"border_width",
|
||||
"border_opa",
|
||||
@@ -239,6 +240,30 @@ RECT_PROPS = {
|
||||
"shadow_opa",
|
||||
)
|
||||
}
|
||||
RECT_PROPS.update(
|
||||
bg_grad=LValidator(
|
||||
cv.use_id(lv_gradient_t), lv_gradient_t, retmapper=lambda v: MockObj(f"*{v}")
|
||||
)
|
||||
)
|
||||
RECT_SCHEMA = {
|
||||
**{cv.Optional(prop): validator for prop, validator in RECT_PROPS.items()},
|
||||
cv.Optional(CONF_OPA): cv.invalid(
|
||||
"Rectangles have no single opacity; use bg_opa, border_opa, outline_opa or shadow_opa"
|
||||
),
|
||||
}
|
||||
POLYGON_PROPS = {
|
||||
p: RECT_PROPS[p] for p in ("border_color", "border_width", "border_opa")
|
||||
}
|
||||
POLYGON_SCHEMA = {
|
||||
**{cv.Optional(prop): validator for prop, validator in POLYGON_PROPS.items()},
|
||||
**{
|
||||
cv.Optional(prop): cv.invalid(
|
||||
"Polygons are drawn as an outline; use border_color, border_width and border_opa"
|
||||
)
|
||||
for prop in (*RECT_PROPS, CONF_OPA)
|
||||
if prop not in POLYGON_PROPS
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _draw_line(layer, dsc, points):
|
||||
@@ -264,10 +289,10 @@ def _draw_line(layer, dsc, points):
|
||||
ObjUpdateAction,
|
||||
cv.Schema(
|
||||
{
|
||||
**DRAW_OPA_SCHEMA,
|
||||
**DRAW_SCHEMA,
|
||||
cv.Required(CONF_WIDTH): cv.templatable(cv.int_),
|
||||
cv.Required(CONF_HEIGHT): cv.templatable(cv.int_),
|
||||
**{cv.Optional(prop): STYLE_PROPS[prop] for prop in RECT_PROPS},
|
||||
**RECT_SCHEMA,
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
@@ -423,8 +448,8 @@ async def canvas_draw_image(config, action_id, template_arg, args):
|
||||
LINE_PROPS = {
|
||||
"width": STYLE_PROPS["line_width"],
|
||||
"color": STYLE_PROPS["line_color"],
|
||||
"dash-width": STYLE_PROPS["line_dash_width"],
|
||||
"dash-gap": STYLE_PROPS["line_dash_gap"],
|
||||
"dash_width": STYLE_PROPS["line_dash_width"],
|
||||
"dash_gap": STYLE_PROPS["line_dash_gap"],
|
||||
"round_start": lv_bool,
|
||||
"round_end": lv_bool,
|
||||
}
|
||||
@@ -474,7 +499,7 @@ async def canvas_draw_line(config, action_id, template_arg, args):
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.use_id(lv_canvas_t),
|
||||
cv.Required(CONF_POINTS): cv.ensure_list(point_schema),
|
||||
**{cv.Optional(prop): STYLE_PROPS[prop] for prop in RECT_PROPS},
|
||||
**POLYGON_SCHEMA,
|
||||
},
|
||||
).add_extra(_validate_points),
|
||||
synchronous=True,
|
||||
@@ -503,7 +528,7 @@ async def canvas_draw_polygon(config, action_id, template_arg, args):
|
||||
_draw_line(layer, line_dsc, points)
|
||||
|
||||
return await draw_to_code(
|
||||
config, "rect", RECT_PROPS, do_draw_polygon, action_id, template_arg, args
|
||||
config, "rect", POLYGON_PROPS, do_draw_polygon, action_id, template_arg, args
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ from esphome.const import (
|
||||
CONF_WIDTH,
|
||||
CONF_X,
|
||||
)
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import MockObj
|
||||
from esphome.cpp_types import nullptr
|
||||
|
||||
@@ -64,7 +65,7 @@ from ..lv_validation import (
|
||||
)
|
||||
from ..lvcode import LambdaContext, LocalVariable, lv, lv_add, lv_expr, lv_obj
|
||||
from ..schemas import STATE_SCHEMA
|
||||
from ..styles import LVStyle
|
||||
from ..styles import LVStyle, create_style
|
||||
from ..types import (
|
||||
LV_EVENT,
|
||||
LvCompound,
|
||||
@@ -100,6 +101,12 @@ CONF_SCALES = "scales"
|
||||
CONF_STRIDE = "stride"
|
||||
CONF_TICK_STYLE = "tick_style"
|
||||
|
||||
|
||||
def _arc_style_name(indicator_id: str) -> str:
|
||||
"""Name of the style variable generated for an arc indicator."""
|
||||
return f"meter_arc_{indicator_id}_style"
|
||||
|
||||
|
||||
# LVGL 9.4 Migration: Use scale widget instead of removed meter widget
|
||||
#
|
||||
# The lv_meter widget was removed in LVGL 9.4 and replaced with the more
|
||||
@@ -423,11 +430,9 @@ class MeterType(WidgetType):
|
||||
get_warnings().add(
|
||||
"The 'r_mod' indicator property is not supported in LVGL 9.x and will be ignored."
|
||||
)
|
||||
arc_style = LVStyle(f"meter_arc_{iid.id}", props)
|
||||
style_var = await create_style(_arc_style_name(iid.id), props)
|
||||
tvar = cg.Pvariable(iid, lv_expr.scale_add_section(scale_var))
|
||||
lv.scale_section_set_style(
|
||||
tvar, LV_PART.MAIN, await arc_style.get_var()
|
||||
)
|
||||
lv.scale_section_set_style(tvar, LV_PART.MAIN, style_var)
|
||||
lw = Widget.create(iid, tvar, arc_indicator_type)
|
||||
lw.parent = scale_var
|
||||
await set_indicator_values(scale_var, lw, v)
|
||||
@@ -610,6 +615,15 @@ async def indicator_update_to_code(config, action_id, template_arg, args):
|
||||
|
||||
async def set_value(w: Widget):
|
||||
await set_indicator_values(w.parent, w, config)
|
||||
if (opa := config.get(CONF_OPA)) is None:
|
||||
return
|
||||
opa = await opacity.process(opa)
|
||||
if w.type is arc_indicator_type:
|
||||
style = await cg.get_variable(ID(_arc_style_name(str(w.var))))
|
||||
lv.style_set_arc_opa(style, opa)
|
||||
lv_obj.invalidate(w.parent)
|
||||
else:
|
||||
lv_obj.set_style_opa(w.obj, opa, LV_PART.MAIN)
|
||||
|
||||
return await action_to_code(widget, set_value, action_id, template_arg, args)
|
||||
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
|
||||
from collections.abc import Callable
|
||||
import functools
|
||||
import importlib.util
|
||||
from typing import Any, Self
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components.const import CONF_COLOR_DEPTH
|
||||
from esphome.components.display import CONF_SHOW_TEST_CARD, display_ns
|
||||
import esphome.config_validation as cv
|
||||
@@ -23,6 +25,7 @@ from esphome.const import (
|
||||
CONF_MIRROR_X,
|
||||
CONF_MIRROR_Y,
|
||||
CONF_MODEL,
|
||||
CONF_NUMBER,
|
||||
CONF_OFFSET_HEIGHT,
|
||||
CONF_OFFSET_WIDTH,
|
||||
CONF_PAGES,
|
||||
@@ -306,6 +309,57 @@ def model_schema_extractor(
|
||||
return decorate
|
||||
|
||||
|
||||
def _is_pin_expander(key: str) -> bool:
|
||||
"""Check if a pin config key names a component that provides pins, e.g. an I/O expander."""
|
||||
from esphome import loader
|
||||
|
||||
if key not in pins.PIN_SCHEMA_REGISTRY:
|
||||
if importlib.util.find_spec(f"esphome.components.{key}") is None:
|
||||
return False
|
||||
# Loading the component registers its pin schema. If it fails to load (the
|
||||
# loader logs why), still require it rather than quietly dropping it.
|
||||
if loader.get_component(key) is None:
|
||||
return True
|
||||
return key in pins.PIN_SCHEMA_REGISTRY
|
||||
|
||||
|
||||
def _pin_expanders(value: Any) -> set[str]:
|
||||
"""Find the pin expander components used by any pin configs in a model's defaults."""
|
||||
if isinstance(value, (list, tuple)):
|
||||
return set().union(*(_pin_expanders(v) for v in value))
|
||||
if not isinstance(value, dict):
|
||||
return set()
|
||||
found = set().union(*(_pin_expanders(v) for v in value.values()))
|
||||
if CONF_NUMBER in value:
|
||||
found.update(k for k in value if k != CONF_NUMBER and _is_pin_expander(k))
|
||||
return found
|
||||
|
||||
|
||||
def check_model_requirements(name: str, defaults: dict[str, Any]) -> None:
|
||||
"""
|
||||
Raise a friendly error if any component a display model requires is not configured.
|
||||
These are the components in its ``requires`` default plus any pin expander used by its
|
||||
default pins, which is needed even if the user config overrides those pins.
|
||||
|
||||
This runs during schema validation (before ID references are resolved) so that a
|
||||
model whose default pins live on a pin expander reports the missing expander clearly
|
||||
instead of a cryptic "Couldn't find ID" from the unresolved pin reference.
|
||||
"""
|
||||
# ``raw_config`` is populated before any component schema runs during a real
|
||||
# validation, so presence of a required component is simply a top-level key.
|
||||
# When it is absent (e.g. a unit test that invokes the schema directly) there
|
||||
# is no config to check against, so skip.
|
||||
global_config = CORE.raw_config
|
||||
if global_config is None:
|
||||
return
|
||||
requirements = set(defaults.get("requires", ())) | _pin_expanders(defaults)
|
||||
if missing := {x for x in requirements if x not in global_config}:
|
||||
reqstr = ", ".join(f"'{x}'" for x in sorted(missing))
|
||||
raise cv.Invalid(
|
||||
f"{name} requires component{'s' if len(missing) > 1 else ''} {reqstr} to be configured"
|
||||
)
|
||||
|
||||
|
||||
class DriverChip:
|
||||
"""
|
||||
A class representing a MIPI DBI driver chip model.
|
||||
@@ -671,32 +725,14 @@ class DriverChip:
|
||||
|
||||
def check_requirements(self) -> None:
|
||||
"""
|
||||
Raise a friendly error if any component this model requires is not configured.
|
||||
|
||||
This runs during schema validation (before ID references are resolved) so that a
|
||||
model whose default pins live on a pin expander reports the missing expander clearly
|
||||
instead of a cryptic "Couldn't find ID" from the unresolved pin reference.
|
||||
|
||||
Check that the components this model needs are configured, see check_model_requirements().
|
||||
Also logs a warning if the model is deprecated.
|
||||
"""
|
||||
if deprecation_reason := self.get_default("deprecation_reason"):
|
||||
LOGGER.warning(
|
||||
"Display model %s is deprecated: %s", self.name, deprecation_reason
|
||||
)
|
||||
if requirements := self.get_default("requires", set()):
|
||||
# ``raw_config`` is populated before any component schema runs during a real
|
||||
# validation, so presence of a required component is simply a top-level key.
|
||||
# When it is absent (e.g. a unit test that invokes the schema directly) there
|
||||
# is no config to check against, so skip.
|
||||
global_config = CORE.raw_config
|
||||
if global_config is None:
|
||||
return
|
||||
missing = {x for x in requirements if x not in global_config}
|
||||
if missing:
|
||||
reqstr = ", ".join(f"'{x}'" for x in sorted(missing))
|
||||
raise cv.Invalid(
|
||||
f"{self.name} requires component{'s' if len(missing) > 1 else ''} {reqstr} to be configured"
|
||||
)
|
||||
check_model_requirements(self.name, self.defaults)
|
||||
|
||||
|
||||
def requires_buffer(config) -> bool:
|
||||
|
||||
@@ -85,7 +85,7 @@ COLOR_DEPTHS = {
|
||||
}
|
||||
|
||||
|
||||
def model_schema(config: ConfigType) -> cv.All:
|
||||
def model_schema(config: ConfigType) -> cv.Schema:
|
||||
model = MODELS[config[CONF_MODEL].upper()]
|
||||
transform = model.transform_schema()
|
||||
# CUSTOM model will need to provide a custom init sequence
|
||||
@@ -97,16 +97,14 @@ def model_schema(config: ConfigType) -> cv.All:
|
||||
# Dimensions are optional if the model has a default width
|
||||
cv_dimensions = cv.Optional if model.get_default(CONF_WIDTH) else cv.Required
|
||||
pixel_modes = (PIXEL_MODE_16BIT, PIXEL_MODE_24BIT, "16", "24")
|
||||
schema = display.FULL_DISPLAY_SCHEMA.extend(
|
||||
return display.FULL_DISPLAY_SCHEMA.extend(
|
||||
{
|
||||
model.option(CONF_RESET_PIN, cv.UNDEFINED): pins.gpio_output_pin_schema,
|
||||
cv.GenerateID(): cv.declare_id(MipiDsi),
|
||||
cv_dimensions(CONF_DIMENSIONS): dimension_schema(
|
||||
model.get_default(CONF_DRAW_ROUNDING, 1)
|
||||
),
|
||||
model.option(CONF_ENABLE_PIN, cv.UNDEFINED): cv.ensure_list(
|
||||
pins.gpio_output_pin_schema
|
||||
),
|
||||
**display.enable_pin_schema(model, config),
|
||||
model.option(CONF_COLOR_ORDER, MODE_BGR): cv.enum(COLOR_ORDERS, upper=True),
|
||||
model.option(CONF_DRAW_ROUNDING, 2): power_of_two,
|
||||
model.option(CONF_PIXEL_MODE, PIXEL_MODE_16BIT): cv.one_of(
|
||||
@@ -140,11 +138,6 @@ def model_schema(config: ConfigType) -> cv.All:
|
||||
model.option(CONF_VSYNC_FRONT_PORCH): cv.int_,
|
||||
}
|
||||
)
|
||||
return cv.All(
|
||||
schema,
|
||||
cv.only_on_esp32,
|
||||
only_on_variant(supported=[VARIANT_ESP32P4]),
|
||||
)
|
||||
|
||||
|
||||
@model_schema_extractor(MODELS, model_schema)
|
||||
@@ -155,9 +148,13 @@ def _config_schema(config: ConfigType) -> ConfigType:
|
||||
},
|
||||
extra=cv.ALLOW_EXTRA,
|
||||
)(config)
|
||||
config = model_schema(config)(config)
|
||||
config = cv.All(cv.only_on_esp32, only_on_variant(supported=[VARIANT_ESP32P4]))(
|
||||
config
|
||||
)
|
||||
# Check before the schema, which would fail on default pins of a missing expander
|
||||
model = MODELS[config[CONF_MODEL].upper()]
|
||||
model.check_requirements()
|
||||
config = model_schema(config)(config)
|
||||
width, height, _offset_width, _offset_height, _pad_width, _pad_height = (
|
||||
model.get_dimensions(config)
|
||||
)
|
||||
|
||||
@@ -14,7 +14,7 @@ DsiDriverChip(
|
||||
pclk_frequency="60MHz",
|
||||
lane_bit_rate="730Mbps",
|
||||
color_order="RGB",
|
||||
requires={"psram", "pi4ioe5v6408"},
|
||||
requires={"psram"},
|
||||
reset_pin={"pi4ioe5v6408": {"address": 0x43}, "number": 4},
|
||||
initsequence=[
|
||||
(0xFF, 0x98, 0x81, 0x01), # Select Page 1
|
||||
@@ -69,7 +69,7 @@ TAB5_ST7123 = DsiDriverChip(
|
||||
pclk_frequency="80MHz",
|
||||
lane_bit_rate="960Mbps",
|
||||
color_order="RGB",
|
||||
requires={"psram", "pi4ioe5v6408"},
|
||||
requires={"psram"},
|
||||
reset_pin={"pi4ioe5v6408": {"address": 0x43}, "number": 4},
|
||||
initsequence=[
|
||||
(0x01,),
|
||||
@@ -122,7 +122,7 @@ DsiDriverChip(
|
||||
pclk_frequency="70MHz",
|
||||
lane_bit_rate="965Mbps",
|
||||
color_order="RGB",
|
||||
requires={"psram", "pi4ioe5v6408"},
|
||||
requires={"psram"},
|
||||
reset_pin={"pi4ioe5v6408": {"address": 0x43}, "number": 4},
|
||||
initsequence=[
|
||||
(0x01,),
|
||||
|
||||
@@ -17,7 +17,7 @@ DsiDriverChip(
|
||||
color_order="RGB",
|
||||
enable_pin=[{"xl9535": None, "number": 0}, {"xl9535": None, "number": 7}],
|
||||
reset_pin={"xl9535": None, "number": 2},
|
||||
requires={"psram", "xl9535"},
|
||||
requires={"psram"},
|
||||
initsequence=(
|
||||
(0xE0, 0x00),
|
||||
(0xE1, 0x93),
|
||||
|
||||
@@ -154,9 +154,7 @@ def model_schema(config: ConfigType) -> cv.Schema:
|
||||
cv_dimensions(CONF_DIMENSIONS): dimension_schema(
|
||||
model.get_default(CONF_DRAW_ROUNDING, 1)
|
||||
),
|
||||
model.option(CONF_ENABLE_PIN, cv.UNDEFINED): cv.ensure_list(
|
||||
pins.gpio_output_pin_schema
|
||||
),
|
||||
**display.enable_pin_schema(model, config),
|
||||
model.option(CONF_COLOR_ORDER, MODE_RGB): cv.enum(COLOR_ORDERS, upper=True),
|
||||
model.option(CONF_DRAW_ROUNDING, 2): power_of_two,
|
||||
model.option(CONF_PIXEL_MODE, PIXEL_MODE_16BIT): cv.one_of(
|
||||
@@ -226,14 +224,14 @@ def _config_schema(config: ConfigType) -> ConfigType:
|
||||
},
|
||||
extra=cv.ALLOW_EXTRA,
|
||||
)(config)
|
||||
schema = model_schema(config)
|
||||
config = cv.All(
|
||||
schema,
|
||||
cv.only_on_esp32,
|
||||
only_on_variant(supported=[VARIANT_ESP32S3, VARIANT_ESP32P4, VARIANT_ESP32S31]),
|
||||
)(config)
|
||||
# Check before the schema, which would fail on default pins of a missing expander
|
||||
model = MODELS[config[CONF_MODEL].upper()]
|
||||
model.check_requirements()
|
||||
config = model_schema(config)(config)
|
||||
width, height, _offset_width, _offset_height, _pad_width, _pad_height = (
|
||||
model.get_dimensions(config)
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ ST7701S(
|
||||
spi_mode="MODE3",
|
||||
cs_pin={"xl9535": None, "number": 17},
|
||||
reset_pin={"xl9535": None, "number": 5},
|
||||
requires={"psram", "xl9535"},
|
||||
requires={"psram"},
|
||||
hsync_pin=39,
|
||||
vsync_pin=40,
|
||||
pclk_pin=41,
|
||||
@@ -57,7 +57,7 @@ t_rgb = ST7701S(
|
||||
invert_colors=False,
|
||||
spi_mode="MODE3",
|
||||
cs_pin={"xl9535": None, "number": 3},
|
||||
requires={"psram", "xl9535"},
|
||||
requires={"psram"},
|
||||
de_pin=45,
|
||||
hsync_pin=47,
|
||||
vsync_pin=41,
|
||||
|
||||
@@ -115,7 +115,7 @@ st7701s.extend(
|
||||
vsync_pulse_width=8,
|
||||
vsync_back_porch=20,
|
||||
cs_pin={"pca9554": None, "number": 4},
|
||||
requires={"psram", "pca9554"},
|
||||
requires={"psram"},
|
||||
de_pin=18,
|
||||
hsync_pin=16,
|
||||
vsync_pin=17,
|
||||
|
||||
@@ -12,7 +12,7 @@ wave_4_3 = RgbDriverChip(
|
||||
pclk_frequency="16MHz",
|
||||
reset_pin={"ch422g": None, "number": 3},
|
||||
enable_pin={"ch422g": None, "number": 2},
|
||||
requires={"psram", "ch422g"},
|
||||
requires={"psram"},
|
||||
de_pin=5,
|
||||
hsync_pin={"number": 46, "ignore_strapping_warning": True},
|
||||
vsync_pin={"number": 3, "ignore_strapping_warning": True},
|
||||
|
||||
@@ -197,9 +197,7 @@ def model_schema(config: ConfigType) -> cv.All | cv.Schema:
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MipiSpi),
|
||||
cv_dimensions(CONF_DIMENSIONS): dimension_schema(1),
|
||||
model.option(CONF_ENABLE_PIN, cv.UNDEFINED): cv.ensure_list(
|
||||
pins.gpio_output_pin_schema
|
||||
),
|
||||
**display.enable_pin_schema(model, config),
|
||||
model.option(CONF_COLOR_ORDER, MODE_BGR): cv.enum(
|
||||
COLOR_ORDERS, upper=True
|
||||
),
|
||||
|
||||
@@ -22,7 +22,6 @@ DriverChip(
|
||||
transforms={CONF_MIRROR_X, CONF_MIRROR_Y},
|
||||
use_axis_flips=True,
|
||||
reset_delay=20,
|
||||
requires={"pca9554"},
|
||||
initsequence=(
|
||||
delay(100),
|
||||
(0xFF, 0x20, 0x10, 0x10),
|
||||
|
||||
@@ -124,6 +124,7 @@ from esphome.core import (
|
||||
)
|
||||
from esphome.core.config import UNIT_OF_MEASUREMENT_MAX_LENGTH
|
||||
from esphome.core.entity_helpers import (
|
||||
FollowEntity,
|
||||
SubEntities,
|
||||
entity_duplicate_validator,
|
||||
lazy_load_validator,
|
||||
@@ -1109,6 +1110,10 @@ def sub_sensors(
|
||||
return SubEntities(new_sensor, config, parent)
|
||||
|
||||
|
||||
# Makes a plain sensor follow another sensor: follow_sensor(entity, source)
|
||||
follow_sensor = FollowEntity("add_on_state_callback", ((cg.float_, "x"),), "x")
|
||||
|
||||
|
||||
SENSOR_IN_RANGE_CONDITION_SCHEMA = cv.All(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Sensor),
|
||||
|
||||
@@ -22,6 +22,7 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import (
|
||||
FollowEntity,
|
||||
SubEntities,
|
||||
entity_duplicate_validator,
|
||||
queue_entity_register,
|
||||
@@ -238,6 +239,12 @@ def sub_text_sensors(
|
||||
return SubEntities(new_text_sensor, config, parent)
|
||||
|
||||
|
||||
# Makes a plain text sensor follow another text sensor: follow_text_sensor(entity, source)
|
||||
follow_text_sensor = FollowEntity(
|
||||
"add_on_state_callback", ((cg.std_string_ref.operator("const"), "x"),), "x"
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.CORE)
|
||||
async def to_code(config):
|
||||
cg.add_global(text_sensor_ns.using)
|
||||
|
||||
@@ -1264,6 +1264,18 @@ class CoreFinalValidateStep(ConfigValidationStep):
|
||||
fv.full_config.reset(token)
|
||||
|
||||
|
||||
class OptionalPinValidationStep(ConfigValidationStep):
|
||||
"""Remove optional pins that are also used elsewhere"""
|
||||
|
||||
# After the ID pass, and before component final validations which may inspect pin use
|
||||
priority = -15.0
|
||||
|
||||
def run(self, result: Config) -> None:
|
||||
if result.errors:
|
||||
return
|
||||
pins.PIN_SCHEMA_REGISTRY.remove_optional_conflicts(result)
|
||||
|
||||
|
||||
class PinUseValidationCheck(ConfigValidationStep):
|
||||
"""Check for pin reuse"""
|
||||
|
||||
@@ -1437,6 +1449,7 @@ def validate_config(
|
||||
result.add_validation_step(PrefetchRemoteFilesValidationStep())
|
||||
result.add_validation_step(IDPassValidationStep())
|
||||
result.add_validation_step(CoreFinalValidateStep())
|
||||
result.add_validation_step(OptionalPinValidationStep())
|
||||
result.add_validation_step(PinUseValidationCheck())
|
||||
|
||||
result.add_validation_step(RemoveReferenceValidationStep())
|
||||
|
||||
@@ -416,10 +416,13 @@ void Application::teardown_components(uint32_t timeout_ms) {
|
||||
}
|
||||
}
|
||||
|
||||
void Application::add_looping_components_by_state_(bool match_loop_done) {
|
||||
void Application::add_looping_components_by_state_(bool inactive) {
|
||||
for (auto *obj : this->components_) {
|
||||
if (obj->has_overridden_loop() &&
|
||||
((obj->get_component_state() & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP_DONE) == match_loop_done) {
|
||||
if (!obj->has_overridden_loop()) {
|
||||
continue;
|
||||
}
|
||||
const uint8_t state = obj->get_component_state() & COMPONENT_STATE_MASK;
|
||||
if ((state == COMPONENT_STATE_LOOP_DONE || state == COMPONENT_STATE_FAILED) == inactive) {
|
||||
this->looping_components_.push_back(obj);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,16 +433,15 @@ class Application {
|
||||
// FixedVector capacity was pre-initialized by codegen with the exact count
|
||||
// of components that override loop(), computed at C++ compile time.
|
||||
|
||||
// Add all components with loop override that aren't already LOOP_DONE
|
||||
// Some components (like logger) may call disable_loop() during initialization
|
||||
// before setup runs, so we need to respect their LOOP_DONE state
|
||||
// Add all components with loop override that aren't already LOOP_DONE or FAILED
|
||||
// Some components (like logger) may call disable_loop() during initialization,
|
||||
// and some call mark_failed() from their constructor, before setup runs
|
||||
this->add_looping_components_by_state_(false);
|
||||
this->looping_components_active_end_ = this->looping_components_.size();
|
||||
// Then add any components that are already LOOP_DONE to the inactive section
|
||||
// This handles components that called disable_loop() during initialization
|
||||
// Then add any components that are already LOOP_DONE or FAILED to the inactive section
|
||||
this->add_looping_components_by_state_(true);
|
||||
}
|
||||
void add_looping_components_by_state_(bool match_loop_done);
|
||||
void add_looping_components_by_state_(bool inactive);
|
||||
|
||||
// These methods are called by Component::disable_loop() and Component::enable_loop()
|
||||
// Components should not call these directly - use this->disable_loop() or this->enable_loop()
|
||||
|
||||
@@ -169,7 +169,7 @@ struct Color {
|
||||
uint8_t r = rand >> 16;
|
||||
uint8_t g = rand >> 8;
|
||||
uint8_t b = rand >> 0;
|
||||
const uint16_t max_rgb = std::max({r, g, b});
|
||||
const uint16_t max_rgb = std::max({r, g, b, uint8_t(1)});
|
||||
return Color(uint8_t((uint16_t(r) * 255U / max_rgb)), uint8_t((uint16_t(g) * 255U / max_rgb)),
|
||||
uint8_t((uint16_t(b) * 255U / max_rgb)), w);
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ from esphome.core.config import (
|
||||
UNIT_OF_MEASUREMENT_MAX_LENGTH,
|
||||
)
|
||||
from esphome.cpp_generator import (
|
||||
LambdaExpression,
|
||||
MockObj,
|
||||
MockObjClass,
|
||||
RawExpression,
|
||||
@@ -519,6 +520,23 @@ class ApplyEntity:
|
||||
_APPLY_DOMAIN = "entity_apply"
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class FollowEntity:
|
||||
"""A state callback, its parameters and the value an entity republishes from it."""
|
||||
|
||||
callback: str
|
||||
params: tuple[tuple[SafeExpType, str], ...]
|
||||
value: str
|
||||
|
||||
def __call__(self, entity: MockObj, source: MockObj, *args: SafeExpType) -> None:
|
||||
"""Make ``entity`` publish every state of ``source``; ``args`` precede the callback."""
|
||||
publish = f"{parent_ref(entity).publish_state(RawExpression(self.value))};"
|
||||
callback = LambdaExpression(
|
||||
[publish], list(self.params), capture="", return_type=cg.void
|
||||
)
|
||||
add(getattr(source, self.callback)(*args, callback))
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class SubEntities:
|
||||
"""Create the child entity configured under key, set parent (if bound) and pass it to setter.
|
||||
|
||||
@@ -331,6 +331,8 @@ class FloatLiteral(Literal):
|
||||
def __str__(self):
|
||||
if math.isnan(self.f):
|
||||
return "NAN"
|
||||
if math.isinf(self.f):
|
||||
return "INFINITY" if self.f > 0 else "-INFINITY"
|
||||
return f"{self.f}f"
|
||||
|
||||
|
||||
|
||||
+7
-1
@@ -466,9 +466,15 @@ def receive_exactly(
|
||||
|
||||
while len(data) < amount:
|
||||
try:
|
||||
data += recv_decode(sock, amount - len(data), decode=decode) # type: ignore[operator]
|
||||
chunk = recv_decode(sock, amount - len(data), decode=decode)
|
||||
except OSError as err:
|
||||
raise OTANetworkError(f"receiving {msg}: {err}") from err
|
||||
if not chunk:
|
||||
raise OTANetworkError(
|
||||
f"receiving {msg}: device closed connection after "
|
||||
f"{len(data)} of {amount} bytes"
|
||||
)
|
||||
data += chunk # type: ignore[operator]
|
||||
return data
|
||||
|
||||
|
||||
|
||||
+108
-10
@@ -1,8 +1,10 @@
|
||||
from collections.abc import Callable, Hashable
|
||||
from contextvars import ContextVar
|
||||
from functools import reduce
|
||||
import logging
|
||||
from logging import Logger
|
||||
import operator
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -21,17 +23,64 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core import CORE, ID
|
||||
from esphome.cpp_generator import MockObj, MockObjClass
|
||||
from esphome.schema_extractors import schema_extractor
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
from esphome.types import ConfigType, SafeExpType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from esphome.config import Config
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# A use of a pin: the path of the component using it, the pin provider's id and the pin config
|
||||
PinUse = tuple[list, Any, ConfigType]
|
||||
|
||||
# Set while validating pins that should give way to any other use of the same pin
|
||||
_validating_optional = ContextVar("validating_optional_pins", default=False)
|
||||
|
||||
|
||||
def _remove_value(conf: Any, target: Any) -> bool:
|
||||
"""Remove the object ``target`` from wherever it is nested in ``conf``."""
|
||||
items = conf.items() if isinstance(conf, dict) else enumerate(conf)
|
||||
for key, value in items:
|
||||
if value is target:
|
||||
del conf[key]
|
||||
return True
|
||||
if isinstance(value, (dict, list)) and _remove_value(value, target):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class PinRegistry(dict):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.pins_used = {}
|
||||
# Entries of pins_used that give way to other uses, keyed by id()
|
||||
self.optional_pins = {}
|
||||
|
||||
def reset(self):
|
||||
self.pins_used = {}
|
||||
self.optional_pins = {}
|
||||
|
||||
def optional(self, validator: Callable[[Any], Any]) -> Callable[[Any], Any]:
|
||||
"""
|
||||
Wrap a pin validator so the pins it validates give way to any other use of the
|
||||
same pin. Instead of the pin being reported as used in multiple places,
|
||||
remove_optional_conflicts() removes it from the config, unless every other use
|
||||
sets allow_other_uses, in which case the pin is shared. Use this for pins a
|
||||
component supplies as defaults that a user may want to take over.
|
||||
"""
|
||||
|
||||
@schema_extractor("schema")
|
||||
def wrapper(value: Any) -> Any:
|
||||
if value == SCHEMA_EXTRACT:
|
||||
return validator
|
||||
token = _validating_optional.set(True)
|
||||
try:
|
||||
return validator(value)
|
||||
finally:
|
||||
_validating_optional.reset(token)
|
||||
|
||||
return wrapper
|
||||
|
||||
def get_count(self, key, id, number):
|
||||
"""
|
||||
@@ -86,7 +135,10 @@ class PinRegistry(dict):
|
||||
self.pins_used[pin_key] = []
|
||||
# client_id identifies the instance of the providing component
|
||||
client_id = result.get(key)
|
||||
self.pins_used[pin_key].append((path_context.get(), client_id, result))
|
||||
entry = (path_context.get(), client_id, result)
|
||||
self.pins_used[pin_key].append(entry)
|
||||
if _validating_optional.get():
|
||||
self.optional_pins[id(entry)] = entry
|
||||
# return the validated pin config
|
||||
return result
|
||||
|
||||
@@ -110,23 +162,69 @@ class PinRegistry(dict):
|
||||
key = self.get_key(key) if isinstance(key, dict) else key
|
||||
return self[key][0]
|
||||
|
||||
def final_validate(self, fconf):
|
||||
"""
|
||||
Run the final validation for all pins, and check for reuse
|
||||
:param fconf: The full config
|
||||
"""
|
||||
def _grouped(self) -> dict[tuple, list[PinUse]]:
|
||||
"""Group pin uses by (schema key, resolved provider id, pin number)."""
|
||||
# Regroup by (schema key, resolved provider id, pin number) rather than
|
||||
# the validate()-time bucket, so the same physical pin reached via an
|
||||
# explicit id and via {address: ...} (or an omitted id) is recognized as
|
||||
# one reuse group regardless of which syntax was used to reference the hub.
|
||||
grouped: dict[tuple, list] = {}
|
||||
grouped: dict[tuple, list[PinUse]] = {}
|
||||
for (key, _, number), pin_list in self.pins_used.items():
|
||||
for entry in pin_list:
|
||||
client_id = entry[1]
|
||||
provider_id = client_id.id if isinstance(client_id, ID) else client_id
|
||||
grouped.setdefault((key, provider_id, number), []).append(entry)
|
||||
return grouped
|
||||
|
||||
for (key, _, _), pin_list in grouped.items():
|
||||
def remove_optional_conflicts(self, fconf: "Config") -> None:
|
||||
"""
|
||||
Remove optional pins that are also used elsewhere from the config, or mark them
|
||||
as shared if every other use sets allow_other_uses. This runs once ids are resolved and before the components' final validation,
|
||||
so those only see the pins that will be used.
|
||||
:param fconf: The full config
|
||||
"""
|
||||
if not self.optional_pins:
|
||||
return
|
||||
for (key, provider_id, _), pin_list in self._grouped().items():
|
||||
optional = [e for e in pin_list if id(e) in self.optional_pins]
|
||||
others = [e for e in pin_list if id(e) not in self.optional_pins]
|
||||
if not optional or not others:
|
||||
continue
|
||||
if all(e[2].get(CONF_ALLOW_OTHER_USES, False) for e in others):
|
||||
# The other uses ask to share the pin, so share it with the optional ones too
|
||||
for _, _, pin_config in optional:
|
||||
pin_config[CONF_ALLOW_OTHER_USES] = True
|
||||
continue
|
||||
for entry in optional:
|
||||
self._remove_optional(fconf, key, provider_id, entry)
|
||||
|
||||
def _remove_optional(
|
||||
self, fconf: "Config", key: str, provider_id: str | None, entry: PinUse
|
||||
) -> None:
|
||||
"""
|
||||
Remove an optional pin from the config and from the pins used. If it cannot be
|
||||
found in the config it is kept, so final_validate() reports the conflict.
|
||||
"""
|
||||
pin_path, _, pin_config = entry
|
||||
if not _remove_value(fconf.get_config_for_path(pin_path), pin_config):
|
||||
return
|
||||
for pin_list in self.pins_used.values():
|
||||
pin_list[:] = [e for e in pin_list if e is not entry]
|
||||
del self.optional_pins[id(entry)]
|
||||
pin = f"{key} {provider_id} pin" if provider_id is not None else "Pin"
|
||||
_LOGGER.info(
|
||||
"%s %s is used elsewhere, so is not applied as a default for %s",
|
||||
pin,
|
||||
pin_config[CONF_NUMBER],
|
||||
"->".join(str(p) for p in pin_path),
|
||||
)
|
||||
|
||||
def final_validate(self, fconf):
|
||||
"""
|
||||
Run the final validation for all pins, and check for reuse
|
||||
:param fconf: The full config
|
||||
"""
|
||||
for (key, _, _), pin_list in self._grouped().items():
|
||||
count = len(pin_list) # number of places same pin used.
|
||||
final_val_fun = self[key][2] # final validation function
|
||||
for pin_path, client_id, pin_config in pin_list:
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32-s3-devkitc-1
|
||||
|
||||
psram:
|
||||
mode: octal
|
||||
|
||||
spi:
|
||||
clk_pin: GPIO7
|
||||
mosi_pin: GPIO9
|
||||
|
||||
display:
|
||||
- platform: epaper_spi
|
||||
id: epaper_display
|
||||
model: seeed-reterminal-sticky
|
||||
@@ -0,0 +1,23 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32-s3-devkitc-1
|
||||
|
||||
psram:
|
||||
mode: octal
|
||||
|
||||
spi:
|
||||
clk_pin: GPIO7
|
||||
mosi_pin: GPIO9
|
||||
|
||||
# Takes over the model's default enable pin
|
||||
switch:
|
||||
- platform: gpio
|
||||
id: panel_power
|
||||
pin: GPIO47
|
||||
|
||||
display:
|
||||
- platform: epaper_spi
|
||||
id: epaper_display
|
||||
model: seeed-reterminal-sticky
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Tests for applying the default enable pins of epaper_spi models."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
|
||||
def _enable_pin_numbers(main_cpp: str) -> list[int]:
|
||||
"""Get the GPIO numbers passed to set_enable_pins() in the generated code."""
|
||||
match = re.search(r"epaper_display->set_enable_pins\(\{(.*)\}\);", main_cpp)
|
||||
if match is None:
|
||||
return []
|
||||
numbers = []
|
||||
for pin_id in match.group(1).split(", "):
|
||||
pin = re.search(rf"{pin_id}->set_pin\(::GPIO_NUM_(\d+)\);", main_cpp)
|
||||
assert pin is not None
|
||||
numbers.append(int(pin.group(1)))
|
||||
return numbers
|
||||
|
||||
|
||||
def test_default_enable_pin(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""The model's enable pin is applied when it is not used elsewhere."""
|
||||
main_cpp = generate_main(component_config_path("enable_pin_default.yaml"))
|
||||
assert _enable_pin_numbers(main_cpp) == [47]
|
||||
|
||||
|
||||
def test_default_enable_pin_used_elsewhere(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""A default enable pin used by a switch is left out instead of failing validation."""
|
||||
main_cpp = generate_main(component_config_path("enable_pin_used_elsewhere.yaml"))
|
||||
assert _enable_pin_numbers(main_cpp) == []
|
||||
assert "panel_power->set_pin(" in main_cpp
|
||||
|
||||
|
||||
def test_user_enable_pins(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""Enable pins given in the config are applied as given."""
|
||||
main_cpp = generate_main(component_config_path("enable_pin_test.yaml"))
|
||||
assert _enable_pin_numbers(main_cpp) == [25, 26]
|
||||
@@ -0,0 +1,26 @@
|
||||
esphome:
|
||||
name: it8951-enable-pins
|
||||
|
||||
esp32:
|
||||
board: esp32-s3-devkitc-1
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
psram:
|
||||
mode: octal
|
||||
|
||||
spi:
|
||||
clk_pin: 7
|
||||
mosi_pin: 9
|
||||
miso_pin: 8
|
||||
|
||||
# Takes over the model's default 1.8V logic supply enable pin
|
||||
switch:
|
||||
- platform: gpio
|
||||
id: logic_supply
|
||||
pin: GPIO21
|
||||
|
||||
display:
|
||||
- platform: it8951
|
||||
id: epaper_display
|
||||
model: seeed-reterminal-e1003
|
||||
@@ -1,6 +1,8 @@
|
||||
"""Tests for it8951 configuration validation."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from esphome.components.const import CONF_HOLD_STATE
|
||||
@@ -26,3 +28,18 @@ def test_reterminal_e1003_enable_pins_hold_state(
|
||||
enable_pins = config[CONF_ENABLE_PIN]
|
||||
assert [pin[CONF_NUMBER] for pin in enable_pins] == [21, 11]
|
||||
assert all(pin[CONF_HOLD_STATE] for pin in enable_pins)
|
||||
|
||||
|
||||
def test_reterminal_e1003_enable_pin_used_elsewhere(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_fixture_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""A default enable pin used by a switch is left out, and the other is still applied."""
|
||||
main_cpp = generate_main(component_fixture_path("enable_pins.yaml"))
|
||||
|
||||
match = re.search(r"epaper_display->set_enable_pins\(\{(.*)\}\);", main_cpp)
|
||||
assert match is not None
|
||||
pin_ids = match.group(1).split(", ")
|
||||
assert len(pin_ids) == 1
|
||||
assert f"{pin_ids[0]}->set_pin(::GPIO_NUM_11);" in main_cpp
|
||||
assert "logic_supply->set_pin(" in main_cpp
|
||||
Whitespace-only changes.
@@ -0,0 +1,24 @@
|
||||
"""lightwaverf.send_raw never used name; it is dropped with a warning until 2027.5.0."""
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.automation import ACTION_REGISTRY
|
||||
import esphome.components.lightwaverf # noqa: F401
|
||||
from esphome.const import CONF_NAME, CONF_REPEAT
|
||||
|
||||
CODE = [0x04, 0x00, 0x00, 0x00, 0x0F, 0x03, 0x0D, 0x09, 0x08, 0x08]
|
||||
|
||||
|
||||
def test_name_is_dropped_with_a_warning(caplog: pytest.LogCaptureFixture) -> None:
|
||||
config = ACTION_REGISTRY["lightwaverf.send_raw"].schema(
|
||||
{"code": CODE, CONF_NAME: "Sofa"}
|
||||
)
|
||||
assert CONF_NAME not in config
|
||||
assert "'name' on lightwaverf.send_raw has no effect" in caplog.text
|
||||
assert "2027.5.0" in caplog.text
|
||||
|
||||
|
||||
def test_name_is_no_longer_required(caplog: pytest.LogCaptureFixture) -> None:
|
||||
config = ACTION_REGISTRY["lightwaverf.send_raw"].schema({"code": CODE})
|
||||
assert config[CONF_REPEAT] == 10
|
||||
assert "lightwaverf" not in caplog.text
|
||||
@@ -0,0 +1,50 @@
|
||||
esphome:
|
||||
name: test-canvas-draw-options
|
||||
on_boot:
|
||||
- lvgl.canvas.draw_rectangle:
|
||||
id: test_canvas
|
||||
x: 0
|
||||
y: 0
|
||||
width: 20
|
||||
height: 10
|
||||
bg_grad: test_grad
|
||||
- lvgl.canvas.draw_line:
|
||||
id: test_canvas
|
||||
dash_width: 4
|
||||
dash_gap: 2
|
||||
points:
|
||||
- 0,0
|
||||
- 10,10
|
||||
|
||||
esp32:
|
||||
board: lolin_c3_mini
|
||||
|
||||
spi:
|
||||
mosi_pin:
|
||||
number: GPIO2
|
||||
ignore_strapping_warning: true
|
||||
clk_pin: GPIO1
|
||||
|
||||
display:
|
||||
- platform: mipi_spi
|
||||
data_rate: 20MHz
|
||||
model: st7735
|
||||
cs_pin:
|
||||
number: GPIO8
|
||||
ignore_strapping_warning: true
|
||||
dc_pin: GPIO3
|
||||
|
||||
lvgl:
|
||||
gradients:
|
||||
- id: test_grad
|
||||
direction: hor
|
||||
stops:
|
||||
- color: 0xFF0000
|
||||
position: 0
|
||||
- color: 0x0000FF
|
||||
position: 255
|
||||
widgets:
|
||||
- canvas:
|
||||
id: test_canvas
|
||||
width: 32
|
||||
height: 32
|
||||
@@ -0,0 +1,58 @@
|
||||
esphome:
|
||||
name: test-indicator-update-opa
|
||||
on_boot:
|
||||
- lvgl.indicator.update:
|
||||
id: test_arc
|
||||
opa: 50%
|
||||
- lvgl.indicator.update:
|
||||
id: test_needle
|
||||
value: 5
|
||||
opa: 25%
|
||||
- lvgl.indicator.update:
|
||||
id: test_image
|
||||
opa: 75%
|
||||
|
||||
esp32:
|
||||
board: lolin_c3_mini
|
||||
|
||||
spi:
|
||||
mosi_pin:
|
||||
number: GPIO2
|
||||
ignore_strapping_warning: true
|
||||
clk_pin: GPIO1
|
||||
|
||||
display:
|
||||
- platform: mipi_spi
|
||||
data_rate: 20MHz
|
||||
model: st7735
|
||||
cs_pin:
|
||||
number: GPIO8
|
||||
ignore_strapping_warning: true
|
||||
dc_pin: GPIO3
|
||||
|
||||
image:
|
||||
- platform: file
|
||||
file: image.png
|
||||
id: meter_image
|
||||
resize: 8x8
|
||||
type: binary
|
||||
|
||||
lvgl:
|
||||
widgets:
|
||||
- meter:
|
||||
height: 100px
|
||||
width: 100px
|
||||
scales:
|
||||
- id: test_scale
|
||||
range_from: 0
|
||||
range_to: 10
|
||||
indicators:
|
||||
- arc:
|
||||
id: test_arc
|
||||
start_value: 0
|
||||
end_value: 5
|
||||
- line:
|
||||
id: test_needle
|
||||
- image:
|
||||
id: test_image
|
||||
src: meter_image
|
||||
@@ -0,0 +1,69 @@
|
||||
"""Canvas draw options must map onto the LVGL 9 draw descriptor fields."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.__main__ import generate_cpp_contents
|
||||
from esphome.automation import ACTION_REGISTRY
|
||||
import esphome.components.lvgl.widgets.canvas # noqa: F401 - registers the actions
|
||||
from esphome.config import read_config
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def main_cpp(request: pytest.FixtureRequest) -> str:
|
||||
config_path = (
|
||||
Path(request.fspath).parent / "config" / "canvas_draw_options_test.yaml"
|
||||
)
|
||||
original_path = CORE.config_path
|
||||
try:
|
||||
CORE.config_path = config_path
|
||||
CORE.config = read_config({})
|
||||
generate_cpp_contents(CORE.config)
|
||||
return CORE.cpp_main_section
|
||||
finally:
|
||||
CORE.config_path = original_path
|
||||
CORE.reset()
|
||||
|
||||
|
||||
def test_rect_gradient_is_copied_by_value(main_cpp: str) -> None:
|
||||
assert ".bg_grad = *test_grad;" in main_cpp
|
||||
|
||||
|
||||
def test_line_dash_fields(main_cpp: str) -> None:
|
||||
assert ".dash_width = 4;" in main_cpp
|
||||
assert ".dash_gap = 2;" in main_cpp
|
||||
|
||||
|
||||
def test_rect_opa_names_the_per_part_keys() -> None:
|
||||
with pytest.raises(cv.Invalid, match="bg_opa"):
|
||||
ACTION_REGISTRY["lvgl.canvas.draw_rectangle"].schema(
|
||||
{"id": "cv", "x": 0, "y": 0, "width": 10, "height": 10, "opa": "50%"}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"key", ["opa", "bg_color", "bg_grad", "radius", "shadow_width"]
|
||||
)
|
||||
def test_polygon_rejects_non_border_keys(key: str) -> None:
|
||||
with pytest.raises(cv.Invalid, match="drawn as an outline"):
|
||||
ACTION_REGISTRY["lvgl.canvas.draw_polygon"].schema(
|
||||
{"id": "cv", "points": [{"x": 0, "y": 0}], key: 1}
|
||||
)
|
||||
|
||||
|
||||
def test_polygon_accepts_border_keys() -> None:
|
||||
config = ACTION_REGISTRY["lvgl.canvas.draw_polygon"].schema(
|
||||
{
|
||||
"id": "cv",
|
||||
"points": [{"x": 0, "y": 0}],
|
||||
"border_color": 0xFF0000,
|
||||
"border_width": 2,
|
||||
"border_opa": "50%",
|
||||
}
|
||||
)
|
||||
assert config["border_width"] == 2
|
||||
@@ -0,0 +1,49 @@
|
||||
"""lvgl.indicator.update must apply opa to arc, needle and image indicators."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.__main__ import generate_cpp_contents
|
||||
from esphome.config import read_config
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def main_cpp(request: pytest.FixtureRequest) -> str:
|
||||
config_path = (
|
||||
Path(request.fspath).parent / "config" / "indicator_update_opa_test.yaml"
|
||||
)
|
||||
original_path = CORE.config_path
|
||||
try:
|
||||
CORE.config_path = config_path
|
||||
CORE.config = read_config({})
|
||||
generate_cpp_contents(CORE.config)
|
||||
return CORE.cpp_main_section
|
||||
finally:
|
||||
CORE.config_path = original_path
|
||||
CORE.reset()
|
||||
|
||||
|
||||
def test_arc_section_opa_is_applied(main_cpp: str) -> None:
|
||||
assert (
|
||||
"lv_style_set_arc_opa(meter_arc_test_arc_style, static_cast<uint8_t>(127.5f));"
|
||||
in main_cpp
|
||||
)
|
||||
assert "lv_obj_invalidate(test_scale);" in main_cpp
|
||||
|
||||
|
||||
def test_needle_opa_is_applied(main_cpp: str) -> None:
|
||||
assert (
|
||||
"lv_obj_set_style_opa(test_needle->obj, static_cast<uint8_t>(63.75f), LV_PART_MAIN);"
|
||||
in main_cpp
|
||||
)
|
||||
|
||||
|
||||
def test_image_opa_is_applied(main_cpp: str) -> None:
|
||||
assert (
|
||||
"lv_obj_set_style_opa(test_image, static_cast<uint8_t>(191.25f), LV_PART_MAIN);"
|
||||
in main_cpp
|
||||
)
|
||||
@@ -0,0 +1,38 @@
|
||||
esphome:
|
||||
name: enable-pins-address
|
||||
|
||||
esp32:
|
||||
board: esp32-p4-evboard
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
psram:
|
||||
speed: 200MHz
|
||||
|
||||
esp_ldo:
|
||||
- channel: 3
|
||||
voltage: 2.5V
|
||||
|
||||
i2c:
|
||||
sda: GPIO7
|
||||
scl: GPIO8
|
||||
|
||||
pi4ioe5v6408:
|
||||
- id: pi4ioe1
|
||||
address: 0x43
|
||||
- id: pi4ioe2
|
||||
address: 0x44
|
||||
|
||||
# Takes over the default enable pin on the hub at 0x44, given by address
|
||||
switch:
|
||||
- platform: gpio
|
||||
id: takeover
|
||||
pin:
|
||||
pi4ioe5v6408:
|
||||
address: 0x44
|
||||
number: 1
|
||||
|
||||
display:
|
||||
- platform: mipi_dsi
|
||||
id: default_pins
|
||||
model: TEST-ENABLE-BY-ADDRESS
|
||||
@@ -0,0 +1,40 @@
|
||||
"""Tests for applying the default enable pins of mipi_dsi models."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components.mipi import DriverChip
|
||||
|
||||
|
||||
def test_default_enable_pins_by_address(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_fixture_path: Callable[[str], Path],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""Default enable pins naming a hub by address resolve to that hub, and only
|
||||
the pin used elsewhere is left out, not the same pin number on the other hub."""
|
||||
from esphome.components.mipi_dsi.display import MODELS
|
||||
|
||||
models = set(DriverChip.models)
|
||||
model = MODELS["M5STACK-TAB5"].extend(
|
||||
"TEST-ENABLE-BY-ADDRESS",
|
||||
enable_pin=[
|
||||
{"pi4ioe5v6408": {"address": 0x43}, "number": 1},
|
||||
{"pi4ioe5v6408": {"address": 0x44}, "number": 1},
|
||||
],
|
||||
)
|
||||
for name in set(DriverChip.models) - models:
|
||||
monkeypatch.delitem(DriverChip.models, name)
|
||||
monkeypatch.setitem(MODELS, model.name, model)
|
||||
|
||||
main_cpp = generate_main(component_fixture_path("enable_pins_address.yaml"))
|
||||
|
||||
match = re.search(r"default_pins->set_enable_pins\(\{(.*)\}\);", main_cpp)
|
||||
assert match is not None
|
||||
pin_ids = match.group(1).split(", ")
|
||||
assert len(pin_ids) == 1
|
||||
assert f"{pin_ids[0]}->set_parent(pi4ioe1);" in main_cpp
|
||||
assert f"{pin_ids[0]}->set_pin(1);" in main_cpp
|
||||
@@ -8,7 +8,7 @@ import re
|
||||
import pytest
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components.esp32 import KEY_BOARD, VARIANT_ESP32P4
|
||||
from esphome.components.esp32 import KEY_BOARD, VARIANT_ESP32P4, VARIANT_ESP32S3
|
||||
|
||||
# Importing the I/O expanders registers their pin schemas with
|
||||
# pins.PIN_SCHEMA_REGISTRY so that models whose defaults reference expander-backed
|
||||
@@ -215,3 +215,20 @@ def test_code_generation(
|
||||
assert "p4_86->set_rotation(display::DISPLAY_ROTATION_0_DEGREES);" not in main_cpp
|
||||
assert "custom_id->set_rotation(display::DISPLAY_ROTATION_180_DEGREES);" in main_cpp
|
||||
# assert "backlight_id = new light::LightState(mipi_dsi_dsibacklight_id);" in main_cpp
|
||||
|
||||
|
||||
def test_unsupported_variant_reported_before_requirements(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
) -> None:
|
||||
"""On an unsupported chip the variant error is shown, not the missing components."""
|
||||
from esphome.components.mipi_dsi.display import CONFIG_SCHEMA
|
||||
from esphome.core import CORE
|
||||
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32-s3-devkitc-1", KEY_VARIANT: VARIANT_ESP32S3},
|
||||
)
|
||||
CORE.raw_config = {}
|
||||
|
||||
with pytest.raises(cv.Invalid, match=r"This feature is only available on ESP32P4"):
|
||||
CONFIG_SCHEMA({"model": "SEEED-RETERMINAL-D1001"})
|
||||
@@ -0,0 +1,32 @@
|
||||
esphome:
|
||||
name: enable-pins-test
|
||||
|
||||
esp32:
|
||||
board: esp32-s3-devkitc-1
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
psram:
|
||||
mode: octal
|
||||
speed: 80MHz
|
||||
|
||||
i2c:
|
||||
sda: 8
|
||||
scl: 9
|
||||
|
||||
ch422g:
|
||||
- id: ch422g_hub
|
||||
|
||||
# Takes over the model's default backlight enable pin
|
||||
switch:
|
||||
- platform: gpio
|
||||
id: backlight
|
||||
pin:
|
||||
ch422g: ch422g_hub
|
||||
number: 2
|
||||
|
||||
display:
|
||||
- platform: mipi_rgb
|
||||
id: default_pins
|
||||
model: ESP32-S3-TOUCH-LCD-7-800X480
|
||||
update_interval: never
|
||||
@@ -0,0 +1,24 @@
|
||||
esphome:
|
||||
name: enable-pins-test
|
||||
|
||||
esp32:
|
||||
board: esp32-s3-devkitc-1
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
psram:
|
||||
mode: octal
|
||||
speed: 80MHz
|
||||
|
||||
i2c:
|
||||
sda: 8
|
||||
scl: 9
|
||||
|
||||
ch422g:
|
||||
- id: ch422g_hub
|
||||
|
||||
display:
|
||||
- platform: mipi_rgb
|
||||
id: default_pins
|
||||
model: ESP32-S3-TOUCH-LCD-7-800X480
|
||||
update_interval: never
|
||||
@@ -0,0 +1,36 @@
|
||||
"""Tests for applying the default enable pins of mipi_rgb models."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
|
||||
def _enable_pin_numbers(main_cpp: str) -> list[int]:
|
||||
"""Get the expander pin numbers passed to set_enable_pins() in the generated code."""
|
||||
match = re.search(r"default_pins->set_enable_pins\(\{(.*)\}\);", main_cpp)
|
||||
assert match is not None
|
||||
numbers = []
|
||||
for pin_id in match.group(1).split(", "):
|
||||
pin = re.search(rf"{pin_id}->set_pin\((\d+)\);", main_cpp)
|
||||
assert pin is not None
|
||||
numbers.append(int(pin.group(1)))
|
||||
return numbers
|
||||
|
||||
|
||||
def test_default_enable_pins(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_fixture_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""All of the model's enable pins are applied when none are used elsewhere."""
|
||||
main_cpp = generate_main(component_fixture_path("enable_pins_default.yaml"))
|
||||
assert _enable_pin_numbers(main_cpp) == [2, 6]
|
||||
|
||||
|
||||
def test_default_enable_pin_used_elsewhere(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_fixture_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""A default enable pin used by a switch is left out instead of failing validation."""
|
||||
main_cpp = generate_main(component_fixture_path("enable_pins.yaml"))
|
||||
assert _enable_pin_numbers(main_cpp) == [6]
|
||||
assert "backlight->set_pin(" in main_cpp
|
||||
@@ -8,7 +8,12 @@ from typing import Any
|
||||
import pytest
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components.esp32 import KEY_BOARD, KEY_VARIANT, VARIANT_ESP32S3
|
||||
from esphome.components.esp32 import (
|
||||
KEY_BOARD,
|
||||
KEY_VARIANT,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32S3,
|
||||
)
|
||||
from esphome.components.mipi_rgb.display import CONFIG_SCHEMA, FINAL_VALIDATE_SCHEMA
|
||||
|
||||
# Importing pca9554 registers its pin schema with pins.PIN_SCHEMA_REGISTRY so that
|
||||
@@ -87,3 +92,78 @@ def test_model_requires_psram_and_expander(
|
||||
"spi_id": "spi_bus",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_model_requires_expander_when_pins_overridden(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
set_component_config: Any,
|
||||
) -> None:
|
||||
"""A model with default pins on an expander requires it even when the user config
|
||||
overrides all of those pins, and without the expander being listed in ``requires``."""
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32-s3-devkitc-1", KEY_VARIANT: VARIANT_ESP32S3},
|
||||
)
|
||||
set_component_config("psram", True)
|
||||
CORE.raw_config = {"psram": True}
|
||||
|
||||
with pytest.raises(
|
||||
cv.Invalid,
|
||||
match=r"ESP32-S3-TOUCH-LCD-7-800X480 requires component 'ch422g' to be configured",
|
||||
):
|
||||
_validated(
|
||||
{
|
||||
"model": "ESP32-S3-TOUCH-LCD-7-800X480",
|
||||
"reset_pin": 10,
|
||||
"enable_pin": [11, 12],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_model_requires_expander_with_default_pins(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
set_component_config: Any,
|
||||
) -> None:
|
||||
"""The missing expander is reported before the model's default pins on it are validated."""
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32-s3-devkitc-1", KEY_VARIANT: VARIANT_ESP32S3},
|
||||
)
|
||||
set_component_config("psram", True)
|
||||
CORE.raw_config = {"psram": True}
|
||||
|
||||
with pytest.raises(
|
||||
cv.Invalid,
|
||||
match=r"ESP32-S3-TOUCH-LCD-7-800X480 requires component 'ch422g' to be configured",
|
||||
):
|
||||
_validated({"model": "ESP32-S3-TOUCH-LCD-7-800X480"})
|
||||
|
||||
|
||||
def test_unsupported_variant_reported_before_requirements(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
) -> None:
|
||||
"""On an unsupported chip the variant error is shown, not the missing components."""
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32-c3-devkitm-1", KEY_VARIANT: VARIANT_ESP32C3},
|
||||
)
|
||||
CORE.raw_config = {}
|
||||
|
||||
with pytest.raises(
|
||||
cv.Invalid,
|
||||
match=r"This feature is only available on ESP32S3, ESP32P4, ESP32S31",
|
||||
):
|
||||
_validated({"model": "ESP32-S3-TOUCH-LCD-7-800X480"})
|
||||
|
||||
|
||||
def test_expander_that_fails_to_load_is_still_required(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""An expander component that exists but fails to load is still counted as required."""
|
||||
from esphome import loader, pins
|
||||
from esphome.components import mipi
|
||||
|
||||
monkeypatch.setattr(pins, "PIN_SCHEMA_REGISTRY", pins.PinRegistry())
|
||||
monkeypatch.setattr(loader, "get_component", lambda key: None)
|
||||
defaults = {"enable_pin": {"xl9535": "hub", "number": 1, "mode": "output"}}
|
||||
assert mipi._pin_expanders(defaults) == {"xl9535"}
|
||||
@@ -36,6 +36,30 @@ esphome:
|
||||
service: notify.html5
|
||||
data:
|
||||
message: Button was pressed
|
||||
- homeassistant.service:
|
||||
service: !lambda 'return std::string("notify.html5");'
|
||||
data:
|
||||
title: Title
|
||||
data_template:
|
||||
message: The value is {{ my_value }}
|
||||
variables:
|
||||
my_value: !lambda 'return std::string("42");'
|
||||
capture_response: true
|
||||
response_template: !lambda 'return std::string("{{ response.value }}");'
|
||||
on_success:
|
||||
- logger.log: "service succeeded"
|
||||
on_error:
|
||||
- logger.log:
|
||||
format: "service failed: %s"
|
||||
args: [error.c_str()]
|
||||
- homeassistant.action:
|
||||
action: weather.get_forecasts
|
||||
data:
|
||||
entity_id: weather.forecast_home
|
||||
capture_response: true
|
||||
response_template: !lambda 'return std::string("{{ response }}");'
|
||||
on_success:
|
||||
- logger.log: "forecast received"
|
||||
- homeassistant.tag_scanned: pulse
|
||||
- homeassistant.action:
|
||||
action: weather.get_forecasts
|
||||
@@ -307,6 +331,13 @@ api:
|
||||
success: false
|
||||
error_message: !lambda 'return error_msg;'
|
||||
|
||||
- action: test_respond_lambda_success
|
||||
variables:
|
||||
ok: bool
|
||||
then:
|
||||
- api.respond:
|
||||
success: !lambda 'return ok;'
|
||||
|
||||
# ==========================================================================
|
||||
# supports_response: optional (auto-detected - api.respond with data)
|
||||
# Has call_id and return_response - client decides if it wants response
|
||||
|
||||
@@ -21,6 +21,14 @@ esphome:
|
||||
id: waveradar
|
||||
frequency: 5800MHz
|
||||
power_consumption: 91uA
|
||||
- at581x.settings:
|
||||
id: waveradar
|
||||
hw_frontend_reset: !lambda return true;
|
||||
sensing_distance: !lambda return 300;
|
||||
protect_time: !lambda return 1500;
|
||||
trigger_base: !lambda return 600;
|
||||
trigger_keep: !lambda return 12000;
|
||||
stage_gain: !lambda return 4;
|
||||
- at581x.reset:
|
||||
id: waveradar
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ esphome:
|
||||
- bk72xx_ble_tracker.start_scan
|
||||
- bk72xx_ble_tracker.start_scan:
|
||||
continuous: true
|
||||
- bk72xx_ble_tracker.start_scan:
|
||||
continuous: !lambda return false;
|
||||
- bk72xx_ble_tracker.stop_scan
|
||||
- bk72xx_ble_tracker.stop_scan: ble_tracker
|
||||
|
||||
|
||||
@@ -109,3 +109,10 @@ button:
|
||||
- logger.log: Level calibration failed
|
||||
- motion.calibrate_heading:
|
||||
id: bmi270_motion
|
||||
- motion.calibrate_heading:
|
||||
id: bmi270_motion
|
||||
save: true
|
||||
on_success:
|
||||
- logger.log: Heading calibrated
|
||||
on_error:
|
||||
- logger.log: Heading calibration failed
|
||||
@@ -9,6 +9,49 @@ esphome:
|
||||
id: bq25186_charger
|
||||
battery_regulation_voltage: 4.1V
|
||||
fast_charge_current: 200mA
|
||||
- bq25186.apply_configuration:
|
||||
id: bq25186_charger
|
||||
charge_enabled: false
|
||||
flash_charging_mode: true
|
||||
precharge_is_iterm: true
|
||||
termination_percent: 5%
|
||||
vindpm_mode: 4.7v
|
||||
thermal_regulation: 80c
|
||||
battery_ocp_limit: 1500ma
|
||||
battery_uvlo: 3.0v
|
||||
mask_charge_status_interrupt: false
|
||||
mask_ilim_interrupt: false
|
||||
mask_vindpm_interrupt: true
|
||||
ts_auto_function: false
|
||||
vlowv: 2.8v
|
||||
recharge_threshold: 200mv
|
||||
double_timer_during_dpm: true
|
||||
safety_timer: 12h
|
||||
watchdog: 160s_hardware_reset
|
||||
long_press_duration: 15s
|
||||
hw_reset_requires_vin: true
|
||||
autowake: 2s
|
||||
input_current_limit: 200ma
|
||||
- bq25186.apply_configuration:
|
||||
id: bq25186_charger
|
||||
push_button_long_press_action: shutdown_mode
|
||||
wake1_timer: 1s
|
||||
wake2_timer: 3s
|
||||
enable_push_button: false
|
||||
system_regulation: 4.6v
|
||||
sys_mode: battery_only
|
||||
watchdog_15s_enable: false
|
||||
disable_vdppm: true
|
||||
ts_hot: 65c
|
||||
ts_cold: 3c
|
||||
ts_warm_disable: true
|
||||
ts_cool_disable: true
|
||||
ts_ichg: 0.2x
|
||||
ts_vrcg: -200mv
|
||||
mask_ts_interrupt: true
|
||||
mask_treg_interrupt: false
|
||||
mask_bat_interrupt: true
|
||||
mask_pg_interrupt: true
|
||||
|
||||
bq25186:
|
||||
- id: bq25186_charger
|
||||
|
||||
@@ -14,6 +14,9 @@ button:
|
||||
id: bms
|
||||
address: 0x09
|
||||
- bq76972.program_i2c_address: !lambda "return 0x08;"
|
||||
- bq76972.program_i2c_address:
|
||||
id: bms
|
||||
address: !lambda return 0x0A;
|
||||
|
||||
sensor:
|
||||
- platform: bq76972
|
||||
|
||||
@@ -69,3 +69,7 @@ button:
|
||||
- canbus.send: !lambda |-
|
||||
uint8_t val = (uint8_t)id(canbus_test_number).state;
|
||||
return std::vector<uint8_t>{0xAA, val, 0xBB};
|
||||
- canbus.send:
|
||||
can_id: 0x123
|
||||
remote_transmission_request: true
|
||||
data: []
|
||||
@@ -5,6 +5,9 @@ esphome:
|
||||
id: counter_sensor
|
||||
value: 100
|
||||
- counter.increment: counter_sensor
|
||||
- counter.set_value:
|
||||
id: counter_sensor
|
||||
value: !lambda return 42LL;
|
||||
- counter.increment:
|
||||
id: counter_sensor
|
||||
value: -5
|
||||
|
||||
@@ -14,6 +14,8 @@ esphome:
|
||||
then:
|
||||
- deep_sleep.enter:
|
||||
sleep_duration: 60min
|
||||
- deep_sleep.enter:
|
||||
sleep_duration: !lambda return 60000;
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return deep_sleep::woke_from_deep_sleep();'
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
# (the default on ESP-IDF). Entering deep sleep runs the safe shutdown hooks,
|
||||
# where safe_mode confirms the running app image so the bootloader does not
|
||||
# roll back a fresh OTA update when the device goes to sleep before
|
||||
# boot_is_good_after has elapsed.
|
||||
# boot_is_good_after has elapsed. The network this needs also serves the
|
||||
# deep_sleep.enter until/time_id case, which requires a time source.
|
||||
substitutions:
|
||||
wakeup_pin: GPIO4
|
||||
|
||||
@@ -17,3 +18,16 @@ wifi:
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: "superlongpasswordthatnoonewillknow"
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: Sleep until
|
||||
on_press:
|
||||
- deep_sleep.enter:
|
||||
id: deep_sleep_1
|
||||
until: "06:30:00"
|
||||
time_id: sntp_time
|
||||
@@ -27,6 +27,12 @@ esphome:
|
||||
- dfplayer.set_volume: !lambda "return id(dfplayer_test).is_playing() ? 10 : 5;"
|
||||
- dfplayer.set_eq: ROCK
|
||||
- dfplayer.set_eq: !lambda "return id(dfplayer_test).is_playing() ? dfplayer::JAZZ : dfplayer::BASS;"
|
||||
- dfplayer.play_mp3:
|
||||
file: 7
|
||||
- dfplayer.set_volume:
|
||||
volume: 12
|
||||
- dfplayer.set_eq:
|
||||
eq_preset: CLASSIC
|
||||
- dfplayer.play_next
|
||||
- dfplayer.play_previous
|
||||
- dfplayer.reset
|
||||
|
||||
@@ -12,6 +12,10 @@ esphome:
|
||||
delay_after_detect: 0s
|
||||
delay_after_disappear: 0s
|
||||
sensitivity: 6
|
||||
- dfrobot_sen0395.settings:
|
||||
id: mmwave
|
||||
factory_reset: !lambda return false;
|
||||
sensitivity: !lambda return 7;
|
||||
- dfrobot_sen0395.reset
|
||||
|
||||
dfrobot_sen0395:
|
||||
|
||||
@@ -52,6 +52,36 @@ button:
|
||||
minute: !lambda return 45;
|
||||
- ds3231.set_alarm_2:
|
||||
mode: every_minute
|
||||
- ds3231.set_alarm_1:
|
||||
mode: weekly
|
||||
day_of_week: 3
|
||||
hour: !lambda return 8;
|
||||
second: !lambda return 30;
|
||||
enabled: !lambda return true;
|
||||
- ds3231.set_alarm_1:
|
||||
mode: monthly
|
||||
day_of_month: !lambda return 20;
|
||||
minute: !lambda return 10;
|
||||
- ds3231.set_alarm_2:
|
||||
mode: monthly
|
||||
day_of_month: 10
|
||||
enabled: false
|
||||
- ds3231.set_alarm_2:
|
||||
mode: weekly
|
||||
day_of_week: !lambda return 5;
|
||||
hour: !lambda return 18;
|
||||
- ds3231.enable_alarm:
|
||||
alarm: !lambda return 2;
|
||||
- ds3231.disable_alarm:
|
||||
alarm: !lambda return 1;
|
||||
- if:
|
||||
condition:
|
||||
ds3231.is_alarm_triggered:
|
||||
alarm: !lambda return 1;
|
||||
then:
|
||||
- logger.log: alarm 1 triggered
|
||||
- ds3231.set_32khz_output:
|
||||
state: !lambda return false;
|
||||
- ds3231.enable_alarm:
|
||||
alarm: 1
|
||||
- ds3231.disable_alarm:
|
||||
|
||||
@@ -3,6 +3,8 @@ esphome:
|
||||
then:
|
||||
- audio_adc.set_mic_gain: 0db
|
||||
- audio_adc.set_mic_gain: !lambda 'return 4;'
|
||||
- audio_adc.set_mic_gain:
|
||||
mic_gain: 6db
|
||||
|
||||
audio_adc:
|
||||
- platform: es7210
|
||||
|
||||
@@ -35,6 +35,18 @@ espnow:
|
||||
data: [0x01, 0x02, 0x03, 0x04, 0x05]
|
||||
- espnow.broadcast:
|
||||
data: !lambda 'return {0x01, 0x02, 0x03, 0x04, 0x05};'
|
||||
- espnow.broadcast:
|
||||
address: FF:FF:FF:FF:FF:FF
|
||||
data: "options"
|
||||
wait_for_sent: true
|
||||
continue_on_error: false
|
||||
on_sent:
|
||||
- logger.log: "broadcast sent"
|
||||
on_error:
|
||||
- logger.log: "broadcast failed"
|
||||
- espnow.send:
|
||||
address: !lambda 'return {0x11, 0x22, 0x33, 0x44, 0x55, 0x66};'
|
||||
data: "lambda address"
|
||||
- espnow.send:
|
||||
address: 11:22:33:44:55:66
|
||||
data: !lambda 'return std::vector<uint8_t>(data, data + size);'
|
||||
|
||||
@@ -46,6 +46,13 @@ text_sensor:
|
||||
- ezo_pmp.set_calibration_volume:
|
||||
id: hcl_pump
|
||||
volume: 10
|
||||
- ezo_pmp.dose_with_constant_flow_rate:
|
||||
id: hcl_pump
|
||||
volume_per_minute: !lambda return 5.5f;
|
||||
duration: !lambda return 3;
|
||||
- ezo_pmp.set_calibration_volume:
|
||||
id: hcl_pump
|
||||
volume: !lambda return 12.5f;
|
||||
- ezo_pmp.find: hcl_pump
|
||||
- ezo_pmp.dose_continuously: hcl_pump
|
||||
- ezo_pmp.clear_total_volume_dosed: hcl_pump
|
||||
|
||||
@@ -14,3 +14,10 @@ button:
|
||||
speed: 200
|
||||
color: BLUE
|
||||
count: 2
|
||||
- fingerprint_grow.aura_led_control:
|
||||
state: !lambda return fingerprint_grow::FLASHING;
|
||||
speed: !lambda return 100;
|
||||
color: !lambda return fingerprint_grow::RED;
|
||||
count: !lambda return 3;
|
||||
- fingerprint_grow.delete:
|
||||
finger_id: !lambda return 2;
|
||||
@@ -9,6 +9,9 @@ hdmi_cec:
|
||||
- hdmi_cec.send:
|
||||
destination: !lambda return source;
|
||||
data: [0x90, 0x01] # Report Power Status: standby
|
||||
- hdmi_cec.send:
|
||||
destination: 0x0
|
||||
data: !lambda return {0x90, 0x01};
|
||||
- opcode: 0x8C # Give Device Vendor ID
|
||||
then:
|
||||
- lambda: |-
|
||||
|
||||
@@ -5,6 +5,8 @@ esphome:
|
||||
name: "Test"
|
||||
direction: 1
|
||||
- hlk_fm22x.delete: 5
|
||||
- hlk_fm22x.delete:
|
||||
face_id: 7
|
||||
- hlk_fm22x.delete_all:
|
||||
- hlk_fm22x.reset:
|
||||
- hlk_fm22x.scan:
|
||||
|
||||
@@ -35,6 +35,59 @@ esphome:
|
||||
- http_request.get:
|
||||
url: https://esphome.io
|
||||
timeout: !lambda "return 10000;"
|
||||
- http_request.get:
|
||||
url: !lambda 'return std::string("https://esphome.io/get");'
|
||||
method: GET
|
||||
collect_headers:
|
||||
- Content-Type
|
||||
- X-Request-Id
|
||||
max_response_buffer_size: 2kB
|
||||
capture_response: true
|
||||
on_response:
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Content-Type %s, body %zu bytes"
|
||||
args:
|
||||
- response->get_response_header("content-type").c_str()
|
||||
- body.size()
|
||||
- http_request.post:
|
||||
url: !lambda 'return std::string("https://esphome.io/post");'
|
||||
method: POST
|
||||
body: !lambda 'return std::string("{\"key\": \"value\"}");'
|
||||
timeout: 5s
|
||||
collect_headers:
|
||||
- Location
|
||||
max_response_buffer_size: 512
|
||||
on_error:
|
||||
- logger.log: "POST failed"
|
||||
- http_request.post:
|
||||
url: https://esphome.io
|
||||
body: "plain body"
|
||||
timeout: !lambda "return 2500;"
|
||||
- http_request.send:
|
||||
method: PATCH
|
||||
url: !lambda 'return std::string("https://esphome.io/send");'
|
||||
json:
|
||||
key: value
|
||||
dynamic: !lambda 'return std::string("x");'
|
||||
collect_headers:
|
||||
- ETag
|
||||
max_response_buffer_size: 4kB
|
||||
capture_response: true
|
||||
timeout: !lambda "return 3000;"
|
||||
on_response:
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Send status %d, body %s"
|
||||
args:
|
||||
- response->status_code
|
||||
- body.c_str()
|
||||
on_error:
|
||||
- logger.log: "Send failed"
|
||||
- http_request.send:
|
||||
method: DELETE
|
||||
url: https://esphome.io/item
|
||||
body: !lambda 'return std::string("id=1");'
|
||||
- http_request.post:
|
||||
url: https://esphome.io
|
||||
request_headers:
|
||||
|
||||
@@ -21,3 +21,9 @@ button:
|
||||
- htu21d.set_heater_level:
|
||||
id: htu21d_sensor
|
||||
level: 5
|
||||
- htu21d.set_heater:
|
||||
id: htu21d_sensor
|
||||
status: !lambda return false;
|
||||
- htu21d.set_heater_level:
|
||||
id: htu21d_sensor
|
||||
level: !lambda return 3;
|
||||
@@ -10,3 +10,6 @@ esphome:
|
||||
- hub75.set_brightness:
|
||||
id: my_hub75
|
||||
brightness: 50
|
||||
- hub75.set_brightness:
|
||||
id: my_hub75
|
||||
brightness: !lambda return 150;
|
||||
@@ -108,3 +108,6 @@ interval:
|
||||
id: m5epd_display
|
||||
mode: A2
|
||||
- it8951.update: m5epd_display
|
||||
- it8951.update:
|
||||
id: m5epd_display
|
||||
mode: !lambda return it8951::UPDATE_MODE_GL16;
|
||||
@@ -155,6 +155,33 @@ esphome:
|
||||
- light.turn_off:
|
||||
id: test_rgb_light
|
||||
transition_length: 300ms
|
||||
- light.turn_off:
|
||||
id: test_rgb_light
|
||||
state: false
|
||||
transition_length: !lambda return 200;
|
||||
- light.turn_on:
|
||||
id: test_rgb_light
|
||||
red: !lambda return 0.2f;
|
||||
green: !lambda return 0.4f;
|
||||
blue: !lambda return 0.6f;
|
||||
transition_length: !lambda return 350;
|
||||
- light.turn_on:
|
||||
id: test_cwww_light
|
||||
color_temperature: 300 mireds
|
||||
- light.turn_on:
|
||||
id: test_cwww_light
|
||||
color_temperature: !lambda return 250.0f;
|
||||
- light.control:
|
||||
id: test_rgbww_light
|
||||
cold_white: !lambda return 0.6f;
|
||||
warm_white: !lambda return 0.2f;
|
||||
- light.control:
|
||||
id: test_monochromatic_light
|
||||
effect: !lambda return "My Flicker";
|
||||
- light.dim_relative:
|
||||
id: test_monochromatic_light
|
||||
relative_brightness: !lambda return 0.1f;
|
||||
transition_length: !lambda return 150;
|
||||
# Test static effect name resolution at codegen time
|
||||
- light.turn_on:
|
||||
id: test_monochromatic_light
|
||||
|
||||
@@ -9,5 +9,12 @@ button:
|
||||
on_press:
|
||||
lightwaverf.send_raw:
|
||||
code: [0x04, 0x00, 0x00, 0x00, 0x0f, 0x03, 0x0d, 0x09, 0x08, 0x08]
|
||||
name: "Sofa"
|
||||
repeat: 1
|
||||
- platform: template
|
||||
name: "Turn off sofa inverted"
|
||||
on_press:
|
||||
lightwaverf.send_raw:
|
||||
code: [0x04, 0x00, 0x00, 0x00, 0x0f, 0x03, 0x0d, 0x09, 0x08, 0x08]
|
||||
name: "Sofa inverted"
|
||||
inverted: true
|
||||
pulse_length: 300
|
||||
@@ -5,6 +5,10 @@ esphome:
|
||||
level: warn
|
||||
format: "Warning: Logger level is %d"
|
||||
args: [id(logger_id).get_log_level()]
|
||||
- logger.log:
|
||||
tag: custom.tag
|
||||
level: info
|
||||
format: "Tagged log entry"
|
||||
- logger.set_level: WARN
|
||||
- logger.set_level:
|
||||
level: ERROR
|
||||
|
||||
@@ -41,6 +41,10 @@ script:
|
||||
id: test_list_id
|
||||
label:
|
||||
text: row
|
||||
- lvgl.list.add:
|
||||
id: test_list_id
|
||||
checkbox:
|
||||
text: list checkbox
|
||||
|
||||
esphome:
|
||||
on_boot:
|
||||
@@ -107,12 +111,28 @@ lvgl:
|
||||
id: meter_arc_indicator
|
||||
start_value: 0
|
||||
end_value: 180
|
||||
- lvgl.indicator.update:
|
||||
id: meter_arc_indicator
|
||||
value: 90
|
||||
opa: 50%
|
||||
- lvgl.indicator.update:
|
||||
id: minute_hand
|
||||
value: 30
|
||||
opa: !lambda return 128;
|
||||
- lvgl.animation.start:
|
||||
id:
|
||||
- anim_slide
|
||||
- anim_color
|
||||
duration: 3s
|
||||
loop: true
|
||||
- lvgl.animation.start:
|
||||
id: anim_slide
|
||||
start_delay: 250ms
|
||||
- lvgl.update:
|
||||
disp_bg_color: 0x101010
|
||||
disp_bg_opa: 80%
|
||||
- lvgl.update:
|
||||
disp_bg_image: cat_image
|
||||
on_invalidate_area:
|
||||
logger.log: Invalidate area
|
||||
on_resolution_change:
|
||||
@@ -684,8 +704,11 @@ lvgl:
|
||||
on_long_press:
|
||||
lvgl.matrix.button.update:
|
||||
id: [button_a, button_e, button_c]
|
||||
width: 2
|
||||
selected: true
|
||||
control:
|
||||
disabled: true
|
||||
checkable: true
|
||||
on_click:
|
||||
logger.log:
|
||||
format: "matrix button clicked: %d, is button_a = %u"
|
||||
@@ -1064,6 +1087,13 @@ lvgl:
|
||||
outline_width: 4
|
||||
outline_pad: 4
|
||||
outline_opa: cover
|
||||
- lvgl.canvas.draw_rectangle:
|
||||
x: 10
|
||||
y: 10
|
||||
width: !lambda return 120;
|
||||
height: !lambda return 80;
|
||||
shadow_ofs_x: 3
|
||||
shadow_ofs_y: 3
|
||||
- lvgl.canvas.set_pixels:
|
||||
color: red
|
||||
points:
|
||||
@@ -1097,6 +1127,12 @@ lvgl:
|
||||
text: Canvas Text
|
||||
align: center
|
||||
max_width: 150
|
||||
- lvgl.canvas.draw_text:
|
||||
x: 20
|
||||
y: 150
|
||||
font: montserrat_18
|
||||
text: Lambda Width
|
||||
max_width: !lambda return 120;
|
||||
- lvgl.canvas.draw_image:
|
||||
src: cat_image
|
||||
x: 100
|
||||
@@ -1108,6 +1144,35 @@ lvgl:
|
||||
skew_y: 5
|
||||
pivot_x: 25
|
||||
pivot_y: 25
|
||||
- lvgl.canvas.draw_image:
|
||||
src: cat_image
|
||||
x: 50
|
||||
y: 50
|
||||
angle: 45
|
||||
scale: 1.5
|
||||
opa: 70%
|
||||
recolor: red
|
||||
recolor_opa: 30%
|
||||
- lvgl.canvas.draw_image:
|
||||
src: cat_image
|
||||
x: 60
|
||||
y: 60
|
||||
zoom: 2.0
|
||||
- lvgl.canvas.draw_rectangle:
|
||||
x: 10
|
||||
y: 10
|
||||
width: 120
|
||||
height: 80
|
||||
bg_grad: color_bar
|
||||
- lvgl.canvas.draw_line:
|
||||
color: green
|
||||
width: 2
|
||||
opa: 60%
|
||||
dash_width: 6
|
||||
dash_gap: 3
|
||||
points:
|
||||
- 10,10
|
||||
- 90,90
|
||||
- lvgl.canvas.draw_line:
|
||||
color: blue
|
||||
width: 4
|
||||
@@ -1120,7 +1185,6 @@ lvgl:
|
||||
- 200, 50
|
||||
- 50,50
|
||||
- lvgl.canvas.draw_polygon:
|
||||
bg_color: teal
|
||||
border_color: white
|
||||
border_width: 2
|
||||
border_opa: cover
|
||||
@@ -1332,6 +1396,12 @@ lvgl:
|
||||
column: 1
|
||||
text: !lambda return str_sprintf("%.1f", (float) rand() / RAND_MAX * 100);
|
||||
merge_right: false
|
||||
- lvgl.table.cell.update:
|
||||
id: table_id
|
||||
row: 0
|
||||
column: 1
|
||||
text: cropped
|
||||
text_crop: true
|
||||
- lvgl.table.update:
|
||||
id: table_id
|
||||
selected_row: !lambda return (int) ((float) rand() / RAND_MAX * 2);
|
||||
@@ -1363,6 +1433,9 @@ lvgl:
|
||||
- 180, 60
|
||||
- 240, 10
|
||||
- lvgl.page.next:
|
||||
- lvgl.page.next:
|
||||
animation: move_left
|
||||
time: 200ms
|
||||
- switch:
|
||||
align: right_mid
|
||||
- checkbox:
|
||||
|
||||
@@ -75,6 +75,8 @@ lvgl:
|
||||
on_draw_end:
|
||||
- logger.log: draw ended
|
||||
- lvgl.pause:
|
||||
- lvgl.pause:
|
||||
show_snow: true
|
||||
- component.update: tft_display
|
||||
- delay: 60s
|
||||
- lvgl.resume:
|
||||
@@ -20,3 +20,6 @@ esphome:
|
||||
- max7219digit.reverse_on:
|
||||
- max7219digit.reverse_off:
|
||||
- max7219digit.intensity: 10
|
||||
- max7219digit.intensity:
|
||||
intensity: 7
|
||||
- max7219digit.intensity: !lambda return 3;
|
||||
@@ -53,6 +53,60 @@ media_player:
|
||||
- media_player.unshuffle: test_media_player
|
||||
- media_player.group_join: test_media_player
|
||||
- media_player.clear_playlist: test_media_player
|
||||
- media_player.play:
|
||||
id: test_media_player
|
||||
announcement: true
|
||||
- media_player.pause:
|
||||
id: test_media_player
|
||||
announcement: true
|
||||
- media_player.toggle:
|
||||
id: test_media_player
|
||||
announcement: true
|
||||
- media_player.turn_on:
|
||||
id: test_media_player
|
||||
announcement: true
|
||||
- media_player.turn_off:
|
||||
id: test_media_player
|
||||
announcement: true
|
||||
- media_player.next:
|
||||
id: test_media_player
|
||||
announcement: true
|
||||
- media_player.previous:
|
||||
id: test_media_player
|
||||
announcement: true
|
||||
- media_player.mute:
|
||||
id: test_media_player
|
||||
announcement: true
|
||||
- media_player.unmute:
|
||||
id: test_media_player
|
||||
announcement: true
|
||||
- media_player.repeat_off:
|
||||
id: test_media_player
|
||||
announcement: true
|
||||
- media_player.repeat_one:
|
||||
id: test_media_player
|
||||
announcement: true
|
||||
- media_player.repeat_all:
|
||||
id: test_media_player
|
||||
announcement: true
|
||||
- media_player.shuffle:
|
||||
id: test_media_player
|
||||
announcement: true
|
||||
- media_player.unshuffle:
|
||||
id: test_media_player
|
||||
announcement: true
|
||||
- media_player.group_join:
|
||||
id: test_media_player
|
||||
announcement: true
|
||||
- media_player.clear_playlist:
|
||||
id: test_media_player
|
||||
announcement: true
|
||||
- media_player.volume_up:
|
||||
id: test_media_player
|
||||
announcement: true
|
||||
- media_player.volume_down:
|
||||
id: test_media_player
|
||||
announcement: true
|
||||
- wait_until:
|
||||
media_player.is_idle: test_media_player
|
||||
- wait_until:
|
||||
@@ -86,3 +140,7 @@ media_player:
|
||||
id: test_media_player
|
||||
media_url: http://localhost/media.mp3
|
||||
announcement: true
|
||||
- media_player.enqueue:
|
||||
id: test_media_player
|
||||
media_url: http://localhost/media.mp3
|
||||
announcement: !lambda return true;
|
||||
@@ -10,6 +10,10 @@ button:
|
||||
id: midea_unit
|
||||
temperature: !lambda return id(midea_unit).target_temperature + 1.0f;
|
||||
use_fahrenheit: !lambda return false;
|
||||
- midea_ac.follow_me:
|
||||
id: midea_unit
|
||||
temperature: 24.0
|
||||
beeper: !lambda return true;
|
||||
- midea_ac.display_toggle:
|
||||
id: midea_unit
|
||||
- midea_ac.swing_step:
|
||||
|
||||
@@ -30,6 +30,9 @@ esphome:
|
||||
temperature: 22.0
|
||||
- mitsubishi_cn105.clear_remote_temperature:
|
||||
id: ac
|
||||
- mitsubishi_cn105.set_remote_temperature:
|
||||
id: ac
|
||||
temperature: !lambda return 21.5f;
|
||||
- mitsubishi_cn105.vane.control:
|
||||
id: ac
|
||||
vertical:
|
||||
|
||||
@@ -186,6 +186,215 @@ button:
|
||||
read_address: 0x0420
|
||||
read_count: 1
|
||||
|
||||
- platform: template
|
||||
name: "Typed Handlers"
|
||||
on_press:
|
||||
- modbus_client.read_coils:
|
||||
address: !lambda "return 1;"
|
||||
start_address: !lambda "return 0x03;"
|
||||
count: !lambda "return 8;"
|
||||
continuous: !lambda "return false;"
|
||||
allow_broadcast_read: !lambda "return false;"
|
||||
on_sent:
|
||||
then:
|
||||
- logger.log: "coils sent"
|
||||
on_not_sent:
|
||||
then:
|
||||
- lambda: 'ESP_LOGW("modbus_client.test", "coils not sent fc 0x%X", request.empty() ? 0 : request[0]);'
|
||||
on_no_response:
|
||||
then:
|
||||
- logger.log: "coils timeout"
|
||||
retry: !lambda "return false;"
|
||||
on_custom_response:
|
||||
then:
|
||||
- lambda: 'ESP_LOGW("modbus_client.test", "coils custom reply %u bytes", (unsigned) response.size());'
|
||||
- modbus_client.read_coils:
|
||||
address: 0
|
||||
start_address: 0x03
|
||||
allow_broadcast_read: true
|
||||
on_response:
|
||||
then:
|
||||
- lambda: 'ESP_LOGI("modbus_client.test", "broadcast coil0=%d", bits[0]);'
|
||||
- modbus_client.read_discrete_inputs:
|
||||
address: !lambda "return 1;"
|
||||
start_address: !lambda "return 0x00;"
|
||||
count: 4
|
||||
continuous: !lambda "return true;"
|
||||
on_sent:
|
||||
then:
|
||||
- logger.log: "inputs sent"
|
||||
on_not_sent:
|
||||
then:
|
||||
- logger.log: "inputs not sent"
|
||||
on_no_response: !lambda "return false;"
|
||||
on_response:
|
||||
then:
|
||||
- lambda: 'ESP_LOGI("modbus_client.test", "input0=%d n=%u", bits[0], (unsigned) bits.size());'
|
||||
on_custom_response:
|
||||
then:
|
||||
- logger.log: "inputs custom reply"
|
||||
- modbus_client.read_discrete_inputs:
|
||||
address: 0
|
||||
start_address: 0x00
|
||||
allow_broadcast_read: true
|
||||
- modbus_client.read_holding_registers:
|
||||
address: 0x01
|
||||
start_address: !lambda "return 0x10;"
|
||||
continuous: !lambda "return true;"
|
||||
on_sent:
|
||||
then:
|
||||
- logger.log: "holding sent"
|
||||
on_custom_response:
|
||||
then:
|
||||
- lambda: 'ESP_LOGW("modbus_client.test", "holding custom reply fc 0x%X", response.empty() ? 0 : response[0]);'
|
||||
- modbus_client.read_input_registers:
|
||||
address: !lambda "return 1;"
|
||||
start_address: !lambda "return 0x20;"
|
||||
count: 3
|
||||
on_sent:
|
||||
then:
|
||||
- logger.log: "inputs regs sent"
|
||||
on_not_sent:
|
||||
then:
|
||||
- logger.log: "input regs not sent"
|
||||
on_error:
|
||||
then:
|
||||
- lambda: 'ESP_LOGW("modbus_client.test", "input regs exception %d", (int) exception_code);'
|
||||
on_no_response:
|
||||
then:
|
||||
- logger.log: "input regs timeout"
|
||||
on_response:
|
||||
then:
|
||||
- lambda: 'ESP_LOGI("modbus_client.test", "input reg0=%u n=%u", values[0], (unsigned) values.size());'
|
||||
- modbus_client.read_input_registers:
|
||||
address: 0
|
||||
start_address: 0x20
|
||||
allow_broadcast_read: true
|
||||
- modbus_client.read_write_multiple_registers:
|
||||
address: !lambda "return 1;"
|
||||
write_address: !lambda "return 0x0300;"
|
||||
values: [1, 2]
|
||||
read_address: !lambda "return 0x0400;"
|
||||
read_count: !lambda "return 2;"
|
||||
allow_broadcast_read: !lambda "return false;"
|
||||
on_sent:
|
||||
then:
|
||||
- logger.log: "rw sent"
|
||||
on_not_sent:
|
||||
then:
|
||||
- logger.log: "rw not sent"
|
||||
on_error:
|
||||
then:
|
||||
- lambda: 'ESP_LOGW("modbus_client.test", "rw exception %d", (int) exception_code);'
|
||||
on_no_response:
|
||||
then:
|
||||
- logger.log: "rw timeout"
|
||||
on_custom_response:
|
||||
then:
|
||||
- logger.log: "rw custom reply"
|
||||
- modbus_client.send:
|
||||
address: 0
|
||||
pdu: [0x03, 0x00, 0x10, 0x00, 0x01]
|
||||
allow_broadcast_read: true
|
||||
- modbus_client.send:
|
||||
address: !lambda "return 1;"
|
||||
pdu: [0x06, 0x01, 0x02, 0x00, 0x2A]
|
||||
continuous: !lambda "return false;"
|
||||
expect_broadcast_write_response: !lambda "return false;"
|
||||
- modbus_client.write_multiple_coils:
|
||||
address: !lambda "return 1;"
|
||||
start_address: !lambda "return 0x0010;"
|
||||
values: [true, false]
|
||||
expect_broadcast_write_response: !lambda "return false;"
|
||||
on_sent:
|
||||
then:
|
||||
- logger.log: "coils write sent"
|
||||
on_not_sent:
|
||||
then:
|
||||
- logger.log: "coils write not sent"
|
||||
on_no_response:
|
||||
then:
|
||||
- logger.log: "coils write timeout"
|
||||
on_response:
|
||||
then:
|
||||
- logger.log: "coils write acked"
|
||||
on_custom_response:
|
||||
then:
|
||||
- logger.log: "coils write custom reply"
|
||||
- modbus_client.write_multiple_coils:
|
||||
address: 0
|
||||
start_address: 0x0010
|
||||
values: [true]
|
||||
expect_broadcast_write_response: true
|
||||
- modbus_client.write_multiple_registers:
|
||||
address: !lambda "return 1;"
|
||||
start_address: !lambda "return 0x0200;"
|
||||
values: [1, 2]
|
||||
on_sent:
|
||||
then:
|
||||
- logger.log: "regs write sent"
|
||||
on_not_sent:
|
||||
then:
|
||||
- logger.log: "regs write not sent"
|
||||
on_error:
|
||||
then:
|
||||
- lambda: 'ESP_LOGW("modbus_client.test", "regs write exception %d", (int) exception_code);'
|
||||
on_no_response:
|
||||
then:
|
||||
- logger.log: "regs write timeout"
|
||||
on_custom_response:
|
||||
then:
|
||||
- logger.log: "regs write custom reply"
|
||||
- modbus_client.write_multiple_registers:
|
||||
address: 0
|
||||
start_address: 0x0200
|
||||
values: [7]
|
||||
expect_broadcast_write_response: true
|
||||
- modbus_client.write_single_coil:
|
||||
address: !lambda "return 1;"
|
||||
start_address: !lambda "return 0x01;"
|
||||
value: !lambda "return false;"
|
||||
expect_broadcast_write_response: !lambda "return false;"
|
||||
on_sent:
|
||||
then:
|
||||
- logger.log: "coil sent"
|
||||
on_not_sent:
|
||||
then:
|
||||
- logger.log: "coil not sent"
|
||||
on_error:
|
||||
then:
|
||||
- lambda: 'ESP_LOGW("modbus_client.test", "coil exception %d", (int) exception_code);'
|
||||
on_no_response:
|
||||
then:
|
||||
- logger.log: "coil timeout"
|
||||
on_response:
|
||||
then:
|
||||
- logger.log: "coil acked"
|
||||
on_custom_response:
|
||||
then:
|
||||
- logger.log: "coil custom reply"
|
||||
- modbus_client.write_single_coil:
|
||||
address: 0
|
||||
start_address: 0x01
|
||||
value: true
|
||||
expect_broadcast_write_response: true
|
||||
- modbus_client.write_single_register:
|
||||
address: 0x01
|
||||
start_address: !lambda "return 0x0102;"
|
||||
value: 42
|
||||
expect_broadcast_write_response: !lambda "return false;"
|
||||
on_sent:
|
||||
then:
|
||||
- logger.log: "register sent"
|
||||
on_not_sent:
|
||||
then:
|
||||
- logger.log: "register not sent"
|
||||
on_no_response:
|
||||
then:
|
||||
- logger.log: "register timeout"
|
||||
on_custom_response:
|
||||
then:
|
||||
- logger.log: "register custom reply"
|
||||
number:
|
||||
- platform: template
|
||||
name: "Modbus Client Write Value"
|
||||
|
||||
@@ -8,7 +8,9 @@ esphome:
|
||||
condition:
|
||||
modem.enabled:
|
||||
then:
|
||||
modem.send_at: "AT+CGSN"
|
||||
- modem.send_at: "AT+CGSN"
|
||||
- modem.send_at:
|
||||
command: !lambda 'return std::string("AT+CSQ");'
|
||||
else:
|
||||
modem.enable:
|
||||
- if:
|
||||
|
||||
@@ -109,6 +109,18 @@ button:
|
||||
return payload;
|
||||
qos: !lambda return 1;
|
||||
retain: !lambda return false;
|
||||
- mqtt.publish_json:
|
||||
topic: some/topic/json
|
||||
payload: |-
|
||||
root["key"] = "value";
|
||||
qos: 1
|
||||
retain: true
|
||||
- mqtt.publish_json:
|
||||
topic: !lambda return "some/topic/json/lambda";
|
||||
payload: |-
|
||||
root["key"] = 1;
|
||||
qos: !lambda return 2;
|
||||
retain: !lambda return false;
|
||||
|
||||
climate:
|
||||
- platform: thermostat
|
||||
|
||||
@@ -42,6 +42,19 @@ button:
|
||||
cycle: false
|
||||
- number.to_min: number_test_number
|
||||
- number.to_max: number_test_number
|
||||
- number.increment:
|
||||
id: number_test_number
|
||||
mode: INCREMENT
|
||||
cycle: true
|
||||
- number.decrement:
|
||||
id: number_test_number
|
||||
mode: DECREMENT
|
||||
- number.to_min:
|
||||
id: number_test_number
|
||||
mode: TO_MIN
|
||||
- number.to_max:
|
||||
id: number_test_number
|
||||
mode: TO_MAX
|
||||
- number.operation:
|
||||
id: number_test_number
|
||||
operation: increment
|
||||
|
||||
@@ -18,3 +18,5 @@ switch:
|
||||
turn_off_action:
|
||||
then:
|
||||
- openthread.set_poll_period: 5s
|
||||
- openthread.set_poll_period:
|
||||
poll_period: !lambda return 10000;
|
||||
@@ -15,6 +15,12 @@ esphome:
|
||||
- output.set_max_power:
|
||||
id: output_light_output_1
|
||||
max_power: 80%
|
||||
- output.set_min_power:
|
||||
id: output_light_output_1
|
||||
min_power: !lambda return 0.1f;
|
||||
- output.set_max_power:
|
||||
id: output_light_output_1
|
||||
max_power: !lambda return 0.9f;
|
||||
|
||||
output:
|
||||
- platform: ${output_platform}
|
||||
|
||||
@@ -11,6 +11,11 @@ esphome:
|
||||
- tag.set_emulation_message:
|
||||
message: https://www.home-assistant.io/tag/pulse
|
||||
include_android_app_record: false
|
||||
- tag.set_write_message:
|
||||
message: !lambda return std::string("https://www.home-assistant.io/tag/lambda");
|
||||
- tag.set_emulation_message:
|
||||
message: !lambda return std::string("https://www.home-assistant.io/tag/lambda");
|
||||
include_android_app_record: true
|
||||
- tag.emulation_off: nfcc_pn7150
|
||||
- tag.emulation_on: nfcc_pn7150
|
||||
- tag.polling_off: nfcc_pn7150
|
||||
|
||||
@@ -564,3 +564,482 @@ button:
|
||||
on_press:
|
||||
- remote_transmitter.transmit_pd_pioneer:
|
||||
code: [0x23, 0xCB, 0x26, 0x01, 0x00, 0x24, 0x03, 0x0B, 0x05, 0x00, 0x00, 0x00, 0x80]
|
||||
- platform: template
|
||||
name: Repeat on every protocol
|
||||
on_press:
|
||||
- remote_transmitter.transmit_abbwelcome:
|
||||
source_address: 0x1234
|
||||
destination_address: 0x5678
|
||||
message_type: 0x01
|
||||
data: [0x10]
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_aeha:
|
||||
address: 0x8008
|
||||
data: [0x00, 0x02]
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_beo4:
|
||||
source: 0x01
|
||||
command: 0x0C
|
||||
command_repeats: 3
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_brennenstuhl:
|
||||
code: 0xBD2E2C
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_byronsx:
|
||||
address: 0x12
|
||||
command: 0x05
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_canalsat:
|
||||
device: 0x25
|
||||
address: 0x01
|
||||
command: 0x1C
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_canalsatld:
|
||||
device: 0x25
|
||||
address: 0x02
|
||||
command: 0x1C
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_coolix:
|
||||
first: 0xB2BF40
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_dish:
|
||||
address: 1
|
||||
command: 16
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_dooya:
|
||||
id: 0x123456
|
||||
channel: 1
|
||||
button: 1
|
||||
check: 1
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_drayton:
|
||||
address: 0x1234
|
||||
channel: 0x01
|
||||
command: 0x05
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_dyson:
|
||||
code: 0x1215
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_gobox:
|
||||
code: 1
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_haier:
|
||||
code: [0xA6, 0xDA, 0x00, 0x00, 0x40, 0x40, 0x00, 0x80]
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_hob2hood:
|
||||
command: fan_low
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_ira211:
|
||||
command: power
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_jvc:
|
||||
data: 0x10EF
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_keeloq:
|
||||
address: 0x57ffe7b
|
||||
command: 0x02
|
||||
code: 0xd19ef0a9
|
||||
level: true
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_lg:
|
||||
data: 0x20DF10EF
|
||||
nbits: 32
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_magiquest:
|
||||
wand_id: 0x01234567
|
||||
magnitude: 0x1234
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_midea:
|
||||
code: [0xA2, 0x08, 0xFF, 0xFF, 0xFF]
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_mirage:
|
||||
code: [0x56, 0x77, 0x00, 0x00, 0x22, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_nec:
|
||||
address: 0x4242
|
||||
command: 0x8484
|
||||
command_repeats: 3
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_nexa:
|
||||
device: 0x38DDB5A
|
||||
group: 0
|
||||
state: 1
|
||||
channel: 15
|
||||
level: 0
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_nexus:
|
||||
channel: 1
|
||||
address: 0x42
|
||||
temperature: 21.5
|
||||
humidity: 50
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_onkyori:
|
||||
data: 0xD9
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_panasonic:
|
||||
address: 0x4004
|
||||
command: 0x1000BCD
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_pd_pioneer:
|
||||
code: [0x23, 0xCB, 0x26, 0x01, 0x00, 0x24, 0x03, 0x0B, 0x05, 0x00, 0x00, 0x00, 0x80]
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_pioneer_wyt:
|
||||
code: [0x23, 0xCB, 0x26, 0x01, 0x00, 0x24, 0x03, 0x05, 0x00, 0x00, 0x00, 0x00, 0x80]
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_pronto:
|
||||
data: "0000 006D 0001 0000 0015 0016"
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_raw:
|
||||
code: [1000, -1000]
|
||||
carrier_frequency: 38kHz
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_rc5:
|
||||
address: 0x00
|
||||
command: 0x0B
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_rc5x:
|
||||
address: 0x10
|
||||
command: 0x0
|
||||
extension: 0x6
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_rc6:
|
||||
address: 0x00
|
||||
command: 0x0C
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_rc_switch_raw:
|
||||
code: "001010011001111101010000"
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_rc_switch_type_a:
|
||||
group: "11001"
|
||||
device: "01000"
|
||||
state: true
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_rc_switch_type_b:
|
||||
address: 4
|
||||
channel: 2
|
||||
state: true
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_rc_switch_type_c:
|
||||
family: "a"
|
||||
group: 1
|
||||
device: 2
|
||||
state: true
|
||||
protocol: 2
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_rc_switch_type_d:
|
||||
group: "a"
|
||||
device: 2
|
||||
state: true
|
||||
protocol:
|
||||
pulse_length: 350
|
||||
sync: [1, 31]
|
||||
zero: [1, 3]
|
||||
one: [3, 1]
|
||||
inverted: true
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_roomba:
|
||||
data: 0x88
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_samsung:
|
||||
data: 0xABCDEF0123
|
||||
nbits: 40
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_samsung36:
|
||||
address: 0x0400
|
||||
command: 0x000E00FF
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_sony:
|
||||
data: 0xABCDEF
|
||||
nbits: 12
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_symphony:
|
||||
data: 0xE88
|
||||
nbits: 12
|
||||
command_repeats: 4
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_toshiba_ac:
|
||||
rc_code_1: 0xB24DBF4050AF
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_jvc:
|
||||
data: 0x10EF
|
||||
repeat: 3
|
||||
- remote_transmitter.transmit_nec:
|
||||
address: 0x4242
|
||||
command: 0x8484
|
||||
repeat:
|
||||
times: !lambda return 2;
|
||||
wait_time: !lambda return 10000;
|
||||
- platform: template
|
||||
name: Lambda fields on every protocol
|
||||
on_press:
|
||||
- remote_transmitter.transmit_abbwelcome:
|
||||
source_address: !lambda return 0x1234;
|
||||
destination_address: !lambda return 0x5678;
|
||||
three_byte_address: !lambda return false;
|
||||
retransmission: !lambda return true;
|
||||
message_type: !lambda return 0x01;
|
||||
message_id: !lambda return 0x42;
|
||||
data: [0x10, 0x20]
|
||||
- remote_transmitter.transmit_aeha:
|
||||
address: !lambda return 0x8008;
|
||||
carrier_frequency: !lambda return 38000;
|
||||
data: [0x00, 0x02]
|
||||
- remote_transmitter.transmit_aok:
|
||||
remote_id: !lambda return 0x123456;
|
||||
channels: 0x00A1
|
||||
command: !lambda return 0x0B;
|
||||
- remote_transmitter.transmit_beo4:
|
||||
source: !lambda return 0x01;
|
||||
command: !lambda return 0x0C;
|
||||
command_repeats: !lambda return 2;
|
||||
- remote_transmitter.transmit_brennenstuhl:
|
||||
code: !lambda return 0xBD2E2C;
|
||||
- remote_transmitter.transmit_byronsx:
|
||||
address: !lambda return 0x12;
|
||||
command: !lambda return 0x05;
|
||||
- remote_transmitter.transmit_canalsat:
|
||||
device: !lambda return 0x25;
|
||||
address: !lambda return 0x01;
|
||||
command: !lambda return 0x1C;
|
||||
- remote_transmitter.transmit_canalsatld:
|
||||
device: !lambda return 0x25;
|
||||
address: !lambda return 0x01;
|
||||
command: 0x1C
|
||||
- remote_transmitter.transmit_coolix:
|
||||
first: !lambda return 0xB2BF40;
|
||||
second: !lambda return 0xB2BF40;
|
||||
- remote_transmitter.transmit_dish:
|
||||
address: !lambda return 1;
|
||||
command: !lambda return 16;
|
||||
- remote_transmitter.transmit_dooya:
|
||||
id: !lambda return 0x123456;
|
||||
channel: !lambda return 1;
|
||||
button: !lambda return 1;
|
||||
check: !lambda return 1;
|
||||
- remote_transmitter.transmit_drayton:
|
||||
address: !lambda return 0x1230;
|
||||
data: !lambda return 0x8;
|
||||
- remote_transmitter.transmit_drayton:
|
||||
address: 0x1234
|
||||
channel: !lambda return 0x01;
|
||||
command: !lambda return 0x05;
|
||||
- remote_transmitter.transmit_dyson:
|
||||
code: !lambda return 0x1215;
|
||||
index: !lambda return 0x0;
|
||||
- remote_transmitter.transmit_gobox:
|
||||
code: !lambda return 1;
|
||||
- remote_transmitter.transmit_hob2hood:
|
||||
command: !lambda return remote_base::HOB2HOOD_COMMAND_FAN_HIGH;
|
||||
- remote_transmitter.transmit_ira211:
|
||||
command: !lambda return remote_base::IRA211Command::IRA211_COMMAND_POWER;
|
||||
temperature: !lambda return 21.5;
|
||||
mode: !lambda return remote_base::IRA211Mode::IRA211_MODE_TIMER;
|
||||
fan_mode: !lambda return remote_base::IRA211Fan::IRA211_FAN_LOW;
|
||||
- remote_transmitter.transmit_jvc:
|
||||
data: !lambda return 0x10EF;
|
||||
- remote_transmitter.transmit_keeloq:
|
||||
address: 0x57ffe7b
|
||||
command: !lambda return 0x02;
|
||||
code: !lambda return 0xd19ef0a9;
|
||||
level: !lambda return true;
|
||||
- remote_transmitter.transmit_keeloq:
|
||||
address: 0x1234500
|
||||
command: 0x8
|
||||
code: 0xF94A09F2
|
||||
group: !lambda return 0x0F;
|
||||
- remote_transmitter.transmit_keeloq:
|
||||
address: 0x116ea01
|
||||
command: 0x08
|
||||
code: 0xd19ef0a9
|
||||
suffix: !lambda return 0x00;
|
||||
suffix_bits: !lambda return 8;
|
||||
- remote_transmitter.transmit_lg:
|
||||
data: !lambda return 0xFFFFFFFF;
|
||||
nbits: !lambda return 28;
|
||||
- remote_transmitter.transmit_magiquest:
|
||||
wand_id: !lambda return 0x01234567;
|
||||
magnitude: !lambda return 0xFFFF;
|
||||
- remote_transmitter.transmit_nec:
|
||||
address: !lambda return 0x4242;
|
||||
command: !lambda return 0x8484;
|
||||
command_repeats: !lambda return 1;
|
||||
- remote_transmitter.transmit_nexa:
|
||||
device: !lambda return 0x38DDB5A;
|
||||
group: !lambda return 0;
|
||||
state: !lambda return 1;
|
||||
channel: !lambda return 15;
|
||||
level: !lambda return 0;
|
||||
- remote_transmitter.transmit_nexus:
|
||||
channel: !lambda return 1;
|
||||
address: !lambda return 0x42;
|
||||
temperature: !lambda return -12.3f;
|
||||
humidity: !lambda return 69;
|
||||
battery_level: !lambda return false;
|
||||
force_update: !lambda return true;
|
||||
- remote_transmitter.transmit_onkyori:
|
||||
data: !lambda return 0xD9;
|
||||
- remote_transmitter.transmit_panasonic:
|
||||
nbits: !lambda return 56;
|
||||
address: !lambda return 0x4004;
|
||||
address_2: !lambda return 0x12;
|
||||
command: 0x1000BCD
|
||||
carrier_frequency: !lambda return 36000;
|
||||
- remote_transmitter.transmit_pd_pioneer:
|
||||
code: !lambda return {0x23, 0xCB, 0x26, 0x01, 0x00, 0x24, 0x03, 0x0B, 0x05, 0x00, 0x00, 0x00, 0x80};
|
||||
- remote_transmitter.transmit_pioneer:
|
||||
rc_code_1: !lambda return 0xA556;
|
||||
rc_code_2: !lambda return 0xA506;
|
||||
- remote_transmitter.transmit_pioneer_wyt:
|
||||
code: !lambda return {0x23, 0xCB, 0x26, 0x01, 0x00, 0x24, 0x03, 0x05, 0x00, 0x00, 0x00, 0x00, 0x80};
|
||||
- remote_transmitter.transmit_pronto:
|
||||
data: !lambda return std::string("0000 006D 0001 0000 0015 0016");
|
||||
- remote_transmitter.transmit_rc5:
|
||||
address: !lambda return 0x00;
|
||||
command: !lambda return 0x0B;
|
||||
- remote_transmitter.transmit_rc5x:
|
||||
address: !lambda return 0x10;
|
||||
command: !lambda return 0x0;
|
||||
extension: !lambda return 0x6;
|
||||
- remote_transmitter.transmit_rc6:
|
||||
address: !lambda return 0x00;
|
||||
command: 0x0C
|
||||
- remote_transmitter.transmit_rc_switch_raw:
|
||||
code: !lambda return std::string("001010011001111101010000");
|
||||
- remote_transmitter.transmit_rc_switch_type_a:
|
||||
group: !lambda return std::string("11001");
|
||||
device: !lambda return std::string("01000");
|
||||
state: !lambda return true;
|
||||
- remote_transmitter.transmit_rc_switch_type_b:
|
||||
address: !lambda return 4;
|
||||
channel: !lambda return 2;
|
||||
state: !lambda return false;
|
||||
- remote_transmitter.transmit_rc_switch_type_c:
|
||||
family: !lambda return std::string("a");
|
||||
group: !lambda return 1;
|
||||
device: !lambda return 2;
|
||||
state: !lambda return true;
|
||||
- remote_transmitter.transmit_rc_switch_type_d:
|
||||
group: !lambda return std::string("a");
|
||||
device: !lambda return 2;
|
||||
state: !lambda return true;
|
||||
- remote_transmitter.transmit_roomba:
|
||||
data: !lambda return 0x88;
|
||||
- remote_transmitter.transmit_samsung:
|
||||
data: !lambda return 0xABCDEF;
|
||||
nbits: !lambda return 32;
|
||||
- remote_transmitter.transmit_samsung36:
|
||||
address: !lambda return 0x0400;
|
||||
command: !lambda return 0x000E00FF;
|
||||
- remote_transmitter.transmit_somfy_rts:
|
||||
address: !lambda return 0x630139;
|
||||
control: !lambda return 0x08;
|
||||
key: !lambda return 0xA0;
|
||||
rolling_code: 1234
|
||||
control_in_key: !lambda return false;
|
||||
- remote_transmitter.transmit_sony:
|
||||
data: !lambda return 0xABCDEF;
|
||||
nbits: !lambda return 15;
|
||||
- remote_transmitter.transmit_symphony:
|
||||
data: !lambda return 0xE88;
|
||||
nbits: !lambda return 12;
|
||||
command_repeats: !lambda return 2;
|
||||
- remote_transmitter.transmit_toshiba_ac:
|
||||
rc_code_1: !lambda return 0xB24DBF4050AF;
|
||||
rc_code_2: !lambda return 0xD5660001003C;
|
||||
- remote_transmitter.transmit_toto:
|
||||
command: !lambda return 0xEC;
|
||||
rc_code_1: !lambda return 0x0D;
|
||||
rc_code_2: !lambda return 0x0D;
|
||||
- remote_transmitter.transmit_raw:
|
||||
code: [1000, -1000]
|
||||
carrier_frequency: !lambda return 38000;
|
||||
@@ -12,6 +12,11 @@ rf_bridge:
|
||||
high: 0x1234
|
||||
code: 0x123456
|
||||
- rf_bridge.learn
|
||||
- rf_bridge.send_code:
|
||||
sync: !lambda return 0x1234;
|
||||
low: !lambda return 0x1234;
|
||||
high: !lambda return 0x1234;
|
||||
code: !lambda return 0x123456;
|
||||
on_advanced_code_received:
|
||||
- lambda: |-
|
||||
uint32_t test;
|
||||
@@ -27,6 +32,12 @@ rf_bridge:
|
||||
code: "ABC123"
|
||||
- rf_bridge.send_raw:
|
||||
raw: "AAA5070008001000ABC12355"
|
||||
- rf_bridge.send_advanced_code:
|
||||
length: !lambda return 0x04;
|
||||
protocol: !lambda return 0x01;
|
||||
code: !lambda return std::string("ABC123");
|
||||
- rf_bridge.send_raw:
|
||||
raw: !lambda return std::string("AAA5070008001000ABC12355");
|
||||
- rf_bridge.start_bucket_sniffing:
|
||||
- rf_bridge.beep:
|
||||
duration: !lambda return 100;
|
||||
@@ -21,6 +21,14 @@ esphome:
|
||||
- rx8111.schedule_alarm_in:
|
||||
id: rtc
|
||||
duration: !lambda return uint32_t{60};
|
||||
- rx8111.set_alarm:
|
||||
id: rtc
|
||||
second: !lambda return uint16_t{30};
|
||||
hour: !lambda return uint16_t{6};
|
||||
weekday: !lambda return uint16_t{3};
|
||||
- rx8111.set_alarm:
|
||||
id: rtc
|
||||
day: !lambda return uint16_t{20};
|
||||
- rx8111.clear_alarm_flag: rtc
|
||||
- rx8111.clear_timer_flag: rtc
|
||||
- rx8111.clear_event_flag: rtc
|
||||
|
||||
@@ -29,6 +29,28 @@ esphome:
|
||||
then:
|
||||
- logger.log: a script is running
|
||||
- script.execute: my_script_stops_itself
|
||||
- delay:
|
||||
minutes: 1
|
||||
seconds: 2
|
||||
milliseconds: 3
|
||||
- delay:
|
||||
hours: 1
|
||||
- if:
|
||||
all:
|
||||
- script.is_running: my_script
|
||||
- lambda: return true;
|
||||
then:
|
||||
- logger.log: all conditions true
|
||||
- wait_until:
|
||||
condition:
|
||||
not:
|
||||
script.is_running: my_script
|
||||
timeout: !lambda return 2000;
|
||||
- wait_until:
|
||||
condition:
|
||||
not:
|
||||
script.is_running: my_script
|
||||
timeout: 500ms
|
||||
- choose:
|
||||
- if:
|
||||
script.is_running: my_script
|
||||
|
||||
@@ -21,3 +21,7 @@ button:
|
||||
name: seeed_multi_channel_relay change address
|
||||
on_press:
|
||||
- seeed_multi_channel_relay.change_address: 0x12
|
||||
- seeed_multi_channel_relay.change_address:
|
||||
address: 0x13
|
||||
- seeed_multi_channel_relay.change_address:
|
||||
address: !lambda return 0x14;
|
||||
@@ -37,6 +37,19 @@ button:
|
||||
cycle: false
|
||||
- select.first: select_test_select
|
||||
- select.last: select_test_select
|
||||
- select.next:
|
||||
id: select_test_select
|
||||
mode: NEXT
|
||||
cycle: true
|
||||
- select.previous:
|
||||
id: select_test_select
|
||||
mode: PREVIOUS
|
||||
- select.first:
|
||||
id: select_test_select
|
||||
mode: FIRST
|
||||
- select.last:
|
||||
id: select_test_select
|
||||
mode: LAST
|
||||
- if:
|
||||
condition:
|
||||
select.is:
|
||||
|
||||
@@ -10,6 +10,13 @@ esphome:
|
||||
- sim800l.disconnect
|
||||
- sim800l.send_ussd:
|
||||
ussd: test_ussd
|
||||
- sim800l.send_sms:
|
||||
recipient: !lambda return std::string("+15550000000");
|
||||
message: !lambda return std::string("Lambda message");
|
||||
- sim800l.dial:
|
||||
recipient: !lambda return std::string("+15550000001");
|
||||
- sim800l.send_ussd:
|
||||
ussd: !lambda return std::string("*100#");
|
||||
|
||||
sim800l:
|
||||
on_sms_received:
|
||||
|
||||
@@ -57,6 +57,10 @@ media_player:
|
||||
id: media_player_id
|
||||
pipeline: media
|
||||
delay: 500ms
|
||||
- speaker_source.set_playlist_delay:
|
||||
id: media_player_id
|
||||
pipeline: announcement
|
||||
delay: !lambda return 250;
|
||||
- speaker_source.set_announcement_volume:
|
||||
id: media_player_id
|
||||
volume: !lambda return x;
|
||||
@@ -43,6 +43,16 @@ esphome:
|
||||
id: yard_sprinkler_ctrlr
|
||||
valve_number: !lambda return 1;
|
||||
run_duration: !lambda return 120;
|
||||
- sprinkler.set_divider:
|
||||
id: yard_sprinkler_ctrlr
|
||||
divider: !lambda return 3;
|
||||
- sprinkler.set_repeat:
|
||||
id: yard_sprinkler_ctrlr
|
||||
repeat: !lambda return 1;
|
||||
- sprinkler.start_single_valve:
|
||||
id: yard_sprinkler_ctrlr
|
||||
valve_number: !lambda return 1;
|
||||
run_duration: !lambda return 240;
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
|
||||
@@ -34,3 +34,12 @@ switch:
|
||||
- stepper.set_deceleration:
|
||||
id: test_stepper
|
||||
deceleration: 250 steps/s^2
|
||||
- stepper.report_position:
|
||||
id: test_stepper
|
||||
position: !lambda return 100;
|
||||
- stepper.set_speed:
|
||||
id: test_stepper
|
||||
speed: !lambda return 200.0f;
|
||||
- stepper.set_deceleration:
|
||||
id: test_stepper
|
||||
deceleration: !lambda return 180.0f;
|
||||
Loaded 100 of 119 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user