@@ -199,6 +199,23 @@ static int8_t s_lastLedWhite = -1;
199199static int8_t s_lastBeep = -1 ;
200200static int8_t s_lastConnType = -1 ; // PID_CONN_TYPE sentinel: 1=WiFi, 2=Cellular
201201
202+ // Inject-on-next-packet flag: set whenever the sentinels are reset (new
203+ // connection established or session start). The telemetry task checks this
204+ // flag and injects LED/beep/conn-type/SD PIDs directly into the outgoing
205+ // CStorage packet before store.tailer(), guaranteeing these IST-Status values
206+ // are in the FIRST transmitted packet regardless of which buffer getNewest()
207+ // picks up. Without this, a race between process() (updating sentinels and
208+ // filling buffers) and the telemetry loop (slow OTA check over cellular delays
209+ // getNewest()) causes the sentinel-triggered buffer to be overwritten before it
210+ // is transmitted — leaving HA with "Unbekannt" for LED/beep/SD indefinitely.
211+ static volatile bool s_send_state_pids = false ;
212+
213+ // Cached SD card capacity/free space (MiB) — updated by process() each time it
214+ // emits PID_SD_TOTAL_MB / PID_SD_FREE_MB. Read (not written) by the telemetry
215+ // inject block so it does not need to access the SD SPI bus from the wrong task.
216+ static uint32_t s_cachedSdTotalMb = 0 ;
217+ static uint32_t s_cachedSdFreeMb = 0 ;
218+
202219// Pull-OTA constants used in initialize(), standby(), performPullOtaFlash(),
203220// and performPullOtaCheck(). Defined here (before any function body) so that
204221// all translation-unit uses see them regardless of source order.
@@ -650,9 +667,12 @@ void initialize()
650667 // "Unbekannt" IST-Status when HA is reloaded while the device was connected
651668 // (HA loses diag state, device never resends unchanged values unless the
652669 // sentinels are reset).
653- s_lastLedWhite = -1 ;
654- s_lastBeep = -1 ;
655- s_lastConnType = -1 ;
670+ s_lastLedWhite = -1 ;
671+ s_lastBeep = -1 ;
672+ s_lastConnType = -1 ;
673+ // Signal the telemetry task to inject IST-Status PIDs into the very next
674+ // transmitted packet so they are not lost to the getNewest() race.
675+ s_send_state_pids = true ;
656676
657677#if ENABLE_MEMS
658678 if (state.check (STATE_MEMS_READY )) {
@@ -1035,6 +1055,11 @@ void process()
10351055 sdTotalMb = (uint32_t )(tot >> 20 );
10361056 sdFreeMb = (uint32_t )((tot > used ? tot - used : 0 ) >> 20 );
10371057 }
1058+ // Update module-level cache so the telemetry inject block can include
1059+ // the SD values in the guaranteed first-packet injection without
1060+ // touching the SD SPI bus from the wrong task.
1061+ s_cachedSdTotalMb = sdTotalMb;
1062+ s_cachedSdFreeMb = sdFreeMb;
10381063 buffer->add (PID_SD_TOTAL_MB , ELEMENT_UINT32 , &sdTotalMb, sizeof (sdTotalMb));
10391064 buffer->add (PID_SD_FREE_MB , ELEMENT_UINT32 , &sdFreeMb, sizeof (sdFreeMb));
10401065 }
@@ -1276,9 +1301,10 @@ void telemetry(void* inst)
12761301 // reset the state-change detection would suppress the PIDs (value
12771302 // unchanged) and Home Assistant would keep showing "Unbekannt" for the
12781303 // IST-Status and the connection-type timestamps.
1279- s_lastLedWhite = -1 ;
1280- s_lastBeep = -1 ;
1281- s_lastConnType = -1 ;
1304+ s_lastLedWhite = -1 ;
1305+ s_lastBeep = -1 ;
1306+ s_lastConnType = -1 ;
1307+ s_send_state_pids = true ;
12821308
12831309 uint32_t t = millis ();
12841310 do {
@@ -1360,9 +1386,10 @@ void telemetry(void* inst)
13601386 // LED/beep state and connection type to HA. Without this, the
13611387 // sentinels retain their values from the previous cellular session
13621388 // and HA would keep showing stale IST-Status values.
1363- s_lastLedWhite = -1 ;
1364- s_lastBeep = -1 ;
1365- s_lastConnType = -1 ;
1389+ s_lastLedWhite = -1 ;
1390+ s_lastBeep = -1 ;
1391+ s_lastConnType = -1 ;
1392+ s_send_state_pids = true ;
13661393 // switch off cellular module when wifi connected
13671394 if (state.check (STATE_CELL_CONNECTED )) {
13681395 teleClient.cell .end ();
@@ -1406,9 +1433,10 @@ void telemetry(void* inst)
14061433 // LED/beep state and connection type to HA. Without this, the sentinels
14071434 // retain their values from the previous WiFi session and HA would keep
14081435 // showing stale IST-Status values and incorrect connection timestamps.
1409- s_lastLedWhite = -1 ;
1410- s_lastBeep = -1 ;
1411- s_lastConnType = -1 ;
1436+ s_lastLedWhite = -1 ;
1437+ s_lastBeep = -1 ;
1438+ s_lastConnType = -1 ;
1439+ s_send_state_pids = true ;
14121440 }
14131441
14141442 if (millis () - lastRssiTime > SIGNAL_CHECK_INTERVAL * 1000 ) {
@@ -1484,6 +1512,42 @@ void telemetry(void* inst)
14841512 store.timestamp (buffer->timestamp );
14851513 buffer->serialize (store);
14861514 bufman.free (buffer);
1515+ // Inject IST-Status PIDs (LED/beep/conn-type/SD) directly into this
1516+ // packet whenever a new connection has just been established.
1517+ //
1518+ // Without this injection, there is a race between process() and the
1519+ // telemetry loop that reliably loses these PIDs on cellular connections:
1520+ // 1. Sentinel reset → process() adds PIDs to Buffer A, updates sentinel.
1521+ // 2. OTA meta-check over cellular takes several seconds while process()
1522+ // fills Buffers B, C, D … (sentinel already matches, no PIDs).
1523+ // 3. getNewest() returns Buffer D (newest), Buffer A is overwritten.
1524+ // 4. Result: HA never receives LED/beep/SD → "Unbekannt" forever.
1525+ // WiFi is not immune but the OTA check is much faster there, so the race
1526+ // is rarely observed. With this injection both transports are reliable.
1527+ if (s_send_state_pids) {
1528+ s_send_state_pids = false ;
1529+ {
1530+ uint8_t v = enableLedWhite ? 1 : 0 ;
1531+ store.log (PID_LED_WHITE_STATE , &v, 1 );
1532+ s_lastLedWhite = (int8_t )v;
1533+ }
1534+ {
1535+ uint8_t v = enableBeep ? 1 : 0 ;
1536+ store.log (PID_BEEP_STATE , &v, 1 );
1537+ s_lastBeep = (int8_t )v;
1538+ }
1539+ if (state.check (STATE_NET_READY )) {
1540+ uint8_t v = state.check (STATE_WIFI_CONNECTED ) ? 1 : 2 ;
1541+ store.log (PID_CONN_TYPE , &v, 1 );
1542+ s_lastConnType = (int8_t )v;
1543+ }
1544+ #if STORAGE == STORAGE_SD
1545+ // s_cachedSdTotalMb/Free are kept current by process(); they are 0
1546+ // before the first SD read which HA correctly interprets as "no card".
1547+ store.log (PID_SD_TOTAL_MB , &s_cachedSdTotalMb, 1 );
1548+ store.log (PID_SD_FREE_MB , &s_cachedSdFreeMb, 1 );
1549+ #endif
1550+ }
14871551 store.tailer ();
14881552 Serial.print (" [DAT] " );
14891553 Serial.println (store.buffer ());
0 commit comments