Replace 14 TemplatableValue fields with a per-field union that stores
either a constant value or a callable pointer in the same 4 bytes
(on 32-bit targets). A 2-bit-per-field type tag in a uint32_t tracks
field state (unset/constant/stateless lambda/stateful lambda).
Reduces LightControlAction from 128 to 76 bytes per instance on ESP32.
For a config with 8 light actions, this saves 416 bytes of RAM and
476 bytes of flash (from eliminated TemplatableValue template
instantiations).
Add support for the WIZnet W5100/W5100S SPI Ethernet controller
on RP2040 platforms, using the arduino-pico lwIP_w5100 library.
Tested on W5100S-EVB-Pico hardware.
The "Roam check skipped, signal good" message was at VERBOSE level,
making it invisible in normal logs. This made it unclear whether
roaming checks were happening after a successful roam. Promote to
DEBUG to match the "Roam scan" message level.
Split the RECONNECTING log message into two cases:
- "Reconnected after failed roam" when a specific roam target was
set but we ended up on a different AP
- "Reconnected after roam scan" when no roam target was set (the
disconnect was caused by going off-channel during the scan itself)
Split the RECONNECTING log message into two cases:
- "Reconnected after failed roam" when a specific roam target was
set but we ended up on a different AP
- "Reconnected after roam scan" when no roam target was set (the
disconnect was caused by going off-channel during the scan itself)
- Clear roaming_scan_end_ on successful reconnect to prevent grace
period from incorrectly applying to a second disconnect
- Log target BSSID in "Roam successful (via retry)" message
- Move bssid_t roaming_target_bssid_ to 1-byte section to avoid
2 bytes struct padding before ap_timeout_
When a roam connection fails on the first attempt, retry_connect()
transitions to RECONNECTING. The subsequent scan-based retry may
connect to the same better AP, but the success handler treated all
RECONNECTING connections as failed roams and preserved the attempts
counter. This meant a successful roam (that needed two tries) would
incorrectly consume an attempt.
Fix by storing the roam target BSSID and checking it on reconnection.
If the device connected to the intended target, reset the counter
(successful roam). If it fell back to a different AP, preserve the
counter (failed roam, prevent ping-pong).
Use 100-300ms dwell times for roaming scans instead of 400-500ms,
matching the ESP32 IDF scan times. This reduces total off-channel
time from ~6.5s to ~3.9s (13 channels), which reduces the chance
of triggering Beacon Timeout disconnects on aggressive APs.
The longer dwell times are preserved for initial connection scans
where a thorough survey is needed.
On ESP8266, the roaming scan goes off-channel for ~3 seconds. Some
APs aggressively disconnect the client (Beacon Timeout) 8-20 seconds
after the scan finishes. By that time, process_roaming_scan_() has
already set roaming_state_ back to IDLE. When retry_connect() runs,
it sees IDLE and calls clear_roaming_state_(), resetting the attempts
counter to 0. This creates an infinite loop of "attempt 1/3" every
5 minutes.
The aggressive AP behavior cannot be fixed from the ESP side, but we
can prevent the counter from resetting so roaming eventually stops
after 3 attempts.
Fix by recording when the roaming scan completes and adding a 30s
grace period. If a disconnect occurs within that window, the state
transitions to RECONNECTING (preserving the counter) instead of
clearing it.
Closes https://github.com/esphome/esphome/issues/15124