Alright, I thinned down the code and collapsed the 3 classes into a single sketch.
Yes, some of the code doesn't make any sense right now like the "firstBoot" but this is code that is being ported from another system and we are not done with that move.
Additionally, yes, the if in the main loop is dumb but more goes in there that was removed. I left in the if just to leave the parts that gets to the easiest fail case.
I've marked the section where the Arduino evetually dies.
I did leave in enough so that the code will run and reaturn valid data from the API for any testing.
/**
* @filename :
* @brief :
* @author :
* @Libraries :
* FlashStorage@1.0.0
* Arduinojson@6.21.3
* WiFiNINA@1.4.12
* ArduinoHttpClient@0.5.0
* RTCZero@1.6.0
* Arduino Low Power@1.2.2
* BQ24195@0.9.2
* Time@1.6.1
* NTPClient@3.2.1
*
* Copyright (C) Barr Technologies 2023/10/18
*/
#include <SPI.h>
#include <ArduinoJson.h>
#include <BQ24195.h>
#include <FlashStorage.h>
#include <WiFiNINA.h>
#include <TimeLib.h>
#include <NTPClient.h>
#include "ArduinoLowPower.h"
typedef struct {
char hostname[256];
char wifiSsid[32];
char wifiPassword[32];
} DeviceSettings;
//var
bool debugSerial = true;
bool debugLed = true;
bool deviceApproved = false;
bool firstBoot = false;
inline static DeviceSettings deviceSettings;
inline static bool classDebugSerial = false;
inline static bool classDebugLed = false;
inline static bool usedFailback = false;
inline static char macAddress[18] = "";
inline static StaticJsonDocument<256> previousHardwareResults;
//var
#define RED_LED 25
#define GREEN_LED 26
#define BLUE_LED 27
#define FAILSAFE1 "failoverssid"
#define FAILSAFE2 "failoverpass"
FlashStorage(deviceSettings_flashStore, DeviceSettings);
void setup() {
if(debugSerial)
{
delay(5000); // 5 seconds
Serial.begin(115200);
Serial.println(F("---=== Booted ===---"));
}
systemInit(debugSerial, debugLed);
hardwareInit(debugSerial, debugLed);
firstBoot = true;
}
void loop() {
connectToWiFi();
if(firstBoot)
{
firstBoot = false;
}
hardware_checkin();
if(previousHardwareResults["approved"] | false)
{
putToSleep(5000);
} else {
display_authCode(previousHardwareResults);
putToSleep(5000); //5 seconds
}
}
void systemInit(bool& debugSerial, bool& debugLed)
{
//var
bool updateFlash = false;
//var
Serial.println(F("Pre flash"));
Serial.println(deviceSettings.hostname);
Serial.println(deviceSettings.wifiSsid);
Serial.println(deviceSettings.wifiPassword);
classDebugSerial = debugSerial;
classDebugLed = debugLed;
WiFiDrv::pinMode(RED_LED, OUTPUT);
WiFiDrv::pinMode(GREEN_LED, OUTPUT);
WiFiDrv::pinMode(BLUE_LED, OUTPUT);
WiFi.lowPowerMode();
deviceSettings = deviceSettings_flashStore.read();
if(strlen(deviceSettings.hostname) == 0)
{
get_hostname();
updateFlash = true;
}
if(strlen(deviceSettings.wifiSsid) == 0 || strlen(deviceSettings.wifiPassword) == 0)
{
strcpy(deviceSettings.wifiSsid, "ssid");
strcpy(deviceSettings.wifiPassword, "pass");
updateFlash = true;
}
if(updateFlash && !classDebugSerial)
deviceSettings_flashStore.write(deviceSettings);
Serial.println(F("Post flash"));
Serial.println(deviceSettings.hostname);
Serial.println(deviceSettings.wifiSsid);
Serial.println(deviceSettings.wifiPassword);
}
void get_hostname(void)
{
//var
char hexChar[2];
byte mac[6];
//var
WiFi.macAddress(mac);
strcpy(deviceSettings.hostname, "redacted");
for (int runner = 2; runner >= 0; runner--) {
sprintf(hexChar, "%02x", mac[runner]);
strcat(deviceSettings.hostname, hexChar);
}
}
void connectToWiFi(void)
{
//var
WiFiUDP wifiClient;
NTPClient timeClient(wifiClient);
unsigned short errorCount = 1;
//var
while (WiFi.status() != WL_CONNECTED) {
if(classDebugLed)
{
WiFiDrv::analogWrite(GREEN_LED, 1);
WiFiDrv::analogWrite(RED_LED, 1);
}
if(classDebugSerial)
{
Serial.print(F("WiFi join attempt: "));
Serial.println(errorCount);
}
if(!usedFailback)
{
if(classDebugSerial)
{
Serial.print(F("Attempting to connect to SSID: "));
Serial.println(deviceSettings.wifiSsid);
}
WiFi.begin(deviceSettings.wifiSsid, deviceSettings.wifiPassword);
}
else
{
if(classDebugSerial)
{
Serial.print(F("Attempting to connect to failsafe SSID: "));
Serial.println(FAILSAFE1);
}
WiFi.begin(FAILSAFE1, FAILSAFE2);
}
if(classDebugSerial)
printWiFiStatus(WiFi.status());
if(WiFi.status() == WL_CONNECTED)
{
if(classDebugSerial)
{
Serial.print(F("Signal strength in dBm: "));
Serial.println(WiFi.RSSI());
}
if(timeStatus() == timeNotSet)
{
timeClient.begin();
timeClient.setTimeOffset(0);
timeClient.update();
setTime(timeClient.getEpochTime());
if(classDebugSerial)
Serial.println(F("Clock synced to NTP"));
}
} else {
if(classDebugLed)
{
WiFiDrv::analogWrite(GREEN_LED, 0);
WiFiDrv::analogWrite(RED_LED, 1);
}
if(errorCount++ >= 6) //looped 6 times for 10 seconds each time for 1 min total delay
usedFailback = true;
if(classDebugSerial)
{
Serial.println(F("---=== Slow down WiFi join ===---"));
delay(10000);
Serial.println(F("---=== Retry WiFi ===---"));
}
else
LowPower.deepSleep(10000);
}
}
if(classDebugSerial)
{
Serial.print(F("Connected to SSID: "));
if(!usedFailback)
Serial.println(deviceSettings.wifiSsid);
else
Serial.println(FAILSAFE1);
}
if(classDebugLed)
{
WiFiDrv::analogWrite(GREEN_LED, 0);
WiFiDrv::analogWrite(RED_LED, 0);
}
}
void printWiFiStatus(int wifiStatus)
{
Serial.print(F("WiFi status: "));
switch (wifiStatus) {
case WL_CONNECTED: Serial.println(F("connected to WiFi")); break;
case WL_AP_CONNECTED : Serial.println(F("connected in Access Point mode")); break;
case WL_AP_LISTENING : Serial.println(F("listening for connections in Access Point mode")); break;
//case WL_NO_SHIELD: Serial.println(F("assigned when no WiFi shield is present;")); break;
case WL_NO_MODULE: Serial.println(F("communication with an integrated WiFi module fails")); break;
case WL_IDLE_STATUS: Serial.println(F("temporary status assigned when WiFi.begin() is called and remains active until the number of attempts expires (resulting in WL_CONNECT_FAILED) or a connection is established (resulting in WL_CONNECTED)")); break;
case WL_NO_SSID_AVAIL: Serial.println(F("no SSID is available")); break;
case WL_SCAN_COMPLETED: Serial.println(F("scan networks is completed")); break;
case WL_CONNECT_FAILED: Serial.println(F("connection fails for all the attempts")); break;
case WL_CONNECTION_LOST: Serial.println(F("connection is lost")); break;
case WL_DISCONNECTED: Serial.println(F("disconnected from network")); break;
}
}
void hardware_checkin(void)
{
//var
WiFiSSLClient wifiClient;
char rssi_string[5] = "";
char lanIP[16] = "";
char wanIP[16] = "";
char postBody[256] = "";
char responseBody[512] = "";
StaticJsonDocument<512> responseJson;
char previousData_string[256] = "";
char responseData_string[256] = "";
//var
if(classDebugLed)
WiFiDrv::analogWrite(GREEN_LED, 1);
sprintf(rssi_string, "%d", WiFi.RSSI());
get_lanip_address(lanIP);
get_wanip_address(wanIP);
strcpy(postBody, "{ \"macAddress\": \"");
strcat(postBody, macAddress);
strcat(postBody, "\", \"lanip\": \"");
strcat(postBody, lanIP);
strcat(postBody, "\", \"wanip\": \"");
strcat(postBody, wanIP);
strcat(postBody, "\", \"resolution\": \"");
strcat(postBody, "x");
strcat(postBody, "\", \"hostname\": \"");
strcat(postBody, deviceSettings.hostname);
strcat(postBody, "\", \"lowMem\": ");
strcat(postBody, "true");
strcat(postBody, ", \"rssi\": ");
strcat(postBody, rssi_string);
strcat(postBody, ", \"battery\": ");
strcat(postBody, "0");
strcat(postBody, " }");
network_securePost("production.wolfeborokeene.com", "/display", "application/json", postBody, responseBody);
//delete[] postBody; <==-- THIS KILLS THE DEVICE
//ARDUINO EVENTUALLY CRASHES HERE, AFTER THE POST AND BEFORE THE PRINTLN
//ARDUINO EVENTUALLY CRASHES HERE, AFTER THE POST AND BEFORE THE PRINTLN
//ARDUINO EVENTUALLY CRASHES HERE, AFTER THE POST AND BEFORE THE PRINTLN
//ARDUINO EVENTUALLY CRASHES HERE, AFTER THE POST AND BEFORE THE PRINTLN
//ARDUINO EVENTUALLY CRASHES HERE, AFTER THE POST AND BEFORE THE PRINTLN
if(classDebugSerial)
Serial.println(responseBody);
deserializeJson(responseJson, responseBody);
delete[] responseBody;
if(classDebugSerial)
{
Serial.print("responseJson size: ");
Serial.println(responseJson.memoryUsage());
}
previousHardwareResults.remove("updatedSettings");
if(responseJson.containsKey("data"))
{
serializeJson(responseJson["data"], responseData_string);
serializeJson(previousHardwareResults, previousData_string);
if(strcmp(previousData_string, responseData_string) == 0)
previousHardwareResults["updatedSettings"] = false;
else {
previousHardwareResults = responseJson["data"];
previousHardwareResults["updatedSettings"] = true;
}
delete[] responseData_string;
delete[] previousData_string;
} else
previousHardwareResults["updatedSettings"] = false;
responseJson.clear();
responseJson.garbageCollect();
if(classDebugLed)
WiFiDrv::analogWrite(GREEN_LED, 0);
}
void get_lanip_address(char ipAddress[16])
{
//var
IPAddress lanIPObject = WiFi.localIP();
char ipOctet[3];
//var
sprintf(ipOctet, "%d", lanIPObject[0]);
strcat(ipAddress, ipOctet);
strcat(ipAddress, ".");
sprintf(ipOctet, "%d", lanIPObject[1]);
strcat(ipAddress, ipOctet);
strcat(ipAddress, ".");
sprintf(ipOctet, "%d", lanIPObject[2]);
strcat(ipAddress, ipOctet);
strcat(ipAddress, ".");
sprintf(ipOctet, "%d", lanIPObject[3]);
strcat(ipAddress, ipOctet);
}
void get_wanip_address(char ipAddress[16])
{
network_get("api.ipify.org", "/", ipAddress);
}
void putToSleep(int ms)
{
if(classDebugSerial)
{
Serial.print(F("---=== Sleep for "));
if(ms < 1000) {
Serial.print(ms);
Serial.print(F(" ms"));
} else if(ms < 60000) {
Serial.print(ms / 1000);
Serial.print(F(" seconds"));
} else if(ms < 3600000) {
Serial.print(ms / 60000);
Serial.print(F(" minutes"));
} else {
Serial.print(ms / 3600000);
Serial.print(F(" hours"));
};
Serial.println(F(" ===---"));
}
if(ms > 60000)
{
//If sleeping more then 1 min then disconnect
Serial.println(F("Disconnect WiFi"));
WiFi.disconnect();
WiFi.end();
}
if(classDebugSerial)
{
delay(ms);
Serial.println(F("---=== Wake ===---"));
}
else
LowPower.deepSleep(ms);
}
void network_get(char domain[], char url[], char body[])
{
//var
WiFiClient wifiClient;
unsigned long requestTimeout = millis() + 5000;
short headerMarker = 0;
short responseCharacter = 0;
unsigned int bodyRunner = 0;
//var
if (!wifiClient.connect(domain, 80)) {
if(classDebugSerial)
{
Serial.print(F("Failed to connect to "));
Serial.println(domain);
}
} else {
wifiClient.print(F("GET "));
wifiClient.print(url);
wifiClient.println(F(" HTTP/1.1"));
wifiClient.print(F("Host: "));
wifiClient.println(domain);
wifiClient.println(F("Connection: close"));
wifiClient.println();
while (wifiClient.available() == 0) {
if (requestTimeout - millis() < 0) {
if(classDebugSerial)
Serial.println(F("get request Timeout"));
wifiClient.stop();
return;
}
}
while (wifiClient.connected() && headerMarker != 4)
{
responseCharacter = wifiClient.read();
if (responseCharacter > 0)
{
//The end of the header is *defined* as an empty line, the sequence "\r\n\r\n".
switch (headerMarker)
{
case 0:
case 2:
if (responseCharacter == '\r') headerMarker++; else headerMarker = 0;
break;
case 1:
case 3:
if (responseCharacter == '\n') headerMarker++; else headerMarker = 0;
break;
}
}
}
if (headerMarker == 4)
{
//Found blank line, everything else is payload.
while (wifiClient.available())
body[bodyRunner++] = wifiClient.read();
body[bodyRunner] = '\0';
}
}
wifiClient.flush();
wifiClient.stop();
}
void network_securePost(char domain[], char url[], char contentType[], char postBody[], char body[])
{
//var
WiFiSSLClient wifiClient;
unsigned long requestTimeout = millis() + 5000;
short headerMarker = 0;
short responseCharacter = 0;
unsigned int bodyRunner = 0;
//var
if (!wifiClient.connect(domain, 443)) {
if(classDebugSerial)
{
Serial.print(F("Failed to connect to "));
Serial.println(domain);
}
} else {
wifiClient.print(F("POST "));
wifiClient.print(url);
wifiClient.println(F(" HTTP/1.1"));
wifiClient.print(F("Host: "));
wifiClient.println(domain);
wifiClient.print(F("Content-type: "));
wifiClient.println(contentType);
wifiClient.print(F("Content-Length: "));
wifiClient.println(strlen(postBody));
wifiClient.println(F("Connection: close"));
wifiClient.println();
wifiClient.println(postBody);
while (wifiClient.available() == 0) {
if (requestTimeout - millis() < 0) {
if(classDebugSerial)
Serial.println(F("secure post request Timeout"));
wifiClient.stop();
return;
}
}
while (wifiClient.connected() && headerMarker != 4)
{
responseCharacter = wifiClient.read();
if (responseCharacter > 0)
{
//The end of the header is *defined* as an empty line, the sequence "\r\n\r\n".
switch (headerMarker)
{
case 0:
case 2:
if (responseCharacter == '\r') headerMarker++; else headerMarker = 0;
break;
case 1:
case 3:
if (responseCharacter == '\n') headerMarker++; else headerMarker = 0;
break;
}
}
}
if (headerMarker == 4)
{
//Found blank line, everything else is payload.
while (wifiClient.available())
body[bodyRunner++] = wifiClient.read();
body[bodyRunner] = '\0';
}
}
wifiClient.flush();
wifiClient.stop();
}
void hardwareInit(bool debugSerial, bool debugLed)
{
classDebugSerial = debugSerial;
classDebugLed = debugLed;
WiFiDrv::pinMode(RED_LED, OUTPUT);
WiFiDrv::pinMode(GREEN_LED, OUTPUT);
WiFiDrv::pinMode(BLUE_LED, OUTPUT);
get_mac_address();
}
void get_mac_address(void)
{
//var
char hexChar[2];
byte mac[6];
//var
WiFi.macAddress(mac);
for (int runner = 5; runner >= 0; runner--) {
sprintf(hexChar, "%02x", mac[runner]);
strcat(macAddress, hexChar);
if (runner > 0)
strcat(macAddress, ":");
}
}
void display_authCode(StaticJsonDocument<256> hardwareQuery)
{
if(hardwareQuery["updatedSettings"])
{
Serial.println(F("This Device has not yet been activated."));
Serial.println(F("Please access the Device Manager to"));
Serial.println(F("add this device to your account"));
Serial.println(hardwareQuery["authCode"].as<const char*>());
}
};