22 bool ignoreDisconnectFailure =
false;
28 LocalTimeConvert
conv;
29 conv.withConfig(LocalTime::instance().getConfig()).withCurrentTime().convert();
30 uint8_t hour = (uint8_t)(
conv.getLocalTimeHMS().toSeconds() / 3600);
31 Log.info(
"SLEEP entry: parkHours %02u-%02u localHour=%02u => %s",
35 Log.info(
"SLEEP entry: Time invalid => treating as OPEN (per policy)");
53 if (Particle.connected() && !PublishQueuePosix::instance().getCanSleep()) {
54 static size_t lastPendingLogged = (size_t)-1;
55 static unsigned long lastDeferralLogMs = 0;
57 size_t pending = PublishQueuePosix::instance().getNumEvents();
58 unsigned long nowMs = millis();
59 bool shouldLog = (pending != lastPendingLogged) || (nowMs - lastDeferralLogMs) > 5000UL;
61 Log.info(
"Deferring sleep - publish queue has %u pending event(s) or publish in progress",
63 lastPendingLogged = pending;
64 lastDeferralLogMs = nowMs;
75 static bool disconnectRequested =
false;
76 static unsigned long disconnectRequestStartMs = 0;
79 disconnectRequested =
false;
80 disconnectRequestStartMs = 0;
86 uint16_t cloudBudgetSec =
sysStatus.get_cloudDisconnectBudgetSec();
87 if (cloudBudgetSec < 5 || cloudBudgetSec > 120) {
91 uint16_t modemBudgetSec =
sysStatus.get_modemOffBudgetSec();
92 if (modemBudgetSec < 5 || modemBudgetSec > 120) {
96 unsigned long budgetMs = (
unsigned long)((modemBudgetSec > cloudBudgetSec) ? modemBudgetSec : cloudBudgetSec) * 1000UL;
98 if (!disconnectRequested) {
99 Log.info(
"SLEEP: requesting cloud disconnect + modem off");
101 disconnectRequested =
true;
102 disconnectRequestStartMs = millis();
108 if (disconnectRequestStartMs != 0 && (millis() - disconnectRequestStartMs) > budgetMs) {
110 Log.warn(
"SLEEP: disconnect/modem-off exceeded budget (%lu ms) - continuing to sleep",
111 (
unsigned long)(millis() - disconnectRequestStartMs));
112 ignoreDisconnectFailure =
true;
113 disconnectRequested =
false;
114 disconnectRequestStartMs = 0;
116 Log.warn(
"SLEEP: disconnect/modem-off exceeded budget (%lu ms) - raising alert 15",
117 (
unsigned long)(millis() - disconnectRequestStartMs));
120 disconnectRequested =
false;
121 disconnectRequestStartMs = 0;
125 if (!ignoreDisconnectFailure) {
131 int nightSleepSec = -1;
137 Log.info(
"CLOSED-hours deep sleep: disabling sensor (onEnterSleep)");
139 Log.info(
"CLOSED-hours deep sleep: sensorReady after disable=%s",
SensorManager::instance().isSensorReady() ?
"true" :
"false");
143 if (nightSleepSec <= 0) {
144 nightSleepSec = 3600;
150 const int MAX_SLEEP_SEC = 546 * 60;
151 if (nightSleepSec > MAX_SLEEP_SEC) {
152 Log.info(
"Clamping night sleep duration to max supported %d seconds (requested=%d)", MAX_SLEEP_SEC, nightSleepSec);
153 nightSleepSec = MAX_SLEEP_SEC;
159 Log.info(
"Outside opening hours - entering NIGHT HIBERNATE sleep for %d seconds", nightSleepSec);
164 config = SystemSleepConfiguration();
165 config.mode(SystemSleepMode::HIBERNATE)
167 .duration((uint32_t)nightSleepSec * 1000UL);
178 Log.error(
"HIBERNATE sleep returned unexpectedly - disabling HIBERNATE for this session");
190 uint16_t intervalSec =
sysStatus.get_reportingInterval();
191 if (intervalSec == 0) {
192 intervalSec = 1 * 3600;
197 wakeInSeconds = nightSleepSec;
198 Log.info(
"Outside opening hours - using ULTRA_LOW_POWER fallback sleep for %d seconds", wakeInSeconds);
204 time_t now = Time.now();
205 int offset = (int)(now % boundary);
206 int aligned = boundary - offset;
209 }
else if (aligned > boundary) {
212 wakeInSeconds = aligned + 1;
213 Log.info(
"Sleep alignment: now=%lu boundary=%d offset=%d aligned=%d (+1 for margin)",
214 (
unsigned long)now, boundary, offset, aligned);
216 wakeInSeconds = (int)intervalSec;
224 Log.info(
"Deferring sleep - sensor event or LED timer active");
229 if (digitalRead(
BLUE_LED) == HIGH) {
235 config = SystemSleepConfiguration();
242 Log.info(
"Entering ULTRA_LOW_POWER sleep for %d seconds (wakes at boundary or on GPIO)", wakeInSeconds);
246 config.mode(SystemSleepMode::ULTRA_LOW_POWER)
249 .duration(wakeInSeconds * 1000L);
251 SystemSleepResult result = System.sleep(
config);
254 waitFor(Serial.isConnected, 30000);
262 pin_t wakePin = result.wakeupPin();
263 bool pirWake = (wakePin ==
intPin);
265 bool timerWake = !pirWake && !buttonWake;
268 SystemSleepWakeupReason reason = result.wakeupReason();
269 Log.info(
"Woke from ULTRA_LOW_POWER: wakeupReason=%d pin=%d (pir=%d button=%d timer=%d)",
270 (
int)reason, (
int)wakePin, pirWake, buttonWake, timerWake);
277 if (Time.isValid()) {
278 LocalTimeConvert convWake;
279 convWake.withConfig(LocalTime::instance().getConfig()).withCurrentTime().convert();
280 uint8_t hour = (uint8_t)(convWake.getLocalTimeHMS().toSeconds() / 3600);
281 Log.info(
"Wake eval: parkHours %02u-%02u localHour=%02u => %s",
285 Log.info(
"Wake eval: Time invalid => treating as OPEN (per policy)");
291 Log.info(
"WAKE: Button pressed - reason=SERVICE_REQUEST transitioning to CONNECTING_STATE");
303 Log.info(
"Wake: OPEN hours - enabling sensor (onExitSleep)");
306 Log.info(
"Wake: sensorReady=false - initializing from config");
314 Log.info(
"WAKE: CONNECTED mode + OPEN hours - reason=MAINTAIN_CONNECTION transitioning to CONNECTING_STATE");
319 Log.info(
"Woke outside opening hours; keeping sensors powered down");
329 current.set_lastCountTime(Time.now());
330 Log.info(
"Count detected from PIR wake - Hourly: %d, Daily: %d",
335 current.set_occupancyStartTime(Time.now());
336 Log.info(
"Space now OCCUPIED from PIR wake at %s", Time.timeStr().c_str());
338 current.set_lastOccupancyEvent(millis());
357 Log.info(
"WAKE: Timer wake - reason=SCHEDULED_REPORT transitioning to REPORTING_STATE");
364 uint16_t intervalSec =
sysStatus.get_reportingInterval();
365 if (intervalSec == 0) intervalSec = 3600;
367 time_t now = Time.now();
368 time_t lastReport =
sysStatus.get_lastReport();
369 if (lastReport > 0 && (now - lastReport) >= intervalSec) {
370 int overdue = (int)(now - lastReport - intervalSec);
371 Log.info(
"WAKE: PIR + report overdue (%d sec) - transitioning to REPORTING_STATE", overdue);
385 Log.info(
"WAKE: No immediate action needed - transitioning to IDLE_STATE");