Generalized-Core-Counter 3.20
Particle-based generalized core counter firmware
Loading...
Searching...
No Matches
State_Connect.cpp
Go to the documentation of this file.
2#include "Config.h"
3#include "Cloud.h"
4#include "DeviceInfoLedger.h"
5#include "MyPersistentData.h"
6#include "PublishQueuePosixRK.h"
7#include "SensorManager.h"
8#include "device_pinout.h"
9#include "SensorDefinitions.h"
10
11// NOTE:
12// This file was split from StateHandlers.cpp as a mechanical refactor.
13// No behavioral changes were made.
14
15// Maximum amount of time to remain in FIRMWARE_UPDATE_STATE before
16// giving up and returning to normal low-power operation. Mirrors the
17// Wake-Publish-Sleep Cellular example, which uses a 5 minute budget
18// for firmware updates before going back to sleep.
19static const unsigned long firmwareUpdateMaxMs = 5UL * 60UL * 1000UL;
20
22#if Wiring_WiFi
23 return WiFi.isOn();
24#elif Wiring_Cellular
25 return Cellular.isOn();
26#else
27 return false;
28#endif
29}
30
32#if Wiring_Cellular
33 Cellular.disconnect();
34 Cellular.off();
35#elif Wiring_WiFi
36 WiFi.disconnect();
37 WiFi.off();
38#endif
39}
40
42 Particle.disconnect();
44}
45
69 static unsigned long connectionStartTimeStamp; // When this connect attempt started
70 static bool lastEnteredFromReporting = false; // Whether we came from REPORTING_STATE
71 static bool connectRequested = false;
72 static bool postConnectDone = false;
73
74 if (state != oldState) {
76 lastEnteredFromReporting = (oldState == REPORTING_STATE);
77 sysStatus.set_lastConnectionDuration(0);
78 connectionStartTimeStamp = millis();
79 connectRequested = false;
80 postConnectDone = false;
81 }
82
83 unsigned long elapsedMs = millis() - connectionStartTimeStamp;
84 sysStatus.set_lastConnectionDuration(int(elapsedMs / 1000));
85
86 // Use a ledger-configured budget when available; otherwise fall back
87 // to the compiled default maxConnectAttemptMs constant.
88 unsigned long budgetMs = maxConnectAttemptMs;
89 uint16_t budgetSec = sysStatus.get_connectAttemptBudgetSec();
90 if (budgetSec >= 30 && budgetSec <= 900) {
91 budgetMs = (unsigned long)budgetSec * 1000UL;
92 }
93
94 if (!connectRequested) {
95 // Log signal strength at start of connection attempt for field
96 // correlation with connectivity failures (alert 31). On cellular
97 // platforms this gives us a baseline RSSI before the modem fully
98 // connects, helping diagnose poor-reception issues.
99#if Wiring_Cellular
100 CellularSignal sig = Cellular.RSSI();
101 float strengthPct = sig.getStrength();
102 float qualityPct = sig.getQuality();
103 Log.info("Starting connection attempt - Signal: S=%2.0f%% Q=%2.0f%%",
104 (double)strengthPct, (double)qualityPct);
105#endif
106 Log.info("Requesting Particle cloud connection");
107 Particle.connect();
108 connectRequested = true;
109 }
110
111 if (Particle.connected()) {
112 if (!postConnectDone) {
113 connectedStartMs = millis();
114 sysStatus.set_lastConnection(Time.now());
115 if (current.get_alertCode() == 31) {
116 Log.info("Connection successful - clearing alert 31");
117 current.set_alertCode(0);
118 }
119 measure.getSignalStrength();
120 measure.batteryState();
121 Log.info("Enclosure temperature at connect: %4.2f C", (double)current.get_internalTempC());
122 if (sysStatus.get_verboseMode()) {
123 char data[64];
124 snprintf(data, sizeof(data), "Connected in %i secs", sysStatus.get_lastConnectionDuration());
125 publishDiagnosticSafe("Cellular", data, PRIVATE);
126 }
127
129 if (!configOk) {
130 Log.warn("Configuration apply failed (will raise alert 41)");
131 current.raiseAlert(41);
132 } else if (current.get_alertCode() == 41) {
133 Log.info("Configuration apply succeeded - clearing stale alert 41");
134 current.set_alertCode(0);
135 }
136
137 if (!lastEnteredFromReporting) {
138 if (!Cloud::instance().publishDataToLedger()) {
139 current.raiseAlert(42); // data ledger publish failure
140 }
141 }
142
143 size_t pending = PublishQueuePosix::instance().getNumEvents();
144 Log.info("Publish queue depth after connect: %u event(s)", (unsigned)pending);
145
149 }
150
151 postConnectDone = true;
152 }
153
154 if (System.updatesPending()) {
155 Log.info("Updates pending after connect - transitioning to FIRMWARE_UPDATE_STATE");
157 } else {
159 }
160 return;
161 }
162
163 if (elapsedMs > budgetMs) {
164 Log.warn("Connection attempt exceeded budget (%lu ms > %lu ms) - raising alert 31",
165 (unsigned long)elapsedMs, (unsigned long)budgetMs);
166 current.raiseAlert(31);
169 }
170}
171
172// FIRMWARE_UPDATE_STATE: Stay connected for firmware/config updates
174 // Track how long we've been in update mode so we can mirror the
175 // Particle Wake-Publish-Sleep example behaviour: bound the time
176 // spent waiting for an update before going back to sleep.
177 static unsigned long firmwareUpdateStartMs = 0;
178
179 if (state != oldState) {
181 Log.info("Entering FIRMWARE_UPDATE_STATE - keeping device connected for updates");
182
183 firmwareUpdateStartMs = millis();
184
185 // Ensure cloud connection is requested
186 if (!Particle.connected()) {
187 Particle.connect();
188 }
189 }
190
191 // Once connected, ensure configuration is loaded at least once
192 if (Particle.connected()) {
193 static bool configLoadedInUpdateMode = false;
194 if (!configLoadedInUpdateMode) {
195 Log.info("Connected in FIRMWARE_UPDATE_STATE - loading configuration from cloud");
197 configLoadedInUpdateMode = true;
198 }
199
200 // If no updates are pending anymore and no OTA in progress, exit update mode
201 if (!System.updatesPending()) {
202 Log.info("No updates pending - leaving FIRMWARE_UPDATE_STATE to IDLE_STATE");
203 configLoadedInUpdateMode = false;
205 return;
206 }
207 }
208
209 // Optional escape hatch: user button can also exit update mode
210 if (!digitalRead(BUTTON_PIN)) { // Active-low user button
211 Log.info("User button pressed - exiting FIRMWARE_UPDATE_STATE to IDLE_STATE");
213 return;
214 }
215
216 // Firmware update timeout: if we've spent more than firmwareUpdateMaxMs
217 // in this state, mirror the reference example and go to sleep so we can
218 // try again in a future cycle instead of keeping the modem on
219 // indefinitely.
220 if (firmwareUpdateStartMs != 0 && (millis() - firmwareUpdateStartMs) > firmwareUpdateMaxMs) {
221 Log.info("Firmware update timed out after %lu ms in FIRMWARE_UPDATE_STATE - transitioning to SLEEPING_STATE",
222 (unsigned long)(millis() - firmwareUpdateStartMs));
224 }
225}
Cloud Configuration Management - Particle Ledger integration for device configuration.
Global compile-time configuration options and enums.
bool firstConnectionObserved
bool publishDiagnosticSafe(const char *eventName, const char *data, PublishFlags flags=PRIVATE)
Publish a state transition to the log handler.
bool firstConnectionQueueDrainedLogged
unsigned long connectedStartMs
const unsigned long maxConnectAttemptMs
void publishStateTransition()
Persistent Data Storage Structures - EEPROM/Retained Memory Management.
#define sysStatus
#define current
Singleton wrapper around ISensor implementations.
#define measure
Convenience macro for accessing the SensorManager singleton.
bool isRadioPoweredOn()
void requestFullDisconnectAndRadioOff()
void handleConnectingState()
CONNECTING_STATE: establish cloud connection using a phased, non-blocking state machine.
void handleFirmwareUpdateState()
void requestRadioPowerOff()
@ SLEEPING_STATE
@ FIRMWARE_UPDATE_STATE
@ REPORTING_STATE
@ IDLE_STATE
bool loadConfigurationFromCloud()
Load and apply configuration from cloud ledgers.
Definition Cloud.cpp:153
static Cloud & instance()
Gets the singleton instance of this class, allocating it if necessary.
Definition Cloud.cpp:20
const pin_t BUTTON_PIN
Pinout definitions for the carrier board and sensors.