Since the timezone bloat elimination (#13635) removed setenv("TZ")/tzset()
on embedded platforms, libc's ::strftime no longer has access to timezone
state for %Z and %z format specifiers. The time values (hour, minute, etc.)
are correct since they come from our custom timezone parser via struct tm
fields, but %Z and %z read from libc's internal _tzname/_timezone which
are stuck at GMT/+0000.
Fix by substituting %Z and %z in the format string with the correct
"+HHMM"/"-HHMM" designation computed from our parsed timezone offset
before passing to ::strftime. The substitution only runs when %Z or %z
are present in the format string, so there is no overhead for the common
case.
Also refactors strftime_to() to delegate to strftime() instead of
duplicating the ::strftime call, ensuring both code paths get the fix.
Bitfield component_source_index_ (9 bits) and warn_if_blocking_over_
(7 bits) into the same 16 bits. This doubles the max unique component
source names from 255 to 511 without increasing Component size.
The blocking warn threshold max drops from 2550ms to 1270ms which is
more than sufficient since it starts at 50ms and ratchets up.
Fixes test warnings when many components are compiled together:
WARNING Too many unique component source names (max 255)
Replace virtual handler interfaces (GAPEventHandler, GAPScanEventHandler,
GATTcEventHandler, GATTsEventHandler, BLEStatusEventHandler) with
StaticCallbackManager-based dispatch using lambda callbacks.
This eliminates vtable lookups and MI this-pointer adjustment thunks
on every BLE event dispatch in the hot path. Each lambda captures a
single pointer (fits in Callback inline storage, no heap allocation)
and the StaticCallbackManager avoids std::vector template bloat.
Breaking change: external components inheriting from the virtual handler
classes need to switch to add_*_callback() registration instead.
When OTA starts, stop_scan() is called which clears the scan_continuous_
flag. If OTA fails or aborts, the scan was never restarted because
scan_continuous_ was already false. This left the device without BLE
scanning until reboot.
Save the scan_continuous_ state before stopping and restore it on
OTA_ERROR or OTA_ABORT to resume scanning.
Replace the FreeRTOS xQueue (xQueueCreate/xQueueSend/xQueueReceive)
used for WiFi event passing with a lock-free SPSC queue. This avoids
the FreeRTOS kernel spinlock overhead on every loop iteration when
checking for events — the common case is an empty queue.
The LockFreeQueue::pop() fast path is just two atomic loads and a
comparison, vs xQueueReceive which takes a spinlock, checks the
queue, and releases the spinlock even when empty.
WiFi events are rare (connect/disconnect/scan) so heap allocation
for event data is retained — no EventPool needed unlike the BLE
path which processes hundreds of events per second.
This matches the lock-free pattern already used by esp32_ble.
Replace the FreeRTOS xQueue (xQueueCreate/xQueueSend/xQueueReceive)
used for WiFi event passing with a lock-free SPSC queue. This avoids
the FreeRTOS kernel spinlock overhead on every loop iteration when
checking for events — the common case is an empty queue.
The LockFreeQueue::pop() fast path is just two atomic loads and a
comparison, vs xQueueReceive which takes a spinlock, checks the
queue, and releases the spinlock even when empty.
WiFi events are rare (connect/disconnect/scan) so heap allocation
for event data is retained — no EventPool needed unlike the BLE
path which processes hundreds of events per second.
This matches the lock-free pattern already used by esp32_ble.