Compare commits

..
Author SHA1 Message Date
J. Nick Koston 74847eeccc Pick the nRF52 ELF layout by SDK version 2026-09-26 20:52:42 +01:00
J. Nick Koston d9ae0e2cd4 Give the nRF52 binutils their Windows suffix 2026-09-26 20:46:43 +01:00
J. Nick Koston 857564a23d [nrf52] Run analyze-memory on the sdk-nrf toolchain 2026-09-26 20:46:43 +01:00
Keith BurzinskiandClaude Opus 5.5 6c5010032b [pn532] Check InDataExchange status and fix tag handling (#19591)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 14:44:48 -05:00
J. Nick Koston 746691d5c2 [esp8266] Add native toolchain that builds the Arduino core without PlatformIO (#18539) 2026-09-26 20:44:09 +01:00
Keith BurzinskiandClaude Opus 5.5 93ec8d9d95 [pn71xx] Fix NCI transport and card emulation bugs; share code between PN7150 and PN7160 (#19589)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 14:24:45 -05:00
J. Nick Koston dea1200c35 [nrf52] Reboot straight into the app on a 2001 baud touch (#19727) 2026-09-26 20:08:45 +01:00
Paul Traina abd95c5154 [ld2410][mqtt][packages][security] Mark string-valued secrets cv.sensitive (#19387) 2026-09-26 18:43:06 +01:00
cdc77e2126 [xiaomi_body_scale] Add Xiaomi body scale S200 and S400 (#16476)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-09-26 16:11:50 +01:00
Frédéric MetrichandClaude Opus 5.5 04a53264b4 [mk2pvrouter] Add text_sensor platform (#19720)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 15:37:31 +01:00
Frédéric MetrichandClaude Opus 5.5 499ce56c2b [mk2pvrouter] Add binary_sensor platform (#19719)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 14:49:47 +01:00
Jonathan Swoboda 72ac32a2c4 [gree] Encode temperature as offset from 16°C (#19718) 2026-09-26 08:41:52 -04:00
Clyde Stubbs a7e0c0f8a2 [light][lvgl] Add color names for light states (#19712) 2026-09-26 21:19:38 +10:00
J. Nick Koston 1ea45be19e [const] Share CONF_HOST (#19716) 2026-09-26 07:12:05 -04:00
Clyde StubbsandClaude Sonnet 5 7d5117e570 [snapshot] Add feature to record animated gifs from screen (#19421)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-26 20:18:15 +10:00
J. Nick Koston 8132fbef52 [socket] Add outgoing connections to the raw lwIP TCP implementation (#18982) 2026-09-26 11:01:53 +01:00
esphome[bot] 9744058201 Bump bundled esphome-device-builder to 1.16.0 (#19710) 2026-09-26 02:14:12 +00:00
J. Nick Koston 1de5a58dbe [display] Use register_apply_condition for display.is_displaying_page (#19705) 2026-09-26 01:36:31 +00:00
d2c33b0f6f [systa_bus] Add systa_bus component (#15473)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-09-26 02:24:51 +01:00
J. Nick Koston 0d445fb3bc [router] Use register_apply_action for router.speaker.switch_output (#19695) 2026-09-26 02:16:32 +01:00
J. Nick Koston 76de6a672c [core] Render an id constant in an apply field as the object it names (#19694) 2026-09-26 02:16:01 +01:00
J. Nick Koston 2dc7a64e52 [it8951] Use register_apply_action for it8951.update (#19691) 2026-09-25 21:10:10 -04:00
J. Nick Koston a61746045b [core] Pass the copy flag to JsonString explicitly in the StringRef conversion (#19709) 2026-09-26 00:51:49 +00:00
J. Nick Koston a35299af0b [emontx] Use register_apply_action for emontx.send_command (#19687) 2026-09-26 01:34:52 +01:00
J. Nick Koston 3f2de92e5d [openthread] Use register_apply_action for openthread.set_poll_period (#19697) 2026-09-26 01:34:38 +01:00
J. Nick Koston b8c32854ed [bl0906] Use register_apply_action for bl0906.reset_energy (#19688) 2026-09-26 01:34:22 +01:00
J. Nick Koston 0b2e5bfbbe [pid] Use register_apply_action for climate.pid.autotune (#19708) 2026-09-25 20:34:16 -04:00
J. Nick Koston a1d8ea6072 [aic3204] Use register_apply_action for aic3204.set_auto_mute_mode (#19684) 2026-09-26 01:34:06 +01:00
J. Nick Koston 93fabe8b84 [alarm_control_panel] Use register_apply_condition for is_armed and ready (#19707) 2026-09-25 20:33:57 -04:00
J. Nick Koston 2fc48a3d9e [sun] Use register_apply_condition for is_above_horizon and is_below_horizon (#19704) 2026-09-25 20:33:50 -04:00
J. Nick Koston 1a158e5ba0 [mitsubishi_cn105] Use register_apply_action for set_remote_temperature and clear_remote_temperature (#19706) 2026-09-25 20:33:47 -04:00
J. Nick Koston 0569a1262f [voice_assistant] Use register_apply_condition for is_running and connected (#19703) 2026-09-25 20:33:36 -04:00
J. Nick Koston 0b5a1748f7 [tinyusb] Use register_apply_condition for tinyusb.is_mounted (#19702) 2026-09-25 20:33:25 -04:00
J. Nick Koston d71a6e662b [snapshot] Use register_apply_action for snapshot.take (#19696) 2026-09-25 20:33:21 -04:00
J. Nick Koston c8d3f43712 [dfplayer] Use register_apply_condition for dfplayer.is_playing (#19701) 2026-09-25 20:33:17 -04:00
J. Nick Koston 01a2aaef08 [motion] Use register_apply_action for motion.clear_calibration (#19700) 2026-09-25 20:32:57 -04:00
J. Nick Koston 447348d617 [lightwaverf] Use register_apply_action for lightwaverf.send_raw (#19698) 2026-09-25 20:32:53 -04:00
J. Nick Koston c902083ef3 [dfrobot_sen0395] Use register_apply_action for dfrobot_sen0395.reset (#19693) 2026-09-25 20:31:49 -04:00
J. Nick Koston d1dd4e53ce [zephyr_ble_server] Use register_apply_action for ble_server.numeric_comparison_reply (#19692) 2026-09-25 20:31:41 -04:00
J. Nick Koston c0c1643efe [zigbee] Use register_apply_action for zigbee.factory_reset (#19690) 2026-09-25 20:31:05 -04:00
J. Nick Koston 532a6ed239 [mixer] Use register_apply_action for mixer_speaker.apply_ducking (#19689) 2026-09-25 20:30:56 -04:00
J. Nick Koston 3a9048cc62 [speaker_source] Use register_apply_action for speaker_source.set_playlist_delay (#19686) 2026-09-25 20:30:16 -04:00
J. Nick Koston d34b21cb7e [pipsolar] Use register_apply_action for output.pipsolar.set_level (#19685) 2026-09-25 20:30:12 -04:00
J. Nick Koston 5043e12690 [cs5460a] Use register_apply_action for cs5460a.restart (#19683) 2026-09-25 20:29:14 -04:00
J. Nick Koston 3e85af29c6 [hc8] Use register_apply_action for hc8.calibrate (#19681) 2026-09-25 20:29:04 -04:00
J. Nick Koston 1889f6c9b5 [max17043] Use register_apply_action for max17043.sleep_mode (#19682) 2026-09-25 20:28:58 -04:00
J. Nick Koston 2f4eb3e1ee [audio_adc] Use register_apply_action for audio_adc.set_mic_gain (#19679) 2026-09-25 20:28:40 -04:00
Boris Krivonog bcae1f28ef [web_server] Expose temperature units for climate and water heater entities (#19572) 2026-09-26 01:11:25 +01:00
87b10d70a5 [mk2pvrouter] Add tag-based sensor defaults (#18870)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-09-26 01:05:06 +01:00
cfb7d8089e [remote_base] Add AEG Hob2Hood Infrared Protocol (#4687)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-09-26 00:42:21 +01:00
bfe1f7bd17 [pzem6l24] Add PZEM-6L24 three-phase energy monitor sensor (#17849)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-09-25 23:03:14 +00:00
d5d0901e2c [tfluna] New component for the TF-Luna Low Cost LiDAR Distance Sensor (#9199)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-09-25 23:55:14 +01:00
64388318e8 [light] Replace restore_mode with restore_state (#18997)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-09-26 08:47:25 +10:00
J. Nick Koston e83dc25301 [esp_ldo] Use register_apply_action for esp_ldo.voltage.adjust (#19680) 2026-09-26 06:28:18 +10:00
J. Nick Koston a81cf8f5cc [ufire_ec] Use register_apply_action for calibrate_probe and reset (#19675) 2026-09-25 09:58:52 -10:00
J. Nick Koston 6925c5127e [ags10] Use register_apply_action for new_i2c_address and set_zero_point (#19676) 2026-09-25 19:58:34 +00:00
J. Nick Koston d96d0b80f7 [cm1106] Use register_apply_action for cm1106.calibrate_zero (#19678) 2026-09-25 19:56:49 +00:00
J. Nick Koston 27ea1a90e3 [hub75] Use register_apply_action for hub75.set_brightness (#19677) 2026-09-25 20:56:31 +01:00
J. Nick Koston 4f47913745 [rx8130] Use register_apply_action for read_time and write_time (#19674) 2026-09-25 20:55:21 +01:00
J. Nick Koston 447a30da0f [mcp4461] Use register_apply_action for the four wiper actions (#19669) 2026-09-25 15:41:54 -04:00
J. Nick Koston d8696c4f28 [ufire_ise] Use register_apply_action for calibrate_probe_low, calibrate_probe_high and reset (#19670) 2026-09-25 15:41:23 -04:00
J. Nick Koston 0ef68c17fd [uart_mux] Use the apply helpers for select_local, select_bridge and is_local (#19671) 2026-09-25 15:41:18 -04:00
J. Nick Koston aef3a586de [max6956] Use register_apply_action for set_brightness_global and set_brightness_mode (#19672) 2026-09-25 19:40:48 +00:00
J. Nick Koston 5aeab5f361 [hdc302x] Use register_apply_action for heater_on and heater_off (#19673) 2026-09-25 15:40:42 -04:00
dependabot[bot] 9654dd3912 Bump platformdirs from 4.11.11 to 4.11.12 (#19668)
Signed-off-by: dependabot[bot] <support@github.com>
2026-09-25 19:34:38 +00:00
J. Nick Koston 6622e1fe64 [grove_tb6612fng] Use register_apply_action for the six motor actions (#19664) 2026-09-25 19:28:12 +00:00
J. Nick Koston 0c548f77fa [bm8563] Use register_apply_action for read_time, write_time and start_timer (#19665) 2026-09-25 20:24:57 +01:00
J. Nick Koston dfd9d2639f [ezo_pmp] Use register_apply_action for the twelve ezo_pmp actions (#19663) 2026-09-25 15:21:23 -04:00
J. Nick Koston cf9d807bf0 [core] Make Action::stop_complex and Action::is_running non-virtual (#19662) 2026-09-25 19:20:57 +00:00
J. Nick Koston 29512cf323 [pmwcs3] Use register_apply_action for the calibration and address actions (#19666) 2026-09-25 15:20:46 -04:00
J. Nick Koston 456e13adee [ld2410] Use register_apply_action for bluetooth_password.set (#19667) 2026-09-25 19:20:44 +00:00
tronikosandJ. Nick Koston a1b0e85a9e [core] Report unknown for switch, climate and water heater with no state (#19458)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-09-25 19:18:32 +00:00
J. Nick Koston 14bd50d469 [sound_level] Use register_apply_action for sound_level.start and sound_level.stop (#19656) 2026-09-25 18:56:32 +00:00
J. Nick Koston 1edc327835 [cc1101] Use register_apply_action for begin_tx, begin_rx, reset and set_idle (#19661) 2026-09-25 14:56:06 -04:00
J. Nick Koston 288a7b376a [htu21d] Use register_apply_action for htu21d.set_heater and htu21d.set_heater_level (#19658) 2026-09-25 14:52:16 -04:00
J. Nick Koston c16f75f0e2 [pcf85063] Use register_apply_action for pcf85063.write_time and pcf85063.read_time (#19659) 2026-09-25 18:51:41 +00:00
J. Nick Koston 432cae58ae [nau7802] Use register_apply_action for the three calibration actions (#19657) 2026-09-25 18:51:14 +00:00
J. Nick Koston 11c8e8c0c0 [scd30] Use register_apply_action for scd30.force_recalibration_with_reference (#19652) 2026-09-25 14:32:05 -04:00
J. Nick Koston 6a115e6b25 [sen5x] Use register_apply_action for sen5x.start_fan_autoclean (#19653) 2026-09-25 14:31:39 -04:00
J. Nick Koston 9c2616ef7e [pzemdc] Use register_apply_action for pzemdc.reset_energy (#19651) 2026-09-25 14:31:11 -04:00
J. Nick Koston 9ae83f2830 [hbridge] Use register_apply_action for fan.hbridge.brake (#19649) 2026-09-25 14:30:30 -04:00
J. Nick Koston 340e105a76 [pzemac] Use register_apply_action for pzemac.reset_energy (#19650) 2026-09-25 14:28:01 -04:00
J. Nick Koston 857a519946 [sim800l] Use register_apply_action for the five sim800l actions (#19645) 2026-09-25 19:12:51 +01:00
J. Nick Koston 15d756d559 [sps30] Use register_apply_action for the three sps30 actions (#19644) 2026-09-25 18:12:35 +00:00
J. Nick Koston 22035cb9f5 [tm1651] Use register_apply_action for the five tm1651 actions (#19647) 2026-09-25 19:12:27 +01:00
J. Nick Koston 32624e248c [fingerprint_grow] Use register_apply_action for the six fingerprint_grow actions (#19640) 2026-09-25 19:11:51 +01:00
J. Nick Koston ad6996a63c [key_collector] Use register_apply_action for key_collector.enable and key_collector.disable (#19646) 2026-09-25 19:11:05 +01:00
J. Nick Koston 262ade0598 [senseair] Use register_apply_action for the five senseair actions (#19643) 2026-09-25 14:09:37 -04:00
J. Nick Koston 2157ff5b4b [ds1307] Use register_apply_action for ds1307.write_time and ds1307.read_time (#19638) 2026-09-25 14:09:26 -04:00
esphome[bot] 63796e74bb Bump aioesphomeapi from 46.5.0 to 46.6.0 (#19648) 2026-09-25 19:05:26 +01:00
J. Nick Koston a088eff428 [binary_sensor] Use register_apply_action for binary_sensor.invalidate_state (#19637) 2026-09-25 17:53:02 +00:00
J. Nick Koston 0cb421f793 [max7219digit] Use register_apply_action for the seven max7219digit actions (#19641) 2026-09-25 13:45:38 -04:00
J. Nick Koston 23c317f163 [pcf8563] Use register_apply_action for pcf8563.write_time and pcf8563.read_time (#19639) 2026-09-25 18:43:10 +01:00
J. Nick Koston 11e016be59 [sx126x] Use register_apply_action for the five mode and calibration actions (#19635) 2026-09-25 13:16:06 -04:00
J. Nick Koston bdb492c196 [sx127x] Use register_apply_action for the five mode and calibration actions (#19636) 2026-09-25 13:15:13 -04:00
J. Nick Koston d6831c8c75 [duty_time] Use the apply helpers for the start, stop, reset and is_running automations (#19634) 2026-09-25 18:13:46 +01:00
J. Nick Koston c73a71e037 [animation] Use register_apply_action for next_frame, prev_frame and set_frame (#19633) 2026-09-25 13:08:18 -04:00
J. Nick Koston 4bb6c0f877 [update] Use the apply helpers for update.check and update.is_available (#19632) 2026-09-25 13:07:48 -04:00
J. Nick Koston eb85778a60 [mhz19] Use register_apply_action for the four mhz19 actions (#19631) 2026-09-25 13:07:15 -04:00
J. Nick Koston c561c6bbe8 [scd4x] Use register_apply_action for perform_forced_calibration and factory_reset (#19630) 2026-09-25 13:02:58 -04:00
445 changed files with 12218 additions and 8899 deletions

No files matched your search

+5
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1 -2
View File
@@ -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
+4
View File
@@ -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",
+266
View File
@@ -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,
)
+9 -5
View File
@@ -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.
"""
+2 -1
View File
@@ -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",
+18 -1
View File
@@ -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")
+52
View File
@@ -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
-1
View File
@@ -10,7 +10,6 @@
# pylint: disable=unused-import
from esphome.cpp_generator import ( # noqa: F401
ArrayInitializer,
ComponentMarker,
Expression,
FlashStringLiteral,
LineComment,
+14
View File
@@ -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);
+14 -36
View File
@@ -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
+7 -41
View File
@@ -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
+2 -19
View File
@@ -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:
-21
View File
@@ -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
-30
View File
@@ -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
+8 -32
View File
@@ -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:
+9
View File
@@ -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();
+6
View File
@@ -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,
+6 -3
View File
@@ -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);
+3
View File
@@ -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
+4 -21
View File
@@ -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)
-21
View File
@@ -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
+2 -6
View File
@@ -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
+3 -10
View File
@@ -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
+2 -4
View File
@@ -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()"),
)
-20
View File
@@ -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
+13 -40
View File
@@ -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),
)
+9 -34
View File
@@ -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):
-20
View File
@@ -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; }
+1
View File
@@ -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();
-11
View File
@@ -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
+2 -7
View File
@@ -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)"),
)
+1
View File
@@ -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"
+152
View File
@@ -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,
}
-11
View File
@@ -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
+2 -4
View File
@@ -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()"),
)
+2 -5
View File
@@ -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()",
)
-5
View File
@@ -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
+2 -11
View File
@@ -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)
-12
View File
@@ -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); }
-9
View File
@@ -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
+4 -9
View File
@@ -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 -56
View File
@@ -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 -19
View File
@@ -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
+3 -3
View File
@@ -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 -10
View File
@@ -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
+139 -45
View File
@@ -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
+2
View File
@@ -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")
+3 -17
View File
@@ -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
-13
View File
@@ -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);
+29 -164
View File
@@ -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
-134
View File
@@ -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
+1 -1
View File
@@ -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"
+3 -3
View File
@@ -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
+62 -109
View File
@@ -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
+1 -1
View File
@@ -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_() {
+31 -98
View File
@@ -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
+3 -7
View File
@@ -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()"),
)
+2 -12
View File
@@ -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
-8
View File
@@ -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
+2 -17
View File
@@ -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
-18
View File
@@ -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
+6 -28
View File
@@ -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()")
)
+1 -1
View File
@@ -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;
-23
View File
@@ -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
+4 -33
View File
@@ -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
+3 -17
View File
@@ -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
+1 -1
View File
@@ -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);
}
}
}
+5 -19
View File
@@ -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
+3 -3
View File
@@ -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);
}
-21
View File
@@ -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
+12 -20
View File
@@ -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
+3 -23
View File
@@ -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
-20
View File
@@ -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
-1
View File
@@ -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>
-3
View File
@@ -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++) {
+98 -68
View File
@@ -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)
+127 -13
View File
@@ -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(
+2 -1
View File
@@ -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="_"
+28 -41
View File
@@ -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();
+11 -35
View File
@@ -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
+345
View File
@@ -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)
+26 -42
View File
@@ -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