Clean code and add comments

This commit is contained in:
Phat Nguyen
2024-02-04 15:04:38 +07:00
parent 990f2482bb
commit 335fad3f0d
36 changed files with 3785 additions and 1636 deletions

View File

@ -32,67 +32,337 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
#include <Arduino_JSON.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266WiFi.h>
#include <U8g2lib.h>
#include <WiFiClient.h>
#include <WiFiManager.h>
typedef struct {
bool inF; /** Temperature unit */
bool inUSAQI; /** PMS standard */
uint8_t ledBarMode; /** @ref UseLedBar*/
char model[16]; /** Model string value, Just define, don't know how much
memory usage */
char mqttBroker[128]; /** Mqtt broker link */
uint32_t _check; /** Checksum configuration data */
} ServerConfig_t;
static ServerConfig_t serverConfig;
#define WIFI_CONNECT_COUNTDOWN_MAX 180 /** sec */
#define WIFI_CONNECT_RETRY_MS 10000 /** ms */
#define LED_BAR_COUNT_INIT_VALUE (-1) /** */
#define LED_BAR_ANIMATION_PERIOD 100 /** ms */
#define DISP_UPDATE_INTERVAL 5000 /** ms */
#define SERVER_CONFIG_UPDATE_INTERVAL 30000 /** ms */
#define SERVER_SYNC_INTERVAL 60000 /** ms */
#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
#define SENSOR_CO2_UPDATE_INTERVAL 5000 /** ms */
#define SENSOR_PM_UPDATE_INTERVAL 5000 /** ms */
#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 5000 /** ms */
#define DISPLAY_DELAY_SHOW_CONTENT_MS 3000 /** ms */
#define WIFI_HOTSPOT_PASSWORD_DEFAULT \
"cleanair" /** default WiFi AP password \
*/
AirGradient ag = AirGradient(BOARD_DIY_BASIC_KIT);
/**
* @brief Use use LED bar state
*/
typedef enum {
UseLedBarOff, /** Don't use LED bar */
UseLedBarPM, /** Use LED bar for PMS */
UseLedBarCO2, /** Use LED bar for CO2 */
} UseLedBar;
// CONFIGURATION START
/**
* @brief Schedule handle with timing period
*
*/
class AgSchedule {
public:
AgSchedule(int period, void (*handler)(void))
: period(period), handler(handler) {}
void run(void) {
uint32_t ms = (uint32_t)(millis() - count);
if (ms >= period) {
/** Call handler */
handler();
// set to the endpoint you would like to use
String APIROOT = "http://hw.airgradient.com/";
Serial.printf("[AgSchedule] handle 0x%08x, period: %d(ms)\r\n",
(unsigned int)handler, period);
String wifiApPass = "cleanair";
/** Update period time */
count = millis();
}
}
// set to true if you want to connect to wifi. You have 60 seconds to connect.
// Then it will go into an offline mode.
boolean connectWIFI = true;
private:
void (*handler)(void);
int period;
int count;
};
// CONFIGURATION END
/**
* @brief AirGradient server configuration and sync data
*
*/
class AgServer {
public:
void begin(void) {
inF = false;
inUSAQI = false;
configFailed = false;
serverFailed = false;
memset(models, 0, sizeof(models));
memset(mqttBroker, 0, sizeof(mqttBroker));
}
unsigned long currentMillis = 0;
/**
* @brief Get server configuration
*
* @param id Device ID
* @return true Success
* @return false Failure
*/
bool pollServerConfig(String id) {
String uri =
"http://hw.airgradient.com/sensors/airgradient:" + id + "/one/config";
const int oledInterval = 5000;
unsigned long previousOled = 0;
bool co2CalibrationRequest = false;
uint32_t serverConfigLoadTime = 0;
String HOSTPOT = "";
/** Init http client */
WiFiClient wifiClient;
HTTPClient client;
if (client.begin(wifiClient, uri) == false) {
configFailed = true;
return false;
}
const int sendToServerInterval = 60000;
const int pollServerConfigInterval = 30000;
const int co2CalibCountdown = 5; /** Seconds */
unsigned long previoussendToServer = 0;
/** Get */
int retCode = client.GET();
if (retCode != 200) {
client.end();
configFailed = true;
return false;
}
const int co2Interval = 5000;
unsigned long previousCo2 = 0;
int Co2 = 0;
/** clear failed */
configFailed = false;
const int pm25Interval = 5000;
unsigned long previousPm25 = 0;
int pm25 = 0;
/** Get response string */
String respContent = client.getString();
client.end();
Serial.println("Get server config: " + respContent);
const int tempHumInterval = 2500;
unsigned long previousTempHum = 0;
float temp = 0;
int hum = 0;
long val;
/** Parse JSON */
JSONVar root = JSON.parse(respContent);
if (JSON.typeof(root) == "undefined") {
/** JSON invalid */
return false;
}
void failedHandler(String msg);
void boardInit(void);
void getServerConfig(void);
void co2Calibration(void);
/** Get "country" */
if (JSON.typeof_(root["country"]) == "string") {
String country = root["country"];
if (country == "US") {
inF = true;
} else {
inF = false;
}
}
/** Get "pmsStandard" */
if (JSON.typeof_(root["pmsStandard"]) == "string") {
String standard = root["pmsStandard"];
if (standard == "ugm3") {
inUSAQI = false;
} else {
inUSAQI = true;
}
}
/** Get "co2CalibrationRequested" */
if (JSON.typeof_(root["co2CalibrationRequested"]) == "boolean") {
co2Calib = root["co2CalibrationRequested"];
}
/** Get "ledBarMode" */
if (JSON.typeof_(root["ledBarMode"]) == "string") {
String mode = root["ledBarMode"];
if (mode == "co2") {
ledBarMode = UseLedBarCO2;
} else if (mode == "pm") {
ledBarMode = UseLedBarPM;
} else if (mode == "off") {
ledBarMode = UseLedBarOff;
} else {
ledBarMode = UseLedBarOff;
}
}
/** Get model */
if (JSON.typeof_(root["model"]) == "string") {
String model = root["model"];
if (model.length()) {
int len =
model.length() < sizeof(models) ? model.length() : sizeof(models);
memset(models, 0, sizeof(models));
memcpy(models, model.c_str(), len);
}
}
/** Get "mqttBrokerUrl" */
if (JSON.typeof_(root["mqttBrokerUrl"]) == "string") {
String mqtt = root["mqttBrokerUrl"];
if (mqtt.length()) {
int len = mqtt.length() < sizeof(mqttBroker) ? mqtt.length()
: sizeof(mqttBroker);
memset(mqttBroker, 0, sizeof(mqttBroker));
memcpy(mqttBroker, mqtt.c_str(), len);
}
}
/** Show configuration */
showServerConfig();
return true;
}
bool postToServer(String id, String payload) {
/**
* @brief Only post data if WiFi is connected
*/
if (WiFi.isConnected() == false) {
return false;
}
Serial.printf("Post payload: %s\r\n", payload.c_str());
String uri =
"http://hw.airgradient.com/sensors/airgradient:" + id + "/measures";
WiFiClient wifiClient;
HTTPClient client;
if (client.begin(wifiClient, uri.c_str()) == false) {
return false;
}
client.addHeader("content-type", "application/json");
int retCode = client.POST(payload);
client.end();
if ((retCode == 200) || (retCode == 429)) {
serverFailed = false;
return true;
}
serverFailed = true;
return false;
}
/**
* @brief Get temperature configuration unit
*
* @return true F unit
* @return false C Unit
*/
bool isTemperatureUnitF(void) { return inF; }
/**
* @brief Get PMS standard unit
*
* @return true USAQI
* @return false ugm3
*/
bool isPMSinUSAQI(void) { return inUSAQI; }
/**
* @brief Get status of get server coniguration is failed
*
* @return true Failed
* @return false Success
*/
bool isConfigFailed(void) { return configFailed; }
/**
* @brief Get status of post server configuration is failed
*
* @return true Failed
* @return false Success
*/
bool isServerFailed(void) { return serverFailed; }
/**
* @brief Get request calibration CO2
*
* @return true Requested. If result = true, it's clear after function call
* @return false Not-requested
*/
bool isCo2Calib(void) {
bool ret = co2Calib;
if (ret) {
co2Calib = false;
}
return ret;
}
/**
* @brief Get device configuration model name
*
* @return String Model name, empty string if server failed
*/
String getModelName(void) { return String(models); }
/**
* @brief Get mqttBroker url
*
* @return String Broker url, empty if server failed
*/
String getMqttBroker(void) { return String(mqttBroker); }
/**
* @brief Show server configuration parameter
*/
void showServerConfig(void) {
Serial.println("Server configuration: ");
Serial.printf(" inF: %s\r\n", inF ? "true" : "false");
Serial.printf(" inUSAQI: %s\r\n", inUSAQI ? "true" : "false");
Serial.printf("useRGBLedBar: %d\r\n", (int)ledBarMode);
Serial.printf(" Model: %s\r\n", models);
Serial.printf(" Mqtt Broker: %s\r\n", mqttBroker);
}
/**
* @brief Get server config led bar mode
*
* @return UseLedBar
*/
UseLedBar getLedBarMode(void) { return ledBarMode; }
private:
bool inF; /** Temperature unit, true: F, false: C */
bool inUSAQI; /** PMS unit, true: USAQI, false: ugm3 */
bool configFailed; /** Flag indicate get server configuration failed */
bool serverFailed; /** Flag indicate post data to server failed */
bool co2Calib; /** Is co2Ppmcalibration requset */
UseLedBar ledBarMode = UseLedBarCO2; /** */
char models[20]; /** */
char mqttBroker[256]; /** */
};
AgServer agServer;
/** Create airgradient instance for 'DIY_BASIC' board */
AirGradient ag = AirGradient(DIY_BASIC);
static int co2Ppm = -1;
static int pm25 = -1;
static float temp = -1;
static int hum = -1;
static long val;
static String wifiSSID = "";
static bool wifiHasConfig = false; /** */
static void boardInit(void);
static void failedHandler(String msg);
static void co2Calibration(void);
static void serverConfigPoll(void);
static void co2Poll(void);
static void pmPoll(void);
static void tempHumPoll(void);
static void sendDataToServer(void);
static void dispHandler(void);
static String getDevId(void);
static void updateWiFiConnect(void);
AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL, serverConfigPoll);
AgSchedule serverSchedule(SERVER_SYNC_INTERVAL, sendDataToServer);
AgSchedule dispSchedule(DISP_UPDATE_INTERVAL, dispHandler);
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Poll);
AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, pmPoll);
AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumPoll);
void setup() {
Serial.begin(115200);
@ -103,85 +373,53 @@ void setup() {
/** Board init */
boardInit();
/** Init AirGradient server */
agServer.begin();
/** Show boot display */
displayShowText("Basic v4", "Lib:" + ag.getVersion(), "");
displayShowText("DIY basic", "Lib:" + ag.getVersion(), "");
delay(2000);
if (connectWIFI) {
connectToWifi();
/** WiFi connect */
connectToWifi();
if (WiFi.status() == WL_CONNECTED) {
wifiHasConfig = true;
sendPing();
agServer.pollServerConfig(getDevId());
if (agServer.isCo2Calib()) {
co2Calibration();
}
}
/** Show display */
displayShowText("Warm Up", "Serial#", String(ESP.getChipId(), HEX));
delay(10000);
getServerConfig();
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
}
void loop() {
currentMillis = millis();
updateOLED();
updateCo2();
updatePm25();
updateTempHum();
sendToServer();
getServerConfig();
configSchedule.run();
serverSchedule.run();
dispSchedule.run();
co2Schedule.run();
pmsSchedule.run();
tempHumSchedule.run();
updateWiFiConnect();
}
void updateCo2() {
if (currentMillis - previousCo2 >= co2Interval) {
previousCo2 += co2Interval;
Co2 = ag.s8.getCo2();
Serial.println(String(Co2));
}
}
void updatePm25() {
if (currentMillis - previousPm25 >= pm25Interval) {
previousPm25 += pm25Interval;
if (ag.pms5003.readData()) {
pm25 = ag.pms5003.getPm25Ae();
Serial.printf("PM25: %d\r\n", pm25);
}
}
}
void updateTempHum() {
if (currentMillis - previousTempHum >= tempHumInterval) {
previousTempHum += tempHumInterval;
/** Get temperature and humidity */
temp = ag.sht.getTemperature();
hum = ag.sht.getRelativeHumidity();
/** Print debug message */
Serial.printf("SHT Humidity: %d%, Temperature: %0.2f\r\n", hum, temp);
}
}
void updateOLED() {
if (currentMillis - previousOled >= oledInterval) {
previousOled += oledInterval;
String ln1;
String ln2;
String ln3;
if (serverConfig.inUSAQI) {
ln1 = "AQI:" + String(ag.pms5003.convertPm25ToUsAqi(pm25));
} else {
ln1 = "PM :" + String(pm25) + " ug";
}
ln2 = "CO2:" + String(Co2);
if (serverConfig.inF) {
ln3 =
String((temp * 9 / 5) + 32).substring(0, 4) + " " + String(hum) + "%";
} else {
ln3 = String(temp).substring(0, 4) + " " + String(hum) + "%";
}
displayShowText(ln1, ln2, ln3);
static void sendPing() {
JSONVar root;
root["wifi"] = WiFi.RSSI();
root["boot"] = 0;
// delay(1500);
if (agServer.postToServer(getDevId(), JSON.stringify(root))) {
// Ping Server succses
} else {
// Ping server failed
}
// delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
}
void displayShowText(String ln1, String ln2, String ln3) {
@ -198,58 +436,24 @@ void displayShowText(String ln1, String ln2, String ln3) {
ag.display.show();
}
void sendToServer() {
if (currentMillis - previoussendToServer >= sendToServerInterval) {
previoussendToServer += sendToServerInterval;
String payload = "{\"wifi\":" + String(WiFi.RSSI()) +
(Co2 < 0 ? "" : ", \"rco2\":" + String(Co2)) +
(pm25 < 0 ? "" : ", \"pm02\":" + String(pm25)) +
", \"atmp\":" + String(temp) +
(hum < 0 ? "" : ", \"rhum\":" + String(hum)) + "}";
if (WiFi.status() == WL_CONNECTED) {
Serial.println(payload);
String POSTURL = APIROOT +
"sensors/airgradient:" + String(ESP.getChipId(), HEX) +
"/measures";
Serial.println(POSTURL);
WiFiClient client;
HTTPClient http;
http.begin(client, POSTURL);
http.addHeader("content-type", "application/json");
int httpCode = http.POST(payload);
String response = http.getString();
Serial.println(httpCode);
Serial.println(response);
http.end();
} else {
Serial.println("WiFi Disconnected");
}
}
}
// Wifi Manager
void connectToWifi() {
WiFiManager wifiManager;
// WiFi.disconnect(); //to delete previous saved hotspot
String HOTSPOT = "AG-" + String(ESP.getChipId(), HEX);
// displayShowText("Connect", "AG-", String(ESP.getChipId(), HEX));
delay(2000);
// wifiManager.setTimeout(90);
wifiSSID = "AG-" + String(ESP.getChipId(), HEX);
wifiManager.setConfigPortalBlocking(false);
wifiManager.setConfigPortalTimeout(180);
wifiManager.autoConnect(HOTSPOT.c_str(), wifiApPass.c_str());
wifiManager.setConfigPortalTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
wifiManager.autoConnect(wifiSSID.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT);
uint32_t lastTime = millis();
int count = 179;
displayShowText("180 sec", "SSID:",HOTSPOT);
int count = WIFI_CONNECT_COUNTDOWN_MAX;
displayShowText(String(WIFI_CONNECT_COUNTDOWN_MAX) + " sec",
"SSID:", wifiSSID);
while (wifiManager.getConfigPortalActive()) {
wifiManager.process();
uint32_t ms = (uint32_t)(millis() - lastTime);
if (ms >= 1000) {
lastTime = millis();
displayShowText(String(count) + " sec", "SSID:",HOTSPOT);
displayShowText(String(count) + " sec", "SSID:", wifiSSID);
count--;
// Timeout
@ -261,18 +465,11 @@ void connectToWifi() {
if (!WiFi.isConnected()) {
displayShowText("Booting", "offline", "mode");
Serial.println("failed to connect and hit timeout");
delay(6000);
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
}
}
void failedHandler(String msg) {
while (true) {
Serial.println(msg);
delay(1000);
}
}
void boardInit(void) {
static void boardInit(void) {
/** Init SHT sensor */
if (ag.sht.begin(Wire) == false) {
failedHandler("SHT init failed");
@ -293,152 +490,18 @@ void boardInit(void) {
ag.display.setTextColor(1);
}
void showConfig(void) {
Serial.println("Server configuration: ");
Serial.printf(" inF: %s\r\n", serverConfig.inF ? "true" : "false");
Serial.printf(" inUSAQI: %s\r\n",
serverConfig.inUSAQI ? "true" : "false");
Serial.printf("useRGBLedBar: %d\r\n", (int)serverConfig.ledBarMode);
Serial.printf(" Model: %.*s\r\n", sizeof(serverConfig.model),
serverConfig.model);
Serial.printf(" Mqtt Broker: %.*s\r\n", sizeof(serverConfig.mqttBroker),
serverConfig.mqttBroker);
}
void updateServerConfigLoadTime(void) {
serverConfigLoadTime = millis();
if (serverConfigLoadTime == 0) {
serverConfigLoadTime = 1;
static void failedHandler(String msg) {
while (true) {
Serial.println(msg);
delay(1000);
}
}
void getServerConfig(void) {
/** Only trigger load configuration again after pollServerConfigInterval sec
*/
if (serverConfigLoadTime) {
uint32_t ms = (uint32_t)(millis() - serverConfigLoadTime);
if (ms < pollServerConfigInterval) {
return;
}
}
updateServerConfigLoadTime();
Serial.println("Trigger load server configuration");
if (WiFi.status() != WL_CONNECTED) {
Serial.println(
"Ignore get server configuration because WIFI not connected");
return;
}
// WiFiClient wifiClient;
HTTPClient httpClient;
String getUrl = "http://hw.airgradient.com/sensors/airgradient:" +
String(ESP.getChipId(), HEX) + "/one/config";
Serial.println("HttpClient get: " + getUrl);
WiFiClient client;
if (httpClient.begin(client, getUrl) == false) {
Serial.println("HttpClient init failed");
updateServerConfigLoadTime();
return;
}
int respCode = httpClient.GET();
/** get failure */
if (respCode != 200) {
Serial.printf("HttpClient get failed: %d\r\n", respCode);
updateServerConfigLoadTime();
return;
}
String respContent = httpClient.getString();
Serial.println("Server config: " + respContent);
/** Parse JSON */
JSONVar root = JSON.parse(respContent);
if (JSON.typeof_(root) == "undefined") {
Serial.println("Server configura JSON invalid");
updateServerConfigLoadTime();
return;
}
/** Get "country" */
bool inF = serverConfig.inF;
if (JSON.typeof_(root["country"]) == "string") {
String country = root["country"];
if (country == "US") {
inF = true;
} else {
inF = false;
}
}
/** Get "pmStandard" */
bool inUSAQI = serverConfig.inUSAQI;
if (JSON.typeof_(root["pmStandard"]) == "string") {
String standard = root["pmStandard"];
if (standard == "ugm3") {
inUSAQI = false;
} else {
inUSAQI = true;
}
}
/** Get CO2 "co2CalibrationRequested" */
co2CalibrationRequest = false;
if (JSON.typeof_(root["co2CalibrationRequested"]) == "boolean") {
co2CalibrationRequest = root["co2CalibrationRequested"];
}
/** get "model" */
String model = "";
if (JSON.typeof_(root["model"]) == "string") {
String _model = root["model"];
model = _model;
}
/** get "mqttBrokerUrl" */
String mqtt = "";
if (JSON.typeof_(root["mqttBrokerUrl"]) == "string") {
String _mqtt = root["mqttBrokerUrl"];
mqtt = _mqtt;
}
if (inF != serverConfig.inF) {
serverConfig.inF = inF;
}
if (inUSAQI != serverConfig.inUSAQI) {
serverConfig.inUSAQI = inUSAQI;
}
if (model.length()) {
if (model != String(serverConfig.model)) {
memset(serverConfig.model, 0, sizeof(serverConfig.model));
memcpy(serverConfig.model, model.c_str(), model.length());
}
}
if (mqtt.length()) {
if (mqtt != String(serverConfig.mqttBroker)) {
memset(serverConfig.mqttBroker, 0, sizeof(serverConfig.mqttBroker));
memcpy(serverConfig.mqttBroker, mqtt.c_str(), mqtt.length());
}
}
/** Show server configuration */
showConfig();
/** Calibration */
if (co2CalibrationRequest) {
co2Calibration();
}
}
void co2Calibration(void) {
static void co2Calibration(void) {
/** Count down for co2CalibCountdown secs */
for (int i = 0; i < co2CalibCountdown; i++) {
for (int i = 0; i < SENSOR_CO2_CALIB_COUNTDOWN_MAX; i++) {
displayShowText("CO2 calib", "after",
String(co2CalibCountdown - i) + " sec");
String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec");
delay(1000);
}
@ -452,9 +515,110 @@ void co2Calibration(void) {
count++;
}
displayShowText("Finish", "after", String(count) + " sec");
delay(2000);
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
} else {
displayShowText("Calib", "failure!!!", "");
delay(2000);
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
}
}
static void serverConfigPoll(void) {
if (agServer.pollServerConfig(getDevId())) {
if (agServer.isCo2Calib()) {
co2Calibration();
}
}
}
static void co2Poll() {
co2Ppm = ag.s8.getCo2();
Serial.printf("CO2 index: %d\r\n", co2Ppm);
}
void pmPoll() {
if (ag.pms5003.readData()) {
pm25 = ag.pms5003.getPm25Ae();
Serial.printf("PMS2.5: %d\r\n", pm25);
} else {
pm25 = -1;
}
}
static void tempHumPoll() {
temp = ag.sht.getTemperature();
hum = ag.sht.getRelativeHumidity();
Serial.printf("Temperature: %0.2f\r\n", temp);
Serial.printf(" Humidity: %d\r\n", hum);
}
static void sendDataToServer() {
JSONVar root;
root["wifi"] = WiFi.RSSI();
if (co2Ppm >= 0) {
root["rco2"] = co2Ppm;
}
if (pm25 >= 0) {
root["pm02"] = pm25;
}
if (temp >= 0) {
root["atmp"] = temp;
}
if (hum >= 0) {
root["rhum"] = hum;
}
if (agServer.postToServer(getDevId(), JSON.stringify(root)) == false) {
Serial.println("Post to server failed");
}
}
static void dispHandler() {
String ln1 = "";
String ln2 = "";
String ln3 = "";
if (agServer.isPMSinUSAQI()) {
ln1 = "AQI:" + String(ag.pms5003.convertPm25ToUsAqi(pm25));
} else {
ln1 = "PM :" + String(pm25) + " ug";
}
ln2 = "CO2:" + String(co2Ppm);
if (agServer.isTemperatureUnitF()) {
ln3 = String((temp * 9 / 5) + 32).substring(0, 4) + " " + String(hum) + "%";
} else {
ln3 = String(temp).substring(0, 4) + " " + String(hum) + "%";
}
displayShowText(ln1, ln2, ln3);
}
static String getDevId(void) { return getNormalizedMac(); }
/**
* @brief WiFi reconnect handler
*/
static void updateWiFiConnect(void) {
static uint32_t lastRetry;
if (wifiHasConfig == false) {
return;
}
if (WiFi.isConnected()) {
lastRetry = millis();
return;
}
uint32_t ms = (uint32_t)(millis() - lastRetry);
if (ms >= WIFI_CONNECT_RETRY_MS) {
lastRetry = millis();
WiFi.reconnect();
Serial.printf("Re-Connect WiFi\r\n");
}
}
String getNormalizedMac() {
String mac = WiFi.macAddress();
mac.replace(":", "");
mac.toLowerCase();
return mac;
}