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
J. Nick Koston 7294da9f49 [integration] Use register_apply_action for sensor.integration.reset and set_value (#19628) 2026-09-25 12:34:39 -04:00
J. Nick Koston 96d0d677b6 [alarm_control_panel] Use register_apply_action for pending and triggered, drop the chime and ready actions that had no class (#19627) 2026-09-25 12:33:35 -04:00
J. Nick Koston c598d2dee5 [display_menu_base] Use the apply helpers for the eight display_menu actions and is_active (#19626) 2026-09-25 12:32:46 -04:00
J. Nick Koston 73548b0f15 [midea] Use register_apply_action for the eight midea_ac actions (#19625) 2026-09-25 12:32:16 -04:00
J. Nick Koston 9a9169305e [haier] Use register_apply_action for the thirteen climate.haier actions (#19624) 2026-09-25 12:31:34 -04:00
J. Nick Koston 54d05180b7 [duty_time] Use one import style for the automation module (#19622) 2026-09-25 12:30:10 -04:00
J. Nick Koston 1dcf0a0f9d [valve] Use register_apply_action for valve.open, valve.close, valve.stop and valve.toggle (#19618) 2026-09-25 17:00:33 +01:00
J. Nick Koston bb84409261 [sprinkler] Use register_apply_action for the seven keyless controller actions (#19617) 2026-09-25 11:58:56 -04:00
J. Nick Koston 044c4cb9bb [core] Migrate trivial action and condition builders to the shared helpers (#19322) 2026-09-25 16:46:12 +01:00
J. Nick Koston d346835e79 [wireguard] Use the apply helpers for the enable, disable, enabled and peer_online automations (#19616) 2026-09-25 11:04:51 -04:00
J. Nick Koston db566b25d0 [microphone] Use the apply helpers for the capture, mute and condition automations (#19619) 2026-09-25 11:04:14 -04:00
J. Nick Koston 45bcff2c2c [micro_wake_word] Use the apply helpers for the start, stop and model automations (#19620) 2026-09-25 11:03:32 -04:00
J. Nick Koston af4a8dac8e [esp32] Keep the NVS cache in PSRAM when PSRAM is guaranteed (#19378) 2026-09-25 15:56:01 +01:00
J. Nick Koston d3fd124c59 [core] Lint against std::nothrow, use RAMAllocator instead (#19244) 2026-09-25 15:50:32 +01:00
J. Nick Koston 2d8e43ef78 [ota] Add allow_plaintext_upload for devices that never migrated to encryption (#19396) 2026-09-25 15:42:52 +01:00
J. Nick Koston 370d76d013 [deep_sleep] Use register_apply_action for deep_sleep.prevent and deep_sleep.allow (#19607) 2026-09-25 10:26:57 -04:00
J. Nick Koston 3fb48e0bfa [script] Use register_apply_action for script.stop (#19599) 2026-09-25 10:23:33 -04:00
J. Nick Koston a17285afc7 [fan] Use register_apply_action for fan.toggle and fan.turn_off (#19600) 2026-09-25 10:23:03 -04:00
J. Nick Koston f7dd9c21c5 [lock] Use register_apply_action for lock.lock, lock.unlock and lock.open (#19601) 2026-09-25 10:21:47 -04:00
J. Nick Koston 898ab7c701 [safe_mode] Use register_apply_action for safe_mode.mark_successful (#19605) 2026-09-25 10:21:18 -04:00
J. Nick Koston 28343e1bc9 [mqtt] Use register_apply_action for mqtt.publish, mqtt.enable and mqtt.disable (#19602) 2026-09-25 10:20:13 -04:00
J. Nick Koston 6fa56e18bd [sendspin] Use register_apply_action for the switch, static delay and transition_finished actions (#19608) 2026-09-25 10:18:52 -04:00
luar123 cc5ed93247 [zigbee] move init back into setup (#19427) 2026-09-25 09:29:16 +01:00
J. Nick Koston 91210c5ef5 [button] Use register_apply_action for button.press (#19598) 2026-09-25 03:37:03 -04:00
tomaszduda23andJ. Nick Koston 8db901cbfc [nrf52] set WDT timeout to 30sec for Adafruit bootloader (#19596)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2026-09-25 06:00:33 +02:00
J. Nick Koston 4a2f17bc8b [noise] Run the ESP8266 at 160 MHz during the handshake (#19233) 2026-09-25 00:24:34 +01:00
J. Nick Koston 253793c4df [web_server] Mask the value of a password text entity, not only its state (#19385) 2026-09-24 18:56:28 -04:00
Jeff Brown 3ef0acd4b2 [sgp30][sgp4x] Migrate CONF_HUMIDITY_SOURCE to const.py (#19436) 2026-09-24 17:54:58 -04:00
dependabot[bot] 73ca04579c Update pyparsing requirement from >=3.3.2 to >=3.3.3 (#19592)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-24 17:53:46 -04:00
dependabot[bot] 4ccc5fa37e Bump astral-sh/setup-uv from 10.1.0 to 10.2.0 in /.github/actions/restore-python (#19593)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-24 17:53:25 -04:00
dependabot[bot] f70880ce96 Bump astral-sh/setup-uv from 10.1.0 to 10.2.0 (#19594)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-24 17:53:09 -04:00
057bf88e4a [mcp3204] Read a conversion in one SPI transaction (#19423)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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-24 14:06:07 -04:00
tomaszduda23 16757a84e6 [nrf52] add device power manager support to deep sleep component (#19494) 2026-09-24 13:57:16 -04:00
J. Nick Koston a9111a8993 [ota] Treat a client that leaves after the handshake as a clean close (#19401) 2026-09-24 18:36:45 +01:00
J. Nick Koston 211fd3ce54 [core] Dispatch controller updates through generated direct calls (#19588) 2026-09-24 17:24:10 +00:00
Janne Snabb 9df7e825d3 [core] Keep static task TCB in internal RAM (#19426) 2026-09-24 17:59:03 +01:00
Jonathan Swoboda e63428ddce [nextion] Keep add_waveform_data() available without a waveform sensor (#19590) 2026-09-24 12:31:16 -04:00
J. Nick Koston f8e8295088 [api] Decode without a vtable (#19029) 2026-09-24 16:14:25 +01:00
J. Nick Koston 9f5ac44db1 [api] Default response only string fields to a null StringRef (#19025) 2026-09-24 15:52:37 +01:00
dependabot[bot] 6f71b9102b Bump click from 8.3.3 to 8.5.0 (#18925)
Signed-off-by: dependabot[bot] <support@github.com>
2026-09-24 15:49:07 +01:00
04da1827e3 [light] Add transition_state_publish_interval option to publish intermediate states during transitions (#11968)
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-09-24 14:48:46 +00:00
d2fefe2299 [TAS2780] Add TAS2780 audio amplifier (#15708)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
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-24 15:45:51 +01:00
J. Nick Koston f1f83cda07 [api] Collapse the three protobuf decode virtuals into one (#19016) 2026-09-24 15:30:11 +01:00
Ivan KruglovandJ. Nick Koston 7dc98c683b [uart] clock_source option (#16486)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-09-24 15:16:40 +01:00
Bonne EgglestonandClaude 071fc08e32 [core] Silence -Wunused-function for user builds (#19571)
Co-authored-by: Claude <noreply@anthropic.com>
2026-09-24 14:15:52 +00:00
J. Nick Koston c27cbb132d [api] Make generated encode and size entry points type erased (#19015) 2026-09-24 15:13:52 +01:00
J. Nick Koston 7e308bb83d [tests] Define USE_WEBSERVER_PORT in the light unit test override (#19584) 2026-09-24 14:58:54 +01:00
J. Nick Koston 68235f34c5 [automation] Remove dead trigger classes from switch, button, binary_sensor and event (#19563) 2026-09-24 13:49:38 +00:00
J. Nick Koston b59518f6ab [remote_transmitter] Use register_apply_action for the digital_write action (#19550) 2026-09-24 13:28:29 +00:00
J. Nick Koston da148bedfe [api] Pass the encode cursor by value through the protobuf helpers (#19014) 2026-09-24 14:25:50 +01:00
Jesse Hills b0c9a7d5bd [core] Lint duplicate UNIT_/CONF_ constants against both const homes (#19422) 2026-09-24 14:21:32 +01:00
J. Nick Koston 22177680c4 [tests] Point the binary_sensor conditions at the renamed publish target (#19583) 2026-09-24 14:15:18 +01:00
J. Nick Koston e92b1e27ca [core] Let register_apply_action read the parent id from another schema key (#19548) 2026-09-24 09:12:04 -04:00
Ryan Ronnander fed5dd6a9d [light] Accept the top-level white key from Home Assistant's MQTT JSO… (#19570) 2026-09-24 13:11:40 +00:00
J. Nick Koston cbd985826c [speaker] Use register_apply_condition for speaker.is_playing and speaker.is_stopped (#19562) 2026-09-24 09:10:09 -04:00
J. Nick Koston 2ce2b7b25c [event] Remove the unused std::vector set_event_types overload (#19566) 2026-09-24 09:09:35 -04:00
J. Nick Koston acc9324fa8 [tests] Give the binary_sensor and sensor publish targets distinct ids (#19564) 2026-09-24 09:07:41 -04:00
J. Nick Koston cbb4c49011 [automation] Remove dead trigger classes from sensor, number and text_sensor (#19565) 2026-09-24 09:07:07 -04:00
J. Nick Koston afc5c29569 [event] Trigger events without building a temporary std::string (#19567) 2026-09-24 09:06:29 -04:00
J. Nick Koston 993c96875c [core][select] Pass the select.set option as a plain literal (#19568) 2026-09-24 09:05:24 -04:00
J. Nick Koston 75360a5d52 [core] Collapse ESP8266 flash string ifdefs onto the progmem helpers (#19578) 2026-09-24 13:05:21 +00:00
J. Nick Koston 8b11d3da39 [switch] Use register_apply_condition for switch.is_on and switch.is_off (#19553) 2026-09-24 08:57:58 -04:00
J. Nick Koston 5790181f44 [binary_sensor] Use register_apply_condition for binary_sensor.is_on and binary_sensor.is_off (#19554) 2026-09-24 08:57:34 -04:00
J. Nick Koston 4780936867 [fan] Use register_apply_condition for fan.is_on and fan.is_off (#19555) 2026-09-24 08:57:07 -04:00
J. Nick Koston 22959ea1cf [light] Use register_apply_condition for light.is_on and light.is_off (#19556) 2026-09-24 08:56:40 -04:00
Leonardo Rivera e9aecb5fe8 [climate_ir] Store the supported modes in one mask (#19580) 2026-09-24 12:56:11 +00:00
J. Nick Koston 6768cb818b [media_player] Use register_apply_condition for the state and muted conditions (#19557) 2026-09-24 08:55:39 -04:00
J. Nick Koston 1861d8282f [script] Use register_apply_condition for script.is_running (#19558) 2026-09-24 08:55:12 -04:00
J. Nick Koston c39ffd7099 [lock] Use register_apply_condition for lock.is_locked and lock.is_unlocked (#19559) 2026-09-24 08:54:42 -04:00
J. Nick Koston 1671bb6dd3 [time] Use register_apply_condition for time.has_time (#19560) 2026-09-24 08:54:03 -04:00
J. Nick Koston 0ceefd5f91 [mqtt] Use register_apply_condition for mqtt.connected (#19561) 2026-09-24 08:53:29 -04:00
J. Nick Koston 4477e35d66 [dfplayer] Use register_apply_action for the play, volume, eq, device and transport actions (#19549) 2026-09-24 08:52:34 -04:00
J. Nick Koston e98c4bd375 [online_image] Use register_apply_action for the set_url and release actions (#19547) 2026-09-24 08:51:30 -04:00
J. Nick Koston eae8ff7f91 [audio_dac] Use register_apply_action for the mute_off, mute_on and set_volume actions (#19546) 2026-09-24 08:51:04 -04:00
J. Nick Koston f24e1f31f2 [rotary_encoder] Use register_apply_action for the set_value action (#19545) 2026-09-24 08:50:33 -04:00
J. Nick Koston 4cbced407d [ha-addon] Announce the MCP server to Home Assistant (#19413) 2026-09-24 08:49:40 -04:00
J. Nick Koston 64127632f5 [core][logger] Keep ESP8266 log strings in flash and deprecate the RAM option (#19577) 2026-09-24 13:11:50 +01:00
Philip Poten cd5ff787f4 [lvgl] Allow negative text_letter_space, text_line_space (#19576) 2026-09-24 19:18:32 +10:00
J. Nick Koston 698eb71862 [core] Add ESPHOME_strlen_P to the progmem helpers (#19574) 2026-09-24 10:03:33 +01:00
J. Nick Koston e1126a152e [text_sensor] Use register_apply_condition for text_sensor.state (#19541) 2026-09-23 23:30:54 +00:00
J. Nick Koston 807556171b [core][cover] Add register_apply_condition for conditions that only test the parent (#19540) 2026-09-24 11:00:59 +12:00
J. Nick Koston f4300d9cee [esp8266] Add the native ninja emission (#18560) 2026-09-24 10:45:54 +12:00
J. Nick Koston e3951f2ab4 [esp8266_pwm] Use register_apply_action for the set_frequency action (#19516) 2026-09-24 10:30:19 +12:00
J. Nick Koston 438307f548 [light] Use register_apply_action for the light control actions (#19476) 2026-09-24 10:28:36 +12:00
J. Nick Koston b7b1507daf [update] Use register_apply_action for update.perform (#19505) 2026-09-23 18:28:03 -04:00
J. Nick Koston 1be1f67516 [climate] Use register_apply_action for climate.control (#19475) 2026-09-24 10:27:34 +12:00
J. Nick Koston 21dc6af22e [binary_sensor] Use register_apply_action for binary_sensor.template.publish (#19506) 2026-09-23 18:27:29 -04:00
J. Nick Koston 20703f32bb [mitsubishi_cn105] Use register_apply_action for vane.control (#19477) 2026-09-23 17:27:01 -05:00
J. Nick Koston 2ddc6a0741 [sensor] Use register_apply_action for sensor.template.publish (#19507) 2026-09-23 18:26:41 -04:00
J. Nick KostonandJonathan Swoboda c1d82168d8 [event] Use register_apply_action for event.trigger (#19508)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-09-23 23:26:20 +01:00
J. Nick Koston 281c54bcb8 [globals] Use register_apply_action for globals.set (#19481) 2026-09-24 10:25:42 +12:00
J. Nick Koston a47271b1f1 [fan] Use register_apply_action for fan.turn_on (#19473) 2026-09-23 17:25:33 -05:00
J. Nick Koston 22efbd9778 [esp32_ble_tracker] Use register_apply_action for start_scan and stop_scan (#19520) 2026-09-23 23:25:04 +01:00
J. Nick Koston 6a97172867 [stepper] Use register_apply_action for the setter actions (#19470) 2026-09-23 17:24:56 -05:00
J. Nick Koston c275365258 [rp2040_pwm] Use register_apply_action for the set_frequency action (#19517) 2026-09-23 23:24:44 +01:00
J. Nick Koston 4efeee245c [text] Use register_apply_action for text.set (#19509) 2026-09-23 18:24:25 -04:00
J. Nick Koston bd91d3ff23 [display] Use register_apply_action for display.page.show_next and show_previous (#19519) 2026-09-23 23:24:12 +01:00
J. Nick Koston 2a26d5ab62 [number] Use register_apply_action for the set and operation actions (#19510) 2026-09-23 18:23:55 -04:00
J. Nick Koston f58fe2e3ea [select] Use register_apply_action for the set, set_index and operation actions (#19511) 2026-09-23 18:23:30 -04:00
J. Nick Koston 07c8cfdde3 [cc1101] Use register_apply_action for the setter actions (#19472) 2026-09-23 17:23:22 -05:00
J. Nick Koston 802f2b9f24 [libretiny_pwm] Use register_apply_action for the set_frequency action (#19518) 2026-09-23 23:23:20 +01:00
J. Nick Koston 7df946b665 [ledc] Use register_apply_action for the set_frequency action (#19515) 2026-09-23 23:22:44 +01:00
J. Nick Koston aaed14d018 [media_player] Use register_apply_action for the media player actions (#19514) 2026-09-23 23:22:20 +01:00
J. Nick Koston 62b501049f [datetime] Use register_apply_action for date.set, time.set and datetime.set (#19513) 2026-09-23 23:21:39 +01:00
J. Nick Koston 606525ed18 [output] Use register_apply_action for the output actions (#19512) 2026-09-23 17:21:21 -05:00
J. Nick Koston bd1efeae94 [servo] Use register_apply_action for servo.write and servo.detach (#19531) 2026-09-23 17:20:29 -05:00
J. Nick Koston 0110215e31 [template] Use register_apply_action for lock.template.publish and valve.template.publish (#19525) 2026-09-23 17:20:05 -05:00
J. Nick Koston 170bb02fa3 [ln882h_ble_tracker] Use register_apply_action for stop_scan (#19523) 2026-09-23 23:16:08 +01:00
J. Nick Koston 4f6af3cfab [alarm_control_panel] Use register_apply_action for arm_away, arm_home, arm_night and disarm (#19521) 2026-09-23 23:15:51 +01:00
J. Nick Koston 8f14adaa0c [bk72xx_ble_tracker] Use register_apply_action for stop_scan (#19522) 2026-09-23 23:15:25 +01:00
J. Nick Koston 761dd9dd9c [rp2_ble_tracker] Use register_apply_action for stop_scan (#19524) 2026-09-23 23:15:10 +01:00
J. Nick Koston 2e809c3626 [pulse_counter] Use register_apply_action for pulse_counter.set_total_pulses (#19532) 2026-09-23 23:13:44 +01:00
J. Nick Koston bbd8472b66 [espnow] Use register_apply_action for peer.add, peer.delete and set_channel (#19538) 2026-09-23 16:57:32 -05:00
J. Nick Koston 01f9416a79 [text_sensor] Use register_apply_action for text_sensor.template.publish (#19539) 2026-09-23 16:55:58 -05:00
J. Nick Koston 4e2d1cd7fc [hlk_fm22x] Use register_apply_action for scan, enroll, delete, delete_all and reset (#19537) 2026-09-23 16:55:16 -05:00
J. Nick Koston 5e8f620362 [voice_assistant] Use register_apply_action for start, start_continuous and stop (#19536) 2026-09-23 16:53:34 -05:00
J. Nick Koston 9d87fcd3b7 [esp8266] Add the native build spec (#18559) 2026-09-23 22:52:05 +01:00
J. Nick Koston fa7ccc934e [pulse_meter] Use register_apply_action for pulse_meter.set_total_pulses (#19533) 2026-09-23 16:52:00 -05:00
J. Nick Koston 4a7fe83a17 [speaker] Use register_apply_action for the speaker actions (#19535) 2026-09-23 16:51:20 -05:00
Jonathan Swoboda c33ccc7043 [esp32][esp32_hosted] Bump recommended Arduino to 3.3.12 and platform to 55.03.312 and PlatformIO to 6.2.0 (#19417) 2026-09-23 17:50:54 -04:00
J. Nick Koston 4f95ffaa1f [network] Remove deprecated IPAddress::str() from the host build (#19528) 2026-09-23 16:49:59 -05:00
J. Nick Koston 487ef61d0a [nextion] Use register_apply_action for set_brightness and the publish actions (#19529) 2026-09-23 16:47:31 -05:00
J. Nick Koston ce6a66959a [sprinkler] Use register_apply_action for the setter actions (#19471) 2026-09-23 22:41:47 +01:00
dalkleinandClaude Opus 5 70f8ec73c9 [modbus] Log the discarded bytes, not just how many (#19428)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 17:40:01 +01:00
Bonne Eggleston d78d640abd [modbus] Drop a deferred server reply when another frame arrives first (#19534) 2026-09-23 17:33:00 +01:00
cf3552b46e [pid] Pid climate add set deadband parameters action (#14730)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-09-23 17:21:25 +01:00
f6e97b7ada [RX8025T] add RX8025T realtime clock (#13274)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2026-09-23 17:19:09 +01:00
Leonardo Rivera 35607e99a7 [mitsubishi] Use the climate_ir traits() for supported modes (#19485) 2026-09-23 17:18:41 +01:00
myztillxandJ. Nick Koston b36c6bd7ca [dfplayer] Add set_current_track_repeat action (#9186)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-09-23 16:58:06 +01:00
esphome[bot] 9c5479287e Bump bundled esphome-device-builder to 1.15.0 (#19526) 2026-09-23 15:29:47 +00:00
Leonardo Rivera 93df1e1259 [gree] Remove the traits() override now handled by climate_ir (#19484) 2026-09-23 16:29:23 +01:00
tomaszduda23andpre-commit-ci-lite[bot] 26abc1325e [nrf52] disable USB if not configured by esphome to save power (#19493)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-09-23 14:48:39 +02:00
J. Nick Koston 3da7533159 [remote_base] Generate the gobox code as uint64_t (#19478) 2026-09-23 12:24:30 +01:00
J. Nick Koston 1a8a661b56 [rtttl] Use register_apply_action for rtttl.play and rtttl.stop (#19482) 2026-09-23 12:22:05 +01:00
J. Nick Koston 3b570d84ae [rf_bridge] Use register_apply_action for the actions (#19497) 2026-09-23 12:19:46 +01:00
J. Nick Koston 547624edf9 [at581x] Use register_apply_action for the settings and reset actions (#19496) 2026-09-23 12:19:24 +01:00
J. Nick Koston 14baf03aad [cover] Use register_apply_action for the open, close, stop and toggle actions (#19500) 2026-09-23 12:19:07 +01:00
J. Nick Koston 418ae73870 [template] Use register_apply_action for climate.template.publish (#19495) 2026-09-23 12:18:55 +01:00
J. Nick Koston e7ff903284 [valve] Use register_apply_action for valve.control (#19474) 2026-09-23 12:17:06 +01:00
J. Nick Koston 56f7280d55 [switch] Use register_apply_action for the switch actions (#19498) 2026-09-23 12:16:47 +01:00
J. Nick Koston 01fc7511ae [core] Call a lambda whose only statement returns a braced value (#19503) 2026-09-23 12:15:21 +01:00
J. Nick Kostonandesphbot 8c3ebd3414 [docker] Drop the unused PlatformIO library preinstall (#19504)
Co-authored-by: esphbot <287758279+esphbot@users.noreply.github.com>
2026-09-23 12:14:04 +01:00
ca46954335 [fujitsu_general] Read the mode, fan speed and swing fields with their real width (#18297)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-09-23 12:03:59 +01:00
Leonardo RiveraandJ. Nick Koston a4bebc3083 [mitsubishi] Store the fan mode and default directions in one byte each (#19489)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-09-23 10:29:11 +00:00
Leonardo RiveraandJ. Nick Koston c609db16b3 [gree] Store the model in one byte (#19488)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-09-23 11:28:32 +01:00
Leonardo Rivera 00fa63ea19 [climate_ir] Only advertise HEAT_COOL when heat and cool are both supported (#19483) 2026-09-23 11:14:57 +01:00
J. Nick Koston e8d3c06b60 [core][cover] Add register_apply_action for actions that only forward values (#19469) 2026-09-23 09:36:42 +00:00
Leonardo Rivera 359eba7145 [fujitsu_general] Use constexpr for the protocol constants (#19486) 2026-09-23 01:33:30 +01:00
Jeff Brown e76edeb19e [yaml_util][substitutions] Repair bad tests (#19468) 2026-09-23 01:15:38 +01:00
842 changed files with 27447 additions and 21307 deletions

No files matched your search

-3
View File
@@ -31,7 +31,4 @@ RUN \
platformio settings set enable_telemetry No \
&& platformio settings set check_platformio_interval 1000000
COPY script/platformio_install_deps.py platformio.ini ./
RUN ./platformio_install_deps.py platformio.ini --libraries --platforms --tools
WORKDIR /workspaces
+1 -1
View File
@@ -32,7 +32,7 @@ runs:
# detects the activated venv via ``VIRTUAL_ENV`` so the venv layout
# downstream jobs rely on is preserved.
if: steps.cache-venv.outputs.cache-hit != 'true'
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
with:
enable-cache: true
# Pull request saves land in per-PR scopes nothing else can
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
- name: Set up uv
# ``--system`` (below) installs into the setup-python interpreter;
# no venv is created or restored by this workflow.
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
with:
enable-cache: true
# Pull-request-only workflow: a save could never be shared and
-2
View File
@@ -12,9 +12,7 @@ on:
- ".github/workflows/ci-docker.yml"
- "requirements*.txt"
- "pyproject.toml"
- "platformio.ini"
- "esphome/idf_component.yml"
- "script/platformio_install_deps.py"
# Core, build pipeline, toolchain, and target-platform changes can change
# how a toolchain is set up or built, so re-run the per-toolchain compile
# smoke test when they change.
+3 -3
View File
@@ -49,7 +49,7 @@ jobs:
# detects the activated venv via ``VIRTUAL_ENV`` so downstream jobs
# that ``. venv/bin/activate`` see an identical layout.
if: steps.cache-venv.outputs.cache-hit != 'true'
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
with:
enable-cache: true
# Pull request saves land in per-PR scopes nothing else can
@@ -413,7 +413,7 @@ jobs:
- name: Set up uv
# Only needed on cache miss to populate the venv.
if: steps.cache-venv.outputs.cache-hit != 'true'
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
with:
enable-cache: true
# Pull request saves land in per-PR scopes nothing else can
@@ -1274,7 +1274,7 @@ jobs:
# install step (order-of-magnitude faster on cold boots,
# with its own wheel cache). actions/setup-python still
# provides the interpreter.
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
with:
enable-cache: true
# Pull request saves land in per-PR scopes nothing else can
+1 -1
View File
@@ -47,7 +47,7 @@ jobs:
# setup-python interpreter so subsequent ``prek`` /
# ``script/run-in-env.py`` steps find the deps without a
# ``uv run`` prefix.
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
with:
enable-cache: true
# Pin uv version so the action does not have to fetch the
+30 -1
View File
@@ -443,6 +443,20 @@ file does, and it is the authority when they disagree. The most useful starting
Use `synchronous=True` for actions that run to completion inside `play()` without deferring. Use `synchronous=False` if the action may suspend/defer execution (e.g. `delay`, `wait_until`, `script.wait`) or store trigger arguments for later use.
**Actions that only forward templatable values to their parent need no C++ class.** Register them
with `register_apply_action`; do not write a `TEMPLATABLE_VALUE` class or a builder for this shape.
```python
automation.register_apply_action(
"my_component.set_gains",
schema,
automation.ApplyField(CONF_KP, "set_kp", cg.float_),
automation.ApplyField(CONF_KI, "set_ki", cg.float_),
)
```
The `ApplyField`, `ApplyCall` and `register_apply_action` docstrings in `esphome/automation.py` cover
the rest; `cover.control` and `cover.template.publish` are in-tree examples. `TEMPLATABLE_VALUE` with
`cg.templatable` stays for actions whose `play()` has real logic beyond forwarding values.
* **Conditions:**
```cpp
template<typename... Ts> class MyCondition : public Condition<Ts...> {
@@ -456,6 +470,19 @@ file does, and it is the authority when they disagree. The most useful starting
Register with `automation.register_simple_condition("my_component.is_active", MyCondition, schema)`;
`register_bare_condition`, `register_parented_condition` and the decorator follow the action rules.
**Conditions that only test their parent need no C++ class either.** Register them with
`register_apply_condition`; the expression is applied to the parent, and an `ApplyCall` compares
against config values.
```python
automation.register_apply_condition("my_component.is_active", schema, "is_active()")
automation.register_apply_condition(
"my_component.state_is",
schema,
automation.ApplyCall("state == {}", ((CONF_STATE, cg.bool_),)),
)
```
`cover.is_open`, `rtttl.is_playing` and `component.is_idle` are in-tree examples.
* **Type Hints:** Type-hint all function signatures, including test functions and config validators (e.g. `def validate_x(config: ConfigType) -> ConfigType:`, `def test_x() -> None:`). Import `ConfigType` from `esphome.types`.
* **Configuration Validation:**
@@ -710,7 +737,9 @@ file does, and it is the authority when they disagree. The most useful starting
6. **Avoid `std::deque`:** It allocates in 512-byte blocks regardless of element size, guaranteeing at least 512 bytes of RAM usage immediately. This is a major source of crashes on memory-constrained devices.
7. **Detection:** Look for these patterns in compiler output:
7. **Never use `new (std::nothrow)`:** On ESP-IDF exceptions are disabled, so a failed nothrow allocation aborts instead of returning `nullptr`. Use `RAMAllocator` from `esphome/core/helpers.h`; CI rejects `std::nothrow`.
8. **Detection:** Look for these patterns in compiler output:
- Large code sections with STL symbols (vector, map, set)
- `alloc`, `realloc`, `dealloc` in symbol names
- `_M_realloc_insert`, `_M_default_append` (vector reallocation)
+7
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
@@ -457,6 +459,7 @@ esphome/components/rtl87xx/* @kuba2k2
esphome/components/rtttl/* @glmnet @ximex
esphome/components/runtime_image/* @clydebarrow @guillempages @kahrendt
esphome/components/runtime_stats/* @bdraco
esphome/components/rx8025t/* @remcom
esphome/components/rx8130/* @beormund
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
esphome/components/scd4x/* @martgras @sjtrny
@@ -547,7 +550,9 @@ 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
esphome/components/tca9548a/* @andreashergert1984
esphome/components/tca9555/* @mobrembski
@@ -560,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
@@ -636,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 -4
View File
@@ -22,16 +22,13 @@ RUN \
-r /requirements.txt
# Install the ESPHome Device Builder dashboard.
RUN uv pip install --no-cache-dir esphome-device-builder==1.14.9
RUN uv pip install --no-cache-dir esphome-device-builder==1.16.0
RUN \
platformio settings set enable_telemetry No \
&& platformio settings set check_platformio_interval 1000000 \
&& mkdir -p /piolibs
COPY script/platformio_install_deps.py platformio.ini /
RUN /platformio_install_deps.py /platformio.ini --libraries
ARG BUILD_VERSION
LABEL \
@@ -4,7 +4,6 @@
# Home Assistant Add-on: ESPHome
# Sends discovery information to Home Assistant.
# ==============================================================================
declare config
declare port
# We only disable it when disabled explicitly
@@ -19,14 +18,17 @@ port=$(bashio::addon.ingress_port)
# Wait for the ESPHome Device Builder to become available
bashio::net.wait_for "${port}" "127.0.0.1" 300
config=$(\
bashio::var.json \
host "127.0.0.1" \
port "^${port}" \
)
# Send one discovery message; the config is a JSON string built with bashio::var.json.
send_discovery() {
local service=$1
local config=$2
if bashio::discovery "${service}" "${config}" > /dev/null; then
bashio::log.info "Successfully send ${service} discovery information to Home Assistant."
else
bashio::log.error "${service} discovery message to Home Assistant failed!"
fi
}
if bashio::discovery "esphome" "${config}" > /dev/null; then
bashio::log.info "Successfully send discovery information to Home Assistant."
else
bashio::log.error "Discovery message to Home Assistant failed!"
fi
send_discovery "esphome" "$(bashio::var.json host "127.0.0.1" port "^${port}")"
# The Device Builder MCP server, consumed by Home Assistant's mcp integration.
send_discovery "mcp" "$(bashio::var.json url "http://127.0.0.1:${port}/api/mcp")"
+12
View File
@@ -5,3 +5,15 @@ bk72xx:
board: generic-bk7231n-qfn32-tuya
logger:
wifi:
ssid: MySSID
password: password1
ap:
# mqtt and captive_portal together pull in AsyncTCP and ESPAsyncWebServer;
# a stray ESP32 AsyncTCP copy on the library search path breaks this build
captive_portal:
mqtt:
broker: 192.168.178.84
+64 -31
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,
@@ -817,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()
@@ -859,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
@@ -975,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
@@ -1343,8 +1343,12 @@ def _upload_via_native_api(
# fall back to a plaintext upload
noise_psk = None
plaintext_fallback = False
allow_plaintext_upload = False
if (encryption_conf := ota_conf.get(CONF_ENCRYPTION)) is not None:
noise_psk = encryption_conf.get(CONF_KEY)
allow_plaintext_upload = bool(
encryption_conf.get(espota2.CONF_ALLOW_PLAINTEXT_UPLOAD)
)
if not noise_psk:
raise EsphomeError(
"OTA encryption is configured but no key was resolved; "
@@ -1395,6 +1399,7 @@ def _upload_via_native_api(
ota_type,
noise_psk,
plaintext_fallback=plaintext_fallback,
allow_plaintext_upload=allow_plaintext_upload,
)
@@ -1965,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",
@@ -2009,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:
@@ -2020,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
+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,
)
+299 -17
View File
@@ -1,5 +1,8 @@
from collections.abc import Callable
from dataclasses import dataclass, field
import logging
import string
from typing import Any
import esphome.codegen as cg
import esphome.config_validation as cv
@@ -18,15 +21,17 @@ from esphome.const import (
CONF_TYPE_ID,
CONF_UPDATE_INTERVAL,
)
from esphome.core import ID, Lambda
from esphome.core import CORE, ID, EsphomeError, Lambda
from esphome.cpp_generator import (
FlashStringLiteral,
LambdaExpression,
MockObj,
MockObjClass,
TemplateArgsType,
call_lambda,
)
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from esphome.types import ConfigType
from esphome.types import ConfigType, SafeExpType
from esphome.util import Registry
@@ -57,6 +62,7 @@ def maybe_conf(conf, *validators):
with cv.remove_prepend_path([conf]):
return validator({conf: value})
validate.inner_schema = validator
return validate
@@ -207,6 +213,295 @@ validate_action_list = cv.validate_registry("action", ACTION_REGISTRY)
validate_condition = cv.validate_registry_entry("condition", CONDITION_REGISTRY)
validate_condition_list = cv.validate_registry("condition", CONDITION_REGISTRY)
ApplyAction = cg.esphome_ns.class_("ApplyAction", Action)
ApplyCondition = cg.esphome_ns.class_("ApplyCondition", Condition)
def flash_string(config: ConfigType, value: str) -> str:
"""Default renderer for ``std::string`` constants; copies the literal out of flash on ESP8266."""
if CORE.is_esp8266:
return f"progmem_string({FlashStringLiteral(value)})"
return str(cg.safe_exp(value))
def literal_with_length(config: ConfigType, value: str) -> str:
"""Renderer for a ``(const char *, size_t)`` target: a plain literal plus its byte length.
The target compares or copies the bytes in place, so it needs the RAM literal rather than
the PROGMEM rendering on ESP8266, and the length saves a strlen.
"""
return f"{cg.safe_exp(value)}, {len(value.encode('utf-8'))}"
@dataclass(frozen=True)
class ApplyCall:
"""One statement from config keys, e.g. ``"set_range({}, {})"`` with ``((CONF_LOW, cg.float_), ...)``.
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 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
args: tuple[tuple[Any, ...], ...] = ()
def __post_init__(self) -> None:
fields = [
f for _, f, _, _ in string.Formatter().parse(self.target) if f is not None
]
if any(fields):
raise ValueError(
f"apply target {self.target!r}: only bare {{}} placeholders"
)
if len(fields) != len(self.args):
raise ValueError(
f"apply target {self.target!r} has {len(fields)} "
f"placeholder(s) for {len(self.args)} config key(s)"
)
if any(len(arg) not in (2, 3) for arg in self.args):
raise ValueError(
f"apply target {self.target!r}: each arg is (conf_key, type_[, const_fn])"
)
@property
def members(self) -> list[tuple[Any, Any, Any]]:
"""Each arg as ``(conf_key, type_, const_fn or None)``."""
return [
(arg[0], arg[1], arg[2] if len(arg) == 3 else None) for arg in self.args
]
@dataclass(frozen=True)
class ApplyField:
"""One config key forwarded as ``target(value)``, or as statement ``target`` when it has ``{}``.
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 or an id
bypasses it, so the target must also take a plain ``type_``. An absent key emits nothing.
"""
conf_key: str | tuple[str, ...]
target: str
type_: SafeExpType
const_fn: Callable[[ConfigType, Any], str] | None = None
def call(self) -> ApplyCall:
target = self.target if "{}" in self.target else f"{self.target}({{}})"
return ApplyCall(target, ((self.conf_key, self.type_, self.const_fn),))
def _config_lookup(config: ConfigType, key: str | tuple[str, ...]) -> Any:
if isinstance(key, str):
return config.get(key)
for part in key:
if (config := config.get(part)) is None:
return None
return config
def _dict_schema(schema: Any) -> Any:
"""The dict-backed cv.Schema inside cv.All and maybe_* wrappers, or None; cv.Any is not inspected."""
if isinstance(schema, dict):
return cv.Schema(schema)
if isinstance(getattr(schema, "schema", None), dict):
return schema
if isinstance(schema, cv.All):
inner = schema.validators
else:
inner = (
getattr(schema, "inner_schema", None),
) # maybe_conf / maybe_simple_value
for candidate in inner:
if candidate is not None and (found := _dict_schema(candidate)) is not None:
return found
return None
def _check_key_in_schema(
name: str, schema: Any, conf_key: str | tuple[str, ...]
) -> None:
"""Reject a key path the schema does not have; a typo would otherwise be a silent no-op.
Only dict-backed schemas, also inside cv.All and maybe_* wrappers, can be checked.
"""
for part in (conf_key,) if isinstance(conf_key, str) else conf_key:
if (schema := _dict_schema(schema)) is None:
return
markers = {
getattr(marker, "schema", marker): marker for marker in schema.schema
}
if part not in markers:
raise ValueError(f"{name}: config key {part!r} is not in the schema")
schema = schema.schema[markers[part]]
async def _apply_parent(config: ConfigType, id_key: str = CONF_ID) -> str:
# Global-scope qualified so a trigger arg named like the id cannot shadow it.
return f"::{await cg.get_variable(config[id_key])}"
def _apply_lambda_args(args: TemplateArgsType) -> TemplateArgsType:
# Must match ApplyAction::ApplyFn and ApplyCondition::CheckFn exactly for the function
# pointer conversion.
return [
(cg.RawExpression(f"const std::remove_cvref_t<{cg.safe_exp(t)}> &"), arg)
for t, arg in args
]
async def _render_values(
name: str,
target: str,
members: list[tuple[Any, Any, Any]],
values: list[Any],
config: ConfigType,
parent: str,
lambda_args: TemplateArgsType,
compare: bool = False,
) -> list[str]:
"""Render the argument text of one statement; every key must be present.
``compare``: an inlined lambda expression is parenthesized so it binds as a whole
beside an operator.
"""
if any(value is None for value in values):
keys = [key for key, _, _ in members]
raise EsphomeError(f"{name}: {target!r} needs all of {keys}")
exprs: list[str] = []
for (_, type_, const_fn), value in zip(members, values, strict=True):
if isinstance(value, Lambda):
if isinstance(type_, str):
type_ = cg.RawExpression(type_.format(parent=parent))
inner = await cg.process_lambda(value, lambda_args, return_type=type_)
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:
exprs.append(str(cg.safe_exp(value)))
return exprs
def _apply_values(config: ConfigType, members: list[tuple[Any, Any, Any]]) -> list[Any]:
return [_config_lookup(config, key) for key, _, _ in members]
def register_apply_action(
name: str,
schema: cv.Schema,
*fields: ApplyField | ApplyCall,
call: str | None = None,
id_key: str = CONF_ID,
) -> None:
"""Register an action that only forwards config values to its parent, with no C++ class.
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.
"""
# An action stores the value, so a std::string constant stays in flash on ESP8266.
statements_spec = [
(
c.target,
[
(key, t, fn or (flash_string if t is cg.std_string else None))
for key, t, fn in c.members
],
)
for c in (f if isinstance(f, ApplyCall) else f.call() for f in fields)
]
_check_key_in_schema(name, schema, id_key)
for _, members in statements_spec:
for conf_key, _, _ in members:
_check_key_in_schema(name, schema, conf_key)
async def builder(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
parent = await _apply_parent(config, id_key)
lambda_args = _apply_lambda_args(args)
receiver = "apply_call." if call else f"{parent}->"
statements: list[str] = []
for target, members in statements_spec:
values = _apply_values(config, members)
if members and all(value is None for value in values):
continue
exprs = await _render_values(
name, target, members, values, config, parent, lambda_args
)
statements.append(f"{receiver}{target.format(*exprs)};")
if call:
statements = [
f"auto apply_call = {parent}->{call}();",
*statements,
"apply_call.perform();",
]
apply_lambda = LambdaExpression(
["\n".join(statements)], lambda_args, capture="", return_type=cg.void
)
return cg.new_Pvariable(action_id, template_arg, apply_lambda)
register_action(name, ApplyAction, schema, synchronous=True)(builder)
def register_apply_condition(
name: str, schema: cv.Schema, check: str | ApplyCall, id_key: str = CONF_ID
) -> None:
"""Register a condition that is one expression on its parent, with no C++ class.
``check`` is applied to the parent: ``"is_playing()"`` becomes ``parent->is_playing()``; an
``ApplyCall`` such as ``ApplyCall("state == {}", ((CONF_STATE, cg.bool_),))`` compares
against config values, all of which must be present. Write ``== false`` to negate.
String constants are plain literals, so compare a ``std::string`` or ``StringRef`` member.
Generates one stateless function for ``ApplyCondition<Ts...>``.
"""
call = check if isinstance(check, ApplyCall) else ApplyCall(check)
members = call.members
_check_key_in_schema(name, schema, id_key)
for conf_key, _, _ in members:
_check_key_in_schema(name, schema, conf_key)
async def builder(
config: ConfigType,
condition_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
parent = await _apply_parent(config, id_key)
lambda_args = _apply_lambda_args(args)
exprs = await _render_values(
name,
call.target,
members,
_apply_values(config, members),
config,
parent,
lambda_args,
compare=True,
)
check_lambda = LambdaExpression(
[f"return {parent}->{call.target.format(*exprs)};"],
lambda_args,
capture="",
return_type=cg.bool_,
)
return cg.new_Pvariable(condition_id, template_arg, check_lambda)
register_condition(name, ApplyCondition, schema)(builder)
def validate_potentially_and_condition(value):
if isinstance(value, list):
@@ -454,28 +749,15 @@ async def for_condition_to_code(
return var
@register_condition(
register_apply_condition(
"component.is_idle",
LambdaCondition,
maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(cg.Component),
}
),
"is_idle()",
)
async def component_is_idle_condition_to_code(
config: ConfigType,
condition_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
comp = await cg.get_variable(config[CONF_ID])
lambda_ = await cg.process_lambda(
Lambda(f"return {comp}->is_idle();"), args, return_type=bool
)
return new_lambda_pvariable(
condition_id, lambda_, StatelessLambdaCondition, template_arg
)
@register_action(
File diff suppressed because it is too large. Load diff
+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
@@ -64,7 +64,6 @@ from esphome.cpp_types import ( # noqa: F401
Application,
Component,
ComponentPtr,
Controller,
EntityBase,
EntityCategory,
ESPTime,
+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
@@ -41,18 +41,6 @@ StateAnyForwarder = alarm_control_panel_ns.class_("StateAnyForwarder")
StateEnterForwarder = alarm_control_panel_ns.class_("StateEnterForwarder")
AlarmControlPanelState = alarm_control_panel_ns.enum("AlarmControlPanelState")
ArmAwayAction = alarm_control_panel_ns.class_("ArmAwayAction", automation.Action)
ArmHomeAction = alarm_control_panel_ns.class_("ArmHomeAction", automation.Action)
ArmNightAction = alarm_control_panel_ns.class_("ArmNightAction", automation.Action)
DisarmAction = alarm_control_panel_ns.class_("DisarmAction", automation.Action)
PendingAction = alarm_control_panel_ns.class_("PendingAction", automation.Action)
TriggeredAction = alarm_control_panel_ns.class_("TriggeredAction", automation.Action)
ChimeAction = alarm_control_panel_ns.class_("ChimeAction", automation.Action)
ReadyAction = alarm_control_panel_ns.class_("ReadyAction", automation.Action)
AlarmControlPanelCondition = alarm_control_panel_ns.class_(
"AlarmControlPanelCondition", automation.Condition
)
_ALARM_CONTROL_PANEL_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
@@ -196,125 +184,38 @@ async def new_alarm_control_panel(config, *args):
return var
@automation.register_action(
"alarm_control_panel.arm_away",
ArmAwayAction,
ALARM_CONTROL_PANEL_ACTION_SCHEMA,
synchronous=True,
)
async def alarm_action_arm_away_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)
if code_config := config.get(CONF_CODE):
templatable_ = await cg.templatable(code_config, args, cg.std_string)
cg.add(var.set_code(templatable_))
return var
@automation.register_action(
"alarm_control_panel.arm_home",
ArmHomeAction,
ALARM_CONTROL_PANEL_ACTION_SCHEMA,
synchronous=True,
)
async def alarm_action_arm_home_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)
if code_config := config.get(CONF_CODE):
templatable_ = await cg.templatable(code_config, args, cg.std_string)
cg.add(var.set_code(templatable_))
return var
@automation.register_action(
"alarm_control_panel.arm_night",
ArmNightAction,
ALARM_CONTROL_PANEL_ACTION_SCHEMA,
synchronous=True,
)
async def alarm_action_arm_night_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)
if CONF_CODE in config:
templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string)
cg.add(var.set_code(templatable_))
return var
@automation.register_action(
"alarm_control_panel.disarm",
DisarmAction,
ALARM_CONTROL_PANEL_ACTION_SCHEMA,
synchronous=True,
)
async def alarm_action_disarm_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)
if code_config := config.get(CONF_CODE):
templatable_ = await cg.templatable(code_config, args, cg.std_string)
cg.add(var.set_code(templatable_))
return var
@automation.register_action(
"alarm_control_panel.pending",
PendingAction,
ALARM_CONTROL_PANEL_ACTION_SCHEMA,
synchronous=True,
)
async def alarm_action_pending_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
"alarm_control_panel.triggered",
TriggeredAction,
ALARM_CONTROL_PANEL_ACTION_SCHEMA,
synchronous=True,
)
async def alarm_action_trigger_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
"alarm_control_panel.chime",
ChimeAction,
ALARM_CONTROL_PANEL_ACTION_SCHEMA,
synchronous=True,
)
async def alarm_action_chime_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
"alarm_control_panel.ready",
ReadyAction,
ALARM_CONTROL_PANEL_ACTION_SCHEMA,
synchronous=True,
)
@automation.register_condition(
"alarm_control_panel.ready",
AlarmControlPanelCondition,
ALARM_CONTROL_PANEL_CONDITION_SCHEMA,
)
async def alarm_action_ready_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_condition(
"alarm_control_panel.is_armed",
AlarmControlPanelCondition,
ALARM_CONTROL_PANEL_CONDITION_SCHEMA,
)
async def alarm_control_panel_is_armed_to_code(
config, condition_id, template_arg, args
# Mirrors AlarmControlPanel::arm_with_code_: arm first, set the code only when given.
for _name, _arm in (
("alarm_control_panel.arm_away", "arm_away()"),
("alarm_control_panel.arm_home", "arm_home()"),
("alarm_control_panel.arm_night", "arm_night()"),
("alarm_control_panel.disarm", "disarm()"),
):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren)
automation.register_apply_action(
_name,
ALARM_CONTROL_PANEL_ACTION_SCHEMA,
automation.ApplyCall(_arm),
automation.ApplyField(CONF_CODE, "set_code", cg.std_string),
call="make_call",
)
for _name, _call in (
("alarm_control_panel.pending", "pending()"),
("alarm_control_panel.triggered", "triggered()"),
):
automation.register_apply_action(
_name,
ALARM_CONTROL_PANEL_ACTION_SCHEMA,
automation.ApplyCall(_call),
call="make_call",
)
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,84 +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 ArmAwayAction final : public Action<Ts...> {
public:
explicit ArmAwayAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
TEMPLATABLE_VALUE(std::string, code)
void play(const Ts &...x) override { this->alarm_control_panel_->arm_away(this->code_.optional_value(x...)); }
protected:
AlarmControlPanel *alarm_control_panel_;
};
template<typename... Ts> class ArmHomeAction final : public Action<Ts...> {
public:
explicit ArmHomeAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
TEMPLATABLE_VALUE(std::string, code)
void play(const Ts &...x) override { this->alarm_control_panel_->arm_home(this->code_.optional_value(x...)); }
protected:
AlarmControlPanel *alarm_control_panel_;
};
template<typename... Ts> class ArmNightAction final : public Action<Ts...> {
public:
explicit ArmNightAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
TEMPLATABLE_VALUE(std::string, code)
void play(const Ts &...x) override { this->alarm_control_panel_->arm_night(this->code_.optional_value(x...)); }
protected:
AlarmControlPanel *alarm_control_panel_;
};
template<typename... Ts> class DisarmAction final : public Action<Ts...> {
public:
explicit DisarmAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
TEMPLATABLE_VALUE(std::string, code)
void play(const Ts &...x) override { this->alarm_control_panel_->disarm(this->code_.optional_value(x...)); }
protected:
AlarmControlPanel *alarm_control_panel_;
};
template<typename... Ts> class PendingAction final : public Action<Ts...> {
public:
explicit PendingAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
void play(const Ts &...x) override { this->alarm_control_panel_->make_call().pending().perform(); }
protected:
AlarmControlPanel *alarm_control_panel_;
};
template<typename... Ts> class TriggeredAction final : public Action<Ts...> {
public:
explicit TriggeredAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
void play(const Ts &...x) override { this->alarm_control_panel_->make_call().triggered().perform(); }
protected:
AlarmControlPanel *alarm_control_panel_;
};
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:
+2 -3
View File
@@ -81,7 +81,7 @@ def AUTO_LOAD(config: ConfigType) -> list[str]:
api_ns = cg.esphome_ns.namespace("api")
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
APIServer = api_ns.class_("APIServer", cg.Component)
HomeAssistantServiceCallAction = api_ns.class_(
"HomeAssistantServiceCallAction", automation.Action
)
@@ -462,8 +462,7 @@ async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
# Track controller registration for StaticVector sizing
CORE.register_controller()
CORE.register_controller(var)
# Request a log listener slot for API log streaming
request_log_listener()
+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
+10 -2
View File
@@ -600,7 +600,7 @@ bool APIConnection::send_light_state(light::LightState *light) {
uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *light = static_cast<light::LightState *>(entity);
LightStateResponse resp;
auto values = light->remote_values;
auto values = light->get_reported_values();
auto color_mode = values.get_color_mode();
resp.state = values.is_on();
resp.color_mode = static_cast<enums::ColorMode>(color_mode);
@@ -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();
@@ -2267,7 +2270,12 @@ bool APIConnection::send_message_(uint32_t payload_size, uint16_t message_type,
// Capacity reserved above, cannot fail
(void) shared_buf.resize(write_start + payload_size);
ProtoWriteBuffer buffer{&shared_buf, write_start};
encode_fn(msg, buffer PROTO_ENCODE_DEBUG_INIT(&shared_buf));
uint8_t *end = encode_fn(msg, buffer PROTO_ENCODE_DEBUG_INIT(&shared_buf));
#ifdef ESPHOME_DEBUG_API
proto_check_encode_end(end, shared_buf.data() + shared_buf.size());
#else
(void) end;
#endif
return this->send_buffer(ProtoWriteBuffer{&shared_buf}, message_type);
}
// encode_to_buffer is defined inline in api_connection.h (ESPHOME_ALWAYS_INLINE)
+6 -24
View File
@@ -346,11 +346,7 @@ class APIConnection final : public APIServerConnectionBase {
/// Returns false as soon as the TCP buffer is full. Marked nodiscard so we
/// have no silent failures: every caller must handle (or log) a refusal.
template<typename T> [[nodiscard]] bool send_message(const T &msg) {
if constexpr (T::ESTIMATED_SIZE == 0) {
return this->send_message_(0, T::MESSAGE_TYPE, &encode_msg_noop, &msg);
} else {
return this->send_message_(msg.calculate_size(), T::MESSAGE_TYPE, &proto_encode_msg<T>, &msg);
}
return this->send_message_(T::calc_size_msg(&msg), T::MESSAGE_TYPE, &T::encode_msg, &msg);
}
/// Clear the shared write buffer and reserve space for the first message.
@@ -406,16 +402,6 @@ class APIConnection final : public APIServerConnectionBase {
void process_state_subscriptions_();
#endif
// Size thunk — converts void* back to concrete type for direct calculate_size() call
template<typename T> static uint32_t calc_size(const void *msg) {
return static_cast<const T *>(msg)->calculate_size();
}
// Shared no-op encode thunk for empty messages (ESTIMATED_SIZE == 0)
static uint8_t *encode_msg_noop(const void *, ProtoWriteBuffer &buf PROTO_ENCODE_DEBUG_PARAM) {
return buf.get_pos();
}
// Non-template buffer management for send_message
bool send_message_(uint32_t payload_size, uint16_t message_type, MessageEncodeFn encode_fn, const void *msg);
@@ -434,11 +420,7 @@ class APIConnection final : public APIServerConnectionBase {
// Hot paths (state/info) go through fill_and_encode_entity_state/info instead.
// batch_message_type_ is already set by dispatch_message_ before reaching here.
template<typename T> static uint16_t encode_message_to_buffer(T &msg, APIConnection *conn, uint32_t remaining_size) {
if constexpr (T::ESTIMATED_SIZE == 0) {
return encode_to_buffer_slow(0, &encode_msg_noop, &msg, conn, remaining_size);
} else {
return encode_to_buffer_slow(msg.calculate_size(), &proto_encode_msg<T>, &msg, conn, remaining_size);
}
return encode_to_buffer_slow(T::calc_size_msg(&msg), &T::encode_msg, &msg, conn, remaining_size);
}
// Non-template core — fills state fields and encodes
@@ -450,7 +432,7 @@ class APIConnection final : public APIServerConnectionBase {
template<typename T>
static uint16_t fill_and_encode_entity_state(EntityBase *entity, T &msg, APIConnection *conn,
uint32_t remaining_size) {
return fill_and_encode_entity_state(entity, msg, &calc_size<T>, &proto_encode_msg<T>, conn, remaining_size);
return fill_and_encode_entity_state(entity, msg, &T::calc_size_msg, &T::encode_msg, conn, remaining_size);
}
// Non-template core — fills info fields, allocates buffers, and encodes
@@ -462,7 +444,7 @@ class APIConnection final : public APIServerConnectionBase {
template<typename T>
static uint16_t fill_and_encode_entity_info(EntityBase *entity, T &msg, APIConnection *conn,
uint32_t remaining_size) {
return fill_and_encode_entity_info(entity, msg, &calc_size<T>, &proto_encode_msg<T>, conn, remaining_size);
return fill_and_encode_entity_info(entity, msg, &T::calc_size_msg, &T::encode_msg, conn, remaining_size);
}
// Non-template core — fills device_class, then delegates to fill_and_encode_entity_info
@@ -476,8 +458,8 @@ class APIConnection final : public APIServerConnectionBase {
static uint16_t fill_and_encode_entity_info_with_device_class(EntityBase *entity, T &msg,
StringRef &device_class_field, APIConnection *conn,
uint32_t remaining_size) {
return fill_and_encode_entity_info_with_device_class(entity, msg, device_class_field, &calc_size<T>,
&proto_encode_msg<T>, conn, remaining_size);
return fill_and_encode_entity_info_with_device_class(entity, msg, device_class_field, &T::calc_size_msg,
&T::encode_msg, conn, remaining_size);
}
#ifdef USE_VOICE_ASSISTANT
@@ -46,7 +46,13 @@ inline uint16_t ESPHOME_ALWAYS_INLINE APIConnection::encode_to_buffer(uint32_t c
return 0;
}
ProtoWriteBuffer buffer{&shared_buf, shared_buf.size() - calculated_size};
encode_fn(msg, buffer PROTO_ENCODE_DEBUG_INIT(&shared_buf));
uint8_t *end = encode_fn(msg, buffer PROTO_ENCODE_DEBUG_INIT(&shared_buf));
#ifdef ESPHOME_DEBUG_API
// A body that writes fewer bytes than calculate_size() promised would ship stale buffer bytes
proto_check_encode_end(end, shared_buf.data() + shared_buf.size());
#else
(void) end;
#endif
return total_calculated_size;
}
@@ -5,16 +5,13 @@
#include "esphome/components/noise/noise.h"
#include "esphome/core/application.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "proto.h"
#include <cstring>
#include <cinttypes>
#ifdef USE_ESP8266
#include <pgmspace.h>
#endif
namespace esphome::api {
using noise::noise_err_to_logstr;
@@ -26,11 +23,7 @@ static_assert(MAX_HANDSHAKE_SIZE == noise::MAX_HANDSHAKE_SIZE,
"api and noise component handshake size limits must match");
static const char *const TAG = "api.noise";
#ifdef USE_ESP8266
static constexpr char PROLOGUE_INIT[] PROGMEM = "NoiseAPIInit";
#else
static const char *const PROLOGUE_INIT = "NoiseAPIInit";
#endif
static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
@@ -72,11 +65,7 @@ APIError APINoiseFrameHelper::init() {
state_ = State::FAILED;
return APIError::OUT_OF_MEMORY;
}
#ifdef USE_ESP8266
memcpy_P(dst, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
#else
std::memcpy(dst, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
#endif
progmem_memcpy(dst, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
state_ = State::CLIENT_HELLO;
return APIError::OK;
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+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 {
+3 -5
View File
@@ -5,7 +5,6 @@
#include "api_connection.h"
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/controller_registry.h"
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
@@ -61,8 +60,6 @@ void APIServer::socket_failed_(const LogString *msg) {
}
void APIServer::setup() {
ControllerRegistry::register_controller(this);
#ifdef USE_API_NOISE
// Always reserve the slot: flash preferences are positional on esp8266, so
// a yaml key build must keep the layout of a runtime key build
@@ -455,8 +452,9 @@ void APIServer::send_homeassistant_action(const HomeassistantActionRequest &call
// Home Assistant subscribes to actions shortly *after* authenticating, so actions
// fired right at connection time (on_client_connected, on_time_sync, ...) can
// arrive before the subscription and are lost - warn instead of failing silently.
ESP_LOGW(TAG, "Home Assistant %s '%s' dropped; %s",
call.is_event ? LOG_STR_LITERAL("event") : LOG_STR_LITERAL("action"), call.service.c_str(),
ESP_LOGW(TAG, "Home Assistant %s '%.*s' dropped; %s",
call.is_event ? LOG_STR_LITERAL("event") : LOG_STR_LITERAL("action"),
static_cast<int>(call.service.size()), call.service.empty() ? "" : call.service.c_str(),
this->is_connected() ? LOG_STR_LITERAL("client has not subscribed to actions (yet)")
: LOG_STR_LITERAL("no client connected"));
}
+23 -24
View File
@@ -14,7 +14,7 @@
#include "esphome/components/socket/socket.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/controller.h"
#include "esphome/core/entity_includes.h"
#include "esphome/core/log.h"
#include "esphome/core/string_ref.h"
#ifdef USE_PROVISIONING
@@ -49,8 +49,7 @@ struct SavedNoisePsk {
bool load_saved_noise_psk(noise::psk_t &out);
#endif
class APIServer final : public Component,
public Controller
class APIServer final : public Component
#ifdef USE_CAMERA
,
public camera::CameraListener
@@ -94,58 +93,58 @@ class APIServer final : public Component,
void handle_disconnect(APIConnection *conn);
#ifdef USE_BINARY_SENSOR
void on_binary_sensor_update(binary_sensor::BinarySensor *obj) override;
void on_binary_sensor_update(binary_sensor::BinarySensor *obj);
#endif
#ifdef USE_COVER
void on_cover_update(cover::Cover *obj) override;
void on_cover_update(cover::Cover *obj);
#endif
#ifdef USE_FAN
void on_fan_update(fan::Fan *obj) override;
void on_fan_update(fan::Fan *obj);
#endif
#ifdef USE_LIGHT
void on_light_update(light::LightState *obj) override;
void on_light_update(light::LightState *obj);
#endif
#ifdef USE_SENSOR
void on_sensor_update(sensor::Sensor *obj) override;
void on_sensor_update(sensor::Sensor *obj);
#endif
#ifdef USE_SWITCH
void on_switch_update(switch_::Switch *obj) override;
void on_switch_update(switch_::Switch *obj);
#endif
#ifdef USE_TEXT_SENSOR
void on_text_sensor_update(text_sensor::TextSensor *obj) override;
void on_text_sensor_update(text_sensor::TextSensor *obj);
#endif
#ifdef USE_CLIMATE
void on_climate_update(climate::Climate *obj) override;
void on_climate_update(climate::Climate *obj);
#endif
#ifdef USE_NUMBER
void on_number_update(number::Number *obj) override;
void on_number_update(number::Number *obj);
#endif
#ifdef USE_DATETIME_DATE
void on_date_update(datetime::DateEntity *obj) override;
void on_date_update(datetime::DateEntity *obj);
#endif
#ifdef USE_DATETIME_TIME
void on_time_update(datetime::TimeEntity *obj) override;
void on_time_update(datetime::TimeEntity *obj);
#endif
#ifdef USE_DATETIME_DATETIME
void on_datetime_update(datetime::DateTimeEntity *obj) override;
void on_datetime_update(datetime::DateTimeEntity *obj);
#endif
#ifdef USE_TEXT
void on_text_update(text::Text *obj) override;
void on_text_update(text::Text *obj);
#endif
#ifdef USE_SELECT
void on_select_update(select::Select *obj) override;
void on_select_update(select::Select *obj);
#endif
#ifdef USE_LOCK
void on_lock_update(lock::Lock *obj) override;
void on_lock_update(lock::Lock *obj);
#endif
#ifdef USE_VALVE
void on_valve_update(valve::Valve *obj) override;
void on_valve_update(valve::Valve *obj);
#endif
#ifdef USE_MEDIA_PLAYER
void on_media_player_update(media_player::MediaPlayer *obj) override;
void on_media_player_update(media_player::MediaPlayer *obj);
#endif
#ifdef USE_WATER_HEATER
void on_water_heater_update(water_heater::WaterHeater *obj) override;
void on_water_heater_update(water_heater::WaterHeater *obj);
#endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
void send_homeassistant_action(const HomeassistantActionRequest &call);
@@ -188,13 +187,13 @@ class APIServer final : public Component,
#endif
#ifdef USE_ALARM_CONTROL_PANEL
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj);
#endif
#ifdef USE_EVENT
void on_event(event::Event *obj) override;
void on_event(event::Event *obj);
#endif
#ifdef USE_UPDATE
void on_update(update::UpdateEntity *obj) override;
void on_update(update::UpdateEntity *obj);
#endif
#ifdef USE_ZWAVE_PROXY
void on_zwave_proxy_request(const ZWaveProxyRequest &msg);
+66 -59
View File
@@ -195,6 +195,12 @@ void proto_check_bounds_failed(const uint8_t *pos, size_t bytes, const uint8_t *
ESP_LOGE(TAG, "Proto encode bounds check failed in %s: need %zu bytes, %td available", caller, bytes, end - pos);
abort();
}
void proto_check_encode_end(const uint8_t *end, const uint8_t *expected) {
if (end == expected)
return;
ESP_LOGE(TAG, "Proto encode ended %td bytes off the calculated size", end - expected);
abort();
}
void ProtoWriteBuffer::debug_check_bounds_(size_t bytes, const char *caller) {
if (this->pos_ + bytes > this->buffer_->data() + this->buffer_->size()) {
ESP_LOGE(TAG, "ProtoWriteBuffer bounds check failed in %s: bytes=%zu offset=%td buf_size=%zu", caller, bytes,
@@ -210,77 +216,78 @@ void ProtoWriteBuffer::debug_check_encode_size_(uint32_t field_id, uint32_t expe
#endif
void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
void ProtoDecodableMessage::decode_fields(void *msg, const uint8_t *buffer, size_t length, DecodeFieldFn field) {
const uint8_t *ptr = buffer;
const uint8_t *end = buffer + length;
while (ptr < end) {
// Parse field header - ptr < end guarantees len >= 1
// Single-byte varints dominate, so that case advances the cursor inline.
auto read_varint = [&](proto_varint_value_t &value) ESPHOME_ALWAYS_INLINE {
if (ptr == end)
return false;
if (*ptr < 0x80) [[likely]] {
value = *ptr++;
return true;
}
auto res = ProtoVarInt::parse_non_empty(ptr, end - ptr);
if (!res.has_value()) {
if (!res.has_value())
return false;
value = res.value;
ptr += res.consumed;
return true;
};
while (ptr < end) {
proto_varint_value_t tag_value;
if (!read_varint(tag_value)) {
ESP_LOGV(TAG, "Invalid field start at offset %ld", (long) (ptr - buffer));
return;
}
uint32_t tag = static_cast<uint32_t>(res.value);
uint32_t tag = static_cast<uint32_t>(tag_value);
uint32_t field_type = tag & WIRE_TYPE_MASK;
uint32_t field_id = tag >> 3;
ptr += res.consumed;
// Length-delimited fields move this past the length prefix
const uint8_t *data = ptr;
proto_varint_value_t scalar;
switch (field_type) {
case WIRE_TYPE_VARINT: { // VarInt
res = ProtoVarInt::parse(ptr, end - ptr);
if (!res.has_value()) {
ESP_LOGV(TAG, "Invalid VarInt at offset %ld", (long) (ptr - buffer));
return;
}
if (!this->decode_varint(field_id, res.value)) {
ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu64 "!", field_id,
static_cast<uint64_t>(res.value));
}
ptr += res.consumed;
break;
}
case WIRE_TYPE_LENGTH_DELIMITED: { // Length-delimited
res = ProtoVarInt::parse(ptr, end - ptr);
if (!res.has_value()) {
ESP_LOGV(TAG, "Invalid Length Delimited at offset %ld", (long) (ptr - buffer));
return;
}
uint32_t field_length = static_cast<uint32_t>(res.value);
ptr += res.consumed;
if (field_length > static_cast<size_t>(end - ptr)) {
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer));
return;
}
if (!this->decode_length(field_id, ProtoLengthDelimited(ptr, field_length))) {
ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id);
}
ptr += field_length;
break;
}
case WIRE_TYPE_FIXED32: { // 32-bit
if (end - ptr < 4) {
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
return;
}
uint32_t val;
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
// Protobuf fixed32 is little-endian — direct load on LE platforms
memcpy(&val, ptr, 4);
#else
val = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]);
#endif
if (!this->decode_32bit(field_id, Proto32Bit(val))) {
ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
}
ptr += 4;
break;
}
default:
ESP_LOGV(TAG, "Invalid field type %" PRIu32 " at offset %ld", field_type, (long) (ptr - buffer));
if (field_type == WIRE_TYPE_VARINT) [[likely]] {
if (!read_varint(scalar)) {
ESP_LOGV(TAG, "Invalid VarInt at offset %ld", (long) (ptr - buffer));
return;
}
} else {
switch (field_type) {
case WIRE_TYPE_LENGTH_DELIMITED: {
proto_varint_value_t length_value;
if (!read_varint(length_value)) {
ESP_LOGV(TAG, "Invalid Length Delimited at offset %ld", (long) (ptr - buffer));
return;
}
uint32_t field_length = static_cast<uint32_t>(length_value);
if (field_length > static_cast<size_t>(end - ptr)) {
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer));
return;
}
data = ptr;
scalar = field_length;
ptr += field_length;
break;
}
case WIRE_TYPE_FIXED32: {
if (end - ptr < 4) {
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
return;
}
// Byte loads instead of memcpy: ESP-IDF passes -fno-builtin-memcpy, which made this a call
scalar = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]);
ptr += 4;
break;
}
default:
ESP_LOGV(TAG, "Invalid field type %" PRIu32 " at offset %ld", field_type, (long) (ptr - buffer));
return;
}
}
field(msg, tag, data, scalar);
}
}
+243 -170
View File
@@ -10,6 +10,7 @@
#include <cassert>
#include <cstring>
#include <type_traits>
#include <vector>
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
@@ -170,40 +171,43 @@ class ProtoVarInt {
class ProtoMessage;
class ProtoSize;
class ProtoLengthDelimited {
/// Case label for decode_field(): the wire tag of a field, so a field that arrives with another wire
/// type matches no case.
constexpr uint32_t proto_tag(uint32_t field_id, uint32_t wire_type) { return (field_id << 3) | wire_type; }
/// One decoded field: the payload pointer and a scalar holding the varint or fixed32 value, or the
/// length of a length-delimited field. The wire type in the tag says which applies; accessors do not check.
class ProtoFieldValue {
public:
explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {}
std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); }
ProtoFieldValue(const uint8_t *data, proto_varint_value_t scalar) : data_(data), scalar_(scalar) {}
// Direct access to raw data without string allocation
const uint8_t *data() const { return this->value_; }
size_t size() const { return this->length_; }
proto_varint_value_t as_varint() const { return this->scalar_; }
// A bool is sent as 0 or 1, so the low word is enough and saves a second compare with 64 bit varints
bool as_bool() const { return static_cast<uint32_t>(this->scalar_) != 0; }
/// Decode the length-delimited data into a message instance.
// Length-delimited accessors
const uint8_t *data() const { return this->data_; }
size_t size() const { return static_cast<size_t>(this->scalar_); }
std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->data_), this->size()); }
/// Decode the length-delimited payload into a message instance.
/// Template preserves concrete type so decode() resolves statically.
template<typename T> void decode_to_message(T &msg) const;
template<typename T> void decode_to_message(T &msg) const { msg.decode(this->data_, this->size()); }
protected:
const uint8_t *const value_;
const size_t length_;
};
class Proto32Bit {
public:
explicit Proto32Bit(uint32_t value) : value_(value) {}
uint32_t as_fixed32() const { return this->value_; }
int32_t as_sfixed32() const { return static_cast<int32_t>(this->value_); }
// Fixed32 accessors
uint32_t as_fixed32() const { return static_cast<uint32_t>(this->scalar_); }
int32_t as_sfixed32() const { return static_cast<int32_t>(this->as_fixed32()); }
float as_float() const {
union {
uint32_t raw;
float value;
} s{};
s.raw = this->value_;
s.raw = this->as_fixed32();
return s.value;
}
protected:
const uint32_t value_;
private:
const uint8_t *data_;
proto_varint_value_t scalar_;
};
// NOTE: Proto64Bit class removed - wire type 1 (64-bit fixed) not supported
@@ -221,6 +225,9 @@ class Proto32Bit {
proto_check_bounds_failed(pos, n, proto_debug_end_, __builtin_FUNCTION()); \
} while (0)
void proto_check_bounds_failed(const uint8_t *pos, size_t bytes, const uint8_t *end, const char *caller);
/// Aborts unless an encode body ended exactly where calculate_size() promised. A plain check rather than
/// assert(), so NDEBUG cannot switch it off.
void proto_check_encode_end(const uint8_t *end, const uint8_t *expected);
#else
#define PROTO_ENCODE_DEBUG_PARAM
#define PROTO_ENCODE_DEBUG_ARG
@@ -252,7 +259,7 @@ class ProtoWriteBuffer {
*
* Following https://protobuf.dev/programming-guides/encoding/#structure
*/
void encode_field_raw(uint32_t field_id, uint32_t type) { this->encode_varint_raw((field_id << 3) | type); }
void encode_field_raw(uint32_t field_id, uint32_t type) { this->encode_varint_raw(proto_tag(field_id, type)); }
/// Single-pass encode for repeated submessage elements.
/// Thin template wrapper; all buffer work is in the non-template core.
template<typename T> void encode_sub_message(uint32_t field_id, const T &value);
@@ -287,19 +294,31 @@ class ProtoWriteBuffer {
uint8_t *pos_;
};
// A four byte unaligned store is a memcpy call on ESP-IDF (-fno-builtin-memcpy) and on ARM cores without
// unaligned access (Cortex-M0+, ARM9), so those targets share one outlined byte store helper per fixed32
// field. Elsewhere the write inlines to a single store, or on ESP8266 to a few stores that measured
// faster than a call, so it stays inline.
#if defined(USE_ESP32) || (defined(__arm__) && !defined(__ARM_FEATURE_UNALIGNED))
#define PROTO_OUTLINE_FOR_SIZE __attribute__((noinline))
#define PROTO_FIXED32_BYTE_STORES true
#else
#define PROTO_OUTLINE_FOR_SIZE inline
#define PROTO_FIXED32_BYTE_STORES false
#endif
// Varint encoding thresholds — used by both proto_encode_* free functions and ProtoSize.
constexpr uint32_t VARINT_MAX_1_BYTE = 1 << 7; // 128
constexpr uint32_t VARINT_MAX_2_BYTE = 1 << 14; // 16384
/// Static encode helpers for generated encode() functions.
/// Generated code hoists buffer.pos_ into a local uint8_t *__restrict__ pos,
/// then calls these methods which take pos by reference. No struct, no overhead.
/// For sub-messages, pos is synced back to buffer before the call and reloaded after.
/// Static encode helpers for the generated encode bodies. Each takes the write cursor by value and
/// returns it advanced, so outlined calls at -Os chain through the return register instead of a
/// stack slot. Helpers without a _force suffix skip fields holding the proto3 default.
class ProtoEncode {
public:
/// Write a multi-byte varint directly through a pos pointer.
template<typename T>
static inline void encode_varint_raw_loop(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, T value) {
[[nodiscard]] static inline uint8_t *encode_varint_raw_loop(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
T value) {
do {
PROTO_ENCODE_CHECK_BOUNDS(pos, 1);
*pos++ = static_cast<uint8_t>(value | 0x80);
@@ -307,48 +326,49 @@ class ProtoEncode {
} while (value > 0x7F);
PROTO_ENCODE_CHECK_BOUNDS(pos, 1);
*pos++ = static_cast<uint8_t>(value);
return pos;
}
static inline void ESPHOME_ALWAYS_INLINE encode_varint_raw(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t value) {
[[nodiscard]] static inline uint8_t *ESPHOME_ALWAYS_INLINE
encode_varint_raw(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint32_t value) {
if (value < VARINT_MAX_1_BYTE) [[likely]] {
PROTO_ENCODE_CHECK_BOUNDS(pos, 1);
*pos++ = static_cast<uint8_t>(value);
return;
return pos;
}
encode_varint_raw_loop(pos PROTO_ENCODE_DEBUG_ARG, value);
return encode_varint_raw_loop(pos PROTO_ENCODE_DEBUG_ARG, value);
}
/// Encode a varint that is expected to be 1-2 bytes (e.g. zigzag RSSI, small lengths).
static inline void ESPHOME_ALWAYS_INLINE encode_varint_raw_short(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t value) {
[[nodiscard]] static inline uint8_t *ESPHOME_ALWAYS_INLINE
encode_varint_raw_short(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint32_t value) {
if (value < VARINT_MAX_1_BYTE) [[likely]] {
PROTO_ENCODE_CHECK_BOUNDS(pos, 1);
*pos++ = static_cast<uint8_t>(value);
return;
return pos;
}
if (value < VARINT_MAX_2_BYTE) [[likely]] {
PROTO_ENCODE_CHECK_BOUNDS(pos, 2);
*pos++ = static_cast<uint8_t>(value | 0x80);
*pos++ = static_cast<uint8_t>(value >> 7);
return;
return pos;
}
encode_varint_raw_loop(pos PROTO_ENCODE_DEBUG_ARG, value);
return encode_varint_raw_loop(pos PROTO_ENCODE_DEBUG_ARG, value);
}
static inline void ESPHOME_ALWAYS_INLINE encode_varint_raw_64(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM,
uint64_t value) {
[[nodiscard]] static inline uint8_t *ESPHOME_ALWAYS_INLINE
encode_varint_raw_64(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint64_t value) {
if (value < VARINT_MAX_1_BYTE) [[likely]] {
PROTO_ENCODE_CHECK_BOUNDS(pos, 1);
*pos++ = static_cast<uint8_t>(value);
return;
return pos;
}
encode_varint_raw_loop(pos PROTO_ENCODE_DEBUG_ARG, value);
return encode_varint_raw_loop(pos PROTO_ENCODE_DEBUG_ARG, value);
}
/// Encode a 48-bit MAC address (stored in a uint64) as varint.
/// Real MAC addresses occupy the full 48 bits (OUI in upper 24), so the
/// fast path -- any non-zero bit in the top 6 of 48 -- emits exactly 7 bytes
/// with no per-byte branch. Falls back to the general loop otherwise.
/// Caller must guarantee value fits in 48 bits (checked in debug builds).
static inline void ESPHOME_ALWAYS_INLINE encode_varint_raw_48bit(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM,
uint64_t value) {
[[nodiscard]] static inline uint8_t *ESPHOME_ALWAYS_INLINE
encode_varint_raw_48bit(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint64_t value) {
#ifdef ESPHOME_DEBUG_API
assert(value < (1ULL << (MAC_ADDRESS_SIZE * 8)) && "encode_varint_raw_48bit: value exceeds 48 bits");
#endif
@@ -363,38 +383,39 @@ class ProtoEncode {
pos[4] = static_cast<uint8_t>((value >> 28) | 0x80);
pos[5] = static_cast<uint8_t>((value >> 35) | 0x80);
pos[6] = static_cast<uint8_t>(value >> 42);
pos += 7;
return;
return pos + 7;
}
encode_varint_raw_64(pos PROTO_ENCODE_DEBUG_ARG, value);
return encode_varint_raw_64(pos PROTO_ENCODE_DEBUG_ARG, value);
}
static inline void ESPHOME_ALWAYS_INLINE encode_field_raw(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, uint32_t type) {
encode_varint_raw(pos PROTO_ENCODE_DEBUG_ARG, (field_id << 3) | type);
[[nodiscard]] static inline uint8_t *ESPHOME_ALWAYS_INLINE
encode_field_raw(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, uint32_t type) {
return encode_varint_raw(pos PROTO_ENCODE_DEBUG_ARG, proto_tag(field_id, type));
}
/// Write a single precomputed tag byte. Tag must be < 128.
static inline void ESPHOME_ALWAYS_INLINE write_raw_byte(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM,
uint8_t b) {
[[nodiscard]] static inline uint8_t *ESPHOME_ALWAYS_INLINE
write_raw_byte(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint8_t b) {
PROTO_ENCODE_CHECK_BOUNDS(pos, 1);
*pos++ = b;
return pos;
}
/// Reserve one byte for later backpatch (e.g., sub-message length).
/// Advances pos past the reserved byte without writing a value.
static inline void ESPHOME_ALWAYS_INLINE reserve_byte(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM) {
[[nodiscard]] static inline uint8_t *ESPHOME_ALWAYS_INLINE
reserve_byte(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
PROTO_ENCODE_CHECK_BOUNDS(pos, 1);
pos++;
return pos + 1;
}
/// Write raw bytes to the buffer (no tag, no length prefix).
static inline void ESPHOME_ALWAYS_INLINE encode_raw(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM,
const void *data, size_t len) {
[[nodiscard]] static inline uint8_t *ESPHOME_ALWAYS_INLINE
encode_raw(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, const void *data, size_t len) {
PROTO_ENCODE_CHECK_BOUNDS(pos, len);
std::memcpy(pos, data, len);
pos += len;
return pos + len;
}
/// Encode tag + 1-byte length + raw string data. For strings with max_data_length < 128.
/// Tag must be a single-byte varint (< 128). Always encodes (no zero check).
static inline void encode_short_string_force(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint8_t tag,
const StringRef &ref) {
[[nodiscard]] static inline uint8_t *encode_short_string_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint8_t tag, const StringRef &ref) {
#ifdef ESPHOME_DEBUG_API
assert(ref.size() < 128 && "encode_short_string_force: string exceeds max_data_length < 128");
#endif
@@ -402,137 +423,191 @@ class ProtoEncode {
pos[0] = tag;
pos[1] = static_cast<uint8_t>(ref.size());
std::memcpy(pos + 2, ref.c_str(), ref.size());
pos += 2 + ref.size();
return pos + 2 + ref.size();
}
/// Write a precomputed tag byte + 32-bit value in one operation.
static inline void ESPHOME_ALWAYS_INLINE write_tag_and_fixed32(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM,
uint8_t tag, uint32_t value) {
/// Write a precomputed tag byte + 32-bit value. Outlined on embedded: one copy beats inline stores per field.
[[nodiscard]] static PROTO_OUTLINE_FOR_SIZE uint8_t *write_tag_and_fixed32(
uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint8_t tag, uint32_t value) {
PROTO_ENCODE_CHECK_BOUNDS(pos, 5);
pos[0] = tag;
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
std::memcpy(pos + 1, &value, 4);
#else
pos[1] = static_cast<uint8_t>(value & 0xFF);
pos[2] = static_cast<uint8_t>((value >> 8) & 0xFF);
pos[3] = static_cast<uint8_t>((value >> 16) & 0xFF);
pos[4] = static_cast<uint8_t>((value >> 24) & 0xFF);
#endif
pos += 5;
write_fixed32_le(pos + 1, value);
return pos + 5;
}
static inline void encode_string(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id,
const char *string, size_t len, bool force = false) {
if (len == 0 && !force)
return;
encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 2); // type 2: Length-delimited string
[[nodiscard]] static inline uint8_t *encode_string_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, const char *string, size_t len) {
pos = encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 2); // type 2: Length-delimited string
// NOLINTNEXTLINE(readability-inconsistent-ifelse-braces) -- false positive on [[likely]] attribute
if (len < VARINT_MAX_1_BYTE) [[likely]] {
PROTO_ENCODE_CHECK_BOUNDS(pos, 1 + len);
*pos++ = static_cast<uint8_t>(len);
} else {
encode_varint_raw_loop(pos PROTO_ENCODE_DEBUG_ARG, len);
pos = encode_varint_raw_loop(pos PROTO_ENCODE_DEBUG_ARG, len);
PROTO_ENCODE_CHECK_BOUNDS(pos, len);
}
std::memcpy(pos, string, len);
pos += len;
return pos + len;
}
static inline void encode_string(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id,
const std::string &value, bool force = false) {
encode_string(pos PROTO_ENCODE_DEBUG_ARG, field_id, value.data(), value.size(), force);
[[nodiscard]] static inline uint8_t *encode_string(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, const char *string, size_t len) {
if (len == 0)
return pos;
return encode_string_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, string, len);
}
static inline void encode_string(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id,
const StringRef &ref, bool force = false) {
encode_string(pos PROTO_ENCODE_DEBUG_ARG, field_id, ref.c_str(), ref.size(), force);
[[nodiscard]] static inline uint8_t *encode_string_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, const std::string &value) {
return encode_string_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, value.data(), value.size());
}
static inline void encode_bytes(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id,
const uint8_t *data, size_t len, bool force = false) {
encode_string(pos PROTO_ENCODE_DEBUG_ARG, field_id, reinterpret_cast<const char *>(data), len, force);
[[nodiscard]] static inline uint8_t *encode_string(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, const StringRef &ref) {
return encode_string(pos PROTO_ENCODE_DEBUG_ARG, field_id, ref.c_str(), ref.size());
}
static inline void encode_uint32(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id,
uint32_t value, bool force = false) {
if (value == 0 && !force)
return;
encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 0);
encode_varint_raw(pos PROTO_ENCODE_DEBUG_ARG, value);
[[nodiscard]] static inline uint8_t *encode_string_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, const StringRef &ref) {
return encode_string_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, ref.c_str(), ref.size());
}
static inline void encode_uint64(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id,
uint64_t value, bool force = false) {
if (value == 0 && !force)
return;
encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 0);
encode_varint_raw_64(pos PROTO_ENCODE_DEBUG_ARG, value);
[[nodiscard]] static inline uint8_t *encode_bytes(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, const uint8_t *data, size_t len) {
return encode_string(pos PROTO_ENCODE_DEBUG_ARG, field_id, reinterpret_cast<const char *>(data), len);
}
static inline void encode_bool(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, bool value,
bool force = false) {
if (!value && !force)
return;
encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 0);
[[nodiscard]] static inline uint8_t *encode_bytes_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, const uint8_t *data, size_t len) {
return encode_string_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, reinterpret_cast<const char *>(data), len);
}
[[nodiscard]] static inline uint8_t *encode_uint32_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, uint32_t value) {
pos = encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 0);
return encode_varint_raw(pos PROTO_ENCODE_DEBUG_ARG, value);
}
[[nodiscard]] static inline uint8_t *encode_uint32(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, uint32_t value) {
if (value == 0)
return pos;
return encode_uint32_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, value);
}
[[nodiscard]] static inline uint8_t *encode_uint64_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, uint64_t value) {
pos = encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 0);
return encode_varint_raw_64(pos PROTO_ENCODE_DEBUG_ARG, value);
}
[[nodiscard]] static inline uint8_t *encode_uint64(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, uint64_t value) {
if (value == 0)
return pos;
return encode_uint64_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, value);
}
[[nodiscard]] static inline uint8_t *encode_bool_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, bool value) {
pos = encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 0);
PROTO_ENCODE_CHECK_BOUNDS(pos, 1);
*pos++ = value ? 0x01 : 0x00;
return pos;
}
static inline void encode_fixed32(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id,
uint32_t value, bool force = false) {
if (value == 0 && !force)
return;
encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 5);
[[nodiscard]] static inline uint8_t *encode_bool(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, bool value) {
if (!value)
return pos;
return encode_bool_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, value);
}
/// Tag + fixed32 for multi-byte tags; single-byte tags use write_tag_and_fixed32.
[[nodiscard]] static PROTO_OUTLINE_FOR_SIZE uint8_t *encode_fixed32_force(
uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, uint32_t value) {
pos = encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 5);
PROTO_ENCODE_CHECK_BOUNDS(pos, 4);
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
std::memcpy(pos, &value, 4);
pos += 4;
#else
*pos++ = (value >> 0) & 0xFF;
*pos++ = (value >> 8) & 0xFF;
*pos++ = (value >> 16) & 0xFF;
*pos++ = (value >> 24) & 0xFF;
#endif
write_fixed32_le(pos, value);
return pos + 4;
}
[[nodiscard]] static inline uint8_t *encode_fixed32(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, uint32_t value) {
if (value == 0)
return pos;
return encode_fixed32_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, value);
}
// NOTE: Wire type 1 (64-bit fixed: double, fixed64, sfixed64) is intentionally
// not supported to reduce overhead on embedded systems. All ESPHome devices are
// 32-bit microcontrollers where 64-bit operations are expensive. If 64-bit support
// is needed in the future, the necessary encoding/decoding functions must be added.
static inline void encode_float(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, float value,
bool force = false) {
uint32_t raw = float_to_raw(value);
if (raw == 0 && !force)
return;
encode_fixed32(pos PROTO_ENCODE_DEBUG_ARG, field_id, raw);
[[nodiscard]] static inline uint8_t *encode_float(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, float value) {
return encode_fixed32(pos PROTO_ENCODE_DEBUG_ARG, field_id, float_to_raw(value));
}
static inline void encode_int32(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, int32_t value,
bool force = false) {
[[nodiscard]] static inline uint8_t *encode_float_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, float value) {
return encode_fixed32_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, float_to_raw(value));
}
[[nodiscard]] static inline uint8_t *encode_int32_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, int32_t value) {
if (value < 0) {
// negative int32 is always 10 byte long
encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, field_id, static_cast<uint64_t>(value), force);
return;
return encode_uint64_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, static_cast<uint64_t>(value));
}
encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, field_id, static_cast<uint32_t>(value), force);
return encode_uint32_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, static_cast<uint32_t>(value));
}
static inline void encode_int64(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, int64_t value,
bool force = false) {
encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, field_id, static_cast<uint64_t>(value), force);
[[nodiscard]] static inline uint8_t *encode_int32(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, int32_t value) {
if (value == 0)
return pos;
return encode_int32_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, value);
}
static inline void encode_sint32(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id,
int32_t value, bool force = false) {
encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, field_id, encode_zigzag32(value), force);
[[nodiscard]] static inline uint8_t *encode_int64(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, int64_t value) {
return encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, field_id, static_cast<uint64_t>(value));
}
static inline void encode_sint64(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id,
int64_t value, bool force = false) {
encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, field_id, encode_zigzag64(value), force);
[[nodiscard]] static inline uint8_t *encode_int64_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, int64_t value) {
return encode_uint64_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, static_cast<uint64_t>(value));
}
/// Sub-message encoding: sync pos to buffer, delegate, get pos from return value.
[[nodiscard]] static inline uint8_t *encode_sint32(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, int32_t value) {
return encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, field_id, encode_zigzag32(value));
}
[[nodiscard]] static inline uint8_t *encode_sint32_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, int32_t value) {
return encode_uint32_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, encode_zigzag32(value));
}
[[nodiscard]] static inline uint8_t *encode_sint64(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, int64_t value) {
return encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, field_id, encode_zigzag64(value));
}
[[nodiscard]] static inline uint8_t *encode_sint64_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, int64_t value) {
return encode_uint64_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, encode_zigzag64(value));
}
/// Sub-message encoding: sync pos to buffer, delegate, read the cursor back.
template<typename T>
static inline void encode_sub_message(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, ProtoWriteBuffer &buffer,
uint32_t field_id, const T &value) {
[[nodiscard]] static inline uint8_t *encode_sub_message(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
ProtoWriteBuffer &buffer, uint32_t field_id, const T &value) {
buffer.set_pos(pos);
buffer.encode_sub_message(field_id, value);
pos = buffer.get_pos();
return buffer.get_pos();
}
template<typename T>
static inline void encode_optional_sub_message(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM,
ProtoWriteBuffer &buffer, uint32_t field_id, const T &value) {
[[nodiscard]] static inline uint8_t *encode_optional_sub_message(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
ProtoWriteBuffer &buffer, uint32_t field_id,
const T &value) {
buffer.set_pos(pos);
buffer.encode_optional_sub_message(field_id, value);
pos = buffer.get_pos();
return buffer.get_pos();
}
private:
/// Unaligned little endian store of four bytes: byte stores where the outlined helper lives (ESP-IDF, ARM
/// without unaligned access), otherwise a memcpy the compiler folds into one store. Callers bounds check
/// and advance the cursor themselves.
static inline void ESPHOME_ALWAYS_INLINE write_fixed32_le(uint8_t *__restrict__ pos, uint32_t value) {
if constexpr (PROTO_FIXED32_BYTE_STORES) {
// Spelled out so the outlined helper does not itself become a memcpy call
pos[0] = static_cast<uint8_t>(value);
pos[1] = static_cast<uint8_t>(value >> 8);
pos[2] = static_cast<uint8_t>(value >> 16);
pos[3] = static_cast<uint8_t>(value >> 24);
} else {
const uint32_t le = convert_little_endian(value);
__builtin_memcpy(pos, &le, 4);
}
}
};
#undef PROTO_OUTLINE_FOR_SIZE
#undef PROTO_FIXED32_BYTE_STORES
#ifdef HAS_PROTO_MESSAGE_DUMP
/**
@@ -624,11 +699,12 @@ class DumpBuffer {
class ProtoMessage {
public:
// Non-virtual defaults for messages with no fields.
// Concrete message classes hide these with their own implementations.
// All call sites use templates to preserve the concrete type, so virtual
// dispatch is not needed. This eliminates per-message vtable entries for
// encode/calculate_size, saving ~1.3 KB of flash across all message types.
// Non-virtual defaults for messages with no fields; generated classes hide all four. The
// static encode_msg/calc_size_msg take const void * so &T::encode_msg needs no thunk.
static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) {
return buffer.get_pos();
}
static uint32_t calc_size_msg(const void *self) { return 0; }
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { return buffer.get_pos(); }
uint32_t calculate_size() const { return 0; }
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -648,7 +724,13 @@ class ProtoMessage {
// Base class for messages that support decoding
class ProtoDecodableMessage : public ProtoMessage {
public:
void decode(const uint8_t *buffer, size_t length);
/// Stores one decoded field into \p msg; generated per message type. \p scalar is the varint or
/// fixed32 value, or the length of the length-delimited payload at \p data. An unknown field or
/// wrong wire type matches no case and is skipped.
using DecodeFieldFn = void (*)(void *msg, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar);
/// Walk \p buffer and hand every field to \p field. The generated decode() passes the message's
/// own decode_field, so decodable messages carry no vtable.
static void decode_fields(void *msg, const uint8_t *buffer, size_t length, DecodeFieldFn field);
/**
* Count occurrences of a repeated field in a protobuf buffer.
@@ -660,14 +742,15 @@ class ProtoDecodableMessage : public ProtoMessage {
* @return Number of times the field appears in the buffer
*/
static uint32_t count_repeated_field(const uint8_t *buffer, size_t length, uint32_t target_field_id);
protected:
~ProtoDecodableMessage() = default;
virtual bool decode_varint(uint32_t field_id, proto_varint_value_t value) { return false; }
virtual bool decode_length(uint32_t field_id, ProtoLengthDelimited value) { return false; }
virtual bool decode_32bit(uint32_t field_id, Proto32Bit value) { return false; }
// NOTE: decode_64bit removed - wire type 1 not supported
// The destructor stays accessible on purpose: the generated messages are aggregates that brace
// initialise sub message members, which copies a base temporary. That trades away the compile time
// guard against deleting through this type; messages are stack locals and never owned through a base
// pointer. ProtoMessage keeps its guard for the dump builds.
};
#ifndef HAS_PROTO_MESSAGE_DUMP
// decode() passes decode_field explicitly, so nothing here may add a vtable
static_assert(!std::is_polymorphic_v<ProtoDecodableMessage>, "decodable messages carry no vtable");
#endif
class ProtoSize {
public:
@@ -792,7 +875,7 @@ class ProtoSize {
* @return The number of bytes needed to encode the field ID and wire type
*/
static constexpr uint32_t field(uint32_t field_id, uint32_t type) {
uint32_t tag = (field_id << 3) | (type & WIRE_TYPE_MASK);
uint32_t tag = proto_tag(field_id, type & WIRE_TYPE_MASK);
return varint(tag);
}
@@ -876,24 +959,14 @@ class ProtoSize {
// Implementation of methods that depend on ProtoSize being fully defined
// Encode thunk — converts void* back to concrete type for direct encode() call
template<typename T> uint8_t *proto_encode_msg(const void *msg, ProtoWriteBuffer &buf PROTO_ENCODE_DEBUG_PARAM) {
return static_cast<const T *>(msg)->encode(buf PROTO_ENCODE_DEBUG_ARG);
}
// Thin template wrapper; delegates to non-template core in proto.cpp.
template<typename T> inline void ProtoWriteBuffer::encode_sub_message(uint32_t field_id, const T &value) {
this->encode_sub_message(field_id, &value, &proto_encode_msg<T>);
this->encode_sub_message(field_id, &value, &T::encode_msg);
}
// Thin template wrapper; delegates to non-template core.
template<typename T> inline void ProtoWriteBuffer::encode_optional_sub_message(uint32_t field_id, const T &value) {
this->encode_optional_sub_message(field_id, value.calculate_size(), &value, &proto_encode_msg<T>);
}
// Template decode_to_message - preserves concrete type so decode() resolves statically
template<typename T> void ProtoLengthDelimited::decode_to_message(T &msg) const {
msg.decode(this->value_, this->length_);
this->encode_optional_sub_message(field_id, T::calc_size_msg(&value), &value, &T::encode_msg);
}
template<typename T> const char *proto_enum_to_string(T value);
-1
View File
@@ -4,7 +4,6 @@
#ifdef USE_API
#include "esphome/core/component.h"
#include "esphome/core/component_iterator.h"
#include "esphome/core/controller.h"
namespace esphome::api {
class APIConnection;
+1 -2
View File
@@ -1,5 +1,6 @@
import esphome.codegen as cg
from esphome.components import i2c, sensor
from esphome.components.const import UNIT_COUNTS
import esphome.config_validation as cv
from esphome.const import (
CONF_CLEAR,
@@ -33,8 +34,6 @@ CONF_F7 = "f7"
CONF_F8 = "f8"
CONF_NIR = "nir"
UNIT_COUNTS = "#"
AS7341_GAIN = as7341_ns.enum("AS7341Gain")
GAIN_OPTIONS = {
"X0.5": AS7341_GAIN.AS7341_GAIN_0_5X,
@@ -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
+28 -77
View File
@@ -4,8 +4,6 @@ import esphome.codegen as cg
from esphome.components import i2c
import esphome.config_validation as cv
from esphome.const import CONF_FREQUENCY, CONF_ID
from esphome.core import ID
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.types import ConfigType
CODEOWNERS = ["@X-Ryl669"]
@@ -80,30 +78,21 @@ async def to_code(config: ConfigType) -> None:
# Actions
AT581XResetAction = at581x_ns.class_("AT581XResetAction", automation.Action)
AT581XSettingsAction = at581x_ns.class_("AT581XSettingsAction", automation.Action)
@automation.register_action(
automation.register_apply_action(
"at581x.reset",
AT581XResetAction,
maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(AT581XComponent),
}
),
synchronous=True,
maybe_simple_id({cv.Required(CONF_ID): cv.use_id(AT581XComponent)}),
automation.ApplyCall("reset_hardware_frontend()"),
)
async def at581x_reset_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])
return var
def _megahertz(value: float) -> int:
return int(value / 1000000)
def _microamps(value: float) -> int:
return int(value * 1000000)
RADAR_SETTINGS_SCHEMA = cv.Schema(
@@ -111,7 +100,7 @@ RADAR_SETTINGS_SCHEMA = cv.Schema(
cv.Required(CONF_ID): cv.use_id(AT581XComponent),
cv.Optional(CONF_HW_FRONTEND_RESET): cv.templatable(cv.boolean),
cv.Optional(CONF_FREQUENCY, default="5800MHz"): cv.templatable(
cv.All(cv.frequency, cv.one_of(*RADAR_ALLOWED_FREQ))
cv.All(cv.frequency, cv.one_of(*RADAR_ALLOWED_FREQ), _megahertz)
),
cv.Optional(CONF_SENSING_DISTANCE, default=823): cv.templatable(
cv.int_range(min=0, max=1023)
@@ -123,7 +112,7 @@ RADAR_SETTINGS_SCHEMA = cv.Schema(
)
),
cv.Optional(CONF_POWER_CONSUMPTION, default="70uA"): cv.templatable(
cv.All(cv.current, cv.one_of(*RADAR_ALLOWED_CUR_CONSUMPTION))
cv.All(cv.current, cv.one_of(*RADAR_ALLOWED_CUR_CONSUMPTION), _microamps)
),
cv.Optional(CONF_PROTECT_TIME, default="1000ms"): cv.templatable(
cv.All(
@@ -165,60 +154,22 @@ RADAR_SETTINGS_SCHEMA = cv.Schema(
)
@automation.register_action(
# i2c_write_config() must follow the setters: it flushes the staged values.
automation.register_apply_action(
"at581x.settings",
AT581XSettingsAction,
RADAR_SETTINGS_SCHEMA,
synchronous=True,
automation.ApplyField(CONF_FREQUENCY, "set_frequency", cg.int_),
automation.ApplyField(CONF_SENSING_DISTANCE, "set_sensing_distance", cg.int_),
automation.ApplyField(
CONF_POWERON_SELFCHECK_TIME, "set_poweron_selfcheck_time", cg.int_
),
automation.ApplyField(CONF_POWER_CONSUMPTION, "set_power_consumption", cg.int_),
automation.ApplyField(CONF_PROTECT_TIME, "set_protect_time", cg.int_),
automation.ApplyField(CONF_TRIGGER_BASE, "set_trigger_base", cg.int_),
automation.ApplyField(CONF_TRIGGER_KEEP, "set_trigger_keep", cg.int_),
automation.ApplyField(CONF_STAGE_GAIN, "set_stage_gain", cg.int_),
automation.ApplyCall("i2c_write_config()"),
automation.ApplyField(
CONF_HW_FRONTEND_RESET, "reset_hardware_frontend_if", cg.bool_
),
)
async def at581x_settings_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])
# Radar configuration
if (frontend_reset := config.get(CONF_HW_FRONTEND_RESET)) is not None:
template_ = await cg.templatable(frontend_reset, args, cg.int8)
cg.add(var.set_hw_frontend_reset(template_))
if (freq := config.get(CONF_FREQUENCY)) is not None:
if not cg.is_template(freq):
freq = int(freq / 1000000)
template_ = await cg.templatable(freq, args, cg.int_)
cg.add(var.set_frequency(template_))
if (sens_dist := config.get(CONF_SENSING_DISTANCE)) is not None:
template_ = await cg.templatable(sens_dist, args, cg.int_)
cg.add(var.set_sensing_distance(template_))
if (selfcheck := config.get(CONF_POWERON_SELFCHECK_TIME)) is not None:
template_ = await cg.templatable(selfcheck, args, cg.int_)
cg.add(var.set_poweron_selfcheck_time(template_))
if (protect := config.get(CONF_PROTECT_TIME)) is not None:
template_ = await cg.templatable(protect, args, cg.int_)
cg.add(var.set_protect_time(template_))
if (trig_base := config.get(CONF_TRIGGER_BASE)) is not None:
template_ = await cg.templatable(trig_base, args, cg.int_)
cg.add(var.set_trigger_base(template_))
if (trig_keep := config.get(CONF_TRIGGER_KEEP)) is not None:
template_ = await cg.templatable(trig_keep, args, cg.int_)
cg.add(var.set_trigger_keep(template_))
if (stage_gain := config.get(CONF_STAGE_GAIN)) is not None:
template_ = await cg.templatable(stage_gain, args, cg.int_)
cg.add(var.set_stage_gain(template_))
if (power := config.get(CONF_POWER_CONSUMPTION)) is not None:
if not cg.is_template(power):
power = int(power * 1000000)
template_ = await cg.templatable(power, args, cg.int_)
cg.add(var.set_power_consumption(template_))
return var
+4
View File
@@ -38,6 +38,10 @@ class AT581XComponent final : public Component, public i2c::I2CDevice {
bool i2c_write_config();
bool reset_hardware_frontend();
void reset_hardware_frontend_if(bool reset) {
if (reset)
this->reset_hardware_frontend();
}
bool i2c_write_reg(uint8_t addr, uint8_t data);
bool i2c_write_reg(uint8_t addr, uint32_t data);
bool i2c_write_reg(uint8_t addr, uint16_t data);
-69
View File
@@ -1,69 +0,0 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/core/helpers.h"
#include "at581x.h"
namespace esphome::at581x {
template<typename... Ts> class AT581XResetAction final : public Action<Ts...>, public Parented<AT581XComponent> {
public:
void play(const Ts &...x) { this->parent_->reset_hardware_frontend(); }
};
template<typename... Ts> class AT581XSettingsAction final : public Action<Ts...>, public Parented<AT581XComponent> {
public:
TEMPLATABLE_VALUE(int8_t, hw_frontend_reset)
TEMPLATABLE_VALUE(int, frequency)
TEMPLATABLE_VALUE(int, sensing_distance)
TEMPLATABLE_VALUE(int, poweron_selfcheck_time)
TEMPLATABLE_VALUE(int, power_consumption)
TEMPLATABLE_VALUE(int, protect_time)
TEMPLATABLE_VALUE(int, trigger_base)
TEMPLATABLE_VALUE(int, trigger_keep)
TEMPLATABLE_VALUE(int, stage_gain)
void play(const Ts &...x) {
if (this->frequency_.has_value()) {
int v = this->frequency_.value(x...);
this->parent_->set_frequency(v);
}
if (this->sensing_distance_.has_value()) {
int v = this->sensing_distance_.value(x...);
this->parent_->set_sensing_distance(v);
}
if (this->poweron_selfcheck_time_.has_value()) {
int v = this->poweron_selfcheck_time_.value(x...);
this->parent_->set_poweron_selfcheck_time(v);
}
if (this->power_consumption_.has_value()) {
int v = this->power_consumption_.value(x...);
this->parent_->set_power_consumption(v);
}
if (this->protect_time_.has_value()) {
int v = this->protect_time_.value(x...);
this->parent_->set_protect_time(v);
}
if (this->trigger_base_.has_value()) {
int v = this->trigger_base_.value(x...);
this->parent_->set_trigger_base(v);
}
if (this->trigger_keep_.has_value()) {
int v = this->trigger_keep_.value(x...);
this->parent_->set_trigger_keep(v);
}
if (this->stage_gain_.has_value()) {
int v = this->stage_gain_.value(x...);
this->parent_->set_stage_gain(v);
}
// This actually perform all the modification on the system
this->parent_->i2c_write_config();
if (this->hw_frontend_reset_.has_value() && this->hw_frontend_reset_.value(x...) == true) {
this->parent_->reset_hardware_frontend();
}
}
};
} // namespace esphome::at581x
+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
+11 -38
View File
@@ -2,9 +2,8 @@ from esphome import automation
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_VOLUME
from esphome.core import ID, CoroPriority, coroutine_with_priority
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.const import CONF_VOLUME
from esphome.core import CoroPriority, coroutine_with_priority
from esphome.types import ConfigType
CODEOWNERS = ["@kbx81"]
@@ -13,10 +12,6 @@ IS_PLATFORM_COMPONENT = True
audio_dac_ns = cg.esphome_ns.namespace("audio_dac")
AudioDac = audio_dac_ns.class_("AudioDac")
MuteOffAction = audio_dac_ns.class_("MuteOffAction", automation.Action)
MuteOnAction = audio_dac_ns.class_("MuteOnAction", automation.Action)
SetVolumeAction = audio_dac_ns.class_("SetVolumeAction", automation.Action)
MUTE_ACTION_SCHEMA = maybe_simple_id(
{
@@ -33,41 +28,19 @@ SET_VOLUME_ACTION_SCHEMA = cv.maybe_simple_value(
)
@automation.register_action(
"audio_dac.mute_off", MuteOffAction, MUTE_ACTION_SCHEMA, synchronous=True
)
@automation.register_action(
"audio_dac.mute_on", MuteOnAction, MUTE_ACTION_SCHEMA, synchronous=True
)
async def audio_dac_mute_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
for _name, _call in (
("audio_dac.mute_off", "set_mute_off()"),
("audio_dac.mute_on", "set_mute_on()"),
):
automation.register_apply_action(
_name, MUTE_ACTION_SCHEMA, automation.ApplyCall(_call)
)
@automation.register_action(
automation.register_apply_action(
"audio_dac.set_volume",
SetVolumeAction,
SET_VOLUME_ACTION_SCHEMA,
synchronous=True,
automation.ApplyField(CONF_VOLUME, "set_volume", cg.float_),
)
async def audio_dac_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_VOLUME), args, cg.float_)
cg.add(var.set_volume(template_))
return var
@coroutine_with_priority(CoroPriority.CORE)
-41
View File
@@ -1,41 +0,0 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "audio_dac.h"
namespace esphome::audio_dac {
template<typename... Ts> class MuteOffAction final : public Action<Ts...> {
public:
explicit MuteOffAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
void play(const Ts &...x) override { this->audio_dac_->set_mute_off(); }
protected:
AudioDac *audio_dac_;
};
template<typename... Ts> class MuteOnAction final : public Action<Ts...> {
public:
explicit MuteOnAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
void play(const Ts &...x) override { this->audio_dac_->set_mute_on(); }
protected:
AudioDac *audio_dac_;
};
template<typename... Ts> class SetVolumeAction final : public Action<Ts...> {
public:
explicit SetVolumeAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
TEMPLATABLE_VALUE(float, volume)
void play(const Ts &...x) override { this->audio_dac_->set_volume(this->volume_.value(x...)); }
protected:
AudioDac *audio_dac_;
};
} // namespace esphome::audio_dac
+7 -28
View File
@@ -1,7 +1,7 @@
from logging import getLogger
from esphome import automation, core
from esphome.automation import Condition, maybe_simple_id
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
from esphome.components import mqtt, web_server, zigbee
from esphome.components.const import CONF_ON_STATE_CHANGE
@@ -134,15 +134,6 @@ MultiClickTriggerBase = binary_sensor_ns.class_(
MultiClickTrigger = binary_sensor_ns.class_("MultiClickTrigger", MultiClickTriggerBase)
MultiClickTriggerEvent = binary_sensor_ns.struct("MultiClickTriggerEvent")
BinarySensorPublishAction = binary_sensor_ns.class_(
"BinarySensorPublishAction", automation.Action
)
BinarySensorInvalidateAction = binary_sensor_ns.class_(
"BinarySensorInvalidateAction", automation.Action
)
# Condition
BinarySensorCondition = binary_sensor_ns.class_("BinarySensorCondition", Condition)
# Filters
Filter = binary_sensor_ns.class_("Filter")
@@ -648,20 +639,12 @@ BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id(
)
@automation.register_condition(
"binary_sensor.is_on", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA
automation.register_apply_condition(
"binary_sensor.is_on", BINARY_SENSOR_CONDITION_SCHEMA, "state"
)
async def binary_sensor_is_on_to_code(config, condition_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren, True)
@automation.register_condition(
"binary_sensor.is_off", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA
automation.register_apply_condition(
"binary_sensor.is_off", BINARY_SENSOR_CONDITION_SCHEMA, "state == false"
)
async def binary_sensor_is_off_to_code(config, condition_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren, False)
@coroutine_with_priority(CoroPriority.CORE)
@@ -669,20 +652,16 @@ async def to_code(config):
cg.add_global(binary_sensor_ns.using)
@automation.register_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()"),
)
async def binary_sensor_invalidate_state_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
# automation.cpp only implements the click/double_click/multi_click triggers
@@ -18,26 +18,6 @@ struct MultiClickTriggerEvent {
uint32_t max_length;
};
class PressTrigger final : public Trigger<> {
public:
explicit PressTrigger(BinarySensor *parent) {
parent->add_on_state_callback([this](bool state) {
if (state)
this->trigger();
});
}
};
class ReleaseTrigger final : public Trigger<> {
public:
explicit ReleaseTrigger(BinarySensor *parent) {
parent->add_on_state_callback([this](bool state) {
if (!state)
this->trigger();
});
}
};
bool match_interval(uint32_t min_length, uint32_t max_length, uint32_t length);
class ClickTrigger final : public Trigger<> {
@@ -140,53 +120,4 @@ template<size_t N> class MultiClickTrigger final : public MultiClickTriggerBase
std::array<MultiClickTriggerEvent, N> timing_storage_{};
};
class StateTrigger final : public Trigger<bool> {
public:
explicit StateTrigger(BinarySensor *parent) {
parent->add_on_state_callback([this](bool state) { this->trigger(state); });
}
};
class StateChangeTrigger final : public Trigger<optional<bool>, optional<bool> > {
public:
explicit StateChangeTrigger(BinarySensor *parent) {
parent->add_full_state_callback(
[this](optional<bool> old_state, optional<bool> state) { this->trigger(old_state, state); });
}
};
template<typename... Ts> class BinarySensorCondition final : public Condition<Ts...> {
public:
BinarySensorCondition(BinarySensor *parent, bool state) : parent_(parent), state_(state) {}
bool check(const Ts &...x) override { return this->parent_->state == this->state_; }
protected:
BinarySensor *parent_;
bool state_;
};
template<typename... Ts> class BinarySensorPublishAction final : public Action<Ts...> {
public:
explicit BinarySensorPublishAction(BinarySensor *sensor) : sensor_(sensor) {}
TEMPLATABLE_VALUE(bool, state)
void play(const Ts &...x) override {
auto val = this->state_.value(x...);
this->sensor_->publish_state(val);
}
protected:
BinarySensor *sensor_;
};
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
@@ -53,7 +53,6 @@ BK72xxBLETracker = bk72xx_ble_tracker_ns.class_(
)
StartScanAction = bk72xx_ble_tracker_ns.class_("StartScanAction", automation.Action)
StopScanAction = bk72xx_ble_tracker_ns.class_("StopScanAction", automation.Action)
ESPBTAdvertiseTrigger = ble_automation.ESPBTAdvertiseTrigger
BLEServiceDataAdvertiseTrigger = ble_automation.BLEServiceDataAdvertiseTrigger
@@ -121,9 +120,8 @@ async def start_scan_action_to_code(
return var
@automation.register_action(
automation.register_apply_action(
"bk72xx_ble_tracker.stop_scan",
StopScanAction,
automation.maybe_simple_id(
cv.Schema(
{
@@ -131,17 +129,8 @@ async def start_scan_action_to_code(
}
)
),
synchronous=True,
automation.ApplyCall("stop_scan()"),
)
async def stop_scan_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: list,
) -> cg.MockObj:
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
async def to_code(config: ConfigType) -> None:
@@ -38,11 +38,6 @@ template<typename... Ts> class StartScanAction final : public Action<Ts...>, pub
}
};
template<typename... Ts> class StopScanAction final : public Action<Ts...>, public Parented<BK72xxBLETracker> {
public:
void play(const Ts &...x) override { this->parent_->stop_scan(); }
};
} // namespace esphome::bk72xx_ble_tracker
#endif // USE_LIBRETINY
+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 -15
View File
@@ -32,8 +32,6 @@ from esphome.const import (
UNIT_VOLT,
UNIT_WATT,
)
from esphome.core import ID
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.types import ConfigType
# Import ICONS not included in esphome's const.py, from the local components const.py
@@ -45,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(
@@ -138,25 +135,15 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
)
@automation.register_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()"),
)
async def reset_energy_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])
return var
async def to_code(config: ConfigType) -> None:
+3 -12
View File
@@ -207,26 +207,20 @@ BLE_REMOVE_BOND_ACTION_SCHEMA = cv.Schema(
)
@automation.register_action(
automation.register_simple_action(
"ble_client.disconnect",
BLEDisconnectAction,
BLE_CONNECT_ACTION_SCHEMA,
synchronous=False,
)
async def ble_disconnect_to_code(config, action_id, template_arg, args):
parent = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, parent)
@automation.register_action(
automation.register_simple_action(
"ble_client.connect",
BLEConnectAction,
BLE_CONNECT_ACTION_SCHEMA,
synchronous=False,
)
async def ble_connect_to_code(config, action_id, template_arg, args):
parent = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, parent)
@automation.register_action(
@@ -328,15 +322,12 @@ async def passkey_reply_to_code(config, action_id, template_arg, args):
return var
@automation.register_action(
automation.register_simple_action(
"ble_client.remove_bond",
BLERemoveBondAction,
BLE_REMOVE_BOND_ACTION_SCHEMA,
synchronous=True,
)
async def remove_bond_to_code(config, action_id, template_arg, args):
parent = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, parent)
async def to_code(config):
-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 -58
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,70 +23,30 @@ CONFIG_SCHEMA = (
)
@automation.register_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),
}
)
async def bm8563_write_time_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])
return var
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,
automation.ApplyField(CONF_DURATION, "start_timer", cg.uint32),
)
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_action(
"bm8563.read_time",
ReadAction,
automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(BM8563),
}
),
synchronous=True,
)
async def bm8563_read_time_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])
return var
async def to_code(config: ConfigType) -> None:
+4 -14
View File
@@ -16,14 +16,14 @@ from esphome.const import (
DEVICE_CLASS_RESTART,
DEVICE_CLASS_UPDATE,
)
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import (
entity_duplicate_validator,
queue_entity_register,
setup_device_class,
setup_entity,
)
from esphome.cpp_generator import MockObj, MockObjClass, TemplateArgsType
from esphome.cpp_generator import MockObj, MockObjClass
from esphome.types import ConfigType, SafeExpType
CODEOWNERS = ["@esphome/core"]
@@ -40,8 +40,6 @@ button_ns = cg.esphome_ns.namespace("button")
Button = button_ns.class_("Button", cg.EntityBase)
ButtonPtr = Button.operator("ptr")
PressAction = button_ns.class_("PressAction", automation.Action)
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
@@ -123,17 +121,9 @@ BUTTON_PRESS_SCHEMA = maybe_simple_id(
)
@automation.register_action(
"button.press", PressAction, BUTTON_PRESS_SCHEMA, synchronous=True
automation.register_apply_action(
"button.press", BUTTON_PRESS_SCHEMA, automation.ApplyCall("press()")
)
async def button_press_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@coroutine_with_priority(CoroPriority.CORE)
-26
View File
@@ -1,26 +0,0 @@
#pragma once
#include "esphome/components/button/button.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
namespace esphome::button {
template<typename... Ts> class PressAction final : public Action<Ts...> {
public:
explicit PressAction(Button *button) : button_(button) {}
void play(const Ts &...x) override { this->button_->press(); }
protected:
Button *button_;
};
class ButtonPressTrigger final : public Trigger<> {
public:
ButtonPressTrigger(Button *button) {
button->add_on_press_callback([this]() { this->trigger(); });
}
};
} // namespace esphome::button
+41 -102
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,22 +360,15 @@ CC1101_ACTION_SCHEMA = cv.Schema(
)
@automation.register_action(
"cc1101.begin_tx", BeginTxAction, CC1101_ACTION_SCHEMA, synchronous=True
)
@automation.register_action(
"cc1101.begin_rx", BeginRxAction, CC1101_ACTION_SCHEMA, synchronous=True
)
@automation.register_action(
"cc1101.reset", ResetAction, CC1101_ACTION_SCHEMA, synchronous=True
)
@automation.register_action(
"cc1101.set_idle", SetIdleAction, CC1101_ACTION_SCHEMA, synchronous=True
)
async def cc1101_action_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
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):
@@ -424,91 +413,41 @@ async def send_packet_action_to_code(config, action_id, template_arg, args):
return var
# Setter action definitions: (setter_name, validator, template_type, enum_map)
_SETTER_ACTIONS = [
(
"set_frequency",
cv.All(cv.frequency, cv.float_range(min=300.0e6, max=928.0e6)),
float,
None,
),
("set_output_power", cv.float_range(min=-30.0, max=11.0), float, None),
("set_modulation_type", cv.enum(MODULATION, upper=False), Modulation, MODULATION),
("set_symbol_rate", cv.float_range(min=600, max=500000), float, None),
(
"set_rx_attenuation",
cv.enum(RX_ATTENUATION, upper=False),
RxAttenuation,
RX_ATTENUATION,
),
("set_dc_blocking_filter", cv.boolean, bool, None),
("set_manchester", cv.boolean, bool, None),
(
"set_filter_bandwidth",
cv.All(cv.frequency, cv.float_range(min=58000, max=812000)),
float,
None,
),
(
"set_fsk_deviation",
cv.All(cv.frequency, cv.float_range(min=1500, max=381000)),
float,
None,
),
("set_msk_deviation", cv.int_range(min=1, max=8), cg.uint8, None),
("set_channel", cv.uint8_t, cg.uint8, None),
(
"set_channel_spacing",
cv.All(cv.frequency, cv.float_range(min=25000, max=405000)),
float,
None,
),
(
"set_if_frequency",
cv.All(cv.frequency, cv.float_range(min=25000, max=788000)),
float,
None,
),
]
_CONFIG_VALIDATORS = {opt.schema: validator for opt, validator in CONFIG_MAP.items()}
# Each key has a set_<key> action validated exactly like the config option.
_SETTER_ACTIONS = (
(CONF_FREQUENCY, cg.float_),
(CONF_OUTPUT_POWER, cg.float_),
(CONF_MODULATION_TYPE, Modulation),
(CONF_SYMBOL_RATE, cg.float_),
(CONF_RX_ATTENUATION, RxAttenuation),
(CONF_DC_BLOCKING_FILTER, cg.bool_),
(CONF_MANCHESTER, cg.bool_),
(CONF_FILTER_BANDWIDTH, cg.float_),
(CONF_FSK_DEVIATION, cg.float_),
(CONF_MSK_DEVIATION, cg.uint8),
(CONF_CHANNEL, cg.uint8),
(CONF_CHANNEL_SPACING, cg.float_),
(CONF_IF_FREQUENCY, cg.float_),
)
def _register_setter_actions():
for setter_name, validator, templ_type, enum_map in _SETTER_ACTIONS:
class_name = (
"".join(word.capitalize() for word in setter_name.split("_")) + "Action"
def _register_setter_actions() -> None:
for conf_key, value_type in _SETTER_ACTIONS:
automation.register_apply_action(
f"cc1101.set_{conf_key}",
cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(CC1101Component),
cv.Required(CONF_VALUE): cv.templatable(
_CONFIG_VALIDATORS[conf_key]
),
},
key=CONF_VALUE,
),
automation.ApplyField(CONF_VALUE, f"set_{conf_key}", value_type),
)
action_cls = ns.class_(
class_name, automation.Action, cg.Parented.template(CC1101Component)
)
schema = cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(CC1101Component),
cv.Required(CONF_VALUE): cv.templatable(validator),
},
key=CONF_VALUE,
)
async def _setter_action_to_code(
config,
action_id,
template_arg,
args,
_setter=setter_name,
_type=templ_type,
_map=enum_map,
):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
data = config[CONF_VALUE]
if _map and not cg.is_template(data):
data = _map[data]
templ_ = await cg.templatable(data, args, _type)
cg.add(getattr(var, _setter)(templ_))
return var
automation.register_action(
f"cc1101.{setter_name}", action_cls, schema, synchronous=True
)(_setter_action_to_code)
_register_setter_actions()
-99
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; }
@@ -174,83 +154,4 @@ template<typename... Ts> class SendPacketAction final : public Action<Ts...>, pu
size_t data_static_len_{0};
};
template<typename... Ts> class SetSymbolRateAction final : public Action<Ts...>, public Parented<CC1101Component> {
public:
TEMPLATABLE_VALUE(float, symbol_rate)
void play(const Ts &...x) override { this->parent_->set_symbol_rate(this->symbol_rate_.value(x...)); }
};
template<typename... Ts> class SetFrequencyAction final : public Action<Ts...>, public Parented<CC1101Component> {
public:
TEMPLATABLE_VALUE(float, frequency)
void play(const Ts &...x) override { this->parent_->set_frequency(this->frequency_.value(x...)); }
};
template<typename... Ts> class SetOutputPowerAction final : public Action<Ts...>, public Parented<CC1101Component> {
public:
TEMPLATABLE_VALUE(float, output_power)
void play(const Ts &...x) override { this->parent_->set_output_power(this->output_power_.value(x...)); }
};
template<typename... Ts> class SetModulationTypeAction final : public Action<Ts...>, public Parented<CC1101Component> {
public:
TEMPLATABLE_VALUE(Modulation, modulation_type)
void play(const Ts &...x) override { this->parent_->set_modulation_type(this->modulation_type_.value(x...)); }
};
template<typename... Ts> class SetRxAttenuationAction final : public Action<Ts...>, public Parented<CC1101Component> {
public:
TEMPLATABLE_VALUE(RxAttenuation, rx_attenuation)
void play(const Ts &...x) override { this->parent_->set_rx_attenuation(this->rx_attenuation_.value(x...)); }
};
template<typename... Ts>
class SetDcBlockingFilterAction final : public Action<Ts...>, public Parented<CC1101Component> {
public:
TEMPLATABLE_VALUE(bool, dc_blocking_filter)
void play(const Ts &...x) override { this->parent_->set_dc_blocking_filter(this->dc_blocking_filter_.value(x...)); }
};
template<typename... Ts> class SetManchesterAction final : public Action<Ts...>, public Parented<CC1101Component> {
public:
TEMPLATABLE_VALUE(bool, manchester)
void play(const Ts &...x) override { this->parent_->set_manchester(this->manchester_.value(x...)); }
};
template<typename... Ts> class SetFilterBandwidthAction final : public Action<Ts...>, public Parented<CC1101Component> {
public:
TEMPLATABLE_VALUE(float, filter_bandwidth)
void play(const Ts &...x) override { this->parent_->set_filter_bandwidth(this->filter_bandwidth_.value(x...)); }
};
template<typename... Ts> class SetFskDeviationAction final : public Action<Ts...>, public Parented<CC1101Component> {
public:
TEMPLATABLE_VALUE(float, fsk_deviation)
void play(const Ts &...x) override { this->parent_->set_fsk_deviation(this->fsk_deviation_.value(x...)); }
};
template<typename... Ts> class SetMskDeviationAction final : public Action<Ts...>, public Parented<CC1101Component> {
public:
TEMPLATABLE_VALUE(uint8_t, msk_deviation)
void play(const Ts &...x) override { this->parent_->set_msk_deviation(this->msk_deviation_.value(x...)); }
};
template<typename... Ts> class SetChannelAction final : public Action<Ts...>, public Parented<CC1101Component> {
public:
TEMPLATABLE_VALUE(uint8_t, channel)
void play(const Ts &...x) override { this->parent_->set_channel(this->channel_.value(x...)); }
};
template<typename... Ts> class SetChannelSpacingAction final : public Action<Ts...>, public Parented<CC1101Component> {
public:
TEMPLATABLE_VALUE(float, channel_spacing)
void play(const Ts &...x) override { this->parent_->set_channel_spacing(this->channel_spacing_.value(x...)); }
};
template<typename... Ts> class SetIfFrequencyAction final : public Action<Ts...>, public Parented<CC1101Component> {
public:
TEMPLATABLE_VALUE(float, if_frequency)
void play(const Ts &...x) override { this->parent_->set_if_frequency(this->if_frequency_.value(x...)); }
};
} // namespace esphome::cc1101
+28 -77
View File
@@ -50,18 +50,13 @@ from esphome.const import (
CONF_VISUAL,
CONF_WEB_SERVER,
)
from esphome.core import CORE, ID, CoroPriority, Lambda, coroutine_with_priority
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import (
entity_duplicate_validator,
queue_entity_register,
setup_entity,
)
from esphome.cpp_generator import (
LambdaExpression,
MockObj,
MockObjClass,
TemplateArgsType,
)
from esphome.cpp_generator import MockObj, MockObjClass
from esphome.types import ConfigType, SafeExpType
IS_PLATFORM_COMPONENT = True
@@ -169,7 +164,6 @@ def visual_temperature_step(value: Any) -> ConfigType:
# Actions
ControlAction = climate_ns.class_("ControlAction", automation.Action)
StateTrigger = climate_ns.class_(
"StateTrigger", automation.Trigger.template(Climate.operator("ref"))
)
@@ -500,78 +494,35 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
)
@automation.register_action(
automation.register_apply_action(
"climate.control",
ControlAction,
CLIMATE_CONTROL_ACTION_SCHEMA,
synchronous=True,
automation.ApplyField(CONF_MODE, "set_mode", ClimateMode),
automation.ApplyField(CONF_TARGET_TEMPERATURE, "set_target_temperature", cg.float_),
automation.ApplyField(
CONF_TARGET_TEMPERATURE_LOW, "set_target_temperature_low", cg.float_
),
automation.ApplyField(
CONF_TARGET_TEMPERATURE_HIGH, "set_target_temperature_high", cg.float_
),
automation.ApplyField(CONF_TARGET_HUMIDITY, "set_target_humidity", cg.float_),
automation.ApplyField(CONF_FAN_MODE, "set_fan_mode", ClimateFanMode),
automation.ApplyField(
CONF_CUSTOM_FAN_MODE,
"set_fan_mode",
cg.std_string,
const_fn=automation.literal_with_length,
),
automation.ApplyField(CONF_PRESET, "set_preset", ClimatePreset),
automation.ApplyField(
CONF_CUSTOM_PRESET,
"set_preset",
cg.std_string,
const_fn=automation.literal_with_length,
),
automation.ApplyField(CONF_SWING_MODE, "set_swing_mode", ClimateSwingMode),
call="make_call",
)
async def climate_control_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
# All configured fields are folded into a single stateless lambda whose
# constants live in flash; the action stores only a function pointer.
# For custom_fan_mode/custom_preset the static-string path emits the
# (const char *, size_t) overload of set_fan_mode/set_preset to avoid
# constructing a std::string and calling runtime strlen.
FIELDS = (
(CONF_MODE, "set_mode", ClimateMode),
(CONF_TARGET_TEMPERATURE, "set_target_temperature", cg.float_),
(CONF_TARGET_TEMPERATURE_LOW, "set_target_temperature_low", cg.float_),
(CONF_TARGET_TEMPERATURE_HIGH, "set_target_temperature_high", cg.float_),
(CONF_TARGET_HUMIDITY, "set_target_humidity", cg.float_),
(CONF_FAN_MODE, "set_fan_mode", ClimateFanMode),
(CONF_CUSTOM_FAN_MODE, "set_fan_mode", cg.std_string),
(CONF_PRESET, "set_preset", ClimatePreset),
(CONF_CUSTOM_PRESET, "set_preset", cg.std_string),
(CONF_SWING_MODE, "set_swing_mode", ClimateSwingMode),
)
# Normalize trigger args to `const std::remove_cvref_t<T> &` so the
# apply lambda and any inner field lambdas (generated below via
# `process_lambda`) share one parameter spelling that's well-formed for
# any T (value, ref, or const-ref). Matches ControlAction::ApplyFn.
normalized_args = [
(cg.RawExpression(f"const std::remove_cvref_t<{cg.safe_exp(t)}> &"), n)
for t, n in args
]
fwd_args = ", ".join(name for _, name in args)
body_lines: list[str] = []
for conf_key, setter, type_ in FIELDS:
if (value := config.get(conf_key)) is None:
continue
if isinstance(value, Lambda):
inner = await cg.process_lambda(value, normalized_args, return_type=type_)
body_lines.append(f"call.{setter}(({inner})({fwd_args}));")
elif type_ is cg.std_string:
# Static custom strings: emit a flash literal and pass the
# UTF-8 byte length to skip the runtime strlen inside
# set_fan_mode/set_preset.
literal = cg.safe_exp(value)
body_lines.append(
f"call.{setter}({literal}, {len(value.encode('utf-8'))});"
)
else:
body_lines.append(f"call.{setter}({cg.safe_exp(value)});")
apply_args = [
(ClimateCall.operator("ref"), "call"),
*normalized_args,
]
apply_lambda = LambdaExpression(
["\n".join(body_lines)],
apply_args,
capture="",
return_type=cg.void,
)
return cg.new_Pvariable(action_id, template_arg, paren, apply_lambda)
@coroutine_with_priority(CoroPriority.CORE)
-28
View File
@@ -5,34 +5,6 @@
namespace esphome::climate {
// All configured fields are baked into a single stateless lambda whose
// constants live in flash. The action only stores one function pointer
// plus one parent pointer, regardless of how many fields the user set.
// Trigger args are forwarded to the apply function so user lambdas
// (e.g. `target_temperature: !lambda "return x;"`) keep working.
//
// Trigger args are normalized to `const std::remove_cvref_t<Ts> &...` so
// the codegen can emit a matching parameter list for both the apply lambda
// and any inner field lambdas without producing invalid C++ source text
// (e.g. `const T & &` if Ts already carries a reference, or `const const
// T &` if Ts already carries a const). This keeps trigger args no-copy
// regardless of whether the trigger supplies `T`, `T &`, or `const T &`.
template<typename... Ts> class ControlAction final : public Action<Ts...> {
public:
using ApplyFn = void (*)(ClimateCall &, const std::remove_cvref_t<Ts> &...);
ControlAction(Climate *climate, ApplyFn apply) : climate_(climate), apply_(apply) {}
void play(const Ts &...x) override {
auto call = this->climate_->make_call();
this->apply_(call, x...);
call.perform();
}
protected:
Climate *climate_;
ApplyFn apply_;
};
class ControlTrigger final : public Trigger<ClimateCall &> {
public:
ControlTrigger(Climate *climate) {
+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();
+10 -2
View File
@@ -14,6 +14,8 @@ from esphome.types import ConfigType, SafeExpType
_LOGGER = logging.getLogger(__name__)
CONF_SUPPORTS_HEAT_COOL = "supports_heat_cool"
DEPENDENCIES = ["remote_transmitter"]
AUTO_LOAD = ["sensor", "remote_base"]
CODEOWNERS = ["@glmnet"]
@@ -37,6 +39,7 @@ def climate_ir_schema(
{
cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean,
cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean,
cv.Optional(CONF_SUPPORTS_HEAT_COOL): cv.boolean,
cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor),
cv.Optional(CONF_HUMIDITY_SENSOR): cv.use_id(sensor.Sensor),
}
@@ -61,8 +64,13 @@ def climate_ir_with_receiver_schema(
async def register_climate_ir(var: MockObj, config: ConfigType) -> None:
await cg.register_component(var, config)
await remote_base.register_transmittable(var, config)
cg.add(var.set_supports_cool(config[CONF_SUPPORTS_COOL]))
cg.add(var.set_supports_heat(config[CONF_SUPPORTS_HEAT]))
supports_cool = config[CONF_SUPPORTS_COOL]
supports_heat = config[CONF_SUPPORTS_HEAT]
cg.add(var.set_supports_cool(supports_cool))
cg.add(var.set_supports_heat(supports_heat))
# The header default is true, so only the false case needs a call.
if not config.get(CONF_SUPPORTS_HEAT_COOL, supports_cool and supports_heat):
cg.add(var.set_supports_heat_cool(False))
if remote_base.CONF_RECEIVER_ID in config:
await remote_base.register_listener(var, config)
if sensor_id := config.get(CONF_SENSOR):
+7 -12
View File
@@ -13,15 +13,7 @@ climate::ClimateTraits ClimateIR::traits() {
if (this->humidity_sensor_ != nullptr) {
traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY);
}
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL});
if (this->supports_cool_)
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
if (this->supports_heat_)
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
if (this->supports_dry_)
traits.add_supported_mode(climate::CLIMATE_MODE_DRY);
if (this->supports_fan_only_)
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
traits.set_supported_modes(this->modes_);
traits.set_visual_min_temperature(this->minimum_temperature_);
traits.set_visual_max_temperature(this->maximum_temperature_);
@@ -94,9 +86,12 @@ void ClimateIR::dump_config() {
" Min. Temperature: %.1f°C\n"
" Max. Temperature: %.1f°C\n"
" Supports HEAT: %s\n"
" Supports COOL: %s",
this->minimum_temperature_, this->maximum_temperature_, YESNO(this->supports_heat_),
YESNO(this->supports_cool_));
" Supports COOL: %s\n"
" Supports HEAT_COOL: %s",
this->minimum_temperature_, this->maximum_temperature_,
YESNO(this->modes_.count(climate::CLIMATE_MODE_HEAT)),
YESNO(this->modes_.count(climate::CLIMATE_MODE_COOL)),
YESNO(this->modes_.count(climate::CLIMATE_MODE_HEAT_COOL)));
}
} // namespace esphome::climate_ir
+21 -8
View File
@@ -30,8 +30,10 @@ class ClimateIR : public Component,
this->minimum_temperature_ = minimum_temperature;
this->maximum_temperature_ = maximum_temperature;
this->temperature_step_ = temperature_step;
this->supports_dry_ = supports_dry;
this->supports_fan_only_ = supports_fan_only;
if (supports_dry)
this->modes_.insert(climate::CLIMATE_MODE_DRY);
if (supports_fan_only)
this->modes_.insert(climate::CLIMATE_MODE_FAN_ONLY);
this->fan_modes_ = fan_modes;
this->swing_modes_ = swing_modes;
this->presets_ = presets;
@@ -39,8 +41,11 @@ class ClimateIR : public Component,
void setup() override;
void dump_config() override;
void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
void set_supports_cool(bool supports_cool) { this->set_mode_supported_(climate::CLIMATE_MODE_COOL, supports_cool); }
void set_supports_heat(bool supports_heat) { this->set_mode_supported_(climate::CLIMATE_MODE_HEAT, supports_heat); }
void set_supports_heat_cool(bool supports_heat_cool) {
this->set_mode_supported_(climate::CLIMATE_MODE_HEAT_COOL, supports_heat_cool);
}
void set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
void set_humidity_sensor(sensor::Sensor *sensor) { this->humidity_sensor_ = sensor; }
@@ -58,10 +63,18 @@ class ClimateIR : public Component,
// Dummy implement on_receive so implementation is optional for inheritors
bool on_receive(remote_base::RemoteReceiveData data) override { return false; };
bool supports_cool_{true};
bool supports_heat_{true};
bool supports_dry_{false};
bool supports_fan_only_{false};
ESPHOME_ALWAYS_INLINE void set_mode_supported_(climate::ClimateMode mode, bool supported) {
if (supported) {
this->modes_.insert(mode);
} else {
this->modes_.erase(mode);
}
}
// The HEAT_COOL default (supports_cool && supports_heat) is resolved during code generation.
static constexpr climate::ClimateModeMask DEFAULT_MODES{climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL,
climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_HEAT_COOL};
climate::ClimateModeMask modes_{DEFAULT_MODES};
climate::ClimateFanModeMask fan_modes_{};
climate::ClimateSwingModeMask swing_modes_{};
climate::ClimatePresetMask presets_{};
-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 -18
View File
@@ -13,8 +13,6 @@ 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"]
@@ -24,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(
@@ -72,18 +66,8 @@ CALIBRATION_ACTION_SCHEMA = maybe_simple_id(
)
@automation.register_action(
automation.register_apply_action(
"cm1106.calibrate_zero",
CM1106CalibrateZeroAction,
CALIBRATION_ACTION_SCHEMA,
synchronous=True,
automation.ApplyCall("calibrate_zero(400)"),
)
async def cm1106_calibration_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
"""Service code generation entry point."""
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
+3
View File
@@ -23,6 +23,8 @@ 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"
CONF_IS_WRGB = "is_wrgb"
@@ -61,3 +63,4 @@ ICON_SOLAR_POWER = "mdi:solar-power"
KEY_METADATA = "metadata"
UNIT_AMPERE_HOUR = "Ah"
UNIT_COUNTS = "#"
+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,
}
+27 -174
View File
@@ -1,9 +1,7 @@
from collections.abc import Callable
from dataclasses import dataclass
import logging
from esphome import automation
from esphome.automation import Condition, maybe_simple_id
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
from esphome.components import mqtt, web_server
import esphome.config_validation as cv
@@ -38,15 +36,15 @@ from esphome.const import (
DEVICE_CLASS_SHUTTER,
DEVICE_CLASS_WINDOW,
)
from esphome.core import CORE, ID, CoroPriority, Lambda, coroutine_with_priority
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import (
entity_duplicate_validator,
queue_entity_register,
setup_device_class,
setup_entity,
)
from esphome.cpp_generator import LambdaExpression, MockObj, MockObjClass
from esphome.types import ConfigType, SafeExpType, TemplateArgsType
from esphome.cpp_generator import MockObj, MockObjClass
from esphome.types import ConfigType, SafeExpType
IS_PLATFORM_COMPONENT = True
@@ -70,7 +68,6 @@ _LOGGER = logging.getLogger(__name__)
cover_ns = cg.esphome_ns.namespace("cover")
Cover = cover_ns.class_("Cover", cg.EntityBase)
CoverCall = cover_ns.class_("CoverCall")
COVER_OPEN = cover_ns.COVER_OPEN
COVER_CLOSED = cover_ns.COVER_CLOSED
@@ -90,14 +87,6 @@ COVER_OPERATIONS = {
validate_cover_operation = cv.enum(COVER_OPERATIONS, upper=True)
# Actions
OpenAction = cover_ns.class_("OpenAction", automation.Action)
CloseAction = cover_ns.class_("CloseAction", automation.Action)
StopAction = cover_ns.class_("StopAction", automation.Action)
ToggleAction = cover_ns.class_("ToggleAction", automation.Action)
ControlAction = cover_ns.class_("ControlAction", automation.Action)
CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action)
CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition)
CoverIsClosedCondition = cover_ns.class_("CoverIsClosedCondition", Condition)
CoverOpenedTrigger = cover_ns.class_(
"CoverOpenedTrigger", automation.Trigger.template()
)
@@ -256,56 +245,15 @@ COVER_ACTION_SCHEMA = maybe_simple_id(
)
@automation.register_action(
"cover.open", OpenAction, COVER_ACTION_SCHEMA, synchronous=True
)
async def cover_open_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
"cover.close", CloseAction, COVER_ACTION_SCHEMA, synchronous=True
)
async def cover_close_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
"cover.stop", StopAction, COVER_ACTION_SCHEMA, synchronous=True
)
async def cover_stop_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
"cover.toggle", ToggleAction, COVER_ACTION_SCHEMA, synchronous=True
)
async def cover_toggle_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
for _name, _command in (
("cover.open", "set_command_open()"),
("cover.close", "set_command_close()"),
("cover.stop", "set_command_stop()"),
("cover.toggle", "set_command_toggle()"),
):
automation.register_apply_action(
_name, COVER_ACTION_SCHEMA, automation.ApplyCall(_command), call="make_call"
)
COVER_CONTROL_ACTION_SCHEMA = cv.Schema(
@@ -319,125 +267,30 @@ COVER_CONTROL_ACTION_SCHEMA = cv.Schema(
)
@dataclass(frozen=True)
class ApplyField:
"""One field in a folded-lambda action.
`conf_key` is the YAML key looked up in `config`. When present, the
helper emits `statement_fn(target, value_expr)` into the lambda body.
`target` is whatever the statement function needs to identify the
field (typically a setter name like `"set_position"` or a struct
member like `"position"`). `type_` is the C++ return type for
`cg.process_lambda` when the value is a user lambda.
"""
conf_key: str
target: str
type_: object
async def build_apply_lambda_action(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
fields: tuple[ApplyField, ...],
prefix_args: list[tuple[object, str]],
statement_fn: Callable[[str, str], str],
) -> MockObj:
"""Fold configured fields into a single stateless apply lambda action.
Used by both `cover.control` and `cover.template.publish` (and shared
with the template/cover platform). Constants are emitted as flash
immediates; user lambdas are invoked inline so trigger args still flow.
Trigger arg types are normalized to `const std::remove_cvref_t<T> &`
to match the ApplyFn signature for any T (value, ref, or const-ref).
"""
paren = await cg.get_variable(config[CONF_ID])
# Normalize trigger args to `const std::remove_cvref_t<T> &` so the
# apply lambda and any inner field lambdas (generated below via
# `process_lambda`) share one parameter spelling that's well-formed for
# any T.
normalized_args = [
(cg.RawExpression(f"const std::remove_cvref_t<{cg.safe_exp(t)}> &"), n)
for t, n in args
]
fwd_args = ", ".join(name for _, name in args)
body_lines: list[str] = []
for field in fields:
if (value := config.get(field.conf_key)) is None:
continue
if isinstance(value, Lambda):
inner = await cg.process_lambda(
value, normalized_args, return_type=field.type_
)
value_expr = f"({inner})({fwd_args})"
else:
value_expr = str(cg.safe_exp(value))
body_lines.append(statement_fn(field.target, value_expr))
apply_args = [
*prefix_args,
*normalized_args,
]
apply_lambda = LambdaExpression(
["\n".join(body_lines)],
apply_args,
capture="",
return_type=cg.void,
)
return cg.new_Pvariable(action_id, template_arg, paren, apply_lambda)
# CONF_STATE and CONF_POSITION are cv.Exclusive in the schema, so at most
# one is present and both dispatch to set_position.
_COVER_CONTROL_FIELDS: tuple[ApplyField, ...] = (
ApplyField(CONF_STOP, "set_stop", cg.bool_),
ApplyField(CONF_STATE, "set_position", cg.float_),
ApplyField(CONF_POSITION, "set_position", cg.float_),
ApplyField(CONF_TILT, "set_tilt", cg.float_),
automation.register_apply_action(
"cover.control",
COVER_CONTROL_ACTION_SCHEMA,
automation.ApplyField(CONF_STOP, "set_stop", cg.bool_),
automation.ApplyField(CONF_STATE, "set_position", cg.float_),
automation.ApplyField(CONF_POSITION, "set_position", cg.float_),
automation.ApplyField(CONF_TILT, "set_tilt", cg.float_),
call="make_call",
)
@automation.register_action(
"cover.control", ControlAction, COVER_CONTROL_ACTION_SCHEMA, synchronous=True
)
async def cover_control_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
return await build_apply_lambda_action(
config=config,
action_id=action_id,
template_arg=template_arg,
args=args,
fields=_COVER_CONTROL_FIELDS,
prefix_args=[(CoverCall.operator("ref"), "call")],
statement_fn=lambda setter, expr: f"call.{setter}({expr});",
)
COVER_CONDITION_SCHEMA = cv.maybe_simple_value(
{cv.Required(CONF_ID): cv.use_id(Cover)}, key=CONF_ID
)
async def cover_condition_to_code(
config: ConfigType, condition_id: ID, template_arg: MockObj, args: TemplateArgsType
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren)
automation.register_condition(
"cover.is_open", CoverIsOpenCondition, COVER_CONDITION_SCHEMA
)(cover_condition_to_code)
automation.register_condition(
"cover.is_closed", CoverIsClosedCondition, COVER_CONDITION_SCHEMA
)(cover_condition_to_code)
automation.register_apply_condition(
"cover.is_open", COVER_CONDITION_SCHEMA, f"position == {COVER_OPEN}"
)
automation.register_apply_condition(
"cover.is_closed", COVER_CONDITION_SCHEMA, f"position == {COVER_CLOSED}"
)
@coroutine_with_priority(CoroPriority.CORE)
-97
View File
@@ -6,103 +6,6 @@
namespace esphome::cover {
template<typename... Ts> class OpenAction final : public Action<Ts...> {
public:
explicit OpenAction(Cover *cover) : cover_(cover) {}
void play(const Ts &...x) override { this->cover_->make_call().set_command_open().perform(); }
protected:
Cover *cover_;
};
template<typename... Ts> class CloseAction final : public Action<Ts...> {
public:
explicit CloseAction(Cover *cover) : cover_(cover) {}
void play(const Ts &...x) override { this->cover_->make_call().set_command_close().perform(); }
protected:
Cover *cover_;
};
template<typename... Ts> class StopAction final : public Action<Ts...> {
public:
explicit StopAction(Cover *cover) : cover_(cover) {}
void play(const Ts &...x) override { this->cover_->make_call().set_command_stop().perform(); }
protected:
Cover *cover_;
};
template<typename... Ts> class ToggleAction final : public Action<Ts...> {
public:
explicit ToggleAction(Cover *cover) : cover_(cover) {}
void play(const Ts &...x) override { this->cover_->make_call().set_command_toggle().perform(); }
protected:
Cover *cover_;
};
// All configured fields are baked into a single stateless lambda whose
// constants live in flash. Each action stores only one function pointer
// plus one parent pointer, regardless of how many fields the user set.
// Trigger args are forwarded to the apply function so user lambdas
// (e.g. `position: !lambda "return x;"`) keep working.
//
// Trigger args are normalized to `const std::remove_cvref_t<Ts> &...` so
// the codegen can emit a matching parameter list for both the apply lambda
// and any inner field lambdas without producing invalid C++ source text
// (e.g. `const T & &` if Ts already carries a reference, or `const const
// T &` if Ts already carries a const). This keeps trigger args no-copy
// regardless of whether the trigger supplies `T`, `T &`, or `const T &`.
template<typename... Ts> class ControlAction final : public Action<Ts...> {
public:
using ApplyFn = void (*)(CoverCall &, const std::remove_cvref_t<Ts> &...);
ControlAction(Cover *cover, ApplyFn apply) : cover_(cover), apply_(apply) {}
void play(const Ts &...x) override {
auto call = this->cover_->make_call();
this->apply_(call, x...);
call.perform();
}
protected:
Cover *cover_;
ApplyFn apply_;
};
template<typename... Ts> class CoverPublishAction final : public Action<Ts...> {
public:
using ApplyFn = void (*)(Cover *, const std::remove_cvref_t<Ts> &...);
CoverPublishAction(Cover *cover, ApplyFn apply) : cover_(cover), apply_(apply) {}
void play(const Ts &...x) override {
this->apply_(this->cover_, x...);
this->cover_->publish_state();
}
protected:
Cover *cover_;
ApplyFn apply_;
};
template<bool OPEN, typename... Ts> class CoverPositionCondition final : public Condition<Ts...> {
public:
CoverPositionCondition(Cover *cover) : cover_(cover) {}
bool check(const Ts &...x) override { return this->cover_->position == (OPEN ? COVER_OPEN : COVER_CLOSED); }
protected:
Cover *cover_;
};
template<typename... Ts> using CoverIsOpenCondition = CoverPositionCondition<true, Ts...>;
template<typename... Ts> using CoverIsClosedCondition = CoverPositionCondition<false, Ts...>;
template<bool OPEN> class CoverPositionTrigger final : public Trigger<> {
public:
CoverPositionTrigger(Cover *a_cover) : cover_(a_cover) {
-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 -14
View File
@@ -17,8 +17,6 @@ from esphome.const import (
UNIT_VOLT,
UNIT_WATT,
)
from esphome.core import ID
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.types import ConfigType
CODEOWNERS = ["@balrog-kun"]
@@ -32,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"
@@ -131,21 +128,12 @@ async def to_code(config: ConfigType) -> None:
cg.add(var.set_power_sensor(sens))
@automation.register_action(
automation.register_apply_action(
"cs5460a.restart",
CS5460ARestartAction,
maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(CS5460AComponent),
}
),
synchronous=True,
automation.ApplyCall("restart()"),
)
async def restart_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
+60 -117
View File
@@ -1,3 +1,5 @@
from collections.abc import Callable
from esphome import automation
import esphome.codegen as cg
from esphome.components import mqtt, time, web_server
@@ -21,13 +23,13 @@ from esphome.const import (
CONF_WEB_SERVER,
CONF_YEAR,
)
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import (
entity_duplicate_validator,
queue_entity_register,
setup_entity,
)
from esphome.cpp_generator import MockObj, MockObjClass, TemplateArgsType
from esphome.cpp_generator import MockObj, MockObjClass
from esphome.types import ConfigType, SafeExpType
CODEOWNERS = ["@rfdarter", "@jesserockz"]
@@ -40,11 +42,6 @@ DateEntity = datetime_ns.class_("DateEntity", DateTimeBase)
TimeEntity = datetime_ns.class_("TimeEntity", DateTimeBase)
DateTimeEntity = datetime_ns.class_("DateTimeEntity", DateTimeBase)
# Actions
DateSetAction = datetime_ns.class_("DateSetAction", automation.Action)
TimeSetAction = datetime_ns.class_("TimeSetAction", automation.Action)
DateTimeSetAction = datetime_ns.class_("DateTimeSetAction", automation.Action)
DateTimeStateTrigger = datetime_ns.class_(
"DateTimeStateTrigger", automation.Trigger.template(cg.ESPTime)
)
@@ -181,118 +178,64 @@ async def to_code(config: ConfigType) -> None:
cg.add_global(datetime_ns.using)
@automation.register_action(
"datetime.date.set",
DateSetAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(DateEntity),
cv.Required(CONF_DATE): cv.Any(
cv.returning_lambda, cv.date_time(date=True, time=False)
),
}
),
synchronous=True,
)
async def datetime_date_set_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
action_var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(action_var, config[CONF_ID])
date_config = config[CONF_DATE]
if cg.is_template(date_config):
template_ = await cg.templatable(date_config, args, cg.ESPTime)
cg.add(action_var.set_date(template_))
else:
date_struct = cg.StructInitializer(
cg.ESPTime,
("day_of_month", date_config[CONF_DAY]),
("month", date_config[CONF_MONTH]),
("year", date_config[CONF_YEAR]),
def _esptime_initializer(
members: tuple[tuple[str, str], ...],
) -> Callable[[ConfigType, ConfigType], str]:
def const_fn(config: ConfigType, value: ConfigType) -> str:
return str(
cg.StructInitializer(
cg.ESPTime, *((member, value[key]) for member, key in members)
)
)
template_ = await cg.templatable(date_struct, args, cg.ESPTime)
cg.add(action_var.set_date(template_))
return action_var
return const_fn
@automation.register_action(
"datetime.time.set",
TimeSetAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(TimeEntity),
cv.Required(CONF_TIME): cv.Any(
cv.returning_lambda, cv.date_time(date=False, time=True)
),
}
# ESPTime member order, so the designated initializer compiles.
_TIME_MEMBERS = (("second", CONF_SECOND), ("minute", CONF_MINUTE), ("hour", CONF_HOUR))
_DATE_MEMBERS = (("day_of_month", CONF_DAY), ("month", CONF_MONTH), ("year", CONF_YEAR))
for _name, _entity, _key, _target, _date, _time, _members in (
(
"datetime.date.set",
DateEntity,
CONF_DATE,
"set_date",
True,
False,
_DATE_MEMBERS,
),
synchronous=True,
)
async def datetime_time_set_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
action_var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(action_var, config[CONF_ID])
time_config = config[CONF_TIME]
if cg.is_template(time_config):
template_ = await cg.templatable(time_config, args, cg.ESPTime)
cg.add(action_var.set_time(template_))
else:
time_struct = cg.StructInitializer(
cg.ESPTime,
("second", time_config[CONF_SECOND]),
("minute", time_config[CONF_MINUTE]),
("hour", time_config[CONF_HOUR]),
)
template_ = await cg.templatable(time_struct, args, cg.ESPTime)
cg.add(action_var.set_time(template_))
return action_var
@automation.register_action(
"datetime.datetime.set",
DateTimeSetAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(DateTimeEntity),
cv.Required(CONF_DATETIME): cv.Any(
cv.returning_lambda, cv.date_time(date=True, time=True)
),
},
(
"datetime.time.set",
TimeEntity,
CONF_TIME,
"set_time",
False,
True,
_TIME_MEMBERS,
),
synchronous=True,
)
async def datetime_datetime_set_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
action_var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(action_var, config[CONF_ID])
datetime_config = config[CONF_DATETIME]
if cg.is_template(datetime_config):
template_ = await cg.templatable(datetime_config, args, cg.ESPTime)
cg.add(action_var.set_datetime(template_))
else:
datetime_struct = cg.StructInitializer(
cg.ESPTime,
("second", datetime_config[CONF_SECOND]),
("minute", datetime_config[CONF_MINUTE]),
("hour", datetime_config[CONF_HOUR]),
("day_of_month", datetime_config[CONF_DAY]),
("month", datetime_config[CONF_MONTH]),
("year", datetime_config[CONF_YEAR]),
)
template_ = await cg.templatable(datetime_struct, args, cg.ESPTime)
cg.add(action_var.set_datetime(template_))
return action_var
(
"datetime.datetime.set",
DateTimeEntity,
CONF_DATETIME,
"set_datetime",
True,
True,
_TIME_MEMBERS + _DATE_MEMBERS,
),
):
automation.register_apply_action(
_name,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(_entity),
cv.Required(_key): cv.Any(
cv.returning_lambda, cv.date_time(date=_date, time=_time)
),
}
),
automation.ApplyField(
_key, _target, cg.ESPTime, const_fn=_esptime_initializer(_members)
),
call="make_call",
)
-14
View File
@@ -98,20 +98,6 @@ class DateCall {
inline DateCall DateEntity::make_call() { return DateCall(this); }
template<typename... Ts> class DateSetAction final : public Action<Ts...>, public Parented<DateEntity> {
public:
TEMPLATABLE_VALUE(ESPTime, date)
void play(const Ts &...x) override {
auto call = this->parent_->make_call();
if (this->date_.has_value()) {
call.set_date(this->date_.value(x...));
}
call.perform();
}
};
} // namespace esphome::datetime
#endif // USE_DATETIME_DATE
@@ -123,20 +123,6 @@ class DateTimeCall {
inline DateTimeCall DateTimeEntity::make_call() { return DateTimeCall(this); }
template<typename... Ts> class DateTimeSetAction final : public Action<Ts...>, public Parented<DateTimeEntity> {
public:
TEMPLATABLE_VALUE(ESPTime, datetime)
void play(const Ts &...x) override {
auto call = this->parent_->make_call();
if (this->datetime_.has_value()) {
call.set_datetime(this->datetime_.value(x...));
}
call.perform();
}
};
#ifdef USE_TIME
class OnDateTimeTrigger final : public Trigger<>, public Component, public Parented<DateTimeEntity> {
public:
-14
View File
@@ -100,20 +100,6 @@ class TimeCall {
inline TimeCall TimeEntity::make_call() { return TimeCall(this); }
template<typename... Ts> class TimeSetAction final : public Action<Ts...>, public Parented<TimeEntity> {
public:
TEMPLATABLE_VALUE(ESPTime, time)
void play(const Ts &...x) override {
auto call = this->parent_->make_call();
if (this->time_.has_value()) {
call.set_time(this->time_.value(x...));
}
call.perform();
}
};
#ifdef USE_TIME
class OnTimeTrigger final : public Trigger<>, public Component, public Parented<TimeEntity> {
public:
+15 -33
View File
@@ -214,16 +214,6 @@ def _validate_sleep_duration(value: core.TimePeriod) -> core.TimePeriod:
deep_sleep_ns = cg.esphome_ns.namespace("deep_sleep")
DeepSleepComponent = deep_sleep_ns.class_("DeepSleepComponent", cg.Component)
EnterDeepSleepAction = deep_sleep_ns.class_("EnterDeepSleepAction", automation.Action)
PreventDeepSleepAction = deep_sleep_ns.class_(
"PreventDeepSleepAction",
automation.Action,
cg.Parented.template(DeepSleepComponent),
)
AllowDeepSleepAction = deep_sleep_ns.class_(
"AllowDeepSleepAction",
automation.Action,
cg.Parented.template(DeepSleepComponent),
)
WakeupPinMode = deep_sleep_ns.enum("WakeupPinMode")
WAKEUP_PIN_MODES = {
@@ -422,8 +412,12 @@ async def to_code(config: ConfigType) -> None:
if CONF_TOUCH_WAKEUP in config:
cg.add(var.set_touch_wakeup(config[CONF_TOUCH_WAKEUP]))
if CORE.using_zephyr and "zigbee" not in CORE.loaded_integrations:
zephyr_add_prj_conf("POWEROFF", True)
if CORE.using_zephyr:
# Devices are suspended when CPU is entering a low power state
# https://github.com/nrfconnect/sdk-zephyr/blob/v3.7.99-ncs2-2/doc/services/pm/device.rst#system-managed-device-power-management
zephyr_add_prj_conf("PM_DEVICE", True)
if "zigbee" not in CORE.loaded_integrations:
zephyr_add_prj_conf("POWEROFF", True)
cg.add_define("USE_DEEP_SLEEP")
@@ -486,27 +480,15 @@ async def deep_sleep_enter_to_code(
return var
@automation.register_action(
"deep_sleep.prevent",
PreventDeepSleepAction,
automation.maybe_simple_id(DEEP_SLEEP_ACTION_SCHEMA),
synchronous=True,
)
@automation.register_action(
"deep_sleep.allow",
AllowDeepSleepAction,
automation.maybe_simple_id(DEEP_SLEEP_ACTION_SCHEMA),
synchronous=True,
)
async def deep_sleep_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])
return var
for _name, _call in (
("deep_sleep.prevent", "prevent_deep_sleep()"),
("deep_sleep.allow", "allow_deep_sleep()"),
):
automation.register_apply_action(
_name,
automation.maybe_simple_id(DEEP_SLEEP_ACTION_SCHEMA),
automation.ApplyCall(_call),
)
FILTER_SOURCE_FILES = filter_source_files_from_platform(
@@ -121,8 +121,6 @@ class Ext1WakeTrigger : public Trigger<>, public Component {
template<typename... Ts> class EnterDeepSleepAction;
template<typename... Ts> class PreventDeepSleepAction;
/** This component allows setting up the node to go into deep sleep mode to conserve battery.
*
* To set this component up, first set *when* the deep sleep should trigger using set_run_cycles
@@ -298,15 +296,4 @@ template<typename... Ts> class EnterDeepSleepAction final : public Action<Ts...>
#endif
};
template<typename... Ts>
class PreventDeepSleepAction final : public Action<Ts...>, public Parented<DeepSleepComponent> {
public:
void play(const Ts &...x) override { this->parent_->prevent_deep_sleep(); }
};
template<typename... Ts> class AllowDeepSleepAction final : public Action<Ts...>, public Parented<DeepSleepComponent> {
public:
void play(const Ts &...x) override { this->parent_->allow_deep_sleep(); }
};
} // namespace esphome::deep_sleep
+67 -250
View File
@@ -1,3 +1,5 @@
from typing import Any
from esphome import automation
import esphome.codegen as cg
from esphome.components import uart
@@ -10,12 +12,10 @@ 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"
CONF_ENABLE = "enable"
CONF_EQ_PRESET = "eq_preset"
CONF_ON_FINISHED_PLAYBACK = "on_finished_playback"
@@ -34,23 +34,6 @@ DEVICE = {
"TF_CARD": Device.TF_CARD,
}
NextAction = dfplayer_ns.class_("NextAction", automation.Action)
PreviousAction = dfplayer_ns.class_("PreviousAction", automation.Action)
PlayMp3Action = dfplayer_ns.class_("PlayMp3Action", automation.Action)
PlayFileAction = dfplayer_ns.class_("PlayFileAction", automation.Action)
PlayFolderAction = dfplayer_ns.class_("PlayFolderAction", automation.Action)
SetVolumeAction = dfplayer_ns.class_("SetVolumeAction", automation.Action)
VolumeUpAction = dfplayer_ns.class_("VolumeUpAction", automation.Action)
VolumeDownAction = dfplayer_ns.class_("VolumeDownAction", automation.Action)
SetEqAction = dfplayer_ns.class_("SetEqAction", automation.Action)
SleepAction = dfplayer_ns.class_("SleepAction", automation.Action)
ResetAction = dfplayer_ns.class_("ResetAction", automation.Action)
StartAction = dfplayer_ns.class_("StartAction", automation.Action)
PauseAction = dfplayer_ns.class_("PauseAction", automation.Action)
StopAction = dfplayer_ns.class_("StopAction", automation.Action)
RandomAction = dfplayer_ns.class_("RandomAction", automation.Action)
SetDeviceAction = dfplayer_ns.class_("SetDeviceAction", automation.Action)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
@@ -84,41 +67,30 @@ async def to_code(config):
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
@automation.register_action(
"dfplayer.play_next",
NextAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
),
synchronous=True,
DFPLAYER_ACTION_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
)
async def dfplayer_next_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
for _name, _call in (
("dfplayer.play_next", "next()"),
("dfplayer.play_previous", "previous()"),
("dfplayer.volume_up", "volume_up()"),
("dfplayer.volume_down", "volume_down()"),
("dfplayer.sleep", "sleep()"),
("dfplayer.reset", "reset()"),
("dfplayer.start", "start()"),
("dfplayer.pause", "pause()"),
("dfplayer.stop", "stop()"),
("dfplayer.random", "random()"),
):
automation.register_apply_action(
_name, DFPLAYER_ACTION_SCHEMA, automation.ApplyCall(_call)
)
@automation.register_action(
"dfplayer.play_previous",
PreviousAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
),
synchronous=True,
)
async def dfplayer_previous_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
automation.register_apply_action(
"dfplayer.play_mp3",
PlayMp3Action,
cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(DFPlayer),
@@ -126,70 +98,43 @@ async def dfplayer_previous_to_code(config, action_id, template_arg, args):
},
key=CONF_FILE,
),
synchronous=True,
automation.ApplyField(CONF_FILE, "play_mp3", cg.uint16),
)
async def dfplayer_play_mp3_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_FILE], args, cg.uint16)
cg.add(var.set_file(template_))
return var
@automation.register_action(
# loop and file default to what the old action's unset templatable values evaluated to
automation.register_apply_action(
"dfplayer.play",
PlayFileAction,
cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(DFPlayer),
cv.Required(CONF_FILE): cv.templatable(cv.int_),
cv.Optional(CONF_LOOP): cv.templatable(cv.boolean),
cv.Optional(CONF_LOOP, default=False): cv.templatable(cv.boolean),
},
key=CONF_FILE,
),
synchronous=True,
automation.ApplyCall(
"play_file({}, {})", ((CONF_FILE, cg.uint16), (CONF_LOOP, cg.bool_))
),
)
async def dfplayer_play_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_FILE], args, cg.uint16)
cg.add(var.set_file(template_))
if CONF_LOOP in config:
template_ = await cg.templatable(config[CONF_LOOP], args, cg.bool_)
cg.add(var.set_loop(template_))
return var
@automation.register_action(
automation.register_apply_action(
"dfplayer.play_folder",
PlayFolderAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
cv.Required(CONF_FOLDER): cv.templatable(cv.int_),
cv.Optional(CONF_FILE): cv.templatable(cv.int_),
cv.Optional(CONF_LOOP): cv.templatable(cv.boolean),
cv.Optional(CONF_FILE, default=0): cv.templatable(cv.int_),
cv.Optional(CONF_LOOP, default=False): cv.templatable(cv.boolean),
}
),
synchronous=True,
automation.ApplyCall(
"play_folder({}, {}, {})",
((CONF_FOLDER, cg.uint16), (CONF_FILE, cg.uint16), (CONF_LOOP, cg.bool_)),
),
)
async def dfplayer_play_folder_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_FOLDER], args, cg.uint16)
cg.add(var.set_folder(template_))
if CONF_FILE in config:
template_ = await cg.templatable(config[CONF_FILE], args, cg.uint16)
cg.add(var.set_file(template_))
if CONF_LOOP in config:
template_ = await cg.templatable(config[CONF_LOOP], args, cg.bool_)
cg.add(var.set_loop(template_))
return var
@automation.register_action(
automation.register_apply_action(
"dfplayer.set_device",
SetDeviceAction,
cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(DFPlayer),
@@ -197,19 +142,11 @@ async def dfplayer_play_folder_to_code(config, action_id, template_arg, args):
},
key=CONF_DEVICE,
),
synchronous=True,
automation.ApplyField(CONF_DEVICE, "set_device", Device),
)
async def dfplayer_set_device_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_DEVICE], args, Device)
cg.add(var.set_device(template_))
return var
@automation.register_action(
automation.register_apply_action(
"dfplayer.set_volume",
SetVolumeAction,
cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(DFPlayer),
@@ -217,51 +154,11 @@ async def dfplayer_set_device_to_code(config, action_id, template_arg, args):
},
key=CONF_VOLUME,
),
synchronous=True,
automation.ApplyField(CONF_VOLUME, "set_volume", cg.uint8),
)
async def dfplayer_set_volume_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_VOLUME], args, cg.uint8)
cg.add(var.set_volume(template_))
return var
@automation.register_action(
"dfplayer.volume_up",
VolumeUpAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
),
synchronous=True,
)
async def dfplayer_volume_up_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"dfplayer.volume_down",
VolumeDownAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
),
synchronous=True,
)
async def dfplayer_volume_down_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
automation.register_apply_action(
"dfplayer.set_eq",
SetEqAction,
cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(DFPlayer),
@@ -269,122 +166,42 @@ async def dfplayer_volume_down_to_code(config, action_id, template_arg, args):
},
key=CONF_EQ_PRESET,
),
synchronous=True,
automation.ApplyField(CONF_EQ_PRESET, "set_eq", EqPreset),
)
async def dfplayer_set_eq_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_EQ_PRESET], args, EqPreset)
cg.add(var.set_eq(template_))
return var
@automation.register_action(
"dfplayer.sleep",
SleepAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
def _default_enable(value: Any) -> Any:
"""Fill in ``enable: true`` for a bare action or a mapping that only picks the player.
Done before ``maybe_simple_value`` so neither form is wrapped as the ``enable`` value.
"""
if value is None or isinstance(value, dict):
return {CONF_ENABLE: True, **(value or {})}
return value
automation.register_apply_action(
"dfplayer.set_current_track_repeat",
cv.All(
_default_enable,
cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(DFPlayer),
cv.Optional(CONF_ENABLE, default=True): cv.templatable(cv.boolean),
},
key=CONF_ENABLE,
),
),
synchronous=True,
automation.ApplyField(CONF_ENABLE, "set_current_track_repeat", cg.bool_),
)
async def dfplayer_sleep_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"dfplayer.reset",
ResetAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
),
synchronous=True,
)
async def dfplayer_reset_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"dfplayer.start",
StartAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
),
synchronous=True,
)
async def dfplayer_start_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"dfplayer.pause",
PauseAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
),
synchronous=True,
)
async def dfplayer_pause_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"dfplayer.stop",
StopAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
),
synchronous=True,
)
async def dfplayer_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])
return var
@automation.register_action(
"dfplayer.random",
RandomAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
),
synchronous=True,
)
async def dfplayer_random_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_condition(
automation.register_apply_condition(
"dfplayer.is_playing",
DFPlayerIsPlayingCondition,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DFPlayer),
}
),
"is_playing()",
)
async def dfplayer_is_playing_to_code(config, condition_id, template_arg, args):
var = cg.new_Pvariable(condition_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
+7
View File
@@ -102,6 +102,13 @@ void DFPlayer::random() {
this->send_cmd_(0x18);
}
void DFPlayer::set_current_track_repeat(bool enable) {
uint16_t arg = enable ? 0x00 : 0x01;
ESP_LOGD(TAG, "Setting current track repeat to %s",
enable ? LOG_STR_LITERAL("enabled") : LOG_STR_LITERAL("disabled"));
this->send_cmd_(0x19, arg);
}
void DFPlayer::play_folder(uint16_t folder, uint16_t file) {
ESP_LOGD(TAG, "Playing file %d in folder %d", file, folder);
if (folder < 100 && file < 256) {
+6 -99
View File
@@ -33,8 +33,13 @@ class DFPlayer final : public uart::UARTDevice, public Component {
void play_mp3(uint16_t file);
void play_file(uint16_t file);
void play_file_loop(uint16_t file);
void play_file(uint16_t file, bool loop) { loop ? this->play_file_loop(file) : this->play_file(file); }
void play_folder(uint16_t folder, uint16_t file);
void play_folder_loop(uint16_t folder);
// The loop command plays the whole folder, so file is ignored when loop is set.
void play_folder(uint16_t folder, uint16_t file, bool loop) {
loop ? this->play_folder_loop(folder) : this->play_folder(folder, file);
}
void volume_up();
void volume_down();
void set_device(Device device);
@@ -46,6 +51,7 @@ class DFPlayer final : public uart::UARTDevice, public Component {
void pause();
void stop();
void random();
void set_current_track_repeat(bool enable);
bool is_playing() { return is_playing_; }
void dump_config() override;
@@ -71,103 +77,4 @@ class DFPlayer final : public uart::UARTDevice, public Component {
CallbackManager<void()> on_finished_playback_callback_;
};
#define DFPLAYER_SIMPLE_ACTION(ACTION_CLASS, ACTION_METHOD) \
template<typename... Ts> \
class ACTION_CLASS : /* NOLINT */ \
public Action<Ts...>, \
public Parented<DFPlayer> { \
void play(const Ts &...x) override { this->parent_->ACTION_METHOD(); } \
};
DFPLAYER_SIMPLE_ACTION(NextAction, next)
DFPLAYER_SIMPLE_ACTION(PreviousAction, previous)
template<typename... Ts> class PlayMp3Action final : public Action<Ts...>, public Parented<DFPlayer> {
public:
TEMPLATABLE_VALUE(uint16_t, file)
void play(const Ts &...x) override {
auto file = this->file_.value(x...);
this->parent_->play_mp3(file);
}
};
template<typename... Ts> class PlayFileAction final : public Action<Ts...>, public Parented<DFPlayer> {
public:
TEMPLATABLE_VALUE(uint16_t, file)
TEMPLATABLE_VALUE(bool, loop)
void play(const Ts &...x) override {
auto file = this->file_.value(x...);
auto loop = this->loop_.value(x...);
if (loop) {
this->parent_->play_file_loop(file);
} else {
this->parent_->play_file(file);
}
}
};
template<typename... Ts> class PlayFolderAction final : public Action<Ts...>, public Parented<DFPlayer> {
public:
TEMPLATABLE_VALUE(uint16_t, folder)
TEMPLATABLE_VALUE(uint16_t, file)
TEMPLATABLE_VALUE(bool, loop)
void play(const Ts &...x) override {
auto folder = this->folder_.value(x...);
auto file = this->file_.value(x...);
auto loop = this->loop_.value(x...);
if (loop) {
this->parent_->play_folder_loop(folder);
} else {
this->parent_->play_folder(folder, file);
}
}
};
template<typename... Ts> class SetDeviceAction final : public Action<Ts...>, public Parented<DFPlayer> {
public:
TEMPLATABLE_VALUE(Device, device)
void play(const Ts &...x) override {
auto device = this->device_.value(x...);
this->parent_->set_device(device);
}
};
template<typename... Ts> class SetVolumeAction final : public Action<Ts...>, public Parented<DFPlayer> {
public:
TEMPLATABLE_VALUE(uint8_t, volume)
void play(const Ts &...x) override {
auto volume = this->volume_.value(x...);
this->parent_->set_volume(volume);
}
};
template<typename... Ts> class SetEqAction final : public Action<Ts...>, public Parented<DFPlayer> {
public:
TEMPLATABLE_VALUE(EqPreset, eq)
void play(const Ts &...x) override {
auto eq = this->eq_.value(x...);
this->parent_->set_eq(eq);
}
};
DFPLAYER_SIMPLE_ACTION(SleepAction, sleep)
DFPLAYER_SIMPLE_ACTION(ResetAction, reset)
DFPLAYER_SIMPLE_ACTION(StartAction, start)
DFPLAYER_SIMPLE_ACTION(PauseAction, pause)
DFPLAYER_SIMPLE_ACTION(StopAction, stop)
DFPLAYER_SIMPLE_ACTION(RandomAction, random)
DFPLAYER_SIMPLE_ACTION(VolumeUpAction, volume_up)
DFPLAYER_SIMPLE_ACTION(VolumeDownAction, volume_down)
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
+2 -16
View File
@@ -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,26 +46,15 @@ async def to_code(config: ConfigType) -> None:
await uart.register_uart_device(var, config)
@automation.register_action(
automation.register_apply_action(
"dfrobot_sen0395.reset",
DfrobotSen0395ResetAction,
maybe_simple_id(
{
cv.GenerateID(): cv.use_id(DfrobotSen0395Component),
}
),
synchronous=True,
automation.ApplyCall("reset_system()"),
)
async def dfrobot_sen0395_reset_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])
return var
def range_segment_list(input: Any) -> list:
@@ -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
+15 -41
View File
@@ -39,15 +39,6 @@ DisplayPage = display_ns.class_("DisplayPage")
DisplayPagePtr = DisplayPage.operator("ptr")
DisplayRef = Display.operator("ref")
DisplayPageShowAction = display_ns.class_("DisplayPageShowAction", automation.Action)
DisplayPageShowNextAction = display_ns.class_(
"DisplayPageShowNextAction", automation.Action
)
DisplayPageShowPrevAction = display_ns.class_(
"DisplayPageShowPrevAction", automation.Action
)
DisplayIsDisplayingPageCondition = display_ns.class_(
"DisplayIsDisplayingPageCondition", automation.Condition
)
DisplayOnPageChangeTrigger = display_ns.class_(
"DisplayOnPageChangeTrigger", automation.Trigger
)
@@ -288,39 +279,27 @@ async def display_page_show_to_code(config, action_id, template_arg, args):
return var
@automation.register_action(
# The id is the display itself, so it cannot be a lambda; a plain use_id rejects one clearly.
DISPLAY_PAGE_CYCLE_ACTION_SCHEMA = maybe_simple_id(
{
cv.GenerateID(CONF_ID): cv.use_id(Display),
}
)
automation.register_apply_action(
"display.page.show_next",
DisplayPageShowNextAction,
maybe_simple_id(
{
cv.GenerateID(CONF_ID): cv.templatable(cv.use_id(Display)),
}
),
synchronous=True,
DISPLAY_PAGE_CYCLE_ACTION_SCHEMA,
automation.ApplyCall("show_next_page()"),
)
async def display_page_show_next_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
automation.register_apply_action(
"display.page.show_previous",
DisplayPageShowPrevAction,
maybe_simple_id(
{
cv.GenerateID(CONF_ID): cv.templatable(cv.use_id(Display)),
}
),
synchronous=True,
DISPLAY_PAGE_CYCLE_ACTION_SCHEMA,
automation.ApplyCall("show_prev_page()"),
)
async def display_page_show_previous_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_condition(
automation.register_apply_condition(
"display.is_displaying_page",
DisplayIsDisplayingPageCondition,
cv.maybe_simple_value(
{
cv.GenerateID(CONF_ID): cv.use_id(Display),
@@ -328,13 +307,8 @@ async def display_page_show_previous_to_code(config, action_id, template_arg, ar
},
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)
-30
View File
@@ -848,36 +848,6 @@ template<typename... Ts> class DisplayPageShowAction final : public Action<Ts...
}
};
template<typename... Ts> class DisplayPageShowNextAction final : public Action<Ts...> {
public:
DisplayPageShowNextAction(Display *buffer) : buffer_(buffer) {}
void play(const Ts &...x) override { this->buffer_->show_next_page(); }
Display *buffer_;
};
template<typename... Ts> class DisplayPageShowPrevAction final : public Action<Ts...> {
public:
DisplayPageShowPrevAction(Display *buffer) : buffer_(buffer) {}
void play(const Ts &...x) override { this->buffer_->show_prev_page(); }
Display *buffer_;
};
template<typename... Ts> class DisplayIsDisplayingPageCondition final : public Condition<Ts...> {
public:
DisplayIsDisplayingPageCondition(Display *parent) : parent_(parent) {}
void set_page(DisplayPage *page) { this->page_ = page; }
bool check(const Ts &...x) override { return this->parent_->get_active_page() == this->page_; }
protected:
Display *parent_;
DisplayPage *page_;
};
class DisplayOnPageChangeTrigger final : public Trigger<DisplayPage *, DisplayPage *> {
public:
explicit DisplayOnPageChangeTrigger(Display *parent) { parent->add_on_page_change_trigger(this); }
@@ -55,18 +55,6 @@ MenuItemSwitch = display_menu_base_ns.class_("MenuItemSwitch")
MenuItemCommand = display_menu_base_ns.class_("MenuItemCommand")
MenuItemCustom = display_menu_base_ns.class_("MenuItemCustom")
UpAction = display_menu_base_ns.class_("UpAction", automation.Action)
DownAction = display_menu_base_ns.class_("DownAction", automation.Action)
LeftAction = display_menu_base_ns.class_("LeftAction", automation.Action)
RightAction = display_menu_base_ns.class_("RightAction", automation.Action)
EnterAction = display_menu_base_ns.class_("EnterAction", automation.Action)
ShowAction = display_menu_base_ns.class_("ShowAction", automation.Action)
HideAction = display_menu_base_ns.class_("HideAction", automation.Action)
ShowMainAction = display_menu_base_ns.class_("ShowMainAction", automation.Action)
IsActiveCondition = display_menu_base_ns.class_(
"IsActiveCondition", automation.Condition
)
MenuItemType = display_menu_base_ns.enum("MenuItemType")
@@ -294,85 +282,23 @@ MENU_ACTION_SCHEMA = maybe_simple_id(
)
@automation.register_action(
"display_menu.up", UpAction, MENU_ACTION_SCHEMA, synchronous=True
for _name, _call in (
("display_menu.up", "up()"),
("display_menu.down", "down()"),
("display_menu.left", "left()"),
("display_menu.right", "right()"),
("display_menu.enter", "enter()"),
("display_menu.show", "show()"),
("display_menu.hide", "hide()"),
("display_menu.show_main", "show_main()"),
):
automation.register_apply_action(
_name, MENU_ACTION_SCHEMA, automation.ApplyCall(_call)
)
automation.register_apply_condition(
"display_menu.is_active", MENU_ACTION_SCHEMA, "is_active()"
)
async def menu_up_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
"display_menu.down", DownAction, MENU_ACTION_SCHEMA, synchronous=True
)
async def menu_down_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
"display_menu.left", LeftAction, MENU_ACTION_SCHEMA, synchronous=True
)
async def menu_left_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
"display_menu.right", RightAction, MENU_ACTION_SCHEMA, synchronous=True
)
async def menu_right_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
"display_menu.enter", EnterAction, MENU_ACTION_SCHEMA, synchronous=True
)
async def menu_enter_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
"display_menu.show", ShowAction, MENU_ACTION_SCHEMA, synchronous=True
)
async def menu_show_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
"display_menu.hide", HideAction, MENU_ACTION_SCHEMA, synchronous=True
)
async def menu_hide_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
"display_menu.show_main",
ShowMainAction,
MENU_ACTION_SCHEMA,
synchronous=True,
)
async def menu_show_main_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_condition(
"display_menu.is_active",
IsActiveCondition,
automation.maybe_simple_id(
{
cv.GenerateID(CONF_ID): cv.use_id(DisplayMenuComponent),
}
),
)
async def display_menu_is_active_to_code(config, condition_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren)
async def menu_item_to_code(menu, config, parent):
@@ -5,94 +5,6 @@
namespace esphome::display_menu_base {
template<typename... Ts> class UpAction final : public Action<Ts...> {
public:
explicit UpAction(DisplayMenuComponent *menu) : menu_(menu) {}
void play(const Ts &...x) override { this->menu_->up(); }
protected:
DisplayMenuComponent *menu_;
};
template<typename... Ts> class DownAction final : public Action<Ts...> {
public:
explicit DownAction(DisplayMenuComponent *menu) : menu_(menu) {}
void play(const Ts &...x) override { this->menu_->down(); }
protected:
DisplayMenuComponent *menu_;
};
template<typename... Ts> class LeftAction final : public Action<Ts...> {
public:
explicit LeftAction(DisplayMenuComponent *menu) : menu_(menu) {}
void play(const Ts &...x) override { this->menu_->left(); }
protected:
DisplayMenuComponent *menu_;
};
template<typename... Ts> class RightAction final : public Action<Ts...> {
public:
explicit RightAction(DisplayMenuComponent *menu) : menu_(menu) {}
void play(const Ts &...x) override { this->menu_->right(); }
protected:
DisplayMenuComponent *menu_;
};
template<typename... Ts> class EnterAction final : public Action<Ts...> {
public:
explicit EnterAction(DisplayMenuComponent *menu) : menu_(menu) {}
void play(const Ts &...x) override { this->menu_->enter(); }
protected:
DisplayMenuComponent *menu_;
};
template<typename... Ts> class ShowAction final : public Action<Ts...> {
public:
explicit ShowAction(DisplayMenuComponent *menu) : menu_(menu) {}
void play(const Ts &...x) override { this->menu_->show(); }
protected:
DisplayMenuComponent *menu_;
};
template<typename... Ts> class HideAction final : public Action<Ts...> {
public:
explicit HideAction(DisplayMenuComponent *menu) : menu_(menu) {}
void play(const Ts &...x) override { this->menu_->hide(); }
protected:
DisplayMenuComponent *menu_;
};
template<typename... Ts> class ShowMainAction final : public Action<Ts...> {
public:
explicit ShowMainAction(DisplayMenuComponent *menu) : menu_(menu) {}
void play(const Ts &...x) override { this->menu_->show_main(); }
protected:
DisplayMenuComponent *menu_;
};
template<typename... Ts> class IsActiveCondition final : public Condition<Ts...> {
public:
explicit IsActiveCondition(DisplayMenuComponent *menu) : menu_(menu) {}
bool check(const Ts &...x) override { return this->menu_->is_active(); }
protected:
DisplayMenuComponent *menu_;
};
class DisplayMenuOnEnterTrigger final : public Trigger<const MenuItem *> {
public:
explicit DisplayMenuOnEnterTrigger(MenuItem *parent) : parent_(parent) {
-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 -29
View File
@@ -3,16 +3,12 @@ import esphome.codegen as cg
from esphome.components import i2c, time
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.core import ID
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.types import ConfigType
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(
@@ -22,46 +18,25 @@ CONFIG_SCHEMA = time.TIME_SCHEMA.extend(
).extend(i2c.i2c_device_schema(0x68))
@automation.register_action(
automation.register_apply_action(
"ds1307.write_time",
WriteAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(DS1307Component),
}
),
synchronous=True,
automation.ApplyCall("write_time()"),
)
async def ds1307_write_time_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])
return var
@automation.register_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()"),
)
async def ds1307_read_time_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])
return var
async def to_code(config: ConfigType) -> None:
@@ -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
+15 -82
View File
@@ -1,10 +1,4 @@
from esphome.automation import (
Action,
Condition,
maybe_simple_id,
register_action,
register_condition,
)
from esphome import automation
import esphome.codegen as cg
from esphome.components import binary_sensor, sensor
import esphome.config_validation as cv
@@ -19,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"
@@ -29,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", 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", Condition, cg.Parented
)
CONFIG_SCHEMA = cv.All(
@@ -86,76 +70,25 @@ async def to_code(config: ConfigType) -> None:
# AUTOMATIONS
DUTY_TIME_ID_SCHEMA = maybe_simple_id(
DUTY_TIME_ID_SCHEMA = automation.maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(DutyTimeSensor),
}
)
@register_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()"
)
async def sensor_runtime_start_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])
return var
@register_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"
)
async def sensor_runtime_stop_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])
return var
@register_action(
"sensor.duty_time.reset", ResetAction, DUTY_TIME_ID_SCHEMA, synchronous=True
)
async def sensor_runtime_reset_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])
return var
@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)
@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
Loaded 100 of 842 files, more files were not shown because too many files have changed in this diff. Show more