mirror of
https://github.com/esphome/esphome.git
synced 2026-10-26 16:02:13 +00:00
Compare commits
100
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74847eeccc | ||
|
|
d9ae0e2cd4 | ||
|
|
857564a23d | ||
|
|
6c5010032b | ||
|
|
746691d5c2 | ||
|
|
93ec8d9d95 | ||
|
|
dea1200c35 | ||
|
|
abd95c5154 | ||
|
|
cdc77e2126 | ||
|
|
04a53264b4 | ||
|
|
499ce56c2b | ||
|
|
72ac32a2c4 | ||
|
|
a7e0c0f8a2 | ||
|
|
1ea45be19e | ||
|
|
7d5117e570 | ||
|
|
8132fbef52 | ||
|
|
9744058201 | ||
|
|
1de5a58dbe | ||
|
|
d2c33b0f6f | ||
|
|
0d445fb3bc | ||
|
|
76de6a672c | ||
|
|
2dc7a64e52 | ||
|
|
a61746045b | ||
|
|
a35299af0b | ||
|
|
3f2de92e5d | ||
|
|
b8c32854ed | ||
|
|
0b2e5bfbbe | ||
|
|
a1d8ea6072 | ||
|
|
93fabe8b84 | ||
|
|
2fc48a3d9e | ||
|
|
1a158e5ba0 | ||
|
|
0569a1262f | ||
|
|
0b5a1748f7 | ||
|
|
d71a6e662b | ||
|
|
c8d3f43712 | ||
|
|
01a2aaef08 | ||
|
|
447348d617 | ||
|
|
c902083ef3 | ||
|
|
d1dd4e53ce | ||
|
|
c0c1643efe | ||
|
|
532a6ed239 | ||
|
|
3a9048cc62 | ||
|
|
d34b21cb7e | ||
|
|
5043e12690 | ||
|
|
3e85af29c6 | ||
|
|
1889f6c9b5 | ||
|
|
2f4eb3e1ee | ||
|
|
bcae1f28ef | ||
|
|
87b10d70a5 | ||
|
|
cfb7d8089e | ||
|
|
bfe1f7bd17 | ||
|
|
d5d0901e2c | ||
|
|
64388318e8 | ||
|
|
e83dc25301 | ||
|
|
a81cf8f5cc | ||
|
|
6925c5127e | ||
|
|
d96d0b80f7 | ||
|
|
27ea1a90e3 | ||
|
|
4f47913745 | ||
|
|
447a30da0f | ||
|
|
d8696c4f28 | ||
|
|
0ef68c17fd | ||
|
|
aef3a586de | ||
|
|
5aeab5f361 | ||
|
|
9654dd3912 | ||
|
|
6622e1fe64 | ||
|
|
0c548f77fa | ||
|
|
dfd9d2639f | ||
|
|
cf9d807bf0 | ||
|
|
29512cf323 | ||
|
|
456e13adee | ||
|
|
a1b0e85a9e | ||
|
|
14bd50d469 | ||
|
|
1edc327835 | ||
|
|
288a7b376a | ||
|
|
c16f75f0e2 | ||
|
|
432cae58ae | ||
|
|
11c8e8c0c0 | ||
|
|
6a115e6b25 | ||
|
|
9c2616ef7e | ||
|
|
9ae83f2830 | ||
|
|
340e105a76 | ||
|
|
857a519946 | ||
|
|
15d756d559 | ||
|
|
22035cb9f5 | ||
|
|
32624e248c | ||
|
|
ad6996a63c | ||
|
|
262ade0598 | ||
|
|
2157ff5b4b | ||
|
|
63796e74bb | ||
|
|
a088eff428 | ||
|
|
0cb421f793 | ||
|
|
23c317f163 | ||
|
|
11e016be59 | ||
|
|
bdb492c196 | ||
|
|
d6831c8c75 | ||
|
|
c73a71e037 | ||
|
|
4bb6c0f877 | ||
|
|
eb85778a60 | ||
|
|
c561c6bbe8 |
No files matched your search
@@ -422,6 +422,7 @@ esphome/components/pn7150_i2c/* @jesserockz @kbx81
|
||||
esphome/components/pn7160/* @jesserockz @kbx81
|
||||
esphome/components/pn7160_i2c/* @jesserockz @kbx81
|
||||
esphome/components/pn7160_spi/* @jesserockz @kbx81
|
||||
esphome/components/pn71xx/* @jesserockz @kbx81
|
||||
esphome/components/power_supply/* @esphome/core
|
||||
esphome/components/preferences/* @esphome/core
|
||||
esphome/components/provisioning/* @esphome/core
|
||||
@@ -429,6 +430,7 @@ esphome/components/psram/* @esphome/core
|
||||
esphome/components/pulse_meter/* @cstaahl @stevebaxter @TrentHouliston
|
||||
esphome/components/pvvx_mithermometer/* @pasiz
|
||||
esphome/components/pylontech/* @functionpointer
|
||||
esphome/components/pzem6l24/* @nuttytree
|
||||
esphome/components/qmi8658/* @clydebarrow
|
||||
esphome/components/qmp6988/* @andrewpc
|
||||
esphome/components/qr_code/* @wjtje
|
||||
@@ -548,6 +550,7 @@ esphome/components/sx126x/* @swoboda1337
|
||||
esphome/components/sx127x/* @swoboda1337
|
||||
esphome/components/sy6970/* @linkedupbits
|
||||
esphome/components/syslog/* @clydebarrow
|
||||
esphome/components/systa_bus/* @Mat931
|
||||
esphome/components/t6615/* @tylermenezes
|
||||
esphome/components/tas2780/* @remcom
|
||||
esphome/components/tc74/* @sethgirvan
|
||||
@@ -562,6 +565,7 @@ esphome/components/template/datetime/* @rfdarter
|
||||
esphome/components/template/event/* @nohat
|
||||
esphome/components/template/fan/* @ssieb
|
||||
esphome/components/text/* @mauritskorse
|
||||
esphome/components/tfluna/* @candrews
|
||||
esphome/components/thermopro_ble/* @sittner
|
||||
esphome/components/thermostat/* @kbx81
|
||||
esphome/components/time/* @esphome/core
|
||||
@@ -638,6 +642,7 @@ esphome/components/wts01/* @alepee
|
||||
esphome/components/x9c/* @EtienneMD
|
||||
esphome/components/xdb401/* @RT530
|
||||
esphome/components/xgzp68xx/* @gcormier
|
||||
esphome/components/xiaomi_body_scale/* @dckiller51
|
||||
esphome/components/xiaomi_hhccjcy10/* @fariouche
|
||||
esphome/components/xiaomi_lywsd02mmc/* @juanluss31
|
||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ RUN \
|
||||
-r /requirements.txt
|
||||
|
||||
# Install the ESPHome Device Builder dashboard.
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.15.0
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.16.0
|
||||
|
||||
RUN \
|
||||
platformio settings set enable_telemetry No \
|
||||
|
||||
+59
-32
@@ -16,6 +16,7 @@ from typing import TYPE_CHECKING, Protocol
|
||||
# cause them to be loaded before external components are processed, resulting
|
||||
# in the built-in version being used instead of the external component one.
|
||||
from esphome import const, platform_hooks
|
||||
from esphome.build_helpers.native import analysis_backend, native_backend
|
||||
from esphome.const import (
|
||||
ALLOWED_NAME_CHARS,
|
||||
ARGUMENT_HELP_DEVICE,
|
||||
@@ -762,7 +763,6 @@ def _wrap_to_code(name, comp, yaml_util):
|
||||
|
||||
@functools.wraps(comp.to_code)
|
||||
async def wrapped(conf):
|
||||
cg.add(cg.ComponentMarker(name))
|
||||
cg.add(cg.LineComment(f"{name}:"))
|
||||
if comp.config_schema is not None:
|
||||
# sort_keys: voluptuous fills defaults in set order, so an
|
||||
@@ -818,7 +818,9 @@ def write_cpp_file() -> int:
|
||||
from esphome.build_gen import espidf
|
||||
|
||||
espidf.write_project()
|
||||
else:
|
||||
elif not CORE.using_native_toolchain:
|
||||
# Other native builds generate their project at compile time;
|
||||
# never write a platformio.ini for them
|
||||
from esphome.build_gen import platformio
|
||||
|
||||
platformio.write_project()
|
||||
@@ -860,20 +862,14 @@ def compile_program(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
toolchain.create_factory_bin()
|
||||
toolchain.create_ota_bin()
|
||||
toolchain.create_elf_copy()
|
||||
from esphome.build_helpers.idedata import IDEDATA_BEST_EFFORT_ERRORS
|
||||
from esphome.build_helpers.idedata import warn_if_idedata_missing
|
||||
|
||||
try:
|
||||
if toolchain.get_idedata() is None:
|
||||
_LOGGER.warning("No idedata was generated for this build")
|
||||
except IDEDATA_BEST_EFFORT_ERRORS as err:
|
||||
# The firmware already built; an idedata failure must not fail
|
||||
# a successful build.
|
||||
_LOGGER.warning(
|
||||
"Could not generate idedata: %s (IDE, clang-tidy, and "
|
||||
"memory-analysis data will be unavailable for this build)",
|
||||
err,
|
||||
)
|
||||
_LOGGER.debug("Idedata failure detail", exc_info=True)
|
||||
warn_if_idedata_missing(toolchain.get_idedata)
|
||||
elif CORE.using_native_toolchain:
|
||||
raise EsphomeError(
|
||||
f"Toolchain '{CORE.toolchain.value}' resolved but no platform "
|
||||
"backend claimed the build"
|
||||
)
|
||||
else:
|
||||
from esphome.platformio import toolchain
|
||||
|
||||
@@ -976,12 +972,15 @@ def upload_using_esptool(
|
||||
|
||||
if file is not None:
|
||||
flash_images = [FlashImage(path=file, offset="0x0")]
|
||||
elif CORE.using_toolchain_esp_idf:
|
||||
from esphome.espidf import toolchain
|
||||
|
||||
flash_images = [
|
||||
FlashImage(path=toolchain.get_factory_firmware_path(), offset="0x0")
|
||||
]
|
||||
elif (native := native_backend()) is not None:
|
||||
# Every native backend supplies its own 0x0 flash image (bootloader
|
||||
# and partitions included where the target needs them)
|
||||
image = native.get_factory_firmware_path()
|
||||
if not image.is_file():
|
||||
raise EsphomeError(
|
||||
f"{image} does not exist; compile the configuration first"
|
||||
)
|
||||
flash_images = [FlashImage(path=image, offset="0x0")]
|
||||
else:
|
||||
from esphome.platformio import toolchain
|
||||
|
||||
@@ -1971,12 +1970,12 @@ def command_update_all(args: ArgsProtocol) -> int | None:
|
||||
def command_idedata(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
import json
|
||||
|
||||
if CORE.using_toolchain_esp_idf:
|
||||
# Native ESP-IDF derives idedata from the build's compile_commands.json,
|
||||
# so the configuration must already be compiled.
|
||||
from esphome.espidf import toolchain as espidf_toolchain
|
||||
native_toolchain = native_backend()
|
||||
|
||||
idedata = espidf_toolchain.get_idedata()
|
||||
if native_toolchain is not None:
|
||||
# Native toolchains derive idedata from the build's
|
||||
# compile_commands.json, so the configuration must already be compiled.
|
||||
idedata = native_toolchain.get_idedata()
|
||||
if idedata is None:
|
||||
_LOGGER.error(
|
||||
"No idedata available; compile the configuration first",
|
||||
@@ -2015,6 +2014,17 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
from esphome.analyze_memory.cli import MemoryAnalyzerCLI
|
||||
from esphome.analyze_memory.ram_strings import RamStringsAnalyzer
|
||||
|
||||
# Refuse an unsupported toolchain before paying for a full compile
|
||||
analysis_toolchain = analysis_backend()
|
||||
if analysis_toolchain is None and not CORE.using_toolchain_platformio:
|
||||
_LOGGER.error(
|
||||
"analyze-memory is not supported with the '%s' toolchain on %s; "
|
||||
"re-run with --toolchain platformio",
|
||||
CORE.toolchain.value if CORE.toolchain else "unresolved",
|
||||
CORE.target_platform,
|
||||
)
|
||||
return 1
|
||||
|
||||
# Always compile to ensure fresh data (fast if no changes - just relinks)
|
||||
exit_code = write_cpp(config)
|
||||
if exit_code != 0:
|
||||
@@ -2026,13 +2036,30 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
|
||||
# Get idedata for analysis
|
||||
idedata = None
|
||||
if CORE.using_toolchain_esp_idf:
|
||||
from esphome.espidf import toolchain
|
||||
if analysis_toolchain is not None:
|
||||
objdump = analysis_toolchain.get_objdump_path()
|
||||
readelf = analysis_toolchain.get_readelf_path()
|
||||
for tool in (objdump, readelf):
|
||||
if not tool.is_file():
|
||||
# The analyzer would silently fall back to host
|
||||
# binutils, which cannot read the target ELF
|
||||
_LOGGER.error(
|
||||
"%s is missing; the toolchain install may be incomplete "
|
||||
"(recompile, or run 'esphome clean-all' if it persists)",
|
||||
tool,
|
||||
)
|
||||
return 1
|
||||
objdump_path = str(objdump)
|
||||
readelf_path = str(readelf)
|
||||
|
||||
objdump_path = str(toolchain.get_objdump_path())
|
||||
readelf_path = str(toolchain.get_readelf_path())
|
||||
|
||||
firmware_elf = toolchain.get_elf_path()
|
||||
firmware_elf = analysis_toolchain.get_elf_path()
|
||||
if not firmware_elf.is_file():
|
||||
# The analyzer swallows tool failures, so a missing ELF would
|
||||
# produce an exit-0 zeroed report
|
||||
_LOGGER.error(
|
||||
"%s is missing; compile the configuration first", firmware_elf
|
||||
)
|
||||
return 1
|
||||
else:
|
||||
from esphome.platformio import toolchain
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ from __future__ import annotations
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable
|
||||
import heapq
|
||||
import json
|
||||
from operator import itemgetter
|
||||
from pathlib import Path
|
||||
import sys
|
||||
@@ -43,7 +42,7 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
|
||||
# Symbol size threshold for detailed analysis
|
||||
SYMBOL_SIZE_THRESHOLD: int = (
|
||||
10 # Show symbols larger than this in detailed analysis
|
||||
100 # Show symbols larger than this in detailed analysis
|
||||
)
|
||||
# Lower threshold for RAM symbols (RAM is more constrained)
|
||||
RAM_SYMBOL_SIZE_THRESHOLD: int = 24
|
||||
|
||||
@@ -68,12 +68,16 @@ def idedata_candidates(build_path: Path) -> list[Path]:
|
||||
The candidate idedata JSON paths, most specific first
|
||||
"""
|
||||
name = build_path.name
|
||||
data_dir = build_path.parent.parent / "idedata"
|
||||
# Native backends suffix the cache by toolchain (<name>.arduino.json)
|
||||
suffixed = sorted(data_dir.glob(f"{name}.*.json")) if data_dir.is_dir() else []
|
||||
return [
|
||||
# In .pioenvs for test builds
|
||||
build_path / ".pioenvs" / name / "idedata.json",
|
||||
# Both toolchains cache it in the data dir, which holds this build dir:
|
||||
# <data_dir>/idedata/<name>.json next to <data_dir>/build/<name>
|
||||
build_path.parent.parent / "idedata" / f"{name}.json",
|
||||
*suffixed,
|
||||
# Regular builds, invoked from the config dir or from anywhere
|
||||
Path.cwd() / ".esphome" / "idedata" / f"{name}.json",
|
||||
Path.home() / ".esphome" / "idedata" / f"{name}.json",
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
"""Native Arduino ESP8266 build driver (the PlatformIO ``run`` equivalent)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from esphome.build_helpers.ccache import resolve_ccache_path
|
||||
from esphome.const import (
|
||||
CONF_COMPILE_PROCESS_LIMIT,
|
||||
CONF_ESPHOME,
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError
|
||||
from esphome.helpers import write_file
|
||||
from esphome.types import ConfigType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from esphome.arduino8266.framework import InstalledPaths
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# ESP8266 user RAM (matches upload.maximum_ram_size in every board manifest)
|
||||
_MAX_RAM_SIZE = 81920
|
||||
|
||||
|
||||
def _warn_ignored_platformio_options() -> None:
|
||||
"""Warn for component-added platformio options the native build drops."""
|
||||
from esphome.core.config import NATIVE_ARDUINO_CONSUMED_PIO_OPTIONS
|
||||
|
||||
consumed = NATIVE_ARDUINO_CONSUMED_PIO_OPTIONS
|
||||
for key in sorted(CORE.platformio_options or {}):
|
||||
if key not in consumed:
|
||||
_LOGGER.warning(
|
||||
"platformio_options->%s is ignored when building with the "
|
||||
"native 'arduino' toolchain",
|
||||
key,
|
||||
)
|
||||
|
||||
|
||||
_RAM_SECTIONS = (".data", ".rodata", ".bss")
|
||||
_FLASH_SECTIONS = (".irom0.text", ".text", ".text1", ".data", ".rodata")
|
||||
|
||||
|
||||
def get_build_dir() -> Path:
|
||||
return CORE.relative_pioenvs_path(CORE.name)
|
||||
|
||||
|
||||
def get_elf_path() -> Path:
|
||||
return get_build_dir() / "firmware.elf"
|
||||
|
||||
|
||||
def _toolchain_tool(name: str) -> Path:
|
||||
# Imported here, not at module scope: the serial upload/logs fast path
|
||||
# resolves this module for its artifact paths alone, and framework
|
||||
# pulls in the whole package-download stack
|
||||
from esphome.arduino8266 import framework
|
||||
|
||||
return framework.toolchain_tool(framework.get_toolchain_path(), name)
|
||||
|
||||
|
||||
def get_factory_firmware_path() -> Path:
|
||||
"""The image to serial-flash at 0x0 (same bytes as firmware.bin: the
|
||||
8266 factory copy exists for artifact-contract parity, not content)."""
|
||||
return get_build_dir() / "firmware.factory.bin"
|
||||
|
||||
|
||||
def get_addr2line_path() -> Path:
|
||||
return _toolchain_tool("addr2line")
|
||||
|
||||
|
||||
def get_objdump_path() -> Path:
|
||||
return _toolchain_tool("objdump")
|
||||
|
||||
|
||||
def get_readelf_path() -> Path:
|
||||
return _toolchain_tool("readelf")
|
||||
|
||||
|
||||
def run_compile(config: ConfigType, verbose: bool) -> int:
|
||||
from esphome.arduino8266 import framework
|
||||
from esphome.build_gen import arduino8266 as build_gen
|
||||
|
||||
_warn_ignored_platformio_options()
|
||||
paths = framework.check_and_install(CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION])
|
||||
# Resolved once: the probe is not free and three consumers need it
|
||||
ccache = resolve_ccache_path()
|
||||
build_gen.write_project(paths, ccache)
|
||||
|
||||
build_dir = get_build_dir()
|
||||
env = framework.get_build_env(paths.toolchain, ccache)
|
||||
|
||||
# The DB is a pure function of build.ninja; regenerate when it is older
|
||||
compdb = build_dir / "compile_commands.json"
|
||||
ninja_file = build_dir / "build.ninja"
|
||||
if not compdb.is_file() or compdb.stat().st_mtime < ninja_file.stat().st_mtime:
|
||||
_write_compile_commands(paths.ninja, build_dir, env)
|
||||
|
||||
cmd = [str(paths.ninja)]
|
||||
if verbose:
|
||||
cmd.append("-v")
|
||||
if jobs := config[CONF_ESPHOME].get(CONF_COMPILE_PROCESS_LIMIT):
|
||||
cmd += ["-j", str(jobs)]
|
||||
# Explicit targets: a generator defect that drops them fails loudly
|
||||
# instead of a green no-op run leaving stale artifacts in place
|
||||
targets = ["firmware.factory.bin", "firmware.ota.bin"]
|
||||
cmd += targets
|
||||
|
||||
# cwd, not -C: drops ninja's "Entering directory" banner
|
||||
_LOGGER.debug("Running: %s", " ".join(cmd))
|
||||
rc = subprocess.run(
|
||||
cmd, cwd=build_dir, env=env, check=False, close_fds=False
|
||||
).returncode
|
||||
if rc != 0:
|
||||
return rc
|
||||
|
||||
# ninja already refused missing targets; existence covers a rule that
|
||||
# ran but wrote elsewhere
|
||||
build_dir_artifacts = (
|
||||
get_elf_path(),
|
||||
build_dir / "firmware.bin",
|
||||
get_factory_firmware_path(),
|
||||
build_dir / "firmware.ota.bin",
|
||||
)
|
||||
for artifact in build_dir_artifacts:
|
||||
if not artifact.is_file():
|
||||
_LOGGER.error("Build produced no %s", artifact)
|
||||
return 1
|
||||
|
||||
if not _print_size_summary(build_dir, paths):
|
||||
# Cause already warned; name the consequence for CI harnesses
|
||||
_LOGGER.warning("Firmware size summary unavailable for this build")
|
||||
from esphome.build_helpers.idedata import warn_if_idedata_missing
|
||||
|
||||
warn_if_idedata_missing(lambda: get_idedata(ccache))
|
||||
return 0
|
||||
|
||||
|
||||
def _write_compile_commands(
|
||||
ninja_path: Path, build_dir: Path, env: dict[str, str]
|
||||
) -> None:
|
||||
compdb = build_dir / "compile_commands.json"
|
||||
result = subprocess.run(
|
||||
[str(ninja_path), "-C", str(build_dir), "-t", "compdb", "c", "cxx", "asm"],
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
close_fds=False,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
# Drop any stale database so consumers (IDE integration, clang-tidy,
|
||||
# the memory analyzer) can't silently read outdated data.
|
||||
compdb.unlink(missing_ok=True)
|
||||
raise EsphomeError(f"Could not generate compile_commands.json: {result.stderr}")
|
||||
try:
|
||||
entries = json.loads(result.stdout)
|
||||
except ValueError as err:
|
||||
compdb.unlink(missing_ok=True)
|
||||
raise EsphomeError(
|
||||
f"ninja produced an unparsable compile database: {err} "
|
||||
f"(output starts {result.stdout[:120]!r})"
|
||||
) from err
|
||||
if not entries:
|
||||
# compdb exits 0 with [] for unknown rule names; a renamed compile
|
||||
# rule must fail the build, not silently strand every consumer
|
||||
compdb.unlink(missing_ok=True)
|
||||
raise EsphomeError(
|
||||
"ninja produced an empty compile database; the generator's rule "
|
||||
"names no longer match"
|
||||
)
|
||||
write_file(compdb, result.stdout)
|
||||
|
||||
|
||||
def _parse_app_size(build_dir: Path, paths: InstalledPaths) -> int | None:
|
||||
"""Read the app flash budget (irom0_0_seg length) from the linker script."""
|
||||
from esphome.build_gen.arduino8266 import get_flash_ld_path
|
||||
from esphome.components.esp8266.build_surgery import segment_length
|
||||
|
||||
# Warnings, not debug: without the app size the Flash summary line is
|
||||
# dropped and CI's memory-impact extraction loses its flash metric.
|
||||
ld_path = get_flash_ld_path(build_dir, paths)
|
||||
try:
|
||||
ld_text = ld_path.read_text(encoding="utf-8")
|
||||
except (OSError, UnicodeDecodeError) as err:
|
||||
# A corrupt script degrades the same way, never aborts the build
|
||||
_LOGGER.warning("Cannot read linker script for the Flash summary: %s", err)
|
||||
return None
|
||||
if not (app_size := segment_length(ld_text, "irom0_0_seg")):
|
||||
_LOGGER.warning("No usable irom0_0_seg in %s; skipping Flash summary", ld_path)
|
||||
return None
|
||||
return app_size
|
||||
|
||||
|
||||
def _print_size_summary(build_dir: Path, paths: InstalledPaths) -> bool:
|
||||
"""Print the RAM/Flash lines ``ci_memory_impact_extract.py`` parses;
|
||||
False when skipped."""
|
||||
from esphome.arduino8266.framework import toolchain_tool
|
||||
from esphome.build_helpers.size_summary import print_size_line
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[
|
||||
str(toolchain_tool(paths.toolchain, "size")),
|
||||
"-A",
|
||||
"-d",
|
||||
str(build_dir / "firmware.elf"),
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
close_fds=False,
|
||||
)
|
||||
except (OSError, subprocess.CalledProcessError) as err:
|
||||
# The summary is a bonus artifact like idedata; a truncated
|
||||
# toolchain extraction must not discard an already-linked build
|
||||
_LOGGER.warning("Could not summarize firmware size: %s", err)
|
||||
return False
|
||||
# -d prints decimal sizes; anything else trips the missing-sections guard
|
||||
sections = {
|
||||
parts[0]: int(parts[1])
|
||||
for line in result.stdout.splitlines()
|
||||
if (parts := line.split())[:1] and parts[0].startswith(".") and len(parts) >= 2
|
||||
if parts[1].isdigit()
|
||||
}
|
||||
if missing := set(_RAM_SECTIONS + _FLASH_SECTIONS) - set(sections):
|
||||
# A defaulted 0 would print a confidently wrong total for CI's metric
|
||||
_LOGGER.warning(
|
||||
"Size output is missing section(s) %s; skipping the size summary",
|
||||
", ".join(sorted(missing)),
|
||||
)
|
||||
return False
|
||||
# Resolve the flash budget before printing: a RAM line without its
|
||||
# Flash line would skew CI's memory-impact extraction
|
||||
app_size = _parse_app_size(build_dir, paths)
|
||||
if not app_size:
|
||||
return False
|
||||
ram = sum(sections[s] for s in _RAM_SECTIONS)
|
||||
flash = sum(sections[s] for s in _FLASH_SECTIONS)
|
||||
print_size_line("RAM", ram, _MAX_RAM_SIZE)
|
||||
print_size_line("Flash", flash, app_size)
|
||||
return True
|
||||
|
||||
|
||||
def get_idedata(ccache: str | None = None) -> dict | None:
|
||||
"""Derive idedata from the build's compile_commands.json (same
|
||||
contract as ``espidf.toolchain.get_idedata``)."""
|
||||
from esphome.build_helpers.idedata import load_or_build_idedata
|
||||
|
||||
# A disabled ccache resolves to None without spawning anything, so
|
||||
# re-resolving here costs nothing when the caller has no answer
|
||||
launcher = ccache or resolve_ccache_path()
|
||||
return load_or_build_idedata(
|
||||
get_build_dir() / "compile_commands.json",
|
||||
get_elf_path(),
|
||||
# Suffixed so a platformio->arduino->platformio round trip on one
|
||||
# config never serves the other toolchain's cache shape
|
||||
CORE.relative_internal_path("idedata", f"{CORE.name}.arduino.json"),
|
||||
# The compile DB's commands carry the same ccache prefix the ninja
|
||||
# rules were generated with
|
||||
launcher=str(launcher) if launcher else None,
|
||||
)
|
||||
@@ -239,9 +239,9 @@ class ApplyCall:
|
||||
|
||||
Each arg is ``(conf_key, type_)`` or ``(conf_key, type_, const_fn)``. A ``conf_key`` may be a
|
||||
path into nested sections. A plain ``str`` ``type_`` is raw C++ type text and may use
|
||||
``{parent}``. ``const_fn(config, value)`` renders a constant's argument text; a lambda bypasses
|
||||
it. The statement is skipped when none of its keys is set, always emitted when it has no
|
||||
keys, and a partial set is a config error.
|
||||
``{parent}``. ``const_fn(config, value)`` renders a constant's argument text; a lambda or an
|
||||
id bypasses it. The statement is skipped when none of its keys is set, always emitted when it
|
||||
has no keys, and a partial set is a config error.
|
||||
"""
|
||||
|
||||
target: str
|
||||
@@ -280,8 +280,8 @@ class ApplyField:
|
||||
Double a literal brace in a template. ``conf_key`` may be a path into nested sections.
|
||||
``type_`` may be a C++ type string using ``{parent}`` when the type is only known per
|
||||
instance. ``const_fn(config, value)`` renders a constant's argument text when ``cg.safe_exp``
|
||||
is not the right spelling (unit conversion belongs in the validator); a lambda bypasses it,
|
||||
so the target must also take a plain ``type_``. An absent key emits nothing.
|
||||
is not the right spelling (unit conversion belongs in the validator); a lambda or an id
|
||||
bypasses it, so the target must also take a plain ``type_``. An absent key emits nothing.
|
||||
"""
|
||||
|
||||
conf_key: str | tuple[str, ...]
|
||||
@@ -380,6 +380,9 @@ async def _render_values(
|
||||
expr = call_lambda(inner)
|
||||
bare = compare and isinstance(expr, cg.RawExpression)
|
||||
exprs.append(f"({expr})" if bare else str(expr))
|
||||
elif isinstance(value, ID):
|
||||
# Qualified like the parent, so a trigger arg named like the id cannot shadow it.
|
||||
exprs.append(f"::{await cg.get_variable(value)}")
|
||||
elif const_fn is not None:
|
||||
exprs.append(const_fn(config, value))
|
||||
else:
|
||||
@@ -402,6 +405,7 @@ def register_apply_action(
|
||||
|
||||
Generates one stateless function for ``ApplyAction<Ts...>``: the parent (read from
|
||||
``id_key``) and constants are baked in, lambdas are called inline with the trigger args.
|
||||
A constant that is an id (``cv.use_id`` under ``cv.templatable``) is the object it names.
|
||||
With ``call`` every statement targets the call object ``auto apply_call = parent->call()``,
|
||||
and ``apply_call.perform()`` is appended.
|
||||
"""
|
||||
|
||||
@@ -1132,7 +1132,8 @@ def write_project(paths: InstalledPaths, ccache: str | None) -> bool:
|
||||
"rule elf2bin",
|
||||
# --flash_size deliberately stays board-derived, as under
|
||||
# PlatformIO (which reads upload.maximum_size, not the ldscript).
|
||||
f" command = $python {_q(framework / 'tools' / 'elf2bin.py')} --eboot {_q(framework / 'bootloaders' / 'eboot' / 'eboot.elf')} --app $in --flash_mode {flash_mode} --flash_freq {_FLASH_FREQ_MHZ} --flash_size {_flash_size_str(BOARDS[board][KEY_FLASH_SIZE])} --path {_q(toolchain_bin)} --out $out",
|
||||
# -W: the framework's own elf2bin.py trips SyntaxWarning on 3.12+.
|
||||
f" command = $python -W ignore::SyntaxWarning {_q(framework / 'tools' / 'elf2bin.py')} --eboot {_q(framework / 'bootloaders' / 'eboot' / 'eboot.elf')} --app $in --flash_mode {flash_mode} --flash_freq {_FLASH_FREQ_MHZ} --flash_size {_flash_size_str(BOARDS[board][KEY_FLASH_SIZE])} --path {_q(toolchain_bin)} --out $out",
|
||||
" description = BIN $out",
|
||||
"rule copy",
|
||||
" command = $python $buildtool copy $in $out",
|
||||
|
||||
@@ -11,6 +11,7 @@ consumers (IDE integration, clang-tidy) expect:
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
@@ -21,6 +22,8 @@ import subprocess
|
||||
from esphome.core import EsphomeError
|
||||
from esphome.helpers import write_file
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Everything idedata generation may raise after a successful link; idedata
|
||||
# is a bonus artifact, so consumers warn instead of failing the build
|
||||
IDEDATA_BEST_EFFORT_ERRORS = (
|
||||
@@ -31,7 +34,21 @@ IDEDATA_BEST_EFFORT_ERRORS = (
|
||||
ValueError,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
def warn_if_idedata_missing(get_idedata: Callable[[], dict | None]) -> None:
|
||||
"""Run an idedata generator, downgrading any failure to a warning:
|
||||
the firmware already built."""
|
||||
try:
|
||||
if get_idedata() is None:
|
||||
_LOGGER.warning("No idedata was generated for this build")
|
||||
except IDEDATA_BEST_EFFORT_ERRORS as err:
|
||||
_LOGGER.warning(
|
||||
"Could not generate idedata: %s (IDE, clang-tidy, and "
|
||||
"memory-analysis data will be unavailable for this build)",
|
||||
err,
|
||||
)
|
||||
_LOGGER.debug("Idedata failure detail", exc_info=True)
|
||||
|
||||
|
||||
# C++ translation-unit suffixes used to identify ESPHome source files.
|
||||
_CXX_SUFFIXES = (".cpp", ".cc")
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
"""Resolution of the native (non-PlatformIO) build backend for a config.
|
||||
|
||||
Kept deliberately light: the serial upload and logs fast path resolves a
|
||||
backend for its artifact paths alone, so importing this must not pull in a
|
||||
platform component package or the backend itself.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
from types import ModuleType
|
||||
|
||||
from esphome.const import Toolchain
|
||||
from esphome.core import CORE, EsphomeError
|
||||
|
||||
# Native build backend per (target platform, toolchain)
|
||||
NATIVE_TOOLCHAIN_MODULES = {
|
||||
("esp32", Toolchain.ESP_IDF): "esphome.espidf.toolchain",
|
||||
("esp8266", Toolchain.ARDUINO): "esphome.arduino8266.toolchain",
|
||||
}
|
||||
|
||||
|
||||
def native_backend() -> ModuleType | None:
|
||||
"""The native build backend module for the resolved toolchain."""
|
||||
if not CORE.using_native_toolchain:
|
||||
return None
|
||||
key = (CORE.target_platform, CORE.toolchain)
|
||||
if (module_path := NATIVE_TOOLCHAIN_MODULES.get(key)) is None:
|
||||
# Degrading to the PlatformIO path would build with the wrong backend
|
||||
raise EsphomeError(
|
||||
f"Toolchain '{CORE.toolchain.value}' has no native build backend "
|
||||
f"module for platform {CORE.target_platform}"
|
||||
)
|
||||
return importlib.import_module(module_path)
|
||||
|
||||
|
||||
# Binutils and the linked image for memory analysis, for toolchains that build
|
||||
# without PlatformIO but have no native build backend (which supplies them)
|
||||
ANALYSIS_TOOLCHAIN_MODULES = {
|
||||
("nrf52", Toolchain.SDK_NRF): "esphome.components.nrf52.toolchain",
|
||||
}
|
||||
|
||||
|
||||
def analysis_backend() -> ModuleType | None:
|
||||
"""The module giving objdump, readelf and the ELF of a non-PlatformIO build.
|
||||
|
||||
None means PlatformIO's idedata supplies them (or nothing can).
|
||||
"""
|
||||
if (native := native_backend()) is not None:
|
||||
return native
|
||||
module_path = ANALYSIS_TOOLCHAIN_MODULES.get((CORE.target_platform, CORE.toolchain))
|
||||
return importlib.import_module(module_path) if module_path else None
|
||||
@@ -10,7 +10,6 @@
|
||||
# pylint: disable=unused-import
|
||||
from esphome.cpp_generator import ( # noqa: F401
|
||||
ArrayInitializer,
|
||||
ComponentMarker,
|
||||
Expression,
|
||||
FlashStringLiteral,
|
||||
LineComment,
|
||||
|
||||
@@ -106,6 +106,20 @@ bool AGS10Component::set_zero_point_with_factory_defaults() { return this->set_z
|
||||
|
||||
bool AGS10Component::set_zero_point_with_current_resistance() { return this->set_zero_point_with(ZP_CURRENT); }
|
||||
|
||||
void AGS10Component::set_zero_point(AGS10SetZeroPointActionMode mode, uint16_t value) {
|
||||
switch (mode) {
|
||||
case FACTORY_DEFAULT:
|
||||
this->set_zero_point_with_factory_defaults();
|
||||
break;
|
||||
case CURRENT_VALUE:
|
||||
this->set_zero_point_with_current_resistance();
|
||||
break;
|
||||
case CUSTOM_VALUE:
|
||||
this->set_zero_point_with(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool AGS10Component::set_zero_point_with(uint16_t value) {
|
||||
std::array<uint8_t, 5> data{0x00, 0x0C, (uint8_t) ((value >> 8) & 0xFF), (uint8_t) (value & 0xFF), 0};
|
||||
data[4] = crc8(data.data(), 4, 0xFF, 0x31, true);
|
||||
|
||||
@@ -2,11 +2,19 @@
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome::ags10 {
|
||||
|
||||
enum AGS10SetZeroPointActionMode {
|
||||
// Zero-point reset.
|
||||
FACTORY_DEFAULT,
|
||||
// Zero-point calibration with current resistance.
|
||||
CURRENT_VALUE,
|
||||
// Zero-point calibration with custom resistance.
|
||||
CUSTOM_VALUE,
|
||||
};
|
||||
|
||||
class AGS10Component final : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
/**
|
||||
@@ -47,6 +55,11 @@ class AGS10Component final : public PollingComponent, public i2c::I2CDevice {
|
||||
*/
|
||||
bool set_zero_point_with_current_resistance();
|
||||
|
||||
/**
|
||||
* Sets zero-point by mode; the value is only used for CUSTOM_VALUE.
|
||||
*/
|
||||
void set_zero_point(AGS10SetZeroPointActionMode mode, uint16_t value);
|
||||
|
||||
/**
|
||||
* Sets zero-point with the value.
|
||||
*/
|
||||
@@ -100,39 +113,4 @@ class AGS10Component final : public PollingComponent, public i2c::I2CDevice {
|
||||
template<size_t N> optional<std::array<uint8_t, N>> read_and_check_(uint8_t a_register);
|
||||
};
|
||||
|
||||
template<typename... Ts> class AGS10NewI2cAddressAction final : public Action<Ts...>, public Parented<AGS10Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, new_address)
|
||||
|
||||
void play(const Ts &...x) override { this->parent_->new_i2c_address(this->new_address_.value(x...)); }
|
||||
};
|
||||
|
||||
enum AGS10SetZeroPointActionMode {
|
||||
// Zero-point reset.
|
||||
FACTORY_DEFAULT,
|
||||
// Zero-point calibration with current resistance.
|
||||
CURRENT_VALUE,
|
||||
// Zero-point calibration with custom resistance.
|
||||
CUSTOM_VALUE,
|
||||
};
|
||||
|
||||
template<typename... Ts> class AGS10SetZeroPointAction final : public Action<Ts...>, public Parented<AGS10Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, value)
|
||||
TEMPLATABLE_VALUE(AGS10SetZeroPointActionMode, mode)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
switch (this->mode_.value(x...)) {
|
||||
case FACTORY_DEFAULT:
|
||||
this->parent_->set_zero_point_with_factory_defaults();
|
||||
break;
|
||||
case CURRENT_VALUE:
|
||||
this->parent_->set_zero_point_with_current_resistance();
|
||||
break;
|
||||
case CUSTOM_VALUE:
|
||||
this->parent_->set_zero_point_with(this->value_.value(x...));
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace esphome::ags10
|
||||
@@ -17,8 +17,6 @@ from esphome.const import (
|
||||
UNIT_OHM,
|
||||
UNIT_PARTS_PER_BILLION,
|
||||
)
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CONF_RESISTANCE = "resistance"
|
||||
@@ -28,12 +26,6 @@ DEPENDENCIES = ["i2c"]
|
||||
ags10_ns = cg.esphome_ns.namespace("ags10")
|
||||
AGS10Component = ags10_ns.class_("AGS10Component", cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
# Actions
|
||||
AGS10NewI2cAddressAction = ags10_ns.class_(
|
||||
"AGS10NewI2cAddressAction", automation.Action
|
||||
)
|
||||
AGS10SetZeroPointAction = ags10_ns.class_("AGS10SetZeroPointAction", automation.Action)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -91,24 +83,11 @@ AGS10_NEW_I2C_ADDRESS_SCHEMA = cv.maybe_simple_value(
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"ags10.new_i2c_address",
|
||||
AGS10NewI2cAddressAction,
|
||||
AGS10_NEW_I2C_ADDRESS_SCHEMA,
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_ADDRESS, "new_i2c_address", cg.uint8),
|
||||
)
|
||||
async def ags10newi2caddress_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
address = await cg.templatable(config[CONF_ADDRESS], args, cg.uint8)
|
||||
cg.add(var.set_new_address(address))
|
||||
return var
|
||||
|
||||
|
||||
AGS10SetZeroPointActionMode = ags10_ns.enum("AGS10SetZeroPointActionMode")
|
||||
AGS10_SET_ZERO_POINT_ACTION_MODE = {
|
||||
@@ -128,24 +107,11 @@ AGS10_SET_ZERO_POINT_SCHEMA = cv.Schema(
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"ags10.set_zero_point",
|
||||
AGS10SetZeroPointAction,
|
||||
AGS10_SET_ZERO_POINT_SCHEMA,
|
||||
synchronous=True,
|
||||
automation.ApplyCall(
|
||||
"set_zero_point({}, {})",
|
||||
((CONF_MODE, AGS10SetZeroPointActionMode), (CONF_VALUE, cg.uint16)),
|
||||
),
|
||||
)
|
||||
async def ags10setzeropoint_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
mode = await cg.templatable(
|
||||
config.get(CONF_MODE), args, AGS10SetZeroPointActionMode
|
||||
)
|
||||
cg.add(var.set_mode(mode))
|
||||
value = await cg.templatable(config[CONF_VALUE], args, cg.uint16)
|
||||
cg.add(var.set_value(value))
|
||||
return var
|
||||
@@ -4,8 +4,6 @@ from esphome.components import i2c
|
||||
from esphome.components.audio_dac import AudioDac
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_MODE
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@kbx81"]
|
||||
@@ -14,7 +12,6 @@ DEPENDENCIES = ["i2c"]
|
||||
aic3204_ns = cg.esphome_ns.namespace("aic3204")
|
||||
AIC3204 = aic3204_ns.class_("AIC3204", AudioDac, cg.Component, i2c.I2CDevice)
|
||||
|
||||
SetAutoMuteAction = aic3204_ns.class_("SetAutoMuteAction", automation.Action)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
@@ -36,25 +33,11 @@ SET_AUTO_MUTE_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"aic3204.set_auto_mute_mode",
|
||||
SetAutoMuteAction,
|
||||
SET_AUTO_MUTE_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_MODE, "set_auto_mute_mode", cg.uint8),
|
||||
)
|
||||
async def aic3204_set_volume_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
template_ = await cg.templatable(config.get(CONF_MODE), args, cg.uint8)
|
||||
cg.add(var.set_auto_mute_mode(template_))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "aic3204.h"
|
||||
|
||||
namespace esphome::aic3204 {
|
||||
|
||||
template<typename... Ts> class SetAutoMuteAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit SetAutoMuteAction(AIC3204 *aic3204) : aic3204_(aic3204) {}
|
||||
|
||||
TEMPLATABLE_VALUE(uint8_t, auto_mute_mode)
|
||||
|
||||
void play(const Ts &...x) override { this->aic3204_->set_auto_mute_mode(this->auto_mute_mode_.value(x...)); }
|
||||
|
||||
protected:
|
||||
AIC3204 *aic3204_;
|
||||
};
|
||||
|
||||
} // namespace esphome::aic3204
|
||||
@@ -42,10 +42,6 @@ StateEnterForwarder = alarm_control_panel_ns.class_("StateEnterForwarder")
|
||||
AlarmControlPanelState = alarm_control_panel_ns.enum("AlarmControlPanelState")
|
||||
|
||||
|
||||
AlarmControlPanelCondition = alarm_control_panel_ns.class_(
|
||||
"AlarmControlPanelCondition", automation.Condition
|
||||
)
|
||||
|
||||
_ALARM_CONTROL_PANEL_SCHEMA = (
|
||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||
@@ -216,18 +212,10 @@ for _name, _call in (
|
||||
)
|
||||
|
||||
|
||||
automation.register_simple_condition(
|
||||
"alarm_control_panel.ready",
|
||||
AlarmControlPanelCondition,
|
||||
ALARM_CONTROL_PANEL_CONDITION_SCHEMA,
|
||||
)
|
||||
|
||||
|
||||
automation.register_simple_condition(
|
||||
"alarm_control_panel.is_armed",
|
||||
AlarmControlPanelCondition,
|
||||
ALARM_CONTROL_PANEL_CONDITION_SCHEMA,
|
||||
)
|
||||
for _name in ("alarm_control_panel.ready", "alarm_control_panel.is_armed"):
|
||||
automation.register_apply_condition(
|
||||
_name, ALARM_CONTROL_PANEL_CONDITION_SCHEMA, "is_armed_pending_or_triggered()"
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.CORE)
|
||||
|
||||
@@ -130,6 +130,12 @@ class AlarmControlPanel : public EntityBase {
|
||||
|
||||
// is the state one of the armed states
|
||||
bool is_state_armed(AlarmControlPanelState state);
|
||||
/// Armed, pending (entry delay) or triggered; not ARMING (exit delay). Backs the is_armed and ready
|
||||
/// conditions.
|
||||
bool is_armed_pending_or_triggered() {
|
||||
auto state = this->get_state();
|
||||
return this->is_state_armed(state) || state == ACP_STATE_PENDING || state == ACP_STATE_TRIGGERED;
|
||||
}
|
||||
|
||||
protected:
|
||||
friend AlarmControlPanelCall;
|
||||
|
||||
@@ -27,16 +27,4 @@ static_assert(std::is_trivially_copyable_v<StateAnyForwarder>);
|
||||
static_assert(sizeof(StateEnterForwarder<ACP_STATE_TRIGGERED>) <= sizeof(void *));
|
||||
static_assert(std::is_trivially_copyable_v<StateEnterForwarder<ACP_STATE_TRIGGERED>>);
|
||||
|
||||
template<typename... Ts> class AlarmControlPanelCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
AlarmControlPanelCondition(AlarmControlPanel *parent) : parent_(parent) {}
|
||||
bool check(const Ts &...x) override {
|
||||
return this->parent_->is_state_armed(this->parent_->get_state()) ||
|
||||
this->parent_->get_state() == ACP_STATE_PENDING || this->parent_->get_state() == ACP_STATE_TRIGGERED;
|
||||
}
|
||||
|
||||
protected:
|
||||
AlarmControlPanel *parent_;
|
||||
};
|
||||
|
||||
} // namespace esphome::alarm_control_panel
|
||||
@@ -1,8 +1,6 @@
|
||||
#pragma once
|
||||
#include "esphome/components/image/image.h"
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
|
||||
namespace esphome::animation {
|
||||
|
||||
class Animation final : public image::Image {
|
||||
@@ -35,32 +33,4 @@ class Animation final : public image::Image {
|
||||
int loop_current_iteration_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class AnimationNextFrameAction final : public Action<Ts...> {
|
||||
public:
|
||||
AnimationNextFrameAction(Animation *parent) : parent_(parent) {}
|
||||
void play(const Ts &...x) override { this->parent_->next_frame(); }
|
||||
|
||||
protected:
|
||||
Animation *parent_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class AnimationPrevFrameAction final : public Action<Ts...> {
|
||||
public:
|
||||
AnimationPrevFrameAction(Animation *parent) : parent_(parent) {}
|
||||
void play(const Ts &...x) override { this->parent_->prev_frame(); }
|
||||
|
||||
protected:
|
||||
Animation *parent_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class AnimationSetFrameAction final : public Action<Ts...> {
|
||||
public:
|
||||
AnimationSetFrameAction(Animation *parent) : parent_(parent) {}
|
||||
TEMPLATABLE_VALUE(uint16_t, frame)
|
||||
void play(const Ts &...x) override { this->parent_->set_frame(this->frame_.value(x...)); }
|
||||
|
||||
protected:
|
||||
Animation *parent_;
|
||||
};
|
||||
|
||||
} // namespace esphome::animation
|
||||
@@ -6,8 +6,6 @@ from esphome.components.file.image import image_schema, write_image
|
||||
from esphome.components.image import Image_, validate_settings
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_REPEAT
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@syndlex"]
|
||||
@@ -26,17 +24,6 @@ animation_ns = cg.esphome_ns.namespace("animation")
|
||||
|
||||
Animation_ = animation_ns.class_("Animation", Image_)
|
||||
|
||||
# Actions
|
||||
NextFrameAction = animation_ns.class_(
|
||||
"AnimationNextFrameAction", automation.Action, cg.Parented.template(Animation_)
|
||||
)
|
||||
PrevFrameAction = animation_ns.class_(
|
||||
"AnimationPrevFrameAction", automation.Action, cg.Parented.template(Animation_)
|
||||
)
|
||||
SetFrameAction = animation_ns.class_(
|
||||
"AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_)
|
||||
)
|
||||
|
||||
ANIMATION_SCHEMA = image_schema(Animation_).extend(
|
||||
{
|
||||
cv.Optional(CONF_LOOP): cv.All(
|
||||
@@ -72,28 +59,17 @@ SET_FRAME_SCHEMA = cv.Schema(
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"animation.next_frame", NextFrameAction, NEXT_FRAME_SCHEMA, synchronous=True
|
||||
automation.register_apply_action(
|
||||
"animation.next_frame", NEXT_FRAME_SCHEMA, automation.ApplyCall("next_frame()")
|
||||
)
|
||||
@automation.register_action(
|
||||
"animation.prev_frame", PrevFrameAction, PREV_FRAME_SCHEMA, synchronous=True
|
||||
automation.register_apply_action(
|
||||
"animation.prev_frame", PREV_FRAME_SCHEMA, automation.ApplyCall("prev_frame()")
|
||||
)
|
||||
@automation.register_action(
|
||||
"animation.set_frame", SetFrameAction, SET_FRAME_SCHEMA, synchronous=True
|
||||
automation.register_apply_action(
|
||||
"animation.set_frame",
|
||||
SET_FRAME_SCHEMA,
|
||||
automation.ApplyField(CONF_FRAME, "set_frame", cg.uint16),
|
||||
)
|
||||
async def animation_action_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
if (frame := config.get(CONF_FRAME)) is not None:
|
||||
template_ = await cg.templatable(frame, args, cg.uint16)
|
||||
cg.add(var.set_frame(template_))
|
||||
return var
|
||||
|
||||
|
||||
async def setup_animation(config: ConfigType) -> None:
|
||||
|
||||
@@ -803,6 +803,9 @@ message SwitchStateResponse {
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool state = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
// If the switch does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
bool missing_state = 4;
|
||||
}
|
||||
message SwitchCommandRequest {
|
||||
option (id) = 33;
|
||||
@@ -1244,6 +1247,9 @@ message ClimateStateResponse {
|
||||
float current_humidity = 14;
|
||||
float target_humidity = 15;
|
||||
uint32 device_id = 16 [(field_ifdef) = "USE_DEVICES"];
|
||||
// If the climate device does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
bool missing_state = 17;
|
||||
}
|
||||
message ClimateCommandRequest {
|
||||
option (id) = 48;
|
||||
@@ -1330,6 +1336,9 @@ message WaterHeaterStateResponse {
|
||||
uint32 state = 6;
|
||||
float target_temperature_low = 7;
|
||||
float target_temperature_high = 8;
|
||||
// If the water heater does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
bool missing_state = 9;
|
||||
}
|
||||
|
||||
// Bitmask for WaterHeaterCommandRequest.has_fields
|
||||
|
||||
@@ -709,6 +709,7 @@ uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection
|
||||
auto *a_switch = static_cast<switch_::Switch *>(entity);
|
||||
SwitchStateResponse resp;
|
||||
resp.state = a_switch->state;
|
||||
resp.missing_state = !a_switch->has_state();
|
||||
return fill_and_encode_entity_state(a_switch, resp, conn, remaining_size);
|
||||
}
|
||||
|
||||
@@ -754,6 +755,7 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection
|
||||
auto traits = climate->get_traits();
|
||||
resp.mode = static_cast<enums::ClimateMode>(climate->mode);
|
||||
resp.action = static_cast<enums::ClimateAction>(climate->action);
|
||||
resp.missing_state = !climate->has_state();
|
||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE))
|
||||
resp.current_temperature = climate->current_temperature;
|
||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
||||
@@ -1447,6 +1449,7 @@ uint16_t APIConnection::try_send_water_heater_state(EntityBase *entity, APIConne
|
||||
auto *wh = static_cast<water_heater::WaterHeater *>(entity);
|
||||
WaterHeaterStateResponse resp;
|
||||
resp.mode = static_cast<enums::WaterHeaterMode>(wh->get_mode());
|
||||
resp.missing_state = !wh->has_state();
|
||||
resp.current_temperature = wh->get_current_temperature();
|
||||
resp.target_temperature = wh->get_target_temperature();
|
||||
resp.target_temperature_low = wh->get_target_temperature_low();
|
||||
|
||||
@@ -938,6 +938,7 @@ uint8_t *SwitchStateResponse::encode_msg(const void *self, ProtoWriteBuffer &buf
|
||||
#ifdef USE_DEVICES
|
||||
pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.device_id);
|
||||
#endif
|
||||
pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 4, msg.missing_state);
|
||||
return pos;
|
||||
}
|
||||
uint32_t SwitchStateResponse::calc_size_msg(const void *self) {
|
||||
@@ -948,6 +949,7 @@ uint32_t SwitchStateResponse::calc_size_msg(const void *self) {
|
||||
#ifdef USE_DEVICES
|
||||
size += ProtoSize::calc_uint32(1, msg.device_id);
|
||||
#endif
|
||||
size += ProtoSize::calc_bool(1, msg.missing_state);
|
||||
return size;
|
||||
}
|
||||
void SwitchCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) {
|
||||
@@ -1630,6 +1632,7 @@ uint8_t *ClimateStateResponse::encode_msg(const void *self, ProtoWriteBuffer &bu
|
||||
#ifdef USE_DEVICES
|
||||
pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 16, msg.device_id);
|
||||
#endif
|
||||
pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 17, msg.missing_state);
|
||||
return pos;
|
||||
}
|
||||
uint32_t ClimateStateResponse::calc_size_msg(const void *self) {
|
||||
@@ -1652,6 +1655,7 @@ uint32_t ClimateStateResponse::calc_size_msg(const void *self) {
|
||||
#ifdef USE_DEVICES
|
||||
size += ProtoSize::calc_uint32(2, msg.device_id);
|
||||
#endif
|
||||
size += ProtoSize::calc_bool(2, msg.missing_state);
|
||||
return size;
|
||||
}
|
||||
void ClimateCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) {
|
||||
@@ -1806,6 +1810,7 @@ uint8_t *WaterHeaterStateResponse::encode_msg(const void *self, ProtoWriteBuffer
|
||||
if (uint32_t raw = float_to_raw(msg.target_temperature_high); raw != 0) [[likely]] {
|
||||
pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 69, raw);
|
||||
}
|
||||
pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 9, msg.missing_state);
|
||||
return pos;
|
||||
}
|
||||
uint32_t WaterHeaterStateResponse::calc_size_msg(const void *self) {
|
||||
@@ -1821,6 +1826,7 @@ uint32_t WaterHeaterStateResponse::calc_size_msg(const void *self) {
|
||||
size += ProtoSize::calc_uint32(1, msg.state);
|
||||
size += ProtoSize::calc_float(1, msg.target_temperature_low);
|
||||
size += ProtoSize::calc_float(1, msg.target_temperature_high);
|
||||
size += ProtoSize::calc_bool(1, msg.missing_state);
|
||||
return size;
|
||||
}
|
||||
void WaterHeaterCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data,
|
||||
|
||||
@@ -1109,11 +1109,12 @@ class ListEntitiesSwitchResponse final : public InfoResponseProtoMessage {
|
||||
class SwitchStateResponse final : public StateResponseProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 26;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 11;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 13;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const LogString *message_name() const override { return LOG_STR("switch_state_response"); }
|
||||
#endif
|
||||
bool state{false};
|
||||
bool missing_state{false};
|
||||
static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM);
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||
return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG);
|
||||
@@ -1690,7 +1691,7 @@ class ListEntitiesClimateResponse final : public InfoResponseProtoMessage {
|
||||
class ClimateStateResponse final : public StateResponseProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 47;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 68;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 71;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const LogString *message_name() const override { return LOG_STR("climate_state_response"); }
|
||||
#endif
|
||||
@@ -1707,6 +1708,7 @@ class ClimateStateResponse final : public StateResponseProtoMessage {
|
||||
StringRef custom_preset{nullptr, 0}; // null until set, encode only
|
||||
float current_humidity{0.0f};
|
||||
float target_humidity{0.0f};
|
||||
bool missing_state{false};
|
||||
static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM);
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||
return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG);
|
||||
@@ -1786,7 +1788,7 @@ class ListEntitiesWaterHeaterResponse final : public InfoResponseProtoMessage {
|
||||
class WaterHeaterStateResponse final : public StateResponseProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 133;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 35;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 37;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const LogString *message_name() const override { return LOG_STR("water_heater_state_response"); }
|
||||
#endif
|
||||
@@ -1796,6 +1798,7 @@ class WaterHeaterStateResponse final : public StateResponseProtoMessage {
|
||||
uint32_t state{0};
|
||||
float target_temperature_low{0.0f};
|
||||
float target_temperature_high{0.0f};
|
||||
bool missing_state{false};
|
||||
static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM);
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||
return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG);
|
||||
|
||||
@@ -1342,6 +1342,7 @@ const char *SwitchStateResponse::dump_to(DumpBuffer &out) const {
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, ESPHOME_PSTR("device_id"), this->device_id);
|
||||
#endif
|
||||
dump_field(out, ESPHOME_PSTR("missing_state"), this->missing_state);
|
||||
return out.c_str();
|
||||
}
|
||||
const char *SwitchCommandRequest::dump_to(DumpBuffer &out) const {
|
||||
@@ -1684,6 +1685,7 @@ const char *ClimateStateResponse::dump_to(DumpBuffer &out) const {
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, ESPHOME_PSTR("device_id"), this->device_id);
|
||||
#endif
|
||||
dump_field(out, ESPHOME_PSTR("missing_state"), this->missing_state);
|
||||
return out.c_str();
|
||||
}
|
||||
const char *ClimateCommandRequest::dump_to(DumpBuffer &out) const {
|
||||
@@ -1751,6 +1753,7 @@ const char *WaterHeaterStateResponse::dump_to(DumpBuffer &out) const {
|
||||
dump_field(out, ESPHOME_PSTR("state"), this->state);
|
||||
dump_field(out, ESPHOME_PSTR("target_temperature_low"), this->target_temperature_low);
|
||||
dump_field(out, ESPHOME_PSTR("target_temperature_high"), this->target_temperature_high);
|
||||
dump_field(out, ESPHOME_PSTR("missing_state"), this->missing_state);
|
||||
return out.c_str();
|
||||
}
|
||||
const char *WaterHeaterCommandRequest::dump_to(DumpBuffer &out) const {
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cerrno>
|
||||
#include <sys/select.h>
|
||||
|
||||
namespace esphome::async_tcp {
|
||||
|
||||
@@ -42,7 +41,15 @@ bool AsyncClient::connect(const char *host, uint16_t port) {
|
||||
return false;
|
||||
}
|
||||
|
||||
socket_->setblocking(false);
|
||||
if (socket_->setblocking(false) != 0) {
|
||||
// Capture before the log and close() clobber errno
|
||||
const int saved_errno = errno;
|
||||
ESP_LOGE(TAG, "Failed to set nonblocking: errno %d", saved_errno);
|
||||
close();
|
||||
if (error_cb_)
|
||||
error_cb_(error_arg_, this, saved_errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
int err = socket_->connect((struct sockaddr *) &addr, addrlen);
|
||||
if (err == 0) {
|
||||
@@ -97,45 +104,22 @@ void AsyncClient::loop() {
|
||||
return;
|
||||
|
||||
if (connecting_) {
|
||||
// For connecting, we need to check writability, not readability
|
||||
// The Application's select() only monitors read FDs, so we do our own check here
|
||||
// For ESP platforms lwip_select() might be faster, but this code isn't used
|
||||
// on those platforms anyway. If it was, we'd fix the Application select()
|
||||
// to report writability instead of doing it this way.
|
||||
int fd = socket_->get_fd();
|
||||
if (fd < 0) {
|
||||
ESP_LOGW(TAG, "Invalid socket fd");
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
fd_set writefds;
|
||||
FD_ZERO(&writefds);
|
||||
FD_SET(fd, &writefds);
|
||||
|
||||
struct timeval tv = {0, 0};
|
||||
int ret = select(fd + 1, nullptr, &writefds, nullptr, &tv);
|
||||
|
||||
if (ret > 0 && FD_ISSET(fd, &writefds)) {
|
||||
int error = 0;
|
||||
socklen_t len = sizeof(error);
|
||||
if (socket_->getsockopt(SOL_SOCKET, SO_ERROR, &error, &len) == 0 && error == 0) {
|
||||
int err = 0;
|
||||
switch (socket::poll_connect(*socket_, err)) {
|
||||
case socket::ConnectPollResult::CONNECT_POLL_RESULT_PENDING:
|
||||
break;
|
||||
case socket::ConnectPollResult::CONNECT_POLL_RESULT_CONNECTED:
|
||||
connecting_ = false;
|
||||
connected_ = true;
|
||||
if (connect_cb_)
|
||||
connect_cb_(connect_arg_, this);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Connection failed: %d", error);
|
||||
break;
|
||||
case socket::ConnectPollResult::CONNECT_POLL_RESULT_ERROR:
|
||||
ESP_LOGW(TAG, "Connection failed: %d", err);
|
||||
close();
|
||||
if (error_cb_)
|
||||
error_cb_(error_arg_, this, error);
|
||||
}
|
||||
} else if (ret < 0) {
|
||||
const int err = errno;
|
||||
ESP_LOGE(TAG, "Select error: %d", err);
|
||||
close();
|
||||
if (error_cb_)
|
||||
error_cb_(error_arg_, this, err);
|
||||
error_cb_(error_arg_, this, err);
|
||||
break;
|
||||
}
|
||||
} else if (connected_) {
|
||||
// For connected sockets, use the Application's select() results
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_MIC_GAIN
|
||||
from esphome.core import ID, CoroPriority, coroutine_with_priority
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.const import CONF_MIC_GAIN
|
||||
from esphome.core import CoroPriority, coroutine_with_priority
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@kbx81"]
|
||||
@@ -12,8 +11,6 @@ IS_PLATFORM_COMPONENT = True
|
||||
audio_adc_ns = cg.esphome_ns.namespace("audio_adc")
|
||||
AudioAdc = audio_adc_ns.class_("AudioAdc")
|
||||
|
||||
SetMicGainAction = audio_adc_ns.class_("SetMicGainAction", automation.Action)
|
||||
|
||||
|
||||
SET_MIC_GAIN_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||
{
|
||||
@@ -24,25 +21,11 @@ SET_MIC_GAIN_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"audio_adc.set_mic_gain",
|
||||
SetMicGainAction,
|
||||
SET_MIC_GAIN_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_MIC_GAIN, "set_mic_gain", cg.float_),
|
||||
)
|
||||
async def audio_adc_set_mic_gain_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
template_ = await cg.templatable(config.get(CONF_MIC_GAIN), args, cg.float_)
|
||||
cg.add(var.set_mic_gain(template_))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.CORE)
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "audio_adc.h"
|
||||
|
||||
namespace esphome::audio_adc {
|
||||
|
||||
template<typename... Ts> class SetMicGainAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit SetMicGainAction(AudioAdc *audio_adc) : audio_adc_(audio_adc) {}
|
||||
|
||||
TEMPLATABLE_VALUE(float, mic_gain)
|
||||
|
||||
void play(const Ts &...x) override { this->audio_adc_->set_mic_gain(this->mic_gain_.value(x...)); }
|
||||
|
||||
protected:
|
||||
AudioAdc *audio_adc_;
|
||||
};
|
||||
|
||||
} // namespace esphome::audio_adc
|
||||
@@ -134,9 +134,6 @@ MultiClickTriggerBase = binary_sensor_ns.class_(
|
||||
MultiClickTrigger = binary_sensor_ns.class_("MultiClickTrigger", MultiClickTriggerBase)
|
||||
MultiClickTriggerEvent = binary_sensor_ns.struct("MultiClickTriggerEvent")
|
||||
|
||||
BinarySensorInvalidateAction = binary_sensor_ns.class_(
|
||||
"BinarySensorInvalidateAction", automation.Action
|
||||
)
|
||||
|
||||
# Filters
|
||||
Filter = binary_sensor_ns.class_("Filter")
|
||||
@@ -655,16 +652,15 @@ async def to_code(config):
|
||||
cg.add_global(binary_sensor_ns.using)
|
||||
|
||||
|
||||
automation.register_simple_action(
|
||||
automation.register_apply_action(
|
||||
"binary_sensor.invalidate_state",
|
||||
BinarySensorInvalidateAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(BinarySensor),
|
||||
},
|
||||
key=CONF_ID,
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyCall("invalidate_state()"),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -120,14 +120,4 @@ template<size_t N> class MultiClickTrigger final : public MultiClickTriggerBase
|
||||
std::array<MultiClickTriggerEvent, N> timing_storage_{};
|
||||
};
|
||||
|
||||
template<typename... Ts> class BinarySensorInvalidateAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit BinarySensorInvalidateAction(BinarySensor *sensor) : sensor_(sensor) {}
|
||||
|
||||
void play(const Ts &...x) override { this->sensor_->invalidate_state(); }
|
||||
|
||||
protected:
|
||||
BinarySensor *sensor_;
|
||||
};
|
||||
|
||||
} // namespace esphome::binary_sensor
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/datatypes.h"
|
||||
|
||||
@@ -47,8 +46,6 @@ struct sbe24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-ali
|
||||
int8_t h{0};
|
||||
} __attribute__((packed));
|
||||
|
||||
template<typename... Ts> class ResetEnergyAction;
|
||||
|
||||
class BL0906;
|
||||
|
||||
using ActionCallbackFuncPtr = void (BL0906::*)();
|
||||
@@ -85,9 +82,10 @@ class BL0906 final : public PollingComponent, public uart::UARTDevice {
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
template<typename... Ts> friend class ResetEnergyAction;
|
||||
/// Queue an energy counter reset for the next poll
|
||||
void reset_energy() { this->enqueue_action_(&BL0906::reset_energy_); }
|
||||
|
||||
protected:
|
||||
void reset_energy_();
|
||||
|
||||
void read_data_(uint8_t address, float reference, sensor::Sensor *sensor);
|
||||
@@ -103,9 +101,4 @@ class BL0906 final : public PollingComponent, public uart::UARTDevice {
|
||||
std::vector<ActionCallbackFuncPtr> action_queue_{};
|
||||
};
|
||||
|
||||
template<typename... Ts> class ResetEnergyAction final : public Action<Ts...>, public Parented<BL0906> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->enqueue_action_(&BL0906::reset_energy_); }
|
||||
};
|
||||
|
||||
} // namespace esphome::bl0906
|
||||
@@ -43,7 +43,6 @@ CONF_TOTAL_ENERGY = "total_energy"
|
||||
|
||||
bl0906_ns = cg.esphome_ns.namespace("bl0906")
|
||||
BL0906 = bl0906_ns.class_("BL0906", cg.PollingComponent, uart.UARTDevice)
|
||||
ResetEnergyAction = bl0906_ns.class_("ResetEnergyAction", automation.Action)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
@@ -136,15 +135,14 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
)
|
||||
|
||||
|
||||
automation.register_parented_action(
|
||||
automation.register_apply_action(
|
||||
"bl0906.reset_energy",
|
||||
ResetEnergyAction,
|
||||
maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(BL0906),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyCall("reset_energy()"),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -34,24 +34,4 @@ class BM8563 final : public time::RealTimeClock, public i2c::I2CDevice {
|
||||
uint8_t byte_to_bcd2_(uint8_t value);
|
||||
};
|
||||
|
||||
template<typename... Ts> class WriteAction final : public Action<Ts...>, public Parented<BM8563> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->write_time(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class ReadAction final : public Action<Ts...>, public Parented<BM8563> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->read_time(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class TimerAction final : public Action<Ts...>, public Parented<BM8563> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint32_t, duration)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto duration = this->duration_.value(x...);
|
||||
this->parent_->start_timer(duration);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esphome::bm8563
|
||||
@@ -3,8 +3,6 @@ import esphome.codegen as cg
|
||||
from esphome.components import i2c, time
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_DURATION, CONF_ID
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.types import ConfigType
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
@@ -13,9 +11,6 @@ I2C_ADDR = 0x51
|
||||
|
||||
bm8563_ns = cg.esphome_ns.namespace("bm8563")
|
||||
BM8563 = bm8563_ns.class_("BM8563", time.RealTimeClock, i2c.I2CDevice)
|
||||
WriteAction = bm8563_ns.class_("WriteAction", automation.Action)
|
||||
ReadAction = bm8563_ns.class_("ReadAction", automation.Action)
|
||||
TimerAction = bm8563_ns.class_("TimerAction", automation.Action)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
time.TIME_SCHEMA.extend(
|
||||
@@ -28,51 +23,29 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
automation.register_parented_action(
|
||||
"bm8563.write_time",
|
||||
WriteAction,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(BM8563),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
BM8563_ACTION_SCHEMA = automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(BM8563),
|
||||
}
|
||||
)
|
||||
|
||||
automation.register_apply_action(
|
||||
"bm8563.write_time", BM8563_ACTION_SCHEMA, automation.ApplyCall("write_time()")
|
||||
)
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"bm8563.read_time", BM8563_ACTION_SCHEMA, automation.ApplyCall("read_time()")
|
||||
)
|
||||
|
||||
automation.register_apply_action(
|
||||
"bm8563.start_timer",
|
||||
TimerAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(BM8563),
|
||||
cv.Required(CONF_DURATION): cv.templatable(cv.positive_time_period_seconds),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def bm8563_start_timer_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_DURATION], args, cg.uint32)
|
||||
cg.add(var.set_duration(template_))
|
||||
return var
|
||||
|
||||
|
||||
automation.register_parented_action(
|
||||
"bm8563.read_time",
|
||||
ReadAction,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(BM8563),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_DURATION, "start_timer", cg.uint32),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -351,10 +351,6 @@ async def to_code(config):
|
||||
|
||||
|
||||
# Actions
|
||||
BeginTxAction = ns.class_("BeginTxAction", automation.Action)
|
||||
BeginRxAction = ns.class_("BeginRxAction", automation.Action)
|
||||
ResetAction = ns.class_("ResetAction", automation.Action)
|
||||
SetIdleAction = ns.class_("SetIdleAction", automation.Action)
|
||||
SendPacketAction = ns.class_(
|
||||
"SendPacketAction", automation.Action, cg.Parented.template(CC1101Component)
|
||||
)
|
||||
@@ -364,36 +360,15 @@ CC1101_ACTION_SCHEMA = cv.Schema(
|
||||
)
|
||||
|
||||
|
||||
automation.register_parented_action(
|
||||
"cc1101.begin_tx",
|
||||
BeginTxAction,
|
||||
CC1101_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
)
|
||||
|
||||
|
||||
automation.register_parented_action(
|
||||
"cc1101.begin_rx",
|
||||
BeginRxAction,
|
||||
CC1101_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
)
|
||||
|
||||
|
||||
automation.register_parented_action(
|
||||
"cc1101.reset",
|
||||
ResetAction,
|
||||
CC1101_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
)
|
||||
|
||||
|
||||
automation.register_parented_action(
|
||||
"cc1101.set_idle",
|
||||
SetIdleAction,
|
||||
CC1101_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
)
|
||||
for _name, _call in (
|
||||
("cc1101.begin_tx", "begin_tx()"),
|
||||
("cc1101.begin_rx", "begin_rx()"),
|
||||
("cc1101.reset", "reset()"),
|
||||
("cc1101.set_idle", "set_idle()"),
|
||||
):
|
||||
automation.register_apply_action(
|
||||
_name, CC1101_ACTION_SCHEMA, automation.ApplyCall(_call)
|
||||
)
|
||||
|
||||
|
||||
def validate_raw_data(value):
|
||||
|
||||
@@ -130,26 +130,6 @@ class CC1101Component final : public Component,
|
||||
};
|
||||
|
||||
// Action Wrappers
|
||||
template<typename... Ts> class BeginTxAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->begin_tx(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class BeginRxAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->begin_rx(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class ResetAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->reset(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetIdleAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->set_idle(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SendPacketAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
void set_data_template(std::function<std::vector<uint8_t>(Ts...)> func) { this->data_func_ = func; }
|
||||
|
||||
@@ -435,6 +435,7 @@ void Climate::save_state_(const ClimateTraits &traits) {
|
||||
}
|
||||
|
||||
void Climate::publish_state() {
|
||||
this->set_has_state(true);
|
||||
ESP_LOGV(TAG, "'%s' >>", this->name_.c_str());
|
||||
auto traits = this->get_traits();
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
@@ -23,14 +22,4 @@ class CM1106Component final : public PollingComponent, public uart::UARTDevice {
|
||||
bool cm1106_write_command_(const uint8_t *command, size_t command_len, uint8_t *response, size_t response_len);
|
||||
};
|
||||
|
||||
template<typename... Ts> class CM1106CalibrateZeroAction final : public Action<Ts...> {
|
||||
public:
|
||||
CM1106CalibrateZeroAction(CM1106Component *cm1106) : cm1106_(cm1106) {}
|
||||
|
||||
void play(const Ts &...x) override { this->cm1106_->calibrate_zero(400); }
|
||||
|
||||
protected:
|
||||
CM1106Component *cm1106_;
|
||||
};
|
||||
|
||||
} // namespace esphome::cm1106
|
||||
@@ -22,10 +22,6 @@ cm1106_ns = cg.esphome_ns.namespace("cm1106")
|
||||
CM1106Component = cm1106_ns.class_(
|
||||
"CM1106Component", cg.PollingComponent, uart.UARTDevice
|
||||
)
|
||||
CM1106CalibrateZeroAction = cm1106_ns.class_(
|
||||
"CM1106CalibrateZeroAction",
|
||||
automation.Action,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
@@ -70,9 +66,8 @@ CALIBRATION_ACTION_SCHEMA = maybe_simple_id(
|
||||
)
|
||||
|
||||
|
||||
automation.register_simple_action(
|
||||
automation.register_apply_action(
|
||||
"cm1106.calibrate_zero",
|
||||
CM1106CalibrateZeroAction,
|
||||
CALIBRATION_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
automation.ApplyCall("calibrate_zero(400)"),
|
||||
)
|
||||
@@ -23,6 +23,7 @@ CONF_ENABLE_OTA_DOWNGRADE_PROTECTION = "enable_ota_downgrade_protection"
|
||||
CONF_ENABLED = "enabled"
|
||||
CONF_GYROSCOPE_ODR = "gyroscope_odr"
|
||||
CONF_GYROSCOPE_RANGE = "gyroscope_range"
|
||||
CONF_HOST = "host"
|
||||
CONF_HUMIDITY_SOURCE = "humidity_source"
|
||||
CONF_IAQ = "iaq"
|
||||
CONF_IGNORE_NOT_FOUND = "ignore_not_found"
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
"""Standard CSS named colors, as 0xRRGGBB values."""
|
||||
|
||||
CSS_COLORS: dict[str, int] = {
|
||||
"aliceblue": 0xF0F8FF,
|
||||
"antiquewhite": 0xFAEBD7,
|
||||
"aqua": 0x00FFFF,
|
||||
"aquamarine": 0x7FFFD4,
|
||||
"azure": 0xF0FFFF,
|
||||
"beige": 0xF5F5DC,
|
||||
"bisque": 0xFFE4C4,
|
||||
"black": 0x000000,
|
||||
"blanchedalmond": 0xFFEBCD,
|
||||
"blue": 0x0000FF,
|
||||
"blueviolet": 0x8A2BE2,
|
||||
"brown": 0xA52A2A,
|
||||
"burlywood": 0xDEB887,
|
||||
"cadetblue": 0x5F9EA0,
|
||||
"chartreuse": 0x7FFF00,
|
||||
"chocolate": 0xD2691E,
|
||||
"coral": 0xFF7F50,
|
||||
"cornflowerblue": 0x6495ED,
|
||||
"cornsilk": 0xFFF8DC,
|
||||
"crimson": 0xDC143C,
|
||||
"cyan": 0x00FFFF,
|
||||
"darkblue": 0x00008B,
|
||||
"darkcyan": 0x008B8B,
|
||||
"darkgoldenrod": 0xB8860B,
|
||||
"darkgray": 0xA9A9A9,
|
||||
"darkgreen": 0x006400,
|
||||
"darkgrey": 0xA9A9A9,
|
||||
"darkkhaki": 0xBDB76B,
|
||||
"darkmagenta": 0x8B008B,
|
||||
"darkolivegreen": 0x556B2F,
|
||||
"darkorange": 0xFF8C00,
|
||||
"darkorchid": 0x9932CC,
|
||||
"darkred": 0x8B0000,
|
||||
"darksalmon": 0xE9967A,
|
||||
"darkseagreen": 0x8FBC8F,
|
||||
"darkslateblue": 0x483D8B,
|
||||
"darkslategray": 0x2F4F4F,
|
||||
"darkslategrey": 0x2F4F4F,
|
||||
"darkturquoise": 0x00CED1,
|
||||
"darkviolet": 0x9400D3,
|
||||
"deeppink": 0xFF1493,
|
||||
"deepskyblue": 0x00BFFF,
|
||||
"dimgray": 0x696969,
|
||||
"dimgrey": 0x696969,
|
||||
"dodgerblue": 0x1E90FF,
|
||||
"firebrick": 0xB22222,
|
||||
"floralwhite": 0xFFFAF0,
|
||||
"forestgreen": 0x228B22,
|
||||
"fuchsia": 0xFF00FF,
|
||||
"gainsboro": 0xDCDCDC,
|
||||
"ghostwhite": 0xF8F8FF,
|
||||
"goldenrod": 0xDAA520,
|
||||
"gold": 0xFFD700,
|
||||
"gray": 0x808080,
|
||||
"green": 0x008000,
|
||||
"greenyellow": 0xADFF2F,
|
||||
"grey": 0x808080,
|
||||
"honeydew": 0xF0FFF0,
|
||||
"hotpink": 0xFF69B4,
|
||||
"indianred": 0xCD5C5C,
|
||||
"indigo": 0x4B0082,
|
||||
"ivory": 0xFFFFF0,
|
||||
"khaki": 0xF0E68C,
|
||||
"lavenderblush": 0xFFF0F5,
|
||||
"lavender": 0xE6E6FA,
|
||||
"lawngreen": 0x7CFC00,
|
||||
"lemonchiffon": 0xFFFACD,
|
||||
"lightblue": 0xADD8E6,
|
||||
"lightcoral": 0xF08080,
|
||||
"lightcyan": 0xE0FFFF,
|
||||
"lightgoldenrodyellow": 0xFAFAD2,
|
||||
"lightgray": 0xD3D3D3,
|
||||
"lightgreen": 0x90EE90,
|
||||
"lightgrey": 0xD3D3D3,
|
||||
"lightpink": 0xFFB6C1,
|
||||
"lightsalmon": 0xFFA07A,
|
||||
"lightseagreen": 0x20B2AA,
|
||||
"lightskyblue": 0x87CEFA,
|
||||
"lightslategray": 0x778899,
|
||||
"lightslategrey": 0x778899,
|
||||
"lightsteelblue": 0xB0C4DE,
|
||||
"lightyellow": 0xFFFFE0,
|
||||
"lime": 0x00FF00,
|
||||
"limegreen": 0x32CD32,
|
||||
"linen": 0xFAF0E6,
|
||||
"magenta": 0xFF00FF,
|
||||
"maroon": 0x800000,
|
||||
"mediumaquamarine": 0x66CDAA,
|
||||
"mediumblue": 0x0000CD,
|
||||
"mediumorchid": 0xBA55D3,
|
||||
"mediumpurple": 0x9370DB,
|
||||
"mediumseagreen": 0x3CB371,
|
||||
"mediumslateblue": 0x7B68EE,
|
||||
"mediumspringgreen": 0x00FA9A,
|
||||
"mediumturquoise": 0x48D1CC,
|
||||
"mediumvioletred": 0xC71585,
|
||||
"midnightblue": 0x191970,
|
||||
"mintcream": 0xF5FFFA,
|
||||
"mistyrose": 0xFFE4E1,
|
||||
"moccasin": 0xFFE4B5,
|
||||
"navajowhite": 0xFFDEAD,
|
||||
"navy": 0x000080,
|
||||
"oldlace": 0xFDF5E6,
|
||||
"olive": 0x808000,
|
||||
"olivedrab": 0x6B8E23,
|
||||
"orange": 0xFFA500,
|
||||
"orangered": 0xFF4500,
|
||||
"orchid": 0xDA70D6,
|
||||
"palegoldenrod": 0xEEE8AA,
|
||||
"palegreen": 0x98FB98,
|
||||
"paleturquoise": 0xAFEEEE,
|
||||
"palevioletred": 0xDB7093,
|
||||
"papayawhip": 0xFFEFD5,
|
||||
"peachpuff": 0xFFDAB9,
|
||||
"peru": 0xCD853F,
|
||||
"pink": 0xFFC0CB,
|
||||
"plum": 0xDDA0DD,
|
||||
"powderblue": 0xB0E0E6,
|
||||
"purple": 0x800080,
|
||||
"rebeccapurple": 0x663399,
|
||||
"red": 0xFF0000,
|
||||
"rosybrown": 0xBC8F8F,
|
||||
"royalblue": 0x4169E1,
|
||||
"saddlebrown": 0x8B4513,
|
||||
"salmon": 0xFA8072,
|
||||
"sandybrown": 0xF4A460,
|
||||
"seagreen": 0x2E8B57,
|
||||
"seashell": 0xFFF5EE,
|
||||
"sienna": 0xA0522D,
|
||||
"silver": 0xC0C0C0,
|
||||
"skyblue": 0x87CEEB,
|
||||
"slateblue": 0x6A5ACD,
|
||||
"slategray": 0x708090,
|
||||
"slategrey": 0x708090,
|
||||
"snow": 0xFFFAFA,
|
||||
"springgreen": 0x00FF7F,
|
||||
"steelblue": 0x4682B4,
|
||||
"tan": 0xD2B48C,
|
||||
"teal": 0x008080,
|
||||
"thistle": 0xD8BFD8,
|
||||
"tomato": 0xFF6347,
|
||||
"turquoise": 0x40E0D0,
|
||||
"violet": 0xEE82EE,
|
||||
"wheat": 0xF5DEB3,
|
||||
"white": 0xFFFFFF,
|
||||
"whitesmoke": 0xF5F5F5,
|
||||
"yellow": 0xFFFF00,
|
||||
"yellowgreen": 0x9ACD32,
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
|
||||
@@ -108,14 +107,4 @@ class CS5460AComponent final : public Component,
|
||||
uint32_t prev_raw_energy_{0};
|
||||
};
|
||||
|
||||
template<typename... Ts> class CS5460ARestartAction final : public Action<Ts...> {
|
||||
public:
|
||||
CS5460ARestartAction(CS5460AComponent *cs5460a) : cs5460a_(cs5460a) {}
|
||||
|
||||
void play(const Ts &...x) override { cs5460a_->restart(); }
|
||||
|
||||
protected:
|
||||
CS5460AComponent *cs5460a_;
|
||||
};
|
||||
|
||||
} // namespace esphome::cs5460a
|
||||
@@ -30,7 +30,6 @@ PGA_GAIN_OPTIONS = {
|
||||
}
|
||||
|
||||
CS5460AComponent = cs5460a_ns.class_("CS5460AComponent", spi.SPIDevice, cg.Component)
|
||||
CS5460ARestartAction = cs5460a_ns.class_("CS5460ARestartAction", automation.Action)
|
||||
|
||||
CONF_SAMPLES = "samples"
|
||||
CONF_PHASE_OFFSET = "phase_offset"
|
||||
@@ -129,13 +128,12 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(var.set_power_sensor(sens))
|
||||
|
||||
|
||||
automation.register_simple_action(
|
||||
automation.register_apply_action(
|
||||
"cs5460a.restart",
|
||||
CS5460ARestartAction,
|
||||
maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(CS5460AComponent),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyCall("restart()"),
|
||||
)
|
||||
@@ -12,9 +12,6 @@ CODEOWNERS = ["@glmnet"]
|
||||
|
||||
dfplayer_ns = cg.esphome_ns.namespace("dfplayer")
|
||||
DFPlayer = dfplayer_ns.class_("DFPlayer", cg.Component)
|
||||
DFPlayerIsPlayingCondition = dfplayer_ns.class_(
|
||||
"DFPlayerIsPlayingCondition", automation.Condition
|
||||
)
|
||||
|
||||
MULTI_CONF = True
|
||||
CONF_FOLDER = "folder"
|
||||
@@ -199,12 +196,12 @@ automation.register_apply_action(
|
||||
)
|
||||
|
||||
|
||||
automation.register_parented_condition(
|
||||
automation.register_apply_condition(
|
||||
"dfplayer.is_playing",
|
||||
DFPlayerIsPlayingCondition,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
}
|
||||
),
|
||||
"is_playing()",
|
||||
)
|
||||
@@ -77,9 +77,4 @@ class DFPlayer final : public uart::UARTDevice, public Component {
|
||||
CallbackManager<void()> on_finished_playback_callback_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class DFPlayerIsPlayingCondition final : public Condition<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->is_playing(); }
|
||||
};
|
||||
|
||||
} // namespace esphome::dfplayer
|
||||
@@ -20,9 +20,6 @@ DfrobotSen0395Component = dfrobot_sen0395_ns.class_(
|
||||
)
|
||||
|
||||
# Actions
|
||||
DfrobotSen0395ResetAction = dfrobot_sen0395_ns.class_(
|
||||
"DfrobotSen0395ResetAction", automation.Action
|
||||
)
|
||||
DfrobotSen0395SettingsAction = dfrobot_sen0395_ns.class_(
|
||||
"DfrobotSen0395SettingsAction", automation.Action
|
||||
)
|
||||
@@ -49,15 +46,14 @@ async def to_code(config: ConfigType) -> None:
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
|
||||
automation.register_parented_action(
|
||||
automation.register_apply_action(
|
||||
"dfrobot_sen0395.reset",
|
||||
DfrobotSen0395ResetAction,
|
||||
maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DfrobotSen0395Component),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyCall("reset_system()"),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -7,12 +7,6 @@
|
||||
|
||||
namespace esphome::dfrobot_sen0395 {
|
||||
|
||||
template<typename... Ts>
|
||||
class DfrobotSen0395ResetAction final : public Action<Ts...>, public Parented<DfrobotSen0395Component> {
|
||||
public:
|
||||
void play(const Ts &...x) { this->parent_->enqueue(make_unique<ResetSystemCommand>()); }
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
class DfrobotSen0395SettingsAction final : public Action<Ts...>, public Parented<DfrobotSen0395Component> {
|
||||
public:
|
||||
|
||||
@@ -94,6 +94,8 @@ class DfrobotSen0395Component final : public uart::UARTDevice, public Component
|
||||
#endif
|
||||
|
||||
int8_t enqueue(std::unique_ptr<Command> cmd);
|
||||
/// Queue a sensor reset
|
||||
void reset_system() { this->enqueue(make_unique<ResetSystemCommand>()); }
|
||||
|
||||
protected:
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
|
||||
@@ -39,9 +39,6 @@ DisplayPage = display_ns.class_("DisplayPage")
|
||||
DisplayPagePtr = DisplayPage.operator("ptr")
|
||||
DisplayRef = Display.operator("ref")
|
||||
DisplayPageShowAction = display_ns.class_("DisplayPageShowAction", automation.Action)
|
||||
DisplayIsDisplayingPageCondition = display_ns.class_(
|
||||
"DisplayIsDisplayingPageCondition", automation.Condition
|
||||
)
|
||||
DisplayOnPageChangeTrigger = display_ns.class_(
|
||||
"DisplayOnPageChangeTrigger", automation.Trigger
|
||||
)
|
||||
@@ -301,9 +298,8 @@ automation.register_apply_action(
|
||||
)
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
automation.register_apply_condition(
|
||||
"display.is_displaying_page",
|
||||
DisplayIsDisplayingPageCondition,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.use_id(Display),
|
||||
@@ -311,13 +307,8 @@ automation.register_apply_action(
|
||||
},
|
||||
key=CONF_PAGE_ID,
|
||||
),
|
||||
automation.ApplyCall("get_active_page() == {}", ((CONF_PAGE_ID, DisplayPagePtr),)),
|
||||
)
|
||||
async def display_is_displaying_page_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
page = await cg.get_variable(config[CONF_PAGE_ID])
|
||||
var = cg.new_Pvariable(condition_id, template_arg, paren)
|
||||
cg.add(var.set_page(page))
|
||||
return var
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.CORE)
|
||||
|
||||
@@ -848,18 +848,6 @@ template<typename... Ts> class DisplayPageShowAction final : public Action<Ts...
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class DisplayIsDisplayingPageCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
DisplayIsDisplayingPageCondition(Display *parent) : parent_(parent) {}
|
||||
|
||||
void set_page(DisplayPage *page) { this->page_ = page; }
|
||||
bool check(const Ts &...x) override { return this->parent_->get_active_page() == this->page_; }
|
||||
|
||||
protected:
|
||||
Display *parent_;
|
||||
DisplayPage *page_;
|
||||
};
|
||||
|
||||
class DisplayOnPageChangeTrigger final : public Trigger<DisplayPage *, DisplayPage *> {
|
||||
public:
|
||||
explicit DisplayOnPageChangeTrigger(Display *parent) { parent->add_on_page_change_trigger(this); }
|
||||
|
||||
@@ -55,13 +55,4 @@ class DS1307Component final : public time::RealTimeClock, public i2c::I2CDevice
|
||||
} ds1307_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class WriteAction final : public Action<Ts...>, public Parented<DS1307Component> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->write_time(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class ReadAction final : public Action<Ts...>, public Parented<DS1307Component> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->read_time(); }
|
||||
};
|
||||
} // namespace esphome::ds1307
|
||||
@@ -9,8 +9,6 @@ CODEOWNERS = ["@badbadc0ffee"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
ds1307_ns = cg.esphome_ns.namespace("ds1307")
|
||||
DS1307Component = ds1307_ns.class_("DS1307Component", time.RealTimeClock, i2c.I2CDevice)
|
||||
WriteAction = ds1307_ns.class_("WriteAction", automation.Action)
|
||||
ReadAction = ds1307_ns.class_("ReadAction", automation.Action)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = time.TIME_SCHEMA.extend(
|
||||
@@ -20,27 +18,24 @@ CONFIG_SCHEMA = time.TIME_SCHEMA.extend(
|
||||
).extend(i2c.i2c_device_schema(0x68))
|
||||
|
||||
|
||||
automation.register_parented_action(
|
||||
automation.register_apply_action(
|
||||
"ds1307.write_time",
|
||||
WriteAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DS1307Component),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyCall("write_time()"),
|
||||
)
|
||||
|
||||
|
||||
automation.register_parented_action(
|
||||
automation.register_apply_action(
|
||||
"ds1307.read_time",
|
||||
ReadAction,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DS1307Component),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyCall("read_time()"),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
@@ -47,27 +46,4 @@ class DutyTimeSensor final : public sensor::Sensor, public PollingComponent {
|
||||
bool restore_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class BaseAction : public Action<Ts...>, public Parented<DutyTimeSensor> {};
|
||||
|
||||
template<typename... Ts> class StartAction : public BaseAction<Ts...> {
|
||||
void play(const Ts &...x) override { this->parent_->start(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class StopAction : public BaseAction<Ts...> {
|
||||
void play(const Ts &...x) override { this->parent_->stop(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class ResetAction : public BaseAction<Ts...> {
|
||||
void play(const Ts &...x) override { this->parent_->reset(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class RunningCondition final : public Condition<Ts...>, public Parented<DutyTimeSensor> {
|
||||
public:
|
||||
explicit RunningCondition(DutyTimeSensor *parent, bool state) : Parented(parent), state_(state) {}
|
||||
|
||||
protected:
|
||||
bool check(const Ts &...x) override { return this->parent_->is_running() == this->state_; }
|
||||
bool state_;
|
||||
};
|
||||
|
||||
} // namespace esphome::duty_time_sensor
|
||||
@@ -13,8 +13,6 @@ from esphome.const import (
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_SECOND,
|
||||
)
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CONF_LAST_TIME = "last_time"
|
||||
@@ -23,14 +21,6 @@ duty_time_sensor_ns = cg.esphome_ns.namespace("duty_time_sensor")
|
||||
DutyTimeSensor = duty_time_sensor_ns.class_(
|
||||
"DutyTimeSensor", sensor.Sensor, cg.PollingComponent
|
||||
)
|
||||
BaseAction = duty_time_sensor_ns.class_("BaseAction", automation.Action, cg.Parented)
|
||||
StartAction = duty_time_sensor_ns.class_("StartAction", BaseAction)
|
||||
StopAction = duty_time_sensor_ns.class_("StopAction", BaseAction)
|
||||
ResetAction = duty_time_sensor_ns.class_("ResetAction", BaseAction)
|
||||
SetAction = duty_time_sensor_ns.class_("SetAction", BaseAction)
|
||||
RunningCondition = duty_time_sensor_ns.class_(
|
||||
"RunningCondition", automation.Condition, cg.Parented
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
@@ -87,51 +77,18 @@ DUTY_TIME_ID_SCHEMA = automation.maybe_simple_id(
|
||||
)
|
||||
|
||||
|
||||
automation.register_parented_action(
|
||||
"sensor.duty_time.start",
|
||||
StartAction,
|
||||
DUTY_TIME_ID_SCHEMA,
|
||||
synchronous=True,
|
||||
for _name, _call in (
|
||||
("sensor.duty_time.start", "start()"),
|
||||
("sensor.duty_time.stop", "stop()"),
|
||||
("sensor.duty_time.reset", "reset()"),
|
||||
):
|
||||
automation.register_apply_action(
|
||||
_name, DUTY_TIME_ID_SCHEMA, automation.ApplyCall(_call)
|
||||
)
|
||||
|
||||
automation.register_apply_condition(
|
||||
"sensor.duty_time.is_running", DUTY_TIME_ID_SCHEMA, "is_running()"
|
||||
)
|
||||
|
||||
|
||||
automation.register_parented_action(
|
||||
"sensor.duty_time.stop",
|
||||
StopAction,
|
||||
DUTY_TIME_ID_SCHEMA,
|
||||
synchronous=True,
|
||||
automation.register_apply_condition(
|
||||
"sensor.duty_time.is_not_running", DUTY_TIME_ID_SCHEMA, "is_running() == false"
|
||||
)
|
||||
|
||||
|
||||
automation.register_parented_action(
|
||||
"sensor.duty_time.reset",
|
||||
ResetAction,
|
||||
DUTY_TIME_ID_SCHEMA,
|
||||
synchronous=True,
|
||||
)
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"sensor.duty_time.is_running", RunningCondition, DUTY_TIME_ID_SCHEMA
|
||||
)
|
||||
async def duty_time_is_running_to_code(
|
||||
config: ConfigType,
|
||||
condition_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren, True)
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"sensor.duty_time.is_not_running", RunningCondition, DUTY_TIME_ID_SCHEMA
|
||||
)
|
||||
async def duty_time_is_not_running_to_code(
|
||||
config: ConfigType,
|
||||
condition_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren, False)
|
||||
@@ -11,8 +11,7 @@ from esphome.const import (
|
||||
CONF_RX_BUFFER_SIZE,
|
||||
CONF_UART_ID,
|
||||
)
|
||||
from esphome.core import CORE, ID
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.core import CORE
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
@@ -23,9 +22,6 @@ DEPENDENCIES = ["uart"]
|
||||
emontx_ns = cg.esphome_ns.namespace("emontx")
|
||||
EmonTx = emontx_ns.class_("EmonTx", cg.Component, uart.UARTDevice)
|
||||
|
||||
# Action to send command to emonTx
|
||||
EmonTxSendCommandAction = emontx_ns.class_("EmonTxSendCommandAction", automation.Action)
|
||||
|
||||
CONF_EMONTX_ID = "emontx_id"
|
||||
CONF_TAG_NAME = "tag_name"
|
||||
CONF_ON_JSON = "on_json"
|
||||
@@ -139,20 +135,16 @@ EMONTX_SEND_COMMAND_ACTION_SCHEMA = cv.Schema(
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
def _plain_literal(config: ConfigType, value: str) -> str:
|
||||
return str(cg.safe_exp(value))
|
||||
|
||||
|
||||
automation.register_apply_action(
|
||||
"emontx.send_command",
|
||||
EmonTxSendCommandAction,
|
||||
EMONTX_SEND_COMMAND_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
# A constant is a plain literal for the const char * overload; a lambda returns a
|
||||
# std::string and takes the inline overload.
|
||||
automation.ApplyField(
|
||||
CONF_COMMAND, "send_command", cg.std_string, const_fn=_plain_literal
|
||||
),
|
||||
)
|
||||
async def emontx_send_command_action_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_COMMAND], args, cg.std_string)
|
||||
cg.add(var.set_command(template_))
|
||||
return var
|
||||
@@ -94,9 +94,9 @@ void EmonTx::dump_config() {
|
||||
*
|
||||
* @param command The command string to send (LF will be appended automatically).
|
||||
*/
|
||||
void EmonTx::send_command(const std::string &command) {
|
||||
ESP_LOGD(TAG, "Sending command to emonTx: %s", command.c_str());
|
||||
this->write_str(command.c_str());
|
||||
void EmonTx::send_command(const char *command) {
|
||||
ESP_LOGD(TAG, "Sending command to emonTx: %s", command);
|
||||
this->write_str(command);
|
||||
this->write_byte('\n');
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
@@ -39,7 +38,8 @@ class EmonTx final : public Component, public uart::UARTDevice {
|
||||
template<typename F> void add_on_data_callback(F &&callback) { this->data_callbacks_.add(std::forward<F>(callback)); }
|
||||
|
||||
// Send command to emonTx via UART
|
||||
void send_command(const std::string &command);
|
||||
void send_command(const char *command);
|
||||
void send_command(const std::string &command) { this->send_command(command.c_str()); }
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void init_sensors(size_t count) { this->sensors_.init(count); }
|
||||
@@ -58,12 +58,4 @@ class EmonTx final : public Component, public uart::UARTDevice {
|
||||
std::array<char, MAX_LINE_LENGTH + 1> buffer_{};
|
||||
};
|
||||
|
||||
// Action to send command to emonTx
|
||||
template<typename... Ts> class EmonTxSendCommandAction final : public Action<Ts...>, public Parented<EmonTx> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(std::string, command)
|
||||
|
||||
void play(const Ts &...x) override { this->parent_->send_command(this->command_.value(x...)); }
|
||||
};
|
||||
|
||||
} // namespace esphome::emontx
|
||||
@@ -5,6 +5,7 @@ import re
|
||||
import subprocess
|
||||
from typing import Any
|
||||
|
||||
from esphome.build_helpers.native import native_backend
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -14,6 +15,7 @@ from esphome.const import (
|
||||
CONF_FRAMEWORK,
|
||||
CONF_PLATFORM_VERSION,
|
||||
CONF_SOURCE,
|
||||
CONF_TOOLCHAIN,
|
||||
CONF_VERSION,
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
@@ -21,6 +23,7 @@ from esphome.const import (
|
||||
KEY_TARGET_PLATFORM,
|
||||
PLATFORM_ESP8266,
|
||||
ThreadModel,
|
||||
Toolchain,
|
||||
)
|
||||
from esphome.core import (
|
||||
CORE,
|
||||
@@ -43,6 +46,7 @@ from .const import (
|
||||
CONF_ENABLE_SERIAL1,
|
||||
CONF_RESTORE_FROM_FLASH,
|
||||
KEY_BOARD,
|
||||
KEY_DECODE_TOOLS,
|
||||
KEY_ESP8266,
|
||||
KEY_PIN_INITIAL_STATES,
|
||||
KEY_SCANF_FLOAT,
|
||||
@@ -105,6 +109,47 @@ def set_core_data(config: ConfigType) -> ConfigType:
|
||||
return config
|
||||
|
||||
|
||||
_TOOLCHAINS = (Toolchain.PLATFORMIO, Toolchain.ARDUINO)
|
||||
_validate_toolchain = cv.toolchain_enum(_TOOLCHAINS)
|
||||
_resolve_toolchain = cv.resolve_toolchain("ESP8266", _TOOLCHAINS, Toolchain.PLATFORMIO)
|
||||
|
||||
|
||||
def _validate_native_toolchain(config: ConfigType) -> ConfigType:
|
||||
"""Constraints of the native (non-PlatformIO) Arduino toolchain."""
|
||||
if not CORE.using_toolchain_arduino:
|
||||
return config
|
||||
from esphome.arduino8266.framework import MIN_FRAMEWORK_VERSION
|
||||
|
||||
conf = config[CONF_FRAMEWORK]
|
||||
version = cv.Version.parse(conf[CONF_VERSION])
|
||||
if version < MIN_FRAMEWORK_VERSION:
|
||||
raise cv.Invalid(
|
||||
"'toolchain: arduino' requires framework version "
|
||||
f"{MIN_FRAMEWORK_VERSION} or newer"
|
||||
)
|
||||
# platform_version is a PlatformIO concept; drop it, warning when a
|
||||
# custom pin is discarded
|
||||
if (
|
||||
conf.pop(CONF_PLATFORM_VERSION, _ARDUINO_4_PLATFORM_SPEC)
|
||||
!= _ARDUINO_4_PLATFORM_SPEC
|
||||
):
|
||||
_LOGGER.warning(
|
||||
"'platform_version' is ignored by 'toolchain: arduino'; the native "
|
||||
"toolchain downloads the framework and compiler directly"
|
||||
)
|
||||
if conf[CONF_SOURCE] != _format_framework_arduino_version(version):
|
||||
raise cv.Invalid(
|
||||
"'toolchain: arduino' does not support a custom framework source; "
|
||||
"use 'toolchain: platformio'"
|
||||
)
|
||||
if config[CONF_BOARD] not in BOARDS:
|
||||
raise cv.Invalid(
|
||||
f"Board '{config[CONF_BOARD]}' is not supported by "
|
||||
"'toolchain: arduino'; use 'toolchain: platformio'"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
def get_download_types(storage_json: StorageJSON) -> list[dict[str, str]]:
|
||||
"""Binary-download entries for a built ESP8266 firmware.
|
||||
|
||||
@@ -134,7 +179,7 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
|
||||
# a PIO platformio/framework-arduinoespressif8266 value
|
||||
# List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif8266
|
||||
# Same encoding the native toolchain uses for its package download, so a
|
||||
# version bump cannot drift between the two paths.
|
||||
# custom-source check against this value cannot drift from what it fetches.
|
||||
from esphome.arduino8266.framework import framework_package_version
|
||||
|
||||
try:
|
||||
@@ -196,7 +241,7 @@ def _arduino_check_versions(value: ConfigType) -> ConfigType:
|
||||
platform_version = value.get(CONF_PLATFORM_VERSION)
|
||||
if platform_version is None:
|
||||
if version >= cv.Version(3, 1, 0):
|
||||
platform_version = _parse_platform_version(str(ARDUINO_4_PLATFORM_VERSION))
|
||||
platform_version = _ARDUINO_4_PLATFORM_SPEC
|
||||
else:
|
||||
platform_version = _parse_platform_version(str(ARDUINO_3_PLATFORM_VERSION))
|
||||
value[CONF_PLATFORM_VERSION] = platform_version
|
||||
@@ -219,6 +264,10 @@ def _parse_platform_version(value: Any) -> str:
|
||||
return value
|
||||
|
||||
|
||||
# The platform_version derived for every core >= 3.1.0 config
|
||||
_ARDUINO_4_PLATFORM_SPEC = _parse_platform_version(str(ARDUINO_4_PLATFORM_VERSION))
|
||||
|
||||
|
||||
ARDUINO_FRAMEWORK_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -251,11 +300,13 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_ENABLE_SERIAL1): cv.boolean,
|
||||
cv.Optional(CONF_ENABLE_FULL_PRINTF, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ENABLE_SCANF_FLOAT): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_TOOLCHAIN, visibility=cv.Visibility.ADVANCED
|
||||
): _validate_toolchain,
|
||||
}
|
||||
),
|
||||
# Until the native toolchain lands, PlatformIO is the only backend;
|
||||
# reject a --toolchain this platform cannot serve yet.
|
||||
cv.require_platformio_toolchain("ESP8266"),
|
||||
_resolve_toolchain,
|
||||
_validate_native_toolchain,
|
||||
set_core_data,
|
||||
)
|
||||
|
||||
@@ -295,12 +346,13 @@ def _choose_ld_script(board: str) -> str:
|
||||
|
||||
@coroutine_with_priority(CoroPriority.PLATFORM)
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
use_platformio = CORE.using_toolchain_platformio
|
||||
cg.add(esp8266_ns.setup_preferences())
|
||||
|
||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||
cg.add_platformio_option("lib_compat_mode", "strict")
|
||||
|
||||
cg.add_platformio_option("board", config[CONF_BOARD])
|
||||
if use_platformio:
|
||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||
cg.add_platformio_option("lib_compat_mode", "strict")
|
||||
cg.add_platformio_option("board", config[CONF_BOARD])
|
||||
cg.add_build_flag("-DUSE_ESP8266")
|
||||
cg.set_cpp_standard("gnu++20")
|
||||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||
@@ -318,38 +370,30 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
# The native generator reads the same decision (KEY_SCANF_FLOAT)
|
||||
CORE.data[KEY_ESP8266][KEY_SCANF_FLOAT] = bool(enable_scanf_float)
|
||||
|
||||
extra_scripts = [
|
||||
"pre:ccache.py",
|
||||
"pre:testing_mode.py",
|
||||
"pre:exclude_updater.py",
|
||||
"pre:exclude_waveform.py",
|
||||
"pre:relocate_ratetable.py",
|
||||
]
|
||||
if not enable_scanf_float:
|
||||
extra_scripts.append("pre:remove_float_scanf.py")
|
||||
extra_scripts.append("post:post_build.py")
|
||||
cg.add_platformio_option("extra_scripts", extra_scripts)
|
||||
if use_platformio:
|
||||
extra_scripts = [
|
||||
"pre:ccache.py",
|
||||
"pre:testing_mode.py",
|
||||
"pre:exclude_updater.py",
|
||||
"pre:exclude_waveform.py",
|
||||
"pre:relocate_ratetable.py",
|
||||
]
|
||||
if not enable_scanf_float:
|
||||
extra_scripts.append("pre:remove_float_scanf.py")
|
||||
extra_scripts.append("post:post_build.py")
|
||||
cg.add_platformio_option("extra_scripts", extra_scripts)
|
||||
|
||||
conf = config[CONF_FRAMEWORK]
|
||||
cg.add_platformio_option("framework", "arduino")
|
||||
cg.add_build_flag("-DUSE_ARDUINO")
|
||||
cg.add_build_flag("-DUSE_ESP8266_FRAMEWORK_ARDUINO")
|
||||
cg.add_build_flag("-Wno-nonnull-compare")
|
||||
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
|
||||
cg.add_platformio_option(
|
||||
"platform_packages",
|
||||
[
|
||||
f"platformio/framework-arduinoespressif8266@{conf[CONF_SOURCE]}",
|
||||
# Override the ancient tool-esptoolpy ~1.30000.0 pinned by
|
||||
# platform-espressif8266 4.2.1 with the same 5.x build the
|
||||
# pioarduino ESP32 platform uses, so both platforms share the
|
||||
# same installed package and stop reinstalling on every switch.
|
||||
# The 0.0.1 path component is pioarduino's stable registry
|
||||
# release tag (not the tool version); the tool itself is 5.2.0.
|
||||
"pioarduino/tool-esptoolpy@https://github.com/pioarduino/registry/releases/download/0.0.1/esptoolpy-v5.2.0.zip",
|
||||
],
|
||||
)
|
||||
if use_platformio:
|
||||
cg.add_platformio_option("framework", "arduino")
|
||||
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
|
||||
cg.add_platformio_option(
|
||||
"platform_packages",
|
||||
[f"platformio/framework-arduinoespressif8266@{conf[CONF_SOURCE]}"],
|
||||
)
|
||||
|
||||
# Default for platformio is LWIP2_LOW_MEMORY with:
|
||||
# - MSS=536
|
||||
@@ -378,8 +422,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
# Force-include inline std::__throw_* overrides so GCC dead-strips the unused
|
||||
# libstdc++ error message strings (e.g. "basic_string::_M_create") from DRAM.
|
||||
# See throw_stubs.h for details. Must be prepended before <string>, so this
|
||||
# uses build_src_flags with -include.
|
||||
# See throw_stubs.h. Unconditional: the native build generator reads
|
||||
# the same option, keeping one source of truth.
|
||||
cg.add_platformio_option(
|
||||
"build_src_flags", "-include esphome/components/esp8266/throw_stubs.h"
|
||||
)
|
||||
@@ -409,6 +453,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
# implementation in the Arduino ESP8266 core.
|
||||
cg.add_build_flag("-Wl,--wrap=millis")
|
||||
|
||||
# Unconditional: the native build generator reads the same option,
|
||||
# keeping one source of truth
|
||||
cg.add_platformio_option("board_build.flash_mode", config[CONF_BOARD_FLASH_MODE])
|
||||
|
||||
ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
|
||||
@@ -417,7 +463,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.RawExpression(f"VERSION_CODE({ver.major}, {ver.minor}, {ver.patch})"),
|
||||
)
|
||||
|
||||
if config[CONF_BOARD] in BOARDS:
|
||||
if use_platformio and config[CONF_BOARD] in BOARDS:
|
||||
cg.add_platformio_option(
|
||||
"board_build.ldscript", _choose_ld_script(config[CONF_BOARD])
|
||||
)
|
||||
@@ -458,8 +504,24 @@ async def finalize_serial_config() -> None:
|
||||
cg.add_build_flag("-DNO_GLOBAL_SERIAL1")
|
||||
|
||||
|
||||
# Called by __main__.compile_program; returning False falls through to the
|
||||
# PlatformIO toolchain.
|
||||
def run_compile(args, config: ConfigType) -> bool:
|
||||
# Positive check: the native backend only runs when explicitly resolved
|
||||
toolchain = native_backend()
|
||||
if toolchain is None:
|
||||
return False
|
||||
if toolchain.run_compile(config, CORE.verbose) != 0:
|
||||
raise EsphomeError("ESP8266 native build failed")
|
||||
return True
|
||||
|
||||
|
||||
# Called by writer.py
|
||||
def copy_files() -> None:
|
||||
# Native builds skip the PlatformIO extra scripts; the build generator
|
||||
# carries their logic
|
||||
if CORE.using_toolchain_arduino:
|
||||
return
|
||||
dir = Path(__file__).parent
|
||||
for script in (
|
||||
"post_build",
|
||||
@@ -516,22 +578,54 @@ ESP8266_EXCEPTION_CODES = {
|
||||
}
|
||||
|
||||
|
||||
def _decode_pc(config: ConfigType, addr: str) -> None:
|
||||
def _resolve_decode_tools(config: ConfigType) -> tuple[str, str] | None:
|
||||
"""``(addr2line, elf)`` for this build, or None after warning why."""
|
||||
if (native_toolchain := native_backend()) is not None:
|
||||
addr2line = native_toolchain.get_addr2line_path()
|
||||
elf = native_toolchain.get_elf_path()
|
||||
for path in (addr2line, elf):
|
||||
if not path.is_file():
|
||||
_LOGGER.warning("Cannot decode crash addresses: %s missing", path)
|
||||
return None
|
||||
return str(addr2line), str(elf)
|
||||
from esphome.platformio import toolchain
|
||||
|
||||
idedata = toolchain.get_idedata(config)
|
||||
if not idedata.addr2line_path or not idedata.firmware_elf_path:
|
||||
_LOGGER.debug("decode_pc no addr2line")
|
||||
_LOGGER.warning("Cannot decode crash addresses: no addr2line or ELF in idedata")
|
||||
return None
|
||||
return idedata.addr2line_path, idedata.firmware_elf_path
|
||||
|
||||
|
||||
def _decode_tools(config: ConfigType) -> tuple[str, str] | None:
|
||||
"""Resolved once per run: the tools are a property of the build, not of
|
||||
the address, so a stack dump cannot repeat the failure warning."""
|
||||
data = CORE.data.setdefault(KEY_ESP8266, {})
|
||||
if KEY_DECODE_TOOLS not in data:
|
||||
data[KEY_DECODE_TOOLS] = _resolve_decode_tools(config)
|
||||
return data[KEY_DECODE_TOOLS]
|
||||
|
||||
|
||||
def _decode_pc(config: ConfigType, addr: str, *, bulk: bool = False) -> None:
|
||||
"""Decode one crash address. ``bulk``: the caller is scanning every
|
||||
8-hex stack word, most of which are not code addresses -- unmappable
|
||||
ones log at debug so real frames are not buried."""
|
||||
# Bulk stack words are ~150 per dump; only named registers warn
|
||||
log = _LOGGER.debug if bulk else _LOGGER.warning
|
||||
if (tools := _decode_tools(config)) is None:
|
||||
log("Not decoded %s (no addr2line or ELF)", addr)
|
||||
return
|
||||
command = [idedata.addr2line_path, "-pfiaC", "-e", idedata.firmware_elf_path, addr]
|
||||
addr2line, elf = tools
|
||||
command = [addr2line, "-pfiaC", "-e", elf, addr]
|
||||
try:
|
||||
translation = subprocess.check_output(command, close_fds=False).decode().strip()
|
||||
except Exception: # noqa: BLE001 # pylint: disable=broad-except
|
||||
except Exception as err: # noqa: BLE001 # pylint: disable=broad-except
|
||||
log("Could not decode crash address %s (%s)", addr, err)
|
||||
_LOGGER.debug("Caught exception for command %s", command, exc_info=1)
|
||||
return
|
||||
|
||||
if "?? ??:0" in translation:
|
||||
# Nothing useful
|
||||
log("Not decoded %s (address not in %s)", addr, elf)
|
||||
return
|
||||
translation = translation.replace(" at ??:?", "").replace(":?", "")
|
||||
_LOGGER.warning("Decoded %s", translation)
|
||||
@@ -601,6 +695,6 @@ def process_stacktrace(config: ConfigType, line: str, backtrace_state: bool) ->
|
||||
|
||||
if backtrace_state:
|
||||
for addr in re.finditer(STACKTRACE_ESP8266_BACKTRACE_PC_RE, line):
|
||||
_decode_pc(config, addr.group())
|
||||
_decode_pc(config, addr.group(), bulk=True)
|
||||
|
||||
return backtrace_state
|
||||
@@ -19,6 +19,8 @@ KEY_SERIAL1_REQUIRED = "serial1_required"
|
||||
KEY_SCANF_FLOAT = "scanf_float"
|
||||
# Per-board flash-layout override consumed by board_ld_script()
|
||||
KEY_LDSCRIPT = "ldscript"
|
||||
# Crash-decode tools, resolved once per run by __init__
|
||||
KEY_DECODE_TOOLS = "decode_tools"
|
||||
|
||||
# esp8266 namespace is already defined by arduino, manually prefix esphome
|
||||
esp8266_ns = cg.global_ns.namespace("esphome").namespace("esp8266")
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
from typing import Any
|
||||
|
||||
from esphome.automation import Action, register_action
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp32 import VARIANT_ESP32P4, only_on_variant
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_VOLTAGE
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.final_validate import full_config
|
||||
from esphome.types import ConfigType
|
||||
|
||||
@@ -16,7 +15,6 @@ DOMAIN = "esp_ldo"
|
||||
|
||||
esp_ldo_ns = cg.esphome_ns.namespace("esp_ldo")
|
||||
EspLdo = esp_ldo_ns.class_("EspLdo", cg.Component)
|
||||
AdjustAction = esp_ldo_ns.class_("AdjustAction", Action)
|
||||
|
||||
CHANNELS = (1, 2, 3, 4)
|
||||
CHANNELS_INTERNAL = (1, 2)
|
||||
@@ -123,9 +121,8 @@ def adjusted_ldo_id(value: Any) -> ID:
|
||||
return value
|
||||
|
||||
|
||||
@register_action(
|
||||
automation.register_apply_action(
|
||||
"esp_ldo.voltage.adjust",
|
||||
AdjustAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): adjusted_ldo_id,
|
||||
@@ -134,16 +131,5 @@ def adjusted_ldo_id(value: Any) -> ID:
|
||||
),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_VOLTAGE, "adjust_voltage", cg.float_),
|
||||
)
|
||||
async def ldo_voltage_adjust_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, parent)
|
||||
template_ = await cg.templatable(config[CONF_VOLTAGE], args, cg.float_)
|
||||
cg.add(var.set_voltage(template_))
|
||||
return var
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
#ifdef USE_ESP32_VARIANT_ESP32P4
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esp_ldo_regulator.h"
|
||||
|
||||
namespace esphome::esp_ldo {
|
||||
@@ -27,18 +26,6 @@ class EspLdo final : public Component {
|
||||
esp_ldo_channel_handle_t handle_{};
|
||||
};
|
||||
|
||||
template<typename... Ts> class AdjustAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit AdjustAction(EspLdo *ldo) : ldo_(ldo) {}
|
||||
|
||||
TEMPLATABLE_VALUE(float, voltage)
|
||||
|
||||
void play(const Ts &...x) override { this->ldo_->adjust_voltage(this->voltage_.value(x...)); }
|
||||
|
||||
protected:
|
||||
EspLdo *ldo_;
|
||||
};
|
||||
|
||||
} // namespace esphome::esp_ldo
|
||||
|
||||
#endif // USE_ESP32_VARIANT_ESP32P4
|
||||
@@ -459,7 +459,10 @@ void ESPHomeOTAComponent::handle_data_() {
|
||||
tv.tv_usec = 0;
|
||||
this->client_->setsockopt(SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
this->client_->setsockopt(SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
|
||||
this->client_->setblocking(true);
|
||||
if (this->client_->setblocking(true) != 0) {
|
||||
this->log_socket_error_(LOG_STR("blocking"));
|
||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||
}
|
||||
|
||||
// Acknowledge auth OK - 1 byte
|
||||
this->data_write_byte_(ota::OTA_RESPONSE_AUTH_OK);
|
||||
|
||||
@@ -44,91 +44,18 @@ EZO_PMP_NO_ARGS_ACTION_SCHEMA = maybe_simple_id(
|
||||
}
|
||||
)
|
||||
|
||||
# Actions that do not require more arguments
|
||||
for _name, _method in (
|
||||
("ezo_pmp.find", "find()"),
|
||||
("ezo_pmp.dose_continuously", "dose_continuously()"),
|
||||
("ezo_pmp.clear_total_volume_dosed", "clear_total_volume_dosed()"),
|
||||
("ezo_pmp.clear_calibration", "clear_calibration()"),
|
||||
("ezo_pmp.pause_dosing", "pause_dosing()"),
|
||||
("ezo_pmp.stop_dosing", "stop_dosing()"),
|
||||
):
|
||||
automation.register_apply_action(
|
||||
_name, EZO_PMP_NO_ARGS_ACTION_SCHEMA, automation.ApplyCall(_method)
|
||||
)
|
||||
|
||||
EzoPMPFindAction = ezo_pmp_ns.class_("EzoPMPFindAction", automation.Action)
|
||||
EzoPMPClearTotalVolumeDispensedAction = ezo_pmp_ns.class_(
|
||||
"EzoPMPClearTotalVolumeDispensedAction", automation.Action
|
||||
)
|
||||
EzoPMPClearCalibrationAction = ezo_pmp_ns.class_(
|
||||
"EzoPMPClearCalibrationAction", automation.Action
|
||||
)
|
||||
EzoPMPPauseDosingAction = ezo_pmp_ns.class_(
|
||||
"EzoPMPPauseDosingAction", automation.Action
|
||||
)
|
||||
EzoPMPStopDosingAction = ezo_pmp_ns.class_("EzoPMPStopDosingAction", automation.Action)
|
||||
EzoPMPDoseContinuouslyAction = ezo_pmp_ns.class_(
|
||||
"EzoPMPDoseContinuouslyAction", automation.Action
|
||||
)
|
||||
|
||||
# Actions that require more arguments
|
||||
EzoPMPDoseVolumeAction = ezo_pmp_ns.class_("EzoPMPDoseVolumeAction", automation.Action)
|
||||
EzoPMPDoseVolumeOverTimeAction = ezo_pmp_ns.class_(
|
||||
"EzoPMPDoseVolumeOverTimeAction", automation.Action
|
||||
)
|
||||
EzoPMPDoseWithConstantFlowRateAction = ezo_pmp_ns.class_(
|
||||
"EzoPMPDoseWithConstantFlowRateAction", automation.Action
|
||||
)
|
||||
EzoPMPSetCalibrationVolumeAction = ezo_pmp_ns.class_(
|
||||
"EzoPMPSetCalibrationVolumeAction", automation.Action
|
||||
)
|
||||
EzoPMPChangeI2CAddressAction = ezo_pmp_ns.class_(
|
||||
"EzoPMPChangeI2CAddressAction", automation.Action
|
||||
)
|
||||
EzoPMPArbitraryCommandAction = ezo_pmp_ns.class_(
|
||||
"EzoPMPArbitraryCommandAction", automation.Action
|
||||
)
|
||||
|
||||
|
||||
automation.register_simple_action(
|
||||
"ezo_pmp.find",
|
||||
EzoPMPFindAction,
|
||||
EZO_PMP_NO_ARGS_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
)
|
||||
|
||||
|
||||
automation.register_simple_action(
|
||||
"ezo_pmp.dose_continuously",
|
||||
EzoPMPDoseContinuouslyAction,
|
||||
EZO_PMP_NO_ARGS_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
)
|
||||
|
||||
|
||||
automation.register_simple_action(
|
||||
"ezo_pmp.clear_total_volume_dosed",
|
||||
EzoPMPClearTotalVolumeDispensedAction,
|
||||
EZO_PMP_NO_ARGS_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
)
|
||||
|
||||
|
||||
automation.register_simple_action(
|
||||
"ezo_pmp.clear_calibration",
|
||||
EzoPMPClearCalibrationAction,
|
||||
EZO_PMP_NO_ARGS_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
)
|
||||
|
||||
|
||||
automation.register_simple_action(
|
||||
"ezo_pmp.pause_dosing",
|
||||
EzoPMPPauseDosingAction,
|
||||
EZO_PMP_NO_ARGS_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
)
|
||||
|
||||
|
||||
automation.register_simple_action(
|
||||
"ezo_pmp.stop_dosing",
|
||||
EzoPMPStopDosingAction,
|
||||
EZO_PMP_NO_ARGS_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
)
|
||||
|
||||
|
||||
# Actions that require Multiple Args
|
||||
|
||||
EZO_PMP_DOSE_VOLUME_ACTION_SCHEMA = cv.All(
|
||||
{
|
||||
@@ -139,21 +66,11 @@ EZO_PMP_DOSE_VOLUME_ACTION_SCHEMA = cv.All(
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"ezo_pmp.dose_volume",
|
||||
EzoPMPDoseVolumeAction,
|
||||
EZO_PMP_DOSE_VOLUME_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_VOLUME, "dose_volume", cg.double),
|
||||
)
|
||||
async def ezo_pmp_dose_volume_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
template_ = await cg.templatable(config[CONF_VOLUME], args, cg.double)
|
||||
cg.add(var.set_volume(template_))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
EZO_PMP_DOSE_VOLUME_OVER_TIME_ACTION_SCHEMA = cv.All(
|
||||
@@ -168,24 +85,14 @@ EZO_PMP_DOSE_VOLUME_OVER_TIME_ACTION_SCHEMA = cv.All(
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"ezo_pmp.dose_volume_over_time",
|
||||
EzoPMPDoseVolumeOverTimeAction,
|
||||
EZO_PMP_DOSE_VOLUME_OVER_TIME_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
automation.ApplyCall(
|
||||
"dose_volume_over_time({}, {})",
|
||||
((CONF_VOLUME, cg.double), (CONF_DURATION, cg.int_)),
|
||||
),
|
||||
)
|
||||
async def ezo_pmp_dose_volume_over_time_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
template_ = await cg.templatable(config[CONF_VOLUME], args, cg.double)
|
||||
cg.add(var.set_volume(template_))
|
||||
|
||||
template_ = await cg.templatable(config[CONF_DURATION], args, cg.int_)
|
||||
cg.add(var.set_duration(template_))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
EZO_PMP_DOSE_WITH_CONSTANT_FLOW_RATE_ACTION_SCHEMA = cv.All(
|
||||
@@ -200,26 +107,14 @@ EZO_PMP_DOSE_WITH_CONSTANT_FLOW_RATE_ACTION_SCHEMA = cv.All(
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"ezo_pmp.dose_with_constant_flow_rate",
|
||||
EzoPMPDoseWithConstantFlowRateAction,
|
||||
EZO_PMP_DOSE_WITH_CONSTANT_FLOW_RATE_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
automation.ApplyCall(
|
||||
"dose_with_constant_flow_rate({}, {})",
|
||||
((CONF_VOLUME_PER_MINUTE, cg.double), (CONF_DURATION, cg.int_)),
|
||||
),
|
||||
)
|
||||
async def ezo_pmp_dose_with_constant_flow_rate_to_code(
|
||||
config, action_id, template_arg, args
|
||||
):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
template_ = await cg.templatable(config[CONF_VOLUME_PER_MINUTE], args, cg.double)
|
||||
cg.add(var.set_volume(template_))
|
||||
|
||||
template_ = await cg.templatable(config[CONF_DURATION], args, cg.int_)
|
||||
cg.add(var.set_duration(template_))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
EZO_PMP_SET_CALIBRATION_VOLUME_ACTION_SCHEMA = cv.All(
|
||||
@@ -231,21 +126,11 @@ EZO_PMP_SET_CALIBRATION_VOLUME_ACTION_SCHEMA = cv.All(
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"ezo_pmp.set_calibration_volume",
|
||||
EzoPMPSetCalibrationVolumeAction,
|
||||
EZO_PMP_SET_CALIBRATION_VOLUME_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_VOLUME, "set_calibration_volume", cg.double),
|
||||
)
|
||||
async def ezo_pmp_set_calibration_volume_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
template_ = await cg.templatable(config[CONF_VOLUME], args, cg.double)
|
||||
cg.add(var.set_volume(template_))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
EZO_PMP_CHANGE_I2C_ADDRESS_ACTION_SCHEMA = cv.All(
|
||||
@@ -255,21 +140,11 @@ EZO_PMP_CHANGE_I2C_ADDRESS_ACTION_SCHEMA = cv.All(
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"ezo_pmp.change_i2c_address",
|
||||
EzoPMPChangeI2CAddressAction,
|
||||
EZO_PMP_CHANGE_I2C_ADDRESS_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_ADDRESS, "change_i2c_address", cg.int_),
|
||||
)
|
||||
async def ezo_pmp_change_i2c_address_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.int_)
|
||||
cg.add(var.set_address(template_))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
EZO_PMP_ARBITRARY_COMMAND_ACTION_SCHEMA = cv.All(
|
||||
@@ -279,18 +154,8 @@ EZO_PMP_ARBITRARY_COMMAND_ACTION_SCHEMA = cv.All(
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"ezo_pmp.arbitrary_command",
|
||||
EzoPMPArbitraryCommandAction,
|
||||
EZO_PMP_ARBITRARY_COMMAND_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_COMMAND, "exec_arbitrary_command", cg.std_string),
|
||||
)
|
||||
async def ezo_pmp_arbitrary_command_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
template_ = await cg.templatable(config[CONF_COMMAND], args, cg.std_string)
|
||||
cg.add(var.set_command(template_))
|
||||
|
||||
return var
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
@@ -113,137 +112,4 @@ class EzoPMP final : public PollingComponent, public i2c::I2CDevice {
|
||||
#endif
|
||||
};
|
||||
|
||||
// Action Templates
|
||||
template<typename... Ts> class EzoPMPFindAction final : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPFindAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(const Ts &...x) override { this->ezopmp_->find(); }
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPDoseContinuouslyAction final : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPDoseContinuouslyAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(const Ts &...x) override { this->ezopmp_->dose_continuously(); }
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPDoseVolumeAction final : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPDoseVolumeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(const Ts &...x) override { this->ezopmp_->dose_volume(this->volume_.value(x...)); }
|
||||
TEMPLATABLE_VALUE(double, volume)
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPDoseVolumeOverTimeAction final : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPDoseVolumeOverTimeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
this->ezopmp_->dose_volume_over_time(this->volume_.value(x...), this->duration_.value(x...));
|
||||
}
|
||||
TEMPLATABLE_VALUE(double, volume)
|
||||
TEMPLATABLE_VALUE(int, duration)
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPDoseWithConstantFlowRateAction final : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPDoseWithConstantFlowRateAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
this->ezopmp_->dose_with_constant_flow_rate(this->volume_.value(x...), this->duration_.value(x...));
|
||||
}
|
||||
TEMPLATABLE_VALUE(double, volume)
|
||||
TEMPLATABLE_VALUE(int, duration)
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPSetCalibrationVolumeAction final : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPSetCalibrationVolumeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(const Ts &...x) override { this->ezopmp_->set_calibration_volume(this->volume_.value(x...)); }
|
||||
TEMPLATABLE_VALUE(double, volume)
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPClearTotalVolumeDispensedAction final : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPClearTotalVolumeDispensedAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(const Ts &...x) override { this->ezopmp_->clear_total_volume_dosed(); }
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPClearCalibrationAction final : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPClearCalibrationAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(const Ts &...x) override { this->ezopmp_->clear_calibration(); }
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPPauseDosingAction final : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPPauseDosingAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(const Ts &...x) override { this->ezopmp_->pause_dosing(); }
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPStopDosingAction final : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPStopDosingAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(const Ts &...x) override { this->ezopmp_->stop_dosing(); }
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPChangeI2CAddressAction final : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPChangeI2CAddressAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(const Ts &...x) override { this->ezopmp_->change_i2c_address(this->address_.value(x...)); }
|
||||
TEMPLATABLE_VALUE(int, address)
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPArbitraryCommandAction final : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPArbitraryCommandAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(const Ts &...x) override { this->ezopmp_->exec_arbitrary_command(this->command_.value(x...)); }
|
||||
TEMPLATABLE_VALUE(std::string, command)
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
} // namespace esphome::ezo_pmp
|
||||
@@ -83,7 +83,7 @@ void FanCall::validate_() {
|
||||
auto traits = this->parent_.get_traits();
|
||||
|
||||
if (this->speed_.has_value()) {
|
||||
this->speed_ = clamp(*this->speed_, 1, static_cast<int>(traits.supported_speed_count()));
|
||||
this->speed_ = clamp(*this->speed_, 1, traits.supported_speed_count());
|
||||
|
||||
// https://developers.home-assistant.io/docs/core/entity/fan/#preset-modes
|
||||
// "Manually setting a speed must disable any set preset mode"
|
||||
|
||||
@@ -14,7 +14,7 @@ class FanTraits {
|
||||
|
||||
public:
|
||||
FanTraits() = default;
|
||||
FanTraits(bool oscillation, bool speed, bool direction, uint8_t speed_count)
|
||||
FanTraits(bool oscillation, bool speed, bool direction, int speed_count)
|
||||
: oscillation_(oscillation), speed_(speed), direction_(direction), speed_count_(speed_count) {}
|
||||
|
||||
/// Return if this fan supports oscillation.
|
||||
@@ -26,9 +26,9 @@ class FanTraits {
|
||||
/// Set whether this fan supports speed levels.
|
||||
void set_speed(bool speed) { this->speed_ = speed; }
|
||||
/// Return how many speed levels the fan has
|
||||
uint8_t supported_speed_count() const { return this->speed_count_; }
|
||||
int supported_speed_count() const { return this->speed_count_; }
|
||||
/// Set how many speed levels this fan has.
|
||||
void set_supported_speed_count(uint8_t speed_count) { this->speed_count_ = speed_count; }
|
||||
void set_supported_speed_count(int speed_count) { this->speed_count_ = speed_count; }
|
||||
/// Return if this fan supports changing direction
|
||||
bool supports_direction() const { return this->direction_; }
|
||||
/// Set whether this fan supports changing direction
|
||||
|
||||
@@ -37,16 +37,6 @@ FingerprintGrowComponent = fingerprint_grow_ns.class_(
|
||||
"FingerprintGrowComponent", cg.PollingComponent, uart.UARTDevice
|
||||
)
|
||||
|
||||
EnrollmentAction = fingerprint_grow_ns.class_("EnrollmentAction", automation.Action)
|
||||
CancelEnrollmentAction = fingerprint_grow_ns.class_(
|
||||
"CancelEnrollmentAction", automation.Action
|
||||
)
|
||||
DeleteAction = fingerprint_grow_ns.class_("DeleteAction", automation.Action)
|
||||
DeleteAllAction = fingerprint_grow_ns.class_("DeleteAllAction", automation.Action)
|
||||
LEDControlAction = fingerprint_grow_ns.class_("LEDControlAction", automation.Action)
|
||||
AuraLEDControlAction = fingerprint_grow_ns.class_(
|
||||
"AuraLEDControlAction", automation.Action
|
||||
)
|
||||
|
||||
AuraLEDState = fingerprint_grow_ns.enum("GrowAuraLEDState", True)
|
||||
AURA_LED_STATES = {
|
||||
@@ -181,103 +171,68 @@ async def to_code(config):
|
||||
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"fingerprint_grow.enroll",
|
||||
EnrollmentAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(FingerprintGrowComponent),
|
||||
cv.Required(CONF_FINGER_ID): cv.templatable(cv.uint16_t),
|
||||
cv.Optional(CONF_NUM_SCANS): cv.templatable(cv.uint8_t),
|
||||
},
|
||||
key=CONF_FINGER_ID,
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def fingerprint_grow_enroll_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
|
||||
template_ = await cg.templatable(config[CONF_FINGER_ID], args, cg.uint16)
|
||||
cg.add(var.set_finger_id(template_))
|
||||
if CONF_NUM_SCANS in config:
|
||||
template_ = await cg.templatable(config[CONF_NUM_SCANS], args, cg.uint8)
|
||||
cg.add(var.set_num_scans(template_))
|
||||
return var
|
||||
|
||||
|
||||
automation.register_parented_action(
|
||||
"fingerprint_grow.cancel_enroll",
|
||||
CancelEnrollmentAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(FingerprintGrowComponent),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"fingerprint_grow.delete",
|
||||
DeleteAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(FingerprintGrowComponent),
|
||||
cv.Required(CONF_FINGER_ID): cv.templatable(cv.uint16_t),
|
||||
},
|
||||
key=CONF_FINGER_ID,
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def fingerprint_grow_delete_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
|
||||
template_ = await cg.templatable(config[CONF_FINGER_ID], args, cg.uint16)
|
||||
cg.add(var.set_finger_id(template_))
|
||||
return var
|
||||
|
||||
|
||||
automation.register_parented_action(
|
||||
"fingerprint_grow.delete_all",
|
||||
DeleteAllAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(FingerprintGrowComponent),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
|
||||
|
||||
FINGERPRINT_GROW_LED_CONTROL_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||
FINGERPRINT_GROW_ID_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(FingerprintGrowComponent),
|
||||
cv.Required(CONF_STATE): cv.templatable(cv.boolean),
|
||||
},
|
||||
key=CONF_STATE,
|
||||
}
|
||||
)
|
||||
|
||||
automation.register_apply_action(
|
||||
"fingerprint_grow.enroll",
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(FingerprintGrowComponent),
|
||||
cv.Required(CONF_FINGER_ID): cv.templatable(cv.uint16_t),
|
||||
cv.Optional(CONF_NUM_SCANS, default=2): cv.templatable(
|
||||
cv.int_range(min=1, max=255)
|
||||
),
|
||||
},
|
||||
key=CONF_FINGER_ID,
|
||||
),
|
||||
automation.ApplyCall(
|
||||
"enroll_fingerprint({}, {})",
|
||||
((CONF_FINGER_ID, cg.uint16), (CONF_NUM_SCANS, cg.uint8)),
|
||||
),
|
||||
)
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"fingerprint_grow.cancel_enroll",
|
||||
FINGERPRINT_GROW_ID_SCHEMA,
|
||||
automation.ApplyCall("finish_enrollment(1)"),
|
||||
)
|
||||
|
||||
automation.register_apply_action(
|
||||
"fingerprint_grow.delete",
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(FingerprintGrowComponent),
|
||||
cv.Required(CONF_FINGER_ID): cv.templatable(cv.uint16_t),
|
||||
},
|
||||
key=CONF_FINGER_ID,
|
||||
),
|
||||
automation.ApplyField(CONF_FINGER_ID, "delete_fingerprint", cg.uint16),
|
||||
)
|
||||
|
||||
automation.register_apply_action(
|
||||
"fingerprint_grow.delete_all",
|
||||
FINGERPRINT_GROW_ID_SCHEMA,
|
||||
automation.ApplyCall("delete_all_fingerprints()"),
|
||||
)
|
||||
|
||||
automation.register_apply_action(
|
||||
"fingerprint_grow.led_control",
|
||||
LEDControlAction,
|
||||
FINGERPRINT_GROW_LED_CONTROL_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(FingerprintGrowComponent),
|
||||
cv.Required(CONF_STATE): cv.templatable(cv.boolean),
|
||||
},
|
||||
key=CONF_STATE,
|
||||
),
|
||||
automation.ApplyField(CONF_STATE, "led_control", cg.bool_),
|
||||
)
|
||||
async def fingerprint_grow_led_control_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
|
||||
template_ = await cg.templatable(config[CONF_STATE], args, cg.bool_)
|
||||
cg.add(var.set_state(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"fingerprint_grow.aura_led_control",
|
||||
AuraLEDControlAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(FingerprintGrowComponent),
|
||||
@@ -287,15 +242,13 @@ async def fingerprint_grow_led_control_to_code(config, action_id, template_arg,
|
||||
cv.Required(CONF_COUNT): cv.templatable(cv.uint8_t),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyCall(
|
||||
"aura_led_control({}, {}, {}, {})",
|
||||
(
|
||||
(CONF_STATE, cg.uint8),
|
||||
(CONF_SPEED, cg.uint8),
|
||||
(CONF_COLOR, cg.uint8),
|
||||
(CONF_COUNT, cg.uint8),
|
||||
),
|
||||
),
|
||||
)
|
||||
async def fingerprint_grow_aura_led_control_to_code(
|
||||
config, action_id, template_arg, args
|
||||
):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
|
||||
for key in [CONF_STATE, CONF_SPEED, CONF_COLOR, CONF_COUNT]:
|
||||
template_ = await cg.templatable(config[key], args, cg.uint8)
|
||||
cg.add(getattr(var, f"set_{key}")(template_))
|
||||
return var
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
@@ -209,71 +208,4 @@ class FingerprintGrowComponent final : public PollingComponent, public uart::UAR
|
||||
CallbackManager<void(uint16_t)> enrollment_failed_callback_;
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
class EnrollmentAction final : public Action<Ts...>, public Parented<FingerprintGrowComponent> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, finger_id)
|
||||
TEMPLATABLE_VALUE(uint8_t, num_scans)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto finger_id = this->finger_id_.value(x...);
|
||||
auto num_scans = this->num_scans_.value(x...);
|
||||
if (num_scans) {
|
||||
this->parent_->enroll_fingerprint(finger_id, num_scans);
|
||||
} else {
|
||||
this->parent_->enroll_fingerprint(finger_id, 2);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
class CancelEnrollmentAction final : public Action<Ts...>, public Parented<FingerprintGrowComponent> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->finish_enrollment(1); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class DeleteAction final : public Action<Ts...>, public Parented<FingerprintGrowComponent> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, finger_id)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto finger_id = this->finger_id_.value(x...);
|
||||
this->parent_->delete_fingerprint(finger_id);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class DeleteAllAction final : public Action<Ts...>, public Parented<FingerprintGrowComponent> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->delete_all_fingerprints(); }
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
class LEDControlAction final : public Action<Ts...>, public Parented<FingerprintGrowComponent> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(bool, state)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto state = this->state_.value(x...);
|
||||
this->parent_->led_control(state);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
class AuraLEDControlAction final : public Action<Ts...>, public Parented<FingerprintGrowComponent> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, state)
|
||||
TEMPLATABLE_VALUE(uint8_t, speed)
|
||||
TEMPLATABLE_VALUE(uint8_t, color)
|
||||
TEMPLATABLE_VALUE(uint8_t, count)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto state = this->state_.value(x...);
|
||||
auto speed = this->speed_.value(x...);
|
||||
auto color = this->color_.value(x...);
|
||||
auto count = this->count_.value(x...);
|
||||
|
||||
this->parent_->aura_led_control(state, speed, color, count);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esphome::fingerprint_grow
|
||||
@@ -226,7 +226,7 @@ uint8_t GreeClimate::vertical_swing_() {
|
||||
}
|
||||
|
||||
uint8_t GreeClimate::temperature_() {
|
||||
return (uint8_t) roundf(clamp<float>(this->target_temperature, GREE_TEMP_MIN, GREE_TEMP_MAX));
|
||||
return (uint8_t) roundf(clamp<float>(this->target_temperature, GREE_TEMP_MIN, GREE_TEMP_MAX)) - GREE_TEMP_MIN;
|
||||
}
|
||||
|
||||
uint8_t GreeClimate::preset_() {
|
||||
|
||||
@@ -20,28 +20,9 @@ grove_tb6612fng_ns = cg.esphome_ns.namespace("grove_tb6612fng")
|
||||
GROVE_TB6612FNG = grove_tb6612fng_ns.class_(
|
||||
"GroveMotorDriveTB6612FNG", cg.Component, i2c.I2CDevice
|
||||
)
|
||||
GROVETB6612FNGMotorRunAction = grove_tb6612fng_ns.class_(
|
||||
"GROVETB6612FNGMotorRunAction", automation.Action
|
||||
)
|
||||
GROVETB6612FNGMotorBrakeAction = grove_tb6612fng_ns.class_(
|
||||
"GROVETB6612FNGMotorBrakeAction", automation.Action
|
||||
)
|
||||
GROVETB6612FNGMotorStopAction = grove_tb6612fng_ns.class_(
|
||||
"GROVETB6612FNGMotorStopAction", automation.Action
|
||||
)
|
||||
GROVETB6612FNGMotorStandbyAction = grove_tb6612fng_ns.class_(
|
||||
"GROVETB6612FNGMotorStandbyAction", automation.Action
|
||||
)
|
||||
GROVETB6612FNGMotorNoStandbyAction = grove_tb6612fng_ns.class_(
|
||||
"GROVETB6612FNGMotorNoStandbyAction", automation.Action
|
||||
)
|
||||
GROVETB6612FNGMotorChangeAddressAction = grove_tb6612fng_ns.class_(
|
||||
"GROVETB6612FNGMotorChangeAddressAction", automation.Action
|
||||
)
|
||||
|
||||
DIRECTION_TYPE = {
|
||||
"FORWARD": 1,
|
||||
"BACKWARD": 2,
|
||||
"BACKWARD": -1,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
@@ -61,9 +42,8 @@ async def to_code(config):
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"grove_tb6612fng.run",
|
||||
GROVETB6612FNGMotorRunAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG),
|
||||
@@ -72,99 +52,52 @@ async def to_code(config):
|
||||
cv.Required(CONF_DIRECTION): cv.enum(DIRECTION_TYPE, upper=True),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyCall(
|
||||
"dc_motor_run({}, {} * {})",
|
||||
((CONF_CHANNEL, cg.uint8), (CONF_DIRECTION, cg.int16), (CONF_SPEED, cg.uint16)),
|
||||
),
|
||||
)
|
||||
async def grove_tb6612fng_run_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
|
||||
template_channel = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8)
|
||||
template_speed = await cg.templatable(config[CONF_SPEED], args, cg.uint16)
|
||||
cg.add(var.set_channel(template_channel))
|
||||
cg.add(var.set_speed(template_speed))
|
||||
cg.add(var.set_direction(config[CONF_DIRECTION] == "FORWARD"))
|
||||
return var
|
||||
CHANNEL_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG),
|
||||
cv.Required(CONF_CHANNEL): cv.templatable(cv.int_range(min=0, max=1)),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"grove_tb6612fng.break",
|
||||
GROVETB6612FNGMotorBrakeAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG),
|
||||
cv.Required(CONF_CHANNEL): cv.templatable(cv.int_range(min=0, max=1)),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
CHANNEL_SCHEMA,
|
||||
automation.ApplyField(CONF_CHANNEL, "dc_motor_brake", cg.uint8),
|
||||
)
|
||||
async def grove_tb6612fng_break_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
|
||||
template_channel = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8)
|
||||
cg.add(var.set_channel(template_channel))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"grove_tb6612fng.stop",
|
||||
GROVETB6612FNGMotorStopAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG),
|
||||
cv.Required(CONF_CHANNEL): cv.templatable(cv.int_range(min=0, max=1)),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def grove_tb6612fng_stop_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
|
||||
template_channel = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8)
|
||||
cg.add(var.set_channel(template_channel))
|
||||
return var
|
||||
|
||||
|
||||
automation.register_parented_action(
|
||||
"grove_tb6612fng.standby",
|
||||
GROVETB6612FNGMotorStandbyAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
CHANNEL_SCHEMA,
|
||||
automation.ApplyField(CONF_CHANNEL, "dc_motor_stop", cg.uint8),
|
||||
)
|
||||
|
||||
|
||||
automation.register_parented_action(
|
||||
"grove_tb6612fng.no_standby",
|
||||
GROVETB6612FNGMotorNoStandbyAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
PARENT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG),
|
||||
}
|
||||
)
|
||||
|
||||
automation.register_apply_action(
|
||||
"grove_tb6612fng.standby", PARENT_SCHEMA, automation.ApplyCall("standby()")
|
||||
)
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"grove_tb6612fng.no_standby", PARENT_SCHEMA, automation.ApplyCall("not_standby()")
|
||||
)
|
||||
|
||||
automation.register_apply_action(
|
||||
"grove_tb6612fng.change_address",
|
||||
GROVETB6612FNGMotorChangeAddressAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG),
|
||||
cv.Required(CONF_ADDRESS): cv.i2c_address,
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_ADDRESS, "set_i2c_addr", cg.uint8),
|
||||
)
|
||||
async def grove_tb6612fng_change_address_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
|
||||
template_channel = await cg.templatable(config[CONF_ADDRESS], args, cg.uint8)
|
||||
cg.add(var.set_address(template_channel))
|
||||
return var
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
// #include "esphome/core/helpers.h"
|
||||
@@ -161,61 +160,4 @@ class GroveMotorDriveTB6612FNG final : public Component, public i2c::I2CDevice {
|
||||
uint8_t buffer_[16];
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
class GROVETB6612FNGMotorRunAction final : public Action<Ts...>, public Parented<GroveMotorDriveTB6612FNG> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, channel)
|
||||
TEMPLATABLE_VALUE(uint16_t, speed)
|
||||
|
||||
void set_direction(bool forward) { this->forward_ = forward; }
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto channel = this->channel_.value(x...);
|
||||
int16_t speed = this->speed_.value(x...);
|
||||
if (!this->forward_) {
|
||||
speed = -speed;
|
||||
}
|
||||
this->parent_->dc_motor_run(channel, speed);
|
||||
}
|
||||
|
||||
protected:
|
||||
bool forward_{true};
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
class GROVETB6612FNGMotorBrakeAction final : public Action<Ts...>, public Parented<GroveMotorDriveTB6612FNG> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, channel)
|
||||
|
||||
void play(const Ts &...x) override { this->parent_->dc_motor_brake(this->channel_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
class GROVETB6612FNGMotorStopAction final : public Action<Ts...>, public Parented<GroveMotorDriveTB6612FNG> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, channel)
|
||||
|
||||
void play(const Ts &...x) override { this->parent_->dc_motor_stop(this->channel_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
class GROVETB6612FNGMotorStandbyAction final : public Action<Ts...>, public Parented<GroveMotorDriveTB6612FNG> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->standby(); }
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
class GROVETB6612FNGMotorNoStandbyAction final : public Action<Ts...>, public Parented<GroveMotorDriveTB6612FNG> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->not_standby(); }
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
class GROVETB6612FNGMotorChangeAddressAction final : public Action<Ts...>, public Parented<GroveMotorDriveTB6612FNG> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, address)
|
||||
|
||||
void play(const Ts &...x) override { this->parent_->set_i2c_addr(this->address_.value(x...)); }
|
||||
};
|
||||
|
||||
} // namespace esphome::grove_tb6612fng
|
||||
@@ -27,9 +27,6 @@ DECAY_MODE_OPTIONS = {
|
||||
"FAST": DecayMode.DECAY_MODE_FAST,
|
||||
}
|
||||
|
||||
# Actions
|
||||
BrakeAction = hbridge_ns.class_("BrakeAction", automation.Action)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
fan.fan_schema(HBridgeFan)
|
||||
.extend(
|
||||
@@ -39,7 +36,7 @@ CONFIG_SCHEMA = (
|
||||
cv.Optional(CONF_DECAY_MODE, default="SLOW"): cv.enum(
|
||||
DECAY_MODE_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1, max=255),
|
||||
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1),
|
||||
cv.Optional(CONF_ENABLE_PIN): cv.use_id(output.FloatOutput),
|
||||
cv.Optional(CONF_PRESET_MODES): validate_preset_modes,
|
||||
}
|
||||
@@ -48,11 +45,10 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
automation.register_simple_action(
|
||||
automation.register_apply_action(
|
||||
"fan.hbridge.brake",
|
||||
BrakeAction,
|
||||
maybe_simple_id({cv.GenerateID(): cv.use_id(HBridgeFan)}),
|
||||
synchronous=True,
|
||||
automation.ApplyCall("brake()"),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/output/binary_output.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include "esphome/components/fan/fan.h"
|
||||
@@ -14,7 +13,7 @@ enum DecayMode {
|
||||
|
||||
class HBridgeFan final : public Component, public fan::Fan {
|
||||
public:
|
||||
HBridgeFan(uint8_t speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {}
|
||||
HBridgeFan(int speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {}
|
||||
|
||||
void set_pin_a(output::FloatOutput *pin_a) { pin_a_ = pin_a; }
|
||||
void set_pin_b(output::FloatOutput *pin_b) { pin_b_ = pin_b; }
|
||||
@@ -35,7 +34,7 @@ class HBridgeFan final : public Component, public fan::Fan {
|
||||
output::FloatOutput *pin_b_;
|
||||
output::FloatOutput *enable_{nullptr};
|
||||
output::BinaryOutput *oscillating_{nullptr};
|
||||
uint8_t speed_count_{};
|
||||
int speed_count_{};
|
||||
DecayMode decay_mode_{DECAY_MODE_SLOW};
|
||||
fan::FanTraits traits_;
|
||||
|
||||
@@ -46,13 +45,4 @@ class HBridgeFan final : public Component, public fan::Fan {
|
||||
void set_hbridge_levels_(float a_level, float b_level, float enable);
|
||||
};
|
||||
|
||||
template<typename... Ts> class BrakeAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit BrakeAction(HBridgeFan *parent) : parent_(parent) {}
|
||||
|
||||
void play(const Ts &...x) override { this->parent_->brake(); }
|
||||
|
||||
HBridgeFan *parent_;
|
||||
};
|
||||
|
||||
} // namespace esphome::hbridge
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
@@ -26,11 +25,4 @@ class HC8Component final : public PollingComponent, public uart::UARTDevice {
|
||||
bool warmup_complete_{false};
|
||||
};
|
||||
|
||||
template<typename... Ts> class HC8CalibrateAction final : public Action<Ts...>, public Parented<HC8Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, baseline)
|
||||
|
||||
void play(const Ts &...x) override { this->parent_->calibrate(this->baseline_.value(x...)); }
|
||||
};
|
||||
|
||||
} // namespace esphome::hc8
|
||||
@@ -12,15 +12,12 @@ from esphome.const import (
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
)
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.types import ConfigType
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
hc8_ns = cg.esphome_ns.namespace("hc8")
|
||||
HC8Component = hc8_ns.class_("HC8Component", cg.PollingComponent, uart.UARTDevice)
|
||||
HC8CalibrateAction = hc8_ns.class_("HC8CalibrateAction", automation.Action)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
@@ -73,20 +70,8 @@ CALIBRATION_ACTION_SCHEMA = cv.Schema(
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"hc8.calibrate",
|
||||
HC8CalibrateAction,
|
||||
CALIBRATION_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_BASELINE, "calibrate", cg.uint16),
|
||||
)
|
||||
async def hc8_calibration_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_BASELINE], args, cg.uint16)
|
||||
cg.add(var.set_baseline(template_))
|
||||
return var
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
@@ -48,21 +47,4 @@ class HDC302XComponent final : public PollingComponent, public i2c::I2CDevice {
|
||||
uint32_t conversion_delay_ms_();
|
||||
};
|
||||
|
||||
template<typename... Ts> class HeaterOnAction final : public Action<Ts...>, public Parented<HDC302XComponent> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, power)
|
||||
TEMPLATABLE_VALUE(uint32_t, duration)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto power_val = this->power_.value(x...);
|
||||
auto duration_val = this->duration_.value(x...);
|
||||
this->parent_->start_heater(power_val, duration_val);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class HeaterOffAction final : public Action<Ts...>, public Parented<HDC302XComponent> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->stop_heater(); }
|
||||
};
|
||||
|
||||
} // namespace esphome::hdc302x
|
||||
@@ -18,8 +18,6 @@ from esphome.const import (
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.types import ConfigType
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
@@ -37,10 +35,6 @@ POWER_MODE_OPTIONS = {
|
||||
"ULTRA_LOW_POWER": HDC302XPowerMode.ULTRA_LOW_POWER,
|
||||
}
|
||||
|
||||
# Actions
|
||||
HeaterOnAction = hdc302x_ns.class_("HeaterOnAction", automation.Action)
|
||||
HeaterOffAction = hdc302x_ns.class_("HeaterOffAction", automation.Action)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -118,30 +112,14 @@ HDC302X_HEATER_ON_ACTION_SCHEMA = maybe_simple_id(
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"hdc302x.heater_on",
|
||||
HeaterOnAction,
|
||||
HDC302X_HEATER_ON_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
automation.ApplyCall(
|
||||
"start_heater({}, {})", ((CONF_POWER, cg.uint16), (CONF_DURATION, cg.uint32))
|
||||
),
|
||||
)
|
||||
async def hdc302x_heater_on_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_POWER], args, cg.uint16)
|
||||
cg.add(var.set_power(template_))
|
||||
template_ = await cg.templatable(config[CONF_DURATION], args, cg.uint32)
|
||||
cg.add(var.set_duration(template_))
|
||||
return var
|
||||
|
||||
|
||||
automation.register_parented_action(
|
||||
"hdc302x.heater_off",
|
||||
HeaterOffAction,
|
||||
HDC302X_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
automation.register_apply_action(
|
||||
"hdc302x.heater_off", HDC302X_ACTION_SCHEMA, automation.ApplyCall("stop_heater()")
|
||||
)
|
||||
@@ -187,7 +187,7 @@ void HeatpumpIRClimate::transmit_state() {
|
||||
swing_h_cmd = HDIR_SWING;
|
||||
}
|
||||
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO)) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
fan_speed_cmd = FAN_2;
|
||||
break;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/core/automation.h"
|
||||
|
||||
namespace esphome::htu21d {
|
||||
|
||||
@@ -34,26 +33,4 @@ class HTU21DComponent final : public PollingComponent, public i2c::I2CDevice {
|
||||
HTU21DSensorModels sensor_model_{HTU21D_SENSOR_MODEL_HTU21D};
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetHeaterLevelAction final : public Action<Ts...>, public Parented<HTU21DComponent> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, level)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto level = this->level_.value(x...);
|
||||
|
||||
this->parent_->set_heater_level(level);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetHeaterAction final : public Action<Ts...>, public Parented<HTU21DComponent> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(bool, status)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto status = this->status_.value(x...);
|
||||
|
||||
this->parent_->set_heater(status);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esphome::htu21d
|
||||
@@ -17,8 +17,6 @@ from esphome.const import (
|
||||
UNIT_EMPTY,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.types import ConfigType
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
@@ -27,8 +25,6 @@ htu21d_ns = cg.esphome_ns.namespace("htu21d")
|
||||
HTU21DComponent = htu21d_ns.class_(
|
||||
"HTU21DComponent", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
SetHeaterLevelAction = htu21d_ns.class_("SetHeaterLevelAction", automation.Action)
|
||||
SetHeaterAction = htu21d_ns.class_("SetHeaterAction", automation.Action)
|
||||
HTU21DSensorModels = htu21d_ns.enum("HTU21DSensorModels")
|
||||
|
||||
MODELS = {
|
||||
@@ -86,9 +82,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(var.set_sensor_model(config[CONF_MODEL]))
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"htu21d.set_heater_level",
|
||||
SetHeaterLevelAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(HTU21DComponent),
|
||||
@@ -96,24 +91,11 @@ async def to_code(config: ConfigType) -> None:
|
||||
},
|
||||
key=CONF_LEVEL,
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_LEVEL, "set_heater_level", cg.uint8),
|
||||
)
|
||||
async def set_heater_level_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
level_ = await cg.templatable(config[CONF_LEVEL], args, cg.uint8)
|
||||
cg.add(var.set_level(level_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"htu21d.set_heater",
|
||||
SetHeaterAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(HTU21DComponent),
|
||||
@@ -121,16 +103,5 @@ async def set_heater_level_to_code(
|
||||
},
|
||||
key=CONF_STATUS,
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_STATUS, "set_heater", cg.bool_),
|
||||
)
|
||||
async def set_heater_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
status_ = await cg.templatable(config[CONF_STATUS], args, cg.bool_)
|
||||
cg.add(var.set_status(status_))
|
||||
return var
|
||||
@@ -18,8 +18,7 @@ from esphome.const import (
|
||||
CONF_ROTATION,
|
||||
CONF_UPDATE_INTERVAL,
|
||||
)
|
||||
from esphome.core import ID, EnumValue
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.core import EnumValue
|
||||
import esphome.final_validate as fv
|
||||
from esphome.helpers import add_class_to_obj
|
||||
from esphome.types import ConfigType
|
||||
@@ -167,7 +166,6 @@ ROTATIONS = {
|
||||
HUB75Display = hub75_ns.class_("HUB75Display", cg.PollingComponent, display.Display)
|
||||
Hub75Config = cg.global_ns.struct("Hub75Config")
|
||||
Hub75Pins = cg.global_ns.struct("Hub75Pins")
|
||||
SetBrightnessAction = hub75_ns.class_("SetBrightnessAction", automation.Action)
|
||||
|
||||
|
||||
def _merge_board_pins(config: ConfigType) -> ConfigType:
|
||||
@@ -619,9 +617,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(var.set_writer(lambda_))
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"hub75.set_brightness",
|
||||
SetBrightnessAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(HUB75Display),
|
||||
@@ -629,16 +626,5 @@ async def to_code(config: ConfigType) -> None:
|
||||
},
|
||||
key=CONF_BRIGHTNESS,
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_BRIGHTNESS, "set_brightness", cg.uint8),
|
||||
)
|
||||
async def hub75_set_brightness_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_BRIGHTNESS], args, cg.uint8)
|
||||
cg.add(var.set_brightness(template_))
|
||||
return var
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <utility>
|
||||
|
||||
#include "esphome/components/display/display_buffer.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -51,13 +50,6 @@ class HUB75Display final : public display::Display {
|
||||
bool enabled_{false};
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetBrightnessAction final : public Action<Ts...>, public Parented<HUB75Display> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, brightness)
|
||||
|
||||
void play(const Ts &...x) override { this->parent_->set_brightness(this->brightness_.value(x...)); }
|
||||
};
|
||||
|
||||
} // namespace esphome::hub75
|
||||
|
||||
#endif
|
||||
@@ -118,7 +118,7 @@ void IDFI2CBus::dump_config() {
|
||||
if (s.second) {
|
||||
ESP_LOGCONFIG(TAG, "Found device at address 0x%02X", s.first);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "Unknown error at address 0x%02X", s.first);
|
||||
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +36,7 @@ from esphome.const import (
|
||||
CONF_UPDATE_INTERVAL,
|
||||
CONF_WIDTH,
|
||||
)
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import MockObj, RawExpression, TemplateArgsType
|
||||
from esphome.cpp_generator import RawExpression
|
||||
from esphome.final_validate import full_config
|
||||
from esphome.types import ConfigType
|
||||
|
||||
@@ -61,7 +60,6 @@ VCOM_REGISTER_OPTIONS = (VCOM_REGISTER_DEFAULT, VCOM_REGISTER_ALT)
|
||||
|
||||
it8951_ns = cg.esphome_ns.namespace("it8951")
|
||||
IT8951Display = it8951_ns.class_("IT8951Display", display.Display, spi.SPIDevice)
|
||||
IT8951UpdateAction = it8951_ns.class_("IT8951UpdateAction", automation.Action)
|
||||
|
||||
# Hardware waveform modes exposed to YAML. Strings are mapped to the C++
|
||||
# UpdateMode enum so the runtime can store the mode as a uint16_t rather
|
||||
@@ -80,6 +78,7 @@ UPDATE_MODE_OPTIONS = {
|
||||
"A2": UpdateMode.UPDATE_MODE_A2,
|
||||
"FAST": UpdateMode.UPDATE_MODE_DU,
|
||||
"FULL": UpdateMode.UPDATE_MODE_GC16,
|
||||
"DEFAULT": UpdateMode.UPDATE_MODE_NONE,
|
||||
}
|
||||
# Maps the YAML mode string directly to the C++ UpdateMode enum value, so the
|
||||
# config option and the it8951.update action share one validator.
|
||||
@@ -419,26 +418,13 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(var.set_transform(RawExpression(str(transform_value))))
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"it8951.update",
|
||||
IT8951UpdateAction,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(IT8951Display),
|
||||
cv.Optional(CONF_MODE): cv.templatable(update_mode),
|
||||
cv.Optional(CONF_MODE, default="DEFAULT"): cv.templatable(update_mode),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_MODE, "update_mode", UpdateMode),
|
||||
)
|
||||
async def it8951_update_action_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
display_var = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, display_var)
|
||||
if mode := config.get(CONF_MODE):
|
||||
mode = await cg.templatable(mode, args, UpdateMode)
|
||||
cg.add(var.set_mode(mode))
|
||||
return var
|
||||
@@ -777,12 +777,12 @@ void IT8951Display::update() {
|
||||
}
|
||||
|
||||
void IT8951Display::update_mode(UpdateMode mode) {
|
||||
if (!this->is_ready())
|
||||
return;
|
||||
if (mode == UPDATE_MODE_NONE) {
|
||||
ESP_LOGW(TAG, "Unknown update mode");
|
||||
this->update();
|
||||
return;
|
||||
}
|
||||
if (!this->is_ready())
|
||||
return;
|
||||
this->start_update_(mode);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#include "esphome/components/display/display.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
@@ -350,24 +349,4 @@ class IT8951Display : public Display,
|
||||
uint8_t dev_info_attempts_{0};
|
||||
};
|
||||
|
||||
// --- Automation action ---
|
||||
template<typename... Ts> class IT8951UpdateAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit IT8951UpdateAction(IT8951Display *display) : display_(display) {}
|
||||
TEMPLATABLE_VALUE(UpdateMode, mode)
|
||||
|
||||
protected:
|
||||
void play(const Ts &...x) override {
|
||||
if (!this->display_->is_ready())
|
||||
return;
|
||||
if (this->mode_.has_value()) {
|
||||
this->display_->update_mode(this->mode_.value(x...));
|
||||
} else {
|
||||
this->display_->update();
|
||||
}
|
||||
}
|
||||
|
||||
IT8951Display *display_;
|
||||
};
|
||||
|
||||
} // namespace esphome::it8951
|
||||
@@ -35,8 +35,6 @@ CONF_ON_RESULT = "on_result"
|
||||
|
||||
key_collector_ns = cg.esphome_ns.namespace("key_collector")
|
||||
KeyCollector = key_collector_ns.class_("KeyCollector", cg.Component)
|
||||
EnableAction = key_collector_ns.class_("EnableAction", automation.Action)
|
||||
DisableAction = key_collector_ns.class_("DisableAction", automation.Action)
|
||||
|
||||
X_TYPE = cg.std_string_ref.operator("const")
|
||||
|
||||
@@ -134,25 +132,19 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(var.set_enabled(config[CONF_ENABLE_ON_BOOT]))
|
||||
|
||||
|
||||
automation.register_parented_action(
|
||||
KEY_COLLECTOR_ACTION_SCHEMA = automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(KeyCollector),
|
||||
}
|
||||
)
|
||||
|
||||
automation.register_apply_action(
|
||||
"key_collector.enable",
|
||||
EnableAction,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(KeyCollector),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
KEY_COLLECTOR_ACTION_SCHEMA,
|
||||
automation.ApplyCall("set_enabled(true)"),
|
||||
)
|
||||
|
||||
|
||||
automation.register_parented_action(
|
||||
automation.register_apply_action(
|
||||
"key_collector.disable",
|
||||
DisableAction,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(KeyCollector),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
KEY_COLLECTOR_ACTION_SCHEMA,
|
||||
automation.ApplyCall("set_enabled(false)"),
|
||||
)
|
||||
@@ -54,12 +54,4 @@ class KeyCollector final : public Component {
|
||||
bool enabled_{};
|
||||
};
|
||||
|
||||
template<typename... Ts> class EnableAction final : public Action<Ts...>, public Parented<KeyCollector> {
|
||||
void play(const Ts &...x) override { this->parent_->set_enabled(true); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class DisableAction final : public Action<Ts...>, public Parented<KeyCollector> {
|
||||
void play(const Ts &...x) override { this->parent_->set_enabled(false); }
|
||||
};
|
||||
|
||||
} // namespace esphome::key_collector
|
||||
@@ -4,8 +4,6 @@ import esphome.codegen as cg
|
||||
from esphome.components import uart
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_PASSWORD, CONF_THROTTLE, CONF_TIMEOUT
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.types import ConfigType
|
||||
|
||||
AUTO_LOAD = ["ld24xx"]
|
||||
@@ -86,33 +84,15 @@ CALIBRATION_ACTION_SCHEMA = maybe_simple_id(
|
||||
|
||||
|
||||
# Actions
|
||||
BluetoothPasswordSetAction = ld2410_ns.class_(
|
||||
"BluetoothPasswordSetAction", automation.Action
|
||||
)
|
||||
|
||||
|
||||
BLUETOOTH_PASSWORD_SET_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(LD2410Component),
|
||||
cv.Required(CONF_PASSWORD): cv.templatable(cv.string_strict),
|
||||
cv.Required(CONF_PASSWORD): cv.sensitive(cv.templatable(cv.string_strict)),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
automation.register_apply_action(
|
||||
"bluetooth_password.set",
|
||||
BluetoothPasswordSetAction,
|
||||
BLUETOOTH_PASSWORD_SET_SCHEMA,
|
||||
synchronous=True,
|
||||
automation.ApplyField(CONF_PASSWORD, "set_bluetooth_password", cg.std_string),
|
||||
)
|
||||
async def bluetooth_password_set_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
template_ = await cg.templatable(config[CONF_PASSWORD], args, cg.std_string)
|
||||
cg.add(var.set_password(template_))
|
||||
return var
|
||||
@@ -1,20 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "ld2410.h"
|
||||
|
||||
namespace esphome::ld2410 {
|
||||
|
||||
template<typename... Ts> class BluetoothPasswordSetAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit BluetoothPasswordSetAction(LD2410Component *ld2410_comp) : ld2410_comp_(ld2410_comp) {}
|
||||
TEMPLATABLE_VALUE(std::string, password)
|
||||
|
||||
void play(const Ts &...x) override { this->ld2410_comp_->set_bluetooth_password(this->password_.value(x...)); }
|
||||
|
||||
protected:
|
||||
LD2410Component *ld2410_comp_;
|
||||
};
|
||||
|
||||
} // namespace esphome::ld2410
|
||||
@@ -24,7 +24,6 @@
|
||||
#endif
|
||||
#include "esphome/components/ld24xx/ld24xx.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
@@ -31,9 +31,6 @@
|
||||
|
||||
namespace esphome::ld24xx {
|
||||
|
||||
/// Parse a little-endian 16-bit unsigned value from two bytes.
|
||||
static constexpr uint16_t two_byte_to_uint16(uint8_t low, uint8_t high) { return ((uint16_t) high << 8) | low; }
|
||||
|
||||
// Helper to find index of value in constexpr array
|
||||
template<size_t N> optional<size_t> find_index(const uint32_t (&arr)[N], uint32_t value) {
|
||||
for (size_t i = 0; i < N; i++) {
|
||||
|
||||
@@ -10,20 +10,13 @@ from esphome.components.const import CONF_CHANNEL_COLORS, CONF_IS_WRGB
|
||||
from esphome.config_helpers import filter_source_files_from_defines
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BLUE,
|
||||
CONF_BRIGHTNESS,
|
||||
CONF_COLD_WHITE,
|
||||
CONF_COLD_WHITE_COLOR_TEMPERATURE,
|
||||
CONF_COLOR_BRIGHTNESS,
|
||||
CONF_COLOR_CORRECT,
|
||||
CONF_COLOR_MODE,
|
||||
CONF_COLOR_TEMPERATURE,
|
||||
CONF_DEFAULT_TRANSITION_LENGTH,
|
||||
CONF_EFFECTS,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_FLASH_TRANSITION_LENGTH,
|
||||
CONF_GAMMA_CORRECT,
|
||||
CONF_GREEN,
|
||||
CONF_ICON,
|
||||
CONF_ID,
|
||||
CONF_INITIAL_STATE,
|
||||
@@ -35,17 +28,14 @@ from esphome.const import (
|
||||
CONF_ON_TURN_ON,
|
||||
CONF_OUTPUT_ID,
|
||||
CONF_POWER_SUPPLY,
|
||||
CONF_RED,
|
||||
CONF_RESTORE_MODE,
|
||||
CONF_RESTORE_STATE,
|
||||
CONF_RGB_ORDER,
|
||||
CONF_STATE,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_WARM_WHITE,
|
||||
CONF_WARM_WHITE_COLOR_TEMPERATURE,
|
||||
CONF_WEB_SERVER,
|
||||
CONF_WHITE,
|
||||
)
|
||||
from esphome.core import CORE, ID, CoroPriority, HexInt, Lambda, coroutine_with_priority
|
||||
from esphome.core import CORE, ID, CoroPriority, HexInt, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import (
|
||||
entity_duplicate_validator,
|
||||
queue_entity_register,
|
||||
@@ -64,6 +54,17 @@ from .effects import (
|
||||
RGB_EFFECTS,
|
||||
validate_effects,
|
||||
)
|
||||
from .restore_state import (
|
||||
LEGACY_RESTORE_MODES,
|
||||
RESTORE_STATE_NONE,
|
||||
RESTORE_STATE_SCHEMA,
|
||||
_build_state_lambda,
|
||||
_initial_state_overridden_by_legacy_mode,
|
||||
_initial_state_statements,
|
||||
_legacy_cold_boot_statements,
|
||||
_legacy_restore_statements,
|
||||
_restore_state_statements,
|
||||
)
|
||||
from .types import ( # noqa: F401
|
||||
AddressableLight,
|
||||
AddressableLightState,
|
||||
@@ -273,17 +274,34 @@ def migrate_channel_colors(
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
"""Validate all recorded effect name references against their target lights.
|
||||
"""Validate every configured light's own resolved config, and all recorded
|
||||
effect name references against their target lights.
|
||||
|
||||
This runs once per light platform instance. If no light platform is configured,
|
||||
this never runs — but the ID validator will catch the missing light ID separately.
|
||||
FINAL_VALIDATE_SCHEMA for a platform-based domain like `light:` runs once for
|
||||
the whole domain, not once per entry -- `config` is the full list of light
|
||||
platform entries across the file, not a single light's own config.
|
||||
"""
|
||||
for light_config in config:
|
||||
restore_mode = light_config.get(CONF_RESTORE_MODE)
|
||||
if restore_mode is not None:
|
||||
legacy = LEGACY_RESTORE_MODES[restore_mode]
|
||||
if _initial_state_overridden_by_legacy_mode(
|
||||
legacy, light_config.get(CONF_INITIAL_STATE)
|
||||
):
|
||||
_LOGGER.warning(
|
||||
"[%s] 'initial_state: state' is ignored because 'restore_mode: %s' "
|
||||
"always sets the light %s at boot; use 'restore_state:' instead for "
|
||||
"per-field control",
|
||||
light_config.get(CONF_NAME) or light_config[CONF_ID],
|
||||
restore_mode,
|
||||
"ON" if legacy.cold_boot_state else "OFF",
|
||||
)
|
||||
|
||||
data = _get_data()
|
||||
if not data.effect_refs and not data.effect_cycle_refs:
|
||||
return
|
||||
|
||||
# Drain the lists so we only validate once even though
|
||||
# FINAL_VALIDATE_SCHEMA runs for each light platform instance.
|
||||
# Drain the lists so each recorded reference is only validated once.
|
||||
refs = data.effect_refs
|
||||
data.effect_refs = []
|
||||
cycle_refs = data.effect_cycle_refs
|
||||
@@ -328,18 +346,6 @@ def _final_validate(config: ConfigType) -> None:
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
LightRestoreMode = light_ns.enum("LightRestoreMode")
|
||||
RESTORE_MODES = {
|
||||
"RESTORE_DEFAULT_OFF": LightRestoreMode.LIGHT_RESTORE_DEFAULT_OFF,
|
||||
"RESTORE_DEFAULT_ON": LightRestoreMode.LIGHT_RESTORE_DEFAULT_ON,
|
||||
"ALWAYS_OFF": LightRestoreMode.LIGHT_ALWAYS_OFF,
|
||||
"ALWAYS_ON": LightRestoreMode.LIGHT_ALWAYS_ON,
|
||||
"RESTORE_INVERTED_DEFAULT_OFF": LightRestoreMode.LIGHT_RESTORE_INVERTED_DEFAULT_OFF,
|
||||
"RESTORE_INVERTED_DEFAULT_ON": LightRestoreMode.LIGHT_RESTORE_INVERTED_DEFAULT_ON,
|
||||
"RESTORE_AND_OFF": LightRestoreMode.LIGHT_RESTORE_AND_OFF,
|
||||
"RESTORE_AND_ON": LightRestoreMode.LIGHT_RESTORE_AND_ON,
|
||||
}
|
||||
|
||||
# Schema default that also matches the C++ initializer in light_state.h; codegen
|
||||
# skips the setter when the config equals it.
|
||||
DEFAULT_FLASH_TRANSITION_LENGTH = "0s"
|
||||
@@ -354,9 +360,10 @@ LIGHT_SCHEMA = (
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||
mqtt.MQTTJSONLightComponent
|
||||
),
|
||||
cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum(
|
||||
RESTORE_MODES, upper=True, space="_"
|
||||
cv.Exclusive(CONF_RESTORE_MODE, "restore"): cv.one_of(
|
||||
*LEGACY_RESTORE_MODES, upper=True, space="_"
|
||||
),
|
||||
cv.Exclusive(CONF_RESTORE_STATE, "restore"): RESTORE_STATE_SCHEMA,
|
||||
cv.Optional(CONF_ON_TURN_ON): auto.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOnTrigger),
|
||||
@@ -435,6 +442,28 @@ class LightType(enum.IntEnum):
|
||||
ADDRESSABLE = 3
|
||||
|
||||
|
||||
def _apply_default_restore_mode(
|
||||
default_restore_mode: str,
|
||||
) -> Callable[[ConfigType], ConfigType]:
|
||||
# cv.Exclusive has no default, so apply the default here if neither key is configured.
|
||||
def validator(config: ConfigType) -> ConfigType:
|
||||
if CONF_RESTORE_MODE not in config and CONF_RESTORE_STATE not in config:
|
||||
config[CONF_RESTORE_MODE] = cv.one_of(
|
||||
*LEGACY_RESTORE_MODES, upper=True, space="_"
|
||||
)(default_restore_mode)
|
||||
return config
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
_BASE_SCHEMAS: dict[LightType, cv.Schema] = {
|
||||
LightType.BINARY: BINARY_LIGHT_SCHEMA,
|
||||
LightType.BRIGHTNESS_ONLY: BRIGHTNESS_ONLY_LIGHT_SCHEMA,
|
||||
LightType.RGB: RGB_LIGHT_SCHEMA,
|
||||
LightType.ADDRESSABLE: ADDRESSABLE_LIGHT_SCHEMA,
|
||||
}
|
||||
|
||||
|
||||
def light_schema(
|
||||
class_: MockObjClass,
|
||||
type_: LightType,
|
||||
@@ -450,25 +479,14 @@ def light_schema(
|
||||
for key, default, validator in [
|
||||
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
|
||||
(CONF_ICON, icon, cv.icon),
|
||||
(
|
||||
CONF_RESTORE_MODE,
|
||||
default_restore_mode,
|
||||
cv.enum(RESTORE_MODES, upper=True, space="_"),
|
||||
),
|
||||
]:
|
||||
if default is not cv.UNDEFINED:
|
||||
schema[cv.Optional(key, default=default)] = validator
|
||||
|
||||
if type_ == LightType.BINARY:
|
||||
return BINARY_LIGHT_SCHEMA.extend(schema)
|
||||
if type_ == LightType.BRIGHTNESS_ONLY:
|
||||
return BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend(schema)
|
||||
if type_ == LightType.RGB:
|
||||
return RGB_LIGHT_SCHEMA.extend(schema)
|
||||
if type_ == LightType.ADDRESSABLE:
|
||||
return ADDRESSABLE_LIGHT_SCHEMA.extend(schema)
|
||||
|
||||
raise ValueError(f"Invalid light type: {type_}")
|
||||
result = _BASE_SCHEMAS[type_].extend(schema)
|
||||
if default_restore_mode is not cv.UNDEFINED:
|
||||
result.add_extra(_apply_default_restore_mode(default_restore_mode))
|
||||
return result
|
||||
|
||||
|
||||
def validate_color_temperature_channels(value):
|
||||
@@ -487,31 +505,43 @@ def validate_color_temperature_channels(value):
|
||||
|
||||
@setup_entity("light")
|
||||
async def setup_light_core_(light_var, config, output_var):
|
||||
cg.add(light_var.set_restore_mode(config[CONF_RESTORE_MODE]))
|
||||
# All 8 legacy restore_mode values, and the restore_state key, are just different
|
||||
# ways to build the same state callback and save_enabled flag that LightState's
|
||||
# runtime actually understands.
|
||||
initial_state_config = config.get(CONF_INITIAL_STATE)
|
||||
initial_statements = await _initial_state_statements(initial_state_config)
|
||||
|
||||
if (initial_state_config := config.get(CONF_INITIAL_STATE)) is not None:
|
||||
# Emit a stateless lambda that constructs the initial state — values live
|
||||
# in flash as code, not stored in the LightState object (~40 bytes saved).
|
||||
initial_state = LightStateRTCState(
|
||||
initial_state_config.get(CONF_COLOR_MODE, ColorMode.UNKNOWN),
|
||||
initial_state_config.get(CONF_STATE, False),
|
||||
initial_state_config.get(CONF_BRIGHTNESS, 1.0),
|
||||
initial_state_config.get(CONF_COLOR_BRIGHTNESS, 1.0),
|
||||
initial_state_config.get(CONF_RED, 1.0),
|
||||
initial_state_config.get(CONF_GREEN, 1.0),
|
||||
initial_state_config.get(CONF_BLUE, 1.0),
|
||||
initial_state_config.get(CONF_WHITE, 1.0),
|
||||
initial_state_config.get(CONF_COLOR_TEMPERATURE, 1.0),
|
||||
initial_state_config.get(CONF_COLD_WHITE, 1.0),
|
||||
initial_state_config.get(CONF_WARM_WHITE, 1.0),
|
||||
restore_mode = config.get(CONF_RESTORE_MODE)
|
||||
restore_state_config = config.get(CONF_RESTORE_STATE)
|
||||
if restore_state_config == RESTORE_STATE_NONE:
|
||||
# restore_state: none is explicit shorthand for "no restoring at all" --
|
||||
restore_state_config = None
|
||||
|
||||
if restore_mode is not None:
|
||||
legacy = LEGACY_RESTORE_MODES[restore_mode]
|
||||
initial_statements.extend(
|
||||
_legacy_cold_boot_statements(legacy, initial_state_config)
|
||||
)
|
||||
args = [(LightStateRTCState.operator("ref"), "s")]
|
||||
lamb = await cg.process_lambda(
|
||||
Lambda(f"s = {initial_state};"),
|
||||
args,
|
||||
return_type=cg.void,
|
||||
restore_statements = _legacy_restore_statements(legacy)
|
||||
save_enabled = legacy.save_enabled
|
||||
elif restore_state_config is not None:
|
||||
restore_statements = await _restore_state_statements(
|
||||
restore_state_config, initial_state_config
|
||||
)
|
||||
cg.add(light_var.set_initial_state(lamb))
|
||||
save_enabled = True
|
||||
else:
|
||||
# Neither key configured: no persistence, and no cold-boot forcing either.
|
||||
restore_statements = []
|
||||
save_enabled = False
|
||||
|
||||
if (
|
||||
lamb := await _build_state_lambda(
|
||||
initial_statements, restore_statements, save_enabled
|
||||
)
|
||||
) is not None:
|
||||
cg.add(light_var.set_state_callback(lamb))
|
||||
if save_enabled: # matches LightState::save_enabled_'s own default of false
|
||||
cg.add(light_var.set_save_enabled(save_enabled))
|
||||
|
||||
if (
|
||||
default_transition_length := config.get(CONF_DEFAULT_TRANSITION_LENGTH)
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
from collections.abc import Callable
|
||||
import logging
|
||||
from typing import Any, NamedTuple
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const.css_colors import CSS_COLORS
|
||||
from esphome.config import path_context
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -7,6 +12,7 @@ from esphome.const import (
|
||||
CONF_BRIGHTNESS,
|
||||
CONF_BRIGHTNESS_LIMITS,
|
||||
CONF_COLD_WHITE,
|
||||
CONF_COLOR,
|
||||
CONF_COLOR_BRIGHTNESS,
|
||||
CONF_COLOR_MODE,
|
||||
CONF_COLOR_TEMPERATURE,
|
||||
@@ -28,6 +34,7 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core import CORE, ID, EsphomeError, Lambda
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .types import (
|
||||
@@ -42,8 +49,32 @@ from .types import (
|
||||
ToggleAction,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_INCLUDE_NONE = "include_none"
|
||||
|
||||
_STATE_ON_OFF = cv.one_of("ON", "OFF", upper=True)
|
||||
|
||||
|
||||
@schema_extractor("one_of")
|
||||
def validate_light_state(value: Any) -> Any:
|
||||
"""Validate a light on/off state.
|
||||
|
||||
Documented as 'ON'/'OFF', but accepts all boolean forms for backward compatibility.
|
||||
"""
|
||||
if value == SCHEMA_EXTRACT:
|
||||
return ("ON", "OFF")
|
||||
try:
|
||||
return _STATE_ON_OFF(value) == "ON"
|
||||
except cv.Invalid:
|
||||
pass
|
||||
try:
|
||||
return cv.boolean(value)
|
||||
except cv.Invalid as err:
|
||||
raise cv.Invalid(
|
||||
f"Expected 'ON', 'OFF', or a boolean value, got {value!r}"
|
||||
) from err
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"light.toggle",
|
||||
@@ -71,21 +102,103 @@ async def light_toggle_to_code(config, action_id, template_arg, args):
|
||||
return var
|
||||
|
||||
|
||||
class LightStateField(NamedTuple):
|
||||
"""One field of a light's state: the single source for the schemas and boot-time
|
||||
codegen that deal with it."""
|
||||
|
||||
conf_key: str
|
||||
# Member of LightStateRTCState holding this field.
|
||||
member: str
|
||||
validator: Callable[[Any], Any]
|
||||
# The color mode providing this field, if it implies one.
|
||||
color_mode: MockObj | None = None
|
||||
templatable: bool = True
|
||||
|
||||
|
||||
# In LightStateRTCState member order.
|
||||
LIGHT_STATE_FIELDS: tuple[LightStateField, ...] = (
|
||||
LightStateField(CONF_STATE, "state", validate_light_state),
|
||||
LightStateField(
|
||||
CONF_COLOR_MODE,
|
||||
"color_mode",
|
||||
cv.enum(COLOR_MODES, upper=True, space="_"),
|
||||
templatable=False,
|
||||
),
|
||||
LightStateField(CONF_BRIGHTNESS, "brightness", cv.percentage, ColorMode.BRIGHTNESS),
|
||||
LightStateField(
|
||||
CONF_COLOR_BRIGHTNESS, "color_brightness", cv.percentage, ColorMode.RGB
|
||||
),
|
||||
LightStateField(CONF_RED, "red", cv.percentage, ColorMode.RGB),
|
||||
LightStateField(CONF_GREEN, "green", cv.percentage, ColorMode.RGB),
|
||||
LightStateField(CONF_BLUE, "blue", cv.percentage, ColorMode.RGB),
|
||||
LightStateField(CONF_WHITE, "white", cv.percentage, ColorMode.WHITE),
|
||||
LightStateField(
|
||||
CONF_COLOR_TEMPERATURE,
|
||||
"color_temp",
|
||||
cv.color_temperature,
|
||||
ColorMode.COLOR_TEMPERATURE,
|
||||
),
|
||||
LightStateField(
|
||||
CONF_COLD_WHITE, "cold_white", cv.percentage, ColorMode.COLD_WARM_WHITE
|
||||
),
|
||||
LightStateField(
|
||||
CONF_WARM_WHITE, "warm_white", cv.percentage, ColorMode.COLD_WARM_WHITE
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@schema_extractor("one_of")
|
||||
def validate_color(value: Any) -> str | int:
|
||||
"""Validate a CSS color name or a 0xRRGGBB value."""
|
||||
if value == SCHEMA_EXTRACT:
|
||||
return ["CSS color name", "hex color value"]
|
||||
if isinstance(value, int) or (
|
||||
isinstance(value, str) and value.lower().startswith("0x")
|
||||
):
|
||||
return cv.hex_int_range(0, 0xFFFFFF)(value)
|
||||
return cv.one_of(*CSS_COLORS, lower=True)(value)
|
||||
|
||||
|
||||
COLOR_SCHEMA: dict[cv.Optional, Any] = {cv.Optional(CONF_COLOR): validate_color}
|
||||
|
||||
|
||||
def color_to_rgb(config: ConfigType) -> ConfigType:
|
||||
"""Replace a `color` CSS name or 0xRRGGBB value with red, green and blue values.
|
||||
|
||||
The light scales its color so the brightest channel is at full level, so a dark
|
||||
color is given as a full-level color plus a color brightness.
|
||||
"""
|
||||
if (color := config.pop(CONF_COLOR, None)) is None:
|
||||
return config
|
||||
if any(key in config for key in (CONF_RED, CONF_GREEN, CONF_BLUE)):
|
||||
raise cv.Invalid(
|
||||
f"'{CONF_COLOR}' cannot be used with '{CONF_RED}', '{CONF_GREEN}' or '{CONF_BLUE}'"
|
||||
)
|
||||
rgb = color if isinstance(color, int) else CSS_COLORS[color]
|
||||
channels = (rgb >> 16 & 0xFF, rgb >> 8 & 0xFF, rgb & 0xFF)
|
||||
peak = max(channels)
|
||||
if CONF_COLOR_BRIGHTNESS not in config:
|
||||
config[CONF_COLOR_BRIGHTNESS] = peak / 255
|
||||
elif peak < 0xFF:
|
||||
_LOGGER.warning(
|
||||
"'%s' overrides the brightness of color '%s'",
|
||||
CONF_COLOR_BRIGHTNESS,
|
||||
f"0x{color:06X}" if isinstance(color, int) else color,
|
||||
)
|
||||
for key, value in zip((CONF_RED, CONF_GREEN, CONF_BLUE), channels, strict=True):
|
||||
config[key] = value / peak if peak else 0.0
|
||||
return config
|
||||
|
||||
|
||||
LIGHT_STATE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_COLOR_MODE): cv.enum(COLOR_MODES, upper=True, space="_"),
|
||||
cv.Optional(CONF_STATE): cv.templatable(cv.boolean),
|
||||
cv.Optional(CONF_BRIGHTNESS): cv.templatable(cv.percentage),
|
||||
cv.Optional(CONF_COLOR_BRIGHTNESS): cv.templatable(cv.percentage),
|
||||
cv.Optional(CONF_RED): cv.templatable(cv.percentage),
|
||||
cv.Optional(CONF_GREEN): cv.templatable(cv.percentage),
|
||||
cv.Optional(CONF_BLUE): cv.templatable(cv.percentage),
|
||||
cv.Optional(CONF_WHITE): cv.templatable(cv.percentage),
|
||||
cv.Optional(CONF_COLOR_TEMPERATURE): cv.templatable(cv.color_temperature),
|
||||
cv.Optional(CONF_COLD_WHITE): cv.templatable(cv.percentage),
|
||||
cv.Optional(CONF_WARM_WHITE): cv.templatable(cv.percentage),
|
||||
cv.Optional(field.conf_key): (
|
||||
cv.templatable(field.validator) if field.templatable else field.validator
|
||||
)
|
||||
for field in LIGHT_STATE_FIELDS
|
||||
}
|
||||
)
|
||||
).extend(COLOR_SCHEMA)
|
||||
LIGHT_STATE_SCHEMA.add_extra(color_to_rgb)
|
||||
|
||||
LIGHT_CONTROL_ACTION_SCHEMA = LIGHT_STATE_SCHEMA.extend(
|
||||
{
|
||||
@@ -342,7 +455,8 @@ LIGHT_ADDRESSABLE_SET_ACTION_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_BLUE): cv.templatable(cv.percentage),
|
||||
cv.Optional(CONF_WHITE): cv.templatable(cv.percentage),
|
||||
}
|
||||
)
|
||||
).extend(COLOR_SCHEMA)
|
||||
LIGHT_ADDRESSABLE_SET_ACTION_SCHEMA.add_extra(color_to_rgb)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
|
||||
@@ -33,6 +33,7 @@ from esphome.cpp_generator import MockObjClass
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
from esphome.util import Registry
|
||||
|
||||
from .automation import validate_light_state
|
||||
from .types import (
|
||||
COLOR_MODES,
|
||||
AddressableColorWipeEffect,
|
||||
@@ -281,7 +282,7 @@ async def random_effect_to_code(config, effect_id):
|
||||
cv.ensure_list(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_STATE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_STATE, default=True): validate_light_state,
|
||||
cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage,
|
||||
cv.Optional(CONF_COLOR_MODE): cv.enum(
|
||||
COLOR_MODES, upper=True, space="_"
|
||||
|
||||
@@ -13,6 +13,20 @@ namespace esphome::light {
|
||||
|
||||
static const char *const TAG = "light";
|
||||
|
||||
// Colour modes are bitmasks of capabilities. A mode the light doesn't support may be a bare set of
|
||||
// required capabilities (see restore_state.py's colour mode inference): use the first supported
|
||||
// mode that provides all of them, or leave it unchanged if there is none.
|
||||
static ColorMode resolve_color_mode(const LightTraits &traits, ColorMode requested) {
|
||||
if (requested == ColorMode::UNKNOWN || traits.supports_color_mode(requested))
|
||||
return requested;
|
||||
auto wanted = static_cast<uint8_t>(requested);
|
||||
for (ColorMode mode : traits.get_supported_color_modes()) {
|
||||
if ((static_cast<uint8_t>(mode) & wanted) == wanted)
|
||||
return mode;
|
||||
}
|
||||
return requested;
|
||||
}
|
||||
|
||||
LightState::LightState(LightOutput *output) : output_(output) {}
|
||||
|
||||
LightTraits LightState::get_traits() { return this->output_->get_traits(); }
|
||||
@@ -40,38 +54,14 @@ void LightState::setup() {
|
||||
|
||||
auto call = this->make_call();
|
||||
LightStateRTCState recovered{};
|
||||
if (this->initial_state_callback_) {
|
||||
this->initial_state_callback_(recovered);
|
||||
this->initial_state_callback_ = nullptr; // One-shot — no longer needed
|
||||
bool restored = false;
|
||||
if (this->save_enabled_) {
|
||||
this->rtc_ = this->make_entity_preference<LightStateRTCState>();
|
||||
restored = this->rtc_.load(&recovered);
|
||||
}
|
||||
switch (this->restore_mode_) {
|
||||
case LIGHT_RESTORE_DEFAULT_OFF:
|
||||
case LIGHT_RESTORE_DEFAULT_ON:
|
||||
case LIGHT_RESTORE_INVERTED_DEFAULT_OFF:
|
||||
case LIGHT_RESTORE_INVERTED_DEFAULT_ON:
|
||||
this->rtc_ = this->make_entity_preference<LightStateRTCState>();
|
||||
// Attempt to load from preferences, else fall back to default values
|
||||
if (!this->rtc_.load(&recovered)) {
|
||||
recovered.state = (this->restore_mode_ == LIGHT_RESTORE_DEFAULT_ON ||
|
||||
this->restore_mode_ == LIGHT_RESTORE_INVERTED_DEFAULT_ON);
|
||||
} else if (this->restore_mode_ == LIGHT_RESTORE_INVERTED_DEFAULT_OFF ||
|
||||
this->restore_mode_ == LIGHT_RESTORE_INVERTED_DEFAULT_ON) {
|
||||
// Inverted restore state
|
||||
recovered.state = !recovered.state;
|
||||
}
|
||||
break;
|
||||
case LIGHT_RESTORE_AND_OFF:
|
||||
case LIGHT_RESTORE_AND_ON:
|
||||
this->rtc_ = this->make_entity_preference<LightStateRTCState>();
|
||||
this->rtc_.load(&recovered);
|
||||
recovered.state = (this->restore_mode_ == LIGHT_RESTORE_AND_ON);
|
||||
break;
|
||||
case LIGHT_ALWAYS_OFF:
|
||||
recovered.state = false;
|
||||
break;
|
||||
case LIGHT_ALWAYS_ON:
|
||||
recovered.state = true;
|
||||
break;
|
||||
if (this->state_callback_) {
|
||||
this->state_callback_(recovered, restored);
|
||||
this->state_callback_ = nullptr; // One-shot — no longer needed
|
||||
}
|
||||
|
||||
// A light coming up on boot must never end up on-but-invisible: if the resolved restore
|
||||
@@ -82,7 +72,7 @@ void LightState::setup() {
|
||||
recovered.brightness = 1.0f;
|
||||
}
|
||||
|
||||
call.set_color_mode_if_supported(recovered.color_mode);
|
||||
call.set_color_mode_if_supported(resolve_color_mode(traits, recovered.color_mode));
|
||||
call.set_state(recovered.state);
|
||||
call.set_brightness_if_supported(recovered.brightness);
|
||||
call.set_color_brightness_if_supported(recovered.color_brightness);
|
||||
@@ -428,17 +418,14 @@ void LightState::set_transformer_remote_values_(const LightColorValues &target,
|
||||
#endif
|
||||
|
||||
void LightState::save_remote_values_() {
|
||||
if (!this->save_enabled_)
|
||||
return;
|
||||
LightStateRTCState saved;
|
||||
saved.color_mode = this->remote_values.get_color_mode();
|
||||
switch (this->restore_mode_) {
|
||||
case LIGHT_RESTORE_AND_OFF:
|
||||
case LIGHT_RESTORE_AND_ON:
|
||||
saved.state = (this->restore_mode_ == LIGHT_RESTORE_AND_ON);
|
||||
break;
|
||||
default:
|
||||
saved.state = this->remote_values.is_on();
|
||||
break;
|
||||
}
|
||||
// Always the real on/off status (RESTORE_AND_ON/OFF used to persist a hardcoded
|
||||
// true/false here instead; harmless, since those modes force `state` again on
|
||||
// every load regardless of what was saved -- see _legacy_restore_statements).
|
||||
saved.state = this->remote_values.is_on();
|
||||
saved.brightness = this->remote_values.get_brightness();
|
||||
saved.color_brightness = this->remote_values.get_color_brightness();
|
||||
saved.red = this->remote_values.get_red();
|
||||
|
||||
@@ -44,33 +44,7 @@ class LightTargetStateReachedListener {
|
||||
virtual void on_light_target_state_reached() = 0;
|
||||
};
|
||||
|
||||
enum LightRestoreMode : uint8_t {
|
||||
LIGHT_RESTORE_DEFAULT_OFF,
|
||||
LIGHT_RESTORE_DEFAULT_ON,
|
||||
LIGHT_ALWAYS_OFF,
|
||||
LIGHT_ALWAYS_ON,
|
||||
LIGHT_RESTORE_INVERTED_DEFAULT_OFF,
|
||||
LIGHT_RESTORE_INVERTED_DEFAULT_ON,
|
||||
LIGHT_RESTORE_AND_OFF,
|
||||
LIGHT_RESTORE_AND_ON,
|
||||
};
|
||||
|
||||
struct LightStateRTCState {
|
||||
LightStateRTCState(ColorMode color_mode, bool state, float brightness, float color_brightness, float red, float green,
|
||||
float blue, float white, float color_temp, float cold_white, float warm_white)
|
||||
: brightness(brightness),
|
||||
color_brightness(color_brightness),
|
||||
red(red),
|
||||
green(green),
|
||||
blue(blue),
|
||||
white(white),
|
||||
color_temp(color_temp),
|
||||
cold_white(cold_white),
|
||||
warm_white(warm_white),
|
||||
effect(0),
|
||||
color_mode(color_mode),
|
||||
state(state) {}
|
||||
LightStateRTCState() = default;
|
||||
// Group 4-byte aligned members first
|
||||
float brightness{1.0f};
|
||||
float color_brightness{1.0f};
|
||||
@@ -211,12 +185,14 @@ class LightState : public EntityBase, public Component {
|
||||
float gamma_uncorrect_lut(float value) const { return value; }
|
||||
#endif // USE_LIGHT_GAMMA_LUT
|
||||
|
||||
/// Set the restore mode of this light
|
||||
void set_restore_mode(LightRestoreMode restore_mode) { this->restore_mode_ = restore_mode; }
|
||||
/// Set the callback that resolves the boot-time state, called once during setup then
|
||||
/// cleared. `restored` is true only when a persisted state actually loaded, in which
|
||||
/// case the state argument already holds the loaded values; otherwise it is freshly
|
||||
/// default-constructed. Values live in flash as code.
|
||||
void set_state_callback(void (*callback)(LightStateRTCState &, bool restored)) { this->state_callback_ = callback; }
|
||||
|
||||
/// Set a callback to populate the initial state defaults during setup.
|
||||
/// The callback is called once, then cleared. Values live in flash as code.
|
||||
void set_initial_state(void (*callback)(LightStateRTCState &)) { this->initial_state_callback_ = callback; }
|
||||
/// Set whether this light persists its state to preferences at all.
|
||||
void set_save_enabled(bool save_enabled) { this->save_enabled_ = save_enabled; }
|
||||
|
||||
/// Return whether the light has any effects that meet the trait requirements.
|
||||
bool supports_effects() const { return !this->effects_.empty(); }
|
||||
@@ -382,9 +358,9 @@ class LightState : public EntityBase, public Component {
|
||||
*/
|
||||
std::unique_ptr<std::vector<LightTargetStateReachedListener *>> target_state_reached_listeners_;
|
||||
|
||||
/// Callback to populate initial state defaults — called once during setup, then cleared.
|
||||
/// Callback that resolves the boot-time state — called once during setup, then cleared.
|
||||
/// Values live in flash as function body; no per-instance data storage beyond this pointer.
|
||||
void (*initial_state_callback_)(LightStateRTCState &){nullptr};
|
||||
void (*state_callback_)(LightStateRTCState &, bool restored){nullptr};
|
||||
|
||||
/// Value for storing the index of the currently active effect. 0 if no effect is active
|
||||
uint32_t active_effect_index_{};
|
||||
@@ -406,12 +382,12 @@ class LightState : public EntityBase, public Component {
|
||||
bool next_write_{true};
|
||||
// for effects, true if a transformer (transition) is active.
|
||||
bool is_transformer_active_{false};
|
||||
/// Whether this light persists its state to preferences at all.
|
||||
bool save_enabled_{false};
|
||||
#ifdef USE_LIGHT_TRANSITION_PUBLISH_INTERVAL
|
||||
/// True while the active transformer publishes current_values on an interval from loop().
|
||||
bool transition_publish_enabled_{false};
|
||||
#endif
|
||||
/// Restore mode of the light.
|
||||
LightRestoreMode restore_mode_;
|
||||
};
|
||||
|
||||
} // namespace esphome::light
|
||||
@@ -0,0 +1,345 @@
|
||||
"""
|
||||
Functions and classes to translate the `restore_mode:` and `restore_state:` config keys into
|
||||
the single runtime state callback that `LightState` actually understands.
|
||||
"""
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_COLOR_MODE, CONF_STATE
|
||||
from esphome.core import Lambda
|
||||
from esphome.cpp_generator import call_lambda
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .automation import LIGHT_STATE_FIELDS, validate_light_state
|
||||
from .types import ColorMode, LightStateRTCState
|
||||
|
||||
RESTORE_STATE_KEEP = "KEEP"
|
||||
RESTORE_STATE_INVERT = "INVERT"
|
||||
RESTORE_STATE_INITIAL = "INITIAL"
|
||||
RESTORE_STATE_ALL = "ALL"
|
||||
RESTORE_STATE_NONE = "NONE"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LegacyRestoreMode:
|
||||
cold_boot_state: bool
|
||||
restore_action: bool | str | None # None = no override, "INVERT", or force-to-bool
|
||||
save_enabled: bool
|
||||
|
||||
|
||||
LEGACY_RESTORE_MODES: dict[str, LegacyRestoreMode] = {
|
||||
"RESTORE_DEFAULT_OFF": LegacyRestoreMode(False, None, True),
|
||||
"RESTORE_DEFAULT_ON": LegacyRestoreMode(True, None, True),
|
||||
"ALWAYS_OFF": LegacyRestoreMode(False, None, False),
|
||||
"ALWAYS_ON": LegacyRestoreMode(True, None, False),
|
||||
"RESTORE_INVERTED_DEFAULT_OFF": LegacyRestoreMode(
|
||||
False, RESTORE_STATE_INVERT, True
|
||||
),
|
||||
"RESTORE_INVERTED_DEFAULT_ON": LegacyRestoreMode(True, RESTORE_STATE_INVERT, True),
|
||||
"RESTORE_AND_OFF": LegacyRestoreMode(False, False, True),
|
||||
"RESTORE_AND_ON": LegacyRestoreMode(True, True, True),
|
||||
}
|
||||
|
||||
# (config key, LightStateRTCState member) for every field, and for every field but `state`.
|
||||
_ALL_STATE_FIELDS: tuple[tuple[str, str], ...] = tuple(
|
||||
(field.conf_key, field.member) for field in LIGHT_STATE_FIELDS
|
||||
)
|
||||
_STATE_STRUCT_FIELDS: tuple[tuple[str, str], ...] = tuple(
|
||||
(conf_key, member)
|
||||
for conf_key, member in _ALL_STATE_FIELDS
|
||||
if conf_key != CONF_STATE
|
||||
)
|
||||
# Canonical struct-member order, used only to make generated code deterministic --
|
||||
# these are independent field assignments, so the actual order never affects behavior.
|
||||
_MEMBER_ORDER: tuple[str, ...] = tuple(member for _, member in _ALL_STATE_FIELDS)
|
||||
|
||||
# A pending `s.<member> = <value>;` statement, tagged with the member it writes.
|
||||
StateStatement = tuple[str, str]
|
||||
|
||||
|
||||
def _inferred_color_mode(initial_state_config: ConfigType | None) -> str | None:
|
||||
"""A C++ expression for the capabilities needed by the colour fields `initial_state:`
|
||||
sets, or None if `color_mode:` is given or no colour field is set.
|
||||
|
||||
Colour modes are bitmasks of capabilities, so the result is a bare capability set,
|
||||
not necessarily a real mode; LightState::setup() resolves it to a mode the light
|
||||
actually supports.
|
||||
"""
|
||||
if not initial_state_config or CONF_COLOR_MODE in initial_state_config:
|
||||
return None
|
||||
modes = sorted(
|
||||
{
|
||||
str(field.color_mode)
|
||||
for field in LIGHT_STATE_FIELDS
|
||||
if field.color_mode is not None
|
||||
and initial_state_config.get(field.conf_key) is not None
|
||||
}
|
||||
)
|
||||
if not modes:
|
||||
return None
|
||||
mask = " | ".join(f"static_cast<uint8_t>({mode})" for mode in modes)
|
||||
return f"static_cast<{ColorMode}>({mask})"
|
||||
|
||||
|
||||
def _partition_state_statements(
|
||||
initial_statements: list[StateStatement],
|
||||
restore_statements: list[StateStatement],
|
||||
save_enabled: bool,
|
||||
) -> list[str]:
|
||||
"""Split initial/restore statements into what must run unconditionally versus what
|
||||
depends on `restored`, and render the resulting lambda body lines.
|
||||
|
||||
Fields whose statement is identical in both branches (e.g. RESTORE_AND_ON's
|
||||
cold-boot and restore-time statements are both "s.state = true;") are hoisted out
|
||||
of the `restored` branch entirely, so only the fields that actually depend on
|
||||
`restored` end up inside it -- down to no branch at all when every field overlaps.
|
||||
A member appearing more than once in the same list keeps only its last statement
|
||||
(a plain, side-effect-free assignment): matches sequential-execution semantics,
|
||||
since an earlier write to the same member is always fully overwritten by a later
|
||||
one in the original code this replaces.
|
||||
|
||||
`save_enabled` is false exactly when `restore_statements` is empty and `restored`
|
||||
is unconditionally false at the call site (nothing is ever loaded), so the
|
||||
initial-only branch can skip its `if (!restored)` guard entirely.
|
||||
"""
|
||||
# dict() over (member, statement) pairs keeps the *last* entry per member.
|
||||
initial_map = dict(initial_statements)
|
||||
restore_map = dict(restore_statements)
|
||||
# A member outside _MEMBER_ORDER would be silently skipped below instead of
|
||||
# raising -- catch that here so a typo doesn't turn into wrong state on a device.
|
||||
assert set(initial_map) <= set(_MEMBER_ORDER)
|
||||
assert set(restore_map) <= set(_MEMBER_ORDER)
|
||||
|
||||
common: list[str] = []
|
||||
only_initial: list[str] = []
|
||||
only_restore: list[str] = []
|
||||
for member in _MEMBER_ORDER:
|
||||
initial_stmt = initial_map.get(member)
|
||||
restore_stmt = restore_map.get(member)
|
||||
if initial_stmt is not None and initial_stmt == restore_stmt:
|
||||
common.append(initial_stmt)
|
||||
continue
|
||||
if initial_stmt is not None:
|
||||
only_initial.append(initial_stmt)
|
||||
if restore_stmt is not None:
|
||||
only_restore.append(restore_stmt)
|
||||
|
||||
body = common
|
||||
if only_restore and only_initial:
|
||||
body += ["if (restored) {", *only_restore, "} else {", *only_initial, "}"]
|
||||
elif only_restore:
|
||||
body += ["if (restored) {", *only_restore, "}"]
|
||||
elif only_initial:
|
||||
if save_enabled:
|
||||
body += ["if (!restored) {", *only_initial, "}"]
|
||||
else:
|
||||
body += only_initial
|
||||
return body
|
||||
|
||||
|
||||
async def _build_state_lambda(
|
||||
initial_statements: list[StateStatement],
|
||||
restore_statements: list[StateStatement],
|
||||
save_enabled: bool,
|
||||
) -> Lambda | None:
|
||||
"""
|
||||
Combine the initial and restore statements into a single lambda that applies the
|
||||
correct values to a `LightStateRTCState &s` depending on whether a persisted state
|
||||
actually loaded.
|
||||
"""
|
||||
if not initial_statements and not restore_statements:
|
||||
return None
|
||||
body = _partition_state_statements(
|
||||
initial_statements, restore_statements, save_enabled
|
||||
)
|
||||
args = [(LightStateRTCState.operator("ref"), "s"), (cg.bool_, "restored")]
|
||||
return await cg.process_lambda(Lambda("\n".join(body)), args, return_type=cg.void)
|
||||
|
||||
|
||||
async def _process_value(value: Any, member: str) -> str:
|
||||
if isinstance(value, Lambda):
|
||||
return_type = cg.bool_ if member == CONF_STATE else cg.float_
|
||||
lamb = await cg.process_lambda(value, [], return_type=return_type)
|
||||
return call_lambda(lamb)
|
||||
return cg.safe_exp(value)
|
||||
|
||||
|
||||
async def _initial_state_statements(
|
||||
initial_state_config: ConfigType | None,
|
||||
) -> list[StateStatement]:
|
||||
"""
|
||||
Create assignments for every field the user set in `initial_state:`, in canonical
|
||||
struct-member order. A field given as `!lambda` is resolved and called immediately,
|
||||
the same way `light.control`'s own field lambdas are.
|
||||
"""
|
||||
if not initial_state_config:
|
||||
return []
|
||||
statements: list[StateStatement] = []
|
||||
for conf_key, member in _ALL_STATE_FIELDS:
|
||||
if (value := initial_state_config.get(conf_key)) is None:
|
||||
continue
|
||||
statements.append(
|
||||
(member, f"s.{member} = {await _process_value(value, member)};")
|
||||
)
|
||||
if (inferred := _inferred_color_mode(initial_state_config)) is not None:
|
||||
statements.append(("color_mode", f"s.color_mode = {inferred};"))
|
||||
return statements
|
||||
|
||||
|
||||
async def _resolve_initial_value(
|
||||
conf_key: str, member: str, initial_state_config: ConfigType | None
|
||||
) -> str:
|
||||
"""
|
||||
Return the C++ expression to use for a `restore_state:` field whose value is INITIAL
|
||||
"""
|
||||
if (
|
||||
initial_state_config is not None
|
||||
and (value := initial_state_config.get(conf_key)) is not None
|
||||
):
|
||||
return await _process_value(value, member)
|
||||
if conf_key == CONF_COLOR_MODE and (
|
||||
inferred := _inferred_color_mode(initial_state_config)
|
||||
):
|
||||
return inferred
|
||||
return f"LightStateRTCState{{}}.{member}"
|
||||
|
||||
|
||||
async def _restore_state_statements(
|
||||
restore_state_config: ConfigType, initial_state_config: ConfigType | None
|
||||
) -> list[StateStatement]:
|
||||
"""
|
||||
Create a list of statements to apply the user's `restore_state:` config
|
||||
"""
|
||||
statements: list[StateStatement] = []
|
||||
state = restore_state_config[CONF_STATE]
|
||||
if state == RESTORE_STATE_INVERT:
|
||||
statements.append(("state", "s.state = !s.state;"))
|
||||
elif state == RESTORE_STATE_INITIAL:
|
||||
expr = await _resolve_initial_value(CONF_STATE, "state", initial_state_config)
|
||||
statements.append(("state", f"s.state = {expr};"))
|
||||
elif state != RESTORE_STATE_KEEP:
|
||||
statements.append(("state", f"s.state = {cg.safe_exp(state)};"))
|
||||
for conf_key, member in _STATE_STRUCT_FIELDS:
|
||||
value = restore_state_config[conf_key]
|
||||
if value == RESTORE_STATE_INITIAL:
|
||||
expr = await _resolve_initial_value(conf_key, member, initial_state_config)
|
||||
elif value == RESTORE_STATE_KEEP:
|
||||
continue
|
||||
else:
|
||||
expr = cg.safe_exp(value)
|
||||
statements.append((member, f"s.{member} = {expr};"))
|
||||
return statements
|
||||
|
||||
|
||||
def _legacy_restore_statements(mode: LegacyRestoreMode) -> list[StateStatement]:
|
||||
"""
|
||||
Create a list of statements to apply the legacy restore_mode: behavior.
|
||||
"""
|
||||
if mode.restore_action is None:
|
||||
return []
|
||||
if mode.restore_action == RESTORE_STATE_INVERT:
|
||||
return [("state", "s.state = !s.state;")]
|
||||
return [("state", f"s.state = {str(mode.restore_action).lower()};")]
|
||||
|
||||
|
||||
def _legacy_cold_boot_statements(
|
||||
mode: LegacyRestoreMode, initial_state_config: ConfigType | None
|
||||
) -> list[StateStatement]:
|
||||
"""
|
||||
Create a list of statements to apply the legacy restore_mode: cold-boot behavior.
|
||||
"""
|
||||
existing_state = (
|
||||
initial_state_config.get(CONF_STATE) if initial_state_config else None
|
||||
)
|
||||
if existing_state is None:
|
||||
if not mode.cold_boot_state:
|
||||
return [] # already matches LightStateRTCState's own default
|
||||
elif existing_state == mode.cold_boot_state:
|
||||
return [] # initial_state: already set exactly this value
|
||||
return [("state", f"s.state = {str(mode.cold_boot_state).lower()};")]
|
||||
|
||||
|
||||
def _initial_state_overridden_by_legacy_mode(
|
||||
mode: LegacyRestoreMode, initial_state_config: ConfigType | None
|
||||
) -> bool:
|
||||
"""
|
||||
Is the user-config `initial_state:` value for `state` overridden by the legacy mode?
|
||||
"""
|
||||
if initial_state_config is None or CONF_STATE not in initial_state_config:
|
||||
return False
|
||||
return initial_state_config[CONF_STATE] != mode.cold_boot_state
|
||||
|
||||
|
||||
def _keep_or(validator: Callable[[Any], Any]) -> Callable[[Any], Any]:
|
||||
"""
|
||||
Extend a validator to also accept the `KEEP`/`INITIAL` options
|
||||
"""
|
||||
|
||||
@schema_extractor("one_of")
|
||||
def validate(value: Any) -> Any:
|
||||
if value == SCHEMA_EXTRACT:
|
||||
# Editor completion: the sentinels plus the wrapped validator's own values,
|
||||
# if it is an enum. Numeric validators have none to offer.
|
||||
try:
|
||||
inner = tuple(validator(SCHEMA_EXTRACT))
|
||||
except cv.Invalid:
|
||||
inner = ()
|
||||
return (RESTORE_STATE_KEEP, RESTORE_STATE_INITIAL, *inner)
|
||||
if isinstance(value, str):
|
||||
upper = value.strip().upper()
|
||||
if upper in (RESTORE_STATE_KEEP, RESTORE_STATE_INITIAL):
|
||||
return upper
|
||||
return validator(value)
|
||||
|
||||
return validate
|
||||
|
||||
|
||||
@schema_extractor("one_of")
|
||||
def _validate_restore_state_state(value: Any) -> str | bool:
|
||||
if value == SCHEMA_EXTRACT:
|
||||
return (
|
||||
RESTORE_STATE_KEEP,
|
||||
RESTORE_STATE_INVERT,
|
||||
RESTORE_STATE_INITIAL,
|
||||
*validate_light_state(SCHEMA_EXTRACT),
|
||||
)
|
||||
if isinstance(value, str):
|
||||
upper = value.strip().upper()
|
||||
if upper in (RESTORE_STATE_KEEP, RESTORE_STATE_INVERT, RESTORE_STATE_INITIAL):
|
||||
return upper
|
||||
return validate_light_state(value)
|
||||
|
||||
|
||||
_RESTORE_STATE_FIELDS_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(field.conf_key, default=RESTORE_STATE_KEEP): (
|
||||
_validate_restore_state_state
|
||||
if field.conf_key == CONF_STATE
|
||||
else _keep_or(field.validator)
|
||||
)
|
||||
for field in LIGHT_STATE_FIELDS
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@schema_extractor("schema")
|
||||
def RESTORE_STATE_SCHEMA(value: Any) -> ConfigType | str:
|
||||
"""
|
||||
The restore_state: config key can be a mapping of per-field overrides, `all`, or `none`.
|
||||
"""
|
||||
if value == SCHEMA_EXTRACT:
|
||||
# The `all`/`none` string shorthands have no representation in the extracted
|
||||
# docs schema; only the per-field mapping form is walked here.
|
||||
return _RESTORE_STATE_FIELDS_SCHEMA
|
||||
if isinstance(value, str):
|
||||
upper = value.strip().upper()
|
||||
if upper == RESTORE_STATE_ALL:
|
||||
value = {}
|
||||
elif upper == RESTORE_STATE_NONE:
|
||||
return RESTORE_STATE_NONE
|
||||
return _RESTORE_STATE_FIELDS_SCHEMA(value)
|
||||
@@ -11,8 +11,6 @@ from esphome.const import (
|
||||
CONF_REPEAT,
|
||||
CONF_WRITE_PIN,
|
||||
)
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
from esphome.cpp_helpers import gpio_pin_expression
|
||||
from esphome.types import ConfigType
|
||||
|
||||
@@ -24,7 +22,6 @@ lightwaverf_ns = cg.esphome_ns.namespace("lightwaverf")
|
||||
LIGHTWAVERFComponent = lightwaverf_ns.class_(
|
||||
"LightWaveRF", cg.Component, cg.PollingComponent
|
||||
)
|
||||
LightwaveRawAction = lightwaverf_ns.class_("SendRawAction", automation.Action)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
@@ -36,49 +33,36 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
).extend(cv.polling_component_schema("1s"))
|
||||
|
||||
|
||||
LIGHTWAVE_SEND_SCHEMA = cv.Any(
|
||||
cv.int_range(min=1),
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(LIGHTWAVERFComponent),
|
||||
cv.Required(CONF_NAME): cv.string,
|
||||
cv.Required(CONF_CODE): cv.All(
|
||||
[cv.Any(cv.hex_uint8_t)],
|
||||
cv.Length(min=10),
|
||||
),
|
||||
cv.Optional(CONF_REPEAT, default=10): cv.int_,
|
||||
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PULSE_LENGTH, default=330): cv.int_,
|
||||
}
|
||||
LIGHTWAVE_SEND_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(LIGHTWAVERFComponent),
|
||||
cv.Required(CONF_NAME): cv.string,
|
||||
cv.Required(CONF_CODE): cv.All(
|
||||
[cv.Any(cv.hex_uint8_t)],
|
||||
cv.Length(min=10),
|
||||
),
|
||||
cv.Optional(CONF_REPEAT, default=10): cv.int_,
|
||||
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PULSE_LENGTH, default=330): cv.int_,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
automation.register_apply_action(
|
||||
"lightwaverf.send_raw",
|
||||
LIGHTWAVE_SEND_SCHEMA,
|
||||
automation.ApplyCall(
|
||||
"send_rx({}, {}, {}, {})",
|
||||
(
|
||||
(CONF_CODE, cg.std_vector.template(cg.uint8)),
|
||||
(CONF_REPEAT, cg.uint8),
|
||||
(CONF_INVERTED, cg.bool_),
|
||||
(CONF_PULSE_LENGTH, cg.int_),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"lightwaverf.send_raw",
|
||||
LightwaveRawAction,
|
||||
LIGHTWAVE_SEND_SCHEMA,
|
||||
synchronous=True,
|
||||
)
|
||||
async def send_raw_to_code(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
template_ = await cg.templatable(config[CONF_REPEAT], args, cg.int_)
|
||||
cg.add(var.set_repeat(template_))
|
||||
template_ = await cg.templatable(config[CONF_INVERTED], args, cg.int_)
|
||||
cg.add(var.set_inverted(template_))
|
||||
template_ = await cg.templatable(config[CONF_PULSE_LENGTH], args, cg.int_)
|
||||
cg.add(var.set_pulse_length(template_))
|
||||
cg.add(var.set_code(config[CONF_CODE]))
|
||||
return var
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
Loaded 100 of 445 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user