What's happening
The moon-phase Canvas clockface (cw-cf-0x07) connects to wifi fine at boot,
draws its splash screen, then goes dark/unreachable minutes later with no
trace on the network (no DHCP lease, no ARP entry, no mDNS, port 80/3232
closed everywhere on a full subnet sweep). The owner (Kristina) reports the
panel "rebooting" repeatedly, which is what tipped this off.
Root cause
firmware/lib/cw-commons/WiFiController.h, isConnected():
bool isConnected()
{
if (improvSerial.isConnected()) {
elapsedTimeOffline = 0;
return true;
} else {
if (elapsedTimeOffline == 0 && !connectionSucessfulOnce)
elapsedTimeOffline = millis();
if ((millis() - elapsedTimeOffline) > 1000 * 60 * 5) // restart if offline 5min
StatusController::getInstance()->forceRestart();
return false;
}
}
elapsedTimeOffline is only ever set to millis() when
!connectionSucessfulOnce — i.e. only on the very first disconnect, before
any successful connection has ever happened. Once connectionSucessfulOnce
flips true (first successful wifi join), it is never set again on
subsequent drops, and stays pinned at 0 for the rest of the device's
uptime (it's only reset back to 0, never re-armed to millis(), on
reconnect).
So the "restart after 5 minutes offline" check silently becomes
millis() - 0 > 5min — i.e. total device uptime, not time-since-drop.
Once the device has been up longer than 5 minutes, any wifi drop after that
point causes forceRestart() on essentially the next disconnected loop
iteration, not after a real 5-minute grace period.
Impact
On a marginal/flaky wifi signal, this turns "briefly drop and reconnect"
into a hard crash-loop: boot → connect just long enough to run setup() →
drop → immediate restart → repeat. Each connected window can be too short
for OTA, the local settings webserver (CWWebServer.h, port 80), or even a
DHCP/ARP scan to catch it. From the outside the device looks completely
absent from the network, even though it's actively cycling.
Suggested fix
Track elapsed-time-offline per disconnect, not gated on
connectionSucessfulOnce:
bool isConnected()
{
if (improvSerial.isConnected()) {
elapsedTimeOffline = 0;
return true;
} else {
if (elapsedTimeOffline == 0)
elapsedTimeOffline = millis();
if ((millis() - elapsedTimeOffline) > 1000 * 60 * 5)
StatusController::getInstance()->forceRestart();
return false;
}
}
i.e. drop the && !connectionSucessfulOnce guard entirely, so the timer
arms on every transition into the disconnected state, not just the first
one. This gives a real 5-minute grace period on every drop instead of only
the first.
Checklist
What's happening
The moon-phase Canvas clockface (
cw-cf-0x07) connects to wifi fine at boot,draws its splash screen, then goes dark/unreachable minutes later with no
trace on the network (no DHCP lease, no ARP entry, no mDNS, port 80/3232
closed everywhere on a full subnet sweep). The owner (Kristina) reports the
panel "rebooting" repeatedly, which is what tipped this off.
Root cause
firmware/lib/cw-commons/WiFiController.h,isConnected():elapsedTimeOfflineis only ever set tomillis()when!connectionSucessfulOnce— i.e. only on the very first disconnect, beforeany successful connection has ever happened. Once
connectionSucessfulOnceflips
true(first successful wifi join), it is never set again onsubsequent drops, and stays pinned at
0for the rest of the device'suptime (it's only reset back to
0, never re-armed tomillis(), onreconnect).
So the "restart after 5 minutes offline" check silently becomes
millis() - 0 > 5min— i.e. total device uptime, not time-since-drop.Once the device has been up longer than 5 minutes, any wifi drop after that
point causes
forceRestart()on essentially the next disconnected loopiteration, not after a real 5-minute grace period.
Impact
On a marginal/flaky wifi signal, this turns "briefly drop and reconnect"
into a hard crash-loop: boot → connect just long enough to run
setup()→drop → immediate restart → repeat. Each connected window can be too short
for OTA, the local settings webserver (
CWWebServer.h, port 80), or even aDHCP/ARP scan to catch it. From the outside the device looks completely
absent from the network, even though it's actively cycling.
Suggested fix
Track elapsed-time-offline per disconnect, not gated on
connectionSucessfulOnce:i.e. drop the
&& !connectionSucessfulOnceguard entirely, so the timerarms on every transition into the disconnected state, not just the first
one. This gives a real 5-minute grace period on every drop instead of only
the first.
Checklist
60s+ after a fresh power cycle (reboot / blackout pattern visible?)
isConnected()fix aboveclockface (
cw-cf-0x07) crashed on boot from a 32KB staticDynamicJsonDocumentfaulting static init — already shrunk as alive patch, not yet committed/merged
crash-looping) and confirm stable reconnect behavior on a forced
wifi drop
canvasServer/canvasFilevia the localsettings page (
/, port 80) to point at the moon-canvas CloudflareWorker so the moon-phase clockface actually renders instead of
showing "Params weren't set"