Compare commits

...
Author SHA1 Message Date
J. Nick Koston ca54b92204 Merge remote-tracking branch 'origin/dev' into apply-entities-switch 2026-10-19 07:52:32 -10:00
J. Nick Koston 85dd0f71b3 [core] Add Apply entities so hub forwarding buttons, switches, numbers and selects need no class (#20935) 2026-10-19 07:50:56 -10:00
J. Nick Koston 7cf72c287d [web_server] Pass the listing flag as a bool and share the state event send (#21112) 2026-10-19 07:50:39 -10:00
J. Nick Koston dc56b27f33 [api] Send the device info strings from flash and keep the version out of RAM (#21106) 2026-10-19 07:50:21 -10:00
J. Nick Koston 384c2ec26c [ci] Extend the hardware-test skill with the audio, globals, outgoing connection and Home Assistant light flows (#21104) 2026-10-19 07:49:34 -10:00
Jonathan Swoboda 6868011336 [combination][speaker_source][const] Share CONF_SOURCES from components/const (#21129) 2026-10-19 07:49:05 -10:00
J. Nick Koston 8ffdbfd4ce [homeassistant] Judge a light report against the transition it arrived during (#21100) 2026-10-19 07:48:32 -10:00
J. Nick Koston 1cd5641900 [web_server] Keep the remaining ESP8266 web server strings in flash (#21113) 2026-10-19 12:25:30 -04:00
Clyde Stubbs 15b988be9c [cst9220] Fix long press reported as several short presses (#21034) 2026-10-19 20:44:22 +11:00
Clyde Stubbs 2d0e3faa3d [st7305] Add reflective LCD display driver (#21099) 2026-10-19 19:57:05 +11:00
esphome[bot] 2087d28684 Bump aioesphomeapi from 46.12.0 to 46.14.0 (#21114) 2026-10-19 08:07:50 +00:00
b919b692f7 [scd4x] Fix ambient_pressure_compensation unit handling (#19888)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-10-18 21:54:33 -10:00
Clyde Stubbs 4498ad2c77 [core] Warn when the .esphome build tree is tracked by git (#18973) 2026-10-19 18:38:47 +11:00
esphome[bot] 2addbaaa7f [ci] Refresh integration test durations (#21105) 2026-10-18 21:31:16 -10:00
1dc4006bfc [usb_audio] Add USB Audio Class (UAC) host support (#11728)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-18 17:44:59 -10:00
Clyde StubbsandClaude Opus 5.5 4d4ba5a1bc [core] Move CONF_MAX_PACKET_SIZE to components/const (#21102)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:37:35 -10:00
Jesse Hills 0e8e45c528 [mipi_spi] Fix GC9107 native size and add M5Stack AtomS3 model (#21087) 2026-10-19 16:35:15 +13:00
J. Nick Kostonandpre-commit-ci-lite[bot] 28e43235fa [gpio_expander][pcf8574][mpr121][ch422g][ch423][pcm5122][weikai] Move the GPIO expanders and pin providers onto the shared pin helpers (#20868)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-10-18 16:27:01 -10:00
J. Nick Koston ded5ad6cb1 [wifi] Build the wifi actions and conditions with the apply helpers (#20842) 2026-10-18 15:23:31 -10:00
J. Nick Koston ab656acbf1 [api] Build api.connected with register_apply_condition (#20836) 2026-10-18 15:23:19 -10:00
J. Nick Koston 52390ebbe4 [ethernet] Build the ethernet automations with the apply helpers (#20845) 2026-10-18 15:22:23 -10:00
J. Nick Koston 8d6b40f735 [components] Use Component::status_comm_result for communication failures (#20864) 2026-10-18 15:19:53 -10:00
J. Nick Koston 257a636099 [rx8025t][rx8130][rx8111][ufm01] Use the shared BCD helpers (#20861) 2026-10-18 15:17:24 -10:00
J. Nick Koston b53b3968b4 [light] Build light.toggle with register_apply_action (#20833) 2026-10-18 15:10:02 -10:00
J. Nick Koston 73ba726838 [ci] Extend the hardware-test skill with the flows learned since it was added (#21011) 2026-10-18 15:09:01 -10:00
J. Nick Koston 490d4723ca [ota] Log why the upload switched to UDP (#20996) 2026-10-18 15:08:46 -10:00
Jesse Hills a6dee800d1 Merge branch 'beta' into dev 2026-10-19 14:03:21 +13:00
Jesse Hills 2001dd2be8 Merge pull request #21097 from esphome/bump-2026.10.0b5
2026.10.0b5
2026-10-19 14:03:02 +13:00
J. Nick Koston c793ab30a7 [core][gpio_expander] Add a shared expander pin class and pin codegen helpers (#20866) 2026-10-18 14:48:35 -10:00
Clyde StubbsandClaude Opus 5.5 1753c45e53 [core] Move CONF_PROVIDER and CONF_REMOTE_ID to components/const (#21096)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 14:47:37 -10:00
J. Nick Koston 1767e4b0be [sensor] Add SensorWithDedup for child sensors that skip repeated values (#20925) 2026-10-18 14:47:19 -10:00
J. Nick Koston 5575dd8928 [core] Add final_validate.get_declaring_config (#20912) 2026-10-18 14:46:57 -10:00
J. Nick Koston 704dd4b445 [components] Report communication failures with status_comm_result in the remaining components (#21079) 2026-10-19 00:17:57 +00:00
e6ad995c8e [i2s_audio] Add ESP32-S31 support (#20115)
Co-authored-by: cosmicDustOfLightLength <cosmicDustOfLightLength@example.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-19 00:07:52 +00:00
J. Nick Koston 84ce39e493 [components] Use the codegen helpers in the remaining components (#21078) 2026-10-18 14:05:37 -10:00
J. Nick Koston d9242520e3 [components] Decode bytes with the shared helpers (#21080) 2026-10-18 20:00:48 -04:00
J. Nick Koston 03bf2de7df [components] Decode BLE fields with the shared helpers (#21082) 2026-10-18 19:59:19 -04:00
J. Nick Koston a70d75e469 [components] Read UART bytes with read_available and discard_available (#21084) 2026-10-18 13:59:09 -10:00
J. Nick Koston 2dcb97774d [tests] Add an esp32-s31-idf base file and shared i2c and i2s_audio packages (#21086) 2026-10-18 19:54:48 -04:00
Jacopo Labardi be9741c6de [const] Share CONF_VALUE_TYPE across components (#21062) 2026-10-18 13:02:22 -10:00
Jesse Hills a6a4b49e75 Bump version to 2026.10.0b5 2026-10-19 11:29:05 +13:00
Bonne EgglestonandJ. Nick Koston 649f2c91f5 [api] Poll the outgoing dial every loop (#21085)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-19 11:29:02 +13:00
Bonne Eggleston ffa20abe96 [api] Poll the outgoing dial every loop on deep sleep builds (#21077) 2026-10-19 11:29:02 +13:00
J. Nick Koston f979ddcf0d [bl0906] Read stale receive bytes instead of spinning on flush() (#21035) 2026-10-19 11:29:02 +13:00
J. Nick Koston 26064f1415 [uart_tcp] Expose the connection entities as platforms (#21008) 2026-10-19 11:29:02 +13:00
ab-tools b1c138c612 [audio] Compute the remaining AudioStreamInfo conversions in 64 bits (#21092) 2026-10-18 12:12:50 -10:00
ab-tools 9526233821 [audio] Compute frames_to_microseconds() in 64 bits (#21089) 2026-10-18 11:39:37 -10:00
J. Nick Koston c50ee215f2 [tcp_uart] Expose the connection entities as platforms (#21009) 2026-10-19 10:36:23 +13:00
Jonathan Swoboda 0fbba03522 [debug] Log uptime in dump_config (#20989) 2026-10-19 10:33:13 +13:00
Jonathan Swoboda 7ccaedfb9e [esp32] Bump recommended ESP-IDF to 5.5.6 (#20987) 2026-10-19 10:33:13 +13:00
esphome[bot] 0173127239 Bump bundled esphome-device-builder to 1.24.0 (#20851)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-10-19 10:33:13 +13:00
Clyde Stubbs 235c614e68 [epaper_spi] Ensure test card gets drawn (#20804) 2026-10-19 10:33:13 +13:00
Jonathan Swoboda 79823c89ab [mipi_rgb] Rotate pure RGB panels in software (#20757) 2026-10-19 10:33:13 +13:00
J. Nick Koston e09cb64756 [bluetooth_connection] Value-initialize the connection parameters for ESP-IDF 6.1 (#20774) 2026-10-19 10:33:13 +13:00
J. Nick Koston bd5b3fa641 [esp32_ble_client] Value-initialize the connection parameters for ESP-IDF 6.1 (#20776) 2026-10-19 10:33:13 +13:00
Kevin AhrendtandJ. Nick Koston f464994f85 [api][sendspin] Add the Sendspin client id to the capabilities message (#20775)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-19 10:33:13 +13:00
esphome[bot] 4552dbad7a Bump aioesphomeapi from 46.11.0 to 46.12.0 (#20781)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-10-19 10:33:13 +13:00
J. Nick Koston ad8cd0dd3e [noise] Initialize the protocol id's reserved field for ESP-IDF 6.1 (#20779) 2026-10-19 10:33:13 +13:00
puddlyandJ. Nick Koston dd2582bdfd [usb_uart] Expose interface_class and port_number (#20659)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-19 10:33:13 +13:00
J. Nick Koston ba575600dc [mqtt] Match subscription topics in a loop instead of recursing per character (#20767) 2026-10-19 10:33:13 +13:00
esphome[bot] cd6c0dd402 Bump aioesphomeapi from 46.10.0 to 46.11.0 (#20766)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-10-19 10:33:13 +13:00
4645d34e1a [web_server_idf] Keep active SSE sessions out of the LRU purge (#17383)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-19 10:33:13 +13:00
dff1422e36 [mdns] Advertise the connected interface in the network TXT record (#20673)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-19 10:33:13 +13:00
Bonne EgglestonandJ. Nick Koston 512e43793b [api] Poll the outgoing dial every loop (#21085)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-18 20:47:02 +00:00
603f16623f [usb_host] Size the FIFO split on ESP32-S2/S3 from periodic packet requests (#21081)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-18 20:44:05 +00:00
218e696654 [globals] Add RTC memory support (#13954)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.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>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-18 10:40:55 -10:00
Clyde Stubbs 3e68e0f5e1 [epaper_spi] Add the UC8151D controller and the Waveshare 2.13" B V3 (#21022) 2026-10-19 07:40:16 +11:00
dd456ea0b2 [homeassistant] Add the light platform (#11354)
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-10-18 10:02:31 -10:00
Bonne Eggleston e54d66e620 [api] Poll the outgoing dial every loop on deep sleep builds (#21077) 2026-10-18 09:36:35 -10:00
J. Nick Koston a3bb17d2c6 [components] Create BLE and modbus devices with the new helpers (#21026) 2026-10-18 15:09:58 -04:00
71c0f46e35 [bme68x_bsec2] disable loop (#20994)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-18 08:33:17 -10:00
J. Nick Koston a153fcc8ee [bl0906][kamstrup_kmp][ufm01] Drop stale receive bytes with discard_available (#21071) 2026-10-18 14:30:10 -04:00
85724892cf [light] Add power estimation and current limiting for addressable LED strips (#15348)
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-10-18 08:08:31 -10:00
J. Nick Koston deea006b39 [components] Set referenced components with cg.add_variable and register_parented (a-l) (#21021) 2026-10-18 17:43:05 +00:00
J. Nick Kostonandpre-commit-ci-lite[bot] 619728b686 [components] Drop stale UART receive bytes with discard_available (#21030)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-10-18 07:23:06 -10:00
J. Nick Koston c35d98b622 [core] Add progmem_string_table_find() (#20790) 2026-10-18 07:19:14 -10:00
J. Nick Koston b2d6c78499 [light] Add LightState::set_external_state() for outputs that mirror a light elsewhere (#20787) 2026-10-18 07:18:47 -10:00
J. Nick KostonandJonathan Swoboda a801e414a0 [components] Set referenced components with cg.add_variable and register_parented (m-z) (#21023)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-10-18 13:32:06 +00:00
J. Nick Koston 22a36de4ae [components] Decode little-endian I2C values with the new helpers (#20939) 2026-10-18 09:20:58 -04:00
J. Nick Koston eca95348ac [components] Assemble 24-bit readings with encode_uint24 (#21016) 2026-10-18 09:16:50 -04:00
J. Nick KostonandJonathan Swoboda ed9616d512 [components] Set pins with cg.add_pin (#21019)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-10-18 09:10:22 -04:00
J. Nick Koston 0cd1ae01f1 [components] Use cv.uint8_t and cv.uint16_t for full byte ranges (#21015) 2026-10-18 08:52:14 -04:00
J. Nick Koston 2a416fbce6 [core] Document that hub entities go in platforms in AGENTS.md (#21003) 2026-10-18 08:45:21 -04:00
J. Nick Koston 270d989481 [core] Add cg.add_pin and cg.add_variable for optional pins and ids (#21018) 2026-10-18 08:41:33 -04:00
J. Nick Koston 3665bcc4e3 [ble_device_base][ble_client][modbus] Add new_ble_device_component, new_ble_node_component and new_modbus_client_component (#21025) 2026-10-18 08:40:35 -04:00
J. Nick Koston 0922df9bb3 [uart] Add discard_available to drop stale receive bytes (#21029) 2026-10-18 08:39:02 -04:00
J. Nick Koston 54a28b3e3b [bl0906] Read stale receive bytes instead of spinning on flush() (#21035) 2026-10-18 08:37:48 -04:00
Michele PorelliandJ. Nick Koston 032a2b7200 [core] Let entities choose flash or RTC storage for their preferences (#21017)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-18 06:58:40 +00:00
J. Nick Koston 0bb78186f2 [components] Set display lambdas with display.add_writer (#21028) 2026-10-17 18:34:38 -10:00
J. Nick Koston 2fe1333cba [display] Add add_writer for display lambdas (#21027) 2026-10-17 18:34:38 -10:00
J. Nick Koston 265754c467 [components] Drop get_setup_priority overrides that return the default (#21014) 2026-10-17 17:43:16 -10:00
6580f4f314 [micro_wake_word] Allow configurable capture buffer duration (#20942)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-17 16:43:07 -10:00
J. Nick Koston 5ad63e3bb6 [http_request] Only treat a URL as secure when its scheme is https (#20867) 2026-10-18 02:38:07 +00:00
J. Nick Koston a8ab035b59 [lock] Filter on_lock and on_unlock with when (#20858) 2026-10-17 16:33:05 -10:00
J. Nick Koston 968d57c56b [core][time] Add BCD helpers and a shared RTC BCD calendar codec (#20860) 2026-10-17 16:32:13 -10:00
J. Nick Koston 1307faef06 [core] Add Component::status_comm_result (#20863) 2026-10-17 16:31:17 -10:00
ee117886b9 [vevor_dtx] Add Vevor weather station component (#17224)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-17 15:42:55 -10:00
1dbad7b951 [airton] Add support to climate for Airton IR A/C units (#7753)
Co-authored-by: Arjan <44190435+vingerha@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-18 01:37:58 +00:00
J. Nick Koston 5df6c65cc2 [api] Test api.connected without options and with a lambda (#20835) 2026-10-17 15:23:35 -10:00
J. Nick Koston af5efabdc6 [light] Test light.toggle with a transition length (#20832) 2026-10-17 15:23:22 -10:00
J. Nick Koston a2754f655c [dfrobot_sen0609][http_request] Use ESPHOME_strstr_P (#20825) 2026-10-17 15:23:06 -10:00
J. Nick Koston 34e6d3ec9c [pylontech] Restore the CONF_ID import (#21013) 2026-10-17 15:21:36 -10:00
J. Nick Koston 21362f8c92 [ethernet] Test the ethernet.connected condition (#20844) 2026-10-17 15:21:18 -10:00
J. Nick Koston 626d84bc64 [sensor][number] Test in_range with every bound combination (#20847) 2026-10-17 15:20:52 -10:00
J. Nick Koston 68fa7b9847 [wifi] Test the wifi.connected and wifi.enabled conditions (#20841) 2026-10-17 15:20:00 -10:00
J. Nick Koston 32dc3b8535 [http_request] Test ota.http_request.flash with every field (#20829) 2026-10-17 15:19:24 -10:00
J. Nick Koston 74bcec34ad [i2c] Add I2CDevice::update_register for register read-modify-write (#20854) 2026-10-17 15:18:36 -10:00
J. Nick Koston c1687aba4c [components] Let core report communication failures in dump_config (5/5) (#20874) 2026-10-17 15:16:21 -10:00
J. Nick Koston ec642f928f [i2c] Add little-endian and signed byte decoding helpers (#20938) 2026-10-17 15:15:36 -10:00
J. Nick Koston ec9a207f42 [components] Create UART devices with new_uart_component (#20919) 2026-10-17 15:15:21 -10:00
J. Nick Koston 3b7847d951 [components] Create SPI devices with new_spi_component (#20920) 2026-10-17 21:13:49 -04:00
J. Nick Koston 1197f22f00 [fusb302b] Expose the sensors as platforms (#21004) 2026-10-17 15:09:49 -10:00
J. Nick Koston e034662dc6 [uart_tcp] Expose the connection entities as platforms (#21008) 2026-10-17 15:09:32 -10:00
J. Nick Koston 42e849967a [bq76972] Expose the sensors as a sensor platform (#21006) 2026-10-17 15:09:14 -10:00
J. Nick Koston e6f1ed8d51 [tcp_uart] Expose the connection entities as platforms (#21009) 2026-10-17 15:07:42 -10:00
Igor StadnykandJ. Nick Koston bb9067acd7 [tfluna] Recover from transient runtime reconfiguration failures (#20992)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-17 15:07:16 -10:00
Michele Porelli 928cf7d696 [libretiny] Emulate rtc preference storage on the Beken BK7231T, BK7231N and BK7238 with flash and the reset reason (#21007) 2026-10-17 14:59:40 -10:00
89afb111ad [mt6701] Add MT6701 (MagnTek magnetic encoder) component (#17643)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-17 14:06:46 -10:00
Brandon HarveyandJ. Nick Koston d1ec52e95a [sen6x] Add device status binary sensors (#18784)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-17 23:34:15 +00:00
Bonne Egglestonandclydebarrow 818d43f742 [epaper_spi] Run the loop continuously between the steps of an update (#20554)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
2026-10-18 10:25:36 +11:00
1a28a9220b [uart] Add a binary sensor that fires on a byte sequence (#14032)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-17 22:35:46 +00:00
Brandon HarveyandJ. Nick Koston 022733c46b [sen6x] Add PM number concentration sensors (#18782)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-17 12:16:45 -10:00
0f6fc503bd [sen6x] Add temperature compensation, acceleration and startup delay (#18781)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-17 11:01:14 -10:00
Bonne Egglestonandclydebarrow ae62960193 [epaper_spi] Add the UC8279 controller and the Xteink X4 Pro (#20545)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
2026-10-18 07:51:07 +11:00
325c747dcd [sen6x] Add CO2 configuration options (#18780)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-17 10:41:13 -10:00
Brandon HarveyandJ. Nick Koston 5759c6b8e6 [deep_sleep] Honor nested wakeup_pin_mode on ESP32 single-pin configs (#16618)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-17 10:25:40 -10:00
c9ba6c355c [hoermann_hcp] Add an actuator error binary sensor (#20299)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-17 10:00:31 -10:00
Brandon HarveyandJ. Nick Koston d02da3c8de [wifi] Support hidden option in single-network shorthand (#18157)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-17 09:54:55 -10:00
Bascht74andJ. Nick Koston a576092f25 [remote_base] Send the 72-bit Jarolift TDEF group frame with transmit_keeloq (#20927)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-17 09:52:01 -10:00
Simonas Kazlauskas 792d650fb2 [remote_base] Add a protocol for 433MHz A-OK remotes (#20991) 2026-10-17 09:44:25 -10:00
f72857151a [pylontech] Add individual cell voltage and temperature sensors (#16852)
Co-authored-by: Michael <michael_e@gmx-topmail.com>
Co-authored-by: ritschys <117527311+ritschys@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-17 19:28:52 +00:00
5f8f075e76 [zephyr_coredump] Store a coredump in flash on nRF52 and print it to the next log reader (#13122)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-17 19:22:28 +00:00
Matias FernandezandJ. Nick Koston c2c8cb5eb3 [climate_ir_lg] Fix swing command to not include climate state (#17417)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-17 09:12:35 -10:00
J. Nick Koston 5c2b05d0eb [ci] Run the C++ unit tests on macOS without a sanitizer whose runtime aborts (#21000) 2026-10-17 09:00:51 -10:00
Igor StadnykandJ. Nick Koston e110613ef1 [core] Construct App once in the C++ unit test main (#21001)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-17 19:00:37 +00:00
luar123 4324b7d084 [zigbee] bump esp-zigbee-sdk to 2.0.5 (#20998) 2026-10-17 08:58:49 -10:00
37d6140193 [epaper_spi] Fix partial refresh and port waveshare 2.90inv2 (#18959)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
Co-authored-by: luar123 <49960470+luar123@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-17 08:55:37 -10:00
4f192130f8 [water_heater] Hand the component to the core team (#20999)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-17 18:51:06 +00:00
Hasan Khalilandpre-commit-ci-lite[bot] 4ef17cebdb [api] Pre-reserve Home Assistant state subscriptions vector (#20986)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-10-17 08:01:01 -10:00
35276a38bc [rtsp_server] Add an RTSP server that streams the esp32_camera picture (#17990)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-17 07:18:52 -10:00
J. Nick Koston 635df9c6ad [socket] Share the allow-list reject log and config lines through Ipv4Allow (#20983) 2026-10-17 06:51:29 -10:00
Jonathan Swoboda d16d8c111e [debug] Log uptime in dump_config (#20989) 2026-10-17 06:50:54 -10:00
Jonathan Swoboda 1ead61f11f [esp32] Remove trailing comment from ECDSA test signing key (#20988) 2026-10-17 06:50:11 -10:00
J. Nick Koston bd06d37d1a [components] Check I2C chip IDs with check_chip_id (#20943) 2026-10-17 06:49:46 -10:00
Jonathan Swoboda 7c6fac91ff [esp32] Bump recommended ESP-IDF to 5.5.6 (#20987) 2026-10-17 06:49:15 -10:00
J. Nick Koston 052a6096e6 [components] Log hex dumps with the ESP_LOG*_HEX macros (#20932) 2026-10-17 16:46:16 +00:00
J. Nick Koston 25b684f762 [pylontech] Parse each line as it arrives with one fixed buffer (#20959) 2026-10-17 06:44:41 -10:00
J. Nick Koston 62f8cc3436 [uart] Add read_available helpers to drain available bytes in chunks (#20947) 2026-10-17 06:41:51 -10:00
J. Nick Koston 34f9152d61 [i2c] Add write_registers for constant register tables (#20945) 2026-10-17 06:41:20 -10:00
luar123 73af6b0500 [core][scheduler] run process_to_add after component phase (#20993) 2026-10-17 06:40:39 -10:00
J. Nick Koston a384aae5b1 [button] Add factory reset, restart and query button schema presets (#20915) 2026-10-17 06:38:45 -10:00
J. Nick Koston 905267a9c3 [esp32_ble] Build ble.enabled, ble.enable and ble.disable with the apply helpers (#20839) 2026-10-17 06:38:25 -10:00
J. Nick Koston d08c845c65 [components] Create I2C devices with new_i2c_component (#20909) 2026-10-17 06:36:49 -10:00
J. Nick Koston 91dfb92c60 [esp32_ble] Test ble.enabled, ble.enable and ble.disable (#20838) 2026-10-17 09:22:29 -04:00
J. Nick Koston cef9418a0f [i2c][uart][spi] Add new_i2c_component, new_uart_component and new_spi_component (#20908) 2026-10-17 09:20:28 -04:00
J. Nick Koston c0a693dfce [components] Use xor_bytes and sum16_bytes for checksums (#20922) 2026-10-17 09:18:24 -04:00
Clyde Stubbs 1b4721d445 [core] Script improvements including choose (#20547) 2026-10-17 21:23:12 +11:00
f8d31c3e54 [dfrobot_sen0465] Add DFRobot SEN0465-SEN0476 electrochemical gas sensors (#20880)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-16 22:47:50 -10:00
eafcca8274 [mics_5524] Add datasheet model, diagnostics and advanced wiring (2/2) (#20521)
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-10-16 22:47:28 -10:00
Remco van Essen 06b585d373 [i2s_audio] Fix speaker restart loop with full_duplex (#20984) 2026-10-16 22:05:21 -10:00
HugoPeters1024andJ. Nick Koston 885f2fe5d8 [kamstrup_kmp] Fix Kamstrup KMP reads with echoing optical heads (#20897)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-16 22:04:01 -10:00
J. Nick Koston 4e27b2fc12 [components] Collect UART lines with LineReader (#20956) 2026-10-16 19:54:11 -10:00
J. Nick Koston 375900bfcd [core] Add LineReader to collect lines in a fixed buffer (#20952) 2026-10-16 19:54:11 -10:00
Zhaoqi Xu 6d664d3fef [web_server] Report numbers without a state as NA (#20973) 2026-10-16 19:15:04 -10:00
J. Nick Koston 4dc2b5911b [core] Move CONF_DRIVE_STRENGTH to the shared component constants (#20975) 2026-10-16 18:21:04 -10:00
tomaszduda23 687466b6cd [logger][usb_cdc_acm][core] Add CDC uart to NRF52 (#11634) 2026-10-16 18:14:51 -10:00
J. Nick Koston dcdc042a4b [host] Define a no-op InterruptLock (#20974) 2026-10-16 17:53:33 -10:00
d2a1f7ee31 [zephyr] Fix preference log levels and report settings read/save errors (#20810)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-17 03:02:54 +00:00
J. Nick Koston a5a883fd19 [core] Add xor_bytes and sum16_bytes checksum helpers (#20921) 2026-10-16 15:57:00 -10:00
J. Nick Koston 7671d4410c [i2c] Add check_chip_id helper (#20941) 2026-10-16 15:55:53 -10:00
J. Nick Koston 298bd5ec72 [core] Add ESP_LOGV_HEX, ESP_LOGVV_HEX and ESP_LOGD_HEX (#20931) 2026-10-16 15:55:28 -10:00
J. Nick Koston b8afcdfb3c [i2c] Write 16-bit registers without a heap allocation (#20907) 2026-10-16 15:54:53 -10:00
luar123andJ. Nick Koston 9031df757b [core][scheduler] add missing process_to_add call (#20961)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-17 01:33:29 +00:00
J. Nick Koston 107bebebad [components] Use sign_extend for raw sensor readings (#20934) 2026-10-16 15:24:45 -10:00
J. Nick Koston 1147b024d6 [core] Add sign_extend helper (#20933) 2026-10-16 21:17:59 -04:00
J. Nick Koston 36ba2f6106 [components] Build enum option dicts with options() (#20911) 2026-10-16 14:56:34 -10:00
J. Nick Koston bb0eabf241 [core] Add options() to C++ enums (#20910) 2026-10-16 14:56:34 -10:00
Frédéric MetrichandClaude d9969dd533 [teleinfo] Skip sensor values that are not numbers instead of publishing 0 (#20960)
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-16 14:55:08 -10:00
Clyde Stubbs 51ae3bb642 [ci] Rename merge conflict label to has-conflicts (#20969) 2026-10-16 14:31:06 -10:00
J. Nick Koston e21d3217bd Merge branch 'apply-entities' into apply-entities-switch 2026-10-16 14:07:10 -10:00
J. Nick Koston 754a960ed1 [core] Declare apply locals right before their first use
A local declared at the top ran option_at before a publish placed before
the statement, which changed the call order and grew ld2412 by 16 bytes.
2026-10-16 14:06:17 -10:00
J. Nick Koston 49337d8ad3 Merge branch 'apply-entities' into apply-entities-switch 2026-10-16 14:04:31 -10:00
J. Nick Koston f7a60f5859 [core] Give select apply statements an option local
Select apply statements can name the selected option text as option; the
local is declared only when a statement uses it.
2026-10-16 14:04:26 -10:00
b3ef5cf2f3 [online_image] Report an error when a download can never complete (#20594)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: guillempages <guillempages@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-16 23:58:12 +00:00
J. Nick Koston 2edef5cfd9 [seeed_mr24hpc1] Use Apply switches 2026-10-16 13:46:00 -10:00
J. Nick Koston 825e71c74a [ld2450] Use Apply switches 2026-10-16 13:46:00 -10:00
J. Nick Koston 3275f6e4a8 [ld2412] Use Apply switches 2026-10-16 13:46:00 -10:00
J. Nick Koston 3e393631eb [ld2410] Use Apply switches 2026-10-16 13:45:59 -10:00
J. Nick Koston c6bfc403b5 [at581x] Use Apply switches 2026-10-16 13:45:59 -10:00
J. Nick Koston b8a0de91db [core] Simplify the Apply entity helpers
Fold the function cache into SubEntities, make each domain's APPLY_ENTITY public,
shorten the class and docstring comments and reuse the shared test hub.
2026-10-16 13:42:23 -10:00
Hasan Khalil b3f5789dc6 [mipi_rgb] direct-to-PSRAM fast rotated blitting (#20905) 2026-10-17 10:39:44 +11:00
J. Nick Koston 1ddacb4931 [tests] Set i2c_id in the component tests that clash with tca9548a (#20963) 2026-10-16 13:24:34 -10:00
J. Nick Koston 2937d81216 Mark the test recorders as intentional mutable globals 2026-10-16 12:20:50 -10:00
J. Nick Koston 05a4b256db Reject an unknown publish= value 2026-10-16 11:45:47 -10:00
J. Nick Koston d0427c36eb [core] Share the apply function naming with automations and check the Apply class at codegen 2026-10-16 11:04:36 -10:00
J. Nick Koston a990231342 [core] Add Apply button, switch, number and select entities for hub forwarders 2026-10-16 10:48:29 -10:00
Igor StadnykandJ. Nick Koston 10d3ced897 [core] Parse unit values as one decimal so listed floats match, fixing at581x power consumption (#20889)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-16 10:11:56 -10:00
Bascht74 6fa7b87e6a [lp5562] Add the current per channel and logarithmic dimming (#20903) 2026-10-16 10:04:45 -10:00
c0b7c5e590 [dfrobot_sen0395] Forward trigger arguments in settings actions (#20885)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-16 10:03:07 -10:00
dependabot[bot] 57dd09018e Bump yamlrocks from 0.6.1 to 0.7.0 (#20904)
Signed-off-by: dependabot[bot] <support@github.com>
2026-10-16 10:00:58 -10:00
Clyde Stubbs 04aa59563d [display] Drop the color order from draw_pixels_at() and ColorUtil (#20884) 2026-10-17 06:45:03 +11:00
9440012413 [mics_5524] Add MiCS-5524 gas sensor platform (1/2) (#19721)
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-10-16 09:14:06 -10:00
Bascht74 38db8c1554 [ci] Fail a hung integration test instead of waiting for the job limit (#20902) 2026-10-16 09:00:55 -10:00
ab0c32c2aa [haier_water_heater] Initial water-heater implementation tested with Haier VH3W (#20765)
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-10-16 08:50:13 -10:00
cd6a34401b [web_server] Do not match internal entities by URL unless include_internal is set (#20883)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-16 08:22:46 -10:00
Igor StadnykandJ. Nick Koston 5ab92ed363 [ld2450] Handle footer bytes in zone query responses (#20852)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-16 08:15:17 -10:00
1368ce4342 [dfrobot_sen0609] Add restart, factory reset and query buttons (#20877)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-16 17:31:58 +00:00
Remco van Essen dc0d79635b [ld2410][ld2412] Move CONF_QUERY_PARAMS to the shared constants (#20878) 2026-10-16 07:12:49 -10:00
J. Nick Koston 1ff6b29b0d [components] Let core report communication failures in dump_config (4/5) (#20873) 2026-10-16 08:41:52 -04:00
J. Nick Koston 49caeae780 [components] Let core report communication failures in dump_config (3/5) (#20872) 2026-10-16 08:40:54 -04:00
J. Nick Koston e1a6c615b1 [core] Document passing the failure reason to mark_failed in AGENTS.md (#20879) 2026-10-16 08:40:06 -04:00
J. Nick Koston e95e56f100 [components] Let core report communication failures in dump_config (2/5) (#20871) 2026-10-16 08:39:20 -04:00
J. Nick Koston 126dc8b0b2 [components] Let core report communication failures in dump_config (1/5) (#20870) 2026-10-16 08:38:55 -04:00
esphome[bot] 3298988f5d Bump bundled esphome-device-builder to 1.24.0 (#20851)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-10-15 23:11:26 -10:00
802c2633ed [image] Draw rows in the outer loop and hand opaque RGB565 and RGB blocks to the display (#20653)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:38:58 +11:00
51b1fdcc8b [i2s_audio] Add full duplex support (#19959)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-15 21:35:46 -10:00
6555da2906 [display] Fall back per pixel when a bulk blit does not match the panel (#20652)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 16:57:11 +11:00
J. Nick Koston 6dbd15b903 [pca9505] Validate interrupt_pin with the shared expander validator (#20853) 2026-10-15 15:02:26 -10:00
Jonathan Swoboda 0399379c0d [lp5562] Pin the test fixture to the shared I2C bus (#20856) 2026-10-16 00:56:40 +00:00
Kevin Ahrendt 6817960365 [audio] Deprecate AudioSourceTransferBuffer (#20801) 2026-10-15 13:38:03 -10:00
Kevin Ahrendt cfdaf958e9 [audio][i2s_audio] Fix 16-bit mono playback at 44.1 kHz on the original ESP32 (#20806) 2026-10-15 13:32:35 -10:00
1e89abdb13 [http_request] Allow a per-request timeout (#20821)
Co-authored-by: p1ngb4ck <kleinecke.oliver@googlemail.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-10-15 13:17:46 -10:00
J. Nick Koston c838d4826e [core] Add ESPHOME_strstr_P (#20811) 2026-10-15 23:09:02 +00:00
J. Nick Koston 69215deb58 [core] Make the YAML load listener hook public (#20805) 2026-10-15 12:54:34 -10:00
J. Nick Koston 5b234bcc20 [wifi] Leave the cooldown from the loop after a credential save, not from the caller's context (#20802) 2026-10-15 12:53:51 -10:00
Panoplianandclydebarrow c101b3c101 [mipi_spi] Honour show_test_card when pages or a lambda are set (#20807)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
2026-10-15 22:38:28 +00:00
Anton ViktorovandJ. Nick Koston c0d8a0dfe9 [as734x] [3/3] Add illuminance, photopic irradiance, PAR, PPFD, colour temperature and RGB (#18012)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-15 11:38:15 -10:00
J. Nick Koston 27c6b7caf5 [ci] Add hardware-test agent skill (#20817) 2026-10-15 21:36:36 +00:00
699e3bd037 [usb_uart] Runtime modem pin control for USB and host (#20330)
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-10-15 11:03:27 -10:00
Anton ViktorovandJ. Nick Koston a58ce2d641 [as734x] [2/3] Add basic counts, calibration and saturation level (#18011)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-15 10:48:18 -10:00
Remco van EssenandClaude Opus 5.5 27d8d49b17 [dfrobot_sen0609] Add number, switch and select settings (#20813)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 10:41:20 -10:00
Remco van Essen 304627efd3 [ld2410][ld2412] Move CONF_OUT_PIN_LEVEL to the shared constants (#20820) 2026-10-15 10:34:00 -10:00
7ed06e0ada [as734x] [1/3] Add AS7341/AS7343/TCS3448 spectral sensor hub with counts sensors (#17038)
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-10-15 10:19:56 -10:00
dependabot[bot] 7e564c12dd Bump py7zr from 1.1.3 to 1.1.4 (#20816)
Signed-off-by: dependabot[bot] <support@github.com>
2026-10-15 09:58:07 -10:00
dependabot[bot] 6f6fdd0f8a Bump actions/create-github-app-token from 3.2.0 to 3.3.0 (#20815)
Signed-off-by: dependabot[bot] <support@github.com>
2026-10-15 09:55:47 -10:00
45ea5f4220 [nrf52] nrf52840dongle support (#19009)
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-10-15 09:03:53 -10:00
techdregsandJ. Nick Koston 108ed67d31 [rx8111] Add basic RX8111CE real-time clock support (#20785)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-15 08:30:25 -10:00
Remco van EssenandClaude Opus 5.5 6356458081 [dfrobot_sen0609] Add DFRobot SEN0609/SEN0610 radar (#20809)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 18:27:57 +00:00
Panoplian bf67b49104 [i2c] Let host unit tests with I2C build on macOS (#20738) 2026-10-15 07:47:52 -10:00
Panoplian 1947c3d355 [improv_ble] Start Improv again after a session has stopped (#20808) 2026-10-15 07:47:14 -10:00
J. Nick Koston cf4e69273c [shelly_dimmer] Keep the STM32 device table in flash (#20643) 2026-10-15 07:28:55 -10:00
J. Nick Koston c433e96734 [homeassistant] Share the Python list parser between the platforms (#20793) 2026-10-15 07:28:27 -10:00
J. Nick Koston 93dc80d895 [esp8266] Refuse two components that drive hardware timer 1 (#20794) 2026-10-15 07:28:07 -10:00
J. Nick Koston 7f342054fe [wifi] Retry a failed fallback AP once per ap_timeout and start the captive portal once (#20784) 2026-10-15 07:27:19 -10:00
Jeroenandjeroen85 d924d2daf7 [core] Extend fast value formatting to four decimal places (#20800)
Co-authored-by: jeroen85 <26403565+jeroen85@users.noreply.github.com>
2026-10-15 07:15:11 -10:00
Clyde Stubbs 49b12daa58 [epaper_spi] Ensure test card gets drawn (#20804) 2026-10-15 10:55:19 +00:00
Clyde Stubbs d3faafc578 [display] Always treat 888 pixel blocks as b,g,r in memory (#20799) 2026-10-15 20:49:50 +11:00
Remco van Essen 7b08e738d3 [fusb302b] Add sensors, contract text sensor and automations (#20796) 2026-10-14 22:13:58 -10:00
J. Nick Koston 62e3c25c83 [lvgl] Fix the menu test after lambdas gained a line reset (#20798) 2026-10-14 21:50:47 -10:00
6a50f5c166 [apc_proteous] Cover component for APC Proteous gate motors (#13403)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-15 17:46:40 +11:00
Clyde Stubbs bb0837d814 [lvgl] Implement menu widget (#18022) 2026-10-15 17:44:23 +11:00
Bonne Egglestonandclydebarrow ecd5980c5d [epaper_spi] Pass bits per pixel and plane count to the base constructor (#20683)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
2026-10-15 17:41:37 +11:00
8d963bde5b [openthread] Add ESP32 OpenThread Border Router support (#18104)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.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>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-14 20:02:33 -10:00
J. Nick Koston ea6d5499b3 [lsm6ds] Add the LSM6DSO and match all chips to their datasheets (#20733) 2026-10-14 19:24:00 -10:00
41e82b0295 [hdmi_cec] Add HDMI-CEC bus component (part 1: bus core) (#9189)
Co-authored-by: Stéphane Lepin <stephane.lepin@gmail.com>
Co-authored-by: Stéphane Lepin <Palakis@users.noreply.github.com>
Co-authored-by: Niko <nikokirste@hotmail.de>
Co-authored-by: Minims <github@minims.fr>
Co-authored-by: Nicholas Pace <segfly@users.noreply.github.com>
Co-authored-by: Nicholas Pace <3300476+segfly@users.noreply.github.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>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-15 04:16:09 +00:00
J. Nick Koston 39705a65e3 [bmi270] Keep the range name and scale tables in flash (#20715) 2026-10-14 17:44:46 -10:00
J. Nick Koston a522553e82 [lsm6ds] Keep the range and chip names in flash (#20718) 2026-10-14 17:43:54 -10:00
J. Nick Koston a07a1ab728 [qmi8658] Keep the range name tables in flash (#20714) 2026-10-14 17:43:34 -10:00
J. Nick Koston 9a5d2d9753 [api] Encode action strings straight from flash (#20425) 2026-10-14 17:38:39 -10:00
J. Nick Koston 3188e0d36f [ota] Fall back to a UDP data phase when the link is lossy (#20498) 2026-10-15 16:30:56 +13:00
Jonathan Swoboda f3eaf698d6 [mipi_rgb] Rotate pure RGB panels in software (#20757) 2026-10-14 22:05:01 -04:00
J. Nick Koston f7057e1270 [tests] Reset saved preferences before every C++ unit test (#20771) 2026-10-14 16:01:45 -10:00
J. Nick Koston 6edfbb594d [bluetooth_connection] Value-initialize the connection parameters for ESP-IDF 6.1 (#20774) 2026-10-14 22:00:07 -04:00
J. Nick Koston 22defcd704 [esp32_ble_client] Value-initialize the connection parameters for ESP-IDF 6.1 (#20776) 2026-10-14 21:59:56 -04:00
Jonathan Swoboda 0f408234d0 [core] Add ksz8851snl to the static analysis manifest for IDF 6 (#20788) 2026-10-14 21:53:17 -04:00
Jonathan Swoboda 92e0e6db6b [wifi] Call esp_supp_dpp_init without a callback on ESP-IDF 6 (#20789) 2026-10-15 01:40:07 +00:00
908c2f6778 [wifi] Add Wi-Fi Easy Connect (DPP) provisioning on the ESP32 (#17934)
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-10-14 14:29:47 -10:00
Keith BurzinskiandClaude Opus 5.5 af709ac9a5 [openthread] Fail instead of aborting when NVS is unavailable (#20717)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:30:36 -10:00
Panoplian fe57eef3f6 [core] Test that includes inside a package resolve next to the package (#20739) 2026-10-14 13:28:01 -10:00
d0b9f195b0 [wifi] Apply reboot_timeout with a fallback AP unless a station is connected to it (#9958)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-14 13:23:37 -10:00
eafeef7cb8 [fusb302b] Add FUSB302B driver (#20760)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-14 13:18:20 -10:00
471774cdbf [ecocomfort2] Fix the clock sync and read boost from the unit state (#20782)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-10-14 13:14:04 -10:00
Kevin AhrendtandJ. Nick Koston 4df6291feb [api][sendspin] Add the Sendspin client id to the capabilities message (#20775)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-14 22:56:51 +00:00
d8ca4a9460 [audio_dac] Add ES9038Q2M Katana platform (#15017)
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-10-14 22:50:41 +00:00
48a0a41cce [wifi] Implement access point WISP mode (#13633)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-14 12:49:37 -10:00
esphome[bot] 0e2c4cf5c2 Bump aioesphomeapi from 46.11.0 to 46.12.0 (#20781)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-10-14 12:43:07 -10:00
ea76073d86 [micro_wake_word] Allow selecting the inference task core (#20126)
Co-authored-by: n-IA-hane <n-IA-hane@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-14 12:42:24 -10:00
J. Nick Koston dcb943ded3 [noise] Initialize the protocol id's reserved field for ESP-IDF 6.1 (#20779) 2026-10-14 11:45:54 -10:00
dependabot[bot] 2c6f59738c Bump filelock from 4.1.0 to 4.1.1 (#20778)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-14 21:41:28 +00:00
puddlyandJ. Nick Koston 04715fb32b [usb_uart] Expose interface_class and port_number (#20659)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-14 21:41:12 +00:00
nullable.ethandJ. Nick Koston 3df9360876 [i2s_audio] Add keep_alive option to speaker to prevent pop/click on audio start (#15565)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-14 11:39:30 -10:00
dependabot[bot] 61f3de0bc0 Bump pylint from 4.1.2 to 4.1.3 (#20773)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-14 21:30:26 +00:00
dependabot[bot] cd97c9a9cb Bump platformdirs from 4.12.4 to 4.13.0 (#20772)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-14 21:29:17 +00:00
J. Nick Koston d3668a7186 [mqtt] Match subscription topics in a loop instead of recursing per character (#20767) 2026-10-14 11:13:32 -10:00
J. Nick Koston ac74796263 [friedrich] Clear saved preferences in the test fixture (#20770) 2026-10-14 10:52:13 -10:00
c56bb14037 [mqtt] Print crash info over MQTT (#17935)
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-10-14 10:39:55 -10:00
J. Nick Koston a4278b8dec [ci] Run benchmarks when a component the Python import benchmark imports changes (#20763) 2026-10-14 10:35:33 -10:00
Panoplian 6d173c7371 [bq25186] Set i2c_id in the component tests (#20742) 2026-10-14 10:35:01 -10:00
esphome[bot] 1779b011c9 Bump aioesphomeapi from 46.10.0 to 46.11.0 (#20766)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-10-14 20:21:45 +00:00
PanoplianandJ. Nick Koston 3c4cf50cb6 [core] Point compile errors after a lambda at the right line (#20740)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-14 10:02:51 -10:00
J. Nick Koston e869d1f7ab [ci] Give the BK72xx clang-tidy environment the static allocation flag the real build has (#20764) 2026-10-14 09:56:09 -10:00
e1a996435d [ld2450] Add polygon zones evaluated on the device (#20154)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-14 19:29:16 +00:00
Jonathan Swoboda c5a624a1f2 [tests] Count OTA key probe retries on a fake clock (#20761) 2026-10-14 15:21:35 -04:00
Remco van Essen 15fb7f775e [tmc2209] Add chopper and PWM tuning actions (#20753) 2026-10-14 08:59:21 -10:00
J. Nick Koston 3fa72e61c6 [mqtt] Build the mqtt triggers with build_trigger_callback (#20433) 2026-10-14 08:55:39 -10:00
Kevin Ahrendt 163b09a473 [microphone] Speed up MicrophoneSource audio conversion (#20755) 2026-10-14 08:47:25 -10:00
J. Nick Koston 7afc2768f8 [esp8266] Check that every component's saved values fit together (#20686) 2026-10-14 08:45:51 -10:00
56f1a2bf46 [midea_portasplit] Add Midea PortaSplit climate component (#18033)
Co-authored-by: Claude Opus 4.8 <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: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-14 08:45:21 -10:00
Panoplian fee47fe2f1 [pcf85063] Convert the weekday to and from the chip's 0-6 range (#20736) 2026-10-14 08:43:23 -10:00
Panoplian b994e081c3 [bm8563] Convert the weekday to and from the chip's 0-6 range (#20737) 2026-10-14 08:42:47 -10:00
Jonathan Swoboda a1f740b71b [lvgl] Keep LV_CONF_PATH one argument when the build path has a space (#20758) 2026-10-14 08:36:17 -10:00
Remco van EssenandClaude Opus 5.5 7f144365ba [fusb302b] Add USB PD sink protocol (#20632)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 08:35:29 -10:00
Marcus VoßandJ. Nick Koston 928459d16f [cse7766] Keep last power reading between measurements at low load (#20631)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-14 08:33:11 -10:00
Jonathan Swoboda 9163359445 [core] Analyze the ethernet component under ESP-IDF (#20745) 2026-10-14 08:31:39 -10:00
Jonathan Swoboda 67307f0eeb [ci] Select the feature-gated files the platform clang-tidy jobs were missing (#20743) 2026-10-14 08:31:06 -10:00
91e0edc11d [pn532] Fix NDEF parsing greater than 255 bytes (#18188)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-14 08:15:48 -10:00
Jonathan Swoboda 9fe75e262f [ci] Add a host clang-tidy environment and job (#20747) 2026-10-14 08:09:53 -10:00
Kevin Ahrendt 0132f722a3 [audio] Bump esp-audio-libs to v4.1.0 (#20754) 2026-10-14 13:24:36 -04:00
Jonathan Swoboda 7d7a78308d [ci] Test tinyusb_keyboard in isolation (#20749) 2026-10-14 10:05:02 -04:00
J. Nick Koston 9c568525b0 [core] Add CodSpeed benchmarks for importing entity components (#20648) 2026-10-14 09:08:41 -04:00
J. Nick Koston 10f42e3e7c [shelly_dimmer] Build the STM32 firmware update path in CI (#20677) 2026-10-14 09:03:34 -04:00
J. Nick Koston 25d4b88605 [nfc] Add unit tests for the URI record prefix codes (#20724) 2026-10-14 09:01:59 -04:00
J. Nick Koston d6abf35138 [sx126x][sx127x] Stop the watchdog reset loop when a packet is sent before setup (#20727) 2026-10-14 09:01:10 -04:00
J. Nick Koston 66ea99ce65 [seeed_mr60fda2] Compile without binary sensors or selects (#20726) 2026-10-14 09:00:08 -04:00
J. Nick Koston d9298cb7b9 [seeed_mr60bha2] Compile without sensors or binary sensors (#20729) 2026-10-14 08:59:27 -04:00
J. Nick Koston 34e1cf96d5 [bmi270] Build the component on ESP8266 in CI (#20731) 2026-10-14 08:58:56 -04:00
56cc03c4d4 [haier] Haier Select component added (#16148)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Pavlo Dudnytskyi <Pavlo.Dudnytskyi@addsecure.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-13 23:27:18 -10:00
35b416c6c0 [web_server_idf] Keep active SSE sessions out of the LRU purge (#17383)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-13 22:19:06 -10:00
Petar Petrov 7f2c3ba3f5 [display] Let other components draw into a display (#20622) 2026-10-14 06:11:47 +00:00
Keith BurzinskiandClaude Opus 5.5 a97604d574 [esp32_ble] Rely on preferences for NVS init (#20708)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:10:51 -10:00
Petar Petrov 1146b77836 [socket] Add TcpClientLink::connecting() (#20722) 2026-10-13 19:19:17 -10:00
J. Nick Koston 1338902ac5 [heatpumpir] Link only the configured protocol classes (#20707) 2026-10-14 17:06:22 +13:00
60a1d70805 [core] Move deferred include processing to parse_yaml (#16344)
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-10-13 17:46:54 -10:00
Ryan LudwigandCursor 887d4c3a83 [esp32_can] Add NO_ACK mode (#20706)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-13 17:36:15 -10:00
J. Nick Koston c1fa73470e [display] Walk the blit fallback in frame buffer order and resolve the bitness once (#20651) 2026-10-13 17:31:13 -10:00
J. Nick Koston e41d16ba7e [haier] Keep the alarm message table in flash (#20641) 2026-10-14 03:23:28 +00:00
J. Nick Koston 5171113c87 [core] Let PROGMEM_STRING_TABLE hold more than 255 bytes (#20646) 2026-10-13 17:17:48 -10:00
techdregsandJ. Nick Koston 5718e4b779 [tmp102] Add text sensor for diagnostics (#20629)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-13 17:06:56 -10:00
J. Nick Koston 40e12cd0d8 [image] Add CodSpeed benchmarks for image, runtime image and display block draws (#20649) 2026-10-13 17:01:47 -10:00
Brandon HarveyandJ. Nick Koston 07029d0077 [sen6x] Add measurement control and maintenance actions (#18783)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-14 15:47:31 +13:00
Christopher Reimer bb1119d83f [esp32_can] Support 20 kbit/s on ESP32 revision 2 or later (#20426) 2026-10-13 22:34:03 -04:00
7b591b26d6 [sy6970] Service the I2C watchdog so charge/LED settings persist (#19424)
Co-authored-by: Willem Oldemans <>
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-10-13 16:19:58 -10:00
f45e6f0bf4 [midea] Add display_control and send the display toggle over UART without a transmitter (#18671)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-13 16:03:18 -10:00
Keith BurzinskiandClaude Opus 5.5 4b8af08155 [network] Notify listeners when the active interface changes (#20684)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:57:59 -05:00
613973b432 Bump ruff from 0.16.10 to 0.17.0 (#20694)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-10-13 21:53:44 -04:00
dependabot[bot] 9ee4314bc2 Bump esptool from 5.4.0 to 5.5.0 (#20693)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-13 15:41:30 -10:00
fredkotzeandJ. Nick Koston 59f55dbe9e [epaper_spi] Add Waveshare 2.13in-G (JD79660) display models (#18141)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-13 15:40:28 -10:00
dependabot[bot] 8fce95454a Bump github/codeql-action/init from 4.38.2 to 4.38.3 (#20701)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-13 15:39:49 -10:00
dependabot[bot] 16134ee993 Bump astral-sh/setup-uv from 10.2.0 to 10.3.0 (#20704)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-13 15:39:38 -10:00
dependabot[bot] bc39d56bb4 Bump CodSpeedHQ/action from 5.4.0 to 5.4.2 (#20705)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-13 15:39:29 -10:00
dependabot[bot] c5522abc71 Bump github/codeql-action/analyze from 4.38.2 to 4.38.3 (#20699)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-13 21:39:11 -04:00
dependabot[bot] 7cb5906d2a Bump actions/upload-artifact from 7.0.1 to 7.0.2 (#20702)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-14 01:38:42 +00:00
dependabot[bot] d452dbce30 Bump actions/download-artifact from 8.0.1 to 8.0.2 (#20703)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-14 01:38:39 +00:00
dependabot[bot] 81e5b50087 Bump backports-zstd from 1.7.0 to 1.8.0 (#20695)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-13 15:38:35 -10:00
dependabot[bot] 426a4a2dce Bump filelock from 4.0.12 to 4.1.0 (#20696)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-13 15:38:26 -10:00
dependabot[bot]andesphome[bot] c95c17c4e7 Bump pyupgrade from 3.21.2 to 3.22.0 (#20698)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-10-13 15:38:17 -10:00
dependabot[bot] 576d3325fe Bump astral-sh/setup-uv from 10.2.0 to 10.3.0 in /.github/actions/restore-python (#20700)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-13 15:37:47 -10:00
dependabot[bot] c9cf0ad92e Bump platformdirs from 4.12.3 to 4.12.4 (#20697)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-13 21:37:36 -04:00
Felipe Guedes f505212e64 [epaper_spi] Add Waveshare 2.66" e-Paper Module (B) support (#20546) 2026-10-13 15:03:05 -10:00
J. Nick Koston 52a5e6a007 [rpi_dpi_rgb] Set removal of the deprecated component for 2027.5.0 (#20691) 2026-10-14 13:32:18 +13:00
J. Nick Koston 3b261d2cf0 [st7789v] Set removal of the deprecated component for 2027.5.0 (#20689) 2026-10-14 13:30:52 +13:00
J. Nick Koston 9238fcc727 [st7735] Set removal of the deprecated component for 2027.5.0 (#20688) 2026-10-14 13:30:21 +13:00
J. Nick Koston 96902cc122 [qspi_dbi] Set removal of the deprecated component for 2027.5.0 (#20690) 2026-10-14 13:29:52 +13:00
J. Nick Koston 95711d83d1 [ili9xxx] Set removal of the deprecated component for 2027.5.0 (#20687) 2026-10-14 13:29:29 +13:00
Panoplian 3cdcf0417a [lvgl] Make the list_outside_block tests work offline (#20676) 2026-10-13 13:31:08 -10:00
J. Nick Koston c6e90de262 [ci] Catch out-of-class member log tags and keep midea's in flash (#20674) 2026-10-14 12:21:24 +13:00
J. Nick Koston 92f6ab8367 [ci] Lint log tags in headers and core (#20668) 2026-10-14 12:21:24 +13:00
J. Nick Koston f61955baa2 [core] Declare the remaining log tags with ESPHOME_LOG_TAG (#20666) 2026-10-14 12:21:23 +13:00
J. Nick Koston 36e1d831d3 [core] Add a decimal fast path to parse_number<float> (#20639) 2026-10-14 12:18:35 +13:00
J. Nick Koston 63616bf642 [rtttl] Play a configured song from static storage without copying it (#19569) 2026-10-14 12:06:27 +13:00
d275926f65 [mdns] Advertise the connected interface in the network TXT record (#20673)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-13 17:55:22 -05:00
Andrej Friesen f265c46c21 [PCA9505] Add PCA9505/PCA9506 40-channel GPIO expander (#20667) 2026-10-13 12:50:43 -10:00
Frédéric MetrichandClaude Opus 5.5 7d735f0677 [emontx] Lock the emonTx on reboot when its firmware supports it (#20621)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 12:37:09 -10:00
Remco van Essen 5c23a827b6 [tmc2209] Add current and diagnostic sensors (#20638) 2026-10-13 12:35:55 -10:00
Jonathan Swoboda e265c0c0fc [core] Sync the static analysis dependency pins with the code (#20681) 2026-10-13 12:30:42 -10:00
J. Nick Koston a0c7d754cb [syslog] Keep the log level to severity table in flash (#20662) 2026-10-13 12:22:35 -10:00
Jesse Hills a6dbd72c53 Merge branch 'beta' into dev 2026-10-14 11:15:09 +13:00
Jesse Hills 9d530f5c56 Merge pull request #20680 from esphome/bump-2026.10.0b4
2026.10.0b4
2026-10-14 11:14:49 +13:00
Panoplian 6562c49686 [thermostat] Allow a dry-only thermostat without cool_action (#20682) 2026-10-13 12:08:13 -10:00
guillempages a97ce3a201 [image] Fix pillow deprecation warning (#20679) 2026-10-13 11:58:21 -10:00
Jesse Hills 5a78f636b9 Bump version to 2026.10.0b4 2026-10-14 10:43:50 +13:00
tomaszduda23andClaude 53d504137b [nrf52] Accept S140 7.2.0 and 7.3.0 in the sd140_v7 DFU package (#20661)
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-14 10:43:49 +13:00
bb0546fc0b [script] Keep the running script.wait item out of the queue while its chain runs (#20570)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-14 10:43:49 +13:00
emes30andJ. Nick Koston b43630cf8e [rp2_pio] Use pioasm 4.1.0 so PIO programs run on RP2350 (#19260)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-14 10:43:49 +13:00
Firat HajiyevandFirat17aze c9664d4e42 [wifi] Reject unsupported WPA3 on ESP8266 (#20592)
Co-authored-by: Firat17aze <187820330+Firat17aze@users.noreply.github.com>
2026-10-14 10:43:49 +13:00
Kevin Ahrendt 39ea6a3220 [audio] Bump microFLAC to v0.3.0 (#20591) 2026-10-14 10:43:49 +13:00
J. Nick Koston f4a6d6f18b [core] Create the PlatformIO HTTP cache dir before the prefetch resolve threads run (#20579) 2026-10-14 10:43:49 +13:00
esphome[bot] 6c24d4aa43 Bump aioesphomeapi from 46.9.0 to 46.10.0 (#20563) 2026-10-14 10:43:49 +13:00
J. Nick Koston 0dc1d3b5a1 [toshiba] Move the lookup tables to PROGMEM (#20636) 2026-10-13 11:25:18 -10:00
tomaszduda23andClaude 42341d05f9 [nrf52] Accept S140 7.2.0 and 7.3.0 in the sd140_v7 DFU package (#20661)
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-13 11:12:08 -10:00
J. Nick Koston f941fee5ed [homeassistant] Add sensor platform benchmarks (#20637) 2026-10-13 11:10:14 -10:00
Bonne Eggleston 8721976066 [epaper_spi] Abandon an update when the busy pin never clears (#20555) 2026-10-14 07:47:09 +11:00
Bonne Eggleston 05bf7d3440 [epaper_spi] Write pixel blocks straight into mono buffers (#20552) 2026-10-14 07:40:56 +11:00
918b7d2553 [script] Keep the running script.wait item out of the queue while its chain runs (#20570)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-13 09:31:19 -10:00
rwrozelleandClaude Opus 5.5 417ab4d2e9 [openthread] Add core_options for OpenThread compile-time options (#20595)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:00:45 -04:00
Jonathan Swoboda 0821c84b7a [core] Let a nested repeat shadow the outer iteration variable (#20634) 2026-10-13 14:35:24 -04:00
Clyde Stubbs a827058928 [lvgl] Change deprecated calls to current version (#20620) 2026-10-13 14:27:50 -04:00
J. Nick Koston 3063b76210 [haier_tundra] Build the IR frame on the stack (#20635) 2026-10-13 14:25:36 -04:00
Remco van EssenandClaude Opus 5.5 55261ae7e5 [tmc2209] Add driver diagnostics and stall detection (#20618)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 08:17:25 -10:00
cociwebandJ. Nick Koston 1d8423edd8 [es8388] Add initial option configuration for DAC output and ADC input mic (#16342)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-13 08:14:43 -10:00
n-IA-haneandn-IA-hane d27ec8ec49 [voice_assistant] Make TTS playback start timeout configurable (#18707)
Co-authored-by: n-IA-hane <n-IA-hane@users.noreply.github.com>
2026-10-13 08:13:26 -10:00
Ivan KruglovandJ. Nick Koston 5acbd44067 [pcf8574] Add reset option to preserve outputs across MCU reboots (#20589)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-13 08:12:28 -10:00
40271fb597 [haier_tundra] New Climate IR component (#20469)
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-10-13 08:10:36 -10:00
95766ed3ad [icm20689] Add ICM-20689 6-axis accelerometer/gyroscope (SPI) (#19234)
Co-authored-by: Ludovic BOUÉ <938089+lboue@users.noreply.github.com>
Co-authored-by: Claude Sonnet 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@home-assistant.io>
2026-10-13 08:07:32 -10:00
4a80d785a8 [filter_lifetime] Add component with sensor/lambda support for is_on … (#13914)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-13 07:52:46 -10:00
J. Nick Koston c1c1d2b2ae [core] Add probe_ota_key, a Noise handshake with the device that sends no image (#20601) 2026-10-13 07:28:32 -10:00
Mat931andJonathan Swoboda 29a668af04 [esp32_camera] Fix compile warning (#20628)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-10-13 14:01:17 +00:00
J. Nick Koston fd541bca2c [core] Document constexpr constants, per-model schemas and isolated build tests in AGENTS.md (#20617) 2026-10-13 08:20:32 -04:00
J. Nick Koston 192ae31e45 [tests] Turn off the Hypothesis deadline for the MAC address tests (#20614) 2026-10-13 08:19:10 -04:00
Frédéric MetrichandClaude Opus 5.5 77c9fa2fa4 [emontx] Read the emonTx firmware version (#20619)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 22:43:02 -10:00
guillempagesandJ. Nick Koston 25843c5242 [image] Implement RGB111 format (#20114)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 22:16:44 -10:00
7881dfc5d8 [deep_sleep] Reject wakeup_pin configs that fail after validation (#18616)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:29:42 +11:00
e289a94451 [friedrich] New Climate IR component (#9178)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 17:11:21 -10:00
5017165800 [selec_meter] Add EM4M (3-phase) support via model option (#17782)
Co-authored-by: Claude Sonnet 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@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-13 02:58:25 +00:00
8acf885241 [bq76972] Add BQ76972 BMS chip monitoring component (#17720)
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-10-12 16:43:51 -10:00
f829447b2e [tmp102] Add number entities for runtime thermostat thresholds (#20596)
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-10-12 16:25:49 -10:00
74e96b7da6 [INA3221] Add optional flags to expose all functionality (#16309)
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-10-12 16:21:00 -10:00
753c10e5ea [ld2410s] Add the distance sensor, firmware version and common parameters (Part 2) (#8486)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 16:06:45 -10:00
J. Nick Koston 98d3d23ce1 [ecocomfort2] Test the component in isolation (#20613) 2026-10-12 15:59:25 -10:00
Rodrigo MartínandJ. Nick Koston d6e5b9b03b [bq25186] Add the status sensors, buttons, PG/GPO switch and watchdog option (part 2) (#15736)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 15:50:12 -10:00
J. Nick Koston 02287fdf7b [sensor] Share filter_out and throttle_with_priority value tables in flash (#20078) 2026-10-12 15:48:27 -10:00
J. Nick Koston 2001f60954 [core] Keep string constants in apply conditions in flash on ESP8266 (#20107) 2026-10-12 15:48:05 -10:00
J. Nick Koston 5a39097b3a [light] Keep one copy of the color mode lookup table in flash (#20394) 2026-10-12 15:46:41 -10:00
J. Nick Koston fb14db267c [const] Move CONF_SERIAL_NUMBER to the shared component constants (#20611) 2026-10-13 01:45:52 +00:00
J. Nick Koston 36900e36f9 [esp32_power_management] Build the flash power-down path in a standalone test variant (#20612) 2026-10-12 15:45:09 -10:00
a035c17e04 [gt911] Fix the interrupt pin initialisation after reset (#15021)
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-10-12 15:42:24 -10:00
88fdf822cd [runtime_image] Speed up RGB565 JPEG decoding (#19454)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 15:41:30 -10:00
e7a7aa150d [yaml_util] Raise a clear error for a non-mapping defaults: in a package (#19425)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 15:36:57 -10:00
J. Nick Koston 54f228766c [stcc4] Use SUB_SENSOR for the output sensors (#20605) 2026-10-12 21:12:39 -04:00
Bascht74 58188305c4 [uart] Keep the swapped UART0 pins on an ESP8266 reload (#20463) 2026-10-12 15:09:32 -10:00
J. Nick Koston afedddafe2 [systa_bus] Use SUB_SENSOR and explicit sub_sensors calls for the sensors (#20610) 2026-10-12 20:57:01 -04:00
J. Nick Koston c3c235dea7 [pzem6l24] Use SUB_SENSOR and explicit sub_sensors calls for the sensors (#20609) 2026-10-12 20:56:58 -04:00
J. Nick Koston 39a7f4482a [bh1745] Use SUB_SENSOR for the sensors (#20606) 2026-10-12 20:52:16 -04:00
J. Nick Koston f3f3ca5a8a [runtime_image] Add CodSpeed benchmarks for JPEG decoding (#20603) 2026-10-12 14:50:24 -10:00
Jesse HillsandJ. Nick Koston bdd2dcd8c6 [weikai] Mark configurable classes as final (#17292)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-12 14:39:08 -10:00
J. Nick Koston a3e373b063 [noise] Add generate_encryption_key and use it in the wizard and dashboard_import (#20600) 2026-10-12 20:31:36 -04:00
dc9a7cffbd [ota] Refuse a second OTA while one is already running (#20588)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-13 00:28:38 +00:00
4cae63bbaa [ecocomfort2] Add the read-only sensors and clock sync (Part 2) (#15447)
Co-authored-by: gledian <gledina.kruja@gmail.com>
Co-authored-by: Claude Opus 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-10-12 14:27:16 -10:00
8aab968136 [aranet] Add Aranet BLE sensor support (#18263)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 14:11:10 -10:00
J. Nick Koston 80fa6efd1e [core] Document string tables, shared CRC helpers, NaN sentinels and polling start-up in AGENTS.md (#20599) 2026-10-12 13:44:49 -10:00
J. Nick Koston 3ad83ad02b [text_sensor] Share substitute and map filter tables in flash (#20076) 2026-10-12 13:40:38 -10:00
J. Nick Koston b66cde0270 [sensor] Share calibrate_linear and calibrate_polynomial tables in flash (#20074) 2026-10-12 13:39:38 -10:00
J. Nick Koston 331d12af2f [binary_sensor] Share multi click timing tables in flash and skip the default cooldown setter (#20071) 2026-10-12 13:39:10 -10:00
318e4b79c1 [apc1] Add support for the ScioSense APC1 air quality sensor (UART) (#19274)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2026-10-12 13:38:34 -10:00
J. Nick Koston 99a4ae1f51 [ota] Add old_key so an upload can retry with the previous key (#19398) 2026-10-12 13:35:27 -10:00
J. Nick Koston 72ca565a0e [core] Resume if and while from the end of their branch instead of an embedded continuation action (#20283) 2026-10-12 13:35:03 -10:00
J. Nick Koston cce435a31e [gpio] Keep each interlock group in one shared flash table (#20238) 2026-10-12 13:34:44 -10:00
J. Nick Koston 6c86a8903f [core] Report incomplete YAML discovery and record remote package sources (#20312) 2026-10-12 13:28:17 -10:00
b21354d6d9 [mistral_ir] Add climate component for Mistral MHFE1126A IR AC units (#17812)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 13:22:34 -10:00
Ilya EsinandJ. Nick Koston fa519cea64 [apds9930] Add ambient light and proximity sensor (#13125)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 13:14:31 -10:00
Remco van EssenandJ. Nick Koston 132e9dac8b [tmc2209] Add TMC2209 stepper driver (#20437)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 13:13:11 -10:00
0c43d689d7 [sh1122] Add OLED display driver (#15295)
Co-authored-by: Nicolas Reinecke <nr@das-labor.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 13:11:51 -10:00
73d5bb3c97 [remko_ar715] Add Remko AR-715 IR climate platform (#17173)
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-10-12 13:09:40 -10:00
81c275777d [bmi270] Add optional BMM150 magnetometer support via aux interface (#18436)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-12 13:08:41 -10:00
54fdeb7b1b [samsung] Add climate IR samsung support (#8004)
Co-authored-by: Georgi Filipov <georgi.filipov@uplink.bg>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.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-10-12 13:01:22 -10:00
150e32c4be [renogy_inverter_ble] Add Renogy inverter BLE sensor component (#16754)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 12:56:50 -10:00
1e13b7f973 [tinyusb] Add tinyusb_keyboard component (#16079)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 12:56:28 -10:00
Rodrigo MartínandJ. Nick Koston d08d324a83 [bq25186] Add bq25186 1-Cell, 1A Linear Battery Charger component (part 1) (#15700)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 12:54:35 -10:00
88668f448e [max17048] Add MAX17048 Li+ Cell Fuel Gauge IC (#13585)
Co-authored-by: Rodrigo Martín <contact@rodrigomartin.dev>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 12:53:16 -10:00
c5baae35d5 [ezsp_proxy_tap] Add SiLabs EZSP auto-ACK implementation (#20304)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 12:51:48 -10:00
1c315c3bf2 [ns2009] Add Nsiway NS2009 touchscreen controller component (#10597)
Co-authored-by: J. Nick Koston <nick@koston.org>
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-10-12 12:49:07 -10:00
f9f41ef16e [MAX31888] Add temperature sensor (#13567)
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-10-12 12:45:08 -10:00
a68cf669aa [tca8418] Add TCA8418 keypad component (#18521)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-12 12:39:11 -10:00
3ddbf8b3ef [lp5562] Add LP5562 4-channel I2C LED driver output component (#18453)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 12:12:11 -10:00
Ryan GammonandJ. Nick Koston b88eafcd00 [bq27220] Add BQ27220 battery fuel gauge component (#17702)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 12:05:34 -10:00
d7dd25a1bc [ld2410s] Add HLK-LD2410S presence radar component (#10452)
Co-authored-by: NovakI <novaki@debian.ka11>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 11:57:16 -10:00
83e5e32e2b [i2s_clockless_led_strip] Drives LED strips using I2S and DMA (#20255)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 11:52:05 -10:00
584d4878eb [lis3dh] Add LIS3DH accelerometer component (#17393)
Co-authored-by: Claude <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-10-12 11:44:33 -10:00
93183c661e [ecocomfort2] Add component for Fantini Cosmi VMC units via BLE (Part 1: hub + fan) (#17092)
Co-authored-by: gledian <gledina.kruja@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 11:36:27 -10:00
Anton ViktorovandJ. Nick Koston 5a819f4b48 [bh1745] Add BH1745 colour sensor (#6349)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 11:34:04 -10:00
Anton ViktorovandJ. Nick Koston 2cd7c0eb36 [husb238] Add HUSB238 USB PD controller hub with binary sensors (#16572)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 11:31:16 -10:00
1c8808339a [power_management] Add the power_management domain with an esp32_power_management platform (#12325)
Co-authored-by: Kevin Ahrendt <kahrendt@gmail.com>
Co-authored-by: Kevin Ahrendt <kevin.ahrendt@openhomefoundation.org>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 11:23:55 -10:00
matt123pandpre-commit-ci-lite[bot] 6bf091104f [es7210] Add TDM support for future AEC support (#18954)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-10-12 11:22:17 -10:00
Martin KlingensmithandJ. Nick Koston 4e3845ba68 [pd_pioneer_ir] Add Pioneer mini-split IR climate component (#17680)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 11:20:13 -10:00
0b6856c052 [udp] Add IPv6 unicast and multicast (#20590)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-12 11:17:58 -10:00
Stephen CoxandJ. Nick Koston ffa849753a [ds3231] Add DS3231 real-time clock hub with temperature sensor (#18883)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 11:00:05 -10:00
0f087f425a [ld2460] Add HLK-LD2460 radar hub with target sensors (#19002)
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-10-12 10:52:04 -10:00
833c6634ad [lis2dw12] Add ST LIS2DW12 accelerometer component (#14780)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-13 09:44:51 +13:00
5a3bee638f [siemens_ira211] Add the Siemens IRA211 protocol and climate platform (#15154)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-13 09:33:33 +13:00
66ba258baa [udp] Wake the main loop when a datagram arrives (#20585)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-12 08:53:22 -10:00
216e1bee5c [socket] Read the LwIP interface list under the core lock (#20583)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 18:15:44 +00:00
emes30andJ. Nick Koston e0436e2600 [rp2_pio] Use pioasm 4.1.0 so PIO programs run on RP2350 (#19260)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 18:01:11 +00:00
ca69c33530 [whirlpool] Add the HISENSE_ARG model for Hisense and BGH units sold in Argentina (#17352)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-12 07:57:23 -10:00
J. Nick Koston aa0bd9af72 [core] Add YAML secret reference and registration helpers (#20343) 2026-10-12 07:55:38 -10:00
Jonathan Swoboda 9c35bb7426 [esp32] Check the TLS option the active IDF version writes in tests (#20584) 2026-10-12 07:48:33 -10:00
J. Nick Koston af8a0739f0 [web_server] Self contained AP mode: embedded interface served as a captive portal (#18517) 2026-10-12 07:46:11 -10:00
99b61760c3 [remote_base] Add the Pioneer WYT transmit protocol (#17175)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 07:37:31 -10:00
db9138c550 [bl0939] Add current transformer measurement mode support (#13350)
Co-authored-by: ShuishengPeng <63339353+ackPeng@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-10-12 07:34:25 -10:00
J. Nick Koston 5bb1f0f779 [statsd] Use the socket UDP API on ESP8266 and RP2040 (#20557) 2026-10-12 07:17:01 -10:00
J. Nick Koston 4c549d6e92 [e131] Use the socket UDP API on ESP8266 and RP2040 (#20559) 2026-10-12 07:16:47 -10:00
J. Nick Koston dc719b300b [udp] Use the socket UDP API on ESP8266 and RP2040 (#20558) 2026-10-12 07:16:29 -10:00
J. Nick Koston f854da4f8d [cli] Add --prompt-ota-key to present a different key for one upload (#19397) 2026-10-12 07:15:51 -10:00
Firat HajiyevandFirat17aze 2dad6e4f62 [wifi] Reject unsupported WPA3 on ESP8266 (#20592)
Co-authored-by: Firat17aze <187820330+Firat17aze@users.noreply.github.com>
2026-10-12 07:15:13 -10:00
J. Nick Koston 72ea9ae458 [remote_base] Generate the byte payload transmit actions with no C++ class (#20568) 2026-10-12 07:14:19 -10:00
Kevin Ahrendt 9e8845885b [audio] Bump microFLAC to v0.3.0 (#20591) 2026-10-12 11:22:59 -04:00
J. Nick Koston 00872d4336 [ethernet] Skip setters that pass the default PHY address, MDC, MDIO, clock and SPI interface (#20070) 2026-10-12 09:56:53 -04:00
J. Nick Koston 780ccfc462 [adc] Skip the output raw, sample count, attenuation and sampling mode setters when they match the defaults (#20068) 2026-10-12 09:56:24 -04:00
J. Nick Koston 9acc07a973 [remote_base] Generate transmit_pronto and transmit_rc6 with no C++ class (#20564) 2026-10-12 07:48:02 -04:00
J. Nick Koston 698988669d [remote_transmitter] Compile pronto, rc6, midea and lambda byte payloads in the test config (#20565) 2026-10-12 07:37:51 -04:00
J. Nick Koston bde67d83f2 [noise] Exclude the handshake write step from the loop blocking guard (#20573) 2026-10-12 07:33:19 -04:00
J. Nick Koston 057a0c5843 [api] Exclude the spare key refill from the loop blocking guard (#20574) 2026-10-12 07:31:15 -04:00
J. Nick Koston c212950df5 [wifi] Exclude the station connect from the loop blocking guard on LibreTiny (#20575) 2026-10-12 07:30:59 -04:00
J. Nick Koston 3c88d39192 [remote_base] List the hob2hood, nexus and onkyori protocol defines in defines.h (#20577) 2026-10-12 07:28:33 -04:00
J. Nick Koston c0d4287c6e [core] Create the PlatformIO HTTP cache dir before the prefetch resolve threads run (#20579) 2026-10-12 07:28:07 -04:00
BugHaverandJ. Nick Koston 3472ac3dcc [heatpumpir] Add Eco and Boost presets and fix fan speeds on Mitsubishi Heavy ZJ and ZMP (#16877)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-11 23:44:01 -10:00
5a90bceba0 [cse7766] Clamp a power factor above 1 instead of publishing NaN (#15307)
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-10-11 23:42:17 -10:00
af76f19ab4 [heatpumpir] Add ZMP IR receiver (#17099)
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-10-11 23:18:22 -10:00
e92d239996 [dsmr] add Switzerland channel 1-1 sensors (EWL Luzern AM550) (#17391)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-11 22:37:05 -10:00
602bf00435 [daikin_brc] Add sub_unit parameter for multi-unit addressing (#15234)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-11 22:21:26 -10:00
mfishma 1e05106a78 [mipi_spi] Add M5STACK-ATOMS3R-ST7735 as a display panel configuration (#18673) 2026-10-12 08:01:48 +00:00
marshnandJ. Nick Koston 061b27af02 [remote_base] Add a data option and checksum handling to the Drayton protocol (#13060)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-11 21:54:55 -10:00
Siddharth SinghandJ. Nick Koston d265ed2b9e [remote_base] Fix NEC command_repeats to send repeat frames (#17663)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-11 21:29:09 -10:00
20cdca6f18 [remote_base] Decode the full MagiQuest frame and check its checksum (#10622)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-12 07:01:12 +00:00
toupsandJ. Nick Koston c668ca516c [remote_base] updates to support 56 bit panasonic codes (#20260)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-11 20:48:55 -10:00
Frédéric MetrichandClaude Opus 5.5 7fbdcb0205 [emontx] Lock the emonTx across reboots (#20576)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:42:52 -10:00
Zach Isbach 92a4e283a9 [epaper_spi] Add Good Display GDEY0213F51 4-color e-paper support (#17991) 2026-10-12 17:40:59 +11:00
Frédéric MetrichandClaude Opus 5.5 ba9061e34e [nextion] Pass std::string to custom-protocol automations that defer (#20571)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:30:53 -10:00
0c645ac3a1 [remote_base] Adding Marantz RC5 Extended protocol (#13164)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 06:24:50 +00:00
J. Nick Koston eb8a793f6e [core] Add a name truncation helper and keep the MAC suffix in the nRF52 BLE name (#19768) 2026-10-11 20:22:11 -10:00
36082b23c2 [remote_base] Add the Somfy RTS transmit protocol (#16502)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.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-10-11 20:11:15 -10:00
esphome[bot] 8191cd18d2 [ci] Refresh integration test durations (#20578) 2026-10-12 06:02:07 +00:00
2692e0f189 [remote_base] Add Nexus RF protocol support (#13577)
Co-authored-by: zry98 <dev@zry.io>
Co-authored-by: Citizen <kvirbickas@gmail.com>
Co-authored-by: Citizen07 <34106434+Citizen07@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
Signed-off-by: zry98 <dev@zry.io>
2026-10-11 19:36:07 -10:00
Ivan KruglovandJ. Nick Koston 29db7b3f51 [bl0939] Add optional internal temperature sensor (#19415)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 05:33:44 +00:00
Firat Hajiyev 1c1b4d13ad [core] Mask integer-valued credentials without changing code generation (#20430) 2026-10-11 18:58:41 -10:00
J. Nick Koston f8076fe90b [core] Add UnavoidableBlockingScope for blocking that cannot be shortened (#19020) 2026-10-11 18:48:41 -10:00
19eeab0385 [wifi] Add DHCP lease cache (#17767)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-11 18:48:13 -10:00
3f21ddf6ff [lvgl] Add a color picker widget (#17883)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-12 15:12:22 +11:00
J. Nick Koston 5c1c946e88 [wake_on_lan] Use the socket UDP API on ESP8266 and RP2040 (#20556) 2026-10-11 18:09:11 -10:00
J. Nick Koston 698935c321 [seeed_mr24hpc1] Remove the duplicate startup delay constant left by the beta merge (#20569) 2026-10-11 17:38:41 -10:00
J. Nick Koston 0c6543c4a6 [remote_base] Name transmit as a template in ProtocolCall::perform (#20566) 2026-10-11 17:38:38 -10:00
4688df3b94 [mcp9808] Remove k7hpn as codeowner (#20561)
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-10-11 17:06:46 -10:00
esphome[bot] 32f416109e Bump aioesphomeapi from 46.9.0 to 46.10.0 (#20563) 2026-10-12 03:05:16 +00:00
J. Nick Koston 08a55c97b7 [remote_base] Generate the simple transmit actions with no C++ class (#20549) 2026-10-11 17:03:59 -10:00
d19bb7f4be [cse7761] Add current_input option for current transformer boards such as the Sonoff POW CT (#7836)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-11 17:00:16 -10:00
J. Nick Koston 6095d9d610 [improv_serial] Remove the duplicate timeout id left by the beta merge (#20562) 2026-10-11 16:56:56 -10:00
Jesse Hills 95d9b68ac9 Merge branch 'beta' into dev 2026-10-12 15:42:45 +13:00
Jesse Hills 2c4b1f3ed5 Merge pull request #20542 from esphome/bump-2026.10.0b3
2026.10.0b3
2026-10-12 15:42:25 +13:00
J. Nick KostonandClaude Opus 4.6 847be14d1e [socket] Add lwip raw UDP socket implementation (#14677)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-10-11 16:27:41 -10:00
J. Nick Koston 40606a6c08 [core] Do not search the same heap twice in RAMAllocator single-region requests (#19381) 2026-10-11 16:24:36 -10:00
J. Nick Koston 650a02beb0 [remote_transmitter] Compile the transmit actions the test config left out (#20550) 2026-10-11 22:13:15 -04:00
J. Nick Koston c6ed2069a5 [json][web_server] Link strings that outlive the document instead of copying them (#19380) 2026-10-11 15:55:51 -10:00
J. Nick Koston fa4bc28d3e [text] Build the on_value trigger with build_callback_automation (#19613) 2026-10-11 15:54:44 -10:00
J. Nick Koston 95de93bebb [fan] Build the on_state trigger with build_parent_callback_automation (#19612) 2026-10-11 15:54:25 -10:00
J. Nick Koston 028db614ca [select] Build the on_value trigger with build_callback_automation (#19609) 2026-10-11 15:54:09 -10:00
J. Nick Koston c38674bb7b [climate] Build the on_state and on_control triggers with build_callback_automation (#19611) 2026-10-11 15:53:55 -10:00
Clyde StubbsandClaude Opus 5.5 4ba5afaa1b [core] Move CONF_ON_STOP to the shared component constants (#20548)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 01:37:17 +00:00
J. Nick Koston 0cd239dfd1 [uart] Keep constant switch, button and write payloads in flash (#20082) 2026-10-12 01:16:34 +00:00
J. Nick Koston ed6ab4565d [core] Emit filter, action and condition lists one item at a time (#20116) 2026-10-11 15:12:59 -10:00
J. Nick Koston 5d64b65d72 [esp32_ble_server] Keep constant set_value payloads in shared flash tables (#20314) 2026-10-11 15:12:09 -10:00
J. Nick Koston 993fd8d7a2 [thermostat] Keep presets in shared flash tables (#20080) 2026-10-11 15:04:16 -10:00
J. Nick Koston 9295342c16 [event] Keep event types in a shared flash table (#20217) 2026-10-11 14:58:49 -10:00
J. Nick Koston e16b3a6245 [api] Share one flash string pool between API action and Home Assistant action strings (#20281) 2026-10-11 14:57:20 -10:00
J. Nick Koston da0c59a57c [http_request] Keep action request headers in a shared flash table (#20225) 2026-10-11 14:57:06 -10:00
J. Nick Koston 40425610d5 [remote_base] Keep constant abbwelcome payloads in shared flash tables (#20311) 2026-10-11 14:55:22 -10:00
Bascht74 643f29fae6 [uart] Let a UART say it has no line timing (#20227) 2026-10-11 14:49:19 -10:00
59d31505e9 [mipi_spi] Add LCDwiki ES3C35P model (ST77922 QSPI) (#19011)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 00:48:47 +00:00
J. Nick Koston e50a0d902e [core] Expose the preferences storage helpers to components (#20535) 2026-10-11 20:42:21 -04:00
Bascht74andpre-commit-ci-lite[bot] 22c17fe7e1 [remote_base] Add optional suffix bits to KeeLoq after the HCS301 word (#19891)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-10-11 14:36:27 -10:00
techdregs 826a7a252a [tmp102] Add comparator alert binary sensor (#20540) 2026-10-11 14:33:06 -10:00
J. Nick Koston ce19978cfd [ci] Tell too-big PR authors that review waits until the PR is under the cap (#20543) 2026-10-11 14:17:07 -10:00
J. Nick Koston 427e0c9064 [const] Move the colour channel config keys to the shared constants (#20544) 2026-10-11 13:50:50 -10:00
J. Nick Koston 0df7b0b366 [core] Move zstd_module from the api wizard to esphome.helpers (#20342) 2026-10-12 12:46:59 +13:00
Jesse Hills bd4b32873a Bump version to 2026.10.0b3 2026-10-12 11:52:33 +13:00
Bascht74 9e580db434 [uart] Reuse the ISR pin argument on an ESP8266 software serial reload (#20541) 2026-10-12 11:52:31 +13:00
Jesse Hills 87187c5391 [api] Let a wizard input pick multiple entities (#20537) 2026-10-12 11:52:31 +13:00
tomaszduda23andClaude 60402614cd [nrf52] fix zigbee time component (#20527)
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-12 11:52:25 +13:00
puddly 2727d35655 [usb_uart] Fix invalid stop bits and parity enum mappings (#20408) 2026-10-12 11:51:44 +13:00
J. Nick Koston fb1ef29d54 [uart] Stop ESP8266 software serial RX ISR from blocking for a whole byte (#18519) 2026-10-12 11:51:39 +13:00
Frédéric MetrichandClaude Opus 5.5 7c0f63ea20 [core] Copy trigger arguments in wait_until and script.wait (#20536)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:46:44 +13:00
heikkikiiandJ. Nick Koston 5d75828ceb [seeed_mr24hpc1] Wait for the module to boot before sending UART traffic (#18860)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 11:46:44 +13:00
bd2e8d184d [gree] Keep display light on by default for YAN/YAA/YAC/YAC1FB9 (#18516)
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-10-12 11:46:44 +13:00
J. Nick Koston 3c7ce71d6f [uart_tcp] Prove the accept has run before checking what a new client receives (#20489) 2026-10-12 11:46:44 +13:00
Frédéric MetrichandClaude Opus 5.5 58e03a875f [teleinfo] Fix out-of-bounds read when splitting a frame into groups (#20513)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:46:44 +13:00
Clyde StubbsandClaude Opus 5.5 41a9fca881 [ci] Reset sdkconfig after ESP-IDF component discovery (#20511)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:46:44 +13:00
ec58e864f5 [canbus] Fix high frame rates causing crashes due to unbounded iteration (#14481)
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-10-12 11:46:44 +13:00
esphome[bot] 375fbd829e Bump bundled esphome-device-builder to 1.23.1 (#20497) 2026-10-12 11:46:44 +13:00
4bae89bd4a [nrf52] Fix BLE OTA/log device discovery & transport selection on macOS (#17101)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-12 11:46:43 +13:00
Bascht74andJ. Nick Koston 897b2f16a9 [tests] Close leftover server connections in the tcp_uart bridge and TCP client link tests (#20215)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-12 11:46:43 +13:00
tomaszduda23 93c8796ae8 [nrf52] fix build error on 3.4.0 with pch ON (#20474) 2026-10-12 11:46:43 +13:00
J. Nick Koston 654afb536e [core] Fix the native ESP8266 and host builds on Windows without ccache (#20468) 2026-10-12 11:46:43 +13:00
jabla 97154f9e5f [espidf] Clean the build when the IDF target changes (#20473) 2026-10-12 11:46:43 +13:00
c9e49b8e8f [teleinfo] Add predefined properties for main tags of TIC standard and historical (#9167)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-12 11:42:45 +13:00
21804a75de [openthread] add mesh_local_eid openthread_info sensor (#13924)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-11 12:41:14 -10:00
J. Nick Koston 3ae52e21cf [usb_host] Warn instead of failing when a device duplicates a usb_uart entry (#20373) 2026-10-12 11:38:38 +13:00
Zhaoqi Xu f2395f9001 [heatpumpir] Fix fan speeds for mitsubishi_msc protocol (#20460) 2026-10-12 11:38:38 +13:00
Ali Alaeiandtomaszduda23 3d248496ab [zephyr_mcumgr] Enable MCUMGR_GRP_OS for UART transport, not just BLE (#18319)
Co-authored-by: tomaszduda23 <tomaszduda23@gmail.com>
2026-10-12 11:38:38 +13:00
esphome[bot] 34eca948c4 Bump bundled esphome-device-builder to 1.23.0 (#20446) 2026-10-12 11:38:37 +13:00
J. Nick Koston 58815575ef [core] Pass a single condition to actions directly instead of wrapping it in a group (#19223) 2026-10-12 11:38:37 +13:00
J. Nick Koston 28b012389d [ci] Keep bus group partners on the same runner (#20431) 2026-10-12 11:38:37 +13:00
J. Nick Koston 54f86537ad [esp32][libretiny] Skip registering static destructors that never run (#20440) 2026-10-12 11:38:37 +13:00
J. Nick Koston 3284d93a33 [nrf52] Fake a larger flash in testing mode (#20439) 2026-10-12 11:38:37 +13:00
J. Nick Koston 01686a5f67 [improv_serial] Use a numeric scheduler id for the Wi-Fi connect timeout (#20360) 2026-10-12 11:38:37 +13:00
J. Nick Koston 3b0c485701 [mqtt] Use a numeric scheduler id for the deferred state publish (#20359) 2026-10-12 11:38:37 +13:00
J. Nick Koston e61d348f0c [wifi] Use numeric scheduler ids instead of string names (#20357) 2026-10-12 11:38:37 +13:00
J. Nick Koston 22e594bc62 [esp32_ble_client] Track conn_id per attempt and stop logging an error on a failed open (#20417) 2026-10-12 11:38:37 +13:00
J. Nick Koston 061733672f [ci] Refit component batch estimates to a full run (#20401) 2026-10-12 11:38:37 +13:00
J. Nick Koston b1f5da9561 [esp8266] Keep ArduinoJson's float tables in flash (Arduino core 3.1.2-esphome.3) (#20416) 2026-10-12 11:38:37 +13:00
J. Nick Koston db517059a3 [ota] Collapse duplicate CI test variants (#20407) 2026-10-12 11:38:37 +13:00
J. Nick Koston 0b40892775 [mcp23xxx_base] Keep the interrupt handler in IRAM on ESP8266 (#20409) 2026-10-12 11:38:37 +13:00
J. Nick Koston 8e6ed71e2a [ethernet] Collapse duplicate CI test variants (#20406) 2026-10-12 11:38:37 +13:00
J. Nick Koston cf569702c4 [ci] Stop the toolchain tests from installing ESP-IDF (#20413) 2026-10-12 11:38:37 +13:00
Bascht74 9a35c5229b [uart] Reuse the ISR pin argument on an ESP8266 software serial reload (#20541) 2026-10-11 22:36:06 +00:00
Jesse Hills 96eaf42336 [api] Let a wizard input pick multiple entities (#20537) 2026-10-12 11:22:29 +13:00
tomaszduda23andClaude 94894d60b7 [nrf52] fix zigbee time component (#20527)
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-11 12:15:23 -10:00
puddly 4155b82f84 [usb_uart] Fix invalid stop bits and parity enum mappings (#20408) 2026-10-12 10:58:31 +13:00
J. Nick Koston 61d98534f6 [uart] Stop ESP8266 software serial RX ISR from blocking for a whole byte (#18519) 2026-10-12 10:56:21 +13:00
embeandJ. Nick Koston 8060dadd6c [micro_wake_word] Add opt-in internal RAM placement (#20529)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-11 11:49:49 -10:00
luar123andJ. Nick Koston 7d7db6bf90 [zigbee] add report action on esp32 (#20488)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-11 11:31:49 -10:00
Frédéric MetrichandClaude Opus 5.5 2ef95bbc28 [core] Copy trigger arguments in wait_until and script.wait (#20536)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:22:52 +00:00
luar123 3035a08b0a [zigbee] Improve basic cluster and add manufacturer and app version setting (#20519) 2026-10-11 11:19:43 -10:00
Frédéric MetrichandClaude Opus 5.5 9b34db14d4 [mqtt] Reject deferring actions in on_json_message (#20538)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:15:15 +00:00
heikkikiiandJ. Nick Koston 9998849009 [seeed_mr24hpc1] Wait for the module to boot before sending UART traffic (#18860)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-11 11:10:50 -10:00
Maciej KucharekandJ. Nick Koston f2db62adf2 [mipi_dsi] Add JC1060P470-V2 (#18695)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-11 11:08:02 -10:00
Frédéric MetrichandClaude 36fbf5bf22 [emontx] Add pause/resume actions to let another component take over the UART (#18756)
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-11 21:07:39 +00:00
Fabio Pugliese OrnellasandJ. Nick Koston 5cdefa910f [cse7761] Add new sensors (#17531)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-11 11:06:05 -10:00
Frédéric MetrichandClaude Opus 5.5 eb56ce2b76 [haier] Reject deferring actions in on_status_message (#20539)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:05:09 -10:00
2ab06fa937 [pipsolar] Add QET/QLT total energy sensors (#17347)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-11 10:39:22 -10:00
Clyde StubbsandClaude bea0ef264a [lvgl] Migrate to 9.6.0 (#20257)
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-12 07:36:32 +11:00
153c51e39f [gree] Keep display light on by default for YAN/YAA/YAC/YAC1FB9 (#18516)
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-10-11 10:24:50 -10:00
f562d7a6a4 [socket] Add join_multicast_group() and set_ipv6_multicast_if() helpers (#17045)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.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-10-11 09:59:44 -10:00
b73f151619 [pmsx003] add support for luftmy ld15 (#15296)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-11 09:49:06 -10:00
Frédéric MetrichandClaude Opus 5.5 da43b6292d [mk2pvrouter] Store the frame as bytes and read with read_byte() (#20530)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:41:58 -10:00
Frédéric MetrichandClaude Opus 5.5 b10eef537e [teleinfo] Log the tag name for an invalid timestamp (#20532)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:41:28 -10:00
Frédéric MetrichandClaude Opus 5.5 5c5737303f [teleinfo] Store the frame as bytes and read with read_byte() (#20531)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:41:02 -10:00
Frédéric MetrichandClaude Opus 5.5 16a474cce5 [core] Only treat action names as actions in has_non_synchronous_actions() (#20524)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:40:27 -10:00
86192d89f4 [ufm01] Improve stale recovery, add diagnostics and clear flow action (#18899)
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 5.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-11 09:29:56 -10:00
J. Nick Koston c130e2808b [display] Build on_page_change with build_callback_automation and compile it out when unused (#20456) 2026-10-11 09:23:05 -10:00
J. Nick Koston 78c7cd725d [remote_base] Transmit constant Haier, Mirage and AEHA codes without a heap vector (#20287) 2026-10-11 09:22:39 -10:00
Frédéric MetrichandClaude Opus 5.5 eff3a6f9a2 [modbus_client] Use the core deferring-actions validators (#20526)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:17:39 -10:00
Frédéric MetrichandClaude Opus 5.5 8449820480 [emontx] Use the core validate_synchronous_automation() helper (#20525)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:17:18 -10:00
9355bdc3c5 [canbus] Bus error logging, Auto-recover bus-off on ESP32 (#10953)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-11 09:12:28 -10:00
J. Nick Koston eb1aa2bfd6 [web_server] Keep the cover, valve and alarm panel action tables in flash (#20397) 2026-10-11 09:07:06 -10:00
6322cef8e2 [tmp102] Add hardware configuration options (#20063)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-11 09:06:40 -10:00
Frédéric MetrichandClaude Opus 5.5 c8a300164b [core] Add validators that reject deferring actions in an automation (#20523)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 08:53:37 -10:00
J. Nick Koston 86af0af0e4 [core] Add sum_bytes() and use it for the additive byte checksums components hand-roll (#20507) 2026-10-11 08:48:47 -10:00
J. Nick Koston 8e8261c39e [core] Add this week's review lessons to AGENTS.md (#20522) 2026-10-11 18:14:56 +00:00
Mat931andpre-commit-ci-lite[bot] e9073f7298 [systa_bus] Add text_sensor (#20518)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-10-11 07:38:37 -10:00
Frédéric Metrich 5186728d53 [emontx] Pass on_json/on_data data without copies and reject deferring actions (#20516) 2026-10-11 06:52:41 -10:00
Bonne Eggleston 1312d5eec0 [modbus] Pass the request's address to client device callbacks (#20504) 2026-10-11 06:51:41 -10:00
Frédéric MetrichandClaude Opus 5.5 c5cbc4a4e0 [emontx] Test lines of exactly MAX_LINE_LENGTH bytes (#20517)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:50:42 +00:00
J. Nick Koston 7f7ae4c8fc [uart_tcp] Prove the accept has run before checking what a new client receives (#20489) 2026-10-11 08:47:12 -04:00
J. Nick Koston 493db0c2bb [esp32] Add allow_insecure_tls() for components that skip certificate verification (#20506) 2026-10-11 08:45:19 -04:00
6001cde234 [emontx] Discard the rest of a line that overflows the buffer (#20515)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-10-11 08:41:02 -04:00
Frédéric MetrichandClaude Opus 5.5 3df03e4be9 [emontx] Read UART bytes with read_byte() (#20514)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 08:31:07 -04:00
1402cb7b64 [tuya] Add forced minute boundary time sync (#18603)
Co-authored-by: J. Nick Koston <nick@koston.org>
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-10-11 00:32:39 -10:00
Frédéric MetrichandClaude Opus 5.5 2681126986 [teleinfo] Fix out-of-bounds read when splitting a frame into groups (#20513)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:31:14 -10:00
J. Nick Koston 3e7fafb705 [daikin] Prefix the PendingComfort enumerators (#20512) 2026-10-11 00:09:55 -10:00
Clyde StubbsandClaude Opus 5.5 b6096c9437 [ci] Reset sdkconfig after ESP-IDF component discovery (#20511)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 10:04:57 +00:00
J. Nick Koston 681b481654 [tests] Reset shared App state before every host test (#20510) 2026-10-11 00:02:00 -10:00
84694b602a [ld2412] Fix crash when only some gate thresholds are configured (#18194)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-10 23:14:38 -10:00
cb54f6d6cc [daikin] Add presets and half-degree temperature steps (#17891)
Co-authored-by: James Nimmo <james@nimmo.net.nz>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-10 23:13:21 -10:00
TJ HornerandJ. Nick Koston 254690100c [hub75] add set_rotation and get_rotation methods (#13921)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-10 17:52:24 -10:00
6bc19d2a1a [espnow] Add support for ESPNow with ESP8266 (#14407)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-11 03:45:03 +00:00
fdf1752307 [toshiba] Add RAS-B10N3KV2 model support (#17550)
Co-authored-by: Claude Fable 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: Petr Helebrant <petr@helebrant.cz>
Co-authored-by: longerCZE <72692179+longerCZE@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Signed-off-by: Petr Helebrant <petr@helebrant.cz>
2026-10-10 17:35:42 -10:00
377a43c5be [toshiba] Add 'seiya' model support (#17830)
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>
Signed-off-by: Petr Helebrant <petr@helebrant.cz>
2026-10-10 17:17:22 -10:00
J. Nick Koston 70e4463644 [const] Move the adc_time and adc_averaging constants to the shared constants (#20503) 2026-10-10 16:56:35 -10:00
J. Nick Koston e6da33d3f0 [const] Move the verify_ssl constant to the shared constants (#20501) 2026-10-10 16:49:54 -10:00
Clyde StubbsandClaude Opus 5.5 eeaa8c78d8 [cli] Add line format error message option (#20470)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:33:18 +11:00
8af91c9663 [feedback] Support for overshoot_duration (#13186)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-11 01:23:31 +00:00
3be8c5b6b8 [canbus] Fix high frame rates causing crashes due to unbounded iteration (#14481)
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-10-10 15:07:36 -10:00
luar123 1763adf526 [zigbee] add support for SED on esp32 (#20487) 2026-10-10 13:24:13 -10:00
Paul Traina 924d7364ba [mipi_spi] ESP32-2432S028-9342 panel is RGB, not BGR (#19419) 2026-10-10 13:22:56 -10:00
esphome[bot] 5f43b8168e Bump bundled esphome-device-builder to 1.23.1 (#20497) 2026-10-10 12:00:39 -10:00
J. Nick Koston 642c3b7a9e [const] Add the milliampere hour unit to the shared constants (#20495) 2026-10-10 11:43:21 -10:00
78d3b9f5a1 [nrf52] Fix BLE OTA/log device discovery & transport selection on macOS (#17101)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-10 21:17:01 +00:00
6559ea470b [tca9548a] Allow separate I²C bus frequencies on each multiplexer port (#10422)
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-10-10 10:58:09 -10:00
J. Nick Koston ebbe3860ec [const] Move aqi to the shared constants (#20494) 2026-10-10 09:43:57 -10:00
Rupayon HaldarandJ. Nick Koston ff96530e4d [esp32] Honor sdkconfig CPU frequency options (#20158)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-10 09:13:05 -10:00
865863abf5 [cm1106] Add ABC function (#8894)
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: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-10-10 09:12:40 -10:00
17cbb75161 [mitsubishi] Control both vanes on multi-vane Mitsubishi minisplit heads (#12609)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-10 09:11:14 -10:00
f70b6a6b03 [mitsubishi] Add fahrenheit_compatibility mode (#18880)
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-10-10 09:04:06 -10:00
J. Nick Koston 2ac48d408f [const] Move automatic_baseline_calibration to the shared constants (#20492) 2026-10-10 08:56:49 -10:00
667dbd1ec3 [adalight] Add a component build test (#19103)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-10 08:56:16 -10:00
Jeff Brown a4ef8a6c90 [addressable_light] Clear white channel when setting HSV color (#20265) 2026-10-10 08:43:33 -10:00
Jonathan Swoboda ea7c277c08 [ci] Mark PRs with merge conflicts stale after 30 days (#20490) 2026-10-10 14:34:29 -04:00
Bascht74andJ. Nick Koston eb1afa4245 [tests] Close leftover server connections in the tcp_uart bridge and TCP client link tests (#20215)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-10 08:15:30 -10:00
J. Nick Koston 3447b68136 [ci] Compile an ESP8266 config with the native toolchain on Windows (#20467) 2026-10-10 08:00:40 -10:00
tomaszduda23 9f4a7cf5cc [nrf52] fix build error on 3.4.0 with pch ON (#20474) 2026-10-10 07:49:35 -10:00
J. Nick Koston 4ba0e1bfa2 [core] Fix the native ESP8266 and host builds on Windows without ccache (#20468) 2026-10-10 07:39:49 -10:00
jabla f4fbc3215d [espidf] Clean the build when the IDF target changes (#20473) 2026-10-10 07:28:40 -10:00
721228c1d2 [teleinfo] Add predefined properties for main tags of TIC standard and historical (#9167)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-10 08:31:06 -04:00
Clyde Stubbs f9a81869ef [it8951] Hold display enable pins off during deep-sleep (#20471) 2026-10-10 17:43:38 +11:00
J. Nick Koston d3e7bb119c [core] Deprecate the named status_momentary_warning/error overloads (#20369) 2026-10-10 02:39:21 +00:00
J. Nick Koston dd45f8a6f8 [usb_host] Warn instead of failing when a device duplicates a usb_uart entry (#20373) 2026-10-09 16:38:05 -10:00
ba7d3bd770 [gpio][esp32][uart] Add option to hold gpio during (deep)sleep and reboot (3/3) (#18964)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-10 02:37:46 +00:00
J. Nick Koston dc553aae55 [core] Use status_momentary_warning/error without a name in components (#20363) 2026-10-09 22:27:19 -04:00
J. Nick Koston f7b9646705 [improv_ble] Build the state triggers with build_callback_automation (#20444) 2026-10-09 16:06:14 -10:00
J. Nick Koston 0ae24f7536 [core] Catch heap allocating helper calls with explicit template arguments (#20391) 2026-10-10 02:00:41 +00:00
J. Nick Kostonandpre-commit-ci-lite[bot] 41cc96bbb8 [light] Keep the effect list in a flash table (#20204)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-10-09 15:55:09 -10:00
Zhaoqi Xu 6b03d68fac [heatpumpir] Fix fan speeds for mitsubishi_msc protocol (#20460) 2026-10-09 21:53:38 -04:00
J. Nick Koston 640d677913 [st7735][st7789v][waveshare_epaper] Declare log tags with ESPHOME_LOG_TAG (#20295) 2026-10-09 15:51:55 -10:00
J. Nick Koston 8567e69d46 [core] Add status_momentary_warning/error overloads without a name (#20362) 2026-10-09 15:51:38 -10:00
J. Nick Koston 4d187a2bb7 [valve] Build on_open and on_closed with build_callback_automation (#20442) 2026-10-09 15:43:22 -10:00
J. Nick Koston 073394b1eb [core] Let a trigger filter name its parent with {parent} (#20448) 2026-10-09 15:43:22 -10:00
J. Nick Koston 02bfa07819 [opentherm] Build before_send and before_process_response with build_callback_automation (#20450) 2026-10-09 15:42:55 -10:00
J. Nick Koston fdc163a30d [tuya] Build on_datapoint_update with build_trigger_callback (#20453) 2026-10-09 15:42:43 -10:00
J. Nick Koston bc44e8b550 [display] Test on_page_change with each from and to combination (#20457) 2026-10-09 21:40:40 -04:00
J. Nick Koston e894397503 [datetime] Build on_value with build_callback_automation (#20445) 2026-10-09 15:39:25 -10:00
J. Nick Koston f1a23a02c2 [time] Build on_time_sync with build_callback_automation (#20441) 2026-10-09 15:39:06 -10:00
J. Nick Koston 95dc9a749b [display_menu_base] Build the menu item triggers with build_parent_callback_automation (#20443) 2026-10-09 15:39:00 -10:00
J. Nick Koston 59963dd246 [espidf] Declare the map byproduct only when IDF has not (#20462) 2026-10-09 15:38:16 -10:00
J. Nick Koston 79fd5edc7f [core] Bind InternalGPIOPin to the platform pin class instead of a virtual base (#20455) 2026-10-09 15:37:10 -10:00
J. Nick Koston 5d2433f69a [graphical_display_menu] Build on_redraw with build_callback_automation (#20452) 2026-10-09 15:36:57 -10:00
J. Nick Koston 1b49e79bf8 [microphone] Build on_data with build_callback_automation (#20451) 2026-10-09 15:35:38 -10:00
Bascht74 4e4056d790 [uart] Share uart_tcp's write pacing and uart_id matcher (#20464) 2026-10-09 15:11:48 -10:00
Ali Alaeiandtomaszduda23 e5f5ca07c4 [zephyr_mcumgr] Enable MCUMGR_GRP_OS for UART transport, not just BLE (#18319)
Co-authored-by: tomaszduda23 <tomaszduda23@gmail.com>
2026-10-09 12:50:50 -10:00
a4a09043ec [deep_sleep][gpio][core][power_supply] Prepare for gpio hold (2/3) (#18966)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-09 11:32:24 -10:00
esphome[bot] a613073ee3 Bump bundled esphome-device-builder to 1.23.0 (#20446) 2026-10-09 10:06:10 -10:00
PolarGooseandJ. Nick Koston 8416a29862 [dlms_meter] Add support for "Landis+Gyr E450 HDLC Cyprus". Adapt the code for new API of dlms_parser (#16942)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-09 10:03:38 -10:00
J. Nick Koston 9ab0fa9669 [dlms_meter] Remove deprecated predefined sensor keys (#20386) 2026-10-09 09:36:28 -10:00
J. Nick Koston 05aeb2f094 [http_request] Test shared, lambda and trigger argument request headers (#20224) 2026-10-09 09:25:36 -10:00
J. Nick Koston c859c347cb [core] Pass a single condition to actions directly instead of wrapping it in a group (#19223) 2026-10-09 09:24:32 -10:00
J. Nick Koston 7ab24bf850 [nextion] Remove deprecated dump_device_info option (#20388) 2026-10-09 09:23:46 -10:00
J. Nick Koston cfbdd58e4a [lvgl] Remove deprecated update_on_release option (#20387) 2026-10-09 09:23:28 -10:00
8084916e30 [remote_base] Add Onkyo RI Wired Serial Support (#18595)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-09 19:23:10 +00:00
J. Nick Koston 9d02ab3734 [remote_base] Keep constant IR codes in flash (#20083) 2026-10-09 09:22:32 -10:00
J. Nick Koston 3442bb6624 [core] Remove deprecated esphome::RingBuffer alias (#20390) 2026-10-09 09:21:29 -10:00
J. Nick Koston 23d13d3a6e [core] Stop re-exporting alloc_helpers.h from helpers.h (#20389) 2026-10-09 09:21:16 -10:00
J. Nick Kostonandpre-commit-ci-lite[bot] 5cd7bf5993 [wireguard] Keep allowed IPs in a shared flash table (#20087)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-10-09 09:20:46 -10:00
J. Nick Koston f7b80e6d44 [dlms_meter] Remove deprecated provider option (#20385) 2026-10-09 09:20:17 -10:00
J. Nick Koston 588fde91b2 [template] Keep alarm codes in a shared flash table (#20084) 2026-10-09 09:19:35 -10:00
J. Nick Koston 94e6bd90d8 [sensor] Merge the radar timeout and throttle filters into one (#20118) 2026-10-09 09:19:22 -10:00
J. Nick Koston 456e3d8979 [ci] Keep bus group partners on the same runner (#20431) 2026-10-09 09:10:18 -10:00
J. Nick Koston 76ba7f0c88 [espidf] Cache the discovered component list once per target (#20432) 2026-10-09 09:10:06 -10:00
J. Nick Koston 0652e6c3fb [esp32][libretiny] Skip registering static destructors that never run (#20440) 2026-10-09 19:02:22 +00:00
luar123andJ. Nick Koston dbad1d4629 [core][zigbee] Wake main loop on defer/set_timeout from background task (#20356)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-09 18:58:33 +00:00
J. Nick Koston dd8315059c [core] Document the trigger callback helpers in AGENTS.md (#20435) 2026-10-09 08:56:50 -10:00
J. Nick Koston 822bbb85dc [core] Build trigger callbacks as stateless lambdas (#19606) 2026-10-09 08:50:32 -10:00
J. Nick Koston daea315b13 [nrf52] Fake a larger flash in testing mode (#20439) 2026-10-09 08:36:42 -10:00
J. Nick Koston 52b274c5e8 [improv_serial] Collapse duplicate CI test variants (#20405) 2026-10-09 07:34:34 -10:00
J. Nick Koston b5716a70c2 [ethernet] Remove deprecated clk_mode option (#20384) 2026-10-09 17:26:47 +00:00
J. Nick Koston 3eb8155ed2 [logger] Collapse duplicate CI test variants (#20402) 2026-10-09 07:22:48 -10:00
J. Nick Koston 11368a0b16 [adc] Collapse duplicate CI test variants (#20400) 2026-10-09 07:22:20 -10:00
J. Nick Koston cf0e64f84c [improv_serial] Use a numeric scheduler id for the Wi-Fi connect timeout (#20360) 2026-10-09 07:21:53 -10:00
J. Nick Koston d98ddeeb56 [mqtt] Use a numeric scheduler id for the deferred state publish (#20359) 2026-10-09 07:21:29 -10:00
J. Nick Koston 0701329ea7 [wifi] Use numeric scheduler ids instead of string names (#20357) 2026-10-09 07:20:59 -10:00
J. Nick Koston f5ce33a8de [esp32_ble_client] Track conn_id per attempt and stop logging an error on a failed open (#20417) 2026-10-09 07:16:17 -10:00
J. Nick Koston f89a603582 [esp8266] Keep switch lookup tables out of RAM (#20398) 2026-10-09 07:15:59 -10:00
J. Nick Koston 279f68c432 [tuya] Use numeric scheduler ids instead of string names (#20365) 2026-10-09 07:15:33 -10:00
J. Nick Koston b6ee8f8035 [power_supply] Use a numeric scheduler id for the power off timeout (#20367) 2026-10-09 07:15:14 -10:00
J. Nick Koston cea872be0b [modbus] Use a numeric scheduler id for the deferred server reply (#20368) 2026-10-09 07:14:59 -10:00
J. Nick Koston 558de5ebee [core] Use numeric scheduler ids instead of string names (5/5) (#20378) 2026-10-09 07:14:39 -10:00
J. Nick Koston aba0c5d7ee [dallas_temp] Use a numeric scheduler id and drop the cached address string (#20361) 2026-10-09 07:14:19 -10:00
J. Nick Koston 4c17ee7393 [core] Use numeric scheduler ids instead of string names (2/5) (#20375) 2026-10-09 07:13:46 -10:00
J. Nick Koston 24237a07c9 [ci] Refit component batch estimates to a full run (#20401) 2026-10-09 07:12:35 -10:00
J. Nick Koston d4d5ee87cb [ir_rf_proxy] Collapse duplicate CI test variants (#20399) 2026-10-09 07:12:14 -10:00
J. Nick Koston 56e6e332cb [ci] Skip the static analyzer on the generated protobuf dump file (#20414) 2026-10-09 07:11:45 -10:00
J. Nick Koston 7ac679aa04 [esp8266] Keep ArduinoJson's float tables in flash (Arduino core 3.1.2-esphome.3) (#20416) 2026-10-09 07:11:30 -10:00
J. Nick Koston 8a09e49e31 [ci] Skip the memory impact target build when the base test is missing (#20410) 2026-10-09 07:11:18 -10:00
J. Nick Koston 3485297e6e [core] Use numeric scheduler ids instead of string names (4/5) (#20377) 2026-10-09 07:11:01 -10:00
J. Nick Koston 541562451e [sprinkler] Key timers by index and keep them in a fixed array (#20379) 2026-10-09 07:10:48 -10:00
J. Nick Koston 824834f847 [core] Use numeric scheduler ids instead of string names (3/5) (#20376) 2026-10-09 07:10:19 -10:00
J. Nick Koston bc6fecfadb [core] Use numeric scheduler ids instead of string names (1/5) (#20374) 2026-10-09 07:10:04 -10:00
J. Nick Koston a2434f9aa3 [core] Document numeric scheduler ids in AGENTS.md (#20382) 2026-10-09 07:09:33 -10:00
J. Nick Koston 466081acc1 [core] Use numeric scheduler ids instead of string names (ESP32 2/2) (#20381) 2026-10-09 07:09:04 -10:00
J. Nick Koston d9b9ce2909 [core] Use numeric scheduler ids instead of string names (ESP32 1/2) (#20380) 2026-10-09 07:07:56 -10:00
J. Nick Koston a0f0949078 [mdns] Collapse duplicate CI test variants (#20403) 2026-10-09 06:42:00 -10:00
J. Nick Koston 6f4d0e9488 [debug] Collapse duplicate CI test variants (#20404) 2026-10-09 06:32:33 -10:00
J. Nick Koston 65e8345ab8 [ota] Collapse duplicate CI test variants (#20407) 2026-10-09 06:32:07 -10:00
J. Nick Koston de5c2cda09 [mcp23xxx_base] Keep the interrupt handler in IRAM on ESP8266 (#20409) 2026-10-09 06:31:42 -10:00
J. Nick Koston 9adb7e92e1 [ethernet] Collapse duplicate CI test variants (#20406) 2026-10-09 06:31:26 -10:00
J. Nick Koston c7d6a2e898 [sendspin] Fix duplicate definitions after the beta merge (#20424) 2026-10-08 22:13:45 -10:00
Jesse Hills 6188778538 Merge branch 'beta' into dev 2026-10-09 15:42:24 +13:00
Jesse Hills 2affc04e62 Merge pull request #20421 from esphome/bump-2026.10.0b2
2026.10.0b2
2026-10-09 15:42:04 +13:00
Clyde Stubbs 6513b176f4 [spd2010] Fix DOMAIN (#20422) 2026-10-09 02:23:56 +00:00
646f386f12 [spd2010] Add touchscreen platform (#19057)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-09 12:50:09 +11:00
J. Nick Koston 6a463c801e [ci] Stop the toolchain tests from installing ESP-IDF (#20413) 2026-10-08 21:42:45 -04:00
Jesse Hills d9795399ad [core] Use component DOMAIN constants instead of name literals (#20419) 2026-10-09 13:50:29 +13:00
Jesse Hills a648822027 Bump version to 2026.10.0b2 2026-10-09 13:45:36 +13:00
J. Nick Koston c6244fa4b9 [esp32] Record the PC for cache error and interrupt watchdog crashes (#20415) 2026-10-09 13:45:35 +13:00
J. Nick Koston f6746b4977 [api] Fix C++ unit test link failure when api is only a dependency (#20418) 2026-10-09 13:45:35 +13:00
J. Nick Koston f1f1a77151 [ci] Deal clang-tidy files across five shards (#20411) 2026-10-09 13:45:35 +13:00
J. Nick Koston ba1af627fd [ci] Pin the shared I2C bus in test configs that left it implicit (#20412) 2026-10-09 13:45:35 +13:00
J. Nick Koston b82dfd7e64 [mdns] Bump espressif/mdns to 1.14.0 (#20383) 2026-10-09 13:45:35 +13:00
Kevin AhrendtandJ. Nick Koston 7de2bcb584 [api][sendspin] Add a request for the Sendspin pairing token (#20355)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-09 13:45:35 +13:00
esphome[bot] d5d11e1077 Bump aioesphomeapi from 46.8.0 to 46.9.0 (#20358) 2026-10-09 13:45:34 +13:00
Kevin Ahrendt ab66b20944 [sendspin] Add an unpaired access switch (#20350) 2026-10-09 13:45:34 +13:00
Kevin Ahrendt baf1682635 [sendspin] Add the dynamic pairing code (#20349) 2026-10-09 13:45:34 +13:00
Kevin Ahrendt eea11834f8 [sendspin] Add the static pairing code, pairing window and unpaired access option (#20348) 2026-10-09 13:45:34 +13:00
Kevin Ahrendt 1d86cc39e9 [sendspin] Update to sendspin-cpp v0.9.3 (#20347) 2026-10-09 13:45:34 +13:00
J. Nick Koston 686252f151 [esp8266] Fix Arduino core regressions that keep data in RAM and link the unused unwinder (#20337) 2026-10-09 13:45:23 +13:00
J. Nick Koston f5c15f66a5 [esp8266] Force-include the throw stubs into the core and libraries on the native toolchain (#20336) 2026-10-09 13:45:23 +13:00
J. Nick Koston 9d6b50320d [noise] Bump noise-c to 0.1.31 and libsodium to 1.10021.12 (#20345) 2026-10-09 13:45:23 +13:00
Jesse HillsandJ. Nick Koston b903c54be4 [combination] Use cv.rename_key for the deprecated coeffecient key (#20159)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-09 13:45:23 +13:00
esphome[bot] 63b4a59588 Bump bundled esphome-device-builder to 1.22.0 (#20334) 2026-10-09 13:45:23 +13:00
Jesse Hills adc6255fd4 [core] Let cv.rename_key pass non-dict input through (#20331) 2026-10-09 13:45:23 +13:00
J. Nick Koston b28e7a1357 [esp8266] Normalize misspelled boards and fall back to PlatformIO for unknown ones (#20322) 2026-10-09 13:45:23 +13:00
J. Nick Koston e7ba9a5726 [api] Add a progmem option for bytes fields kept in flash (#20318) 2026-10-09 13:45:22 +13:00
J. Nick Koston 2586e78127 [core] Warn on every run that Intel Mac support ends by 2027.6.0 (#20303) 2026-10-09 13:45:22 +13:00
J. Nick Koston 23f6dedb42 Pin smp to 4.1.0 on Intel macOS so cbor2 installs from a wheel (#20302) 2026-10-09 13:45:22 +13:00
David Buezas 10f30ddb59 [cc1101] Keep the remote_receiver interrupt on a shared GDO0 pin in begin_tx (#20296) 2026-10-09 13:45:22 +13:00
Keith BurzinskiandClaude Opus 5.5 96a66f3ddd [core] Wake the main loop on its own task notification index on ESP32 (#20326)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 13:45:22 +13:00
esphome[bot] 5bf8ceac66 Bump aioesphomeapi from 46.7.0 to 46.8.0 (#20321) 2026-10-09 13:45:22 +13:00
mrtoy-me 4d42352ec5 [tas58xx] Improve TAS5805M audio performance and use GPIO1 for TAS5825M faults (#20319) 2026-10-09 13:45:22 +13:00
esphome[bot] 479232b6c0 Bump aioesphomeapi from 46.6.0 to 46.7.0 (#20317) 2026-10-09 13:45:22 +13:00
J. Nick Koston 0d596a20f8 [esp32] Record the PC for cache error and interrupt watchdog crashes (#20415) 2026-10-09 00:25:11 +00:00
Jesse Hills 5ca272b262 [mipi_dsi] Make config tests pass in isolation (#20420) 2026-10-09 13:20:52 +13:00
J. Nick Koston ff0d4347d6 [api] Fix C++ unit test link failure when api is only a dependency (#20418) 2026-10-08 14:05:18 -10:00
Jesse Hills e03db60a52 [core] Add a DOMAIN constant to every component (#20395) 2026-10-08 14:04:26 -10:00
J. Nick Koston b4bd95b2ac [ci] Deal clang-tidy files across five shards (#20411) 2026-10-08 13:48:03 -10:00
J. Nick Koston fe21e3ad16 [ci] Pin the shared I2C bus in test configs that left it implicit (#20412) 2026-10-08 13:14:00 -10:00
Jesse Hills 85108bfd84 [qspi_amoled] Remove legacy renamed component stub (#20396) 2026-10-08 12:58:20 -10:00
J. Nick Koston 92bfd0ea7e [mdns] Bump espressif/mdns to 1.14.0 (#20383) 2026-10-08 21:18:21 +00:00
dependabot[bot] 542de8f03a Bump j178/prek-action from 3.0.0 to 3.0.1 (#20372)
Signed-off-by: dependabot[bot] <support@github.com>
2026-10-08 09:36:01 -10:00
dependabot[bot] 06864917a5 Bump filelock from 4.0.10 to 4.0.12 (#20371)
Signed-off-by: dependabot[bot] <support@github.com>
2026-10-08 19:31:14 +00:00
dependabot[bot] bcfe6fd1d2 Bump prek from 0.5.4 to 0.5.5 (#20370)
Signed-off-by: dependabot[bot] <support@github.com>
2026-10-08 19:30:04 +00:00
Kevin AhrendtandJ. Nick Koston e395e626ec [api][sendspin] Add a request for the Sendspin pairing token (#20355)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-08 19:12:20 +00:00
esphome[bot] f9b5eca2b9 Bump aioesphomeapi from 46.8.0 to 46.9.0 (#20358) 2026-10-08 18:48:19 +00:00
Kevin Ahrendt 920295527f [sendspin] Add an unpaired access switch (#20350) 2026-10-08 08:13:31 -10:00
Kevin Ahrendt 9c79fa7430 [sendspin] Add the dynamic pairing code (#20349) 2026-10-08 13:36:12 -04:00
J. Nick Koston 4b2f8122e3 [udp] Keep constant write payloads in shared flash tables (#20268) 2026-10-08 07:31:09 -10:00
J. Nick Koston 052f5a4cc6 [core] Declare component log tags with ESPHOME_LOG_TAG (#20291) 2026-10-08 07:24:17 -10:00
Kevin Ahrendt 78bf74eafb [sendspin] Add the static pairing code, pairing window and unpaired access option (#20348) 2026-10-08 07:19:14 -10:00
Kevin Ahrendt b1c145dd5f [sendspin] Update to sendspin-cpp v0.9.3 (#20347) 2026-10-08 06:33:46 -10:00
J. Nick Koston 3055343b6d [esp8266] Use toolchain 10.3.0-esphome.3, which trims newlib RAM (#20335) 2026-10-08 06:15:11 -10:00
J. Nick Koston 70f64374d4 [esp8266] Use toolchain 10.3.0-esphome.3, which trims newlib RAM (#20335) 2026-10-08 06:11:51 -10:00
J. Nick Koston 1362e52aa7 [esp8266] Fix Arduino core regressions that keep data in RAM and link the unused unwinder (#20337) 2026-10-08 06:09:39 -10:00
J. Nick Koston e51e0e683f [esp8266] Force-include the throw stubs into the core and libraries on the native toolchain (#20336) 2026-10-08 06:06:45 -10:00
J. Nick Koston 7910d02b71 [web_server] Emit the resource tables with cg.extern_progmem_array (#20300) 2026-10-08 11:34:34 -04:00
J. Nick Koston f224cb37d6 [esp32_ble_server] Cover string, typed number, repeated and trigger argument set_value payloads in the component test (#20308) 2026-10-08 11:32:50 -04:00
J. Nick Koston 79813a1eac [canbus] Keep constant send payloads in shared flash tables (#20309) 2026-10-08 11:14:02 -04:00
J. Nick Koston 9066ad6cd0 [modbus_client] Cover shared constant and trigger argument write values in the component test (#20327) 2026-10-08 11:13:03 -04:00
J. Nick Koston f5e4c0356f [core] Move zstd_module from the api wizard to esphome.helpers (#20342) 2026-10-08 11:08:38 -04:00
J. Nick Koston 3c634ecfd8 [noise] Bump noise-c to 0.1.31 and libsodium to 1.10021.12 (#20345) 2026-10-08 06:48:08 -04:00
Jesse HillsandJ. Nick Koston 57eeca6341 [combination] Use cv.rename_key for the deprecated coeffecient key (#20159)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-07 20:15:43 -10:00
esphome[bot] 23ba80c9c7 Bump bundled esphome-device-builder to 1.22.0 (#20334) 2026-10-07 20:03:30 -10:00
Jesse Hills 73bbd75d3f [core] Let cv.rename_key pass non-dict input through (#20331) 2026-10-08 17:29:56 +13:00
J. Nick Koston 0a7763d902 [esp8266] Normalize misspelled boards and fall back to PlatformIO for unknown ones (#20322) 2026-10-08 17:05:18 +13:00
J. Nick Koston 7aeed8455e [api] Add a progmem option for bytes fields kept in flash (#20318) 2026-10-08 16:24:27 +13:00
J. Nick Koston 8eba5a1c28 [core] Warn on every run that Intel Mac support ends by 2027.6.0 (#20303) 2026-10-08 16:23:01 +13:00
J. Nick Koston 0f552a8eef Pin smp to 4.1.0 on Intel macOS so cbor2 installs from a wheel (#20302) 2026-10-08 16:14:40 +13:00
David Buezas 31c488e5b0 [cc1101] Keep the remote_receiver interrupt on a shared GDO0 pin in begin_tx (#20296) 2026-10-07 22:53:12 -04:00
J. Nick Koston 1ee4940d4d [remote_base] Cover shared, empty and trigger argument abbwelcome payloads in the component test (#20307) 2026-10-07 16:43:44 -10:00
J. Nick Koston 640ef1bb17 [canbus] Cover shared, string and trigger argument send payloads in the component tests (#20306) 2026-10-07 16:43:34 -10:00
luar123 42edfe0681 [CI] Remove obsolete platform labels (#20301) 2026-10-07 22:18:47 -04:00
Keith BurzinskiandClaude Opus 5.5 6a6d263c0d [core] Wake the main loop on its own task notification index on ESP32 (#20326)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 21:17:16 -05:00
esphome[bot] 68a5e33182 Bump aioesphomeapi from 46.7.0 to 46.8.0 (#20321) 2026-10-08 00:17:23 +00:00
mrtoy-me 883af036a9 [tas58xx] Improve TAS5805M audio performance and use GPIO1 for TAS5825M faults (#20319) 2026-10-07 13:57:46 -10:00
esphome[bot] 8f003dfa3b Bump aioesphomeapi from 46.6.0 to 46.7.0 (#20317) 2026-10-07 13:31:24 -10:00
Jesse Hills 6d3792e5c5 [ci] Only run sync dependency versions workflow on PRs targeting dev (#20316) 2026-10-08 12:22:07 +13:00
Jesse Hills c73ec262e9 Merge branch 'beta' into dev 2026-10-08 12:15:20 +13:00
Jesse Hills 33cf262616 Merge pull request #20305 from esphome/bump-2026.10.0b1
2026.10.0b1
2026-10-08 12:14:49 +13:00
Jesse Hills 7a1bfed81f Bump version to 2026.11.0-dev 2026-10-08 12:01:52 +13:00
J. Nick Koston 12c03d539a [cc1101] Keep constant send_packet payloads in shared flash tables (#20264) 2026-10-07 10:54:56 -10:00
J. Nick Koston 24fc86c2c4 [core] Deprecate TemplatableValue::is_static_string and get_static_string (#20280) 2026-10-07 20:48:46 +00:00
Jesse Hills 5e4990badc Bump version to 2026.10.0b1 2026-10-08 09:27:43 +13:00
J. Nick Koston 16568c0453 [sx126x] Keep constant send_packet payloads in shared flash tables (#20273) 2026-10-07 10:24:14 -10:00
J. Nick Koston 677c089614 [sx127x] Keep constant send_packet payloads in shared flash tables (#20275) 2026-10-07 10:23:59 -10:00
J. Nick Koston 6db2e781ce [api] Keep Home Assistant action fields in a flash table (#20237) 2026-10-07 10:09:49 -10:00
J. Nick Koston 3296bccd3c [api] Keep the protobuf write cursor in a register and emit sub-message tags inline (#20124) 2026-10-07 10:08:25 -10:00
J. Nick Koston 0323fd36d4 [core] Clone git PlatformIO packages in parallel in the prefetch (#18837) 2026-10-08 09:00:07 +13:00
J. Nick Koston 1ba323e9a3 [core] Keep log tags in flash on ESP8266 with ESPHOME_LOG_TAG (#20290) 2026-10-07 09:51:17 -10:00
Jesse HillsandJ. Nick Koston 6f940a486b [api] Add a device wizard for Home Assistant onboarding (#19966)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-10-07 09:49:31 -10:00
dependabot[bot] 82cffe81f3 Bump platformdirs from 4.12.2 to 4.12.3 (#20297)
Signed-off-by: dependabot[bot] <support@github.com>
2026-10-07 19:30:59 +00:00
dependabot[bot] 887e3c302f Bump filelock from 4.0.9 to 4.0.10 (#20298)
Signed-off-by: dependabot[bot] <support@github.com>
2026-10-07 19:30:26 +00:00
TheSwert 2560a4436e [audio_http][core] Add ca_certificate_path to audio_http media source (#19392) 2026-10-07 09:28:10 -10:00
Clyde StubbsandClaude Sonnet 5 6dec759261 [core] Select I/O expander pin providers by I2C address (#18939)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-07 09:18:40 -10:00
1c0d406cbb [serial_proxy] Add USB_SERIAL port type and device identity (#19308)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2026-10-07 19:05:07 +00:00
Jesse Hills ae6ebd2d9d [epaper_spi] Add is_updating condition (#20261) 2026-10-07 14:52:25 -04:00
J. Nick Koston 13a8efd694 [api] Reply when an infrared/RF raw timings transmit has completed (#19086) 2026-10-07 08:25:44 -10:00
678975b6b2 [usb_host] iManufacturer and iProduct descriptor filters (#19095)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2026-10-07 13:25:28 -05:00
006f5f0fc9 [xiaomi_mccgq02hl] Add Xiaomi Mijia Door/Window Sensor 2 (MCCGQ02HL) (#20104)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-07 07:09:35 -10:00
J. Nick Koston cf96c53279 [espnow] Keep constant send payloads in shared flash tables (#20271) 2026-10-07 05:40:12 -10:00
J. Nick Kostonandpre-commit-ci-lite[bot] 72413494b0 [esp32] Disable mbedTLS TLS and TLS-only crypto when no component needs them (#18877)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-10-07 05:29:17 -10:00
J. Nick Koston 51dbd3a63f [core] Test that if and while branches resume after delays, stops, nesting and script modes (#20282) 2026-10-07 08:33:20 -04:00
J. Nick Koston 19561d44a2 [cc1101] Cover shared, string and trigger argument send_packet payloads in the component test (#20263) 2026-10-07 08:32:18 -04:00
J. Nick Koston 1661f84deb [udp] Cover identical constant and trigger argument write payloads in the component test (#20266) 2026-10-07 08:32:04 -04:00
J. Nick Koston f0035c1db6 [sx127x] Cover shared constant, string and trigger argument send_packet payloads in the component test (#20274) 2026-10-07 08:31:24 -04:00
J. Nick Koston b78f00a037 [espnow] Cover a send payload lambda that reads trigger arguments in the component test (#20270) 2026-10-07 08:31:04 -04:00
J. Nick Koston 40809ffa10 [sx126x] Cover shared constant and trigger argument send_packet payloads in the component test (#20272) 2026-10-07 08:30:48 -04:00
luar123 e4d8913680 [zigbee] Add zigbee support for number on esp32 (#20243) 2026-10-07 08:21:17 -04:00
Bascht74 d234a4d51e [tcp_uart][uart_tcp] Add a disconnects sensor (#20096) 2026-10-06 20:45:29 -10:00
Raphael HehlandJ. Nick Koston 3cc89a35a2 [hoermann_hcp] Send each command once, and the lamp as a state (#19765)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-06 20:23:08 -10:00
Jonathan Swoboda 15aac378df [ci] Run label checks in parallel instead of cancelling them (#20259) 2026-10-06 20:19:41 -10:00
J. Nick Kostonandpre-commit-ci-lite[bot] 0e04b230fe [api] Add outgoing connections so the device can dial Home Assistant (or anything that implements native API) (#18919)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-10-07 16:22:14 +13:00
Guillaume RischardandClyde Stubbs f3f79c301b [mipi_spi] Add Waveshare ESP32-S3-Touch-LCD-1.46 SPD2010 model (#19056)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2026-10-07 13:24:08 +11:00
Francesco Pace 923700dcc9 [espectre] Add Wi-Fi CSI motion detection component (#19801) 2026-10-06 19:46:06 -05:00
Clyde Stubbs 19dce8840d [ci] Ignore esphome/idf_component.yml in external component bot (#20258) 2026-10-07 11:19:44 +11:00
Jesse Hills e040647530 [homeassistant] Add text, select and button platforms (#19964) 2026-10-07 11:46:36 +13:00
Bascht74andJ. Nick Koston ab5a4b4f04 [uart_tcp] Claim the UART, reject duplicate server ports, pace on loop_interval (#20180)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-06 15:50:28 -06:00
dependabot[bot] cb1b4b0bd7 Update tzdata requirement from >=2026.4 to >=2026.5 (#20250)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-06 17:07:13 -04:00
dependabot[bot] 597a4e46b1 Bump pylint from 4.1.1 to 4.1.2 (#20249)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-06 17:06:37 -04:00
Clyde Stubbs e68a499429 [motion] Add gesture detection (#16787) 2026-10-06 15:57:20 -05:00
Jonathan Swoboda a56c85949a [wireguard] Bump the library to esphome/wireguard 0.4.8 (#20246) 2026-10-06 16:28:03 -04:00
J. Nick Koston 4eeb110247 [esp32] Extract prefetched ESP-IDF tool archives in parallel (#18841) 2026-10-07 09:26:46 +13:00
mrtoy-me 194273955c [tas58xx] Add support for tas5825 dac (#19971) 2026-10-06 15:22:00 -05:00
Clyde StubbsandClaude Opus 5.5 490d478e54 [mipi_spi] Light platform for AMOLED display brightness (#20212)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 07:02:34 +11:00
Keith BurzinskiandClaude Fable 5.1 1520d228bc [i2s_audio] Park the speaker data-out pin low at setup (#20226)
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-06 11:43:49 -06:00
Bascht74 61ac0cb753 [tcp_uart] Keep received bytes readable after the connection drops (#20164) 2026-10-06 11:39:02 -06:00
dependabot[bot] 2b02602a49 Bump smpclient from 7.2.0 to 7.3.0 (#17443)
Signed-off-by: dependabot[bot] <support@github.com>
2026-10-06 11:37:14 -06:00
Bascht74andJ. Nick Koston cad670785b [socket] Add host tests for TcpClientLink and TcpListener (#20167)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-06 11:28:06 -06:00
Kevin Ahrendt 980fed9e96 [sendspin] Require a type on the switch platform (#20189) 2026-10-06 11:23:57 -06:00
Jonathan Swoboda f670993fe9 [ethernet] Pin the IDF version in the KSZ8851SNL test fixtures (#20245) 2026-10-06 11:40:53 -04:00
Jonathan Swoboda fb20966353 [wireguard] Restart the connection while the peer is offline so the endpoint is re-resolved (#20231) 2026-10-06 11:16:57 -04:00
Clyde Stubbs 74d0a90308 [esphome] Revise warning text for password use (#20230) 2026-10-06 11:11:25 -04:00
J. Nick Koston fef3804aa5 [espidf] Fix V1 ECDSA signed builds when skipping the bootloader (#20244) 2026-10-06 09:09:47 -06:00
luar123 e66ef45df7 [zigbee] add missing tests (#20234) 2026-10-06 11:08:01 -04:00
J. Nick Koston 1f53ccb572 [api] Cover Home Assistant action fields in the api test config (#20236) 2026-10-06 11:04:45 -04:00
b63794b53c [web_server_idf] Respect explicit HTTP header limit (#18657)
Co-authored-by: jeroen85 <26403565+jeroen85@users.noreply.github.com>
Co-authored-by: Jeroen Jansen <jeroen@Jeroens-MacBook-Air.local>
2026-10-06 11:00:50 -04:00
J. Nick Koston a69c648c6f [neopixelbus] Cover addressable_color_wipe with a random color on ESP8266 (#20221) 2026-10-06 09:04:52 -04:00
J. Nick Koston 29090d50a7 [event] Cover how configs and components read and set event types in the component test (#20216) 2026-10-06 09:04:37 -04:00
J. Nick Koston 9b1a75b0ed [fan] Cover preset mode reads and runtime sets in the component test (#20209) 2026-10-06 09:03:58 -04:00
J. Nick Koston b7ddd1daa2 [climate] Cover custom mode reads and runtime sets in the component test (#20208) 2026-10-06 09:03:20 -04:00
Bascht74 6002c28a8d [host] Ignore SIGPIPE so a write to a closed peer fails with EPIPE (#20228) 2026-10-06 22:38:16 +11:00
Bascht74 718b6651fe [ci] Run pylint when its version, dependencies or configuration change (#20202) 2026-10-05 21:20:28 -05:00
J. Nick Koston 6191b7553e [light] Cover get_effects() as published configs use it in the component test (#20203) 2026-10-05 21:07:37 -05:00
Bascht74 082a0b746a [lvgl] Remove redundant __aexit__ override (#20201) 2026-10-05 19:45:15 -05:00
J. Nick Koston 8d8bd73112 [ble_device_base] Keep MAC filter lists in shared flash tables (#20088) 2026-10-05 19:28:36 -05:00
J. Nick Koston 3a3707926d [wifi] Run the LN882x fallback AP on its own instead of retrying AP+STA every loop (#20166) 2026-10-05 19:25:14 -05:00
J. Nick Koston 80dc93d6ce [core] [select] Keep select options in flash with a shared ConstVector (#20117) 2026-10-05 19:24:31 -05:00
J. Nick Koston 40af90ad25 [logger] Use the static ring buffer structure as its handle on ESP32 (#20129) 2026-10-05 19:24:13 -05:00
J. Nick Koston 4097f45677 [sx1509] Keep the keypad keys in flash (#20111) 2026-10-05 19:23:39 -05:00
esphome[bot] 039f07d332 Bump bundled esphome-device-builder to 1.21.0 (#20199) 2026-10-05 22:10:00 +00:00
dependabot[bot] cdcc7bbf74 Bump ruff from 0.16.9 to 0.16.10 (#20196)
Signed-off-by: dependabot[bot] <support@github.com>
2026-10-05 22:07:49 +00:00
dependabot[bot]andJ. Nick Koston 9af9414dcc Bump cryptography from 48.0.1 to 50.0.2 (#20193)
Co-authored-by: J. Nick Koston <nick@koston.org>
Signed-off-by: dependabot[bot] <support@github.com>
2026-10-05 22:07:43 +00:00
dependabot[bot] cf334e8758 Bump pylint from 4.0.10 to 4.1.1 (#20194)
Signed-off-by: dependabot[bot] <support@github.com>
2026-10-05 22:07:37 +00:00
dependabot[bot]andJ. Nick Koston 888043762e Bump filelock from 4.0.6 to 4.0.9 (#20195)
Co-authored-by: J. Nick Koston <nick@koston.org>
Signed-off-by: dependabot[bot] <support@github.com>
2026-10-05 22:07:33 +00:00
dependabot[bot] 7af96c8d36 Bump CodSpeedHQ/action from 5.2.1 to 5.4.0 (#20197)
Signed-off-by: dependabot[bot] <support@github.com>
2026-10-05 22:07:10 +00:00
J. Nick Koston a778baddca [usb_uart] Only compile the vendor drivers that are configured (#20190) 2026-10-05 17:56:10 -04:00
Samuel Sieb fc441646f4 [unicode][font] move extract_unicode_codepoint to new component (#19969) 2026-10-05 17:40:06 +00:00
J. Nick Koston 4a47a984a0 [audio_file] Keep audio files in flash instead of building them in setup() (#20072) 2026-10-05 12:41:33 -04:00
J. Nick Koston 8735aee847 [wifi] Leave the AP before an LN882x reboots (#20176) 2026-10-05 10:56:24 -05:00
J. Nick Koston c8eaf518ba [libretiny] Flag the LN882H SDK default MAC (#20175) 2026-10-05 10:56:02 -05:00
J. Nick Koston bb8c23805a [wifi] Connect LN882x to the BSSID asked for (#20174) 2026-10-05 10:55:11 -05:00
J. Nick Koston f334b9ea11 [core] Decode base64 in a single pass (#20123) 2026-10-05 10:54:17 -05:00
J. Nick Koston 666cf169b9 [remote_base] Keep RC switch protocols in flash (#20081) 2026-10-05 10:54:03 -05:00
J. Nick Koston a058937fe0 [api] Count packed varints a machine word at a time (#20122) 2026-10-05 10:53:47 -05:00
J. Nick Koston 417fdd9dc7 [nrf52] Share the ccache between devices (#20028) 2026-10-05 10:53:35 -05:00
Sebastian Noack bc388b1de5 [epaper_spi] Add SSD1681 and the Waveshare 1.54in V2 panel (#18073) 2026-10-05 11:51:49 -04:00
Jonathan Swoboda 9820896430 [wireguard] Bump the library to esphome/wireguard 0.4.7 (#20188) 2026-10-05 11:34:07 -04:00
J. Nick Koston 4258b226a4 [wifi] Default LN882x power save to none, as on BK72xx and RTL87xx (#20170) 2026-10-05 10:26:22 -05:00
J. Nick Koston 5be0aca6d2 [libretiny] Build LN882x with 1-byte enums to match the SDK's WiFi library (#20173) 2026-10-05 10:24:19 -05:00
J. Nick Koston 1110857ffa [wifi] Keep ESP8266's report of a replaced attempt from failing the next one (#20183) 2026-10-05 10:21:52 -05:00
J. Nick Koston 9a97419cfc [ethernet] Store PHY registers in a StaticVector sized from the config (#20131) 2026-10-05 10:21:23 -05:00
ca74e71348 [emontx] Use total state class for energy sensors (#20128)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-10-05 09:16:21 -05:00
Jonathan Swoboda 00303fb72b [esp32][api][noise] Resolve arduino's libsodium requirement to the converted esphome/libsodium (#20185) 2026-10-05 09:55:18 -04:00
Bascht74 aa02a0996a [socket][tcp_uart][uart_tcp] Reject an IPv6 host at config time (#20165) 2026-10-05 08:48:33 -05:00
Bascht74 59b49d34cc [tcp_uart][uart_tcp] Expose the no-arg load_settings() and mark UartTcp final (#20163) 2026-10-05 08:47:18 -05:00
Bascht74 862047386c [modbus] Reject send_wait_time and turnaround_time above 65535 ms (#20160) 2026-10-05 08:46:36 -05:00
Clyde Stubbs bbd05e4811 [esp32] Work around ESP-IDF stack bug on P4 (#20181) 2026-10-05 07:50:18 -05:00
J. Nick Koston a02fa71dc8 Merge pull request #20179 from esphome/sync/integration-durations 2026-10-05 07:48:03 -05:00
esphome[bot] 99fd1bca3e [ci] Refresh integration test durations 2026-10-05 05:58:43 +00:00
J. Nick Koston 2d73ea24ca [libretiny] Keep a scan that finds no networks from resetting the LN882x (#20169) 2026-10-04 21:23:09 -05:00
Max Slotov 645144ad4d [uart] Validate parity and stop bits correctly (#20161) 2026-10-05 13:21:48 +11:00
J. Nick Koston 88378ab354 [mipi_spi] Keep the init sequence in flash (#20089) 2026-10-04 21:21:28 -05:00
J. Nick Koston f91ac5ed1d [mipi_dsi] Keep the init sequence in flash (#20091) 2026-10-04 21:20:56 -05:00
J. Nick Koston 3b319240c6 [mipi_rgb] Keep the init sequence in flash (#20090) 2026-10-04 21:20:32 -05:00
Clyde StubbsandJ. Nick Koston d9b3f299e0 [exponential_moving_average] Add EMA preserved across reboots (#19882)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-10-05 13:19:12 +11:00
J. Nick Koston 6f2d2a2001 [libretiny] Fix LN882x boot crash with the logger or a uart on UART0 (#20162) 2026-10-05 14:53:47 +13:00
esphome[bot] 8b9d51cf93 Bump bundled esphome-device-builder to 1.20.0 (#20172)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-10-04 20:21:46 -05:00
J. Nick Koston 66eec7af15 [wifi] Keep a second fallback AP start from resetting the LN882x (#20168) 2026-10-05 13:58:35 +13:00
J. Nick Koston 60e511282a [udp] Keep addresses in a shared flash table (#20085) 2026-10-05 13:45:18 +13:00
J. Nick Koston e4be4c7fcb [select] Keep select.is option lists in flash (#20108) 2026-10-04 18:30:45 -05:00
J. Nick Koston 99bf080092 [select] Cover select.is in the component test (#20112) 2026-10-04 18:30:44 -05:00
J. Nick Koston 0017b9c439 [matrix_keypad] Keep the keys in flash (#20110) 2026-10-05 12:26:42 +13:00
J. Nick Koston ad1b6ab37e [web_server] Keep allowed origins in a shared flash table (#20086) 2026-10-05 12:23:36 +13:00
J. Nick Koston acf7257293 [core] Use the static queue structure as the FreeRTOSQueue handle (#20133) 2026-10-05 12:17:22 +13:00
J. Nick Koston b36c5441cb [esp32] Remove the unused loop_task_handle global (#20132) 2026-10-05 12:16:16 +13:00
J. Nick Koston feedea4954 [web_server] Remove dead ESP-IDF event source connect callback and pack expose_log_ (#20130) 2026-10-05 12:15:58 +13:00
J. Nick Koston 4f9cd7821a [core] Log ignored --skip-bootloader at debug level (#20059) 2026-10-05 12:14:42 +13:00
J. Nick Koston 31e0a31c65 [api] Skip the listen backlog setter when it matches the default (#20066) 2026-10-05 12:13:41 +13:00
J. Nick Koston e09cab94d4 [version] Skip the hide hash and hide timestamp setters when they match the defaults (#20067) 2026-10-05 12:13:01 +13:00
J. Nick Koston e708c85100 [uart] Skip the parity setter when it matches the default (#20069) 2026-10-05 12:12:34 +13:00
J. Nick Koston cfbbd13b86 [remote_receiver] Pack carrier_duty_percent_ next to with_dma_ (#20135) 2026-10-05 12:05:29 +13:00
J. Nick Koston 8f0e032b5e [esp32_ble] Group the one byte io_cap_ with the other byte fields (#20136) 2026-10-05 12:05:15 +13:00
J. Nick Koston 0dc10d9f30 [improv_ble] Pack the byte fields into the padding after connecting_sta_ (#20137) 2026-10-05 12:04:44 +13:00
J. Nick Koston c4aa8fa9c5 [wifi] Place ap_ before the byte fields so they pack after it (#20138) 2026-10-05 12:02:40 +13:00
J. Nick Koston 9b83f37b78 [core] Let the empty Scheduler mutex take no space on single-threaded platforms (#20139) 2026-10-05 12:02:27 +13:00
J. Nick Koston a1a4474a01 [script] Let the empty argument tuple take no space for scripts without parameters (#20140) 2026-10-05 12:02:24 +13:00
J. Nick Koston 3a06318b65 [template] Use distinct empty types so both unused select members take no space (#20141) 2026-10-05 12:02:18 +13:00
J. Nick Koston 2f9e37d6a9 [wifi] Let the empty pending_ flags struct take no space (#20142) 2026-10-05 12:00:23 +13:00
4813 changed files with 152754 additions and 24798 deletions
+151
View File
@@ -0,0 +1,151 @@
---
name: hardware-test
description: Test an ESPHome change on real boards over USB. Use this whenever a task asks to test, verify, reproduce, bench, A/B or soak a pull request, a branch or a crash on hardware, or mentions flashing, serial logs, esphome upload, a captive portal or fallback AP test, or results to post on a PR. Covers finding the serial ports, building from the right checkout, flashing each chip family (including the LibreTiny kits that stock tools cannot flash), bounded log capture, proven test flows and how to report results.
allowed-tools: Read, Write, Edit, Bash, Glob, Grep
---
# Hardware testing
The goal of a hardware test is evidence a reviewer can trust: which build ran on which board, what was
driven, what the logs show, and the same flow on a `dev` build when a quirk could be pre-existing.
`$SKILL` below is this directory (`.agents/skills/hardware-test`).
## Ground rules
1. **Serial ports cannot be shared.** One bounded capture per port, stopped before any flash on that
port, never an open-ended `esphome logs`. Stop a capture with `pkill -f "[s]erial_capture.py <port> "`,
naming the port so a capture on another board survives; the bracket keeps the pattern from matching
the shell that runs it. When more than one agent session shares the bench, run a copy of the script
under a session-specific name (`cap<topic>.py`) so another session's `pkill` cannot take your capture
down. Never run `esphome logs` in a `while true` loop: the loop respawns it and holds the port.
2. **Credentials stay local.** Put a `secrets.yaml` next to the scratch configs and reference it with
`!secret`. A copy of a device's own config keeps using the secrets file next to that config: two
checkouts can hold different networks, and the wrong one leaves the device unreachable after the OTA. Never paste a config with credentials into a PR, never commit scratch configs or logs.
3. **Build from the tree under test.** `python -m esphome` puts the current directory first on
`sys.path`, so run it with the checkout or worktree as the working directory and pass the yaml by
its absolute path. A venv `esphome` entry point may import an installed snapshot instead of the
checkout. Confirm with `python -c 'import esphome; print(esphome.__file__)'`, and for an A/B grep
the build's `src/` for the change before trusting any number. Tool calls sent together share one
shell, so a `cd` in one can land another's compile in the wrong tree: pass `PYTHONPATH=<worktree>` on
every compile and upload command and print `esphome.__file__` into the compile log.
4. **PR build and baseline build get different `name:` values.** Builds share
`.esphome/build/<name>`, so the same name means `esphome upload` can flash the wrong binary. Saved
Wi-Fi credentials are keyed by the config hash when the config has `networks:`, so a test that must
start unprovisioned gets a fresh name every run. A config with `ap:` only and no `networks:` uses one
fixed key shared by every such config on the board, so a new name does not help there: wipe once at
boot with an `on_boot` lambda that resets the preferences and saves a marker key
(`templates/dut-ap-only-wipe-once-bk72xx.yaml`).
5. **Flash and RAM deltas are measured against the PR's merge base** (`git merge-base origin/dev HEAD`
checked out in a throwaway worktree), with the baseline commit named in the table. When a delta
looks odd, diff symbol sizes (`nm -S -C --size-sort` from the matching toolchain) before quoting it.
6. **Prove a quirk is pre-existing before blaming the PR:** run the identical flow on a `dev` build in
the same session and say so in the results.
7. **Pipe safely.** Set `PYTHONUNBUFFERED=1` on any `esphome run` or `esphome logs` whose output is
piped, or lines land minutes late. Bound every long command with `timeout` (coreutils) or
`perl -e 'alarm shift; exec @ARGV' <seconds> <command>` on macOS without it.
## Workflow
1. **Find the boards:** `python $SKILL/scripts/ports.py` (any Python with pyserial, for example the
ESPHome venv). Port names follow the USB hub port and change on every replug, so never reuse a name
from an earlier session. CH340 adapters (D1 mini, BW15 kit, BK7238, CB3S) all look the same; tell
them apart with a 10 s capture using `serial_capture.py --reset` (ESP8266 prints its ROM banner at
74880 baud, LibreTiny boards print `[LT]` and ESPHome lines, BK7238 prints its bootloader version).
`ports.py --wait 2e8a` blocks until a Pico re-enumerates.
2. **Make a scratch directory** outside the checkout, with `secrets.yaml` and the templates you need.
One yaml per board per variant, file name equal to `name:`.
3. **Compile** in the right tree:
`(cd <worktree> && PYTHONUNBUFFERED=1 timeout 1500 python -m esphome compile <abs yaml> > <name>-compile.log 2>&1)`.
Keep the RAM and flash lines at the end. Two ESP-IDF or two LibreTiny builds at the same time race
over tool caches; run them one at a time.
4. **Flash** per chip family (table below). Stop the capture on that port first.
5. **Capture** with `serial_capture.py PORT SECONDS OUT` in the background, then poll the file with
`grep` in an `until ... sleep 2` loop with a cap. USB CDC boards (Pico, nRF52, native USB ESP32)
drop the port on reboot; the script reopens it until its deadline.
6. **Drive** the test: a second board running a client config, `curl`, an aioesphomeapi script that
presses a template button and subscribes to logs, MQTT publishes, or `interval:` and `on_boot:`
automations in the config itself.
7. **Analyze** with `grep -a -c` counts over the capture and keep an `-events.txt` extract of the lines
that matter. Decode crashes with the platform's `addr2line` (see `reference/boards.md`).
8. **Report** on the PR as a table: build SHA, board and framework, what was driven, counts, result,
plus the `dev` control row and the exact flow in a `<details>` block. No credentials, no port names,
no IP addresses.
## Flashing by chip family
| Board | Flash | Log |
|---|---|---|
| ESP8266 D1 mini (CH340) | `python -m esphome upload <yaml> --device <port>` | `serial_capture.py` at 115200; `esphome logs` on some CH340s emits garbage after the RTS reset. A failed esptool write can wedge the adapter until the board is replugged. `esptool erase_flash` before a test that needs an unprovisioned device: the preference sector survives a reflash |
| ESP32 devkit (CP2102 or CH340) | `esphome upload --device <port>`, ESP-IDF or Arduino | `serial_capture.py`. Default Wi-Fi light sleep delays API driven actions by seconds; judge timing from device logs. `power_save_mode: none` for OTA overlap tests |
| ESP32-S3, C3, C6, H2 native USB | `esphome upload --device /dev/cu.usbmodem*` | `logger: hardware_uart: USB_SERIAL_JTAG`. The USB console can wedge when the app takes over the console (0 bytes, esptool `No serial data received`); replug, or watch through `esphome logs --device <name>.local` |
| Pico W / Pico 2 W | `esphome upload --device <port>` (1200 baud touch); first flash ever via BOOTSEL and `--device /Volumes/RPI-RP2` or `RP2350` | port re-enumerates after upload: `ports.py --wait 2e8a` then capture |
| BW15 kit (RTL8720CF, rtl87xx) | `~/.platformio/penv/.libretiny/bin/python $SKILL/scripts/flash_rtl87xx_kit.py flash write -d <port> <build>/.pioenvs/<name>/*.uf2`. Plain `esphome upload` and plain ltchiptool time out because opening the port asserts DTR/RTS and holds the chip in reset | `serial_capture.py --released`, never `esphome logs` |
| BK7238 / CB3S (bk72xx, RTS on CEN) | `~/.platformio/penv/.libretiny/bin/python $SKILL/scripts/flash_bk72xx.py flash write -d <port> <uf2>` (about 75 s) | logs are on UART2 by default; `logger: hardware_uart: UART1` puts them on the flashing adapter. `serial_capture.py --reset` gives a chip-enable reset; a plain open may or may not reset it, check for the bootloader banner |
| LN882H | ltchiptool forces 1 Mbaud for writes; weak adapters need `LN882H_YM_BAUDRATE` patched to 115200 in the penv's `ln882htool.py`; short BOOT to GND while the tool connects | OTA after the first flash |
| nRF52840 Dongle (PCA10059) | Nordic `nrfutil nrf5sdk-tools dfu usb-serial` on the bootloader port, or `dfu: true` in the config so the 1200 baud touch pin-resets into the bootloader; adafruit-nrfutil is refused | app has two CDC ports, ESPHome log on the second |
| Adafruit-style nRF52 (UF2 bootloader) | `UF2BOOT` volume or adafruit-nrfutil; `dfu: true` works through GPREGRET | single CDC port |
| Any reachable device | `esphome upload <yaml> --device <ip or name.local>`; `--file <bin>` for a prebuilt image | `esphome logs --device <ip>` bounded with `timeout` |
ESP-IDF OTA image: `.esphome/build/<name>/build/firmware.ota.bin`. LibreTiny OTA port is 8892.
## Scripts in `$SKILL/scripts`
- `ports.py`: list ports with VID:PID, product, serial and a board guess; `--wait VID[:PID]`,
`--product NAME`, `--serial SN` (one specific board, whatever path it comes back under) and
`--serial-of PORT` (the serial number behind a path).
- `serial_capture.py PORT SECONDS OUT [--baud] [--released] [--reset] [--append] [--ms]`: bounded timestamped
capture (`--ms` adds milliseconds for timing work), ANSI stripped, `LOG_SECRET` text written as `<secret>`, reopens a vanished port, exits at the
deadline. OUT is truncated unless `--append` is given, so counts over it cover one run.
- `flash_rtl87xx_kit.py`: ltchiptool wrapper for the BW15 kit whose CH340 drives CEN (RTS) and the boot
strap (DTR). `flash read` needs the family `realtek-ambz2`; `flash write` does not.
- `flash_bk72xx.py`: ltchiptool wrapper for BK72xx boards whose RTS drives CEN. It misses the link now
and then; retry up to three times.
- `api_button.py HOST KEY list|press NAME [...]`: list a device's template buttons or press them over the API; the
recipes drive their test configs with it.
- `wifisave_hammer.sh KIND DUT_PORT DUT_YAML DUT_TREE CLIENT_PORT CLIENT_YAML CLIENT_TREE [SETTLE]`:
the fallback AP plus captive portal hammer end to end (compile, flash, capture, counts) with the
templates below. KIND is `esp`, `pico`, `bw15` or `bk`. It ends with `RESULT PASS` (exit 0),
`RESULT FAIL` (exit 3: a crash, a reboot after the AP came up, or the device never joined the network),
`RESULT INVALID`
(exit 2: a precondition was missed, the stimulus was insufficient, or a capture did not last the run)
or exits 1 when a build, flash or port step failed.
Per-topic scripts, each described in the matching `reference/recipes/<component>.md`: `online_image/`
(server, image generator, API driver), `zwave_proxy/` (checksum verifier, device info), `usb_cdc_acm/`
(host driver, Zephyr driver probe), `rtsp_server/` (protocol client, raw rate reader, measurement set,
API image grab), `cse7766/` (API observer with ping), `zephyr_coredump/` (reader flow, decode with the
Zephyr tools), `libretiny_prefs/` (rtc preference harness header), `libretiny_crash/` (BK72xx crash probe header and
capture decoder), `api_outgoing/` (wake cycle and Wi-Fi drop timing readers), `homeassistant_light/` (direct bulb driver), `usb_audio/` (URL player).
## Templates in `$SKILL/templates`
- `dut-fallback-ap.yaml` (package): a device that never finds its network, falls back to its AP and
serves the captive portal. `example-dut.yaml` is the per-run file with the platform block.
- `client-wifisave.yaml` (package): joins that AP, posts N bogus `/wifisave` requests, then the real
credentials from `!secret`. `example-client.yaml` is the per-run file. `bogus_saves: "1"` gives a
plain provisioning check.
- Per-topic device configs, each described in `reference/recipes/<component>.md`: `online-image-dut.yaml`,
`usb-cdc-acm-dongle-dut.yaml`, `nrf52-preferences-dut.yaml`, `rtsp-waveshare-s3-eth-dut.yaml`,
`waveshare-s3-eth-camdiag.yaml`, `s31-cse7766-dut.yaml`, `nrf52-coredump-crash-dut.yaml`,
`bk72xx-rtc-prefs-dut.yaml`, `bk72xx-crash-probe-dut.yaml`, `dut-ap-only-wipe-once-bk72xx.yaml`,
`homeassistant-light-dut.yaml`, `deep-sleep-outgoing-dut.yaml`, `outgoing-wifi-drop-dut.yaml`,
`globals-storage-dut.yaml`, `usb-audio-waveshare-s3-eth-dut.yaml`, `usb-audio-sendspin-s3-eth-dut.yaml`,
`cast-pro-1-sendspin-control.yaml`, `aux-2-preprod-stock-dut.yaml`, `aux-2-preprod-usb-audio-dut.yaml`.
## When something looks wrong
- A capture stopped early or a line is missing: check the timestamps before trusting it.
- The client never joins the AP: another board may still run an AP with the same SSID, or the device
booted with saved credentials because the name was reused. One AP device at a time on the bench.
- Builds come out byte-identical between branches: the wrong tree was imported (rule 3). The reverse
too: two binaries that differ only in the build timestamp and in `__LINE__` values of log calls below
the changed code are the same build; map the offsets with `objdump -h` and `addr2line` first.
- A boot the host cannot capture (a power cut, an OTA-only board): keep the boot report in a flash
preference and repeat it in a periodic alive line, as `scripts/libretiny_prefs/ltprefs.hpp` does.
- Multicast from the host never arrives on some access points: test it device to device. Broadcast
is lossy under Wi-Fi power save, unicast is not. Throttle UDP floods to a fixed packet rate.
- `api:` reboots a board after 15 min without a client: `reboot_timeout: 0s` on `api:` and `mqtt:` for
soak tests.
`reference/boards.md` has per-chip facts and crash decoding; `reference/recipes/` has the proven
flows. Add new findings there.
@@ -0,0 +1,190 @@
# Board facts
Ports are not listed on purpose: they follow the USB hub port and change on every replug. Run
`scripts/ports.py`.
## ESP8266 (D1 mini, NodeMCU, CH340 or CP2102)
- Opening the port usually resets the board through DTR, but not always; `serial_capture.py --reset`
pulses RTS to capture from boot. 74880 baud shows the ROM boot messages (`rst cause`, `boot mode`).
- `esphome logs` on some CH340s emits thousands of duplicated garbage lines right after the RTS reset;
a plain pyserial capture at 115200 is reliable.
- A failed esptool write can wedge the adapter (`termios` error 22, or an open that hangs) until replug.
- The preference sector survives a reflash: a provisioned API key or saved Wi-Fi credentials stay
active. `esptool erase_flash` gives a clean device.
- `Panic core_esp8266_main.cpp:191 __yield` with `ctx: sys` means a yield from lwIP context (a web
handler, an lwIP callback). Decode stacks with
`xtensa-lx106-elf-addr2line -e <build>/.pioenvs/<name>/firmware.elf -f -C -i <addr...>` from the
toolchain under `~/.platformio/packages/toolchain-xtensa/bin/` (PlatformIO builds) or the ESPHome
cache directory (native toolchain builds).
- Constant data lives in RAM unless marked `PROGMEM`; with 80 KB of RAM a few hundred bytes are worth a
row in the PR.
- AsyncMqttClient drops inbound topics over 128 characters; topics of 255 or more wrap a counter and
stall the link until the broker drops TCP.
## ESP32 classic devkit
- Takes ESP-IDF and Arduino. Default Wi-Fi light sleep delays API driven actions by up to 6 s; judge
timings from device logs, not from the host. `power_save_mode: none` for OTA overlap tests (an IDF web
upload of 739 KB took 107 s with power save and 45 s without).
- Receives only a few percent of IPv6 multicast on some access points with power save on; a raw lwIP
socket loses the same, so that is the AP or driver, not a PR.
- Decode with `xtensa-esp32-elf-addr2line` from the IDF tools directory against
`<build>/.pioenvs/<name>/firmware.elf` (PlatformIO) or `<build>/build/<name>.elf` (IDF native).
- Clean reboot between runs with the capture stopped: `esptool --after hard_reset chip_id`.
- Trigger a forced crash only after the app is marked valid (45 s after boot is plenty), never on an
image still pending OTA rollback.
- Signed OTA testing: build with `esp32.framework.advanced.signed_ota_verification.verification_keys`
under a different name, sign with `python -m espsecure sign_data --version 2 --keyfile
tests/components/esp32/dummy_signing_key.pem`, upload with `esphome upload --file`. The verifier
refuses unsigned images afterwards, so restore with a signed plain build.
## ESP32-S3, C3, C6, H2 with native USB
- `/dev/cu.usbmodem*`, esptool works, `logger: hardware_uart: USB_SERIAL_JTAG`.
- The USB console can die at the moment the app takes over the console during a reboot (log stops after
`Disabling RNG early entropy source`, then 0 bytes, esptool reports `No serial data received`). Only a
USB replug fixes it. Fallback: `esphome logs --device <name>.local` (API log stream, which drops lines
while Wi-Fi is off).
- The ESP32-P4 before silicon revision 3 is bricked by rev3-only firmware (boot loop at bootloader entry).
## RP2040 / RP2350 (Pico W, Pico 2 W)
- The CDC port re-enumerates after every reboot and after `esphome upload` (1200 baud touch). Re-detect
with `ports.py --wait 2e8a` before capturing; `serial_capture.py` reopens on its own if the port
vanishes mid-capture.
- First flash ever: BOOTSEL, then `esphome upload --device /Volumes/RPI-RP2` (or `RP2350`).
- arduino-pico creates the lwIP netif in `WiFi.begin()`, so any IGMP join during component setup fails;
joins must wait for `network::is_connected()`.
- Parallel Pico builds race on package extraction after a PlatformIO package wipe; one at a time.
- An RTS pulse from pyserial does not reset the board; reboot with `App.safe_reboot()` or the touch.
## BW15 kit (RTL8720CF, LibreTiny rtl87xx)
- The kit's CH340 wires RTS to CEN and DTR to PA00 (boot strap). A plain pyserial open asserts both and
holds the chip in reset, which is why `esphome upload`, plain `ltchiptool` and `esphome logs` all fail
("Timeout while linking", or silence). `scripts/flash_rtl87xx_kit.py` sets DTR high and RTS released
on every open, pulses RTS once so the ROM answers `ping`, and after the command drops DTR and pulses
RTS to boot the app. Run it with the PlatformIO LibreTiny penv Python.
- `flash write -d <port> <uf2>` takes no family argument; `flash read -d <port> -s 0 -l <len>
realtek-ambz2 <out>` does.
- Capture with `serial_capture.py --released`. The ESPHome log is on the flashing UART already.
- The macOS CH340 driver only pushes DTR/RTS to the wire once a session has driven them, so the first
plain open after a replug streams logs and the second is silent; always open with the lines released.
- Driver quirks seen on `dev`: `WiFi.begin` can refuse the real network on the first try after bogus
credentials (it connects after two cooldowns); right after connecting while the AP is still up the
driver can bus-fault in `rtw_is_adapter_up` (`R0 = 0xdeadbeef`) and reboot; the AP restarts on every
STA retry, so stations cannot stay joined; `WiFi.begin` fails synchronously for an absent network and
lands in a cooldown at once; connecting blocks the loop 2.5 to 4.1 s.
- Decode faults with `arm-none-eabi-addr2line -e <build>/.pioenvs/<name>/raw_firmware.elf -f -C -i <PC>`
from `~/.platformio/packages/toolchain-gccarmnoneeabi/bin/` (`raw_firmware.elf`, not `firmware.elf`).
Fault dumps print PC, LR and R0 to R3.
- The AmebaZ2 SDK sets `LWIP_TCPIP_CORE_LOCKING 0`.
## BK7238, WB3S, CB3S (BK7231N) (LibreTiny bk72xx, CH340, RTS on CEN)
- Stock ltchiptool closes and reopens the port after its reset, and pyserial raises both lines on open,
holding CEN low through the BootROM window. `scripts/flash_bk72xx.py` opens with both released and
pulses the reset right before the link checks. A write takes about 75 s. Only a CEN or power reset
reaches the downloader; no software reboot does.
- ESPHome logs on UART2 by default; `logger: hardware_uart: UART1` puts them on the flashing adapter.
- Opening the port may reset the chip (bootloader banner on open) or may not, depending on the adapter and
what pyserial does with DTR and RTS; `serial_capture.py --reset` pulses RTS on CEN and always does, and
`lt_get_reboot_reason()` reports that as Power-On. Check for the banner at the start of a capture before
assuming either.
- `flash_bk72xx.py` misses the link now and then (`Timed out attempting to link with chip`); retry up to 3
times before suspecting the board.
- Crashes: `__builtin_trap()` and a call into unmapped memory both end as an undefined-instruction trap
(`bk_misc_get_start_type()` 6, `lt_get_reboot_reason()` Crash); reads, unaligned loads and stores to
unmapped addresses never fault on the ARM968. Without Wi-Fi the SDK trap handler spins with interrupts off
for 10 s and the hardware watchdog resets the chip. With Wi-Fi connected a dev build hangs forever after a
crash, and never resets from a plain `portDISABLE_INTERRUPTS(); while (1) {}` either: the boot-time
watchdog is dead once Wi-Fi is up, for any lockup. The crash handler fix (#21012) re-arms the watchdog
from its wrapper, after which the reboot comes within a second (BK7238 and BK7231N).
- The SDK prints nothing on UART1 under `sdk_silent: all`; the bootloader then prints the banked
`cpsr/spsr/r13/r14` of SVC, IRQ, FIQ and SYS plus `ST:` (the retention register) before `go os_addr`.
A CEN or power reset wipes ITCM (reads `0xaaaaaaaa`), so only a watchdog reset keeps a record there. What
survives that reboot on the BK7238: ITCM (`0x3F0000..0x3F4000`) and the top 96 KB of SRAM
(`0x428000..0x440000`, the heap); the bootloader zeroes SRAM up to about `0x420000` and rewrites
`0x400024..0x4000FF` with its own register dump every boot, so neither a `.noinit` after `.bss` nor the
SDK's saved register banks are usable. Code is compiled Thumb, the SDK assembly is ARM; in an exception
handler only r0 to r3 are shared by all modes. BK7231N ITCM is `0x3FEE00..0x400000` (4.5 KB); a `.noinit`
placed there after `.itcm.code` works for a crash record.
- Callbacks run on the `async_tcp` task and log lines can lose a byte. Wi-Fi connect takes tens of ms;
radio enable 160 to 278 ms, disable about 200 ms.
## BW16 kit (RTL8720DN, AmebaD)
- The kit's own USB-C (CH340) is on LOG_UART and flashes fine. Its two-transistor circuit resets while
RTS alone is asserted and straps BURN while DTR alone is; both asserted does nothing. ROM entry is
esptool's classic sequence (`dtr:false,rts:true` 100 ms, `dtr:true,rts:false` 400 ms, both false); the
first pulse after a replug can be lost, retry up to three times.
- Any reopen resets the chip, 460800 is flaky, 115200 is solid: one open per session, in-place baud
changes only. The ROM and the loader NAK continuously while idle, so ACK scans must drain first.
- The AmebaD core has no mDNS responder (`mdns: disabled: true`).
## LN882H
- ltchiptool forces 1 Mbaud for writes; adapters that cannot hold it fail with "Timeout while linking"
or die mid-write. Patch `LN882H_YM_BAUDRATE` in the penv's `ltchiptool/soc/ln882h/util/ln882htool.py`
to 115200 (lost on reprovision). Weak 3.3 V from the adapter: feed the module's 5 V pad instead. Short
BOOT to GND while the tool connects. OTA afterwards.
- `USE_WIFI_AP_EXCLUSIVE` (one radio mode at a time) exists only on LN882x; compile-check changes there.
## nRF52840
- Nordic PCA10059 dongle: the factory Open DFU bootloader enumerates as `1915:521F` on its own port
name; the app as `2FE3:0100` with two CDC ports, ESPHome log on the second. Only Nordic
`nrfutil nrf5sdk-tools` works (`pkg generate --hw-version 52 --sd-req 0x00 --application-version 1
--application zephyr.hex app.zip`, then `dfu usb-serial -pkg app.zip -p <port>`); ESPHome downloads
it on the first build. GPREGRET does not enter the bootloader; a pin reset through P0.19 does, which
`dfu: true` wires up on this board. A pulsing red LED means bootloader; the side RESET button
re-enters it.
- Makerdiary MDK USB dongle: UF2 bootloader without SoftDevice, app at 0x1000, `UF2BOOT` volume,
adafruit-nrfutil, GPREGRET 0x57 works. RGB LED active low (red P0.23, green P0.22, blue P0.24),
button P0.18.
- Adafruit ItsyBitsy nRF52840 Express (UF2 bootloader with S140, bootloader port `239a:0051` plus an
`ITSY840BOOT` volume): the board DTS already has `cdc_acm_uart0`, so the logger port is the first CDC
port and a `usb_cdc_acm` interface 1 the second. A `dfu: true` build enters the bootloader on the
1200 bps touch (an app without it needs a double tap on RESET); `adafruit-nrfutil` over the bootloader
serial port can fail, copying `.pioenvs/<name>/zephyr/zephyr.uf2` to the volume works (the copy reports
an I/O error as the board reboots, which is normal). Reboot a running `dfu: true` app with a 2001 bps touch.
- The PCA10059 board DTS already defines a `cdc_acm_uart` node, so every ESPHome CDC port is one USB port
later than its number (log on `cdc_acm_uart0` is the second port). A third CDC instance needs
`CONFIG_USB_MAX_NUM_TRANSFERS` above 4 or it never moves data (see `recipes/usb_cdc_acm.md`).
- USB CDC boot output: the Zephyr legacy stack's `cdc_acm` TX ring is 1 KB and `uart_poll_out` drops when it
is full, so anything printed in one burst beyond about 1 KB (a coredump, a long table) is lost even with
the port open, and the logger warns `logger took a long time`. Print one line per `loop()` instead.
- Zephyr settings survive a reflash: key a `restore_value` global by a `run` substitution when a test needs
a never-saved value.
## Waveshare ESP32-S3-ETH with the OV5640 camera module
- Native USB console (`303a:1001`), ESP32-S3R8, 16 MB flash, 8 MB octal PSRAM, W5500 on SPI (MOSI 11,
MISO 12, SCLK 13, CS 14, INT 10, RST 9), TF card on 4..7, WS2812 on 21.
- Camera header (vendor schematic): XCLK 3, SIOD 48, SIOC 47, VSYNC 1, HREF 2, PCLK 39, D0..D7 = 41, 45,
46, 42, 40, 38, 15, 18. PWDN is tied to GND and RESET is an RC to 3.3 V on the board, so no reset or
power-down GPIO exists.
- GPIO8 switches the camera 3.3 V rail through a P-channel MOSFET: LOW = camera powered, gate pulled up so
the camera is off by default. `power_down_pin: GPIO8` only pulls it low 10 ms before the probe, which is
not enough; use `switch: gpio GPIO8 restore_mode: ALWAYS_OFF setup_priority: 800` plus `on_boot:
priority: 700` with `delay(1200)` and `esp32_camera: setup_priority: 200`
(`templates/rtsp-waveshare-s3-eth-dut.yaml`). `esphome config` rejects a config that uses GPIO8 for both the switch
and `power_down_pin` (pin reuse), so it is one or the other.
- The sensor at SCCB 0x3C answers only with XCLK running, so the ESPHome i2c scan at boot (before XCLK)
reporting no devices on 48/47 is expected. If the SCCB ID register (0x3100 = 0x78) reads fine but every
0x30xx register reads 0x00 (`Detected camera not supported`, 0x106), reseat the ribbon before blaming
software; `templates/waveshare-s3-eth-camdiag.yaml` runs that probe with LEDC XCLK and raw SCCB reads.
## ESP Thread Border Router board v1.2 (S3 host + H2 RCP)
- S3 host on USB JTAG serial, H2 RCP on its own USB. Spinel UART1 from the host: `rx_pin: GPIO17`,
`tx_pin: GPIO18`, RCP reset `GPIO7` active low, 460800. The config needs `network: enable_ipv6: true`.
- The shipped H2 firmware fails `CheckRadioCapabilities` with IDF 5.5; build `examples/openthread/ot_rcp`
for esp32h2 with ESPHome's own IDF (`IDF_PATH` at the cached framework, its penv and tools,
`IDF_PYTHON_CHECK_CONSTRAINTS=no`) and flash with esptool at 0x0, 0x8000 and 0x10000.
- A crash-looping S3 pulses the H2 reset every boot, so the H2 cannot be flashed and the S3 will not
enter its bootloader over USB. Hold S3 BOOT, tap S3 RST, flash the H2, release, then replug both.
- `dns-sd -L <name> _meshcop._udp local.` shows the `omr` prefix; it disappears when routing is dead.
Keep one capture per chip; never reset the H2 over USB mid-test unless that is the test (the host
logs `Unexpected RCP reset: RESET_OTHER` and recovers).
@@ -0,0 +1,29 @@
# Proven test flows
Each flow names the pull requests it was used on, so their result tables show what to post.
One file per component, named after it:
- [`api.md`](api.md): noise handshake and OTA encryption on the ESP8266, outgoing connection wake timing on deep sleep and after a Wi-Fi drop
- [`captive_portal.md`](captive_portal.md): fallback AP and /wifisave hammer
- [`cse7766.md`](cse7766.md): Sonoff S31 parser check over the API
- [`espnow.md`](espnow.md): ESP8266 broadcast receive
- [`hdmi_cec.md`](hdmi_cec.md): two-board wire
- [`ld2450.md`](ld2450.md): polygon zones
- [`libretiny.md`](libretiny.md): rtc preference emulation, BK72xx crash report on the next boot
- [`mqtt.md`](mqtt.md): broker bench, QoS, reconnect, crash log over MQTT
- [`nrf52.md`](nrf52.md): dongle board support
- [`online_image.md`](online_image.md): stalled and malformed downloads
- [`openthread.md`](openthread.md): border router on the S3 host plus H2 RCP
- [`ota.md`](ota.md): overlap and lock, timeouts, deflate
- [`rtsp_server.md`](rtsp_server.md): camera streaming on the Waveshare ESP32-S3-ETH
- [`scheduler.md`](scheduler.md): loop wake A/B
- [`udp.md`](udp.md): datagrams, multicast, sockets
- [`usb_cdc_acm.md`](usb_cdc_acm.md): nRF52840 dongle host driver
- [`wifi.md`](wifi.md): reboot_timeout with a fallback AP, DHCP lease cache, Easy Connect (DPP)
- [`zephyr.md`](zephyr.md): preferences on the nRF52840 dongle
- [`zephyr_coredump.md`](zephyr_coredump.md): crash and decode on nRF52 boards
- [`zwave_proxy.md`](zwave_proxy.md): frame checksums on a live proxy
- [`globals.md`](globals.md): restoring globals with storage rtc and flash across every reset kind
- [`homeassistant.md`](homeassistant.md): light platform mirrored from a local Home Assistant
- [`usb_audio.md`](usb_audio.md): USB audio speaker and microphone, the usb_host FIFO split, and Music Assistant playback through sendspin
@@ -0,0 +1,30 @@
# api
## Noise handshake and OTA encryption on the ESP8266 (#19062, #19233)
Host side timing is useless on a weak link; use an on-device bench with the minimum of five runs over
two boots. Provision a key with `noise_encryption_set_key` through aioesphomeapi and upload with
`espota2.run_ota(host, 8266, None, bin, noise_psk=key)` when the key is not in the yaml.
## Outgoing connection wake timing on a deep sleep build (#21077)
`templates/deep-sleep-outgoing-dut.yaml`: classic ESP32 ESP-IDF, `wifi: fast_connect`, `api: encryption` with a bare
`outgoing_connection:`, `deep_sleep` 15 s run and 15 s sleep, a `homeassistant` text sensor, and `millis()` logged
from `wifi: on_connect`, `api: on_client_connected` and the sensor's first `on_value`. Home Assistant's dial-in
listener is on 6054; reuse a device name it already has. Flash by USB, `serial_capture.py PORT 360 OUT --reset` for
12 cycles, then `scripts/api_outgoing/cycles.py OUT LABEL` prints per cycle the wifi, client and state millis with
the client-wifi and state-client gaps. Wi-Fi takes 4 to 8 s after a wake, so compare the client-wifi gap, and run
the PR twice around one base run because the spread is a few hundred ms.
Results: https://github.com/esphome/esphome/pull/21077#issuecomment-6254098014
## Outgoing connection on a mains build after a Wi-Fi drop (#21085)
`templates/outgoing-wifi-drop-dut.yaml`: classic ESP32, `outgoing_connection: delay: 2s`, a "Wifi cycle" button doing
`wifi.disable`, 4 s, `wifi.enable`, a `debug` loop-time sensor, millis from the wifi and api triggers. Flash by USB,
`serial_capture.py PORT SECONDS OUT --ms`, press the button N times 40 s apart with `api_button.py DEV_IP KEY press
"Wifi cycle"`, then `wificycles.py CAPTURE LABEL` prints per drop the Wi-Fi-to-dial gap, dials and dials closed, who
connected first, and the loop-time maxima. For the loop cost of polling, repeat with an unreachable
`host: 192.0.2.1`. Home Assistant refuses a dial-in while its own session is READY (`Refusing dial-in adoption`),
so after a drop every dial is closed and HA's incoming reconnect wins 11 to 33 s later; the measurable effect is the
first dial landing in the loop the network flag flips.
Results: https://github.com/esphome/esphome/pull/21085#issuecomment-6254742791
@@ -0,0 +1,21 @@
# captive_portal
## Captive portal `/wifisave` hammer (#20802, #20784)
Reproduces a `/wifisave` arriving during the STA cooldown, and proves a fix on every board.
Device under test: `templates/example-dut.yaml` (bogus network, `ap:` with `ap_timeout: 15s`,
`captive_portal:`). Client: `templates/example-client.yaml` on an ESP32 or a D1 mini; it joins the
device's AP, posts 15 bogus saves 3 s apart, then the real credentials. With the portal up the cooldown
is 30 s, so nearly every save lands inside one. Driver: `scripts/wifisave_hammer.sh`. Settle 40 s on
ESP boards, 90 s on the BW15 (its first attempt on the real network fails, see boards.md).
Counts that matter: `Requested WiFi Settings Change` (saves received), `Exiting cooldown early`, crash
markers, boots (`Log initialized`), and the client's `bogus save status 200` and `real save status 200`.
A healthy run: every save answered 200, one cooldown exit per save (two on the BW15, whose retry enters
the adapter restart), no panic, and the real credentials connect the device within about 10 s.
Fault injection for AP start failures: wrap `this->ap_setup_ = this->wifi_start_ap_(this->ap_)` in
`setup_ap_config_()` with an `HWTEST_AP_FAIL_COUNT` build flag
(`esphome: platformio_options: build_flags: -DHWTEST_AP_FAIL_COUNT=2`), raise the portal's "Captive
portal started" from LOGV to LOGI, and after provisioning curl the device's STA address: connection
refused proves the portal's server is gone.
@@ -0,0 +1,11 @@
# cse7766
## cse7766 on a Sonoff S31 (#20948)
`templates/s31-cse7766-dut.yaml`: `logger: baud_rate: 0` because the CSE7766 owns the UART, VERBOSE logger
with everything but cse7766 quieted, 2 s throttles, and a `globals` frame counter fed by the voltage sensor's
`on_raw_value` and logged every 10 s (about 96 frames per 10 s). OTA only, every reflash reboots the relay.
`scripts/cse7766/run_obs.sh IP SECONDS LABEL [OUTDIR] [NAME_FILTER]` subscribes to states and logs over the
API with a concurrent ping and prints the frame counter, API gaps and warnings. API state gaps of 10 to 20 s
can appear on a weak Wi-Fi path while the device counter stays flat, so judge a UART parser by the
device-side counter, not by the API cadence. With nothing plugged in, current and power read 0.
@@ -0,0 +1,7 @@
# espnow
## ESP-NOW on the ESP8266 (#14407)
The NONOS SDK only delivers broadcast ESP-NOW frames while the station is associated or a soft-AP is
up; a hidden, open, client-less soft-AP (60 s beacon, DHCP stopped) makes broadcasts arrive. In AP-only
mode the device transmits from the soft-AP MAC.
@@ -0,0 +1,15 @@
# globals
## Restoring globals with storage: rtc and flash (#13954)
`templates/globals-storage-dut.yaml` is the shared package: five restoring globals (rtc int, flash int, default int,
rtc `std::string`, rtc `uint8_t[2]`), bumped and logged with the reset reason on every boot, saved and synced after
2 s, odd boots `App.safe_reboot()`. Platform files add the board (`packages: dut: !include globals-storage-dut.yaml`), the `debug` reset-reason text sensor and, on the
ESP32, `deep_sleep: run_duration: 12s, sleep_duration: 8s` (even boots sleep). No network needed: flash by USB,
`serial_capture.py PORT 110 OUT --reset` gives the reset pulse plus a run of soft restarts and sleeps; the ESP32's
EN pulse is a `POWERON_RESET` that empties RTC memory, the D1 mini's RST pulse (`rst cause:2`) keeps it, so the
ESP8266 needs a real power cut by hand; arm long `--released` captures on both ports first (the capture
reopens the port when it comes back, the very first boot lines are still lost, read the next boot's restored
values). Unchanged-build check: the config with every `storage:` removed, built on the PR and on the dev merge
commit under the PR's own commits (not the merge base, which is older dev), `cmp -l` plus an `nm -S` symbol diff;
only the template names differ. Results: https://github.com/esphome/esphome/pull/13954#issuecomment-6254794712
@@ -0,0 +1,9 @@
# hdmi_cec
## HDMI-CEC two-board wire (#9189)
Two boards on one wire: promiscuous mode, a 0x8F request answered with 0x90 0x00, a `destination:`
filter on the reply handler, template buttons for each target plus lambdas calling
`send(source, dest, span)` as another initiator. Healthy numbers: device-side turnaround 15 to 22 ms,
round trip 144 to 156 ms. Press buttons over the API; judge timings from device logs (ESP32 light sleep
lags the press).
@@ -0,0 +1,35 @@
# homeassistant
## homeassistant light platform against a local Home Assistant (#11354)
A local Home Assistant dev install (`hass -c config`, https://127.0.0.1:8123 with a self-signed cert); its ESPHome
entries and entity ids are in `config/.storage/`. Two ESPHome RGBCW bulbs with a plaintext API are the lights, so an
"HA side" change is made directly on a bulb with `scripts/homeassistant_light/bulb.py BULB_IP - on|off|state|restart`.
Add the test devices to HA by hand with the shared `api_key` and "Allow the device to perform Home Assistant actions".
`templates/homeassistant-light-dut.yaml` is a package: two mirrors, buttons for each change, a "Log state" button, a
30 s alive line, an `on_boot` `light.turn_on` for the change-before-first-write path, and VERBOSE logging with the
noisy tags at DEBUG (a tag cannot exceed the global level). A platform file sets the board and includes it:
```yaml
esphome:
name: ha-light-dut
esp32:
board: esp32dev
framework:
type: esp-idf
packages:
dut: !include homeassistant-light-dut.yaml
```
Flash by USB, start a bounded `esphome logs` capture per device, then press buttons with
`scripts/api_button.py DEV_IP KEY press "Button"` and read the bulb with `bulb.py`. Cases: each
button against the bulb and the device log; a direct bulb change that both mirrors follow; restart the bulb, send a
change, expect the 5 s `did not report the change back` and the fallback (with an off and with an on last report);
OTA reboot with a serial capture running to see the boot-time change (the API log client reconnects too late).
Lessons: the light core clamps mireds to the range HA reported; a flash sends nothing; a change sent while HA is
connected but not yet subscribed is dropped by the api server (`client has not subscribed to actions (yet)`); after a
fallback to an off report the mirror keeps the unsent brightness; a mirror that never saw its light on since boot has
no levels to restore. Results: https://github.com/esphome/esphome/pull/11354#issuecomment-6253630336 and
https://github.com/esphome/esphome/pull/11354#issuecomment-6254161409
@@ -0,0 +1,7 @@
# ld2450
## LD2450 polygon zones (#20154)
To make a target "leave" without anyone moving, set the radar's Zone-1 numbers over the target and
switch the zone type to `Filter` (the count drops to 0 within 0.5 s); set it back to `Disabled` after.
Writing those numbers blocks the ld2450 loop about 50 ms.
@@ -0,0 +1,58 @@
# libretiny
## LibreTiny rtc preference emulation (#21007)
The PR keeps `storage: rtc` preferences in FlashDB and deletes them on the first load or save after a boot
whose `lt_get_reboot_reason()` is not software, watchdog, crash or sleep. Only the families listed in
`_libretiny_rtc_supported()` accept the config keys; on others force the backend in with
`platformio_options: build_flags: ["-DUSE_LIBRETINY_RTC_PREFERENCES"]` after confirming the rejection.
- Harness: `scripts/libretiny_prefs/ltprefs.hpp` plus `templates/bk72xx-rtc-prefs-dut.yaml`. `ltprefs_boot()`
loads one rtc and one flash preference, a second pair that shares one key as rtc and as flash, and a third
rtc entry (so a hard reset drops three entries in one pass), logs the reason and both results, saves `100 + step`, syncs; after
a templated delay `ltprefs_exit()` bumps the flash-backed step and runs the step's action (`lt_reboot()`,
`__builtin_trap()`, `lt_reboot_wdt()` or idle). The last 8 boot reports are kept in a flash preference and
repeated in the 10 s alive line, so a boot missed by the capture (a power cut, an OTA-only board) is still
readable. Bump `run:` for fresh keys.
- A store to an unmapped address does not fault on the ARM968, the board carries on: use `__builtin_trap()`
for the crash row. The SDK crash handler reboots about 10 s later. Works on BK7238 and BK7231N.
- Hard resets: `serial_capture.py ... --reset` pulses RTS on CEN (reported as Power-On). A real power cut
needs a hand or a smart plug; the history in the alive line covers the boot the host cannot see. On the
Beken SDK a chip-enable reset and a power cut both report Power-On, there is no separate reset-pin source.
- Step 0 and step 8 wait 30 s before acting so the host can open the port (and pulse a reset) after a flash
without losing the first boot; the flash tool's own reset starts the sequence immediately otherwise.
- OTA-only board: keep the device's wifi, api and ota blocks verbatim, keep safe_mode, read logs with
`esphome logs <yaml> --device <name>.local` (the API attaches late, hence the history), and restore the
device's own config afterwards.
- Plain-build comparison: `raw_firmware.bin` differs by the timestamp and by `__LINE__` in log calls of
functions below the added code; map offsets through `objdump -h` load addresses and `addr2line` before
calling a byte difference a code change.
## BK72xx crash report on the next boot (#21012)
Harness: `scripts/libretiny_crash/bkprobe.hpp` plus `templates/bk72xx-crash-probe-dut.yaml`. A flash
preference holds the step; `on_boot` (priority -100) logs the reset reason, the SDK start type, two canaries
and hex dumps of the low SRAM, a set of marker words and the record area after `.bss`, waits 30 s on step 0
(so a capture can be opened after the flash) or 5 s, saves `step + 1`, syncs, then acts: 0 `__builtin_trap()`,
1 call into unmapped memory, 2 unmapped and unaligned reads and a store (no fault on the ARM968), 3
`lt_reboot()`. A `globals: restore_value` step counter does not work here: it is only saved from the global's
loop, which a crash 50 ms later never reaches; save an `ESPPreferenceObject` and `global_preferences->sync()`
first. Bump `run:` for fresh keys. Keep the `wifi:` block: the bug the fix addresses only shows with Wi-Fi up.
Flow: `flash_bk72xx.py` (retry up to three times), one `serial_capture.py ... --reset` capture of 150 to 230 s,
then `grep -a "libretiny.crash\|probe"`. Decode with
`arm-none-eabi-addr2line -pfiaC -e .pioenvs/<name>/raw_firmware.elf <addr...>` (the ELF is `raw_firmware.elf`
on LibreTiny), or run `scripts/libretiny_crash/decode_capture.py <yaml> <capture>` from the config directory
to feed the capture through the CLI's `LogLineProcessor`.
Expected: `*** CRASH DETECTED ON PREVIOUS BOOT ***`, `Reason: Undefined instruction`, PC on the `udf`,
LR = PC + 1, BT0..BT7 walking the lambda, `StatelessLambdaAction`, `Action::play_complex`, `DelayAction`,
`Scheduler::call`, `Application::loop`. The wild call reports where the core ended up (it executes zeros as
Thumb NOPs until it wraps into real code), not the called address. Without the fix a dev build with Wi-Fi
connected never comes back from the crash; see the BK72xx section in `boards.md`.
OTA-only board: copy the device's own config, add the probe include and the step machine (step 0 crashes
45 s after boot, later steps idle), keep its wifi, api, ota and safe_mode blocks and use the secrets file
next to that config. A LibreTiny OTA is a 1.5 MB UF2 and takes minutes on a slow link, so do not wrap the
upload in a short timeout. Read the replayed report with `esphome logs ... --device <name>.local` (an API
client gets it on subscribe) or on the board's serial log port if it has one.
@@ -0,0 +1,12 @@
# mqtt
## MQTT (#20433, #17935, #20762, #20767, #20768)
A local mosquitto with `allow_anonymous true`, `log_type all` and `log_dest file`: the broker log gives
each subscription's QoS, the device address (`New client connected from <ip> as <name>-<mac>`) and exact
subscribe counts. Kill and restart mosquitto to exercise `on_disconnect` (reason 0) and resubscription.
Keep `reboot_timeout: 0s` on `api:` and `mqtt:`. The ESP-IDF outbox swings free heap about 2 kB right
after bulk subscribe and unsubscribe; read the heap seconds later over several rounds before calling a
leak. Spawned subscriptions need about 2.5 s to reach the broker before the next publish.
`esphome logs --device MQTT` only uses the MQTT transport when the config has no `api:`. Driver pattern: a
paho script publishes and polls the serial capture file.
@@ -0,0 +1,6 @@
# nrf52
## nRF52840 dongle boards (#19009, #20814)
Test config: the two LEDs as inverted GPIO outputs, the button as an inverted pull-up binary sensor, a
10 s alive interval. To retest the nrfutil download path, move the cached `sdk-nrf/nrfutil` aside first.
@@ -0,0 +1,20 @@
# online_image
## online_image stalled downloads (#20594)
One classic ESP32, both frameworks (the ESP32 Arduino build also uses `http_request_idf.cpp`). The host
serves the images: `scripts/online_image/make_images.py <www>` writes 16x16 BMP/PNG/JPEG/QOI plus an 8 bit
palette BMP with a 1078 byte header; `scripts/online_image/oi_server.py <www> 8765 <log>` answers one
misbehaviour per path name (`ok`, `truncated`, `large_header`, `empty`, `stall` holds the socket 40 s,
`closed_early`, `slow` pauses 2.5 s, `chunked`, `chunked_cut`, `noclen`, `huge` claims 20 MB) with an ETag
and a 304 on If-None-Match. `templates/online-image-dut.yaml` has two AUTO-format images (default and
256 byte buffer), API actions `fetch`/`fetch_small` (set_url with `update: false`, then component.update)
and `refetch`/`refetch_small` (component.update only, keeps the cache headers), and a restart button.
`scripts/online_image/drive.py <device ip> <api key> <capture> http://<host ip>:8765 pr|base` runs the
case list over aioesphomeapi and judges each from the serial capture. ESP-IDF facts: a stalled server
ends with `Error downloading image: -28679` (EAGAIN) after the transport timeout, an early close with
`-1`, both before and after the PR; a body without Content-Length and without chunking reads as complete
(Size: 0) and finishes. A build without the fix hangs on the truncated, oversized header, empty, chunked PNG
and cut chunked cases and every later update logs `Image already being updated.`, so a baseline run needs
a reboot per group, the driver's 45 s cap per case, and an outer `timeout` that covers the whole list.
A case expected as `any` passes on `finished` or `error`, never on no outcome.
@@ -0,0 +1,5 @@
# openthread
## Thread border router (#18104)
See boards.md for the ESP Thread BR board. Check that the H2 answers Spinel before blaming a PR.
@@ -0,0 +1,8 @@
# ota
## OTA overlap and lock (#20588), OTA timeouts (#19041), deflate (#19037)
All overlap orders (a web upload during a native OTA and the reverse) must be refused; the lock release
after an interrupted web upload waits about 14 s (ESP8266) or 7 s (ESP-IDF) for disconnect detection. A
refused web client on `web_server_idf` never gets the busy reply; the connection drops at reboot.
`pkill -f "a\|b"` does not alternate; use two pkills.
@@ -0,0 +1,18 @@
# rtsp_server
## RTSP camera streaming on the Waveshare ESP32-S3-ETH (#17990)
`templates/rtsp-waveshare-s3-eth-dut.yaml` (OV5640 pins, GPIO8 camera power switch with the 1.2 s boot delay,
`rtsp_server`, logger VERBOSE for `rtsp_server`); swap the `wifi:` block for the W5500 `ethernet:` block when
the cable is in, and add `network: enable_high_performance: true` for the big TCP buffers. Flash over native
USB, capture the log with `serial_capture.py`, take the IP from the last `IP Address` line that is not
0.0.0.0 (or `dns-sd -Q <name>.local`). `scripts/rtsp_server/rtsp_client.py IP [cases]` covers play, udp,
pipeline, badreq, replay, getparam, teardown, third, stall, timeout; `rawrate2.py IP quiet|chatty|rtcp|bigbuf`
measures the raw receive rate; `measure.sh IP LABEL LOGFILE [OUTDIR]` runs the fixed set (ping, ffmpeg 12 s
twice, two ffmpeg clients, raw reader) and appends to `results.txt`; `cam_api.py IP` pulls one image over the
API; `ffmpeg -rtsp_transport tcp -i rtsp://IP:554/ -frames:v 30 -f image2 f%03d.jpg` plus Pillow checks
decoding. The 5760 byte default TCP send buffer makes the stream stall over Wi-Fi (about 1 fps, dozens of
stall drops per run); `enable_high_performance` gives several fps on Wi-Fi and removes the stalls on Ethernet.
Frame counts vary a lot between runs (auto exposure changes the JPEG size), so always measure twice. Home
Assistant's stream pipeline does not accept MJPEG over RTSP; its ffmpeg camera opens one RTSP session per
snapshot, so `max_connections` needs headroom, and go2rtc transcoding to H.264 is the way to a live view.
@@ -0,0 +1,8 @@
# scheduler
## Scheduler and loop wake A/B (#20356)
An external component whose template button arms an SDK `os_timer` (50 ms, one shot) on the ESP8266;
the callback runs in SYS context, records `millis()` and calls `set_timeout` or `defer`; the timeout
logs the elapsed time and loop count delta. Drive with an aioesphomeapi script (`subscribe_logs` plus
`button_command`). Build the same yaml twice with different names, one per tree.
@@ -0,0 +1,11 @@
# udp
## UDP, multicast, sockets (#20556 to #20559, #20585, #20590)
A listener on port 9 needs no privileges on macOS. Test multicast device to device; host to device
multicast never arrives on some access points, and `239.255.x.x` groups are unreliable even to a stock
listener. Broadcast to boards is lossy under power save, unicast is not. Flood at a fixed packet rate
(the host hits `ENOBUFS` unthrottled); 1500 packets per second survived on every board. A forced Wi-Fi
reconnect on the Pico keeps IGMP membership. Set a long loop interval from `wifi.on_connect`
(`App.set_loop_interval(20000)`), not `on_boot`, or Wi-Fi connect crawls. The `logger.log` action
rejects `%.*s`; use a lambda.
@@ -0,0 +1,52 @@
# usb_audio
## USB audio on the Waveshare ESP32-S3-ETH (#21081, #11728)
`templates/usb-audio-waveshare-s3-eth-dut.yaml`: ESP-IDF, PoE, logs over the API on Ethernet (the native USB port is
the host port, so there is no console), `usb_audio` hub, a 48 kHz 16-bit microphone, a 48 kHz 24-bit speaker (many
speakerphones offer only 24-bit; a wrong format logs the device's alternates), buttons for capture start and stop,
a non-blocking beep fed from an 8 ms interval with `play(buf, len, 0)` (a blocking `play()` on the main loop trips
the task watchdog), and a status interval with mic bytes, speaker state and heap. Drive the buttons with
`scripts/api_button.py IP KEY press "Beep"`. Expect 192 kB/s microphone bytes and the FIFO split
RX 80 / NPTX 16 / PTX 104 in the build's `defines.h`; with the balanced split the 294-byte OUT pipe is refused
(`EP MPS (294) exceeds supported limit (128)`). A device whose OUT endpoint is larger than 600 bytes cannot be
claimed on an S3 at all.
## Music Assistant through sendspin
`templates/usb-audio-sendspin-s3-eth-dut.yaml` overlays the template above with the Apollo CAST-1 wiring: resampler
speakers into a mixer whose inputs never time out, `speaker_source` media player with the sendspin source, and
`sendspin: unpaired_access: true`. A `media_player: platform: speaker` is not a media-source consumer and gives
"Player over its buffer", no audio. Music Assistant finds the device by mDNS within 15 s of boot.
Read the result from the device, not only by ear: sendspin's sync task logs `Lost sync (N us off)` (DEBUG, tag
`SsSync`) for every correction over 5 ms, a positive N inserts that much silence, and each one is an audible click.
For the chunk lead histogram, edit the managed library in the build directory,
`.esphome/build/<name>/managed_components/sendspin__sendspin-cpp/src/sync_task.cpp` (an `SS_LOGD` before the
late-chunk skip in `decode_chunk()` printing the chunk's lead against `new_audio_client_playtime`), update only that
file's `size` and sha256 `hash` in the component's `CHECKSUMS.json`, and compile again; deleting `.component_hash` or
recomputing it makes the manager refuse or refetch. Check the edit is in the image with `strings firmware.ota.bin`.
A lead below -5 ms makes the library skip the whole chunk; an I2S speaker keeps it within 0.1 ms of zero, which is
the figure a USB speaker has to match.
Gotchas: wait two minutes after a reboot before the measured stream (safe mode's boot counter write stalls the cores
for about 140 ms once, 60 to 75 s after boot); with the serial console on, a DEBUG log line per report saturates
UART0 and trips the task watchdog, so use `logger: baud_rate: 0` and the API log; two overlapping `esphome logs`
captures double every count; and every audio test must include a pause and resume cycle, since a stream end that
leaves less than one transfer parked in the driver was what hung the speaker after #11728 merged.
## Controls
Apollo CAST PRO-1 (`templates/cast-pro-1-sendspin-control.yaml`): `Core.yaml` next to it is
`Integrations/ESPHome/Core.yaml` from ApolloAutomation/CAST_PRO-1, which also defines the `version` substitution.
Keep its framework log level at ERROR (DEBUG floods driver output and trips the watchdog); the package reboots itself
into Ethernet-only once it sees a link and appends the MAC suffix to the name. `wifi: enable_on_boot: false` keeps
sendspin off Wi-Fi.
Nabu Casa Connect AUX-2 (a pre-production unit): build from `packages/aux-2-pre-prod.yaml` at NabuCasa/aux-2 commit
283f2e64 (dropped in bde41e01), not the public `aux-2.yaml`, whose W5500, codec and I2S pins differ (the public one
reads W5500 chip ID 0x00 and fails the codec's I2C writes). Point its local `external_components` source at
`github://NabuCasa/aux-2@main`. Templates `aux-2-preprod-stock-dut.yaml` (I2S codec) and
`aux-2-preprod-usb-audio-dut.yaml` (the USB speaker as the mixer output via `!extend mixing_speaker`). The console is
the CH343 (1a86:55d3); first flash over it, OTA after. Forcing the Wi-Fi flag off with `restore_value: no` boot-loops
when Ethernet fails, so let the package switch itself.
@@ -0,0 +1,20 @@
# usb_cdc_acm
## usb_cdc_acm on the nRF52840 dongle (#11634)
`templates/usb-cdc-acm-dongle-dut.yaml`: logger on USB_CDC port 0, `usb_cdc_acm` interface 0 disabled,
interface 1 echoing from a 20 ms lambda, 0x01-prefixed commands for burst, stream, flush, peek, line coding
and counters. Build and flash with `dfu: true` through the app port; the dongle then shows three CDC ports:
the board DTS node `cdc_acm_uart` (unused), `cdc_acm_uart0` (log) and `cdc_acm_uart1` (data), in that order.
Capture the log port with `serial_capture.py`, drive the data port with `scripts/usb_cdc_acm/drive.py
DATA_PORT` (11 cases, prints PASS/FAIL and a summary) and grep the log for `Opened`, `Closed`, `buffer full`,
`flush result`, `line coding`. Boot lines are held in the driver's 1 KB ring until the host opens the port, so
a capture started later still shows the first 1 KB of the boot and then a truncated line.
With three CDC instances the Zephyr legacy stack's `CONFIG_USB_MAX_NUM_TRANSFERS=4` starves the last instance
(each instance holds an OUT read and an IN ZLP), so interface 1 shows DTR changes but moves no data.
`scripts/usb_cdc_acm/cdc_probe.hpp` (include it, call `cdc_probe()` from an interval) reads the Zephyr driver
directly to tell a dead endpoint from a component bug. Raising the transfer pool to 8, or disabling the
board's `cdc_acm_uart` node, fixes it. Design limit of the driver: `fifo_fill` holds `tx_ready` until the
write callback and `write_array` drops when the ring is full, so a 64 KiB full-duplex echo loses one 64 byte
tail on every buffer size.
@@ -0,0 +1,25 @@
# wifi
## `reboot_timeout` with a fallback AP (#9958)
Only one board runs the AP at a time, or stations join the wrong one. AP probe: bogus network,
`ap: {ssid, password, ap_timeout: 5s}`, `reboot_timeout: 60s`, `logger: level: VERBOSE` (ESP32 and
ESP8266 log `AP client connected MAC=` at verbose), `on_boot: logger.log: BOOT`. Station probes:
`reboot_timeout: 0s`, `interval: 150s` running `wifi.disable`, `delay: 20s`, `wifi.enable` (60 s cycles
are too short to cover the due moment). Pass pattern: "Can't connect; rebooting" exactly 60 s after boot
with no station joined, no reboot for minutes while one is, then a reboot within a second of the last
disconnect. The BW15 restarts its AP on every STA retry and cannot be tested this way.
## DHCP lease cache (#17767)
Cycle 20 s awake, `deep_sleep.enter 30s`, wake, 20 s, `App.safe_reboot()`, logging `millis()` at the
`wifi.connected` condition. An RTS hardware reset must fall back to DHCP; a software reboot and a deep
sleep wake must apply the cache. `esphome run --no-logs` silently skipped the upload on one board; use
`upload` then `logs`.
## Wi-Fi Easy Connect (DPP) enrollee (#17934)
Only an Android phone configures: Settings, network details, Add device, Share Wi-Fi (the Wi-Fi list's
QR scanner makes the phone an enrollee). Render the URI with the `qrcode` module and open the PNG.
`esp32: framework: log_level: DEBUG` plus `sdkconfig_options: CONFIG_WPA_DEBUG_PRINT: y` prints the
supplicant state machine.
@@ -0,0 +1,10 @@
# zephyr
## Zephyr preferences on the nRF52840 dongle (#20810)
`templates/nrf52-preferences-dut.yaml`: logger on USB_CDC at VERBOSE, `flash_write_interval: 5s`, one
`globals` with `restore_value` incremented every 15 s. Give each build its own global id so each first boot
sees a never-saved key (settings survive a reflash). Flow per build: flash through the log port, 40 s capture
of the first boot, then start a second capture and reboot with a 2001 baud touch on the dongle's other CDC
port (the dfu rate callback is global, and the capture holds the log port); `serial_capture.py` reopens the
log port after the USB re-enumeration and gets the buffered boot lines (`load setting`, `Loaded N settings`).
@@ -0,0 +1,17 @@
# zephyr_coredump
## zephyr_coredump crash and decode on nRF52 boards (#13122)
`templates/nrf52-coredump-crash-dut.yaml` (dongle; change `board:` to `adafruit_itsybitsy_nrf52840` for the
ItsyBitsy): `zephyr_coredump:`, `dfu: true`, a `restore_value` global keyed by the `run` substitution
(settings survive a reflash, so bump `run` for every new crash), and an `on_boot` sequence that sets the
flag, yields 3 s so the globals loop saves it (a blocking lambda never gets there and the board crash-loops),
syncs preferences, then writes to 0xDEADBEEF. Flash, keep every port closed for 80 s, then
`scripts/zephyr_coredump/flow.sh LABEL PORT ELF [OUTDIR]` does the three opens (3 s cut, 100 s full print,
20 s reopen) and calls `decode.sh`, which keeps the first complete `#CD:BEGIN#..END#` block, runs Zephyr's
`coredump_serial_log_parser.py` and `coredump_gdbserver.py` from the sdk-nrf ESPHome installed and prints
`bt` and `info threads` with its `arm-zephyr-eabi-gdb`. The ELF is `.pioenvs/<name>/zephyr/zephyr/zephyr.elf`.
Expect about 9 KB dumps, 140 to 150 `#CD:` lines, a 673 ms loop warning for the erase, and `Has stored
dump: 0` on the reopen. Check `compiled on` in every capture: another session flashing the same board
mid-test looks exactly like a failed test otherwise. Boards without a network cannot exercise the API and
MQTT delivery paths of the component; say so in the results.
@@ -0,0 +1,23 @@
# zwave_proxy
## zwave_proxy frame checksums on a live proxy (#20922)
A Z-Wave proxy (ESP32-S3 with the module on `usb_uart`) is a live device: flash it over OTA and read logs
over the API, there is no serial console because the native USB is the host port for the module, so the
boot-time `GET_NETWORK_IDS` exchange is never captured. Home Assistant's Z-Wave JS holds the single API
subscription (it polls `0x3B` every 30 s and resubscribes after every reboot), so do not subscribe from a
script, and keep the device `name:` so it stays on Z-Wave JS; give each build its own scratch directory
instead.
1. `logger: level: VERY_VERBOSE` with `logs: zwave_proxy: VERY_VERBOSE` and DEBUG for the rest: the
`Sending:`, `Received frame:` and `CHECKSUM Received ... Calculated` lines are `ESP_LOGVV`, which needs
the compile-time level.
2. `md5` the two `firmware.ota.bin` files to be sure the A and B builds differ, then `esphome upload
--device <name>.local` and a `timeout 330 esphome logs --device <name>.local` capture started right after
the OTA to catch the resubscribe traffic.
3. `scripts/zwave_proxy/verify_checksums.py <captures>` recomputes the XOR (init 0xFF, LENGTH..payload) over
every logged frame and counts bad checksums and ACK/NAK sent and received; `scripts/zwave_proxy/devinfo.py
<name>.local` prints the firmware version, compile time and the Home ID from the API (a Home ID proves the
device-built `GET_NETWORK_IDS` frame was accepted at boot).
4. Pre-existing: one `zwave_proxy took a long time for an operation` warning on the first frame after the
log client connects, caused by VERY_VERBOSE logging; it appears on dev too.
@@ -0,0 +1,33 @@
"""Press a template button on the test device over the API, or list its buttons.
usage: api_button.py HOST KEY list | press "Button name" [...]
"""
import asyncio
import sys
from aioesphomeapi import APIClient, ButtonInfo
HOST, KEY = sys.argv[1], sys.argv[2]
CMD, ARGS = sys.argv[3], sys.argv[4:]
async def main() -> None:
c = APIClient(HOST, 6053, None, noise_psk=KEY)
await c.connect(login=True)
ents, _ = await c.list_entities_services()
buttons = {e.name: e for e in ents if isinstance(e, ButtonInfo)}
if CMD == "list":
print("\n".join(sorted(buttons)))
else:
for name in ARGS:
if name not in buttons:
sys.exit(f"no button named {name!r}; have {sorted(buttons)}")
c.button_command(buttons[name].key)
print("pressed", name)
await asyncio.sleep(0.3)
await asyncio.sleep(0.5)
await c.disconnect()
asyncio.run(main())
@@ -0,0 +1,73 @@
"""Per wake cycle timings from a serial capture of the deep sleep timing config.
usage: cycles.py CAPTURE [LABEL]
A cycle starts at the boot banner; the numbers are the device's millis() from the timing lines, and the
capture clock gap from the first line of the cycle to the "Dialing" line.
"""
import pathlib
import re
import statistics
import sys
cap = (
pathlib.Path(sys.argv[1]).read_text(encoding="utf-8", errors="replace").splitlines()
)
label = sys.argv[2] if len(sys.argv) > 2 else ""
TS = re.compile(r"^\[(\d\d):(\d\d):(\d\d)(?:\.(\d+))?\]")
def secs(line):
m = TS.match(line)
if not m:
return None
h, mi, s, f = m.groups()
return int(h) * 3600 + int(mi) * 60 + int(s) + (int(f) / 10 ** len(f) if f else 0)
cycles = []
cur = None
for line in cap:
if "rst:0x" in line or (cur is None and "Log initialized" in line):
cur = {"start": secs(line), "reset": line.split("]", 1)[-1].strip()[:60]}
cycles.append(cur)
continue
if cur is None:
continue
if "Dialing" in line:
cur.setdefault("dial_cap", secs(line))
m = re.search(r"wifi connected at (\d+) ms", line)
if m:
cur.setdefault("wifi", int(m.group(1)))
m = re.search(r"client connected at (\d+) ms \((.*?)\)", line)
if m and "client" not in cur:
cur["client"] = int(m.group(1))
cur["who"] = m.group(2)
m = re.search(r"first HA state '(.*?)' at (\d+) ms", line)
if m and "state" not in cur:
cur["state"] = int(m.group(2))
print(f"{label} cycles: {len(cycles)}")
print(
f"{'n':>2} {'wifi ms':>8} {'dial +ms':>9} {'client ms':>10} {'state ms':>9} {'client-wifi':>11} {'state-client':>12} client reset"
)
for i, c in enumerate(cycles, 1):
dial = ""
if c.get("dial_cap") is not None and c.get("start") is not None:
dial = f"{(c['dial_cap'] - c['start']) * 1000:.0f}"
cw = c["client"] - c["wifi"] if "client" in c and "wifi" in c else ""
sc = c["state"] - c["client"] if "state" in c and "client" in c else ""
print(
f"{i:>2} {c.get('wifi', ''):>8} {dial:>9} {c.get('client', ''):>10} {c.get('state', ''):>9} {cw:>11} {sc:>12} {c.get('who', '')} {c.get('reset', '')[:28]}"
)
for c in cycles:
if "client" in c and "wifi" in c:
c["client-wifi"] = c["client"] - c["wifi"]
if "state" in c and "client" in c:
c["state-client"] = c["state"] - c["client"]
for key in ("wifi", "client", "state", "client-wifi", "state-client"):
vals = [c[key] for c in cycles if key in c]
if vals:
print(
f"{key}: n={len(vals)} min={min(vals)} median={statistics.median(vals):.0f} max={max(vals)}"
)
@@ -0,0 +1,107 @@
"""Per Wi-Fi drop timings from a serial capture of the outgoing connection timing config.
usage: wificycles.py CAPTURE [LABEL]
A cycle starts at "wifi cycle disabling"; the numbers are the device's millis() from the timing lines.
"dial" says whether the client that connected first came from the device's own dial ("Dialing" seen after the
reconnect and before the hello) or from an incoming connection ("Accept" seen first).
"""
import pathlib
import re
import statistics
import sys
cap = (
pathlib.Path(sys.argv[1]).read_text(encoding="utf-8", errors="replace").splitlines()
)
label = sys.argv[2] if len(sys.argv) > 2 else ""
TS = re.compile(r"^\[(\d\d):(\d\d):(\d\d)(?:\.(\d+))?\]")
def secs(line):
m = TS.match(line)
if not m:
return None
h, mi, se, f = m.groups()
return int(h) * 3600 + int(mi) * 60 + int(se) + (int(f) / 10 ** len(f) if f else 0)
cycles = []
cur = None
loop_max = []
for line in cap:
m = re.search(r"loop time max (\d+) ms", line)
if m:
loop_max.append(int(m.group(1)))
if "wifi cycle disabling" in line:
cur = {"order": []}
cycles.append(cur)
continue
if cur is None:
continue
m = re.search(r"wifi connected at (\d+) ms", line)
if m and "wifi" not in cur:
cur["wifi"] = int(m.group(1))
cur["wifi_cap"] = secs(line)
if "Dialing" in line:
cur["order"].append("dial")
cur["dials"] = cur.get("dials", 0) + 1
if "dial_cap" not in cur and "wifi_cap" in cur:
cur["dial_cap"] = secs(line)
elif "dial_cap" not in cur and "dial_before_wifi_cap" not in cur:
cur["dial_before_wifi_cap"] = secs(line)
if (
re.search(r"client disconnected at \d+ ms \(\d+\.\d+\.\d+\.\d+\)", line)
and "wifi" in cur
and "client" not in cur
):
cur["dial_closed"] = cur.get("dial_closed", 0) + 1
if "Accept " in line and "wifi" in cur:
cur["order"].append("accept")
m = re.search(r"client connected at (\d+) ms \((.*?)\)", line)
if m and "wifi" in cur and "client" not in cur:
cur["client"] = int(m.group(1))
cur["who"] = m.group(2).split(" ")[0]
cur["via"] = cur["order"][0] if cur["order"] else "?"
m = re.search(r"Connect (failed|timeout)", line)
if m:
cur.setdefault("connect_errors", 0)
cur["connect_errors"] += 1
print(f"{label} cycles: {len(cycles)}")
print(
f"{'n':>2} {'wifi ms':>8} {'dial-wifi cap ms':>16} {'client ms':>10} {'client-wifi':>11} via dials closed client"
)
for i, c in enumerate(cycles, 1):
cw = c["client"] - c["wifi"] if "client" in c and "wifi" in c else ""
c["client-wifi"] = cw
dw = ""
if "wifi_cap" in c and c.get("wifi_cap") is not None:
# the first dial of the cycle, whether it was logged before or after the Wi-Fi line
if c.get("dial_before_wifi_cap") is not None:
dw = f"{(c['dial_before_wifi_cap'] - c['wifi_cap']) * 1000:.0f}"
elif "dial_cap" in c and c["dial_cap"] is not None:
dw = f"{(c['dial_cap'] - c['wifi_cap']) * 1000:.0f}"
c["dial-wifi"] = dw
print(
f"{i:>2} {c.get('wifi', ''):>8} {dw:>16} {c.get('client', ''):>10} {cw:>11} {c.get('via', ''):7s} {c.get('dials', 0):>5} {c.get('dial_closed', 0):>6} {c.get('who', '')}"
)
for via in ("dial", "accept"):
vals = [
c["client-wifi"]
for c in cycles
if c.get("via") == via and c["client-wifi"] != ""
]
if vals:
print(
f"client-wifi via {via}: n={len(vals)} min={min(vals)} median={statistics.median(vals):.0f} max={max(vals)}"
)
dws = [float(c["dial-wifi"]) for c in cycles if c.get("dial-wifi") not in ("", None)]
if dws:
print(
f"dial after wifi connected (capture clock): n={len(dws)} min={min(dws):.0f} median={statistics.median(dws):.0f} max={max(dws):.0f}"
)
if loop_max:
print(
f"loop time max per 5 s: n={len(loop_max)} min={min(loop_max)} median={statistics.median(loop_max):.0f} max={max(loop_max)}"
)
@@ -0,0 +1,71 @@
"""Subscribe to states and logs of a device over the API for N seconds and summarize its float sensors.
usage: observe.py IP SECONDS OUTFILE [NAME_FILTER]
Every float sensor state whose entity name contains NAME_FILTER (default: all) is appended to OUTFILE
with a relative timestamp, log lines are appended with a LOG prefix, and a one-line summary per sensor
(count, min, max, last) plus the warning and error count is printed at the end.
"""
import asyncio
import pathlib
import re
import sys
import time
from aioesphomeapi import APIClient, LogLevel
IP, SECS, OUT = sys.argv[1], float(sys.argv[2]), sys.argv[3]
FILTER = sys.argv[4] if len(sys.argv) > 4 else ""
# ESPHome LOG_SECRET span; one cut off before its end marker is secret to the end of the line
SECRET = re.compile(r"\x1b\[8m.*?(?:\x1b\[28m|$)", re.DOTALL)
ANSI = re.compile(r"\x1b\[[0-9;]*m")
async def main() -> None:
c = APIClient(IP, 6053, None)
await c.connect(login=True)
di = await c.device_info()
ents, _ = await c.list_entities_services()
names = {e.key: e.name for e in ents}
readings: dict[str, list[float]] = {}
warnings: list[str] = []
t0 = time.monotonic()
with pathlib.Path(OUT).open("w", encoding="utf-8") as out:
out.write(
f"device {di.name} version {di.esphome_version} compiled {di.compilation_time}\n"
)
def on_state(st) -> None:
n = names.get(st.key, str(st.key))
if FILTER in n and hasattr(st, "state") and isinstance(st.state, float):
readings.setdefault(n, []).append(st.state)
out.write(f"[{time.monotonic() - t0:6.1f}] {n}: {st.state}\n")
out.flush()
def on_log(msg) -> None:
line = (
msg.message.decode(errors="replace")
if isinstance(msg.message, bytes)
else msg.message
)
line = ANSI.sub("", SECRET.sub("<secret>", line))
out.write(f"[{time.monotonic() - t0:6.1f}] LOG {line}\n")
out.flush()
if "[W]" in line or "[E]" in line or "Invalid" in line:
warnings.append(line)
c.subscribe_states(on_state)
c.subscribe_logs(on_log, log_level=LogLevel.LOG_LEVEL_VERBOSE)
await asyncio.sleep(SECS)
await c.disconnect()
summary = "; ".join(
f"{k.split()[-1]} n={len(v)} min={min(v):.3f} max={max(v):.3f} last={v[-1]:.3f}"
for k, v in sorted(readings.items())
)
print(f"{di.compilation_time}: {summary}; warnings/errors {len(warnings)}")
for w in warnings[:5]:
print(" ", w)
asyncio.run(main())
+27
View File
@@ -0,0 +1,27 @@
#!/bin/zsh
# usage: run_obs.sh IP SECONDS LABEL [OUTDIR] [NAME_FILTER]
# Observes states and logs over the API with a concurrent ping and prints a summary: the device-side
# "frames parsed" counter per 10 s, gaps over 5 s between voltage updates seen by the API, and warnings.
# Needs a python with aioesphomeapi (PYTHON, default python3).
D=${0:A:h}
IP=$1; SECS=$2; L=$3; S=${4:-.}; F=${5:-}
PY=${PYTHON:-python3}
setopt pipefail
ping -i 1 -c $SECS $IP > $S/ping-$L.txt 2>&1 &
P=$!
if ! timeout $((SECS + 40)) $PY $D/observe.py $IP $SECS $S/obs-$L.txt "$F" 2>&1 | sed "s/$IP/<ip>/g"; then
echo "observe.py failed or timed out, no summary"; wait $P; exit 1
fi
wait $P
echo "ping: $(tail -2 $S/ping-$L.txt | head -1 | sed "s/$IP/<ip>/g") $(tail -1 $S/ping-$L.txt)"
python3 - "$S/obs-$L.txt" <<'PY'
import re, sys
txt = open(sys.argv[1]).read()
fr = [int(m.group(1)) for m in re.finditer(r"frames parsed: (\d+) in 10 s", txt)]
print(f"device frame counter per 10 s: n={len(fr)} min={min(fr) if fr else None} max={max(fr) if fr else None} mean={sum(fr)/len(fr) if fr else 0:.1f}")
ts = [float(m.group(1)) for m in re.finditer(r"^\[\s*([0-9.]+)\] .*Voltage: ", txt, re.M)]
gaps = [round(b - a, 1) for a, b in zip(ts, ts[1:]) if b - a > 5]
print(f"API voltage updates {len(ts)}, gaps over 5 s: {gaps}")
warn = [l for l in txt.splitlines() if "LOG" in l and ("[W]" in l or "[E]" in l or "Invalid" in l)]
print(f"warnings/errors in log: {len(warn)}", *warn[:3], sep="\n ")
PY
@@ -0,0 +1,51 @@
"""ltchiptool for boards whose RTS drives CEN: reset and link on one open port.
Stock ltchiptool closes and reopens the port after its DTR/RTS reset, and
pyserial raises both lines on open, holding CEN low through the BootROM's
listen window. Open with both lines released and pulse the reset right
before the LinkChecks instead.
"""
import sys
import bk7231tools.serial.main as bk_main
from ltchiptool.__main__ import cli
from ltchiptool.soc.bk72xx.flash import BK72XXFlash
from serial import Serial
class ReleasedSerial(Serial):
def __init__(self, port=None, **kwargs):
super().__init__(None, **kwargs)
self.dtr = False
self.rts = False
self.port = port
self.open()
bk_main.Serial = ReleasedSerial
_connect = BK72XXFlash.flash_connect
def flash_hw_reset(self) -> None:
self._reset_pending = True
def flash_connect(self) -> None:
pending = getattr(self, "_reset_pending", False)
if self.bk and self.conn.linked and not pending:
return
self.flash_build_protocol()
if pending:
self._reset_pending = False
self.bk.hw_reset()
self.conn.linked = False # the chip rebooted, so link again below
_connect(self)
BK72XXFlash.flash_hw_reset = flash_hw_reset
BK72XXFlash.flash_connect = flash_connect
sys.argv = ["ltchiptool", *sys.argv[1:]]
cli()
@@ -0,0 +1,87 @@
"""ltchiptool for the BW15 kit whose CH340 drives CEN (RTS) and PA00 (DTR).
A plain open asserts both lines and holds the chip in reset. Here every open keeps DTR high and RTS
released; the first open pulses RTS so the ROM comes up; after the command DTR low plus an RTS pulse
boots the app. The exit status is ltchiptool's; 2 means the flash succeeded but the app was not
booted (boot pulse failed, or no -d PORT to send it to), so the board sits in the ROM until reset.
Usage: python flash_rtl87xx_kit.py flash write -d <port> firmware.uf2
"""
import sys
import time
import serial
_first = [True]
_orig_open = serial.Serial.open
def _open(self):
self.dtr = True # PA00 high: ROM download mode on the next reset
self.rts = False # CEN released
_orig_open(self)
if _first[0]:
_first[0] = False
self.rts = True
time.sleep(0.1)
self.rts = False
time.sleep(0.3) # reset into the ROM
self.reset_input_buffer()
serial.Serial.open = _open
from ltchiptool.__main__ import cli_entrypoint # noqa: E402
def port_argument(argv: list[str]) -> str | None:
for i, a in enumerate(argv):
if a in ("-d", "--device") and i + 1 < len(argv):
return argv[i + 1]
if a.startswith(("-d=", "--device=")):
return a.split("=", 1)[1]
if a.startswith("-d") and len(a) > 2:
return a[2:]
return None
def boot_app(port: str) -> None:
s = serial.Serial(port=None)
s.port = port
s.dtr = False
s.rts = False
_orig_open(s)
s.rts = True
time.sleep(0.1)
s.rts = False
s.close()
def main() -> int:
rc = 1
try:
result = cli_entrypoint.main(args=sys.argv[1:], standalone_mode=False)
rc = int(result) if isinstance(result, int) else 0
finally:
port = port_argument(sys.argv)
if not port:
print(
"[flash_rtl87xx_kit] no -d PORT given, the application was not booted",
file=sys.stderr,
)
if rc == 0:
rc = 2
else:
try:
boot_app(port)
print("[flash_rtl87xx_kit] application booted")
except (serial.SerialException, OSError) as err:
print(f"[flash_rtl87xx_kit] boot pulse failed: {err}", file=sys.stderr)
if rc == 0:
rc = 2 # flashed, but the board is still in the ROM: press reset
return rc
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,78 @@
"""Read or drive a bulb directly over its own ESPHome API (an HA-side change, independent of the mirror).
usage: bulb.py HOST KEY state | on [brightness 0-1] [ct_mireds|rgb r,g,b] | off | restart
"""
import asyncio
import sys
from aioesphomeapi import APIClient, ButtonInfo, LightColorCapability, LightInfo
HOST, KEY, CMD = sys.argv[1], sys.argv[2], sys.argv[3]
ARGS = sys.argv[4:]
async def main() -> None:
c = APIClient(HOST, 6053, None, noise_psk=None if KEY == "-" else KEY)
await c.connect(login=True)
ents, _ = await c.list_entities_services()
light = next((e for e in ents if isinstance(e, LightInfo)), None)
if light is None:
sys.exit("no light entity on this device; is HOST a bulb?")
if CMD == "state":
fut = asyncio.get_running_loop().create_future()
def on_state(st):
if getattr(st, "key", None) == light.key and not fut.done():
fut.set_result(st)
c.subscribe_states(on_state)
st = await asyncio.wait_for(fut, 5)
print(
f"{light.name}: on={st.state} mode={st.color_mode} bri={st.brightness:.2f} "
f"ct={st.color_temperature:.1f} rgb={st.red:.2f}/{st.green:.2f}/{st.blue:.2f} "
f"white={st.white:.2f} cold={st.cold_white:.2f} warm={st.warm_white:.2f}"
)
elif CMD == "on":
kw = {"state": True}
if ARGS:
kw["brightness"] = float(ARGS[0])
if len(ARGS) > 1:
if "," in ARGS[1]:
r, g, b = (float(x) for x in ARGS[1].split(","))
kw["rgb"] = (r, g, b)
kw["color_mode"] = (
LightColorCapability.RGB
| LightColorCapability.ON_OFF
| LightColorCapability.BRIGHTNESS
)
else:
kw["color_temperature"] = float(ARGS[1])
kw["color_mode"] = (
LightColorCapability.COLOR_TEMPERATURE
| LightColorCapability.ON_OFF
| LightColorCapability.BRIGHTNESS
)
c.light_command(light.key, **kw)
print("bulb on", kw)
elif CMD == "off":
c.light_command(light.key, state=False)
print("bulb off")
elif CMD == "restart":
btn = next(
(
e
for e in ents
if isinstance(e, ButtonInfo) and "restart" in e.name.lower()
),
None,
)
if btn is None:
sys.exit("no restart button on this device")
c.button_command(btn.key)
print("bulb restart pressed")
await asyncio.sleep(0.5)
await c.disconnect()
asyncio.run(main())
@@ -0,0 +1,116 @@
// BK72xx crash probe: forces one crash per step and shows what the SDK and the bootloader leave behind.
// Steps: 0 undefined instruction, 1 call into unmapped memory, 2 unmapped and unaligned accesses (no fault),
// 3 lt_reboot(). The step lives in a flash preference saved and synced before the crash.
#pragma once
#include <Arduino.h>
#include <FreeRTOS.h>
#include <task.h>
#include <cstdint>
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/preferences.h"
extern "C" unsigned bk_misc_get_start_type(void);
extern "C" char _bss_end[];
static constexpr uint32_t PROBE_CANARY_LOW = 0x004000F8; // free word in the SDK-reserved low SRAM
static constexpr uint32_t PROBE_CANARY_ITCM = 0x003F3FF0; // top of ITCM
static char bkprobe_report[112];
static constexpr uint32_t PROBE_MARKS[] = {0x40A1B0, 0x40B000, 0x40C000, 0x40E000, 0x410000, 0x414000, 0x418000,
0x41C000, 0x420000, 0x428000, 0x430000, 0x438000, 0x43C000, 0x43E000,
0x43F000, 0x43F800, 0x43FC00, 0x43FE00, 0x43FFF0};
static constexpr size_t PROBE_MARK_COUNT = sizeof(PROBE_MARKS) / sizeof(PROBE_MARKS[0]);
static inline uint32_t rd(uint32_t a) { return *(volatile uint32_t *) a; }
inline esphome::ESPPreferenceObject bkprobe_pref(uint32_t run) {
return esphome::global_preferences->make_preference<int>(0xBC000000u + run);
}
inline int bkprobe_boot(uint32_t run) {
using namespace esphome;
int step = 0;
bkprobe_pref(run).load(&step);
unsigned st = bk_misc_get_start_type();
snprintf(bkprobe_report, sizeof(bkprobe_report), "step=%d reason=%s(%d) start_type=%u low=%08lx itcm=%08lx top=%08lx",
step, lt_get_reboot_reason_name(lt_get_reboot_reason()), (int) lt_get_reboot_reason(), st,
(unsigned long) rd(PROBE_CANARY_LOW), (unsigned long) rd(PROBE_CANARY_ITCM),
(unsigned long) rd(0x0043FFF0u));
ESP_LOGI("probe", "%s", bkprobe_report);
for (uint32_t a = 0x400000; a < 0x400100; a += 32) {
ESP_LOGI("probe", "%06lx: %08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx", (unsigned long) a,
(unsigned long) rd(a), (unsigned long) rd(a + 4), (unsigned long) rd(a + 8), (unsigned long) rd(a + 12),
(unsigned long) rd(a + 16), (unsigned long) rd(a + 20), (unsigned long) rd(a + 24),
(unsigned long) rd(a + 28));
}
for (size_t i = 0; i < PROBE_MARK_COUNT; i += 7) {
char line[120];
int n = 0;
for (size_t j = i; j < i + 7 && j < PROBE_MARK_COUNT; j++)
n += snprintf(line + n, sizeof(line) - n, " %06lx=%08lx", (unsigned long) PROBE_MARKS[j],
(unsigned long) rd(PROBE_MARKS[j]));
ESP_LOGI("probe", "marks%s", line);
}
uint32_t rec = ((uint32_t) _bss_end + 7) & ~7u;
for (uint32_t a = rec; a < rec + 64; a += 32) {
ESP_LOGI("probe", "rec %06lx: %08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx", (unsigned long) a,
(unsigned long) rd(a), (unsigned long) rd(a + 4), (unsigned long) rd(a + 8), (unsigned long) rd(a + 12),
(unsigned long) rd(a + 16), (unsigned long) rd(a + 20), (unsigned long) rd(a + 24),
(unsigned long) rd(a + 28));
}
*(volatile uint32_t *) PROBE_CANARY_LOW = 0xC0DE0000u | (uint32_t) step;
*(volatile uint32_t *) PROBE_CANARY_ITCM = 0x17C40000u | (uint32_t) step;
return step;
}
__attribute__((noinline)) void bkprobe_data_abort() {
using namespace esphome;
volatile uint32_t v = rd(0xFFFFFFF0);
ESP_LOGI("probe", "read 0xFFFFFFF0 -> %08lx (no fault)", (unsigned long) v);
uint32_t r;
__asm__ volatile("ldr %0, [%1]" : "=r"(r) : "r"(0x00400101) : "memory");
ESP_LOGI("probe", "unaligned ldr 0x400101 -> %08lx (no fault)", (unsigned long) r);
*(volatile uint32_t *) 0xFFFFFFF0 = 1;
ESP_LOGI("probe", "store 0xFFFFFFF0 (no fault)");
}
// act is the step number before it was bumped
inline void bkprobe_act(uint32_t run, int act) {
using namespace esphome;
int next = act + 1;
bkprobe_pref(run).save(&next);
global_preferences->sync();
ESP_LOGI("probe", "acting on step %d (saved %d)", act, next);
// mark candidate storage areas so the next boot shows what survives
uint32_t rec = ((uint32_t) _bss_end + 7) & ~7u;
for (uint32_t i = 0; i < 8; i++)
*(volatile uint32_t *) (rec + 32 + i * 4) = 0xA5A50000u | i; // second half of the record area
for (size_t i = 0; i < PROBE_MARK_COUNT; i++)
*(volatile uint32_t *) PROBE_MARKS[i] = 0x70700000u | (uint32_t) i;
ESP_LOGI("probe", "ramtop before: %08lx", (unsigned long) rd(0x0043FFF0u));
vTaskDelay(pdMS_TO_TICKS(50));
switch (act) {
case 0:
__builtin_trap(); // undefined instruction
break;
case 1: {
void (*fn)() = (void (*)()) 0xFFFFFFF1; // prefetch abort (unmapped, Thumb)
fn();
ESP_LOGI("probe", "call 0xFFFFFFF1 returned (no fault)");
break;
}
case 2:
bkprobe_data_abort();
break;
case 3:
lt_reboot();
break;
default:
break;
}
}
inline void bkprobe_alive(int step) {
using namespace esphome;
ESP_LOGI("probe", "alive step=%d uptime=%lus boot: %s", step, (unsigned long) (millis() / 1000), bkprobe_report);
}
@@ -0,0 +1,25 @@
"""Feed a serial capture through the CLI's stacktrace decoder for a build.
usage: decode_capture.py CONFIG_YAML CAPTURE
Run from the directory that holds the config (its .esphome build is used), with the esphome checkout that
built it on PYTHONPATH. Lines of the crash report are echoed, decoded frames follow them.
"""
import logging
from pathlib import Path
import sys
from esphome.config import read_config
from esphome.core import CORE
from esphome.stacktrace import LogLineProcessor
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
yaml, capture = Path(sys.argv[1]), Path(sys.argv[2])
CORE.config_path = yaml
config = read_config({})
proc = LogLineProcessor(config, CORE.target_platform)
for line in capture.read_text(encoding="utf-8", errors="replace").splitlines():
if "libretiny.crash" in line:
print(line[:120])
proc.process_line(line)
@@ -0,0 +1,155 @@
#pragma once
// BK7238 bench harness for esphome#21007: one rtc and one flash preference, a step counter in flash,
// and an exit action per step so the board drives its own soft reboot, crash and watchdog reboot.
#include <libretiny.h>
#include <cstring>
#include <FreeRTOS.h>
#include <task.h>
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/preferences.h"
using namespace esphome;
// Boot report kept for the alive line, so a boot the host could not capture is still visible
static char ltprefs_report[128];
// Last boots' reports, kept in a flash preference so boots the host never saw stay readable
struct LtprefsHistory {
char line[8][128];
};
static LtprefsHistory ltprefs_hist;
inline ESPPreferenceObject ltprefs_step_pref(uint32_t run) {
return global_preferences->make_preference<int>(0x3B210070u ^ run, true);
}
// Loads the step and both preferences, logs them, saves this step's value.
// Returns the exit action: 0 idle, 1 lt_reboot, 2 crash, 3 watchdog.
inline int ltprefs_boot(int &step, uint32_t run) {
auto reason = lt_get_reboot_reason();
step = 0;
ltprefs_step_pref(run).load(&step);
auto rtcp = global_preferences->make_preference<uint32_t>(0x1B210070u ^ run, false);
auto flp = global_preferences->make_preference<uint32_t>(0x2B210070u ^ run, true);
uint32_t rv = 0, fv = 0;
// a second pair sharing one key as rtc and as flash, and a third rtc entry, so a hard reset drops three
auto rtcp2 = global_preferences->make_preference<uint32_t>(0x5B210070u ^ run, false);
auto flp2 = global_preferences->make_preference<uint32_t>(0x5B210070u ^ run, true);
auto rtcp3 = global_preferences->make_preference<uint32_t>(0x6B210070u ^ run, false);
uint32_t rv2 = 0, fv2 = 0, rv3 = 0;
bool rok = rtcp.load(&rv);
bool fok = flp.load(&fv);
bool rok2 = rtcp2.load(&rv2);
bool fok2 = flp2.load(&fv2);
bool rok3 = rtcp3.load(&rv3);
snprintf(ltprefs_report, sizeof(ltprefs_report),
"step=%d reason=%s(%d) rtc=%s%u flash=%s%u sharedkey rtc=%s%u flash=%s%u rtc3=%s%u", step,
lt_get_reboot_reason_name(reason), (int) reason, rok ? "" : "none ", rv, fok ? "" : "none ", fv,
rok2 ? "" : "none ", rv2, fok2 ? "" : "none ", fv2, rok3 ? "" : "none ", rv3);
ESP_LOGI("lttest", "%s", ltprefs_report);
auto histp = global_preferences->make_preference<LtprefsHistory>(0x4B210070u ^ run, true);
if (!histp.load(&ltprefs_hist))
memset(&ltprefs_hist, 0, sizeof(ltprefs_hist));
memmove(&ltprefs_hist.line[1], &ltprefs_hist.line[0], sizeof(ltprefs_hist.line) - sizeof(ltprefs_hist.line[0]));
strncpy(ltprefs_hist.line[0], ltprefs_report, sizeof(ltprefs_hist.line[0]) - 1);
ltprefs_hist.line[0][sizeof(ltprefs_hist.line[0]) - 1] = 0;
if (!histp.save(&ltprefs_hist)) {
ESP_LOGW("lttest", "history save failed");
}
int act = 0;
bool save = true;
switch (step) {
case 0:
act = 1;
break; // soft reboot
case 1:
act = 2;
break; // crash
case 2:
act = 3;
break; // watchdog
case 3:
act = 0;
break; // idle: chip-enable reset from the host
case 4:
act = 1;
break; // soft reboot after the hard reset
case 5:
act = 0;
break; // idle: chip-enable reset from the host
case 6:
act = 1;
save = false;
break; // no save, soft reboot: deleted, not skipped?
case 7:
act = 0;
break; // idle
case 8:
act = 2;
break; // crash
case 9:
act = 0;
break; // idle
case 10:
act = 0;
break; // idle: power cut by the user
default:
act = 0;
save = false;
break;
}
if (save) {
uint32_t v = 100 + step, v2r = 200 + step, v2f = 300 + step, v3 = 400 + step;
bool srtc = rtcp.save(&v);
bool sfl = flp.save(&v);
bool srtc2 = rtcp2.save(&v2r);
bool sfl2 = flp2.save(&v2f);
bool srtc3 = rtcp3.save(&v3);
bool synced = global_preferences->sync();
ESP_LOGI("lttest", "save %u/%u/%u/%u: rtc=%d flash=%d sharedkey rtc=%d flash=%d rtc3=%d sync=%d", v, v2r, v2f, v3,
srtc, sfl, srtc2, sfl2, srtc3, synced);
}
ESP_LOGI("lttest", "exit action=%d", act);
return act;
}
inline void ltprefs_exit(int &step, uint32_t run, int act) {
step++;
bool saved = ltprefs_step_pref(run).save(&step);
bool synced = global_preferences->sync();
ESP_LOGI("lttest", "step %d save=%d sync=%d", step, saved, synced);
if (!saved || !synced) {
ESP_LOGE("lttest", "step not stored, staying idle instead of repeating the step");
return;
}
vTaskDelay(pdMS_TO_TICKS(200));
switch (act) {
case 1:
ESP_LOGW("lttest", "soft reboot now");
vTaskDelay(pdMS_TO_TICKS(50));
lt_reboot();
break;
case 2:
ESP_LOGW("lttest", "crashing now");
vTaskDelay(pdMS_TO_TICKS(50));
__builtin_trap(); // undefined instruction: a plain bad store does not fault on the ARM968
break;
case 3:
ESP_LOGW("lttest", "watchdog reboot now");
vTaskDelay(pdMS_TO_TICKS(50));
lt_reboot_wdt();
break;
default:
ESP_LOGW("lttest", "idle, waiting for an external reset");
break;
}
}
inline void ltprefs_alive(int step, int act) {
ESP_LOGI("lttest", "alive step=%d act=%d boot: %s", step, act, ltprefs_report);
for (int i = 7; i >= 0; i--) {
if (ltprefs_hist.line[i][0] != 0) {
ESP_LOGI("lttest", " history[-%d]: %s", i, ltprefs_hist.line[i]);
}
}
}
@@ -0,0 +1,179 @@
"""Drive the online_image cases over the native API and judge each from the serial capture."""
import asyncio
import pathlib
import re
import sys
import time
from aioesphomeapi import (
APIClient,
APIConnectionError,
InvalidEncryptionKeyAPIError,
RequiresEncryptionAPIError,
)
HOST, KEY, CAPTURE, BASE, MODE = sys.argv[1:6]
CAP_S = 45
CASES = [
# (label, action, path, expected outcome)
("ok bmp", "fetch", "ok.bmp", "finished"),
("ok png", "fetch", "ok.png", "finished"),
("ok jpg", "fetch", "ok.jpg", "finished"),
("ok qoi", "fetch", "ok.qoi", "finished"),
("truncated bmp", "fetch", "truncated.bmp", "error"),
("ok bmp after error", "fetch", "ok.bmp", "finished"),
("large header, 256 byte buffer", "fetch_small", "large_header.bmp", "error"),
("ok bmp, 256 byte buffer", "fetch_small", "ok.bmp", "finished"),
("empty body", "fetch", "empty.bmp", "error"),
("stalled connection", "fetch", "stall.bmp", "error"),
("closed early", "fetch", "closed_early.bmp", "error"),
("slow but alive", "fetch", "slow.bmp", "finished"),
("chunked bmp", "fetch", "chunked.bmp", "finished"),
("chunked png", "fetch", "chunked.png", "finished"),
("chunked qoi", "fetch", "chunked.qoi", "finished"),
("chunked bmp cut", "fetch", "chunked_cut.bmp", "error"),
("chunked png cut", "fetch", "chunked_cut.png", "any"),
("no content-length", "fetch", "noclen.bmp", "any"),
("huge jpeg claim", "fetch", "huge.jpg", "error"),
("ok jpg after huge", "fetch", "ok.jpg", "finished"),
("ok bmp again", "fetch", "ok.bmp", "finished"),
("ok bmp refetch: 304", "refetch", "", "finished"),
("ok png", "fetch", "ok.png", "finished"),
("ok png refetch: 304", "refetch", "", "finished"),
("chunked bmp", "fetch", "chunked.bmp", "finished"),
("chunked bmp refetch: 304", "refetch", "", "finished"),
("chunked png", "fetch", "chunked.png", "finished"),
("chunked png refetch: not cached", "refetch", "", "finished"),
("chunked png cut", "fetch", "chunked_cut.png", "any"),
("chunked png cut refetch: not cached", "refetch", "", "any"),
]
if MODE == "base":
# Each stuck case is followed by a fetch that shows the symptom, then a reboot.
CASES = [
("ok bmp", "fetch", "ok.bmp", "finished"),
("truncated bmp", "fetch", "truncated.bmp", "error"),
("ok bmp after truncated", "fetch", "ok.bmp", "finished"),
("REBOOT", "", "", ""),
("large header, 256 byte buffer", "fetch_small", "large_header.bmp", "error"),
("ok bmp, 256 byte buffer", "fetch_small", "ok.bmp", "finished"),
("REBOOT", "", "", ""),
("empty body", "fetch", "empty.bmp", "error"),
("ok bmp after empty", "fetch", "ok.bmp", "finished"),
("REBOOT", "", "", ""),
("stalled connection", "fetch", "stall.bmp", "error"),
("ok bmp after stall", "fetch", "ok.bmp", "finished"),
("REBOOT", "", "", ""),
("closed early", "fetch", "closed_early.bmp", "error"),
("ok bmp after closed early", "fetch", "ok.bmp", "finished"),
("REBOOT", "", "", ""),
("chunked png", "fetch", "chunked.png", "finished"),
("ok bmp after chunked png", "fetch", "ok.bmp", "finished"),
("REBOOT", "", "", ""),
("chunked bmp cut", "fetch", "chunked_cut.bmp", "error"),
("ok bmp after chunked cut", "fetch", "ok.bmp", "finished"),
("REBOOT", "", "", ""),
("huge jpeg claim", "fetch", "huge.jpg", "error"),
("ok jpg after huge", "fetch", "ok.jpg", "finished"),
]
OUTCOME = re.compile(r"download (finished|error): (img_small|img)")
INTEREST = re.compile(
r"\[(E|W)\]\[online_image|Image already being updated|download (finished|error)|Downloading image|Updating image|Image fully downloaded|HTTP 304|allocat|Panic|abort\(\)|Guru|Backtrace|rst:0x",
re.IGNORECASE,
)
def tail(path: str, offset: int) -> tuple[list[str], int]:
with pathlib.Path(path).open("rb") as f:
f.seek(offset)
data = f.read()
return data.decode("utf-8", "replace").splitlines(), offset + len(data)
async def connect() -> tuple[APIClient, dict, int]:
for _ in range(60):
try:
c = APIClient(HOST, 6053, None, noise_psk=KEY)
await c.connect(login=True)
ents, services = await c.list_entities_services()
except (InvalidEncryptionKeyAPIError, RequiresEncryptionAPIError) as exc:
raise SystemExit(f"API key problem, not retrying: {exc}") from exc
except (APIConnectionError, OSError) as exc:
# only connection trouble is retried; a wrong key or a missing button must fail at once
print(f"connect: {exc}", flush=True)
await asyncio.sleep(3)
continue
btn = next(
(e.key for e in ents if getattr(e, "object_id", "") == "reboot"), None
)
if btn is None:
raise SystemExit("the config has no reboot button")
return c, {s.name: s for s in services}, btn
raise SystemExit("API never connected")
async def main() -> None:
client, actions, btn = await connect()
with pathlib.Path(CAPTURE).open("rb") as f:
f.seek(0, 2)
offset = f.tell()
rows = []
for label, action, path, expect in CASES:
if label == "REBOOT":
client.button_command(btn)
await asyncio.sleep(1)
await client.disconnect()
await asyncio.sleep(8)
client, actions, btn = await connect()
_, offset = tail(CAPTURE, offset)
print("--- rebooted", flush=True)
continue
img = "img_small" if action.endswith("_small") else "img"
t0 = time.monotonic()
args = {} if action.startswith("refetch") else {"url": f"{BASE}/{path}"}
await client.execute_service(actions[action], args)
outcome, notes = "no outcome", []
while time.monotonic() - t0 < CAP_S:
await asyncio.sleep(0.5)
lines, offset = tail(CAPTURE, offset)
done = False
for line in lines:
if INTEREST.search(line):
notes.append(line.strip())
m = OUTCOME.search(line)
if m and m.group(2) == img:
outcome = m.group(1)
done = True
if done:
break
took = time.monotonic() - t0
if outcome == "no outcome":
took = CAP_S
# drain a little more so the "already being updated" line lands
await asyncio.sleep(1)
lines, offset = tail(CAPTURE, offset)
notes += [line.strip() for line in lines if INTEREST.search(line)]
ok = outcome == expect or (expect == "any" and outcome != "no outcome")
rows.append((label, path, expect, outcome, took, ok, notes))
print(
f"{'PASS' if ok else 'FAIL'} {label:32s} {path:18s} expected {expect:9s} got {outcome:10s} {took:5.1f}s",
flush=True,
)
for n in notes:
print(f" {n}", flush=True)
await asyncio.sleep(1.5)
await client.disconnect()
print(
"\nSUMMARY",
"all expectations met"
if all(r[5] for r in rows)
else "MISMATCHES: " + ", ".join(r[0] for r in rows if not r[5]),
flush=True,
)
if not all(r[5] for r in rows):
sys.exit(1)
asyncio.run(main())
@@ -0,0 +1,38 @@
"""Build the small test images the HTTP server serves."""
from pathlib import Path
import struct
import sys
from PIL import Image
out = Path(sys.argv[1])
out.mkdir(exist_ok=True)
w = h = 16
im = Image.new("RGB", (w, h))
px = im.load()
for y in range(h):
for x in range(w):
px[x, y] = (x * 16, y * 16, (x + y) * 8)
im.save(out / "ok.bmp", "BMP")
im.save(out / "ok.png", "PNG")
im.save(out / "ok.jpg", "JPEG", quality=90)
# QOI with only QOI_OP_RGB codes: valid and trivially encoded.
qoi = b"qoif" + struct.pack(">IIBB", w, h, 3, 0)
for y in range(h):
for x in range(w):
qoi += bytes([0xFE, *px[x, y]])
qoi += bytes(7) + b"\x01"
(out / "ok.qoi").write_bytes(qoi)
# 8x8 8-bit palette BMP: 1078 byte header, pixels start beyond a 256 byte buffer.
pw = ph = 8
data_offset = 14 + 40 + 256 * 4
file_size = data_offset + pw * ph
hdr = b"BM" + struct.pack("<IHHI", file_size, 0, 0, data_offset)
dib = struct.pack("<IiiHHIIiiII", 40, pw, ph, 1, 8, 0, pw * ph, 0, 0, 256, 0)
(out / "large_header.bmp").write_bytes(hdr + dib + bytes(256 * 4) + bytes(pw * ph))
for p in sorted(out.iterdir()):
print(p.name, p.stat().st_size)
@@ -0,0 +1,142 @@
"""HTTP server with one misbehaviour per path, for the online_image stalled-download test."""
import asyncio
from pathlib import Path
import sys
import time
WWW = Path(sys.argv[1])
PORT = int(sys.argv[2])
LOG = Path(sys.argv[3]).open("a", encoding="utf-8", buffering=1) # noqa: SIM115
MIME = {"bmp": "image/bmp", "png": "image/png", "jpg": "image/jpeg", "qoi": "image/qoi"}
IMG = {n: (WWW / f"ok.{n}").read_bytes() for n in MIME}
BMP = IMG["bmp"]
BMP_HDR = 54 # 16x16 24-bit: pixel rows follow directly
LARGE_HEADER = (WWW / "large_header.bmp").read_bytes()
def log(msg: str) -> None:
LOG.write(f"{time.strftime('%H:%M:%S')} {msg}\n")
def headers(ctype: str, extra: list[bytes]) -> bytes:
lines = [
b"HTTP/1.1 200 OK",
b"Content-Type: " + ctype.encode(),
b"Connection: close",
*extra,
b"",
b"",
]
return b"\r\n".join(lines)
def etag(path: str) -> bytes:
return b'"' + path.strip("/").encode() + b'-v1"'
def chunked(body: bytes, size: int) -> bytes:
out = b""
for i in range(0, len(body), size):
part = body[i : i + size]
out += b"%x\r\n" % len(part) + part + b"\r\n"
return out + b"0\r\n\r\n"
async def respond(path: str, req: bytes, w: asyncio.StreamWriter) -> str:
name, _, ext = path.lstrip("/").rpartition(".")
ctype = MIME.get(ext, "application/octet-stream")
body = IMG.get(ext, BMP)
tag = [b"ETag: " + etag(path)]
if b"if-none-match: " + etag(path).lower() in req.lower():
w.write(
b"HTTP/1.1 304 Not Modified\r\nETag: "
+ etag(path)
+ b"\r\nConnection: close\r\n\r\n"
)
return "304 not modified"
if name == "ok":
w.write(headers(ctype, [b"Content-Length: %d" % len(body), *tag]) + body)
return f"full {len(body)} bytes"
if name == "truncated": # cut one byte into the sixth pixel, Content-Length matches
cut = BMP[: BMP_HDR + 5 * 3 + 1]
w.write(headers(ctype, [b"Content-Length: %d" % len(cut)]) + cut)
return f"truncated {len(cut)} bytes"
if name == "large_header":
w.write(
headers(ctype, [b"Content-Length: %d" % len(LARGE_HEADER)]) + LARGE_HEADER
)
return f"large header {len(LARGE_HEADER)} bytes"
if name == "empty":
w.write(headers(ctype, [b"Content-Length: 0"]))
return "empty body"
if (
name == "stall"
): # claims the full length, sends part, then holds the socket open
part = body[: BMP_HDR + 15]
w.write(headers(ctype, [b"Content-Length: %d" % len(body)]) + part)
await w.drain()
await asyncio.sleep(40)
return f"stalled after {len(part)} bytes, closed after 40 s"
if name == "closed_early": # claims the full length, sends part, closes at once
part = body[: BMP_HDR + 15]
w.write(headers(ctype, [b"Content-Length: %d" % len(body)]) + part)
return f"closed after {len(part)} of {len(body)} bytes"
if name == "slow": # pauses under the timeout, then completes
w.write(headers(ctype, [b"Content-Length: %d" % len(body)]) + body[:BMP_HDR])
await w.drain()
await asyncio.sleep(2.5)
w.write(body[BMP_HDR:])
return f"full {len(body)} bytes with a 2.5 s pause"
if name == "chunked":
w.write(
headers(ctype, [b"Transfer-Encoding: chunked", *tag]) + chunked(body, 64)
)
return f"chunked full {len(body)} bytes"
if (
name == "chunked_cut"
): # chunked, ends cleanly at a pixel boundary (or half a png)
cut = body[: BMP_HDR + 5 * 3] if ext == "bmp" else body[: len(body) // 2]
w.write(
headers(ctype, [b"Transfer-Encoding: chunked", *tag]) + chunked(cut, 64)
)
return f"chunked cut {len(cut)} of {len(body)} bytes"
if name == "noclen": # body delimited by connection close
w.write(headers(ctype, []) + body)
return f"no content-length, full {len(body)} bytes"
if name == "huge": # claims 20 MB so the JPEG buffer resize fails
w.write(headers(ctype, [b"Content-Length: 20000000"]) + body)
return f"claims 20000000, sent {len(body)} bytes"
w.write(b"HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\nConnection: close\r\n\r\n")
return "404"
async def handle(r: asyncio.StreamReader, w: asyncio.StreamWriter) -> None:
peer = w.get_extra_info("peername")
try:
async with asyncio.timeout(5):
line = await r.readuntil(b"\r\n")
req = await r.readuntil(b"\r\n\r\n")
path = line.split(b" ")[1].decode()
log(
f"GET {path} from {peer}"
+ (" (If-None-Match)" if b"if-none-match" in req.lower() else "")
)
what = await respond(path, req, w)
await w.drain()
log(f" -> {path}: {what}")
except Exception as exc: # noqa: BLE001
log(f" !! {peer}: {exc!r}")
finally:
w.close()
async def main() -> None:
server = await asyncio.start_server(handle, "0.0.0.0", PORT)
log(f"listening on {PORT}")
async with server:
await server.serve_forever()
asyncio.run(main())
@@ -0,0 +1,123 @@
"""List the boards on USB, or wait for one to enumerate.
ports.py table: port, vid:pid, product, serial number, guess
ports.py --wait 2e8a print the first port with VID 0x2e8a once it appears (default 60 s)
ports.py --wait 1a86:7523 --timeout 30
ports.py --product "USB Serial" first port whose USB product string matches (case-insensitive)
ports.py --wait 2e8a --serial SN the port of one specific board, whatever name it comes back under
ports.py --serial-of PORT print the USB serial number behind a port name
Run with a Python that has pyserial (any ESPHome venv). Port names follow the hub port on macOS, so
re-run this after every replug instead of trusting a name from an earlier session.
"""
import argparse
import sys
import time
from serial.tools import list_ports
GUESS = [
(
(0x1A86, 0x7523),
"CH340: D1 mini, BW15 kit, BK7238/CB3S or LN882H adapter; identify by the boot banner",
),
((0x1A86, 0x55D3), "CH343: ESP32-P4 devkit console"),
((0x1A86, 0x55D4), "CH343: ESP32 devkit"),
((0x1A86, None), "WCH CH34x USB serial bridge"),
((0x10C4, 0xEA60), "CP2102: classic ESP32 devkit (esp32dev)"),
(
(0x303A, 0x1001),
"Espressif USB JTAG serial: S3/C3/C6/H2 native USB (logger hardware_uart USB_SERIAL_JTAG)",
),
(
(0x2E8A, None),
"Raspberry Pi Pico W / Pico 2 W CDC; the port vanishes and comes back on every reboot",
),
((0x1915, 0x521F), "nRF52840 Dongle (PCA10059) in the Open DFU bootloader"),
(
(0x2FE3, 0x0100),
"nRF52840 Dongle running a Zephyr app; two CDC ports, ESPHome log on the second",
),
((0x1915, 0xCAFE), "Makerdiary nRF52840 MDK dongle running a Zephyr app"),
((0x239A, None), "Adafruit style nRF52 board (UF2 bootloader family)"),
]
def guess(vid: int, pid: int) -> str:
for (gv, gp), text in GUESS:
if vid == gv and (gp is None or pid == gp):
return text
return ""
def parse_id(text: str) -> tuple[int, int | None]:
vid, _, pid = text.partition(":")
return int(vid, 16), (int(pid, 16) if pid else None)
def matches(
port, vid: int | None, pid: int | None, product: str | None, serial: str | None
) -> bool:
if vid is not None and (port.vid != vid or (pid is not None and port.pid != pid)):
return False
if product and (port.product or "").lower() != product.lower():
return False
return not serial or (port.serial_number or "") == serial
def main() -> int:
ap = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
)
ap.add_argument(
"--wait",
metavar="VID[:PID]",
help="block until a matching port appears, print it",
)
ap.add_argument("--product", help="USB product string to match")
ap.add_argument("--serial", help="USB serial number to match")
ap.add_argument(
"--serial-of", metavar="PORT", help="print the USB serial number of PORT"
)
ap.add_argument("--timeout", type=float, default=60)
args = ap.parse_args()
if args.serial_of:
for p in list_ports.comports():
if p.device == args.serial_of and p.serial_number:
print(p.serial_number)
return 0
print(f"no USB serial number for {args.serial_of}", file=sys.stderr)
return 1
vid = pid = None
if args.wait:
vid, pid = parse_id(args.wait)
if args.wait or args.product or args.serial:
deadline = time.time() + args.timeout
while True:
for p in sorted(list_ports.comports(), key=lambda p: p.device):
if p.vid and matches(p, vid, pid, args.product, args.serial):
print(p.device)
return 0
if time.time() >= deadline:
print("no matching port", file=sys.stderr)
return 1
time.sleep(1)
rows = [p for p in list_ports.comports() if p.vid]
if not rows:
print("no USB serial ports")
return 1
for p in sorted(rows, key=lambda p: p.device):
print(
f"{p.device:32} {p.vid:04x}:{p.pid:04x} {p.product or '?':34} "
f"{p.serial_number or '':18} {guess(p.vid, p.pid)}"
)
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,33 @@
"""Request one camera image over the native API and report its size (direct esp32_camera check)."""
import asyncio
import sys
from aioesphomeapi import APIClient
async def main():
c = APIClient(sys.argv[1], 6053, None)
await c.connect(login=True)
ents, _ = await c.list_entities_services()
cams = [e for e in ents if type(e).__name__ == "CameraInfo"]
print("camera entities:", [e.name for e in cams])
got = asyncio.get_event_loop().create_future()
def on_state(state):
if type(state).__name__ == "CameraState" and not got.done():
got.set_result(len(state.data))
c.subscribe_states(on_state)
c.request_single_image()
try:
n = await asyncio.wait_for(got, 10)
print("single image bytes:", n)
except TimeoutError:
print("no image within 10 s")
await c.disconnect()
sys.exit(1)
await c.disconnect()
asyncio.run(main())
@@ -0,0 +1,32 @@
#!/bin/zsh
# usage: measure.sh IP LABEL LOGFILE [OUTDIR]
# Runs the fixed RTSP measurement set against one build and appends a result line to OUTDIR/results.txt:
# ping RTT, two 12 s ffmpeg runs, two concurrent ffmpeg clients, and the raw receive rate with the default
# and a 1 MiB socket buffer. LOGFILE is the device capture, used to count "stalled" drops during the run.
# A measurement that produced nothing is written as ERR and the script exits 1, so a broken run never looks
# like a measured one. Needs ffmpeg and a python with the rtsp scripts' dependencies (PYTHON, default python3).
D=${0:A:h}
IP=$1; LABEL=$2; LOG=$3; S=${4:-.}
PY=${PYTHON:-python3}
[ -f "$LOG" ] || { echo "device capture not found: $LOG"; exit 1; }
rtt=$(ping -c 10 -i 0.2 $IP 2>&1 | tail -1 | awk -F/ '{print $5}')
stall0=$(grep -a -c "stalled" $LOG)
frames() { # OUTFILE RC: the frame count of a run that finished on its own, nothing for a failed or killed one
[ "$2" = 0 ] && grep -o 'frame= *[0-9]*' $1 | tail -1 | tr -dc 0-9
}
timeout 40 ffmpeg -hide_banner -rtsp_transport tcp -i rtsp://$IP:554/ -t 12 -f null - > $S/m-f1.txt 2>&1; f1=$(frames $S/m-f1.txt $?); sleep 2
timeout 40 ffmpeg -hide_banner -rtsp_transport tcp -i rtsp://$IP:554/ -t 12 -f null - > $S/m-f2.txt 2>&1; f2=$(frames $S/m-f2.txt $?); sleep 2
timeout 40 ffmpeg -hide_banner -rtsp_transport tcp -i rtsp://$IP:554/ -t 12 -f null - > $S/m-twoA.txt 2>&1 & A=$!; sleep 1
timeout 40 ffmpeg -hide_banner -rtsp_transport tcp -i rtsp://$IP:554/ -t 12 -f null - > $S/m-twoB.txt 2>&1 & B=$!
wait $A; ra=$?; wait $B; rb=$?
fa=$(frames $S/m-twoA.txt $ra); fb=$(frames $S/m-twoB.txt $rb); sleep 2
timeout 40 $PY $D/rawrate2.py $IP quiet > $S/m-raw.txt 2>&1 && raw=$(grep -o "[0-9.]* KiB/s" $S/m-raw.txt | head -1); sleep 2
timeout 40 $PY $D/rawrate2.py $IP bigbuf > $S/m-raw2.txt 2>&1 && raw2=$(grep -o "[0-9.]* KiB/s" $S/m-raw2.txt | head -1); sleep 1
stall1=$(grep -a -c "stalled" $LOG)
bad=0
for v in rtt f1 f2 fa fb raw raw2; do
if [ -z "${(P)v}" ]; then eval "$v=ERR"; bad=1; fi
done
line="$LABEL | rtt avg ${rtt} ms | ffmpeg single 12 s: ${f1}, ${f2} frames | two clients: ${fa} + ${fb} | python raw: ${raw} (1 MiB rcvbuf ${raw2}) | stalls logged: $((stall1 - stall0))"
echo "$line" | tee -a $S/results.txt
if [ $bad = 1 ]; then echo "one or more measurements failed (ERR above)"; exit 1; fi
@@ -0,0 +1,83 @@
import contextlib
import socket
import sys
import time
IP = sys.argv[1]
mode = sys.argv[2]
secs = 8
s = socket.create_connection((IP, 554), timeout=5)
if mode == "bigbuf":
s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1 << 20)
cseq = 0
buf = b""
def read_response() -> bytes:
"""One complete RTSP response: headers through the blank line plus a Content-Length body."""
global buf # noqa: PLW0603
while (end := buf.find(b"\r\n\r\n")) < 0:
chunk = s.recv(4096)
if not chunk:
raise ConnectionError("closed")
buf += chunk
head = buf[:end].decode(errors="replace")
body_len = 0
for line in head.split("\r\n"):
if line.lower().startswith("content-length:"):
body_len = int(line.split(":", 1)[1])
while len(buf) < end + 4 + body_len:
chunk = s.recv(4096)
if not chunk:
raise ConnectionError("closed")
buf += chunk
resp, buf = buf[: end + 4 + body_len], buf[end + 4 + body_len :]
return resp
def req(m, extra="", wait=True):
global cseq # noqa: PLW0603
cseq += 1
url = f"rtsp://{IP}:554/trackID=0" if m == "SETUP" else f"rtsp://{IP}:554/"
s.sendall(f"{m} {url} RTSP/1.0\r\nCSeq: {cseq}\r\n{extra}\r\n".encode())
return read_response() if wait else b""
req("OPTIONS")
req("DESCRIBE", "Accept: application/sdp\r\n")
r = req("SETUP", "Transport: RTP/AVP/TCP;unicast;interleaved=0-1\r\n")
sess = (
[x for x in r.decode(errors="replace").split("\r\n") if x.startswith("Session")][0]
.split()[1]
.split(";")[0]
)
req("PLAY", f"Session: {sess}\r\n")
t0 = time.monotonic()
total = 0
chunks = 0
last_chat = t0
s.settimeout(2)
while time.monotonic() - t0 < secs:
now = time.monotonic()
if mode == "chatty" and now - last_chat > 0.2:
req("GET_PARAMETER", f"Session: {sess}\r\n", wait=False)
last_chat = now
if mode == "rtcp" and now - last_chat > 0.2:
s.sendall(b"$\x01\x00\x08" + b"\x80\xc9\x00\x01\x00\x00\x00\x01")
last_chat = now
try:
d = s.recv(65536)
except TimeoutError:
continue
if not d:
break
total += len(d)
chunks += 1
print(
f"{mode:7s}: {total / 1024 / secs:5.1f} KiB/s, {chunks} recvs, avg {total // max(chunks, 1)} B/recv"
)
with contextlib.suppress(OSError):
req("TEARDOWN", f"Session: {sess}\r\n")
s.close()
@@ -0,0 +1,345 @@
"""Raw RTSP/TCP client for the esphome rtsp_server hardware test.
usage: rtsp_client.py HOST [case ...]
cases: play, udp, pipeline, third, stall, teardown, getparam, badreq, replay, timeout
"""
import socket
import struct
import sys
import time
HOST = sys.argv[1]
PORT = 554
cases = sys.argv[2:] or [
"play",
"udp",
"pipeline",
"badreq",
"replay",
"getparam",
"teardown",
"third",
"stall",
]
results = []
def say(case, ok, detail):
results.append((case, ok, detail))
print(f"{'PASS' if ok else 'FAIL'} {case}: {detail}", flush=True)
class Client:
def __init__(self, timeout=5):
self.s = socket.create_connection((HOST, PORT), timeout=timeout)
self.cseq = 0
self.session = None
self.buf = b""
def send(self, method, extra="", url=None):
self.cseq += 1
url = (
url or f"rtsp://{HOST}:{PORT}/trackID=0"
if method == "SETUP"
else (url or f"rtsp://{HOST}:{PORT}/")
)
req = f"{method} {url} RTSP/1.0\r\nCSeq: {self.cseq}\r\n"
if self.session:
req += f"Session: {self.session}\r\n"
req += extra + "\r\n"
self.s.sendall(req.encode())
return self.cseq
def _recv(self):
chunk = self.s.recv(4096)
if not chunk:
raise ConnectionError("closed")
self.buf += chunk
def read_response(self):
# Read until a full RTSP response (headers + optional body); skip any interleaved data in between
while True:
if self.buf.startswith(b"$"):
if len(self.buf) < 4:
self._recv()
continue
ln = struct.unpack(">H", self.buf[2:4])[0]
if len(self.buf) < 4 + ln:
self._recv()
continue
self.buf = self.buf[4 + ln :]
continue
end = self.buf.find(b"\r\n\r\n")
if end < 0:
self._recv()
continue
head = self.buf[:end].decode(errors="replace")
body_len = 0
for line in head.split("\r\n"):
if line.lower().startswith("content-length:"):
body_len = int(line.split(":", 1)[1])
while len(self.buf) < end + 4 + body_len:
self._recv()
body = self.buf[end + 4 : end + 4 + body_len]
self.buf = self.buf[end + 4 + body_len :]
status = int(head.split(" ", 2)[1])
for line in head.split("\r\n"):
if line.lower().startswith("session:"):
self.session = line.split(":", 1)[1].strip().split(";")[0]
return status, head, body
def read_frames(self, seconds):
"""Collect interleaved RTP packets on channel 0 for `seconds`; return (frames, packets, seq_gaps, sizes)."""
deadline = time.monotonic() + seconds
frames = 0
packets = 0
gaps = 0
last_seq = None
cur = 0
sizes = []
self.s.settimeout(1.0)
while time.monotonic() < deadline:
try:
chunk = self.s.recv(65536)
except TimeoutError:
continue
if not chunk:
break
self.buf += chunk
while True:
if len(self.buf) < 4 or self.buf[0:1] != b"$":
if self.buf and self.buf[0:1] != b"$":
end = self.buf.find(b"\r\n\r\n")
if end < 0:
break
self.buf = self.buf[end + 4 :]
continue
break
ch = self.buf[1]
ln = struct.unpack(">H", self.buf[2:4])[0]
if len(self.buf) < 4 + ln:
break
pkt = self.buf[4 : 4 + ln]
self.buf = self.buf[4 + ln :]
if ch != 0 or len(pkt) < 12:
continue
packets += 1
seq = struct.unpack(">H", pkt[2:4])[0]
marker = pkt[1] & 0x80
if last_seq is not None and seq != ((last_seq + 1) & 0xFFFF):
gaps += 1
last_seq = seq
cur += len(pkt) - 12
if marker:
frames += 1
sizes.append(cur)
cur = 0
return frames, packets, gaps, sizes
def close(self):
self.s.close()
def setup_play(c):
c.send("OPTIONS")
st, head, _ = c.read_response()
assert st == 200, head
c.send("DESCRIBE", "Accept: application/sdp\r\n")
st, head, body = c.read_response()
assert st == 200, head
c.send("SETUP", "Transport: RTP/AVP/TCP;unicast;interleaved=0-1\r\n")
st, head, _ = c.read_response()
assert st == 200, head
c.send("PLAY")
st, head, _ = c.read_response()
assert st == 200, head
return body
for case in cases:
try:
if case == "play":
c = Client()
sdp = setup_play(c)
frames, packets, gaps, sizes = c.read_frames(10)
c.send("TEARDOWN")
c.read_response()
c.close()
ok = frames >= 50 and gaps == 0
say(
case,
ok,
f"{frames} frames in 10 s ({frames / 10:.1f} fps), {packets} RTP packets, {gaps} seq gaps, frame size min {min(sizes) if sizes else 0} max {max(sizes) if sizes else 0}; sdp has m=video {'m=video' in sdp.decode()}",
)
elif case == "udp":
c = Client()
c.send("OPTIONS")
c.read_response()
c.send("SETUP", "Transport: RTP/AVP;unicast;client_port=5000-5001\r\n")
st, head, _ = c.read_response()
c.close()
say(case, st == 461, f"UDP SETUP answered {st}")
elif case == "pipeline":
c = Client()
c.s.sendall(
f"OPTIONS rtsp://{HOST}/ RTSP/1.0\r\nCSeq: 1\r\n\r\nDESCRIBE rtsp://{HOST}/ RTSP/1.0\r\nCSeq: 2\r\nAccept: application/sdp\r\n\r\n".encode()
)
c.cseq = 2
st1, h1, _ = c.read_response()
st2, h2, b2 = c.read_response()
c.close()
cs = [x for x in (h1 + h2).split("\r\n") if x.startswith("CSeq")]
say(
case,
st1 == 200 and st2 == 200 and cs == ["CSeq: 1", "CSeq: 2"],
f"two pipelined requests answered {st1}, {st2} with {cs}",
)
elif case == "badreq":
c = Client()
c.s.sendall(b"BOGUS rtsp://x/ RTSP/1.0\r\nCSeq: 7\r\n\r\n")
st, head, _ = c.read_response()
c.close()
say(
case,
st in (400, 405, 501) and "CSeq: 7" in head,
f"unknown method answered {st}, CSeq echoed {'CSeq: 7' in head}",
)
elif case == "replay":
c = Client()
setup_play(c)
f1, _, _, _ = c.read_frames(3)
c.send("PLAY")
st, head, _ = c.read_response()
f2, _, gaps, _ = c.read_frames(3)
c.send("TEARDOWN")
c.read_response()
c.close()
say(
case,
st == 200 and f2 >= 10 and gaps == 0,
f"second PLAY answered {st}, {f1} then {f2} frames, {gaps} seq gaps after repeat PLAY",
)
elif case == "getparam":
c = Client()
setup_play(c)
c.read_frames(1)
c.send("GET_PARAMETER")
st, head, _ = c.read_response()
f, _, _, _ = c.read_frames(2)
c.send("TEARDOWN")
c.read_response()
c.close()
say(
case,
st == 200 and f > 5,
f"GET_PARAMETER while playing answered {st}, stream continued with {f} frames in 2 s",
)
elif case == "teardown":
c = Client()
setup_play(c)
c.read_frames(2)
c.send("TEARDOWN")
st, head, _ = c.read_response()
c.s.settimeout(3)
time.sleep(1.5)
try:
rest = c.s.recv(65536)
closed = rest == b""
except TimeoutError:
rest = b"x"
closed = False
except ConnectionError:
closed = True
c.close()
say(
case,
st == 200,
f"TEARDOWN answered {st}; connection closed by server afterwards: {closed}",
)
elif case == "third":
a = Client()
setup_play(a)
b = Client()
setup_play(b)
fa, _, ga, _ = a.read_frames(4)
fb, _, gb, _ = b.read_frames(4)
try:
d = Client(timeout=3)
d.send("OPTIONS")
st, _, _ = d.read_response()
third = f"third got {st}"
d.close()
except ConnectionError as e:
# refused, reset or closed by the server; a timeout or a bug propagates and fails the case
third = f"third rejected ({type(e).__name__})"
a.send("TEARDOWN")
a.read_response()
a.close()
b.send("TEARDOWN")
b.read_response()
b.close()
say(
case,
fa > 10 and fb > 10 and ga == 0 and gb == 0 and "rejected" in third,
f"two clients: {fa} and {fb} frames in 4 s each, gaps {ga}/{gb}; {third}",
)
elif case == "stall":
a = Client()
setup_play(a)
b = Client()
setup_play(b)
# stop reading on a for 4 s while b keeps playing
a.s.settimeout(None)
fb1, _, gb1, _ = b.read_frames(4)
a.s.settimeout(1.0)
fa, _, ga, _ = a.read_frames(4)
fb2, _, gb2, _ = b.read_frames(3)
a.send("TEARDOWN")
a.read_response()
a.close()
b.send("TEARDOWN")
b.read_response()
b.close()
say(
case,
fb1 > 10 and fb2 > 10 and fa > 5,
f"b kept {fb1} then {fb2} frames while a stalled 4 s; a recovered {fa} frames after resuming (gaps {ga}); b gaps {gb1}/{gb2}; device log must show stalled/dropping frame",
)
elif case == "timeout":
c = Client(timeout=70)
setup_play(c)
c.s.settimeout(
2
) # a server that stops sending but never closes must not hang the run
t0 = time.monotonic()
dead = False
try:
while time.monotonic() - t0 < 68:
try:
d = c.s.recv(65536)
except TimeoutError:
continue
if not d:
dead = True
break
except ConnectionError:
dead = True
elapsed = time.monotonic() - t0
say(
case,
dead and 50 <= elapsed <= 68,
f"connection closed by server after idle: {dead} at {elapsed:.0f} s (expect the 60 s session timeout when the client sends nothing)",
)
except Exception as e: # noqa: BLE001
say(case, False, f"exception {type(e).__name__}: {e}")
time.sleep(1)
print(
"SUMMARY",
sum(1 for r in results if r[1]),
"pass",
sum(1 for r in results if not r[1]),
"fail",
)
sys.exit(1 if any(not r[1] for r in results) else 0)
@@ -0,0 +1,165 @@
"""Bounded, timestamped serial capture that always exits and closes the port.
serial_capture.py PORT SECONDS OUTFILE [--baud 115200] [--released] [--reset] [--append] [--ms]
default open with DTR/RTS as pyserial leaves them (asserted); fine for ESP devkits and the Pico.
--released drive DTR and RTS low before opening: the BW15 and BK7238 CH340 kits strap the boot pin
(DTR) and reset (RTS), so a plain open holds the chip in reset.
--reset pulse reset after opening (DTR low, RTS high 100 ms, RTS low) to capture a fresh boot.
--baud 115200 by default; 74880 shows the ESP8266 ROM boot messages.
--append keep what OUTFILE already holds; by default it is truncated so counts cover this run only.
Exit status 1 when nothing was received (wrong port name, or a port another capture still holds).
Text ESPHome marks as secret (LOG_SECRET, the Wi-Fi password) is written as <secret>.
Lines are written as [HH:MM:SS]text with ANSI colour stripped and flushed one by one, so a shell
loop can grep the file while the capture runs. If the port is missing or disappears (USB CDC boards
re-enumerate on reboot) the script retries until the deadline, and follows the board by its USB serial
number when it comes back under another device path.
Never run an open-ended serial session: every capture has a deadline, and a flash on the same port
needs the capture stopped first (pkill -f "[s]erial_capture.py" does not match the killing shell).
"""
import argparse
import contextlib
import pathlib
import re
import sys
import time
import serial
from serial.tools import list_ports
ANSI = re.compile(rb"\x1b\[[0-9;]*[A-Za-z]")
# ESPHome's LOG_SECRET span (a Wi-Fi password); a span cut off before its end marker is secret to the end
SECRET = re.compile(rb"\x1b\[8m.*?(?:\x1b\[28m|$)", re.DOTALL)
PORT_ERRORS = (serial.SerialException, OSError)
def identify(port: str) -> tuple[str | None, int]:
"""USB serial number of the port and its position among that device's ports (a dongle has two)."""
ports = sorted(list_ports.comports(), key=lambda p: p.device)
serial_number = next((p.serial_number for p in ports if p.device == port), None)
siblings = [
p.device for p in ports if serial_number and p.serial_number == serial_number
]
return serial_number, siblings.index(port) if port in siblings else 0
def current_path(port: str, serial_number: str | None, index: int) -> str:
"""The port's current device path: a USB CDC board can come back under a new name after a reboot."""
if serial_number:
ports = sorted(list_ports.comports(), key=lambda p: p.device)
siblings = [p.device for p in ports if p.serial_number == serial_number]
if index < len(siblings):
return siblings[index]
return port
def open_port(args: argparse.Namespace, path: str) -> serial.Serial:
s = serial.Serial(port=None, baudrate=args.baud, timeout=0.2)
s.port = path
if args.released:
s.dtr = False
s.rts = False
s.open()
if args.reset:
try:
s.dtr = False
s.rts = True
time.sleep(0.1)
s.rts = False
s.reset_input_buffer()
except PORT_ERRORS:
s.close()
raise
return s
def write_line(out, tag: bytes, data: bytes, ms: bool = False) -> None:
if ms:
now = time.time()
stamp = (
time.strftime("[%H:%M:%S", time.localtime(now))
+ f".{int(now * 1000) % 1000:03d}]"
).encode()
else:
stamp = time.strftime("[%H:%M:%S]").encode()
out.write(stamp + tag + ANSI.sub(b"", SECRET.sub(b"<secret>", data)) + b"\n")
out.flush()
def note(out, text: str, ms: bool = False) -> None:
write_line(out, b"[capture] ", text.encode(), ms)
def capture(args: argparse.Namespace, out) -> int:
"""Returns the number of bytes received."""
deadline = time.time() + args.seconds
buf = b""
s = None
received = 0
serial_number, index = None, 0
while time.time() < deadline:
if s is None:
path = current_path(args.port, serial_number, index)
try:
s = open_port(args, path)
args.reset = False # only reset on the first open
if serial_number is None:
serial_number, index = identify(path)
if path != args.port:
note(out, f"reopened as {path}", args.ms)
except PORT_ERRORS as err:
note(out, f"open failed: {err}", args.ms)
time.sleep(1)
continue
try:
data = s.read(4096)
except PORT_ERRORS as err:
if buf: # keep the cut-off line apart from the next boot's first line
write_line(out, b"[partial]", buf, args.ms)
buf = b""
note(out, f"port lost: {err}", args.ms)
with contextlib.suppress(*PORT_ERRORS):
s.close()
s = None
time.sleep(1)
continue
if not data:
continue
received += len(data)
buf += data
while b"\n" in buf:
line, buf = buf.split(b"\n", 1)
write_line(out, b"", line.rstrip(b"\r"), args.ms)
if buf:
write_line(out, b"[partial]", buf, args.ms)
if s is not None:
s.close()
return received
def main() -> int:
ap = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
)
ap.add_argument("port")
ap.add_argument("seconds", type=float)
ap.add_argument("outfile")
ap.add_argument("--baud", type=int, default=115200)
ap.add_argument("--released", action="store_true")
ap.add_argument("--reset", action="store_true")
ap.add_argument("--append", action="store_true")
ap.add_argument("--ms", action="store_true", help="millisecond timestamps")
args = ap.parse_args()
with pathlib.Path(args.outfile).open("ab" if args.append else "wb") as out:
received = capture(args, out)
if received == 0:
print(f"[capture] nothing received from {args.port}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,30 @@
"""Play a URL on the speaker_source media player over the API (no sync task involved).
usage: playurl.py HOST KEY URL [PLAYER_NAME]
"""
import asyncio
import sys
from aioesphomeapi import APIClient, MediaPlayerInfo
HOST, KEY, URL = sys.argv[1], sys.argv[2], sys.argv[3]
PLAYER = sys.argv[4] if len(sys.argv) > 4 else "Player"
async def main() -> None:
c = APIClient(HOST, 6053, None, noise_psk=KEY)
await c.connect(login=True)
ents, _ = await c.list_entities_services()
players = {e.name: e for e in ents if isinstance(e, MediaPlayerInfo)}
print("media players:", sorted(players))
if PLAYER not in players:
sys.exit(f"no media player named {PLAYER!r}; have {sorted(players)}")
p = players[PLAYER]
c.media_player_command(p.key, media_url=URL)
print("sent play_media to", PLAYER)
await asyncio.sleep(1)
await c.disconnect()
asyncio.run(main())
@@ -0,0 +1,13 @@
#pragma once
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include "esphome/core/log.h"
// Direct look at the Zephyr cdc_acm_uart1 driver state, bypassing the usb_cdc_acm component
inline void cdc_probe() {
const device *d = DEVICE_DT_GET(DT_NODELABEL(cdc_acm_uart1));
uint8_t tmp[32];
int rr = uart_irq_rx_ready(d);
int n = uart_fifo_read(d, tmp, sizeof(tmp));
ESP_LOGI("probe", "driver rx_ready=%d fifo_read=%d first=0x%02X", rr, n, n > 0 ? tmp[0] : 0);
}
@@ -0,0 +1,203 @@
"""Drive the usb_cdc_acm echo firmware on the nRF52840 dongle from the Mac.
usage: drive.py DATA_PORT [case ...]
Cases: short, bulk1k, bulk16k, bulk64k, burst, flush, peek, linecoding, dtr, stream, counters
"""
import random
import sys
import time
import serial
port = sys.argv[1]
cases = sys.argv[2:] or [
"short",
"bulk1k",
"bulk16k",
"bulk64k",
"burst",
"flush",
"peek",
"linecoding",
"dtr",
"stream",
"counters",
]
results = []
def say(case, ok, detail):
results.append((case, ok, detail))
print(f"{'PASS' if ok else 'FAIL'} {case}: {detail}", flush=True)
def info(case, detail):
"""A case whose result lives in the device log; printed, not counted."""
print(f"INFO {case}: {detail}", flush=True)
def open_port(baud=115200):
s = serial.Serial(port, baudrate=baud, timeout=1)
time.sleep(0.3) # let the 100 ms line-state poll see DTR and the echo loop settle
return s
def read_exact(s, n, timeout):
buf = bytearray()
deadline = time.monotonic() + timeout
while len(buf) < n and time.monotonic() < deadline:
chunk = s.read(min(4096, n - len(buf)))
if chunk:
buf += chunk
return bytes(buf)
def payload(n):
# No 0x01 anywhere: the device treats a chunk starting with 0x01 as a command
rnd = random.Random(n)
return bytes(rnd.choice(range(2, 256)) for _ in range(n))
def cmd(s, c, extra=b""):
s.write(b"\x01" + c + extra)
s.flush()
def echo_case(name, n, chunk):
s = open_port()
s.reset_input_buffer()
data = payload(n)
t0 = time.monotonic()
for i in range(0, n, chunk):
s.write(data[i : i + chunk])
got = read_exact(s, n, timeout=max(5, n / 2000))
dt = time.monotonic() - t0
s.close()
ok = got == data
first_bad = next((i for i in range(min(len(got), n)) if got[i] != data[i]), None)
say(
name,
ok,
f"{len(got)}/{n} bytes echoed in {dt:.2f}s ({n / dt / 1024:.1f} KiB/s), first mismatch {first_bad}",
)
for case in cases:
if case == "short":
s = open_port()
s.reset_input_buffer()
ok = True
lat = []
for i in range(20):
msg = f"hello {i}\n".encode()
t0 = time.monotonic()
s.write(msg)
got = read_exact(s, len(msg), 2)
lat.append(time.monotonic() - t0)
ok = ok and got == msg
s.close()
say(
case,
ok,
f"20 lines, latency min {min(lat) * 1000:.0f} ms max {max(lat) * 1000:.0f} ms",
)
elif case == "bulk1k":
echo_case(case, 1024, 1024)
elif case == "bulk16k":
echo_case(case, 16 * 1024, 4096)
elif case == "bulk64k":
echo_case(case, 64 * 1024, 16384)
elif case == "burst":
s = open_port()
s.reset_input_buffer()
cmd(s, b"B")
got = read_exact(s, 1000, 3)
s.close()
expect = bytes((ord("A") + (i % 26)) for i in range(1000))
prefix_ok = got == expect[: len(got)]
say(
case,
prefix_ok and 0 < len(got) < 1000,
f"{len(got)} of 1000 bytes arrived (256 byte TX ring, rest must be dropped and warned), prefix intact {prefix_ok}",
)
elif case == "flush":
s = open_port()
s.reset_input_buffer()
cmd(s, b"F")
got = read_exact(s, 200, 3)
s.close()
expect = bytes((ord("a") + (i % 26)) for i in range(200))
say(case, got == expect, f"{len(got)}/200 bytes, flush result in device log")
time.sleep(0.5)
elif case == "peek":
s = open_port()
s.reset_input_buffer()
cmd(s, b"K", b"Z")
time.sleep(0.5)
s.close()
info(
case,
"peek/read result is in the device log: expect `peek ok=1 0x5A read ok=1 0x5A`",
)
elif case == "linecoding":
for baud in (9600, 115200, 921600):
s = open_port(baud)
s.reset_input_buffer()
cmd(s, b"L")
time.sleep(0.4)
s.close()
time.sleep(0.3)
info(case, "opened at 9600, 115200, 921600; the device log must show each baud")
elif case == "dtr":
for _i in range(5):
s = open_port()
time.sleep(0.3)
s.close()
time.sleep(0.3)
info(
case, "5 open/close cycles; the device log must show 5 Opened and 5 Closed"
)
elif case == "stream":
# device streams 100 x 100 byte lines over 10 s; close the port for 3 s in the middle
s = open_port()
s.reset_input_buffer()
cmd(s, b"S")
part1 = bytearray()
t0 = time.monotonic()
while time.monotonic() - t0 < 2.5:
part1 += s.read(4096)
s.close()
time.sleep(3.0)
s = open_port()
part2 = bytearray()
t0 = time.monotonic()
while time.monotonic() - t0 < 6.0:
part2 += s.read(4096)
s.close()
lines1 = [x for x in bytes(part1).split(b"\n") if x.startswith(b"STREAM")]
lines2 = [x for x in bytes(part2).split(b"\n") if x.startswith(b"STREAM")]
n1 = [int(x[7:10]) for x in lines1]
n2 = [int(x[7:10]) for x in lines2]
resumed = bool(n2) and max(n2) >= 90
say(
case,
resumed,
f"before close: lines {min(n1) if n1 else None}-{max(n1) if n1 else None} ({len(n1)}), after reopen: {min(n2) if n2 else None}-{max(n2) if n2 else None} ({len(n2)}); streaming resumed after reopen {resumed}",
)
elif case == "counters":
s = open_port()
cmd(s, b"C")
time.sleep(0.3)
s.close()
info(case, "counters are in the device log")
time.sleep(0.5)
print(
"SUMMARY",
sum(1 for r in results if r[1]),
"pass",
sum(1 for r in results if not r[1]),
"fail",
)
sys.exit(1 if any(not r[1] for r in results) else 0)
+133
View File
@@ -0,0 +1,133 @@
#!/bin/bash
# wifisave_hammer.sh: the device under test (DUT) falls back to its AP; a client board joins it and posts
# /wifisave repeatedly, then the real credentials. Compiles, flashes, captures both serial logs and
# prints counts. Configs come from templates/example-dut.yaml and templates/example-client.yaml.
#
# Usage: wifisave_hammer.sh DUT_KIND DUT_PORT DUT_YAML DUT_TREE CLIENT_PORT CLIENT_YAML CLIENT_TREE [SETTLE_S]
# DUT_KIND esp (esphome upload), pico (upload, then re-find the CDC port by USB serial number),
# bw15 (flash_rtl87xx_kit.py) or bk (flash_bk72xx.py)
# *_YAML absolute path; the file name without .yaml must equal the config's `name`
# *_TREE checkout or worktree whose esphome code must build that yaml (cwd for python -m esphome)
# SETTLE_S seconds to keep capturing after the client reports the real save (default 40; BW15 wants 90)
# Env: PY (python with esphome; default DUT_TREE/venv/bin/python, else python3 on PATH; an explicit PY must exist), LTPY
# (LibreTiny penv python with ltchiptool; default ~/.platformio/penv/.libretiny/bin/python)
# Output next to each yaml: NAME-compile.log, NAME-upload.log, NAME-serial.log, NAME-capture.err, and DUT NAME-events.txt
# (addresses redacted so the extract can go on a PR; the serial logs themselves stay local).
# Exit status: 0 RESULT PASS (no crash marker or reboot after the AP came up, the device joined the network,
# and real stimulus: saves answered and a cooldown exit); 3 RESULT FAIL (the counts show
# a failure); 2 RESULT INVALID (a precondition or the stimulus was missed, or a capture did not last the run);
# 1 when a build, flash or port step fails (nothing is reported).
set -u
SKILL=$(cd "$(dirname "$0")/.." && pwd)
KIND=$1; DPORT=$2; DYAML=$3; DTREE=$4; CPORT=$5; CYAML=$6; CTREE=$7; SETTLE=${8:-40}
if [ -n "${PY:-}" ]; then [ -x "$PY" ] || { echo "[hammer] FAILED: PY=$PY is not executable"; exit 1; }
else PY=$DTREE/venv/bin/python; [ -x "$PY" ] || PY=$(command -v python3) || { echo "[hammer] FAILED: no python found, set PY"; exit 1; }; fi
LTPY=${LTPY:-$HOME/.platformio/penv/.libretiny/bin/python}
DDIR=$(dirname "$DYAML"); DNAME=$(basename "$DYAML" .yaml)
CDIR=$(dirname "$CYAML"); CNAME=$(basename "$CYAML" .yaml)
DLOG=$DDIR/$DNAME-serial.log; CLOG=$CDIR/$CNAME-serial.log
INVALID=""
AP_WAIT=120; UPLOAD_WAIT=300; SAVE_WAIT=240
# Crash signatures (extended regex), not bare words: "aborting connection attempt" is a normal Wi-Fi retry line
CRASH_RE='Panic |abort\(\)|Fatal exception|Exception \(|Guru Meditation|HardFault|Bus Fault|Backtrace:|Soft WDT reset|wdt reset|Brownout|rst cause:[24]'
# Boot banners and port losses are counted apart: a Pico reboot shows as a port loss (and maybe a banner too),
# while on a UART board a port loss comes from the adapter or the hub, not the chip
BANNER_RE='Log initialized'; LOSS_RE='\[capture\] port lost'
DUT_CAPTURE=$((AP_WAIT + UPLOAD_WAIT + SAVE_WAIT + SETTLE + 60)); CLIENT_CAPTURE=$((SAVE_WAIT + SETTLE + 60))
bound() { local t=$1; shift; if command -v timeout >/dev/null; then timeout "$t" "$@"; else perl -e 'alarm shift; exec @ARGV' "$t" "$@"; fi; }
count() { LC_ALL=C grep -a -c "$1" "$2"; }
wait_for() { # wait_for FILE PATTERN SECONDS -> prints elapsed, status 1 on timeout
local n=0; until LC_ALL=C grep -a -q "$2" "$1"; do sleep 2; n=$((n+2)); [ $n -ge "$3" ] && { echo "$n"; return 1; }; done; echo "$n"; }
# Only this run's captures: another capture on the host (a soak on a third board) must survive
stop_tails() { pkill -f "[s]erial_capture.py $DPORT " 2>/dev/null; pkill -f "[s]erial_capture.py $CPORT " 2>/dev/null; sleep 1; }
fail() { echo "[hammer] FAILED: $1"; stop_tails; exit 1; }
port_never_opened() { # an open failure and no line that is not a capture note: the port, not the firmware
LC_ALL=C grep -a -q '\[capture\] open failed' "$1" && ! LC_ALL=C grep -a -q -v '^\[[0-9:]*\]\[capture\]' "$1"; }
compile() { # compile YAML TREE
(cd "$2" && PYTHONUNBUFFERED=1 bound 1500 "$PY" -m esphome compile "$1" > "${1%.yaml}-compile.log" 2>&1); local rc=$?
echo "[hammer] compile $(basename "$1"): rc=$rc"; return $rc; }
esp_upload() { # esp_upload YAML PORT TREE
(cd "$3" && bound $UPLOAD_WAIT "$PY" -m esphome upload "$1" --device "$2" > "${1%.yaml}-upload.log" 2>&1)
local ok; ok=$(count 'Successfully uploaded' "${1%.yaml}-upload.log"); echo "[hammer] upload $(basename "$1"): ok=$ok"; [ "$ok" -ge 1 ]; }
stop_tails
compile "$DYAML" "$DTREE" || fail "DUT build failed, see $DDIR/$DNAME-compile.log"
compile "$CYAML" "$CTREE" || fail "client build failed, see $CDIR/$CNAME-compile.log"
TAILOPT=""
case $KIND in
esp) esp_upload "$DYAML" "$DPORT" "$DTREE" || fail "DUT upload failed" ;;
pico)
SN=$("$PY" "$SKILL/scripts/ports.py" --serial-of "$DPORT") || fail "no USB serial number for $DPORT"
esp_upload "$DYAML" "$DPORT" "$DTREE" || fail "DUT upload failed"
# The Pico drops its CDC port on reboot and may already be back: take whatever path this board has now;
# the capture follows the serial number if the path changes again
DPORT=$("$PY" "$SKILL/scripts/ports.py" --wait 2e8a --serial "$SN" --timeout 60) || fail "pico did not re-enumerate"
echo "[hammer] pico port after upload: $DPORT"; sleep 2 ;;
bw15|bk)
UF2=$(ls "$DDIR/.esphome/build/$DNAME/.pioenvs/$DNAME/"*.uf2 2>/dev/null | head -1)
[ -n "$UF2" ] || fail "no uf2 under $DDIR/.esphome/build/$DNAME"
echo "[hammer] uf2: ${UF2##*/}"
W=$SKILL/scripts/flash_rtl87xx_kit.py; [ "$KIND" = bk ] && W=$SKILL/scripts/flash_bk72xx.py
(cd "$DDIR" && bound 400 "$LTPY" "$W" flash write -d "$DPORT" "$UF2" > "$DDIR/$DNAME-upload.log" 2>&1); rc=$?
echo "[hammer] $KIND flash: rc=$rc"; [ $rc -eq 0 ] || fail "DUT flash failed, see $DDIR/$DNAME-upload.log"
TAILOPT="--released" ;;
*) echo "unknown DUT_KIND $KIND"; exit 1 ;;
esac
: > "$DLOG"; ("$PY" "$SKILL/scripts/serial_capture.py" "$DPORT" $DUT_CAPTURE "$DLOG" $TAILOPT 2> "${DLOG%-serial.log}-capture.err" &)
if ! n=$(wait_for "$DLOG" "Captive portal\|Starting fallback AP" $AP_WAIT); then
[ -s "$DLOG" ] || fail "the DUT capture produced nothing (the capture itself failed), see ${DLOG%-serial.log}-capture.err"
port_never_opened "$DLOG" && fail "could not open $DPORT (held by another process, or the wrong port), see $DLOG"
fail "DUT never raised its AP within ${n}s, see $DLOG"
fi
echo "[hammer] DUT fallback AP after ${n}s"; sleep 2
# reboots are counted from here; the first banner may predate the capture
BANNER_BASE=$(count "$BANNER_RE" "$DLOG"); LOSS_BASE=$(count "$LOSS_RE" "$DLOG")
esp_upload "$CYAML" "$CPORT" "$CTREE" || fail "client upload failed"
: > "$CLOG"; ("$PY" "$SKILL/scripts/serial_capture.py" "$CPORT" $CLIENT_CAPTURE "$CLOG" 2> "${CLOG%-serial.log}-capture.err" &)
if n=$(wait_for "$CLOG" "real save status\|posting the real" $SAVE_WAIT); then
echo "[hammer] client posted the real credentials after ${n}s; settling ${SETTLE}s"; sleep "$SETTLE"
else
[ -s "$CLOG" ] || fail "the client capture produced nothing (the capture itself failed), see ${CLOG%-serial.log}-capture.err"
port_never_opened "$CLOG" && fail "could not open $CPORT (held by another process, or the wrong port), see $CLOG"
INVALID="client never reached the real save within ${n}s"; echo "[hammer] WARNING: $INVALID"
fi
# A verdict needs complete evidence: both captures must have lasted the run and logged no error of their own
for spec in "$DPORT:${DLOG%-serial.log}-capture.err" "$CPORT:${CLOG%-serial.log}-capture.err"; do
port=${spec%%:*}; err=${spec#*:}
pgrep -f "[s]erial_capture.py $port " >/dev/null || INVALID="${INVALID:+$INVALID; }the capture on $port ended before the run did, see $err"
[ -s "$err" ] && INVALID="${INVALID:+$INVALID; }the capture on $port reported an error, see $err"
done
CRASHES=$(LC_ALL=C grep -a -c -E "$CRASH_RE" "$DLOG")
BANNERS=$(( $(count "$BANNER_RE" "$DLOG") - BANNER_BASE )); LOSSES=$(( $(count "$LOSS_RE" "$DLOG") - LOSS_BASE ))
if [ "$KIND" = pico ]; then
REBOOTS=$BANNERS; [ "$LOSSES" -gt "$REBOOTS" ] && REBOOTS=$LOSSES # one reboot can show as both, so take the larger
else
REBOOTS=$BANNERS; [ "$LOSSES" -gt 0 ] && INVALID="${INVALID:+$INVALID; }the DUT port dropped $LOSSES time(s) mid-run (adapter or hub, not the chip)"
fi
STA=$(LC_ALL=C grep -a 'IP Address:' "$DLOG" | grep -v -c '192\.168\.4\.'); REAL=$(LC_ALL=C grep -a -o 'real save status [-0-9]*' "$CLOG" | tail -1 | awk '{print $4}')
SAVES=$(count 'Requested WiFi Settings Change' "$DLOG"); EXITS=$(count 'cooldown early' "$DLOG"); ANSWERED=$(count 'bogus save status 200' "$CLOG")
echo "[hammer] $DNAME: saves requested $SAVES, cooldown exits $EXITS, crash markers $CRASHES, after the AP came up: boot banners $BANNERS, port losses $LOSSES, reboots $REBOOTS; STA addresses $STA"
FAILED=$(count 'bogus save status -1' "$CLOG")
echo "[hammer] $CNAME: bogus saves answered $ANSWERED, failed $FAILED, real save status ${REAL:-none}"
# Only whole messages, matched where the message starts, so a credential line or an odd SSID never gets in
LC_ALL=C grep -a -E "^\[[0-9:]+\](\[[A-Z]\]\[[a-z_]+:[0-9]+\]: *(Exiting cooldown early|Starting fallback AP|Captive portal|Requested WiFi Settings Change|Retry phase|Connected\$|IP Address: )|\[capture\] port lost|($CRASH_RE))" "$DLOG" | LC_ALL=C tr -cd '\11\12\15\40-\176' | sed -E 's/(IP Address: )[^ ]+/\1<redacted>/' | cut -c1-110 | head -80 > "$DDIR/$DNAME-events.txt"
echo "[hammer] events: $DDIR/$DNAME-events.txt"
stop_tails
[ -z "$INVALID" ] || { echo "[hammer] RESULT INVALID: $INVALID"; exit 2; }
# Provisioning is proven by the device joining the network; the client's answer to the real save can time
# out because the device leaves the AP before replying, so its status is information only
if [ "$CRASHES" -gt 0 ] || [ "$REBOOTS" -gt 0 ] || [ "$STA" -lt 1 ]; then
echo "[hammer] RESULT FAIL: crash markers $CRASHES, reboots $REBOOTS, STA addresses $STA, real save status ${REAL:-none}"; exit 3
fi
# A pass needs the stimulus the flow is about: at least half the configured bogus saves answered and one cooldown exit
CONFIGURED=$(LC_ALL=C grep -a -E '^[[:space:]]*bogus_saves:' "$CYAML" | head -1 | tr -dc '0-9'); CONFIGURED=${CONFIGURED:-15}
if [ "$EXITS" -lt 1 ] || [ $((ANSWERED * 2)) -lt "$CONFIGURED" ]; then
echo "[hammer] RESULT INVALID: insufficient stimulus (bogus saves answered $ANSWERED of $CONFIGURED, cooldown exits $EXITS)"; exit 2
fi
[ "$FAILED" -eq 0 ] || echo "[hammer] WARNING: $FAILED of $CONFIGURED bogus saves got no answer"
echo "[hammer] RESULT PASS"; exit 0
@@ -0,0 +1,39 @@
#!/bin/zsh
# usage: decode.sh CAPTURE ELF OUTPREFIX
# Parses the #CD: lines of a serial capture with Zephyr's coredump tools from the sdk-nrf that ESPHome
# installed (SDK_NRF, default: the sdk-nrf directory under the ESPHome cache), serves the dump to GDB on a
# random port and prints the backtrace and the thread list. The ELF is .pioenvs/<name>/zephyr/zephyr/zephyr.elf.
# Every step is checked: a missing tool, a capture without a complete dump, an empty .bin or a gdbserver
# that died all stop the script with exit 1, and the exit status is GDB's otherwise.
CAP=$1; ELF=$2; OUT=$3
SDK_NRF=${SDK_NRF:-$(ls -d ~/Library/Caches/esphome/sdk-nrf ~/.cache/esphome/sdk-nrf 2>/dev/null | head -1)}
[ -n "$SDK_NRF" ] || { echo "sdk-nrf not found, set SDK_NRF"; exit 1; }
F=$(ls -d $SDK_NRF/frameworks/v*/zephyr/scripts/coredump 2>/dev/null | tail -1)
PY=${PYTHON:-$(ls $SDK_NRF/penvs/*/bin/python 2>/dev/null | head -1)}
# SDK 0.x keeps the tools under arm-zephyr-eabi/, SDK 1.0+ under gnu/arm-zephyr-eabi/
GDB=$(ls $SDK_NRF/toolchains/*/arm-zephyr-eabi/bin/arm-zephyr-eabi-gdb $SDK_NRF/toolchains/*/gnu/arm-zephyr-eabi/bin/arm-zephyr-eabi-gdb 2>/dev/null | tail -1)
for v in F PY GDB; do
[ -n "${(P)v}" ] || { echo "$v not found under $SDK_NRF"; exit 1; }
done
[ -f "$ELF" ] || { echo "ELF not found: $ELF"; exit 1; }
# strip capture timestamps and ANSI so the parser sees the raw log lines
sed -E 's/^\[[0-9:.]+\]//; s/\x1b\[[0-9;]*m//g' "$CAP" > $OUT-all.log
# keep only the first complete #CD:BEGIN# .. #CD:END# block
awk '/#CD:BEGIN#/{if(!done){p=1;buf=""}} p{buf=buf $0 "\n"} /#CD:END#/{if(p){printf "%s", buf; done=1; p=0}}' $OUT-all.log > $OUT-raw.log
echo "CD lines in capture: $(grep -c '#CD:' $OUT-all.log) (BEGIN $(grep -c '#CD:BEGIN#' $OUT-all.log), END $(grep -c '#CD:END#' $OUT-all.log)); first complete block: $(grep -c '#CD:' $OUT-raw.log) lines"
[ -s $OUT-raw.log ] || { echo "no complete #CD:BEGIN# .. #CD:END# block in the capture"; exit 1; }
if ! $PY $F/coredump_serial_log_parser.py $OUT-raw.log $OUT.bin > $OUT-parser.log 2>&1; then
echo "parser failed:"; tail -5 $OUT-parser.log; exit 1
fi
tail -3 $OUT-parser.log
[ -s $OUT.bin ] || { echo "parser wrote an empty $OUT.bin"; exit 1; }
ls -la $OUT.bin | awk '{print "bin bytes", $5}'
PORT=$((20000 + RANDOM % 10000))
$PY $F/coredump_gdbserver.py --port $PORT "$ELF" $OUT.bin > $OUT-gdbserver.log 2>&1 &
G=$!
sleep 3
if ! kill -0 $G 2>/dev/null; then echo "gdbserver exited:"; tail -5 $OUT-gdbserver.log; exit 1; fi
$GDB -q -batch -ex "target remote :$PORT" -ex "bt" -ex "info threads" "$ELF" 2>&1 | grep -v "^warning\|Reading symbols\|^$" | head -40
rc=$pipestatus[1]
kill $G 2>/dev/null
exit $rc
@@ -0,0 +1,29 @@
#!/bin/zsh
# usage: flow.sh LABEL PORT ELF [OUTDIR]
# Run at least 60 s after the board rebooted from its crash, with the port closed. Proves the stored
# coredump waits for a reader and survives a reader that goes away:
# 1) open the port for 3 s and close it (a reader that leaves mid-print)
# 2) open it for 100 s: the whole dump must print again, then be erased
# 3) open it again for 20 s: dump_config must report no stored dump
# then decode.sh parses the first complete dump and prints the GDB backtrace.
# Needs a python with pyserial (PYTHON, default python3).
D=${0:A:h}
L=$1; P=$2; ELF=$3; S=${4:-.}
PY=${PYTHON:-python3}
CAP=$D/../serial_capture.py
capture() { # SECONDS OUTFILE: stop the flow when the capture fails (busy or missing port)
if ! $PY $CAP $P $1 $2 > $2.err 2>&1; then
echo "capture of $2 failed:"; cat $2.err; exit 1
fi
}
capture 3 $S/$L-brief.txt
echo "$L brief 3 s open: $(wc -l < $S/$L-brief.txt) lines, CD lines $(grep -a -c '#CD:' $S/$L-brief.txt), END $(grep -a -c '#CD:END#' $S/$L-brief.txt)"
sleep 5
capture 100 $S/$L-full.txt
echo "$L full 100 s open: $(wc -l < $S/$L-full.txt) lines, CD lines $(grep -a -c '#CD:' $S/$L-full.txt), BEGIN $(grep -a -c '#CD:BEGIN#' $S/$L-full.txt), END $(grep -a -c '#CD:END#' $S/$L-full.txt)"
grep -a "crashtest\|zephyr_coredump\|Reset reason\|Has stored\| Size:\| Error:\|took a long\|compiled on" $S/$L-full.txt | grep -a -v "#CD:[0-9A-F]" | head -16
sleep 3
capture 20 $S/$L-after.txt
echo "$L reopen 20 s: CD lines $(grep -a -c '#CD:' $S/$L-after.txt); $(grep -a 'Has stored\| Size:' $S/$L-after.txt | sed -E 's/^\[[^]]*\]//' | tr '\n' ' ')"
echo "== decode"
$D/decode.sh $S/$L-full.txt $ELF $S/$L
@@ -0,0 +1,24 @@
import asyncio
import sys
from aioesphomeapi import APIClient
async def main():
c = APIClient(sys.argv[1], 6053, None)
await c.connect(login=True)
di = await c.device_info()
print(
"version",
di.esphome_version,
"compiled",
di.compilation_time,
"home_id",
hex(di.zwave_home_id),
"flags",
di.zwave_proxy_feature_flags,
)
await c.disconnect()
asyncio.run(main())
@@ -0,0 +1,45 @@
"""Recompute the Z-Wave checksum (XOR of LENGTH..payload, initial 0xFF) over every frame the proxy logged."""
from functools import reduce
import pathlib
import re
import sys
def zw_checksum(frame: bytes) -> int:
return reduce(lambda a, b: a ^ b, frame[1:-1], 0xFF)
def complete(frame: bytes) -> bool:
"""A data frame is SOF, LENGTH, then LENGTH bytes; the proxy logs at most 168 bytes, so longer ones are cut."""
return len(frame) >= 3 and frame[0] == 0x01 and len(frame) == frame[1] + 2
for path in sys.argv[1:]:
text = pathlib.Path(path).read_text(encoding="utf-8", errors="replace")
sent = re.findall(r"Sending: ((?:[0-9A-F]{2}:)+[0-9A-F]{2})", text)
recv = re.findall(r"Received frame: ((?:[0-9A-F]{2}:)+[0-9A-F]{2})", text)
calc = re.findall(
r"CHECKSUM Received: 0x([0-9A-F]{2}) - Calculated: 0x([0-9A-F]{2})", text
)
bad = len(re.findall(r"Bad checksum", text))
acks_sent = len(re.findall(r"Sending ACK", text))
naks_sent = len(re.findall(r"Sending NAK/CAN", text))
acks_recv = len(re.findall(r"Received ACK", text))
naks_recv = len(re.findall(r"Received NAK", text))
reboots = len(re.findall(r"Boot count|Reset reason|rst:0x", text))
long_ops = len(re.findall(r"zwave_proxy took a long time", text))
rx_frames = [bytes.fromhex(f.replace(":", "")) for f in recv]
tx_frames = [bytes.fromhex(f.replace(":", "")) for f in sent]
rx_cut = sum(1 for b in rx_frames if not complete(b))
tx_cut = sum(1 for b in tx_frames if len(b) > 1 and not complete(b))
rx_ok = sum(1 for b in rx_frames if complete(b) and zw_checksum(b) == b[-1])
tx_ok = sum(1 for b in tx_frames if complete(b) and zw_checksum(b) == b[-1])
calc_ok = sum(1 for r, c in calc if r == c)
cmds = sorted({f.split(":")[3] for f in recv})
print(
f"{path.rsplit('/', 1)[-1]}: tx frames {len(sent)} (checksum ok {tx_ok}, truncated in log {tx_cut}), "
f"rx frames {len(recv)} (recomputed ok {rx_ok}, truncated in log {rx_cut}), "
f"device calc==received {calc_ok}/{len(calc)}, bad checksum warnings {bad}, ACK sent {acks_sent}, NAK sent {naks_sent}, "
f"ACK received {acks_recv}, NAK received {naks_recv}, long ops {long_ops}, rx cmd ids {cmds}"
)
@@ -0,0 +1,23 @@
# Needs packages/aux-2-pre-prod.yaml from NabuCasa/aux-2 at commit 283f2e64 next to this file, with its local
# external_components source pointed at github://NabuCasa/aux-2@main (see reference/recipes/usb_audio.md).
# Nabu Casa Connect AUX-2 on the usb_audio PR tree: stock package (I2S PCM5122 output) as a control.
packages:
core: !include aux-2-pre-prod.yaml
esphome:
name: aux-2-dut
name_add_mac_suffix: false
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
api:
encryption:
key: !secret api_key
logger:
level: DEBUG
sendspin:
unpaired_access: true
@@ -0,0 +1,39 @@
# Needs packages/aux-2-pre-prod.yaml from NabuCasa/aux-2 at commit 283f2e64 next to this file, with its local
# external_components source pointed at github://NabuCasa/aux-2@main (see reference/recipes/usb_audio.md).
# Nabu Casa Connect AUX-2 on the usb_audio PR tree: the Anker S330 on the USB host port as the mixer output.
packages:
core: !include aux-2-pre-prod.yaml
esphome:
name: aux-2-usb-dut
name_add_mac_suffix: false
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
api:
encryption:
key: !secret api_key
logger:
level: DEBUG
baud_rate: 0
sendspin:
unpaired_access: true
usb_audio:
id: usb_audio_id
speaker:
- platform: usb_audio
id: usb_speaker
usb_audio_id: usb_audio_id
sample_rate: 48000
bits_per_sample: 24bit
num_channels: 2
buffer_size: 28800
write_timeout: 50ms
- id: !extend mixing_speaker
output_speaker: usb_speaker
@@ -0,0 +1,44 @@
substitutions:
run: "1"
esphome:
name: bk72xx-crash-probe
includes: [../scripts/libretiny_crash/bkprobe.hpp]
# step 0 waits 30 s so a capture can be opened (and a reset pulsed) after the flash; later steps 5 s
on_boot:
priority: -100
then:
- lambda: id(step) = bkprobe_boot(${run});
- delay: !lambda 'return id(step) == 0 ? 30000 : 5000;'
- lambda: bkprobe_act(${run}, id(step));
bk72xx:
board: generic-bk7238-tuya
logger:
hardware_uart: UART1
level: DEBUG
preferences:
flash_write_interval: 1s
globals:
- id: step
type: int
initial_value: "0"
interval:
- interval: 10s
then:
- lambda: bkprobe_alive(id(step));
# Wi-Fi matters: a crashed BK72xx with Wi-Fi up never reboots on its own without the crash handler fix
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
api:
reboot_timeout: 0s
ota:
- platform: esphome
@@ -0,0 +1,38 @@
substitutions:
run: "1"
esphome:
name: lt21007e
includes: [../scripts/libretiny_prefs/ltprefs.hpp]
platformio_options:
build_flags: ["-DUSE_LIBRETINY_RTC_PREFERENCES"]
on_boot:
priority: -100
then:
- lambda: id(act) = ltprefs_boot(id(step), ${run});
# the first boot waits so the host can open the port and pulse a reset before the sequence starts
- delay: !lambda 'return (id(step) == 0 || id(step) == 8) ? 30000 : 3000;'
- lambda: ltprefs_exit(id(step), ${run}, id(act));
bk72xx:
board: generic-bk7238-tuya
logger:
hardware_uart: UART1
level: DEBUG
preferences:
flash_write_interval: 1s
globals:
- id: step
type: int
initial_value: "0"
- id: act
type: int
initial_value: "0"
interval:
- interval: 10s
then:
- lambda: ltprefs_alive(id(step), id(act));
@@ -0,0 +1,27 @@
# CAST PRO-1 control for the sendspin sync comparison: Apollo's current package, DEBUG logging as on the S3.
esphome:
name: "cast-pro-1-dut"
friendly_name: CAST PRO-1 test
project:
name: "ApolloAutomation.CAST_PRO-1"
version: ${version}
packages:
core: !include Core.yaml
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
enable_on_boot: false
logger:
level: DEBUG
api:
encryption:
key: !secret api_key
sendspin:
manufacturer: Apollo
model: CAST PRO-1 control
unpaired_access: true
@@ -0,0 +1,77 @@
# Package for the client board: it joins the device under test's AP (priority 10), posts
# `bogus_saves` bogus /wifisave requests `save_interval` apart, then posts the real credentials once.
# Include from a per-run yaml that holds the platform block and the substitutions (example-client.yaml);
# real_ssid and real_psk come from the scratch directory's secrets.yaml and must be URL safe.
# Set bogus_saves to 1 for a plain provisioning check. Log lines the driver script counts:
# "bogus save N", "bogus save status 200", "posting the real credentials", "real save status 200"
# The hammer is a single-mode script, so a reconnect to the AP (the device under test rebooted) does not
# start a second series on top of the first.
substitutions:
ap_password: hwtestpass
bogus_saves: "15"
save_interval: 3s
bogus_ssid: hwtest-no-such-network
bogus_psk: nosuchpassword
esphome:
name: ${name}
logger:
level: DEBUG
http_request:
id: http
verify_ssl: false
timeout: 10s
wifi:
id: wifi_id
networks:
- ssid: ${ap_ssid}
password: ${ap_password}
priority: 10
- ssid: ${real_ssid}
password: ${real_psk}
on_connect:
- lambda: |-
char b[40];
ESP_LOGI("test", "connected, ip %s", id(wifi_id).get_ip_addresses()[0].str_to(b));
- if:
condition:
lambda: |-
char b[40];
return strncmp(id(wifi_id).get_ip_addresses()[0].str_to(b), "192.168.4.", 10) == 0;
then:
- script.execute: hammer
script:
- id: hammer
mode: single
then:
- delay: 3s
- logger.log: "hammering /wifisave with bogus credentials"
- repeat:
count: ${bogus_saves}
then:
- lambda: ESP_LOGI("test", "bogus save %u", (unsigned) iteration + 1);
- http_request.get:
url: "http://192.168.4.1/wifisave?ssid=${bogus_ssid}&psk=${bogus_psk}"
capture_response: true
on_response:
- lambda: ESP_LOGI("test", "bogus save status %d", response->status_code);
on_error:
- lambda: ESP_LOGW("test", "bogus save status -1");
- delay: ${save_interval}
- logger.log: "posting the real credentials"
- http_request.get:
url: "http://192.168.4.1/wifisave?ssid=${real_ssid}&psk=${real_psk}"
capture_response: true
on_response:
- lambda: ESP_LOGI("test", "real save status %d", response->status_code);
on_error:
- lambda: ESP_LOGW("test", "real save status -1");
interval:
- interval: 5s
then:
- lambda: ESP_LOGI("test", "alive %u s", (unsigned) (millis() / 1000));
@@ -0,0 +1,46 @@
# Deep sleep wake timing for the outgoing API connection: boot -> dial -> client hello -> first HA state.
esphome:
name: deep-sleep-dut
on_boot:
priority: -100
then:
- lambda: ESP_LOGI("timing", "setup done at %u ms", (unsigned) millis());
esp32:
board: esp32dev
framework:
type: esp-idf
logger:
level: DEBUG
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
fast_connect: true
on_connect:
- lambda: ESP_LOGI("timing", "wifi connected at %u ms", (unsigned) millis());
api:
encryption:
key: !secret api_key
outgoing_connection:
on_client_connected:
- lambda: ESP_LOGI("timing", "client connected at %u ms (%s %s)", (unsigned) millis(), client_info.c_str(), client_address.c_str());
on_client_disconnected:
- lambda: ESP_LOGI("timing", "client disconnected at %u ms", (unsigned) millis());
ota:
- platform: esphome
text_sensor:
- platform: homeassistant
id: ha_state
entity_id: light.ENTITY
on_value:
- lambda: ESP_LOGI("timing", "first HA state '%s' at %u ms", x.c_str(), (unsigned) millis());
deep_sleep:
id: ds
run_duration: 15s
sleep_duration: 15s
@@ -0,0 +1,34 @@
esphome:
name: i19430-dut3
on_boot:
priority: 800
then:
- lambda: |-
auto marker = global_preferences->make_preference<uint32_t>(0x19430001, true);
uint32_t v = 0;
if (!marker.load(&v)) {
ESP_LOGW("test", "first boot: wiping preferences");
global_preferences->reset();
static_cast<esphome::libretiny::LibreTinyPreferences *>(global_preferences)->open();
v = 1;
marker.save(&v);
global_preferences->sync();
}
bk72xx:
board: generic-bk7238
logger:
hardware_uart: UART1
level: VERBOSE
wifi:
ap:
ssid: i19430-ap
captive_portal:
interval:
- interval: 5s
then:
- lambda: ESP_LOGI("test", "alive %u s", (unsigned) (millis() / 1000));
@@ -0,0 +1,31 @@
# Package for a device under test that never finds its network, falls back to its AP and serves the
# captive portal. Include it from a per-run yaml that holds the platform block and the substitutions
# (see example-dut.yaml). Bump `name` on every run: saved credentials are keyed by it, and a reused
# name boots straight onto the real network and never raises the AP.
substitutions:
ap_password: hwtestpass
ap_timeout: 15s
bogus_ssid: hwtest-no-such-network
bogus_psk: nosuchpassword
esphome:
name: ${name}
logger:
level: DEBUG
wifi:
networks:
- ssid: ${bogus_ssid}
password: ${bogus_psk}
ap:
ssid: ${ap_ssid}
password: ${ap_password}
ap_timeout: ${ap_timeout}
captive_portal:
interval:
- interval: 5s
then:
- lambda: ESP_LOGI("test", "alive %u s", (unsigned) (millis() / 1000));
@@ -0,0 +1,18 @@
# Client board for the /wifisave hammer. Copy into a scratch directory next to a secrets.yaml;
# ap_ssid must match the device under test, real_ssid and real_psk must be URL safe.
substitutions:
name: hwtest-client-1
ap_ssid: hwtest-ap
real_ssid: !secret wifi_ssid
real_psk: !secret wifi_password
bogus_saves: "15"
esp32:
board: esp32dev
framework:
type: esp-idf
# esp8266: {board: d1_mini}
packages:
hwtest: !include client-wifisave.yaml
@@ -0,0 +1,16 @@
# Device under test for the /wifisave hammer. Copy into a scratch directory, keep the file name equal
# to `name`, pick one platform block, bump `name` every run.
substitutions:
name: hwtest-dut-1
ap_ssid: hwtest-ap
esp8266:
board: d1_mini
# esp32: {board: esp32dev, framework: {type: esp-idf}}
# rtl87xx: {board: bw15}
# bk72xx: {board: generic-bk7238} # plus logger: {hardware_uart: UART1}, BK72xx logs on UART2 by default
# rp2040: {board: rpipico2w}
packages:
hwtest: !include dut-fallback-ap.yaml
@@ -0,0 +1,66 @@
# Restoring globals with every storage choice; each boot bumps the counters, logs them with the reset reason, saves
# and syncs; odd boots soft restart, even boots deep sleep (ESP32) or idle (D1 mini). The host's reset pulse and a
# power cut give the other reset kinds.
logger:
level: DEBUG
preferences:
flash_write_interval: 1s
globals:
- id: cnt_rtc
type: int
restore_value: true
storage: rtc
initial_value: "0"
- id: cnt_flash
type: int
restore_value: true
storage: flash
initial_value: "0"
- id: cnt_default
type: int
restore_value: true
initial_value: "0"
- id: str_rtc
type: std::string
restore_value: true
storage: rtc
max_restore_data_length: 24
initial_value: '"fresh"'
- id: arr_rtc
type: uint8_t[2]
restore_value: true
storage: rtc
initial_value: "{0, 0}"
esphome:
on_boot:
priority: -100
then:
- lambda: |-
id(cnt_rtc) += 1;
id(cnt_flash) += 1;
id(cnt_default) += 1;
id(arr_rtc)[0] += 1;
id(arr_rtc)[1] += 2;
char buf[24];
snprintf(buf, sizeof(buf), "boot%d", id(cnt_rtc));
ESP_LOGI("gl", "restored rtc=%d flash=%d default=%d str='%s' arr=%u/%u (values before this boot's bump: one less)",
id(cnt_rtc) - 1, id(cnt_flash) - 1, id(cnt_default) - 1, id(str_rtc).c_str(), id(arr_rtc)[0] - 1, id(arr_rtc)[1] - 2);
id(str_rtc) = buf;
- delay: 2s
- lambda: |-
global_preferences->sync();
ESP_LOGI("gl", "saved rtc=%d flash=%d default=%d str='%s' arr=%u/%u", id(cnt_rtc), id(cnt_flash), id(cnt_default),
id(str_rtc).c_str(), id(arr_rtc)[0], id(arr_rtc)[1]);
- if:
condition:
lambda: 'return id(cnt_rtc) % 2 == 1;'
then:
- delay: 3s
- lambda: 'ESP_LOGI("gl", "odd boot, soft restart now"); App.safe_reboot();'
button:
- platform: restart
name: Restart
@@ -0,0 +1,145 @@
# shared part: two Home Assistant lights mirrored on the device plus buttons that drive them
esphome:
# A local change made before the light core's first write (b19a776 sends it instead of merging it).
on_boot:
priority: -100
then:
- lambda: 'ESP_LOGI("halight", "on_boot turning B on at 35 percent before the first write");'
- light.turn_on:
id: bulb_b
brightness: 35%
logger:
level: VERBOSE
logs:
api: DEBUG
api.connection: DEBUG
api.service: DEBUG
wifi: DEBUG
scheduler: DEBUG
component: DEBUG
sensor: DEBUG
mdns: DEBUG
ota: DEBUG
socket: DEBUG
esp-idf: DEBUG
preferences: DEBUG
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
api:
encryption:
key: !secret api_key
reboot_timeout: 0s
ota:
- platform: esphome
light:
- platform: homeassistant
id: bulb_a
name: Mirror A
entity_id: light.ENTITY_A
report_timeout: 5s
on_turn_on:
- logger.log: "bulb_a on_turn_on"
on_turn_off:
- logger.log: "bulb_a on_turn_off"
- platform: homeassistant
id: bulb_b
name: Mirror B
entity_id: light.ENTITY_B
report_timeout: 5s
button:
- platform: template
name: A warm 40
id: a_warm_40
on_press:
- light.turn_on:
id: bulb_a
brightness: 40%
color_temperature: 370 mireds
- platform: template
name: A cool 100
on_press:
- light.turn_on:
id: bulb_a
brightness: 100%
color_temperature: 153 mireds
- platform: template
name: A red
on_press:
- light.turn_on:
id: bulb_a
brightness: 80%
red: 100%
green: 0%
blue: 0%
- platform: template
name: A green dim
on_press:
- light.turn_on:
id: bulb_a
brightness: 20%
red: 0%
green: 100%
blue: 0%
- platform: template
name: A brightness only 60
on_press:
- light.turn_on:
id: bulb_a
brightness: 60%
- platform: template
name: A transition 100
on_press:
- light.turn_on:
id: bulb_a
brightness: 100%
transition_length: 3s
- platform: template
name: A flash
on_press:
- light.turn_on:
id: bulb_a
flash_length: 1s
- platform: template
name: A off
on_press:
- light.turn_off: bulb_a
- platform: template
name: A toggle
on_press:
- light.toggle: bulb_a
- platform: template
name: B warm 50
on_press:
- light.turn_on:
id: bulb_b
brightness: 50%
color_temperature: 300 mireds
- platform: template
name: B off
on_press:
- light.turn_off: bulb_b
- platform: template
name: Log state
on_press:
- lambda: |-
auto v = id(bulb_a).current_values;
ESP_LOGI("halight", "A: on=%d mode=%d bri=%.2f ct=%.1f rgb=%.2f/%.2f/%.2f B: on=%d bri=%.2f",
v.is_on(), (int) v.get_color_mode(), v.get_brightness(), v.get_color_temperature(),
v.get_red(), v.get_green(), v.get_blue(), id(bulb_b).current_values.is_on(),
id(bulb_b).current_values.get_brightness());
interval:
- interval: 30s
then:
- lambda: |-
auto v = id(bulb_a).current_values;
ESP_LOGI("halight", "alive A: on=%d mode=%d bri=%.2f ct=%.1f rgb=%.2f/%.2f/%.2f B: on=%d bri=%.2f",
v.is_on(), (int) v.get_color_mode(), v.get_brightness(), v.get_color_temperature(),
v.get_red(), v.get_green(), v.get_blue(), id(bulb_b).current_values.is_on(),
id(bulb_b).current_values.get_brightness());
@@ -0,0 +1,50 @@
substitutions:
run: "3"
esphome:
name: cd13122d
on_boot:
priority: -100
then:
- lambda: |-
ESP_LOGI("crashtest", "boot, crashed_before=%d run=%s", id(crashed_${run}), "${run}");
- delay: 20s
- if:
condition:
lambda: 'return id(crashed_${run}) == 0;'
then:
- lambda: |-
id(crashed_${run}) = 1;
ESP_LOGW("crashtest", "flag set, letting the loop save it");
- delay: 3s
- lambda: |-
global_preferences->sync();
delay(500);
ESP_LOGW("crashtest", "writing to 0xDEADBEEF now");
delay(100);
*reinterpret_cast<volatile uint32_t *>(0xDEADBEEF) = 1;
else:
- lambda: |-
ESP_LOGI("crashtest", "already crashed once, running normally");
nrf52:
board: nrf52840dongle
dfu: true
logger:
hardware_uart: USB_CDC
level: DEBUG
zephyr_coredump:
globals:
- id: crashed_${run}
type: int
restore_value: true
initial_value: "0"
interval:
- interval: 10s
then:
- lambda: |-
ESP_LOGI("crashtest", "alive");
@@ -0,0 +1,31 @@
esphome:
name: pref20810
on_boot:
priority: -100
then:
- lambda: |-
ESP_LOGI("test", "boot: restored counter_pr=%d", id(counter_pr));
nrf52:
board: nrf52840dongle
dfu: true
logger:
hardware_uart: USB_CDC
level: VERBOSE
preferences:
flash_write_interval: 5s
globals:
- id: counter_pr
type: int
restore_value: true
initial_value: "0"
interval:
- interval: 15s
then:
- lambda: |-
id(counter_pr) += 1;
ESP_LOGI("test", "counter_pr now %d", id(counter_pr));
@@ -0,0 +1,84 @@
esphome:
name: online-image-dut
friendly_name: online-image-dut
esp32:
board: esp32dev
framework:
type: esp-idf
logger:
level: DEBUG
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
power_save_mode: none
api:
encryption:
key: !secret api_key
reboot_timeout: 0s
actions:
- action: fetch
variables:
url: string
then:
- online_image.set_url:
id: img
url: !lambda 'return url;'
update: false
- component.update: img
- action: refetch
then:
- component.update: img
- action: fetch_small
variables:
url: string
then:
- online_image.set_url:
id: img_small
url: !lambda 'return url;'
update: false
- component.update: img_small
- action: refetch_small
then:
- component.update: img_small
http_request:
timeout: 4.5s
verify_ssl: false
display:
image:
- platform: online_image
id: img
url: http://127.0.0.1/none.bmp
format: AUTO
type: RGB565
update_interval: never
on_download_finished:
logger.log:
format: "download finished: img cached=%d"
args: [cached]
on_error:
logger.log: "download error: img"
- platform: online_image
id: img_small
url: http://127.0.0.1/none.bmp
format: AUTO
type: RGB565
buffer_size: 256
update_interval: never
on_download_finished:
logger.log:
format: "download finished: img_small cached=%d"
args: [cached]
on_error:
logger.log: "download error: img_small"
button:
- platform: restart
id: reboot_btn
name: Reboot
@@ -0,0 +1,55 @@
# Mains build (no deep sleep): outgoing API connection timing after a Wi-Fi drop, and the loop cost while a
# connect is pending or the network is down.
esphome:
name: wifi-drop-dut
esp32:
board: esp32dev
framework:
type: esp-idf
logger:
level: DEBUG
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
fast_connect: true
on_connect:
- lambda: ESP_LOGI("timing", "wifi connected at %u ms", (unsigned) millis());
on_disconnect:
- lambda: ESP_LOGI("timing", "wifi disconnected at %u ms", (unsigned) millis());
api:
encryption:
key: !secret api_key
reboot_timeout: 0s
outgoing_connection:
delay: 2s
on_client_connected:
- lambda: ESP_LOGI("timing", "client connected at %u ms (%s %s)", (unsigned) millis(), client_info.c_str(), client_address.c_str());
on_client_disconnected:
- lambda: ESP_LOGI("timing", "client disconnected at %u ms (%s)", (unsigned) millis(), client_info.c_str());
ota:
- platform: esphome
debug:
update_interval: 5s
sensor:
- platform: debug
loop_time:
name: Loop Time
on_value:
- lambda: ESP_LOGI("timing", "loop time max %.0f ms over the last 5 s at %u ms", x, (unsigned) millis());
button:
- platform: template
name: Wifi cycle
on_press:
- lambda: ESP_LOGI("timing", "wifi cycle disabling at %u ms", (unsigned) millis());
- wifi.disable:
- delay: 4s
- lambda: ESP_LOGI("timing", "wifi cycle enabling at %u ms", (unsigned) millis());
- wifi.enable:
@@ -0,0 +1,105 @@
esphome:
name: rtsp17990
on_boot:
# GPIO8 low enables the camera's power supply on the Waveshare ESP32-S3-ETH; give the sensor
# time to come up before esp32_camera (setup_priority 200) probes it
priority: 700
then:
- switch.turn_off: cam_power
- lambda: |-
delay(1200);
esp32:
board: esp32-s3-devkitc-1
variant: esp32s3
flash_size: 16MB
framework:
type: esp-idf
psram:
mode: octal
speed: 80MHz
logger:
level: VERBOSE
hardware_uart: USB_SERIAL_JTAG
logs:
rtsp_server: VERBOSE
esp32_camera: DEBUG
camera: DEBUG
wifi: DEBUG
api: DEBUG
api.connection: DEBUG
api.socket: DEBUG
socket: DEBUG
scheduler: DEBUG
component: DEBUG
app: DEBUG
mdns: DEBUG
ota: DEBUG
logger: DEBUG
psram: DEBUG
esp32: DEBUG
network: DEBUG
i2c: DEBUG
i2c.idf: DEBUG
preferences: DEBUG
debug: DEBUG
sensor: DEBUG
wifi_esp32: DEBUG
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
power_save_mode: none
api:
reboot_timeout: 0s
ota:
- platform: esphome
debug:
update_interval: 10s
sensor:
- platform: debug
free:
name: "Heap Free"
block:
name: "Heap Largest Block"
psram:
name: "PSRAM Free"
# Waveshare ESP32-S3-ETH camera header (Waveshare wiki, WIFI_Web_CAM table)
i2c:
- id: cam_i2c
sda: GPIO48
scl: GPIO47
frequency: 400kHz
switch:
- platform: gpio
id: cam_power
name: Camera Power
pin: GPIO8
restore_mode: ALWAYS_OFF
setup_priority: 800
esp32_camera:
name: cam
setup_priority: 200
data_pins: [GPIO41, GPIO45, GPIO46, GPIO42, GPIO40, GPIO38, GPIO15, GPIO18]
vsync_pin: GPIO1
href_pin: GPIO2
pixel_clock_pin: GPIO39
external_clock:
pin: GPIO3
frequency: 20MHz
i2c_id: cam_i2c
resolution: 640X480
max_framerate: 10fps
rtsp_server:
port: 554
max_connections: 2
@@ -0,0 +1,107 @@
# Basic Config
esphome:
name: s31-cse7766
esp8266:
board: esp01_1m
toolchain: arduino
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
logger:
baud_rate: 0 # (UART logging interferes with cse7766)
level: VERBOSE
logs:
cse7766: VERBOSE
api: DEBUG
api.connection: DEBUG
api.socket: DEBUG
socket: DEBUG
scheduler: DEBUG
component: DEBUG
app: DEBUG
wifi: DEBUG
mdns: DEBUG
ota: DEBUG
logger: DEBUG
sensor: DEBUG
switch: DEBUG
binary_sensor: DEBUG
status_led: DEBUG
uart: DEBUG
gpio: DEBUG
preferences: DEBUG
esp8266: DEBUG
wifi_esp8266: DEBUG
api:
ota:
platform: esphome
# Device Specific Config
uart:
rx_pin: RX
baud_rate: 4800
parity: EVEN
binary_sensor:
- platform: gpio
pin:
number: GPIO0
mode: INPUT_PULLUP
inverted: True
name: "S31 Button"
on_press:
- switch.toggle: relay
- platform: status
name: "S31 Status"
sensor:
- platform: wifi_signal
name: "S31 WiFi Signal"
update_interval: 60s
- platform: cse7766
current:
accuracy_decimals: 3
name: "S31 Current"
filters:
- throttle: 2s
voltage:
accuracy_decimals: 1
name: "S31 Voltage"
filters:
- throttle: 2s
on_raw_value:
then:
- lambda: |-
id(frames) += 1;
power:
accuracy_decimals: 1
name: "S31 Power"
filters:
- throttle: 2s
switch:
- platform: gpio
name: "S31 Relay"
pin: GPIO12
id: relay
restore_mode: RESTORE_DEFAULT_ON
status_led:
pin: GPIO13
globals:
- id: frames
type: uint32_t
initial_value: "0"
interval:
- interval: 10s
then:
- lambda: |-
static uint32_t last = 0;
ESP_LOGI("frames", "cse7766 frames parsed: %u in 10 s (total %u)", (unsigned) (id(frames) - last), (unsigned) id(frames));
last = id(frames);
@@ -0,0 +1,76 @@
# Music Assistant target: the plain USB audio template plus the CAST-1 style sendspin wiring (resampler speakers
# into a mixer whose inputs never time out, speaker_source media player, sendspin hub and sources).
packages:
base: !include usb-audio-waveshare-s3-eth-dut.yaml
# Logs over the API only: a DEBUG line per report would saturate UART0 and trip the task watchdog.
logger:
baud_rate: 0
speaker:
- id: !extend usb_speaker
buffer_size: 28800
write_timeout: 50ms
- platform: mixer
id: mixer_speaker_id
output_speaker: usb_speaker
num_channels: 2
task_stack_in_psram: true
source_speakers:
- id: announcement_mixer_input
timeout: never
- id: media_mixer_input
timeout: never
- platform: resampler
id: announcement_resampling_speaker
output_speaker: announcement_mixer_input
sample_rate: 48000
bits_per_sample: 16
- platform: resampler
id: media_resampling_speaker
output_speaker: media_mixer_input
sample_rate: 48000
bits_per_sample: 16
sendspin:
id: sendspin_hub
# PSRAM stack skews time sync; keep internal
task_stack_in_psram: false
manufacturer: Waveshare
model: ESP32-S3-ETH USB audio
firmware_version: "0.1"
unpaired_access: true
media_source:
- platform: sendspin
id: sendspin_source
- platform: audio_http
id: http_media_source
buffer_size: 500000
- platform: audio_http
id: http_announcement_source
buffer_size: 250000
media_player:
- platform: sendspin
id: sendspin_group_media_player
name: Sendspin Player
- platform: speaker_source
id: external_media_player
name: Player
volume_increment: 0.05
announcement_pipeline:
format: FLAC
num_channels: 1
sample_rate: 48000
speaker: announcement_resampling_speaker
sources:
- http_announcement_source
media_pipeline:
speaker: media_resampling_speaker
format: FLAC
num_channels: 2
sample_rate: 48000
sources:
- sendspin_source
- http_media_source
@@ -0,0 +1,142 @@
# Waveshare ESP32-S3-ETH: USB host for a speakerphone (usb_audio), logs over Ethernet, the USB port is the host port
esphome:
name: usb-audio-dut
esp32:
board: esp32-s3-devkitc-1
variant: esp32s3
flash_size: 16MB
framework:
type: esp-idf
psram:
mode: octal
speed: 80MHz
logger:
level: DEBUG
hardware_uart: UART0
logs:
usb_host: DEBUG
ethernet:
type: W5500
clk_pin: GPIO13
mosi_pin: GPIO11
miso_pin: GPIO12
cs_pin: GPIO14
interrupt_pin: GPIO10
reset_pin: GPIO9
api:
encryption:
key: !secret api_key
reboot_timeout: 0s
ota:
- platform: esphome
usb_audio:
id: usb_audio_id
microphone:
- platform: usb_audio
id: usb_mic
usb_audio_id: usb_audio_id
sample_rate: 48000
bits_per_sample: 16bit
num_channels: 2
buffer_size: 9600
on_data:
- lambda: |-
id(mic_bytes) += x.size();
id(mic_chunks) += 1;
speaker:
- platform: usb_audio
id: usb_speaker
usb_audio_id: usb_audio_id
sample_rate: 48000
bits_per_sample: 24bit
num_channels: 2
buffer_size: 9600
write_timeout: 20ms
globals:
- id: beep_left
type: int
initial_value: "0"
- id: beep_written
type: uint32_t
initial_value: "0"
- id: beep_short
type: uint32_t
initial_value: "0"
- id: beep_phase
type: float
initial_value: "0"
- id: mic_bytes
type: uint32_t
initial_value: "0"
- id: mic_chunks
type: uint32_t
initial_value: "0"
button:
- platform: template
name: Mic start
on_press:
- microphone.capture: usb_mic
- platform: template
name: Mic stop
on_press:
- microphone.stop_capture: usb_mic
- platform: template
name: Beep
on_press:
- lambda: |-
// 2 s of a 1 kHz tone at 48 kHz 24-bit stereo, fed 10 ms at a time from the interval below
id(beep_left) = 200;
id(beep_written) = 0;
id(beep_short) = 0;
id(beep_phase) = 0.0f;
id(usb_speaker).start();
ESP_LOGI("uatest", "beep started");
- platform: template
name: Speaker stop
on_press:
- speaker.stop: usb_speaker
- platform: template
name: Restart
on_press:
- lambda: App.safe_reboot();
interval:
- interval: 8ms
then:
- lambda: |-
if (id(beep_left) <= 0) return;
// 10 ms of 48 kHz stereo 24-bit samples, 3 bytes little-endian per sample
static uint8_t buf[480 * 2 * 3];
float phase = id(beep_phase);
for (int i = 0; i < 480; i++) {
int32_t s = (int32_t) (1500000.0f * sinf(phase));
phase += 2.0f * 3.14159265f * 1000.0f / 48000.0f;
if (phase > 6.2831853f) phase -= 6.2831853f;
for (int c = 0; c < 2; c++) {
uint8_t *o = buf + (i * 2 + c) * 3;
o[0] = s & 0xFF; o[1] = (s >> 8) & 0xFF; o[2] = (s >> 16) & 0xFF;
}
}
size_t n = id(usb_speaker).play(buf, sizeof(buf), 0);
if (n == 0) { id(beep_short) += 1; return; } // not accepted yet, try the same chunk next time
id(beep_phase) = phase;
id(beep_written) += n;
id(beep_left) -= 1;
if (id(beep_left) == 0) ESP_LOGI("uatest", "beep done: %u bytes queued, %u retries", (unsigned) id(beep_written), (unsigned) id(beep_short));
- interval: 5s
then:
- lambda: |-
ESP_LOGI("uatest", "mic capturing=%d bytes=%u chunks=%u | speaker running=%d | heap free %u",
id(usb_mic).is_running(), (unsigned) id(mic_bytes), (unsigned) id(mic_chunks),
id(usb_speaker).is_running(), (unsigned) heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
@@ -0,0 +1,112 @@
esphome:
name: cdc11634
nrf52:
board: nrf52840dongle
dfu: true
logger:
hardware_uart: USB_CDC
level: VERBOSE
logs:
usb_cdc_acm: VERBOSE
scheduler: DEBUG
component: DEBUG
app: DEBUG
preferences: DEBUG
logger: DEBUG
nrf52: DEBUG
dfu: DEBUG
usb_cdc_acm:
interfaces:
- disabled: true
- id: cdc1
globals:
- id: rx_total
type: uint32_t
initial_value: "0"
- id: tx_attempted
type: uint32_t
initial_value: "0"
- id: stream_left
type: int32_t
initial_value: "0"
interval:
- interval: 20ms
then:
- lambda: |-
uint8_t buf[64];
while (id(cdc1).available() > 0) {
// a command is exactly two bytes, so what the host sends after it stays in the driver for peek/read
uint8_t first = 0;
id(cdc1).peek_byte(&first);
size_t n = std::min<size_t>(id(cdc1).available(), sizeof(buf));
if (first == 0x01 && n >= 2)
n = 2;
if (!id(cdc1).read_array(buf, n)) {
ESP_LOGE("test", "read_array(%u) failed", (unsigned) n);
break;
}
id(rx_total) += n;
if (buf[0] == 0x01 && n >= 2) {
const char cmd = buf[1];
if (cmd == 'B') {
// one oversized write: the 256 byte TX ring must drop the rest and warn once
static uint8_t big[1000];
for (size_t i = 0; i < sizeof(big); i++) big[i] = 'A' + (i % 26);
id(cdc1).write_array(big, sizeof(big));
id(tx_attempted) += sizeof(big);
ESP_LOGI("test", "burst 1000 written");
} else if (cmd == 'S') {
id(stream_left) = 100;
ESP_LOGI("test", "stream started");
} else if (cmd == 'L') {
ESP_LOGI("test", "line coding baud=%u data=%u stop=%u parity=%u", (unsigned) id(cdc1).get_baud_rate(),
id(cdc1).get_data_bits(), id(cdc1).get_stop_bits(), (unsigned) id(cdc1).get_parity());
} else if (cmd == 'K') {
// the host sends "\x01KZ" in one write; the Z must still be in the driver here
uint8_t pb = 0, rb = 0;
bool okp = id(cdc1).peek_byte(&pb);
bool okr = id(cdc1).read_byte(&rb);
ESP_LOGI("test", "peek ok=%d 0x%02X read ok=%d 0x%02X (expect ok=1 0x5A for both)", okp, pb, okr, rb);
if (okr) id(rx_total) += 1;
} else if (cmd == 'F') {
static uint8_t pat[200];
for (size_t i = 0; i < sizeof(pat); i++) pat[i] = 'a' + (i % 26);
id(cdc1).write_array(pat, sizeof(pat));
id(tx_attempted) += sizeof(pat);
const uint32_t t0 = millis();
auto res = id(cdc1).flush();
ESP_LOGI("test", "flush result=%d took %u ms", (int) res, (unsigned) (millis() - t0));
} else if (cmd == 'C') {
ESP_LOGI("test", "counters rx=%u tx_attempted=%u (write_array drops silently when the ring is full)", (unsigned) id(rx_total), (unsigned) id(tx_attempted));
} else {
ESP_LOGW("test", "unknown cmd 0x%02X", cmd);
}
continue;
}
id(cdc1).write_array(buf, n);
id(tx_attempted) += n;
}
- interval: 100ms
then:
- lambda: |-
if (id(stream_left) > 0) {
char line[101];
int len = snprintf(line, sizeof(line), "STREAM %03d ", 100 - id(stream_left));
for (int i = len; i < 99; i++) line[i] = 'x';
line[99] = '\n';
id(cdc1).write_array(reinterpret_cast<const uint8_t *>(line), 100);
id(tx_attempted) += 100;
id(stream_left) -= 1;
if (id(stream_left) == 0) {
ESP_LOGI("test", "stream done");
}
}
- interval: 10s
then:
- lambda: |-
ESP_LOGI("test", "alive rx=%u tx_attempted=%u", (unsigned) id(rx_total), (unsigned) id(tx_attempted));
@@ -0,0 +1,83 @@
esphome:
name: camdiag
on_boot:
priority: -100
then:
- output.set_level:
id: xclk
level: 50%
- delay: 200ms
- lambda: |-
auto *bus = id(cam_i2c);
auto scan = [&](const char *what) {
int found = 0;
for (uint8_t a = 0x08; a < 0x78; a++) {
if (bus->write(a, nullptr, 0) == i2c::ERROR_OK) {
ESP_LOGI("camdiag", " ACK at 0x%02X", a);
found++;
}
}
ESP_LOGI("camdiag", "scan %s: %d devices", what, found);
};
auto rd16 = [&](uint16_t reg) {
uint8_t r[2] = {(uint8_t) (reg >> 8), (uint8_t) reg};
uint8_t v = 0xEE;
auto err = bus->write_readv(0x3C, r, 2, &v, 1);
ESP_LOGI("camdiag", " 0x3C [0x%04X] err=%d -> 0x%02X", reg, (int) err, v);
return v;
};
auto rd8 = [&](uint8_t addr, uint8_t reg) {
uint8_t v = 0xEE;
auto err = bus->write_readv(addr, &reg, 1, &v, 1);
ESP_LOGI("camdiag", " 0x%02X [0x%02X] (8 bit) err=%d -> 0x%02X", addr, reg, (int) err, v);
};
id(g8).turn_off(); delay(1500);
id(xclk).set_level(0.0f); delay(300);
scan("GPIO8 LOW, XCLK OFF");
id(xclk).set_level(0.5f); delay(300);
scan("GPIO8 LOW, XCLK ON");
ESP_LOGI("camdiag", "OV5640 style 16 bit reads (expect 300A=0x56 300B=0x40, 302A=0xB0..B2):");
rd16(0x300A); rd16(0x300B); rd16(0x302A); rd16(0x3008); rd16(0x3100); rd16(0x3103); rd16(0x4300);
ESP_LOGI("camdiag", "8 bit reads at 0x3C (GC2145 expects F0=0x21 F1=0x45):");
rd8(0x3C, 0xF0); rd8(0x3C, 0xF1); rd8(0x3C, 0x0A); rd8(0x3C, 0x0B);
ESP_LOGI("camdiag", "write 0x3103=0x11 then read back, then soft reset 0x3008=0x82 and re-read IDs:");
uint8_t w[3] = {0x31, 0x03, 0x11};
auto werr = bus->write(0x3C, w, 3);
ESP_LOGI("camdiag", " write err=%d", (int) werr);
rd16(0x3103);
uint8_t rst[3] = {0x30, 0x08, 0x82};
bus->write(0x3C, rst, 3); delay(100);
rd16(0x300A); rd16(0x300B);
id(g8).turn_on(); delay(500); scan("GPIO8 HIGH (camera off)");
id(g8).turn_off(); delay(1500); scan("GPIO8 LOW again");
esp32:
board: esp32-s3-devkitc-1
variant: esp32s3
flash_size: 16MB
framework:
type: esp-idf
psram:
mode: octal
speed: 80MHz
logger:
level: DEBUG
hardware_uart: USB_SERIAL_JTAG
i2c:
- id: cam_i2c
sda: GPIO48
scl: GPIO47
frequency: 100kHz
scan: true
output:
- platform: gpio
id: g8
pin: GPIO8
- platform: ledc
pin: GPIO3
id: xclk
frequency: 20000000Hz
+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@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
with:
enable-cache: true
# Pull request saves land in per-PR scopes nothing else can
+1 -1
View File
@@ -53,7 +53,7 @@ module.exports = async ({ github, context }) => {
});
const currentLabels = currentLabelsData.map(label => label.name);
const managedLabels = currentLabels.filter(label =>
label.startsWith('component: ') || MANAGED_LABELS.includes(label)
label.startsWith('component: ') || label.startsWith('platform: ') || MANAGED_LABELS.includes(label)
);
// Check for mega-PR early - if present, skip most automatic labeling
+26 -10
View File
@@ -42,8 +42,8 @@ function generateReviewMessages(finalLabels, originalLabelCount, deprecatedInfo,
let message = `${TOO_BIG_MARKER}\n### 📦 Pull Request Size\n\n`;
message +=
`Hey @${prAuthor}, thanks for the contribution! Just a heads up, ` +
`this PR is on the large side `;
`Hey @${prAuthor}, thanks for the contribution. This PR is on the ` +
`large side `;
if (tooManyLabels && tooManyChanges) {
message +=
@@ -57,16 +57,31 @@ function generateReviewMessages(finalLabels, originalLabelCount, deprecatedInfo,
}
message += `, which makes it harder for maintainers to review.\n\n`;
let condition;
if (tooManyLabels && tooManyChanges) {
condition =
`is at most ${TOO_BIG_THRESHOLD} lines (excluding tests) and touches ` +
`at most ${MAX_LABELS} components/areas`;
} else if (tooManyLabels) {
condition = `touches at most ${MAX_LABELS} components/areas`;
} else {
condition = `is at most ${TOO_BIG_THRESHOLD} lines (excluding tests)`;
}
message +=
`Smaller, focused PRs tend to be reviewed much faster since they ` +
`fit into the short gaps between other maintainer work; large ones ` +
`often have to wait for a rare long uninterrupted block of time. ` +
`If you can break this up into smaller pieces that can be reviewed ` +
`independently, it will almost certainly land faster overall.\n\n`;
`**This PR will not be reviewed until it ${condition}.** We know ` +
`that is not what you want to hear after this much work, and we want ` +
`to be upfront so you do not wait for a review that is not coming: ` +
`please split the change. The label is removed automatically once ` +
`the PR ${condition}.\n\n`;
message +=
`Before putting more time in, it's also worth popping into ` +
`\`#devs\` on [Discord](https://esphome.io/chat) so we can help ` +
`you scope things and flag anything already in flight.\n\n`;
`There is no exception you can request. Rarely, a maintainer agrees in ` +
`\`#devs\` on [Discord](https://esphome.io/chat) to review a change that ` +
`genuinely cannot be split as one piece and adds the \`mega-pr\` label, ` +
`but that is rare enough that you should plan on splitting rather than ` +
`wait for it. Smaller, focused PRs are picked up much faster because ` +
`they fit into the short gaps between other maintainer work. For a new ` +
`component, the first PR brings the hub and a single platform (for ` +
`example \`sensor\`); each further platform is its own PR on top of it.\n\n`;
message +=
`For more details (including how to split the work up), see: ` +
`https://developers.esphome.io/contributing/submitting-your-work/` +
@@ -214,6 +229,7 @@ async function handleMaintainerAccessComment(github, context, maintainerAccess)
}
module.exports = {
generateReviewMessages,
handleReviews,
handleMaintainerAccessComment
};
@@ -0,0 +1,38 @@
const { describe, it } = require('node:test');
const assert = require('node:assert/strict');
const { generateReviewMessages } = require('../reviews');
const MAX_LABELS = 15;
const TOO_BIG_THRESHOLD = 1000;
function tooBigMessage(labelCount, additions) {
const files = [{ filename: 'esphome/components/a/a.cpp', additions, deletions: 0 }];
const messages = generateReviewMessages(['too-big'], labelCount, null, files, additions, 0, 'someone', MAX_LABELS, TOO_BIG_THRESHOLD);
assert.equal(messages.length, 1);
return messages[0];
}
describe('too-big review message', () => {
it('names the line cap when the PR is too long', () => {
const message = tooBigMessage(3, 1500);
assert.match(message, /will not be reviewed until it is at most 1000 lines \(excluding tests\)\./);
assert.match(message, /removed automatically once the PR is at most 1000 lines/);
assert.doesNotMatch(message, /components\/areas\./);
});
it('names the area cap when the PR touches too many areas but is short', () => {
const message = tooBigMessage(20, 200);
assert.match(message, /will not be reviewed until it touches at most 15 components\/areas\./);
assert.doesNotMatch(message, /until it is at most 1000 lines/);
});
it('names both caps when both are exceeded', () => {
const message = tooBigMessage(20, 1500);
assert.match(message, /is at most 1000 lines \(excluding tests\) and touches at most 15 components\/areas\./);
});
it('tells new-component authors to bring one platform first', () => {
assert.match(tooBigMessage(3, 1500), /hub and a single platform/);
assert.match(tooBigMessage(3, 1500), /mega-pr/);
});
});
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
with:
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
+2 -2
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@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
with:
enable-cache: true
# Pull-request-only workflow: a save could never be shared and
@@ -97,7 +97,7 @@ jobs:
run: git diff
- if: failure()
name: Archive artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: generated-proto-files
path: |
+2 -2
View File
@@ -128,7 +128,7 @@ jobs:
- name: Upload compile-test image artifact
if: matrix.os == 'ubuntu-24.04' && matrix.build_type == 'docker'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
# The tar is already compressed, so upload it as-is. archive: false
# skips the redundant zip and makes the file name the artifact name
@@ -210,7 +210,7 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download image artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: compile-test-image.tar.zst
- name: Load image
+151 -46
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@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
with:
enable-cache: true
# Pull request saves land in per-PR scopes nothing else can
@@ -81,8 +81,8 @@ jobs:
- name: Install apt packages (cached)
uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3
with:
packages: libsdl2-dev ccache
version: 1.1
packages: libsdl2-dev libmbedtls-dev ccache
version: 1.2
determine-jobs:
name: Determine which jobs to run
@@ -286,7 +286,7 @@ jobs:
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Run prek
uses: j178/prek-action@4e14d07f9231acabce116ccfca13b13dd9755ece # v3.0.0
uses: j178/prek-action@1382ad72dea72f9d53832f2bcb65b3e87a947204 # v3.0.1
with:
prek-version: ${{ steps.prek.outputs.version }}
# This job only runs on pull requests, so nothing ever populates
@@ -395,6 +395,8 @@ jobs:
- common
- determine-jobs
if: needs.determine-jobs.outputs.integration-tests == 'true'
# A hung test is failed by pytest's --timeout below; this bounds the rest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
@@ -415,8 +417,8 @@ jobs:
timeout-minutes: 10
uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3
with:
packages: libsdl2-dev ccache
version: 1.1
packages: libsdl2-dev libmbedtls-dev ccache
version: 1.2
- name: Set up Python 3.13
id: python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
@@ -438,7 +440,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@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
with:
enable-cache: true
# Pull request saves land in per-PR scopes nothing else can
@@ -471,13 +473,14 @@ jobs:
exit 1
fi
echo "Bucket ${{ matrix.bucket.name }}: running ${#test_files[@]} integration tests"
pytest -vv --no-cov --tb=native --durations=30 -n auto --dist worksteal \
# A hung test fails after 30 min: the 15 min shared build lock wait plus a cold compile
pytest -vv --no-cov --tb=native --durations=30 -n auto --dist worksteal --timeout=1800 \
--junitxml=junit-integration.xml "${test_files[@]}"
- name: Upload test artifacts
# Tests that compare rendered output write the image they actually got here, so a
# failure can be looked at without reproducing the whole build locally.
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: integration-test-artifacts-${{ matrix.bucket.name }}
path: test_artifacts/
@@ -488,7 +491,7 @@ jobs:
# script/update_integration_test_durations.py; only full matrix dev
# runs produce usable data.
if: github.ref == 'refs/heads/dev' && needs.determine-jobs.outputs.integration-run-all == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: junit-integration-${{ strategy.job-index }}
path: junit-integration.xml
@@ -528,7 +531,7 @@ jobs:
script/check_import_time.py --check --har importtime.har
- name: Upload waterfall HAR
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: import-time-waterfall
path: importtime.har
@@ -626,7 +629,7 @@ jobs:
apt-get install -y libc6-dbg
- name: Run CodSpeed benchmarks
uses: CodSpeedHQ/action@373d6868929f444bc08d901fd0eb0ad52a8875ea # v5.2.1
uses: CodSpeedHQ/action@f842b9a3ffe213dbb81768fd7f7eeb4dfcf1c369 # v5.4.2
with:
run: |
. venv/bin/activate
@@ -680,7 +683,7 @@ jobs:
include:
- id: clang-tidy
name: Run script/clang-tidy for ESP8266
options: --environment esp8266-arduino-tidy --grep USE_ESP8266
options: --environment esp8266-arduino-tidy --grep USE_ESP8266 --grep USE_SOCKET_IMPL_LWIP_TCP --grep USE_SHD_FIRMWARE_DATA
pio_cache_key: tidyesp8266
- id: clang-tidy
name: Run script/clang-tidy for ESP32 Arduino
@@ -693,12 +696,16 @@ jobs:
ignore_errors: false
- id: clang-tidy
name: Run script/clang-tidy for RP2
options: --environment rp2-tidy --grep USE_RP2
options: --environment rp2-tidy --grep USE_RP2 --grep USE_BLE_SCAN_RESPONSE_MERGER
pio_cache_key: tidyrp2
- id: clang-tidy
name: Run script/clang-tidy for host
options: --environment host-tidy --grep USE_HOST
pio_cache_key: tidyhost
- id: clang-tidy
name: Run script/clang-tidy for LibreTiny
environments: bk72xx-tidy ln882h-tidy rtl87xxb-tidy rtl87xxc-tidy
options: --grep USE_LIBRETINY --grep USE_BK72XX --grep USE_RTL87XX --grep USE_LN882X
options: --grep USE_LIBRETINY --grep USE_BK72XX --grep USE_RTL87XX --grep USE_LN882X --grep USE_LN882H_BLE --grep USE_SOCKET_IMPL_LWIP_SOCKETS
pio_cache_key: tidylibretiny
steps:
@@ -707,6 +714,16 @@ jobs:
with:
# Need history for HEAD~1 to work for checking changed files
fetch-depth: 2
- name: Install apt packages (cached)
if: matrix.pio_cache_key == 'tidyhost'
# libsdl2-dev is needed by the sdl component on the host, libmbedtls-dev
# by dsmr and dlms_meter. Packages and version must match seed-apt-cache
# exactly.
timeout-minutes: 10
uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3
with:
packages: libsdl2-dev libmbedtls-dev ccache
version: 1.2
- name: Restore Python
id: restore-python
@@ -893,14 +910,20 @@ jobs:
matrix:
include:
- id: clang-tidy
name: Run script/clang-tidy for ESP32 IDF 1/3
options: --environment esp32-idf-tidy --split-num 3 --split-at 1
name: Run script/clang-tidy for ESP32 IDF 1/5
options: --environment esp32-idf-tidy --split-num 5 --split-at 1
- id: clang-tidy
name: Run script/clang-tidy for ESP32 IDF 2/3
options: --environment esp32-idf-tidy --split-num 3 --split-at 2
name: Run script/clang-tidy for ESP32 IDF 2/5
options: --environment esp32-idf-tidy --split-num 5 --split-at 2
- id: clang-tidy
name: Run script/clang-tidy for ESP32 IDF 3/3
options: --environment esp32-idf-tidy --split-num 3 --split-at 3
name: Run script/clang-tidy for ESP32 IDF 3/5
options: --environment esp32-idf-tidy --split-num 5 --split-at 3
- id: clang-tidy
name: Run script/clang-tidy for ESP32 IDF 4/5
options: --environment esp32-idf-tidy --split-num 5 --split-at 4
- id: clang-tidy
name: Run script/clang-tidy for ESP32 IDF 5/5
options: --environment esp32-idf-tidy --split-num 5 --split-at 5
steps:
- name: Check out code from GitHub
@@ -983,14 +1006,14 @@ jobs:
- id: clang-tidy
name: Run script/clang-tidy for ESP32 S3
# yamllint disable-line rule:line-length
options: --environment esp32s3-idf-tidy --grep SOC_TEMP_SENSOR_SUPPORTED --grep USE_ESP32_VARIANT_ESP32S3 --grep USE_LOGGER_USB_CDC
options: --environment esp32s3-idf-tidy --grep SOC_TEMP_SENSOR_SUPPORTED --grep USE_ESP32_VARIANT_ESP32S3 --grep USE_LOGGER_USB_CDC --grep SOC_I2S_SUPPORTS_TDM --grep USE_OPENTHREAD
tidy_environment: esp32s3-idf-tidy
- id: clang-tidy
name: Run script/clang-tidy for ESP32 P4
# P4 has no native Wi-Fi/BLE; those run over the hosted co-processor,
# so their code paths differ -- lint them under the P4 build too.
# yamllint disable-line rule:line-length
options: --environment esp32p4-idf-tidy --grep USE_ESP32_VARIANT_ESP32P4 --grep USE_ESP32_HOSTED --grep USE_WIFI --grep USE_BLE
options: --environment esp32p4-idf-tidy --grep USE_ESP32_VARIANT_ESP32P4 --grep CONFIG_IDF_TARGET_ESP32P4 --grep USE_ESP32_HOSTED --grep USE_WIFI --grep USE_BLE
tidy_environment: esp32p4-idf-tidy
- id: clang-tidy
name: Run script/clang-tidy for ESP32 C6
@@ -1096,8 +1119,8 @@ jobs:
timeout-minutes: 10
uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3
with:
packages: libsdl2-dev ccache
version: 1.1
packages: libsdl2-dev libmbedtls-dev ccache
version: 1.2
- name: Check out code from GitHub
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -1329,6 +1352,91 @@ jobs:
# config first, so a separate config pass is redundant.
python3 script/test_build_components.py -e compile -t esp8266-ard -c "$TEST_COMPONENTS" -f --toolchain arduino --fail-on-no-tests
test-esp8266-native-windows:
name: Compile esp8266 with the native toolchain on Windows (ccache ${{ matrix.ccache }})
runs-on: windows-latest
needs:
- common
- determine-jobs
if: github.event_name == 'pull_request' && needs.determine-jobs.outputs.esp8266-native == 'true'
# A hung compiler must fail the job, not hold the runner for six hours.
timeout-minutes: 40
strategy:
fail-fast: false
matrix:
# Both ways the build runs on Windows: ESPHome Desktop bundles
# ccache and puts it on PATH, a pip install has none.
ccache: ["1", "0"]
env:
ESPHOME_CCACHE_ENABLE: ${{ matrix.ccache }}
# The test script prints characters the cp1252 console cannot encode
PYTHONUTF8: "1"
# The exact static build ESPHome Desktop ships
CCACHE_URL: https://github.com/ccache/ccache/releases/download/v4.14/ccache-4.14-windows-x86_64.zip
CCACHE_SHA256: 2568347a697e103ca1b073981c704ad76fb2507d066c38dba038dd73399d968f
steps:
- name: Check out code from GitHub
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Install ccache
if: matrix.ccache == '1'
shell: pwsh
run: |
$dir = Join-Path $env:RUNNER_TEMP "ccache"
New-Item -ItemType Directory -Force $dir | Out-Null
$zip = Join-Path $dir "ccache.zip"
Invoke-WebRequest -Uri $env:CCACHE_URL -OutFile $zip
$hash = (Get-FileHash $zip -Algorithm SHA256).Hash.ToLower()
if ($hash -ne $env:CCACHE_SHA256) {
Write-Host "::error::ccache archive sha256 $hash does not match $env:CCACHE_SHA256"
exit 1
}
Expand-Archive -Path $zip -DestinationPath $dir
$bin = Get-ChildItem -Path $dir -Recurse -Filter ccache.exe | Select-Object -First 1
Add-Content -Path $env:GITHUB_PATH -Value $bin.DirectoryName
& $bin.FullName --version
- name: Compile
shell: pwsh
# One component test: web_server brings wifi, api and the async web
# server libraries, the shape of a typical ESP8266 device
run: |
. .\venv\Scripts\Activate.ps1
$arguments = "script/test_build_components.py", "-e", "compile", "-t", "esp8266-ard", "-c", "web_server", "--base-only", "--toolchain", "arduino", "--fail-on-no-tests"
$p = Start-Process -FilePath python -ArgumentList $arguments -NoNewWindow -PassThru
# Reading the handle keeps ExitCode available after the process exits
$null = $p.Handle
if (-not $p.WaitForExit(25 * 60 * 1000)) {
Write-Host "::error::The compile did not finish in 25 minutes; processes still running:"
Get-CimInstance Win32_Process |
Where-Object { $_.CommandLine -match 'xtensa|ccache|ninja|esphome' } |
Select-Object ProcessId, ParentProcessId, CreationDate, CommandLine |
Format-List | Out-String -Width 4096 | Write-Host
Get-Process |
Where-Object { $_.Name -match 'cc1|ccache|ninja|xtensa|python' } |
Select-Object Id, Name, CPU, WorkingSet64 |
Format-Table | Out-String | Write-Host
taskkill /PID $p.Id /T /F
exit 1
}
exit $p.ExitCode
- name: Show ccache statistics
# Also after a failed or killed compile: the counters say whether
# ccache was ever reached
if: always() && matrix.ccache == '1'
shell: pwsh
# The build keeps its store under the esphome tools cache
run: |
$env:CCACHE_DIR = Join-Path $env:LOCALAPPDATA "esphome\Cache\arduino8266\ccache"
ccache -s
device-builder:
name: Test downstream esphome/device-builder
runs-on: ubuntu-24.04
@@ -1357,7 +1465,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@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
with:
enable-cache: true
# Pull request saves land in per-PR scopes nothing else can
@@ -1416,31 +1524,27 @@ jobs:
echo "::warning::ci_memory_impact_extract.py not found on target branch, skipping memory impact analysis"
fi
# Check if test files exist on the target branch for the requested
# components and platform. When a PR adds new test files for a platform,
# the target branch won't have them yet, so skip instead of failing.
# This check must be done here (not in determine-jobs.py) because
# determine-jobs runs on the PR branch and cannot see what the target
# branch has.
# The build below uses --base-only, so skip unless the target branch has
# test.<platform>.yaml for every requested component (test-*.yaml
# variants alone compile nothing, and a partial set would compare a
# smaller target build against the PR build). Checked here, not in
# determine-jobs.py, because determine-jobs runs on the PR branch and
# cannot see the target branch.
- name: Check for test files on target branch
id: check-tests
if: steps.check-script.outputs.skip != 'true'
run: |
components='${{ toJSON(fromJSON(needs.determine-jobs.outputs.memory_impact).components) }}'
platform="${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}"
found=false
missing=""
for component in $(echo "$components" | jq -r '.[]'); do
# Check for test files matching the platform (test.platform.yaml or test-*.platform.yaml)
for f in tests/components/${component}/test*.${platform}.yaml; do
if [ -f "$f" ]; then
found=true
break 2
fi
done
if [ ! -f "tests/components/${component}/test.${platform}.yaml" ]; then
missing="${missing} ${component}"
fi
done
if [ "$found" = false ]; then
if [ -n "$missing" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
echo "::warning::No test files found on target branch for platform ${platform}, skipping memory impact analysis"
echo "::warning::No test.${platform}.yaml on target branch for${missing}, skipping memory impact analysis"
else
echo "skip=false" >> $GITHUB_OUTPUT
fi
@@ -1560,7 +1664,7 @@ jobs:
fi
- name: Upload memory analysis JSON
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: memory-analysis-target
path: memory-analysis-target.json
@@ -1623,7 +1727,7 @@ jobs:
--platform "$platform"
- name: Upload memory analysis JSON
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: memory-analysis-pr
path: memory-analysis-pr.json
@@ -1653,13 +1757,13 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Download target analysis JSON
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: memory-analysis-target
path: ./memory-analysis
continue-on-error: true
- name: Download PR analysis JSON
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: memory-analysis-pr
path: ./memory-analysis
@@ -1703,6 +1807,7 @@ jobs:
- test-build-components-split
- test-esp32-platformio
- test-esp8266-native
- test-esp8266-native-windows
- device-builder
- memory-impact-target-branch
- memory-impact-pr-branch
@@ -35,7 +35,7 @@ jobs:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
with:
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
+2 -2
View File
@@ -56,7 +56,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -84,6 +84,6 @@ jobs:
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with:
category: "/language:${{matrix.language}}"
+15 -9
View File
@@ -15,7 +15,7 @@ jobs:
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
with:
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
@@ -125,14 +125,20 @@ jobs:
}
async function getEsphomeAndComponentChanges(github, owner, repo, prNumber) {
const changedFiles = await github.rest.pulls.listFiles({
owner: owner,
repo: repo,
pull_number: prNumber,
});
const changedFiles = await github.paginate(
github.rest.pulls.listFiles,
{
owner: owner,
repo: repo,
pull_number: prNumber,
per_page: 100,
}
);
const esphomeChanges = changedFiles.data
.filter(file => file.filename !== "esphome/core/defines.h" && file.filename.startsWith('esphome/'))
// Files used only for development and CI, which do not affect use as an external component
const ignoredFiles = ["esphome/core/defines.h", "esphome/idf_component.yml"];
const esphomeChanges = changedFiles
.filter(file => !ignoredFiles.includes(file.filename) && file.filename.startsWith('esphome/'))
.map(file => {
const match = file.filename.match(/esphome\/([^/]+)/);
return match ? match[1] : null;
@@ -144,7 +150,7 @@ jobs:
}
const uniqueEsphomeChanges = [...new Set(esphomeChanges)];
const componentChanges = changedFiles.data
const componentChanges = changedFiles
.filter(file => file.filename.startsWith('esphome/components/'))
.map(file => {
const match = file.filename.match(/esphome\/components\/([^/]+)\//);
+3 -3
View File
@@ -162,7 +162,7 @@ jobs:
# version: ${{ needs.init.outputs.tag }}
- name: Upload digests
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: digests-${{ matrix.platform.arch }}
path: /tmp/digests
@@ -195,7 +195,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download digests
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
pattern: digests-*
path: /tmp/digests
@@ -245,7 +245,7 @@ jobs:
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
with:
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
+15 -1
View File
@@ -16,16 +16,18 @@ jobs:
# No GITHUB_TOKEN permissions: the reusable workflow mints an ESPHome
# GitHub App token so the labels, comments and closures come from
# esphome[bot] instead of github-actions[bot].
uses: esphome/workflows/.github/workflows/stale.yml@cc3e76de337dc59bc1cba8da58d963cd23b873f1 # main
uses: esphome/workflows/.github/workflows/stale.yml@4fbce13026c393d6eb6b247e394dcd9ef1af76c2 # main
secrets:
ESPHOME_GITHUB_APP_PRIVATE_KEY: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
with:
# Live only on dev: a workflow_dispatch from any other branch is a dry run
dry-run: ${{ github.ref != 'refs/heads/dev' }}
days-before-stale: 90
days-before-stale-conflicting: 30
days-before-close: 7
stale-label: stale
exempt-label: not-stale
conflict-label: has-conflicts
ignored-users: esphbot,codecov-commenter
stale-pr-message: >
There hasn't been any activity on this pull request recently. This
@@ -36,6 +38,18 @@ jobs:
to keep it open. Also, please merge the latest dev branch into your
branch to ensure that it's up to date with the latest changes.
Thank you for your contribution!
conflict-pr-message: >
This pull request has merge conflicts. Please merge the latest dev
branch into your branch to resolve them.
stale-conflicting-pr-message: >
The merge conflicts on this pull request have not been resolved for
30 days, so it has been marked as stale and will be closed if they
are not resolved within 7 days.
Please merge the latest dev branch into your branch. Once the pull
request is mergeable again the stale label is removed.
Thank you for your contribution!
stale-issue-message: >
There hasn't been any activity on this issue recently. Due to the
@@ -7,10 +7,6 @@ on:
permissions:
pull-requests: read # issues.listLabelsOnIssue to detect blocking labels (needs-docs, needs-developer-docs, merge-after-release, chained-pr)
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
check:
name: Check blocking labels
@@ -15,6 +15,8 @@ on:
# base branch checkout, so fork code never runs with the token.
pull_request_target:
types: [opened, synchronize, reopened]
branches:
- dev
paths:
- requirements_dev.txt
- requirements_test.txt
@@ -41,7 +43,7 @@ jobs:
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
with:
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
+2 -2
View File
@@ -19,7 +19,7 @@ jobs:
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
with:
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
@@ -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@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
with:
enable-cache: true
# Pin uv version so the action does not have to fetch the
@@ -20,7 +20,7 @@ jobs:
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
with:
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
+2 -2
View File
@@ -10,7 +10,7 @@ ci:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.16.9
rev: v0.17.0
hooks:
# Run the linter.
- id: ruff
@@ -36,7 +36,7 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/asottile/pyupgrade
rev: v3.21.2
rev: v3.22.0
hooks:
- id: pyupgrade
args: [--py312-plus]
+130 -6
View File
@@ -67,6 +67,7 @@ file does, and it is the authority when they disagree. The most useful starting
- Function-local constants: `lower_snake_case`
- Protected/private fields: `lower_snake_case_with_trailing_underscore_`
- Favor descriptive names over abbreviations
- Device components are named brand plus model (`siemens_ira211`), without a protocol-family prefix
- Enumerator names: prefix every value of an `enum class` with the enum name converted to
`UPPER_SNAKE_CASE` (e.g. `UARTFlushResult::UART_FLUSH_RESULT_SUCCESS`). Never use bare
names like `SUCCESS`, `FAILURE`, `OK`, or `FAIL`: platform SDK headers define macros with
@@ -143,6 +144,9 @@ file does, and it is the authority when they disagree. The most useful starting
* **C++ Preprocessor Directives:**
* **Avoid `#define` for constants:** Using `#define` for constants is discouraged and should be replaced with `const` variables or enums.
* **A constant with a compile-time initializer is `static constexpr`,** not `static const`, at namespace scope
and inside functions; `static const` is only for a value computed at run time. A function-local
`static constexpr` table is a static constant to clang-tidy, so it is `UPPER_SNAKE_CASE` too.
* **Use `#define` only for:**
- Conditional compilation (`#ifdef`, `#ifndef`)
- Compile-time sizes calculated during Python code generation (e.g., configuring `std::array` or `StaticVector` dimensions via `cg.add_define()`)
@@ -177,6 +181,15 @@ file does, and it is the authority when they disagree. The most useful starting
Pick the primitive by cadence: under 250 ms use a gated `loop()`; 500 ms and above use
`set_interval`. Full reasoning, including why `set_interval` costs more below 500 ms:
https://developers.esphome.io/architecture/components/advanced/#quick-rule-of-thumb
* **Repeating work:** use `set_interval`, never a `set_timeout` that re-arms itself; every re-arm
rebuilds the callback and re-queues a scheduler item, and the period drifts.
* **Scheduler ids:** name a timer only when it must be cancelled or replaced, and use a
`static constexpr uint32_t` id, never a string. Ids are per component instance and cannot clash with other
components, so number them from 0 and keep all of a component's ids together in one place.
```cpp
static constexpr uint32_t READ_TIMEOUT_ID = 0;
this->set_timeout(READ_TIMEOUT_ID, 50, [this]() { this->read_(); });
```
* **Don't override a default with the same value:** if a base class method already returns what you
want, do not override it. `Component::get_setup_priority()` returns `setup_priority::DATA`, so a
component that wants `DATA` should simply leave it alone.
@@ -184,6 +197,45 @@ file does, and it is the authority when they disagree. The most useful starting
// Bad - this is exactly what the base class already does
float get_setup_priority() const override { return setup_priority::DATA; }
```
* **Constant data lives in RAM on ESP8266** unless marked `PROGMEM`: `const` tables, class-scope
constant arrays, and in-class `std::array` initializers (a `.rodata` template plus a copy). Mark the
table `PROGMEM` and read it with `progmem_memcpy()` or `progmem_read_byte()` (`esphome/core/hal.h`);
for a table of structs copy the whole entry out and return the copy, never a pointer into flash.
On other platforms they are a plain `memcpy` and dereference, so no `#ifdef` is needed. Declare log tags with
`ESPHOME_LOG_TAG(TAG, "name")`, pass status text as `LOG_STR`, e.g.
`this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL))`, and give an action whose payload is a byte array
a `TEMPLATABLE_BYTES` field so a static code stays in flash.
* **Fixed sets of names** (status words, model names): a `PROGMEM_STRING_TABLE` (`esphome/core/progmem.h`),
logged with `get_log_str` and `LOG_STR_ARG`, copied out with `ESPHOME_strncpy_P` before `publish_state`.
A `dump_config` helper that picks a name returns `const LogString *` from `LOG_STR(...)`.
* **CRCs and bit tricks** come from `esphome/core/helpers.h` (`crc8`, `crc16`, `reverse_bits`, `encode_uint16`);
pick the helper's polynomial and bit-order arguments instead of writing a CRC loop.
* **An unset float is `NAN`**, not `optional<float>`; the optional costs 4 to 8 bytes of padding per member.
* **`PollingComponent` first runs `update()`** after a random share of the interval; call `this->update()` at the
end of `setup()` when the entities need a state at boot.
* **A multi-step command sequence** leaves the step pending when the answer is malformed, so the existing
resend and give-up logic handles it; never advance on an answer that was only logged.
* **Entities that each watch a register field:** one class taking register, shift, mask and match from a Python
tuple table, in typed `StaticVector`s sized by `cg.slot_counter`; not a template per bit, not a virtual
listener base (a second vptr per entity).
* **A global feature define** says the code is compiled in, not that this instance uses it: keep the
per-instance check (`if (this->aux_device_ == ...)`) inside the `#ifdef`.
* **Model-specific code** sits behind a define the Python side emits only for configs that use that model
(`cg.add_define("USE_SELEC_METER_EM4M")`), so the other models' builds leave it out; the runtime model check
stays inside the `#ifdef`.
* **Failures after `setup()`:** `mark_failed()` is for `setup()` only. A runtime read or write failure
sets `status_set_warning()` and the next success clears it; writes skip work when `is_failed()`.
* **`dump_config()` already prints the failure reason:** for a failed component, core prints
`<component> is marked FAILED: <reason>` right after the component's `dump_config()`, using the reason
passed to `mark_failed()`. So `dump_config()` never checks `is_failed()` to log a failure, and no
`error_code_` member exists only to pick that message. Pass the reason where setup fails instead:
`this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL))` for a failed bus transfer, a short specific reason
otherwise (`LOG_STR("Wrong chip ID")`). Core also logs it at that moment, so no `ESP_LOGE` repeats it
before `mark_failed()`; keep a separate line only when it prints a value read at run time, such as the
chip ID that did not match. Only the first reason is kept, so do not call `status_set_error()` before
`mark_failed()` and do not mark the component failed again after a helper already did.
* **Fixed platform facts compile to nothing:** gate on `#ifdef USE_ESP8266` and friends, not on a
runtime check of something the build already knows.
* **Logging string literals:** wrap literals passed as `%s` arguments in `LOG_STR_LITERAL()` so they
can be stored in flash rather than RAM.
```cpp
@@ -230,8 +282,13 @@ file does, and it is the authority when they disagree. The most useful starting
```
* **Component Metadata:**
- `DOMAIN`: Required in every component's `__init__.py`, set to the component's own name as a plain
string (e.g. `DOMAIN = "my_component"`). Other code refers to the component through it, e.g.
`from esphome.components.my_component import DOMAIN`. Keep it in alphabetical order with the other
metadata constants. CI (`lint_component_domain`) fails without it.
- `DEPENDENCIES`: List of required components
- `AUTO_LOAD`: Components to automatically load
- `AUTO_LOAD`: Components to automatically load. Not a way to put entities under the component's
own key; see "Entities of a hub" below.
- `CONFLICTS_WITH`: Incompatible components
- `CODEOWNERS`: GitHub usernames responsible for maintenance
- `MULTI_CONF`: Whether multiple instances are allowed
@@ -243,6 +300,7 @@ file does, and it is the authority when they disagree. The most useful starting
import esphome.config_validation as cv
from esphome.const import CONF_KEY, CONF_ID
DOMAIN = "my_component"
CONF_PARAM = "param" # A constant that does not yet exist in esphome/const.py
my_component_ns = cg.esphome_ns.namespace("my_component")
@@ -322,6 +380,26 @@ file does, and it is the authority when they disagree. The most useful starting
var = await switch.new_switch(config)
```
- **Entities of a hub:** a new component's entities go in platform files (`sensor.py`,
`binary_sensor.py`, ...) that take the hub's id, never under the hub's own key with their platforms
added to `AUTO_LOAD` to make that work. Auto loading a platform because the C++ uses its type is
fine. See https://developers.esphome.io/architecture/components/#exposing-multiple-entities.
```yaml
# Bad - entities under the hub's key
my_hub:
connected:
name: Connected
# Good - the hub, then one platform entry that holds its entities
my_hub:
id: hub1
binary_sensor:
- platform: my_hub
my_hub_id: hub1 # optional with a single hub
connected:
name: Connected
```
- **Optional child entities of a hub:** bind the config once with `sensor.sub_sensors(config)` (or
`sub_binary_sensors`, `sub_text_sensors`, `sub_buttons`, `sub_switches`, `sub_numbers`,
`sub_selects` in their domains), adding `parent=hub` for entities that derive from `Parented<T>`,
@@ -332,15 +410,25 @@ file does, and it is the authority when they disagree. The most useful starting
itself, `hub` for one fetched with `cg.get_variable`) so the calls fit on one line. Loops whose
setter also takes an index, such as `set_gate_threshold(x, n)`, stay as they are.
```python
# my_hub/sensor.py
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
hub = await cg.get_variable(config[CONF_MY_HUB_ID])
sensors = sensor.sub_sensors(config)
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
await sensors(CONF_HUMIDITY, var.set_humidity_sensor)
buttons = button.sub_buttons(config, parent=var)
await buttons(CONF_RESTART, var.set_restart_button)
await sensors(CONF_TEMPERATURE, hub.set_temperature_sensor)
await sensors(CONF_HUMIDITY, hub.set_humidity_sensor)
# my_hub/button.py
async def to_code(config):
hub = await cg.get_variable(config[CONF_MY_HUB_ID])
buttons = button.sub_buttons(config, parent=hub)
await buttons(CONF_RESTART, hub.set_restart_button)
```
- **Pins and referenced components:** set them with `await cg.add_pin(var.set_cs_pin, config[CONF_CS_PIN])`
and `await cg.add_variable(var.set_bus, config[CONF_BUS_ID])`; both skip a `None` value, so pass
`config.get(KEY)` for an optional key, and return what they created. Use `cg.register_parented` for `set_parent`.
* **Automations (Triggers, Actions, Conditions):**
Automations have three building blocks: **Triggers** (fire when something happens), **Actions** (do something), and **Conditions** (check if something is true).
@@ -384,6 +472,25 @@ file does, and it is the authority when they disagree. The most useful starting
)
```
When the callback's parameters differ from the automation's arguments, or the trigger should only
fire for some values, pass `params`, `forward` and `when`; the helper then generates a capture-less
lambda (stored inline, nothing allocated). Name the parent with `automation.parent_ref(var)` and build
`forward` from `MockObj` calls, not f-strings of C++:
```python
parent = automation.parent_ref(var)
index = cg.RawExpression("index")
await automation.build_callback_automation(
var,
"add_on_state_callback",
[(cg.StringRef, "x"), (cg.size_t, "i")],
conf,
params=[(cg.size_t, "index")],
forward=[cg.StringRef(parent.option_at(index)), index],
)
```
`build_parent_callback_automation`, `build_trigger_callback`, `build_callback_automations` and
`build_trigger_automations` cover the other shapes; see their docstrings in `esphome/automation.py`.
**C++ -- no trigger class needed.** The callback registration method must be templatized to accept both `std::function` and lightweight forwarder structs (which avoid heap allocation):
```cpp
class MyComponent : public Component {
@@ -503,8 +610,12 @@ file does, and it is the authority when they disagree. The most useful starting
`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`.
An intentionally empty body (an unused `register_dumper` builder, for example) gets a one-line docstring, not `pass`.
* **Configuration Validation:**
* **One schema per model:** when a component's options depend on a `model` key, use `cv.typed_schema({...},
key=CONF_MODEL, default_type=...)` with each model's own keys and defaults, not one schema plus a validator
that rejects keys and fills defaults afterwards.
* **Reuse existing validators:** Before writing a custom validator, check for an existing one in `config_validation.py` and compose it in `cv.All(...)` rather than duplicating logic across components. For example, rename a config key with `cv.rename_key(CONF_OLD, CONF_NEW, removed_in="2026.6.0")`, and reject mutually-exclusive keys with `cv.has_at_most_one_key(...)` / `cv.has_exactly_one_key(...)`. See how `api` composes `cv.has_exactly_one_key` + `cv.rename_key`.
* **Common Validators:** `cv.int_`, `cv.float_`, `cv.string`, `cv.boolean`, `cv.int_range(min=0, max=100)`, `cv.positive_int`, `cv.percentage`.
* **Complex Validation:** `cv.All(cv.string, cv.Length(min=1, max=50))`, `cv.Any(cv.int_, cv.string)`.
@@ -538,6 +649,18 @@ file does, and it is the authority when they disagree. The most useful starting
* **Testing:**
* **Python:** Run unit tests with `pytest`.
* **C++:** Use `clang-tidy` for static analysis.
* **C++ unit tests:** gtests in `tests/components/<component>/*.cpp`, run with
`script/cpp_unit_test.py <component>`; see `tests/components/README.md`. Set the loop clock in a test
with `LoopBlockingGuard{nullptr, nullptr, now_ms}`; the runner resets it before every test.
* **Integration tests:** `tests/integration/` runs real firmware on the host. Serial components prove
timeouts and recovery with the `uart_mock` fixture (`tests/integration/test_uart_mock_*.py`). Its
`responses` match on content every time they see it; to answer a repeated request differently, use an
`on_tx` lambda with a static counter and `inject_to_rx_buffer` (see `uart_mock_ld2412_requery_after_giveup.yaml`).
* **Keep build-test variants few:** one `test.<platform>.yaml` per platform the code actually differs
on. A variant per board for platform-identical code only adds CI time.
* **A test that needs a shared component in a particular state** (`esp32_ble` security settings, an option
that PSRAM rejects) cannot be grouped: the merged block wins and the whole batch fails. Add the component to
`ISOLATED_COMPONENTS` in `script/analyze_component_buses.py`, or move the option to a `test-<variant>` file.
* **Component Tests:** YAML-based compilation tests are located in `tests/`. The structure is as follows:
```
tests/
@@ -584,6 +707,7 @@ file does, and it is the authority when they disagree. The most useful starting
name: My Sensor
```
Components that define buses directly are flagged as "NEEDS MIGRATION" and cannot be grouped, increasing CI build time.
`!extend` and `!remove` in a `test.*.yaml` also make the component ungroupable; add a standalone entity with its own id instead.
* **Testing All Components Together:** To verify that all components can be tested together without ID conflicts or configuration issues, use:
```bash
+83 -4
View File
@@ -30,6 +30,9 @@ esphome/components/airthings_ble/* @jeromelaban
esphome/components/airthings_wave_base/* @jeromelaban @ncareau
esphome/components/airthings_wave_mini/* @ncareau
esphome/components/airthings_wave_plus/* @jeromelaban @precurse
esphome/components/airton/* @Procsiab
esphome/components/airton/select/* @Procsiab
esphome/components/airton/switch/* @Procsiab
esphome/components/alarm_control_panel/* @grahambrown11 @hwstar
esphome/components/alpha3/* @jan-hofmeier
esphome/components/am2315c/* @swoboda1337
@@ -39,12 +42,17 @@ esphome/components/am43/sensor/* @buxtronix
esphome/components/analog_threshold/* @ianchi
esphome/components/animation/* @syndlex
esphome/components/anova/* @buxtronix
esphome/components/apc1/* @ademuri
esphome/components/apc_proteous/* @clydebarrow
esphome/components/apds9306/* @aodrenah
esphome/components/apds9930/* @iyesin
esphome/components/api/* @esphome/core
esphome/components/aqi/* @freekode @jasstrong @ximex
esphome/components/aranet/* @patrickod
esphome/components/as5600/* @ammmze
esphome/components/as5600/sensor/* @ammmze
esphome/components/as7341/* @mrgnr
esphome/components/as734x/* @latonita @mrgnr
esphome/components/async_tcp/* @esphome/core
esphome/components/at581x/* @X-Ryl669
esphome/components/atc_mithermometer/* @ahpohl
@@ -65,6 +73,7 @@ esphome/components/bedjet/climate/* @jhansche
esphome/components/bedjet/fan/* @jhansche
esphome/components/bedjet/sensor/* @javawizard @jhansche
esphome/components/beken_spi_led_strip/* @Mat931
esphome/components/bh1745/* @latonita
esphome/components/bh1750/* @OttoWinter
esphome/components/bh1900nux/* @B48D81EFCC
esphome/components/binary_sensor/* @esphome/core
@@ -100,6 +109,9 @@ esphome/components/bmp581_i2c/* @danielkent-net @kahrendt
esphome/components/bmp581_spi/* @danielkent-net @kahrendt
esphome/components/bp1658cj/* @Cossid
esphome/components/bp5758d/* @Cossid
esphome/components/bq25186/* @Rapsssito
esphome/components/bq27220/* @rggammon
esphome/components/bq76972/* @limpkin
esphome/components/bridge/* @kbx81
esphome/components/bthome_mithermometer/* @nagyrobi
esphome/components/button/* @esphome/core
@@ -148,6 +160,10 @@ esphome/components/delonghi/* @grob6000
esphome/components/dew_point/* @CFlix
esphome/components/dfplayer/* @glmnet
esphome/components/dfrobot_sen0395/* @niklasweber
esphome/components/dfrobot_sen0465_base/* @tzabbi
esphome/components/dfrobot_sen0465_i2c/* @tzabbi
esphome/components/dfrobot_sen0465_uart/* @tzabbi
esphome/components/dfrobot_sen0609/* @remcom
esphome/components/dht/* @OttoWinter
esphome/components/display_menu_base/* @numo68
esphome/components/dlms_meter/* @latonita @PolarGoose @SimonFischer04 @Tomer27cz
@@ -156,8 +172,10 @@ esphome/components/ds1307/* @badbadc0ffee
esphome/components/ds1603l/* @JakeLC15
esphome/components/ds2484/* @mrk-its
esphome/components/ds248x/* @tomwellnitz
esphome/components/ds3231/* @linkedupbits
esphome/components/dsmr/* @glmnet @PolarGoose
esphome/components/duty_time/* @dudanov
esphome/components/ecocomfort2/* @gledian
esphome/components/ee895/* @Stock-M
esphome/components/ektf2232/touchscreen/* @jesserockz
esphome/components/emc2101/* @ellull
@@ -174,6 +192,7 @@ esphome/components/es7243e/* @kbx81
esphome/components/es8156/* @kbx81
esphome/components/es8311/* @kahrendt @kroimon
esphome/components/es8388/* @P4uLT
esphome/components/es9038q2m_katana/* @GioMez
esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @bdraco @jesserockz @Rapsssito
esphome/components/esp32_ble_client/* @bdraco @jesserockz
@@ -183,26 +202,34 @@ esphome/components/esp32_camera_web_server/* @ayufan
esphome/components/esp32_can/* @Sympatron
esphome/components/esp32_hosted/* @swoboda1337
esphome/components/esp32_hosted/update/* @swoboda1337
esphome/components/esp32_power_management/* @rwrozelle
esphome/components/esp32_rmt/* @jesserockz
esphome/components/esp32_rmt_led_strip/* @jesserockz
esphome/components/esp8266/* @esphome/core
esphome/components/esp_ldo/* @clydebarrow
esphome/components/espectre/* @francescopace
esphome/components/esphome/ota/* @esphome/core
esphome/components/espnow/* @jesserockz
esphome/components/espnow/packet_transport/* @EasilyBoredEngineer
esphome/components/ethernet_info/* @gtjadsonsantos
esphome/components/event/* @nohat
esphome/components/exponential_moving_average/* @clydebarrow
esphome/components/exposure_notifications/* @OttoWinter
esphome/components/ezo/* @ssieb
esphome/components/ezo_pmp/* @carlos-sarmiento
esphome/components/ezsp_proxy_tap/* @kbx81 @puddly
esphome/components/factory_reset/* @anatoly-savchenkov
esphome/components/fastled_base/* @OttoWinter
esphome/components/feedback/* @ianchi
esphome/components/file/* @esphome/core
esphome/components/filter_lifetime/* @tuct
esphome/components/fingerprint_grow/* @alexborro @loongyh @OnFreund
esphome/components/font/* @clydebarrow @esphome/core
esphome/components/friedrich/* @rwrozelle
esphome/components/fs3000/* @kahrendt
esphome/components/ft5x06/* @clydebarrow
esphome/components/ft63x6/* @gpambrozio
esphome/components/fusb302b/* @remcom
esphome/components/gcja5/* @gcormier
esphome/components/gdk101/* @Szewcson
esphome/components/gl_r01_i2c/* @pkejval
@@ -224,9 +251,14 @@ esphome/components/gt911/* @clydebarrow @jesserockz
esphome/components/haier/* @paveldn
esphome/components/haier/binary_sensor/* @paveldn
esphome/components/haier/button/* @paveldn
esphome/components/haier/select/* @paveldn
esphome/components/haier/sensor/* @paveldn
esphome/components/haier/switch/* @paveldn
esphome/components/haier/text_sensor/* @paveldn
esphome/components/haier_tundra/* @fauxpark
esphome/components/haier_tundra/button/* @fauxpark
esphome/components/haier_tundra/switch/* @fauxpark
esphome/components/haier_water_heater/* @TheLexus
esphome/components/havells_solar/* @sourabhjaiswal
esphome/components/hbridge/fan/* @WeekendWarrior
esphome/components/hbridge/light/* @DotNetDann
@@ -235,6 +267,7 @@ esphome/components/hc8/* @omartijn
esphome/components/hdc2010/* @optimusprimespace @ssieb
esphome/components/hdc2080/* @G-Pereira @jesserockz
esphome/components/hdc302x/* @joshuasing
esphome/components/hdmi_cec/* @JosVanEijndhoven
esphome/components/he60r/* @clydebarrow
esphome/components/heatpumpir/* @rob-deutsch
esphome/components/hitachi_ac424/* @sourabhjaiswal
@@ -245,8 +278,12 @@ esphome/components/hmac_md5/* @dwmw2
esphome/components/hmac_sha256/* @dwmw2
esphome/components/hoermann_hcp/* @zweckj
esphome/components/homeassistant/* @esphome/core @OttoWinter
esphome/components/homeassistant/button/* @jesserockz
esphome/components/homeassistant/light/* @egormanga
esphome/components/homeassistant/number/* @landonr
esphome/components/homeassistant/select/* @jesserockz
esphome/components/homeassistant/switch/* @Links2004
esphome/components/homeassistant/text/* @jesserockz
esphome/components/honeywell_hih_i2c/* @Benichou34
esphome/components/honeywellabp/* @RubyBailey
esphome/components/honeywellabp2_i2c/* @jpfaff
@@ -258,6 +295,7 @@ esphome/components/http_request/ota/* @oarcher
esphome/components/http_request/update/* @jesserockz
esphome/components/htu31d/* @betterengineering
esphome/components/hub75/* @stuartparmenter
esphome/components/husb238/* @latonita
esphome/components/hydreon_rgxx/* @functionpointer
esphome/components/hyt271/* @Philippe12
esphome/components/i2c/* @esphome/core
@@ -265,7 +303,9 @@ esphome/components/i2c_device/* @gabest11
esphome/components/i2s_audio/* @jesserockz
esphome/components/i2s_audio/microphone/* @jesserockz
esphome/components/i2s_audio/speaker/* @jesserockz @kahrendt
esphome/components/i2s_clockless_led_strip/* @j9brown
esphome/components/iaqcore/* @yozik04
esphome/components/icm20689/* @lboue
esphome/components/icnt86/* @danepowell
esphome/components/ili9xxx/* @clydebarrow @nielsnl68
esphome/components/improv_base/* @esphome/core
@@ -282,6 +322,7 @@ esphome/components/inkplate/* @jesserockz @JosipKuci
esphome/components/integration/* @OttoWinter
esphome/components/internal_temperature/* @Mat931
esphome/components/interval/* @esphome/core
esphome/components/ir_rf_base/* @bdraco @kbx81
esphome/components/ir_rf_proxy/* @kbx81
esphome/components/it8951/* @koosoli @limengdu @Passific
esphome/components/jsn_sr04t/* @Mafus1
@@ -293,9 +334,11 @@ esphome/components/kuntze/* @ssieb
esphome/components/lc709203f/* @ilikecake
esphome/components/lcd_menu/* @numo68
esphome/components/ld2410/* @regevbr @sebcaps
esphome/components/ld2410s/* @NovakIrs
esphome/components/ld2412/* @Rihan9
esphome/components/ld2420/* @descipher
esphome/components/ld2450/* @hareeshmu
esphome/components/ld2460/* @jesusvallejo
esphome/components/ld24xx/* @kbx81
esphome/components/ld6002b/* @hepter
esphome/components/ledc/* @OttoWinter
@@ -304,6 +347,10 @@ esphome/components/libretiny_pwm/* @kuba2k2
esphome/components/light/* @esphome/core
esphome/components/lightwaverf/* @max246
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
esphome/components/lis2dw12/* @latonita
esphome/components/lis2dw12_i2c/* @latonita
esphome/components/lis2dw12_spi/* @latonita
esphome/components/lis3dh/* @zebble
esphome/components/lm75b/* @beormund
esphome/components/ln882h_ble/* @Bl00d-B0b
esphome/components/ln882h_ble_tracker/* @Bl00d-B0b
@@ -311,6 +358,7 @@ esphome/components/ln882x/* @lamauny
esphome/components/lock/* @esphome/core
esphome/components/logger/* @esphome/core
esphome/components/logger/select/* @clydebarrow
esphome/components/lp5562/* @DennisGaida
esphome/components/lps22/* @nagisa
esphome/components/lsm6ds/* @clydebarrow
esphome/components/ltr390/* @latonita @sjtrny
@@ -321,7 +369,9 @@ esphome/components/m5stack_8angle/* @rnauber
esphome/components/mapping/* @clydebarrow
esphome/components/matrix_keypad/* @ssieb
esphome/components/max17043/* @blacknell
esphome/components/max17048/* @B48D81EFCC
esphome/components/max31865/* @DAVe3283
esphome/components/max31888/* @JiriPrchal
esphome/components/max44009/* @berfenger
esphome/components/max6956/* @looping40
esphome/components/max7219digit/* @rspaargaren
@@ -340,7 +390,6 @@ esphome/components/mcp4461/* @p1ngb4ck
esphome/components/mcp4728/* @berfenger
esphome/components/mcp47a1/* @jesserockz
esphome/components/mcp9600/* @mreditor97
esphome/components/mcp9808/* @k7hpn
esphome/components/md5/* @esphome/core
esphome/components/mdns/* @esphome/core
esphome/components/media_player/* @jesserockz
@@ -349,11 +398,14 @@ esphome/components/micro_wake_word/* @jesserockz @kahrendt
esphome/components/micronova/* @edenhaus @jorre05
esphome/components/microphone/* @jesserockz @kahrendt
esphome/components/mics_4514/* @jesserockz
esphome/components/mics_5524/* @nliaudat
esphome/components/midea/* @dudanov
esphome/components/midea_ir/* @dudanov
esphome/components/midea_portasplit/* @Fexiven
esphome/components/mipi_dsi/* @clydebarrow
esphome/components/mipi_rgb/* @clydebarrow
esphome/components/mipi_spi/* @clydebarrow
esphome/components/mistral_ir/* @lerhum
esphome/components/mitsubishi/* @RubyBailey
esphome/components/mitsubishi_cn105/* @crnjan
esphome/components/mixer/speaker/* @kahrendt
@@ -380,6 +432,9 @@ esphome/components/mpl3115a2/* @kbickar
esphome/components/mpu6886/* @fabaff
esphome/components/ms8607/* @e28eta
esphome/components/msa3xx/* @latonita
esphome/components/mt6701/* @slimcdk
esphome/components/mt6701_i2c/* @slimcdk
esphome/components/mt6701_spi/* @slimcdk
esphome/components/nau7802/* @cujomalainey
esphome/components/network/* @esphome/core
esphome/components/nextion/* @edwardtfn @senexcrenshaw
@@ -392,6 +447,8 @@ esphome/components/noblex/* @AGalfra
esphome/components/noise/* @esphome/core
esphome/components/npi19/* @bakerkj
esphome/components/nrf52/* @tomaszduda23
esphome/components/ns2009/* @tchilov
esphome/components/ns2009/touchscreen/* @tchilov
esphome/components/number/* @esphome/core
esphome/components/one_wire/* @ssieb
esphome/components/online_image/* @clydebarrow @guillempages
@@ -402,10 +459,12 @@ esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core
esphome/components/packet_transport/* @clydebarrow
esphome/components/pca6416a/* @Mat931
esphome/components/pca9505/* @ajfriesen @ssieb
esphome/components/pca9554/* @bdraco @clydebarrow @hwstar
esphome/components/pcf85063/* @brogon
esphome/components/pcf8563/* @KoenBreeman
esphome/components/pcm5122/* @remcom
esphome/components/pd_pioneer_ir/* @martinsah
esphome/components/pi4ioe5v6408/* @jesserockz
esphome/components/pid/* @OttoWinter
esphome/components/pipsolar/* @andreashergert1984
@@ -424,6 +483,7 @@ 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_management/* @rwrozelle
esphome/components/power_supply/* @esphome/core
esphome/components/preferences/* @esphome/core
esphome/components/provisioning/* @esphome/core
@@ -444,6 +504,8 @@ esphome/components/rc522/* @glmnet
esphome/components/rc522_i2c/* @glmnet
esphome/components/rc522_spi/* @glmnet
esphome/components/rd03d/* @jasstrong
esphome/components/remko_ar715/* @naKruul
esphome/components/renogy_inverter_ble/* @emilioaray-dev
esphome/components/resampler/speaker/* @kahrendt
esphome/components/restart/* @esphome/core
esphome/components/rf_bridge/* @jesserockz
@@ -457,12 +519,15 @@ esphome/components/rp2040_pwm/* @jesserockz
esphome/components/rp2_ble_tracker/* @bdraco
esphome/components/rpi_dpi_rgb/* @clydebarrow
esphome/components/rtl87xx/* @kuba2k2
esphome/components/rtsp_server/* @max246
esphome/components/rtttl/* @glmnet @ximex
esphome/components/runtime_image/* @clydebarrow @guillempages @kahrendt
esphome/components/runtime_stats/* @bdraco
esphome/components/rx8025t/* @remcom
esphome/components/rx8111/* @techdregs
esphome/components/rx8130/* @beormund
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
esphome/components/samsung/* @jorofi
esphome/components/scd4x/* @martgras @sjtrny
esphome/components/script/* @esphome/core
esphome/components/sdl/* @bdm310 @clydebarrow
@@ -491,11 +556,14 @@ esphome/components/sfa30/* @ghsensdev
esphome/components/sfa40/* @NoQuarrel
esphome/components/sgp40/* @SenexCrenshaw
esphome/components/sgp4x/* @martgras @SenexCrenshaw
esphome/components/sh1122_base/* @pl4nkton
esphome/components/sh1122_spi/* @pl4nkton
esphome/components/sha256/* @esphome/core
esphome/components/shelly_dimmer/* @edge90 @rnauber
esphome/components/sht3xd/* @mrtoy-me
esphome/components/sht4x/* @sjtrny
esphome/components/shutdown/* @esphome/core @jsuanet
esphome/components/siemens_ira211/* @fonix232
esphome/components/sigma_delta_output/* @Cat-Ion
esphome/components/sim800l/* @glmnet
esphome/components/sm10bit_base/* @Cossid
@@ -512,6 +580,7 @@ esphome/components/sound_level/* @kahrendt
esphome/components/spa06_base/* @danielkent-net
esphome/components/spa06_i2c/* @danielkent-net
esphome/components/spa06_spi/* @danielkent-net
esphome/components/spd2010/* @grischard
esphome/components/speaker/* @jesserockz @kahrendt
esphome/components/speaker/media_player/* @kahrendt @synesthesiam
esphome/components/speaker_source/* @kahrendt
@@ -533,6 +602,7 @@ esphome/components/ssd1331_spi/* @kbx81
esphome/components/ssd1351_base/* @kbx81
esphome/components/ssd1351_spi/* @kbx81
esphome/components/st7123/* @miniskipper
esphome/components/st7305/* @clydebarrow
esphome/components/st7567_base/* @latonita
esphome/components/st7567_i2c/* @latonita
esphome/components/st7567_spi/* @latonita
@@ -557,12 +627,13 @@ esphome/components/t6615/* @tylermenezes
esphome/components/tas2780/* @remcom
esphome/components/tas58xx/* @mrtoy-me @remcom
esphome/components/tc74/* @sethgirvan
esphome/components/tca8418/* @zebble
esphome/components/tca9548a/* @andreashergert1984
esphome/components/tca9555/* @mobrembski
esphome/components/tcl112/* @glmnet
esphome/components/tcp_uart/* @Bascht74
esphome/components/tee501/* @Stock-M
esphome/components/teleinfo/* @0hax
esphome/components/teleinfo/* @0hax @FredM67
esphome/components/tem3200/* @bakerkj
esphome/components/template/alarm_control_panel/* @grahambrown11 @hwstar
esphome/components/template/datetime/* @rfdarter
@@ -574,12 +645,15 @@ esphome/components/thermopro_ble/* @sittner
esphome/components/thermostat/* @kbx81
esphome/components/time/* @esphome/core
esphome/components/tinyusb/* @kbx81
esphome/components/tinyusb_keyboard/* @frederikbolding
esphome/components/tlc5947/* @rnauber
esphome/components/tlc5971/* @IJIJI
esphome/components/tm1621/* @Philippe12
esphome/components/tm1637/* @glmnet
esphome/components/tm1638/* @skykingjwc
esphome/components/tm1651/* @mrtoy-me
esphome/components/tmc2209/* @remcom
esphome/components/tmc22xx/* @remcom
esphome/components/tmp102/* @timsavage
esphome/components/tmp1075/* @sybrenstuvel
esphome/components/tmp117/* @Azimath
@@ -608,8 +682,10 @@ esphome/components/ufire_ec/* @pvizeli
esphome/components/ufire_ise/* @pvizeli
esphome/components/ufm01/* @ljungqvist
esphome/components/ultrasonic/* @ssieb @swoboda1337
esphome/components/unicode/* @esphome/core
esphome/components/update/* @jesserockz
esphome/components/uponor_smatrix/* @kroimon
esphome/components/usb_audio/* @ab-tools
esphome/components/usb_cdc_acm/* @kbx81
esphome/components/usb_host/* @clydebarrow
esphome/components/usb_uart/* @clydebarrow
@@ -618,10 +694,11 @@ esphome/components/vbus/* @ssieb
esphome/components/veml3235/* @kbx81
esphome/components/veml7700/* @latonita
esphome/components/version/* @esphome/core
esphome/components/vevor_dtx/* @Bofr5555
esphome/components/voice_assistant/* @jesserockz @kahrendt
esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
esphome/components/watchdog/* @oarcher
esphome/components/water_heater/* @dhoeben
esphome/components/water_heater/* @esphome/core
esphome/components/waveshare_epaper/* @clydebarrow
esphome/components/waveshare_io_ch32v003/* @latonita
esphome/components/web_server/ota/* @esphome/core
@@ -630,7 +707,7 @@ esphome/components/web_server_idf/* @dentra
esphome/components/weikai/* @DrCoolZic
esphome/components/weikai_i2c/* @DrCoolZic
esphome/components/weikai_spi/* @DrCoolZic
esphome/components/whirlpool/* @glmnet
esphome/components/whirlpool/* @arpena @glmnet
esphome/components/whynter/* @aeonsablaze
esphome/components/wiegand/* @ssieb
esphome/components/wireguard/* @droscy @lhoracek @thomas0bernard
@@ -651,6 +728,7 @@ esphome/components/xiaomi_body_scale/* @dckiller51
esphome/components/xiaomi_hhccjcy10/* @fariouche
esphome/components/xiaomi_lywsd02mmc/* @juanluss31
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
esphome/components/xiaomi_mccgq02hl/* @ahpohl @morph027
esphome/components/xiaomi_mhoc303/* @drug123
esphome/components/xiaomi_mhoc401/* @vevsvevs
esphome/components/xiaomi_rtcgq02lm/* @jesserockz
@@ -659,6 +737,7 @@ esphome/components/xl9535/* @mreditor97
esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68
esphome/components/xxtea/* @clydebarrow
esphome/components/zephyr/* @tomaszduda23
esphome/components/zephyr_coredump/* @tomaszduda23
esphome/components/zephyr_mcumgr/ota/* @tomaszduda23
esphome/components/zephyr_pwm/* @wiomoc
esphome/components/zhlt01/* @cfeenstra1024
+1 -1
View File
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 2026.10.0-dev
PROJECT_NUMBER = 2026.11.0-dev
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
+1 -1
View File
@@ -22,7 +22,7 @@ RUN \
-r /requirements.txt
# Install the ESPHome Device Builder dashboard.
RUN uv pip install --no-cache-dir esphome-device-builder==1.19.0
RUN uv pip install --no-cache-dir esphome-device-builder==1.24.0
RUN \
platformio settings set enable_telemetry No \
+225 -34
View File
@@ -7,6 +7,7 @@ import importlib
import logging
import os
from pathlib import Path
import platform
import re
import sys
import time
@@ -47,14 +48,18 @@ from esphome.const import (
CONF_WEB_SERVER,
CONF_WIFI,
ENV_NOGITIGNORE,
KEY_CORE,
KEY_ESP32,
KEY_TARGET_PLATFORM,
KEY_VARIANT,
PLATFORM_HOST,
SECRETS_FILES,
ErrorFormat,
Toolchain,
)
from esphome.core import CORE, EsphomeError, coroutine
from esphome.enum import StrEnum
from esphome.helpers import get_bool_env, indent, is_ip_address
from esphome.helpers import IS_MACOS, get_bool_env, indent, is_ip_address
from esphome.log import AnsiFore, color, setup_log
from esphome.stacktrace import LogLineProcessor
from esphome.types import ConfigType
@@ -68,6 +73,7 @@ from esphome.util import (
get_serial_ports,
is_picotool_usb_permission_error,
list_yaml_files,
read_secret_line,
run_external_command,
run_external_process,
safe_print,
@@ -477,12 +483,9 @@ def has_ota() -> bool:
def has_native_ota() -> bool:
"""Check if native API OTA upload is available (``platform: esphome``)."""
if CONF_OTA not in CORE.config:
return False
return any(
ota_item.get(CONF_PLATFORM) == CONF_ESPHOME
for ota_item in CORE.config[CONF_OTA]
)
from esphome.espota2 import esphome_ota_items
return bool(esphome_ota_items(CORE.config))
def has_web_server_ota() -> bool:
@@ -788,6 +791,7 @@ def write_cpp(config: ConfigType) -> int:
if not get_bool_env(ENV_NOGITIGNORE):
writer.write_gitignore()
writer.check_build_tree_not_tracked()
generate_cpp_contents(config)
return write_cpp_file()
@@ -829,9 +833,9 @@ def write_cpp_file() -> int:
def compile_program(args: ArgsProtocol, config: ConfigType) -> int:
if CORE.skip_bootloader and not (CORE.is_esp32 and CORE.using_toolchain_esp_idf):
# Info, not a warning: an orchestrator cannot see YAML toolchain
# overrides, this is its expected no-op, and a full build is safe.
_LOGGER.info(
# Debug only: an orchestrator cannot see YAML toolchain overrides,
# so this is its expected no-op, and a full build is safe.
_LOGGER.debug(
"--skip-bootloader ignored: only supported on ESP32 with the "
"esp-idf toolchain"
)
@@ -1230,14 +1234,28 @@ def upload_program(
config: ConfigType, args: ArgsProtocol, devices: list[str]
) -> tuple[int, str | None]:
host = devices[0]
port_type = get_port_type(host)
ota_key = getattr(args, "ota_key", None)
if ota_key and port_type in (PortType.SERIAL, PortType.BOOTSEL):
_LOGGER.warning(
"The presented OTA key only applies to network uploads; ignored for %s",
host,
)
platform_upload = platform_hooks.get_platform_hook(
CORE.target_platform, "upload_program"
)
if platform_upload is not None and platform_upload(config, args, host):
if ota_key and port_type not in (PortType.SERIAL, PortType.BOOTSEL):
# A platform's own network path (nrf52 mcumgr) has no key handshake
_LOGGER.warning(
"The presented OTA key only applies to the %s OTA platform; "
"ignored for %s",
CONF_ESPHOME,
host,
)
return 0, host
port_type = get_port_type(host)
# MQTT and MQTTIP are also OTA paths; MQTTIP gets resolved to a real IP later by
# _resolve_network_devices(). Only SERIAL and BOOTSEL are non-OTA upload paths.
is_partition_table = getattr(args, "partition_table", False)
@@ -1280,6 +1298,8 @@ def upload_program(
network_devices = _resolve_network_devices(devices, config, args)
if chosen_platform == CONF_WEB_SERVER:
if ota_key:
raise EsphomeError(OTA_KEY_PLATFORM_ERROR)
if is_partition_table or is_bootloader:
raise EsphomeError(
f"{option_string} is only supported with the esphome OTA platform; "
@@ -1293,6 +1313,17 @@ def upload_program(
return _upload_via_native_api(config, network_devices, args)
def _ota_upload_platforms(config: ConfigType) -> dict[str, None]:
"""The network OTA platforms the config provides, one entry each, in
config order; the web_server final-validate hook merges duplicates."""
return {
ota_platform: None
for ota_item in config.get(CONF_OTA, [])
if (ota_platform := ota_item.get(CONF_PLATFORM))
in (CONF_ESPHOME, CONF_WEB_SERVER)
}
def _choose_ota_platform(config: ConfigType, requested: str | None) -> str:
"""Pick the OTA platform to use, optionally honoring ``--ota-platform``.
@@ -1305,16 +1336,7 @@ def _choose_ota_platform(config: ConfigType, requested: str | None) -> str:
stream on ESP32/LibreTiny, which inflate it as it arrives.) Falls back to
``web_server`` only when that is the only available platform.
"""
# Use a dict (insertion-ordered) instead of a list so error messages and
# membership checks see one entry per platform even if the user has
# multiple ``ota:`` items of the same platform; the web_server OTA
# platform's final-validate hook merges duplicates anyway.
available: dict[str, None] = {}
for ota_item in config.get(CONF_OTA, []):
platform = ota_item.get(CONF_PLATFORM)
if platform in (CONF_ESPHOME, CONF_WEB_SERVER):
available[platform] = None
available = _ota_upload_platforms(config)
if not available:
raise EsphomeError(
f"Cannot upload Over the Air as the {CONF_OTA} configuration is not "
@@ -1335,14 +1357,17 @@ def _choose_ota_platform(config: ConfigType, requested: str | None) -> str:
return CONF_WEB_SERVER
def _esphome_ota_conf(config: ConfigType) -> ConfigType | None:
"""The validated esphome OTA platform block, if configured."""
from esphome.espota2 import esphome_ota_items
return next(iter(esphome_ota_items(config)), None)
def _upload_via_native_api(
config: ConfigType, network_devices: list[str], args: ArgsProtocol
) -> tuple[int, str | None]:
ota_conf: ConfigType = {}
for ota_item in config.get(CONF_OTA, []):
if ota_item.get(CONF_PLATFORM) == CONF_ESPHOME:
ota_conf = ota_item
break
ota_conf = _esphome_ota_conf(config) or {}
from esphome import espota2
from esphome.components.noise import static_encryption_key
@@ -1352,9 +1377,15 @@ def _upload_via_native_api(
# Fail closed: an encryption block whose key did not resolve must never
# fall back to a plaintext upload
noise_psk = None
old_noise_psk = None
plaintext_fallback = False
allow_plaintext_upload = False
if (encryption_conf := ota_conf.get(CONF_ENCRYPTION)) is not None:
if ota_key := getattr(args, "ota_key", None):
# The user presents a key the device still runs instead of the
# configured one (a key change in progress); an explicit key never
# downgrades to plaintext
noise_psk = ota_key
elif (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)
@@ -1367,6 +1398,8 @@ def _upload_via_native_api(
# Ensure the key is a string, as required by the underlying OTA implementation.
# It arrives here as a SensitiveStr which aioesphomeapi rejects.
noise_psk = str(noise_psk)
if old_key := encryption_conf.get(espota2.CONF_OLD_KEY):
old_noise_psk = str(old_key)
elif api_key := static_encryption_key(config.get(CONF_API) or {}):
# Remove before 2027.3.0: the api key is tried, falling back to plaintext
noise_psk = str(api_key)
@@ -1416,19 +1449,30 @@ def _upload_via_native_api(
noise_psk,
plaintext_fallback=plaintext_fallback,
allow_plaintext_upload=allow_plaintext_upload,
old_noise_psk=old_noise_psk,
)
def _old_key_notice(config: ConfigType) -> None:
"""After `run`, whose image was just built from this config; a plain
`upload` may send a stale image that still carries the previous key."""
from esphome import espota2
for item in espota2.esphome_ota_items(config):
if (item.get(CONF_ENCRYPTION) or {}).get(espota2.CONF_OLD_KEY):
_LOGGER.warning(espota2.OLD_KEY_REMOVE_NOTICE)
def _upload_via_web_server(
config: ConfigType, network_devices: list[str], binary: Path
) -> tuple[int, str | None]:
from esphome import web_server_ota
from esphome.espota2 import esphome_ota_items
from esphome.web_server_helpers import get_web_server_connection
if any(
ota_item.get(CONF_PLATFORM) == CONF_ESPHOME
and ota_item.get(CONF_ENCRYPTION) is not None
for ota_item in config.get(CONF_OTA, [])
ota_item.get(CONF_ENCRYPTION) is not None
for ota_item in esphome_ota_items(config)
):
_LOGGER.warning(
"This config has OTA encryption, but the web_server OTA path sends "
@@ -1736,7 +1780,60 @@ def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
return 0
ENV_OTA_KEY = "ESPHOME_OTA_KEY"
OTA_KEY_PLATFORM_ERROR = (
f"--prompt-ota-key and {ENV_OTA_KEY} only apply to the {CONF_ESPHOME} OTA "
"platform; to flash by serial instead, name the port with --device"
)
def _read_ota_key(args: ArgsProtocol, config: ConfigType) -> None:
"""Take the key to present to the device for this upload, from the
prompt or from ESPHOME_OTA_KEY, before any compile so the prompt is
not buried in build output."""
prompt = getattr(args, "prompt_ota_key", False)
env_key = getattr(args, "env_ota_key", None)
if not prompt and env_key is None:
return
if CORE.data.get(KEY_CORE, {}).get(KEY_TARGET_PLATFORM) == PLATFORM_HOST:
_LOGGER.warning("A host program runs locally; the presented OTA key is ignored")
return
# The HTTP path has no key handshake; decide before prompting or
# compiling. A serial target is a serial flash: the key is not even
# asked for, and --ota-platform means nothing to it. Every entry must
# be serial: a missing port is skipped and the next entry may be a host
devices = getattr(args, "device", None) or []
serial_target = bool(devices) and all(
device == "SERIAL"
or get_port_type(device) in (PortType.SERIAL, PortType.BOOTSEL)
for device in devices
)
chosen = (
_choose_ota_platform(config, getattr(args, "ota_platform", None))
if not serial_target and _ota_upload_platforms(config)
else None
)
if chosen == CONF_WEB_SERVER:
raise EsphomeError(OTA_KEY_PLATFORM_ERROR)
from esphome.components.noise import validate_encryption_key
from esphome.config_validation import Invalid
if serial_target:
_LOGGER.warning(
"The presented OTA key only applies to network uploads; ignored for %s",
", ".join(devices),
)
return
key = read_secret_line("OTA encryption key: ") if prompt else env_key
try:
validate_encryption_key(key)
except Invalid as err:
raise EsphomeError(f"Invalid OTA encryption key: {err}") from err
args.ota_key = key
def command_upload(args: ArgsProtocol, config: ConfigType) -> int | None:
_read_ota_key(args, config)
# Get devices, resolving special identifiers like OTA
devices = choose_upload_log_host(
default=args.device,
@@ -1784,6 +1881,7 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
# Elsewhere the flag is ignored, so serial stays fine there.
_LOGGER.error("--skip-bootloader builds cannot be flashed over serial")
return 1
_read_ota_key(args, config)
exit_code = write_cpp(config)
if exit_code != 0:
return exit_code
@@ -1809,6 +1907,7 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
exit_code, successful_device = upload_program(config, args, devices)
if exit_code == 0:
_LOGGER.info("Successfully uploaded program.")
_old_key_notice(config)
else:
_LOGGER.warning("Failed to upload to %s", devices)
return exit_code
@@ -1945,7 +2044,10 @@ def run_multiple_configs(
safe_print()
cmd = command_builder(f)
rc = run_external_process(*cmd)
# The parent already logged the Intel macOS warning; children skip it.
rc = run_external_process(
*cmd, env={**os.environ, _INTEL_MACOS_WARNED_ENV: "1"}
)
if rc == 0:
print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {str(f)}")
@@ -2201,6 +2303,25 @@ def _add_states_args(parser: argparse.ArgumentParser) -> None:
)
def _error_format(value: str) -> ErrorFormat:
try:
return ErrorFormat(value.lower())
except ValueError:
choices = ", ".join(ErrorFormat)
raise argparse.ArgumentTypeError(
f"invalid choice: '{value}' (choose from {choices})"
) from None
ARGUMENT_HELP_PROMPT_OTA_KEY = (
"Ask for the OTA encryption key to present to the device instead of using "
"the one in the configuration, for a device that still runs a previous key. "
"The key is read without echo, or as one line from stdin when that is not "
"a terminal; it is never taken from the command line. A calling program "
f"can hand the key over in the {ENV_OTA_KEY} environment variable instead."
)
def parse_args(argv):
options_parser = argparse.ArgumentParser(add_help=False)
options_parser.add_argument(
@@ -2263,6 +2384,17 @@ def parse_args(argv):
),
)
options_parser.add_argument(
"--error-format",
type=_error_format,
metavar="{" + ",".join(f.value for f in ErrorFormat) + "}",
help=(
"How configuration errors are shown. 'yaml' shows them inside the "
"failing YAML; 'line' prints one 'file:line:column: error: message' "
"line per error. Default: $ESPHOME_ERROR_FORMAT, else 'yaml'."
),
)
parser = argparse.ArgumentParser(
description=f"ESPHome {const.__version__}", parents=[options_parser]
)
@@ -2358,6 +2490,11 @@ def parse_args(argv):
help="Upload as partition table (OTA).",
action="store_true",
)
parser_upload.add_argument(
"--prompt-ota-key",
action="store_true",
help=ARGUMENT_HELP_PROMPT_OTA_KEY,
)
parser_upload.add_argument(
"--bootloader",
help="Upload as bootloader (OTA).",
@@ -2438,6 +2575,11 @@ def parse_args(argv):
"(HTTP Basic auth) when that is the only configured platform."
),
)
parser_run.add_argument(
"--prompt-ota-key",
action="store_true",
help=ARGUMENT_HELP_PROMPT_OTA_KEY,
)
parser_clean = subparsers.add_parser(
"clean-mqtt",
@@ -2566,9 +2708,18 @@ def parse_args(argv):
args, unknown_args = parser.parse_known_args(arguments)
if unknown_args:
_LOGGER.warning("Ignored unrecognized arguments: %s", unknown_args)
return args
else:
args = parser.parse_args(arguments)
return parser.parse_args(arguments)
# Read here rather than as the option default, so a bad value is reported
# against the environment variable instead of --error-format
if args.error_format is None:
env_value = os.getenv("ESPHOME_ERROR_FORMAT", ErrorFormat.YAML.value)
try:
args.error_format = _error_format(env_value)
except argparse.ArgumentTypeError as err:
parser.error(f"ESPHOME_ERROR_FORMAT: {err}")
return args
def _warn_if_source_tree_mismatch() -> None:
@@ -2614,12 +2765,34 @@ def _warn_if_source_tree_mismatch() -> None:
)
_INTEL_MACOS_REMOVAL = "2027.6.0"
_INTEL_MACOS_WARNED_ENV = "ESPHOME_INTEL_MACOS_WARNED"
def _warn_if_intel_macos() -> None:
"""Warn that Intel (x86_64) Python on macOS loses support by _INTEL_MACOS_REMOVAL."""
if (
not IS_MACOS
or platform.machine() != "x86_64"
or _INTEL_MACOS_WARNED_ENV in os.environ
):
return
_LOGGER.warning(
"Support for Intel Macs will end in ESPHome %s or earlier. The Python "
"packages ESPHome depends on have stopped publishing Intel macOS builds, "
"so future releases will not install on this machine. On an Apple "
"Silicon Mac, switch to a native arm64 Python.",
_INTEL_MACOS_REMOVAL,
)
def run_esphome(argv):
from esphome.address_cache import AddressCache
args = parse_args(argv)
CORE.dashboard = args.dashboard
CORE.testing_mode = args.testing_mode
CORE.error_format = args.error_format
# Create address cache from command-line arguments
CORE.address_cache = AddressCache.from_cli_args(
@@ -2632,7 +2805,25 @@ def run_esphome(argv):
args.log_level = "CRITICAL"
setup_log(log_level=args.log_level)
# Taken out of the environment before any command runs, so no build
# tool inherits a key a calling program handed over
args.env_ota_key = os.environ.pop(ENV_OTA_KEY, None)
if args.command in ("upload", "run"):
# One key is one device's; several configs run in child processes
# that would never see it
if (args.env_ota_key is not None or args.prompt_ota_key) and len(
args.configuration
) > 1:
raise EsphomeError(
f"--prompt-ota-key and {ENV_OTA_KEY} take one configuration; "
f"{len(args.configuration)} were given"
)
elif args.env_ota_key is not None:
_LOGGER.warning(
"%s is set but the %s command does not use it", ENV_OTA_KEY, args.command
)
_warn_if_source_tree_mismatch()
_warn_if_intel_macos()
if args.command in PRE_CONFIG_ACTIONS:
try:

Some files were not shown because too many files have changed in this diff Show More