Replace the inline optional<LightStateRTCState> (~44 bytes) with a
function pointer callback (4 bytes) that populates the initial state
during setup. The callback is a stateless lambda whose values live in
flash as code, not in the LightState object.
Saves ~40 bytes per LightState instance unconditionally — the optional
reserved space for the struct even when initial_state was not configured
(the common case).
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).
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).