33#include "LocalTimeRK.h"
36#include "PublishQueuePosixRK.h"
72static void appWatchdogHandler();
97 "Sleeping",
"Connecting",
"Reporting",
128 waitFor(Serial.isConnected, 10000);
135 System.on(out_of_memory,
144 static ApplicationWatchdog appWatchdog(60000, appWatchdogHandler, 1536);
145 Log.info(
"Application watchdog enabled: 60s timeout");
150 char responseTopic[125];
151 String deviceID = System.deviceID();
152 deviceID.toCharArray(responseTopic,
sizeof(responseTopic));
160 Log.info(
"Platform connectivity: WiFi (radio off until CONNECTING_STATE)");
164 Log.info(
"Platform connectivity: Cellular (radio off until CONNECTING_STATE)");
165 Cellular.disconnect();
168 Log.info(
"Platform connectivity: default (Particle.connect only)");
181 if (
current.get_alertCode() == 16) {
182 Log.info(
"Clearing alert 16 on boot");
190 switch (System.resetReason()) {
191 case RESET_REASON_PIN_RESET:
192 case RESET_REASON_USER:
193 case RESET_REASON_WATCHDOG:
196 case RESET_REASON_UPDATE:
200 Log.info(
"OTA update detected - forcing connection to reload config");
203 case RESET_REASON_POWER_MANAGEMENT:
231 PublishQueuePosix::instance()
232 .withFileQueueSize(800)
236 const bool timeValidBeforeRtc = Time.isValid();
237 ab1805.withFOUT(WKP).setup();
238 ab1805.setWDT(AB1805::WATCHDOG_MAX_SECONDS);
241 const bool rtcReadOk =
ab1805.getRtcAsTime(rtcTime);
242 const bool timeValidAfterRtc = Time.isValid();
243 if (!timeValidBeforeRtc && timeValidAfterRtc) {
245 Log.info(
"RTC restored system time: %s (rtc=%s)",
246 Time.timeStr().c_str(),
247 Time.format(rtcTime, TIME_FORMAT_DEFAULT).c_str());
249 Log.info(
"RTC restored system time: %s (rtc read failed)",
250 Time.timeStr().c_str());
252 }
else if (!timeValidAfterRtc) {
253 Log.warn(
"RTC did not restore time (rtcSet=%s rtcReadOk=%s)",
254 ab1805.isRTCSet() ?
"true" :
"false",
255 rtcReadOk ?
"true" :
"false");
269 if (tz.length() == 0) {
273 LocalTime::instance().withConfig(LocalTimePosixTimezone(tz.c_str()));
276 if (!Time.isValid()) {
277 Log.info(
"Time is invalid - %s so connecting", Time.timeStr().c_str());
280 Log.info(
"Time is valid - %s", Time.timeStr().c_str());
283 conv.withCurrentTime().convert();
284 Log.info(
"Timezone: %s, Local time: %s", tz.c_str(),
conv.format(TIME_FORMAT_DEFAULT).c_str());
285 Log.info(
"Open hours %02u:00-%02u:00, currently: %s",
291 if (System.resetReason() == RESET_REASON_POWER_MANAGEMENT) {
292 uint8_t localHour = (uint8_t)(
conv.getLocalTimeHMS().toSeconds() / 3600);
293 if (localHour ==
sysStatus.get_openTime()) {
294 Log.info(
"Wake from overnight hibernate at opening hour - suppressing alert 40");
302 Log.info(
"CONNECTED mode - connecting on boot to reload config");
312 Log.info(
"Initial operatingMode: %d (%s)",
sysStatus.get_operatingMode(),
313 sysStatus.get_operatingMode() == 0 ?
"CONNECTED" :
314 sysStatus.get_operatingMode() == 1 ?
"LOW_POWER" :
"DISCONNECTED");
318 Log.info(
"Initializing sensor after timezone setup");
322 Log.error(
"Sensor failed to initialize after timezone setup; connecting to report error");
326 Log.info(
"Outside opening hours at startup; sensor will remain powered down");
329 Log.info(
"Startup CLOSED: forcing sensor power down before sleep");
331 Log.info(
"Sensor ready after startup power-down: %s",
SensorManager::instance().isSensorReady() ?
"true" :
"false");
342 Log.info(
"Startup complete");
385 PublishQueuePosix::instance().loop();
389 Log.info(
"Resetting due to low memory");
398 Log.info(
"User switch pressed - connecting to drain queue");
408 uint8_t countingMode =
sysStatus.get_countingMode();
420static void appWatchdogHandler() {
429 if (!Time.isValid()) {
433 uint8_t openHour =
sysStatus.get_openTime();
434 uint8_t closeHour =
sysStatus.get_closeTime();
437 LocalTimeConvert tempConv;
438 tempConv.withConfig(LocalTime::instance().getConfig()).withCurrentTime().convert();
439 uint8_t hour = (uint8_t)(tempConv.getLocalTimeHMS().toSeconds() / 3600);
441 if (openHour < closeHour) {
443 return (hour >= openHour) && (hour < closeHour);
444 }
else if (openHour > closeHour) {
446 return (hour >= openHour) || (hour < closeHour);
455 if (!Time.isValid()) {
460 uint8_t openHour =
sysStatus.get_openTime();
461 uint8_t closeHour =
sysStatus.get_closeTime();
463 LocalTimeConvert tempConv;
464 tempConv.withConfig(LocalTime::instance().getConfig()).withCurrentTime().convert();
465 uint32_t secondsOfDay = tempConv.getLocalTimeHMS().toSeconds();
467 uint32_t openSec = (uint8_t)openHour * 3600;
468 uint32_t closeSec = (uint8_t)closeHour * 3600;
472 return (
int)((24 * 3600UL - secondsOfDay) + openSec);
475 if (openHour < closeHour) {
477 if (secondsOfDay < openSec) {
479 return (
int)(openSec - secondsOfDay);
482 return (
int)((24 * 3600UL - secondsOfDay) + openSec);
484 }
else if (openHour > closeHour) {
486 if (secondsOfDay < openSec && secondsOfDay >= closeSec) {
488 return (
int)(openSec - secondsOfDay);
512 "Unknown",
"Not Charging",
"Charging",
513 "Charged",
"Discharging",
"Fault",
521 unsigned long timeStampValue = Time.now() - (Time.minute() * 60L + Time.second() + 1L);
524 uint8_t battState =
current.get_batteryState();
530 snprintf(data,
sizeof(data),
531 "{\"hourly\":%i, \"daily\":%i, \"battery\":%4.2f,\"key1\":\"%s\", \"temp\":%4.2f, \"resets\":%i, \"alerts\":%i,\"connecttime\":%i,\"timestamp\":%lu000}",
543 Log.info(
"Report payload: hourly=%d daily=%d alert=%d",
544 (
int)
current.get_hourlyCount(),
548 PublishQueuePosix::instance().publish(ProjectConfig::webhookEventName(), data, PRIVATE | WITH_ACK);
549 Log.info(
"Ubidots Webhook: %s", data);
570 int resetReason = System.resetReason();
571 uint32_t resetReasonData = System.resetReasonData();
572 int8_t alertCode =
current.get_alertCode();
573 time_t lastAlert =
current.get_lastAlertTime();
575 snprintf(status,
sizeof(status),
576 "{\"version\":\"%s\",\"resetReason\":%d,\"resetReasonData\":%lu,\"alert\":%d,\"lastAlert\":%ld}",
579 (
unsigned long)resetReasonData,
583 PublishQueuePosix::instance().publish(
"status", status, PRIVATE | WITH_ACK);
584 Log.info(
"Startup status: %s", status);
589 char responseString[64];
592 snprintf(responseString,
sizeof(responseString),
"No Data");
593 }
else if (atoi(data) == 200 || atoi(data) == 201) {
594 snprintf(responseString,
sizeof(responseString),
"Response Received");
603 if (
current.get_alertCode() == 40) {
608 snprintf(responseString,
sizeof(responseString),
609 "Unknown response recevied %i", atoi(data));
611 if (
sysStatus.get_verboseMode() && Particle.connected()) {
614 Log.info(responseString);
637 const size_t DIAGNOSTIC_QUEUE_THRESHOLD = 10;
639 size_t queueDepth = PublishQueuePosix::instance().getNumEvents();
641 if (queueDepth >= DIAGNOSTIC_QUEUE_THRESHOLD) {
642 Log.info(
"Diagnostic publish skipped (queue depth=%u): %s", (
unsigned)queueDepth, eventName);
647 PublishQueuePosix::instance().publish(eventName, data, flags | WITH_ACK);
652 char stateTransitionString[256];
655 snprintf(stateTransitionString,
sizeof(stateTransitionString),
659 snprintf(stateTransitionString,
sizeof(stateTransitionString),
662 snprintf(stateTransitionString,
sizeof(stateTransitionString),
665 Log.info(stateTransitionString);
676 static bool frontTireFlag =
false;
677 if (frontTireFlag ||
sysStatus.get_sensorType() == 1) {
679 frontTireFlag =
false;
681 frontTireFlag =
true;
694 if (Particle.connected()) {
698 Log.info(
"Daily time sync requested");
703 Log.info(
"Running Daily Cleanup");
const char * FIRMWARE_VERSION
Cloud Configuration Management - Particle Ledger integration for device configuration.
Global compile-time configuration options and enums.
void outOfMemoryHandler(system_event_t event, int param)
void publishStartupStatus()
Enqueue a one-time startup status event summarizing firmware version, reset reason,...
void dailyCleanup()
Cleanup function that is run at the beginning of the day.
int secondsUntilNextOpen()
const unsigned long resetWait
bool suppressAlert40ThisSession
bool firstConnectionObserved
bool publishDiagnosticSafe(const char *eventName, const char *data, PublishFlags flags=PRIVATE)
Publish a state transition to the log handler.
bool firstConnectionQueueDrainedLogged
void publishData()
Publish sensor data to Ubidots webhook and device-data ledger.
const char * FIRMWARE_RELEASE_NOTES
volatile bool userSwitchDetected
volatile bool sensorDetect
unsigned long connectedStartMs
void countSignalTimerISR()
SystemSleepConfiguration config
bool hibernateDisabledForSession
void UbidotsHandler(const char *event, const char *data)
const unsigned long maxConnectAttemptMs
void publishStateTransition()
Persistent Data Storage Structures - EEPROM/Retained Memory Management.
This file initilizes the Particle functions and variables needed for control from the console / API c...
SensorType
Enumeration of available sensor types (backward-compatible IDs).
const char * batteryContext[7]
Singleton wrapper around ISensor implementations.
void handleConnectingState()
CONNECTING_STATE: establish cloud connection using a phased, non-blocking state machine.
void handleFirmwareUpdateState()
void handleOccupancyMode()
Handle sensor events in OCCUPANCY mode.
void handleCountingMode()
Handle sensor events in COUNTING mode.
void handleReportingState()
void handleSleepingState()
SLEEPING_STATE: deep sleep between reporting intervals.
Firmware release metadata (version and notes).
void setup()
Perform setup operations; call this from global application setup().
static Cloud & instance()
Gets the singleton instance of this class, allocating it if necessary.
void loop()
Service deferred cloud work; call from main loop.
void setup()
Perform setup operations; call this from global application setup().
static Particle_Functions & instance()
Gets the singleton instance of this class, allocating it if necessary.
static SensorManager & instance()
Get the SensorManager singleton instance.
void initializeFromConfig()
Create and initialize the active sensor based on configuration.
void onEnterSleep()
Notify the sensor that the device is entering deep sleep.
bool initializePinModes()
Pinout definitions for the carrier board and sensors.
const SensorDefinition * getDefinition(SensorType type)
Lookup helper to get the SensorDefinition for a given SensorType.
Static metadata for each supported sensor type.
bool ledDefaultOn
true if LED should be ON at boot (polarity-specific)