mirror of
https://github.com/airgradienthq/arduino.git
synced 2025-07-29 16:37:17 +02:00
Clean code and add comments
This commit is contained in:
@ -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;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
775
examples/Open_Air_O-1PP/Open_Air_O-1PP.ino
Normal file
775
examples/Open_Air_O-1PP/Open_Air_O-1PP.ino
Normal file
@ -0,0 +1,775 @@
|
||||
/*
|
||||
This is the code for the AirGradient Open Air open-source hardware outdoor Air Quality Monitor with an ESP32-C3 Microcontroller.
|
||||
|
||||
It is an air quality monitor for PM2.5, CO2, TVOCs, NOx, Temperature and Humidity and can send data over Wifi.
|
||||
|
||||
Open source air quality monitors and kits are available:
|
||||
Indoor Monitor: https://www.airgradient.com/indoor/
|
||||
Outdoor Monitor: https://www.airgradient.com/outdoor/
|
||||
|
||||
Build Instructions: https://www.airgradient.com/documentation/open-air-pst-kit-1-3/
|
||||
|
||||
The codes needs the following libraries installed:
|
||||
“WifiManager by tzapu, tablatronix” tested with version 2.0.16-rc.2
|
||||
"Arduino_JSON" by Arduino Version 0.2.0
|
||||
|
||||
Please make sure you have esp32 board manager installed. Tested with version 2.0.11.
|
||||
|
||||
Important flashing settings:
|
||||
- Set board to "ESP32C3 Dev Module"
|
||||
- Enable "USB CDC On Boot"
|
||||
- Flash frequency "80Mhz"
|
||||
- Flash mode "QIO"
|
||||
- Flash size "4MB"
|
||||
- Partition scheme "Default 4MB with spiffs (1.2MB APP/1,5MB SPIFFS)"
|
||||
- JTAG adapter "Disabled"
|
||||
|
||||
If you have any questions please visit our forum at
|
||||
https://forum.airgradient.com/
|
||||
|
||||
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
|
||||
*/
|
||||
|
||||
#include <AirGradient.h>
|
||||
#include <Arduino_JSON.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <HardwareSerial.h>
|
||||
#include <WiFiManager.h>
|
||||
#include <Wire.h>
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Application state machine state
|
||||
*
|
||||
*/
|
||||
enum {
|
||||
APP_SM_WIFI_MANAGER_MODE, /** In WiFi Manger Mode */
|
||||
APP_SM_WIFI_MAMAGER_PORTAL_ACTIVE, /** WiFi Manager has connected to mobile
|
||||
phone */
|
||||
APP_SM_WIFI_MANAGER_STA_CONNECTING, /** After SSID and PW entered and OK
|
||||
clicked, connection to WiFI network is
|
||||
attempted*/
|
||||
APP_SM_WIFI_MANAGER_STA_CONNECTED, /** Connecting to WiFi worked */
|
||||
APP_SM_WIFI_OK_SERVER_CONNECTING, /** Once connected to WiFi an attempt to
|
||||
reach the server is performed */
|
||||
APP_SM_WIFI_OK_SERVER_CONNNECTED, /** Server is reachable, all fine */
|
||||
/** Exceptions during WIFi Setup */
|
||||
APP_SM_WIFI_MANAGER_CONNECT_FAILED, /** Cannot connect to WiFi (e.g. wrong
|
||||
password, WPA Enterprise etc.) */
|
||||
APP_SM_WIFI_OK_SERVER_CONNECT_FAILED, /** Connected to WiFi but server not
|
||||
reachable, e.g. firewall block/
|
||||
whitelisting needed etc. */
|
||||
APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED, /** Server reachable but sensor
|
||||
not configured correctly*/
|
||||
|
||||
/** During Normal Operation */
|
||||
APP_SM_WIFI_LOST, /** Connection to WiFi network failed credentials incorrect
|
||||
encryption not supported etc. */
|
||||
APP_SM_SERVER_LOST, /** Connected to WiFi network but the server cannot be
|
||||
reached through the internet, e.g. blocked by firewall
|
||||
*/
|
||||
APP_SM_SENSOR_CONFIG_FAILED, /** Server is reachable but there is some
|
||||
configuration issue to be fixed on the server
|
||||
side */
|
||||
APP_SM_NORMAL,
|
||||
};
|
||||
|
||||
#define LED_FAST_BLINK_DELAY 250 /** ms */
|
||||
#define LED_SLOW_BLINK_DELAY 1000 /** ms */
|
||||
#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 \
|
||||
*/
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* @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();
|
||||
|
||||
Serial.printf("[AgSchedule] handle 0x%08x, period: %d(ms)\r\n",
|
||||
(unsigned int)handler, period);
|
||||
|
||||
/** Update period time */
|
||||
count = millis();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void (*handler)(void);
|
||||
int period;
|
||||
int count;
|
||||
};
|
||||
|
||||
/**
|
||||
* @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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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";
|
||||
|
||||
/** Init http client */
|
||||
HTTPClient client;
|
||||
if (client.begin(uri) == false) {
|
||||
configFailed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Get */
|
||||
int retCode = client.GET();
|
||||
if (retCode != 200) {
|
||||
client.end();
|
||||
configFailed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** clear failed */
|
||||
configFailed = false;
|
||||
|
||||
/** Get response string */
|
||||
String respContent = client.getString();
|
||||
client.end();
|
||||
Serial.println("Get server config: " + respContent);
|
||||
|
||||
/** Parse JSON */
|
||||
JSONVar root = JSON.parse(respContent);
|
||||
if (JSON.typeof(root) == "undefined") {
|
||||
/** JSON invalid */
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 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 'OPEN_AIR_OUTDOOR' board */
|
||||
AirGradient ag(OPEN_AIR_OUTDOOR);
|
||||
|
||||
static float pm1Value01 = 0;
|
||||
static float pm1Value25 = 0;
|
||||
static float pm1Value10 = 0;
|
||||
static float pm1PCount = 0;
|
||||
static float pm1temp = 0;
|
||||
static float pm1hum = 0;
|
||||
|
||||
static float pm2Value01 = 0;
|
||||
static float pm2Value25 = 0;
|
||||
static float pm2Value10 = 0;
|
||||
static float pm2PCount = 0;
|
||||
static float pm2temp = 0;
|
||||
static float pm2hum = 0;
|
||||
|
||||
static int countPosition = 0;
|
||||
static int targetCount = 20;
|
||||
|
||||
WiFiManager wifiManager; /** wifi manager instance */
|
||||
|
||||
int loopCount = 0;
|
||||
|
||||
static int ledSmState = APP_SM_NORMAL;
|
||||
static bool wifiHasConfig = false;
|
||||
static String wifiSSID = "";
|
||||
|
||||
static void boardInit(void);
|
||||
static void failedHandler(String msg);
|
||||
static String getDevId(void);
|
||||
static void sendDataToServerHandler(void);
|
||||
static void serverConfigPoll(void);
|
||||
static void updateWiFiConnect(void);
|
||||
|
||||
AgSchedule agSyncDataSchedule(2000, sendDataToServerHandler);
|
||||
AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL, serverConfigPoll);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
/** Board init */
|
||||
boardInit();
|
||||
|
||||
/** Server init */
|
||||
agServer.begin();
|
||||
|
||||
/** WiFi connect */
|
||||
connectToWifi();
|
||||
|
||||
if (WiFi.isConnected()) {
|
||||
sendPing();
|
||||
|
||||
if (agServer.pollServerConfig(getDevId())) {
|
||||
if (agServer.isConfigFailed()) {
|
||||
ledSmHandler(APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED);
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ledSmHandler(APP_SM_NORMAL);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
agSyncDataSchedule.run();
|
||||
updateWiFiConnect();
|
||||
}
|
||||
|
||||
void sendPing() {
|
||||
JSONVar root;
|
||||
root["wifi"] = WiFi.RSSI();
|
||||
root["boot"] = loopCount;
|
||||
if (agServer.postToServer(getDevId(), JSON.stringify(root))) {
|
||||
ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNNECTED);
|
||||
} else {
|
||||
ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNECT_FAILED);
|
||||
}
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
}
|
||||
|
||||
void sendToServer(int pm1Value01, int pm1Value25, int pm1Value10, int pm1PCount,
|
||||
float pm1temp, float pm1hum, int pm2Value01, int pm2Value25,
|
||||
int pm2Value10, int pm2PCount, float pm2temp, float pm2hum) {
|
||||
|
||||
JSONVar root;
|
||||
root["wifi"] = WiFi.RSSI();
|
||||
root["boot"] = loopCount;
|
||||
root["pm01"] = (int)((pm1Value01 + pm2Value01) / 2);
|
||||
root["pm02"] = (int)((pm1Value25 + pm2Value25) / 2);
|
||||
root["pm003_count"] = (int)((pm1PCount + pm2PCount) / 2);
|
||||
root["atmp"] = (int)((pm1temp + pm2temp) / 2);
|
||||
root["rhum"] = (int)((pm1hum + pm2hum) / 2);
|
||||
root["channels"]["1"]["pm01"] = pm1Value01;
|
||||
root["channels"]["1"]["pm02"] = pm1Value25;
|
||||
root["channels"]["1"]["pm10"] = pm1Value10;
|
||||
root["channels"]["1"]["pm003_count"] = pm1PCount;
|
||||
root["channels"]["1"]["atmp"] = pm1temp;
|
||||
root["channels"]["1"]["rhum"] = pm1hum;
|
||||
root["channels"]["2"]["pm01"] = pm2Value01;
|
||||
root["channels"]["2"]["pm02"] = pm2Value25;
|
||||
root["channels"]["2"]["pm10"] = pm2Value10;
|
||||
root["channels"]["2"]["pm003_count"] = pm2PCount;
|
||||
root["channels"]["2"]["atmp"] = pm2temp;
|
||||
root["channels"]["2"]["rhum"] = pm2hum;
|
||||
|
||||
if (agServer.postToServer(getDevId(), JSON.stringify(root))) {
|
||||
resetWatchdog();
|
||||
}
|
||||
loopCount++;
|
||||
}
|
||||
|
||||
void countdown(int from) {
|
||||
while (from > 0) {
|
||||
Serial.print(from);
|
||||
delay(1000);
|
||||
from--;
|
||||
}
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
void resetWatchdog() { ag.watchdog.reset(); }
|
||||
|
||||
// Wifi Manager
|
||||
void connectToWifi() {
|
||||
wifiSSID = "airgradient-" + String(getNormalizedMac());
|
||||
|
||||
wifiManager.setConfigPortalBlocking(false);
|
||||
wifiManager.setTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
|
||||
|
||||
wifiManager.setAPCallback([](WiFiManager *obj) {
|
||||
/** This callback if wifi connnected failed and try to start configuration
|
||||
* portal */
|
||||
ledSmState = APP_SM_WIFI_MANAGER_MODE;
|
||||
});
|
||||
wifiManager.setSaveConfigCallback([]() {
|
||||
/** Wifi connected save the configuration */
|
||||
ledSmHandler(APP_SM_WIFI_MANAGER_STA_CONNECTED);
|
||||
});
|
||||
wifiManager.setSaveParamsCallback([]() {
|
||||
/** Wifi set connect: ssid, password */
|
||||
ledSmHandler(APP_SM_WIFI_MANAGER_STA_CONNECTING);
|
||||
});
|
||||
wifiManager.autoConnect(wifiSSID.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT);
|
||||
|
||||
xTaskCreate(
|
||||
[](void *obj) {
|
||||
while (wifiManager.getConfigPortalActive()) {
|
||||
wifiManager.process();
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
},
|
||||
"wifi_cfg", 4096, NULL, 10, NULL);
|
||||
|
||||
uint32_t stimer = millis();
|
||||
bool clientConnectChanged = false;
|
||||
while (wifiManager.getConfigPortalActive()) {
|
||||
if (WiFi.isConnected() == false) {
|
||||
if (ledSmState == APP_SM_WIFI_MANAGER_MODE) {
|
||||
uint32_t ms = (uint32_t)(millis() - stimer);
|
||||
if (ms >= 100) {
|
||||
stimer = millis();
|
||||
ledSmHandler(ledSmState);
|
||||
}
|
||||
}
|
||||
|
||||
/** Check for client connect to change led color */
|
||||
bool clientConnected = wifiMangerClientConnected();
|
||||
if (clientConnected != clientConnectChanged) {
|
||||
clientConnectChanged = clientConnected;
|
||||
if (clientConnectChanged) {
|
||||
ledSmHandler(APP_SM_WIFI_MAMAGER_PORTAL_ACTIVE);
|
||||
} else {
|
||||
ledSmHandler(APP_SM_WIFI_MANAGER_MODE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Show display wifi connect result failed */
|
||||
if (WiFi.isConnected() == false) {
|
||||
ledSmHandler(APP_SM_WIFI_MANAGER_CONNECT_FAILED);
|
||||
} else {
|
||||
wifiHasConfig = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ledBlinkDelay(uint32_t tdelay) {
|
||||
ag.statusLed.setOn();
|
||||
delay(tdelay);
|
||||
ag.statusLed.setOff();
|
||||
delay(tdelay);
|
||||
}
|
||||
|
||||
String getNormalizedMac() {
|
||||
String mac = WiFi.macAddress();
|
||||
mac.replace(":", "");
|
||||
mac.toLowerCase();
|
||||
return mac;
|
||||
}
|
||||
|
||||
void boardInit(void) {
|
||||
ag.watchdog.begin();
|
||||
ag.statusLed.begin();
|
||||
ag.button.begin();
|
||||
if (ag.pms5003t_1.begin(Serial0) == false) {
|
||||
failedHandler("PMS5003T_1 init failed");
|
||||
}
|
||||
if (ag.pms5003t_2.begin(Serial1) == false) {
|
||||
failedHandler("PMS5003T_2 init failed");
|
||||
}
|
||||
}
|
||||
|
||||
void failedHandler(String msg) {
|
||||
while (true) {
|
||||
Serial.println(msg);
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
static String getDevId(void) { return getNormalizedMac(); }
|
||||
|
||||
static void sendDataToServerHandler(void) {
|
||||
if (ag.pms5003t_1.readData() && ag.pms5003t_2.readData()) {
|
||||
/** Get data */
|
||||
pm1Value01 = pm1Value01 + ag.pms5003t_1.getPm01Ae();
|
||||
pm1Value25 = pm1Value25 + ag.pms5003t_1.getPm25Ae();
|
||||
pm1Value10 = pm1Value10 + ag.pms5003t_1.getPm10Ae();
|
||||
pm1PCount = pm1PCount + ag.pms5003t_1.getPm03ParticleCount();
|
||||
pm1temp = pm1temp + ag.pms5003t_1.getTemperature();
|
||||
pm1hum = pm1hum + ag.pms5003t_1.getRelativeHumidity();
|
||||
pm2Value01 = pm2Value01 + ag.pms5003t_2.getPm01Ae();
|
||||
pm2Value25 = pm2Value25 + ag.pms5003t_2.getPm25Ae();
|
||||
pm2Value10 = pm2Value10 + ag.pms5003t_2.getPm10Ae();
|
||||
pm2PCount = pm2PCount + ag.pms5003t_2.getPm03ParticleCount();
|
||||
pm2temp = pm2temp + ag.pms5003t_2.getTemperature();
|
||||
pm2hum = pm2hum + ag.pms5003t_2.getRelativeHumidity();
|
||||
countPosition++;
|
||||
if (countPosition == targetCount) {
|
||||
pm1Value01 = pm1Value01 / targetCount;
|
||||
pm1Value25 = pm1Value25 / targetCount;
|
||||
pm1Value10 = pm1Value10 / targetCount;
|
||||
pm1PCount = pm1PCount / targetCount;
|
||||
pm1temp = pm1temp / targetCount;
|
||||
pm1hum = pm1hum / targetCount;
|
||||
pm2Value01 = pm2Value01 / targetCount;
|
||||
pm2Value25 = pm2Value25 / targetCount;
|
||||
pm2Value10 = pm2Value10 / targetCount;
|
||||
pm2PCount = pm2PCount / targetCount;
|
||||
pm2temp = pm2temp / targetCount;
|
||||
pm2hum = pm2hum / targetCount;
|
||||
|
||||
/** Send data */
|
||||
sendToServer(pm1Value01, pm1Value25, pm1Value10, pm1PCount, pm1temp,
|
||||
pm1hum, pm2Value01, pm2Value25, pm2Value10, pm2PCount,
|
||||
pm2temp, pm2hum);
|
||||
|
||||
/** Reset data */
|
||||
countPosition = 0;
|
||||
pm1Value01 = 0;
|
||||
pm1Value25 = 0;
|
||||
pm1Value10 = 0;
|
||||
pm1PCount = 0;
|
||||
pm1temp = 0;
|
||||
pm1hum = 0;
|
||||
pm2Value01 = 0;
|
||||
pm2Value25 = 0;
|
||||
pm2Value10 = 0;
|
||||
pm2PCount = 0;
|
||||
pm2temp = 0;
|
||||
pm2hum = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void serverConfigPoll(void) {
|
||||
if (agServer.pollServerConfig(getDevId())) {
|
||||
Serial.println("Get server configure success");
|
||||
} else {
|
||||
Serial.println("Get server configure failure");
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
void ledSmHandler(int sm) {
|
||||
if (sm > APP_SM_NORMAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
ledSmState = sm;
|
||||
switch (sm) {
|
||||
case APP_SM_WIFI_MANAGER_MODE: {
|
||||
ag.statusLed.setToggle();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_MAMAGER_PORTAL_ACTIVE: {
|
||||
ag.statusLed.setOn();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_MANAGER_STA_CONNECTING: {
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_MANAGER_STA_CONNECTED: {
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_OK_SERVER_CONNECTING: {
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_OK_SERVER_CONNNECTED: {
|
||||
ag.statusLed.setOff();
|
||||
|
||||
/** two time slow blink, then off */
|
||||
for (int i = 0; i < 2; i++) {
|
||||
ledBlinkDelay(LED_SLOW_BLINK_DELAY);
|
||||
}
|
||||
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_MANAGER_CONNECT_FAILED: {
|
||||
/** Three time fast blink then 2 sec pause. Repeat 3 times */
|
||||
ag.statusLed.setOff();
|
||||
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ledBlinkDelay(LED_FAST_BLINK_DELAY);
|
||||
}
|
||||
delay(2000);
|
||||
}
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_OK_SERVER_CONNECT_FAILED: {
|
||||
ag.statusLed.setOff();
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ledBlinkDelay(LED_FAST_BLINK_DELAY);
|
||||
}
|
||||
delay(2000);
|
||||
}
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED: {
|
||||
ag.statusLed.setOff();
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ledBlinkDelay(LED_FAST_BLINK_DELAY);
|
||||
}
|
||||
delay(2000);
|
||||
}
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_LOST: {
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_SERVER_LOST: {
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_SENSOR_CONFIG_FAILED: {
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_NORMAL: {
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool wifiMangerClientConnected(void) {
|
||||
return WiFi.softAPgetStationNum() ? true : false;
|
||||
}
|
792
examples/Open_Air_O-1PPT/Open_Air_O-1PPT.ino
Normal file
792
examples/Open_Air_O-1PPT/Open_Air_O-1PPT.ino
Normal file
@ -0,0 +1,792 @@
|
||||
/*
|
||||
This is the code for the AirGradient Open Air open-source hardware outdoor Air Quality Monitor with an ESP32-C3 Microcontroller.
|
||||
|
||||
It is an air quality monitor for PM2.5, CO2, TVOCs, NOx, Temperature and Humidity and can send data over Wifi.
|
||||
|
||||
Open source air quality monitors and kits are available:
|
||||
Indoor Monitor: https://www.airgradient.com/indoor/
|
||||
Outdoor Monitor: https://www.airgradient.com/outdoor/
|
||||
|
||||
Build Instructions: https://www.airgradient.com/documentation/open-air-pst-kit-1-3/
|
||||
|
||||
The codes needs the following libraries installed:
|
||||
“WifiManager by tzapu, tablatronix” tested with version 2.0.16-rc.2
|
||||
"Arduino_JSON" by Arduino Version 0.2.0
|
||||
|
||||
Please make sure you have esp32 board manager installed. Tested with version 2.0.11.
|
||||
|
||||
Important flashing settings:
|
||||
- Set board to "ESP32C3 Dev Module"
|
||||
- Enable "USB CDC On Boot"
|
||||
- Flash frequency "80Mhz"
|
||||
- Flash mode "QIO"
|
||||
- Flash size "4MB"
|
||||
- Partition scheme "Default 4MB with spiffs (1.2MB APP/1,5MB SPIFFS)"
|
||||
- JTAG adapter "Disabled"
|
||||
|
||||
If you have any questions please visit our forum at
|
||||
https://forum.airgradient.com/
|
||||
|
||||
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
|
||||
*/
|
||||
|
||||
#include <AirGradient.h>
|
||||
#include <Arduino_JSON.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <HardwareSerial.h>
|
||||
#include <WiFiManager.h>
|
||||
#include <Wire.h>
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Application state machine state
|
||||
*
|
||||
*/
|
||||
enum {
|
||||
APP_SM_WIFI_MANAGER_MODE, /** In WiFi Manger Mode */
|
||||
APP_SM_WIFI_MAMAGER_PORTAL_ACTIVE, /** WiFi Manager has connected to mobile
|
||||
phone */
|
||||
APP_SM_WIFI_MANAGER_STA_CONNECTING, /** After SSID and PW entered and OK
|
||||
clicked, connection to WiFI network is
|
||||
attempted*/
|
||||
APP_SM_WIFI_MANAGER_STA_CONNECTED, /** Connecting to WiFi worked */
|
||||
APP_SM_WIFI_OK_SERVER_CONNECTING, /** Once connected to WiFi an attempt to
|
||||
reach the server is performed */
|
||||
APP_SM_WIFI_OK_SERVER_CONNNECTED, /** Server is reachable, all fine */
|
||||
/** Exceptions during WIFi Setup */
|
||||
APP_SM_WIFI_MANAGER_CONNECT_FAILED, /** Cannot connect to WiFi (e.g. wrong
|
||||
password, WPA Enterprise etc.) */
|
||||
APP_SM_WIFI_OK_SERVER_CONNECT_FAILED, /** Connected to WiFi but server not
|
||||
reachable, e.g. firewall block/
|
||||
whitelisting needed etc. */
|
||||
APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED, /** Server reachable but sensor
|
||||
not configured correctly*/
|
||||
|
||||
/** During Normal Operation */
|
||||
APP_SM_WIFI_LOST, /** Connection to WiFi network failed credentials incorrect
|
||||
encryption not supported etc. */
|
||||
APP_SM_SERVER_LOST, /** Connected to WiFi network but the server cannot be
|
||||
reached through the internet, e.g. blocked by firewall
|
||||
*/
|
||||
APP_SM_SENSOR_CONFIG_FAILED, /** Server is reachable but there is some
|
||||
configuration issue to be fixed on the server
|
||||
side */
|
||||
APP_SM_NORMAL,
|
||||
};
|
||||
|
||||
#define LED_FAST_BLINK_DELAY 250 /** ms */
|
||||
#define LED_SLOW_BLINK_DELAY 1000 /** ms */
|
||||
#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 \
|
||||
*/
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* @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();
|
||||
|
||||
Serial.printf("[AgSchedule] handle 0x%08x, period: %d(ms)\r\n",
|
||||
(unsigned int)handler, period);
|
||||
|
||||
/** Update period time */
|
||||
count = millis();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void (*handler)(void);
|
||||
int period;
|
||||
int count;
|
||||
};
|
||||
|
||||
/**
|
||||
* @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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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";
|
||||
|
||||
/** Init http client */
|
||||
HTTPClient client;
|
||||
if (client.begin(uri) == false) {
|
||||
configFailed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Get */
|
||||
int retCode = client.GET();
|
||||
if (retCode != 200) {
|
||||
client.end();
|
||||
configFailed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** clear failed */
|
||||
configFailed = false;
|
||||
|
||||
/** Get response string */
|
||||
String respContent = client.getString();
|
||||
client.end();
|
||||
Serial.println("Get server config: " + respContent);
|
||||
|
||||
/** Parse JSON */
|
||||
JSONVar root = JSON.parse(respContent);
|
||||
if (JSON.typeof(root) == "undefined") {
|
||||
/** JSON invalid */
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 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 'OPEN_AIR_OUTDOOR' board */
|
||||
AirGradient ag(OPEN_AIR_OUTDOOR);
|
||||
|
||||
float pm1Value01 = 0;
|
||||
float pm1Value25 = 0;
|
||||
float pm1Value10 = 0;
|
||||
float pm1PCount = 0;
|
||||
float pm1temp = 0;
|
||||
float pm1hum = 0;
|
||||
|
||||
float pm2Value01 = 0;
|
||||
float pm2Value25 = 0;
|
||||
float pm2Value10 = 0;
|
||||
float pm2PCount = 0;
|
||||
float pm2temp = 0;
|
||||
float pm2hum = 0;
|
||||
|
||||
int countPosition = 0;
|
||||
int targetCount = 20;
|
||||
|
||||
static int ledSmState = APP_SM_NORMAL;
|
||||
static bool wifiHasConfig = false;
|
||||
static String wifiSSID = "";
|
||||
|
||||
WiFiManager wifiManager; /** wifi manager instance */
|
||||
|
||||
int loopCount = 0;
|
||||
int tvocIndex = -1;
|
||||
int noxIndex = -1;
|
||||
|
||||
void boardInit(void);
|
||||
void failedHandler(String msg);
|
||||
static String getDevId(void);
|
||||
static void sendDataToServerHandler(void);
|
||||
static void serverConfigPoll(void);
|
||||
static void tvocPoll(void);
|
||||
static void updateWiFiConnect(void);
|
||||
|
||||
AgSchedule agSyncDataSchedule(2000, sendDataToServerHandler);
|
||||
AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL, serverConfigPoll);
|
||||
AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, tvocPoll);
|
||||
|
||||
// select board LOLIN C3 mini to flash
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
/** Board init */
|
||||
boardInit();
|
||||
|
||||
/** Init AgServer */
|
||||
agServer.begin();
|
||||
|
||||
/** WiFi connect */
|
||||
connectToWifi();
|
||||
|
||||
if (WiFi.isConnected()) {
|
||||
sendPing();
|
||||
|
||||
agServer.pollServerConfig(getDevId());
|
||||
if (agServer.isConfigFailed()) {
|
||||
ledSmHandler(APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED);
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
}
|
||||
}
|
||||
|
||||
ledSmHandler(APP_SM_NORMAL);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
agSyncDataSchedule.run();
|
||||
configSchedule.run();
|
||||
tvocSchedule.run();
|
||||
updateWiFiConnect();
|
||||
}
|
||||
|
||||
void sendPing() {
|
||||
JSONVar root;
|
||||
root["wifi"] = WiFi.RSSI();
|
||||
root["boot"] = loopCount;
|
||||
if (agServer.postToServer(getDevId(), JSON.stringify(root))) {
|
||||
ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNNECTED);
|
||||
} else {
|
||||
ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNECT_FAILED);
|
||||
}
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
}
|
||||
|
||||
void sendToServer(int pm1Value01, int pm1Value25, int pm1Value10, int pm1PCount,
|
||||
float pm1temp, float pm1hum, int pm2Value01, int pm2Value25,
|
||||
int pm2Value10, int pm2PCount, float pm2temp, float pm2hum,
|
||||
int tvoc, int nox) {
|
||||
JSONVar root;
|
||||
root["wifi"] = WiFi.RSSI();
|
||||
root["boot"] = loopCount;
|
||||
if (tvocIndex > 0) {
|
||||
root["tvoc_index"] = loopCount;
|
||||
}
|
||||
if (noxIndex > 0) {
|
||||
root["nox_index"] = loopCount;
|
||||
}
|
||||
root["pm01"] = (int)((pm1Value01 + pm2Value01) / 2);
|
||||
root["pm02"] = (int)((pm1Value25 + pm2Value25) / 2);
|
||||
root["pm003_count"] = (int)((pm1PCount + pm2PCount) / 2);
|
||||
root["atmp"] = (int)((pm1temp + pm2temp) / 2);
|
||||
root["rhum"] = (int)((pm1hum + pm2hum) / 2);
|
||||
root["channels"]["1"]["pm01"] = pm1Value01;
|
||||
root["channels"]["1"]["pm02"] = pm1Value25;
|
||||
root["channels"]["1"]["pm10"] = pm1Value10;
|
||||
root["channels"]["1"]["pm003_count"] = pm1PCount;
|
||||
root["channels"]["1"]["atmp"] = pm1temp;
|
||||
root["channels"]["1"]["rhum"] = pm1hum;
|
||||
root["channels"]["2"]["pm01"] = pm2Value01;
|
||||
root["channels"]["2"]["pm02"] = pm2Value25;
|
||||
root["channels"]["2"]["pm10"] = pm2Value10;
|
||||
root["channels"]["2"]["pm003_count"] = pm2PCount;
|
||||
root["channels"]["2"]["atmp"] = pm2temp;
|
||||
root["channels"]["2"]["rhum"] = pm2hum;
|
||||
|
||||
if (agServer.postToServer(getDevId(), JSON.stringify(root))) {
|
||||
resetWatchdog();
|
||||
}
|
||||
loopCount++;
|
||||
}
|
||||
|
||||
void resetWatchdog() { ag.watchdog.reset(); }
|
||||
|
||||
bool wifiMangerClientConnected(void) {
|
||||
return WiFi.softAPgetStationNum() ? true : false;
|
||||
}
|
||||
|
||||
// Wifi Manager
|
||||
void connectToWifi() {
|
||||
wifiSSID = "airgradient-" + getDevId();
|
||||
|
||||
wifiManager.setConfigPortalBlocking(false);
|
||||
wifiManager.setTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
|
||||
|
||||
wifiManager.setAPCallback([](WiFiManager *obj) {
|
||||
/** This callback if wifi connnected failed and try to start configuration
|
||||
* portal */
|
||||
ledSmState = APP_SM_WIFI_MANAGER_MODE;
|
||||
});
|
||||
wifiManager.setSaveConfigCallback([]() {
|
||||
/** Wifi connected save the configuration */
|
||||
ledSmHandler(APP_SM_WIFI_MANAGER_STA_CONNECTED);
|
||||
});
|
||||
wifiManager.setSaveParamsCallback([]() {
|
||||
/** Wifi set connect: ssid, password */
|
||||
ledSmHandler(APP_SM_WIFI_MANAGER_STA_CONNECTING);
|
||||
});
|
||||
wifiManager.autoConnect(wifiSSID.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT);
|
||||
|
||||
xTaskCreate(
|
||||
[](void *obj) {
|
||||
while (wifiManager.getConfigPortalActive()) {
|
||||
wifiManager.process();
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
},
|
||||
"wifi_cfg", 4096, NULL, 10, NULL);
|
||||
|
||||
uint32_t stimer = millis();
|
||||
bool clientConnectChanged = false;
|
||||
while (wifiManager.getConfigPortalActive()) {
|
||||
if (WiFi.isConnected() == false) {
|
||||
if (ledSmState == APP_SM_WIFI_MANAGER_MODE) {
|
||||
uint32_t ms = (uint32_t)(millis() - stimer);
|
||||
if (ms >= 100) {
|
||||
stimer = millis();
|
||||
ledSmHandler(ledSmState);
|
||||
}
|
||||
}
|
||||
|
||||
/** Check for client connect to change led color */
|
||||
bool clientConnected = wifiMangerClientConnected();
|
||||
if (clientConnected != clientConnectChanged) {
|
||||
clientConnectChanged = clientConnected;
|
||||
if (clientConnectChanged) {
|
||||
ledSmHandler(APP_SM_WIFI_MAMAGER_PORTAL_ACTIVE);
|
||||
} else {
|
||||
ledSmHandler(APP_SM_WIFI_MANAGER_MODE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Show display wifi connect result failed */
|
||||
if (WiFi.isConnected() == false) {
|
||||
ledSmHandler(APP_SM_WIFI_MANAGER_CONNECT_FAILED);
|
||||
} else {
|
||||
wifiHasConfig = true;
|
||||
}
|
||||
}
|
||||
|
||||
String getNormalizedMac() {
|
||||
String mac = WiFi.macAddress();
|
||||
mac.replace(":", "");
|
||||
mac.toLowerCase();
|
||||
return mac;
|
||||
}
|
||||
|
||||
void boardInit(void) {
|
||||
ag.watchdog.begin();
|
||||
ag.statusLed.begin();
|
||||
ag.button.begin();
|
||||
if (ag.pms5003t_1.begin(Serial0) == false) {
|
||||
failedHandler("PMS5003T_1 init failed");
|
||||
}
|
||||
if (ag.pms5003t_2.begin(Serial1) == false) {
|
||||
failedHandler("PMS5003T_2 init failed");
|
||||
}
|
||||
|
||||
/** Init I2C */
|
||||
Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin());
|
||||
/** Init sensor SGP41 */
|
||||
if (ag.sgp41.begin(Wire) == false) {
|
||||
failedHandler("Init SGP41 failed");
|
||||
}
|
||||
}
|
||||
|
||||
void failedHandler(String msg) {
|
||||
while (true) {
|
||||
Serial.println(msg);
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
static String getDevId(void) { return getNormalizedMac(); }
|
||||
|
||||
static void sendDataToServerHandler(void) {
|
||||
if (ag.pms5003t_1.readData() && ag.pms5003t_2.readData()) {
|
||||
pm1Value01 = pm1Value01 + ag.pms5003t_1.getPm01Ae();
|
||||
pm1Value25 = pm1Value25 + ag.pms5003t_1.getPm25Ae();
|
||||
pm1Value10 = pm1Value10 + ag.pms5003t_1.getPm10Ae();
|
||||
pm1PCount = pm1PCount + ag.pms5003t_1.getPm03ParticleCount();
|
||||
pm1temp = pm1temp + ag.pms5003t_1.getTemperature();
|
||||
pm1hum = pm1hum + ag.pms5003t_1.getRelativeHumidity();
|
||||
pm2Value01 = pm2Value01 + ag.pms5003t_2.getPm01Ae();
|
||||
pm2Value25 = pm2Value25 + ag.pms5003t_2.getPm25Ae();
|
||||
pm2Value10 = pm2Value10 + ag.pms5003t_2.getPm10Ae();
|
||||
pm2PCount = pm2PCount + ag.pms5003t_2.getPm03ParticleCount();
|
||||
pm2temp = pm2temp + ag.pms5003t_2.getTemperature();
|
||||
pm2hum = pm2hum + ag.pms5003t_2.getRelativeHumidity();
|
||||
countPosition++;
|
||||
if (countPosition == targetCount) {
|
||||
pm1Value01 = pm1Value01 / targetCount;
|
||||
pm1Value25 = pm1Value25 / targetCount;
|
||||
pm1Value10 = pm1Value10 / targetCount;
|
||||
pm1PCount = pm1PCount / targetCount;
|
||||
pm1temp = pm1temp / targetCount;
|
||||
pm1hum = pm1hum / targetCount;
|
||||
pm2Value01 = pm2Value01 / targetCount;
|
||||
pm2Value25 = pm2Value25 / targetCount;
|
||||
pm2Value10 = pm2Value10 / targetCount;
|
||||
pm2PCount = pm2PCount / targetCount;
|
||||
pm2temp = pm2temp / targetCount;
|
||||
pm2hum = pm2hum / targetCount;
|
||||
sendToServer(pm1Value01, pm1Value25, pm1Value10, pm1PCount, pm1temp,
|
||||
pm1hum, pm2Value01, pm2Value25, pm2Value10, pm2PCount,
|
||||
pm2temp, pm2hum, tvocIndex, noxIndex);
|
||||
|
||||
countPosition = 0;
|
||||
pm1Value01 = 0;
|
||||
pm1Value25 = 0;
|
||||
pm1Value10 = 0;
|
||||
pm1PCount = 0;
|
||||
pm1temp = 0;
|
||||
pm1hum = 0;
|
||||
pm2Value01 = 0;
|
||||
pm2Value25 = 0;
|
||||
pm2Value10 = 0;
|
||||
pm2PCount = 0;
|
||||
pm2temp = 0;
|
||||
pm2hum = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void serverConfigPoll(void) {
|
||||
if (agServer.pollServerConfig(getDevId())) {
|
||||
Serial.println("Get server configure success");
|
||||
} else {
|
||||
Serial.println("Get server configure failure");
|
||||
}
|
||||
}
|
||||
|
||||
static void tvocPoll(void) {
|
||||
tvocIndex = ag.sgp41.getTvocIndex();
|
||||
noxIndex = ag.sgp41.getNoxIndex();
|
||||
|
||||
Serial.printf("tvocIndexindex: %d\r\n", tvocIndex);
|
||||
Serial.printf(" NOx index: %d\r\n", noxIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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");
|
||||
}
|
||||
}
|
||||
|
||||
void ledBlinkDelay(uint32_t tdelay) {
|
||||
ag.statusLed.setOn();
|
||||
delay(tdelay);
|
||||
ag.statusLed.setOff();
|
||||
delay(tdelay);
|
||||
}
|
||||
|
||||
void ledSmHandler(int sm) {
|
||||
if (sm > APP_SM_NORMAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
ledSmState = sm;
|
||||
switch (sm) {
|
||||
case APP_SM_WIFI_MANAGER_MODE: {
|
||||
ag.statusLed.setToggle();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_MAMAGER_PORTAL_ACTIVE: {
|
||||
ag.statusLed.setOn();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_MANAGER_STA_CONNECTING: {
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_MANAGER_STA_CONNECTED: {
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_OK_SERVER_CONNECTING: {
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_OK_SERVER_CONNNECTED: {
|
||||
ag.statusLed.setOff();
|
||||
|
||||
/** two time slow blink, then off */
|
||||
for (int i = 0; i < 2; i++) {
|
||||
ledBlinkDelay(LED_SLOW_BLINK_DELAY);
|
||||
}
|
||||
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_MANAGER_CONNECT_FAILED: {
|
||||
/** Three time fast blink then 2 sec pause. Repeat 3 times */
|
||||
ag.statusLed.setOff();
|
||||
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ledBlinkDelay(LED_FAST_BLINK_DELAY);
|
||||
}
|
||||
delay(2000);
|
||||
}
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_OK_SERVER_CONNECT_FAILED: {
|
||||
ag.statusLed.setOff();
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ledBlinkDelay(LED_FAST_BLINK_DELAY);
|
||||
}
|
||||
delay(2000);
|
||||
}
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED: {
|
||||
ag.statusLed.setOff();
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ledBlinkDelay(LED_FAST_BLINK_DELAY);
|
||||
}
|
||||
delay(2000);
|
||||
}
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_LOST: {
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_SERVER_LOST: {
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_SENSOR_CONFIG_FAILED: {
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_NORMAL: {
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
@ -75,63 +75,320 @@ enum {
|
||||
APP_SM_NORMAL,
|
||||
};
|
||||
|
||||
#define DEBUG true
|
||||
#define LED_FAST_BLINK_DELAY 250 /** ms */
|
||||
#define LED_SLOW_BLINK_DELAY 1000 /** ms */
|
||||
#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 \
|
||||
*/
|
||||
|
||||
#define WIFI_CONNECT_COUNTDOWN_MAX 180 /** sec */
|
||||
#define WIFI_HOTSPOT_PASSWORD_DEFAULT "cleanair"
|
||||
/**
|
||||
* @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;
|
||||
|
||||
AirGradient ag(BOARD_OUTDOOR_MONITOR_V1_3);
|
||||
/**
|
||||
* @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();
|
||||
|
||||
// time in seconds needed for NOx conditioning
|
||||
uint16_t conditioning_s = 10;
|
||||
Serial.printf("[AgSchedule] handle 0x%08x, period: %d(ms)\r\n",
|
||||
(unsigned int)handler, period);
|
||||
|
||||
String APIROOT = "http://hw.airgradient.com/";
|
||||
/** Update period time */
|
||||
count = millis();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
private:
|
||||
void (*handler)(void);
|
||||
int period;
|
||||
int count;
|
||||
};
|
||||
|
||||
// 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;
|
||||
/**
|
||||
* @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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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";
|
||||
|
||||
/** Init http client */
|
||||
HTTPClient client;
|
||||
if (client.begin(uri) == false) {
|
||||
configFailed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Get */
|
||||
int retCode = client.GET();
|
||||
if (retCode != 200) {
|
||||
client.end();
|
||||
configFailed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** clear failed */
|
||||
configFailed = false;
|
||||
|
||||
/** Get response string */
|
||||
String respContent = client.getString();
|
||||
client.end();
|
||||
Serial.println("Get server config: " + respContent);
|
||||
|
||||
/** Parse JSON */
|
||||
JSONVar root = JSON.parse(respContent);
|
||||
if (JSON.typeof(root) == "undefined") {
|
||||
/** JSON invalid */
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 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 'OPEN_AIR_OUTDOOR' board */
|
||||
AirGradient ag(OPEN_AIR_OUTDOOR);
|
||||
|
||||
static int ledSmState = APP_SM_NORMAL;
|
||||
static bool serverFailed = false;
|
||||
static bool configFailed = false;
|
||||
static bool wifiHasConfig = false;
|
||||
|
||||
int loopCount = 0;
|
||||
|
||||
WiFiManager wifiManager; /** wifi manager instance */
|
||||
static bool wifiHasConfig = false;
|
||||
static String wifiSSID = "";
|
||||
|
||||
unsigned long currentMillis = 0;
|
||||
|
||||
const int oledInterval = 5000;
|
||||
unsigned long previousOled = 0;
|
||||
|
||||
const int sendToServerInterval = 60000;
|
||||
const int pollServerConfigInterval = 30000;
|
||||
const int co2CalibCountdown = 5; /** Seconds */
|
||||
unsigned long previoussendToServer = 0;
|
||||
|
||||
const int tvocInterval = 1000;
|
||||
unsigned long previousTVOC = 0;
|
||||
int TVOC = -1;
|
||||
int NOX = -1;
|
||||
|
||||
const int co2Interval = 5000;
|
||||
unsigned long previousCo2 = 0;
|
||||
int Co2 = 0;
|
||||
|
||||
const int pmInterval = 5000;
|
||||
unsigned long previousPm = 0;
|
||||
int tvocIndex = -1;
|
||||
int noxIndex = -1;
|
||||
int co2Ppm = 0;
|
||||
int pm25 = -1;
|
||||
int pm01 = -1;
|
||||
int pm10 = -1;
|
||||
@ -139,156 +396,107 @@ int pm03PCount = -1;
|
||||
float temp;
|
||||
int hum;
|
||||
|
||||
bool co2CalibrationRequest = false;
|
||||
uint32_t serverConfigLoadTime = 0;
|
||||
String HOTSPOT = "";
|
||||
|
||||
// const int tempHumInterval = 2500;
|
||||
// unsigned long previousTempHum = 0;
|
||||
|
||||
void boardInit(void);
|
||||
void failedHandler(String msg);
|
||||
void getServerConfig(void);
|
||||
void co2Calibration(void);
|
||||
static String getDevId(void);
|
||||
static void updateWiFiConnect(void);
|
||||
static void tvocPoll(void);
|
||||
static void pmPoll(void);
|
||||
static void sendDataToServer(void);
|
||||
static void co2Poll(void);
|
||||
static void serverConfigPoll(void);
|
||||
|
||||
AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL, serverConfigPoll);
|
||||
AgSchedule serverSchedule(SERVER_SYNC_INTERVAL, sendDataToServer);
|
||||
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Poll);
|
||||
AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, pmPoll);
|
||||
AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, tvocPoll);
|
||||
|
||||
void setup() {
|
||||
if (DEBUG) {
|
||||
Serial.begin(115200);
|
||||
}
|
||||
Serial.begin(115200);
|
||||
|
||||
/** Board init */
|
||||
boardInit();
|
||||
|
||||
delay(500);
|
||||
/** Server init */
|
||||
agServer.begin();
|
||||
|
||||
countdown(3);
|
||||
/** WiFi connect */
|
||||
connectToWifi();
|
||||
|
||||
if (connectWIFI) {
|
||||
connectToWifi();
|
||||
}
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
if (WiFi.isConnected()) {
|
||||
wifiHasConfig = true;
|
||||
sendPing();
|
||||
Serial.println(F("WiFi connected!"));
|
||||
Serial.println("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
}
|
||||
|
||||
getServerConfig();
|
||||
if (configFailed) {
|
||||
ledSmHandler(APP_SM_SENSOR_CONFIG_FAILED);
|
||||
delay(5000);
|
||||
agServer.pollServerConfig(getDevId());
|
||||
if (agServer.isConfigFailed()) {
|
||||
ledSmHandler(APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED);
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
}
|
||||
}
|
||||
|
||||
ledSmHandler(APP_SM_NORMAL);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
currentMillis = millis();
|
||||
updateTVOC();
|
||||
updateCo2();
|
||||
updatePm();
|
||||
sendToServer();
|
||||
getServerConfig();
|
||||
}
|
||||
|
||||
void updateTVOC() {
|
||||
delay(1000);
|
||||
|
||||
if (currentMillis - previousTVOC >= tvocInterval) {
|
||||
previousTVOC += tvocInterval;
|
||||
TVOC = ag.sgp41.getTvocIndex();
|
||||
NOX = ag.sgp41.getNoxIndex();
|
||||
}
|
||||
}
|
||||
|
||||
void updateCo2() {
|
||||
if (currentMillis - previousCo2 >= co2Interval) {
|
||||
previousCo2 += co2Interval;
|
||||
Co2 = ag.s8.getCo2();
|
||||
Serial.printf("CO2: %d\r\n", Co2);
|
||||
}
|
||||
}
|
||||
|
||||
void updatePm() {
|
||||
if (currentMillis - previousPm >= pmInterval) {
|
||||
previousPm += pmInterval;
|
||||
if (ag.pms5003t_1.readData()) {
|
||||
pm01 = ag.pms5003t_1.getPm01Ae();
|
||||
pm25 = ag.pms5003t_1.getPm25Ae();
|
||||
pm10 = ag.pms5003t_1.getPm10Ae();
|
||||
pm03PCount = ag.pms5003t_1.getPm03ParticleCount();
|
||||
temp = ag.pms5003t_1.getTemperature();
|
||||
hum = ag.pms5003t_1.getRelativeHumidity();
|
||||
}
|
||||
}
|
||||
configSchedule.run();
|
||||
serverSchedule.run();
|
||||
co2Schedule.run();
|
||||
pmsSchedule.run();
|
||||
tvocSchedule.run();
|
||||
updateWiFiConnect();
|
||||
}
|
||||
|
||||
void sendPing() {
|
||||
String payload =
|
||||
"{\"wifi\":" + String(WiFi.RSSI()) + ", \"boot\":" + loopCount + "}";
|
||||
if (postToServer(payload)) {
|
||||
JSONVar root;
|
||||
root["wifi"] = WiFi.RSSI();
|
||||
root["boot"] = loopCount;
|
||||
if (agServer.postToServer(getDevId(), JSON.stringify(root))) {
|
||||
ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNNECTED);
|
||||
} else {
|
||||
ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNECT_FAILED);
|
||||
}
|
||||
delay(5000);
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
}
|
||||
|
||||
bool postToServer(String &payload) {
|
||||
String POSTURL = APIROOT +
|
||||
"sensors/airgradient:" + String(getNormalizedMac()) +
|
||||
"/measures";
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
|
||||
ag.statusLed.setOn();
|
||||
|
||||
http.begin(client, POSTURL);
|
||||
http.addHeader("content-type", "application/json");
|
||||
int httpCode = http.POST(payload);
|
||||
Serial.printf("Post to %s, %d\r\n", POSTURL.c_str(), httpCode);
|
||||
http.end();
|
||||
|
||||
ag.statusLed.setOff();
|
||||
|
||||
return (httpCode == 200);
|
||||
}
|
||||
|
||||
void sendToServer() {
|
||||
if (currentMillis - previoussendToServer >= sendToServerInterval) {
|
||||
previoussendToServer += sendToServerInterval;
|
||||
String payload =
|
||||
"{\"wifi\":" + String(WiFi.RSSI()) +
|
||||
(Co2 < 0 ? "" : ", \"rco2\":" + String(Co2)) +
|
||||
(pm01 < 0 ? "" : ", \"pm01\":" + String(pm01)) +
|
||||
(pm25 < 0 ? "" : ", \"pm02\":" + String(pm25)) +
|
||||
(pm10 < 0 ? "" : ", \"pm10\":" + String(pm10)) +
|
||||
(pm03PCount < 0 ? "" : ", \"pm003_count\":" + String(pm03PCount)) +
|
||||
(TVOC < 0 ? "" : ", \"tvoc_index\":" + String(TVOC)) +
|
||||
(NOX < 0 ? "" : ", \"nox_index\":" + String(NOX)) +
|
||||
", \"atmp\":" + String(temp) +
|
||||
(hum < 0 ? "" : ", \"rhum\":" + String(hum)) +
|
||||
", \"boot\":" + loopCount + "}";
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
postToServer(payload);
|
||||
resetWatchdog();
|
||||
loopCount++;
|
||||
} else {
|
||||
Serial.println("WiFi Disconnected");
|
||||
}
|
||||
static void sendDataToServer(void) {
|
||||
JSONVar root;
|
||||
root["wifi"] = WiFi.RSSI();
|
||||
if (co2Ppm >= 0) {
|
||||
root["rco2"] = co2Ppm;
|
||||
}
|
||||
}
|
||||
|
||||
void countdown(int from) {
|
||||
debug("\n");
|
||||
while (from > 0) {
|
||||
debug(String(from--));
|
||||
debug(" ");
|
||||
delay(1000);
|
||||
if (pm01 >= 0) {
|
||||
root["pm01"] = pm01;
|
||||
}
|
||||
debug("\n");
|
||||
if (pm25 >= 0) {
|
||||
root["pm02"] = pm25;
|
||||
}
|
||||
if (pm10 >= 0) {
|
||||
root["pm10"] = pm10;
|
||||
}
|
||||
if (pm03PCount >= 0) {
|
||||
root["pm003_count"] = pm03PCount;
|
||||
}
|
||||
if (tvocIndex >= 0) {
|
||||
root["tvoc_index"] = tvocIndex;
|
||||
}
|
||||
if (noxIndex >= 0) {
|
||||
root["noxIndex"] = noxIndex;
|
||||
}
|
||||
if (temp >= 0) {
|
||||
root["atmp"] = temp;
|
||||
}
|
||||
if (hum >= 0) {
|
||||
root["rhum"] = hum;
|
||||
}
|
||||
root["boot"] = loopCount;
|
||||
|
||||
// NOTE Need determine offline mode to reset watchdog timer
|
||||
if (agServer.postToServer(getDevId(), JSON.stringify(root))) {
|
||||
resetWatchdog();
|
||||
}
|
||||
loopCount++;
|
||||
}
|
||||
|
||||
void resetWatchdog() {
|
||||
@ -302,7 +510,7 @@ bool wifiMangerClientConnected(void) {
|
||||
|
||||
// Wifi Manager
|
||||
void connectToWifi() {
|
||||
HOTSPOT = "airgradient-" + String(getNormalizedMac());
|
||||
wifiSSID = "airgradient-" + String(getNormalizedMac());
|
||||
|
||||
wifiManager.setConfigPortalBlocking(false);
|
||||
wifiManager.setTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
|
||||
@ -320,7 +528,7 @@ void connectToWifi() {
|
||||
/** Wifi set connect: ssid, password */
|
||||
ledSmHandler(APP_SM_WIFI_MANAGER_STA_CONNECTING);
|
||||
});
|
||||
wifiManager.autoConnect(HOTSPOT.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT);
|
||||
wifiManager.autoConnect(wifiSSID.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT);
|
||||
|
||||
xTaskCreate(
|
||||
[](void *obj) {
|
||||
@ -357,33 +565,13 @@ void connectToWifi() {
|
||||
}
|
||||
|
||||
/** Show display wifi connect result failed */
|
||||
ag.statusLed.setOff();
|
||||
delay(2000);
|
||||
if (WiFi.isConnected() == false) {
|
||||
ledSmHandler(APP_SM_WIFI_MANAGER_CONNECT_FAILED);
|
||||
} else {
|
||||
wifiHasConfig = true;
|
||||
}
|
||||
}
|
||||
|
||||
void debug(String msg) {
|
||||
if (DEBUG)
|
||||
Serial.print(msg);
|
||||
}
|
||||
|
||||
void debug(int msg) {
|
||||
if (DEBUG)
|
||||
Serial.print(msg);
|
||||
}
|
||||
|
||||
void debugln(String msg) {
|
||||
if (DEBUG)
|
||||
Serial.println(msg);
|
||||
}
|
||||
|
||||
void debugln(int msg) {
|
||||
if (DEBUG)
|
||||
Serial.println(msg);
|
||||
}
|
||||
|
||||
String getNormalizedMac() {
|
||||
String mac = WiFi.macAddress();
|
||||
mac.replace(":", "");
|
||||
@ -422,155 +610,11 @@ void failedHandler(String msg) {
|
||||
}
|
||||
}
|
||||
|
||||
void updateServerConfigLoadTime(void) {
|
||||
serverConfigLoadTime = millis();
|
||||
if (serverConfigLoadTime == 0) {
|
||||
serverConfigLoadTime = 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 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(getNormalizedMac()) + "/one/config";
|
||||
Serial.println("HttpClient get: " + getUrl);
|
||||
if (httpClient.begin(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();
|
||||
httpClient.end();
|
||||
configFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
String respContent = httpClient.getString();
|
||||
Serial.println("Server config: " + respContent);
|
||||
httpClient.end();
|
||||
|
||||
/** Parse JSON */
|
||||
JSONVar root = JSON.parse(respContent);
|
||||
if (JSON.typeof_(root) == "undefined") {
|
||||
Serial.println("Server configura JSON invalid");
|
||||
updateServerConfigLoadTime();
|
||||
configFailed = true;
|
||||
return;
|
||||
}
|
||||
configFailed = false;
|
||||
|
||||
/** 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) {
|
||||
/** Count down for co2CalibCountdown secs */
|
||||
for (int i = 0; i < co2CalibCountdown; i++) {
|
||||
Serial.printf("Start CO2 calib after %d sec\r\n", co2CalibCountdown - i);
|
||||
for (int i = 0; i < SENSOR_CO2_CALIB_COUNTDOWN_MAX; i++) {
|
||||
Serial.printf("Start CO2 calib after %d sec\r\n",
|
||||
SENSOR_CO2_CALIB_COUNTDOWN_MAX - i);
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
@ -591,6 +635,76 @@ void co2Calibration(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update tvocIndexindex
|
||||
*
|
||||
*/
|
||||
static void tvocPoll(void) {
|
||||
tvocIndex = ag.sgp41.getTvocIndex();
|
||||
noxIndex = ag.sgp41.getNoxIndex();
|
||||
|
||||
Serial.printf("tvocIndexindex: %d\r\n", tvocIndex);
|
||||
Serial.printf(" NOx index: %d\r\n", noxIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update PMS data
|
||||
*
|
||||
*/
|
||||
static void pmPoll(void) {
|
||||
if (ag.pms5003t_1.readData()) {
|
||||
pm01 = ag.pms5003t_1.getPm01Ae();
|
||||
pm25 = ag.pms5003t_1.getPm25Ae();
|
||||
pm25 = ag.pms5003t_1.getPm10Ae();
|
||||
pm03PCount = ag.pms5003t_1.getPm03ParticleCount();
|
||||
temp = ag.pms5003t_1.getTemperature();
|
||||
hum = ag.pms5003t_1.getRelativeHumidity();
|
||||
}
|
||||
}
|
||||
|
||||
static void co2Poll(void) {
|
||||
co2Ppm = ag.s8.getCo2();
|
||||
Serial.printf("CO2 index: %d\r\n", co2Ppm);
|
||||
}
|
||||
|
||||
static void serverConfigPoll(void) {
|
||||
if (agServer.pollServerConfig(getDevId())) {
|
||||
if (agServer.isCo2Calib()) {
|
||||
co2Calibration();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static String getDevId(void) { return getNormalizedMac(); }
|
||||
|
||||
void ledBlinkDelay(uint32_t tdelay) {
|
||||
ag.statusLed.setOn();
|
||||
delay(tdelay);
|
||||
ag.statusLed.setOff();
|
||||
delay(tdelay);
|
||||
}
|
||||
|
||||
void ledSmHandler(int sm) {
|
||||
if (sm > APP_SM_NORMAL) {
|
||||
return;
|
||||
@ -620,21 +734,22 @@ void ledSmHandler(int sm) {
|
||||
}
|
||||
case APP_SM_WIFI_OK_SERVER_CONNNECTED: {
|
||||
ag.statusLed.setOff();
|
||||
ag.statusLed.setOn();
|
||||
delay(50);
|
||||
ag.statusLed.setOff();
|
||||
delay(950);
|
||||
ag.statusLed.setOn();
|
||||
delay(50);
|
||||
|
||||
/** two time slow blink, then off */
|
||||
for (int i = 0; i < 2; i++) {
|
||||
ledBlinkDelay(LED_SLOW_BLINK_DELAY);
|
||||
}
|
||||
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_MANAGER_CONNECT_FAILED: {
|
||||
/** Three time fast blink then 2 sec pause. Repeat 3 times */
|
||||
ag.statusLed.setOff();
|
||||
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < 3 * 2; i++) {
|
||||
ag.statusLed.setToggle();
|
||||
delay(100);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ledBlinkDelay(LED_FAST_BLINK_DELAY);
|
||||
}
|
||||
delay(2000);
|
||||
}
|
||||
@ -644,9 +759,8 @@ void ledSmHandler(int sm) {
|
||||
case APP_SM_WIFI_OK_SERVER_CONNECT_FAILED: {
|
||||
ag.statusLed.setOff();
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < 4 * 2; i++) {
|
||||
ag.statusLed.setToggle();
|
||||
delay(100);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ledBlinkDelay(LED_FAST_BLINK_DELAY);
|
||||
}
|
||||
delay(2000);
|
||||
}
|
||||
@ -656,9 +770,8 @@ void ledSmHandler(int sm) {
|
||||
case APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED: {
|
||||
ag.statusLed.setOff();
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < 5 * 2; i++) {
|
||||
ag.statusLed.setToggle();
|
||||
delay(100);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ledBlinkDelay(LED_FAST_BLINK_DELAY);
|
||||
}
|
||||
delay(2000);
|
||||
}
|
||||
|
@ -2,41 +2,47 @@
|
||||
This is sample code for the AirGradient library with a minimal implementation to read CO2 values from the SenseAir S8 sensor.
|
||||
|
||||
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
|
||||
*/
|
||||
|
||||
#include <AirGradient.h>
|
||||
|
||||
#ifdef ESP8266
|
||||
AirGradient ag = AirGradient(BOARD_DIY_PRO_INDOOR_V4_2);
|
||||
// AirGradient ag = AirGradient(BOARD_DIY_BASIC_KIT);
|
||||
AirGradient ag = AirGradient(DIY_BASIC);
|
||||
#else
|
||||
// AirGradient ag = AirGradient(BOARD_ONE_INDOOR_MONITOR_V9_0);
|
||||
AirGradient ag = AirGradient(BOARD_OUTDOOR_MONITOR_V1_3);
|
||||
/** Create airgradient instance for 'OPEN_AIR_OUTDOOR' board */
|
||||
AirGradient ag = AirGradient(OPEN_AIR_OUTDOOR);
|
||||
#endif
|
||||
|
||||
void failedHandler(String msg);
|
||||
|
||||
void setup() {
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
|
||||
/** Init CO2 sensor */
|
||||
#ifdef ESP8266
|
||||
if (ag.s8.begin(&Serial) == false) {
|
||||
if (ag.s8.begin(&Serial) == false)
|
||||
{
|
||||
#else
|
||||
if (ag.s8.begin(Serial1) == false) {
|
||||
if (ag.s8.begin(Serial1) == false)
|
||||
{
|
||||
#endif
|
||||
failedHandler("SenseAir S8 init failed");
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
int CO2 = ag.s8.getCo2();
|
||||
Serial.printf("CO2: %d\r\n", CO2);
|
||||
void loop()
|
||||
{
|
||||
int co2Ppm = ag.s8.getCo2();
|
||||
Serial.printf("CO2: %d\r\n", co2Ppm);
|
||||
delay(5000);
|
||||
}
|
||||
|
||||
void failedHandler(String msg) {
|
||||
while (true) {
|
||||
void failedHandler(String msg)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Serial.println(msg);
|
||||
delay(1000);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
This is sample code for the AirGradient library with a minimal implementation to read PM values from the Plantower sensor.
|
||||
This is sample code for the AirGradient library with a minimal implementation
|
||||
to read PM values from the Plantower sensor.
|
||||
|
||||
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
*/
|
||||
@ -7,52 +8,65 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
#include <AirGradient.h>
|
||||
|
||||
#ifdef ESP8266
|
||||
AirGradient ag = AirGradient(BOARD_DIY_PRO_INDOOR_V4_2);
|
||||
// AirGradient ag = AirGradient(BOARD_DIY_BASIC_KIT);
|
||||
AirGradient ag = AirGradient(DIY_BASIC);
|
||||
#else
|
||||
// AirGradient ag = AirGradient(BOARD_ONE_INDOOR_MONITOR_V9_0);
|
||||
AirGradient ag = AirGradient(BOARD_OUTDOOR_MONITOR_V1_3);
|
||||
// AirGradient ag = AirGradient(ONE_INDOOR);
|
||||
AirGradient ag = AirGradient(OPEN_AIR_OUTDOOR);
|
||||
#endif
|
||||
|
||||
void failedHandler(String msg);
|
||||
|
||||
void setup() {
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
#ifdef ESP8266
|
||||
if(ag.pms5003.begin(&Serial) == false) {
|
||||
if (ag.pms5003.begin(&Serial) == false)
|
||||
{
|
||||
failedHandler("Init PMS5003 failed");
|
||||
}
|
||||
#else
|
||||
if (ag.getBoardType() == BOARD_OUTDOOR_MONITOR_V1_3) {
|
||||
if(ag.pms5003t_1.begin(Serial0) == false) {
|
||||
if (ag.getBoardType() == OPEN_AIR_OUTDOOR)
|
||||
{
|
||||
if (ag.pms5003t_1.begin(Serial0) == false)
|
||||
{
|
||||
failedHandler("Init PMS5003T failed");
|
||||
}
|
||||
} else {
|
||||
if(ag.pms5003.begin(Serial0) == false) {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ag.pms5003.begin(Serial0) == false)
|
||||
{
|
||||
failedHandler("Init PMS5003T failed");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void loop() {
|
||||
void loop()
|
||||
{
|
||||
int PM2;
|
||||
#ifdef ESP8266
|
||||
PM2 = ag.pms5003.getPm25Ae();
|
||||
Serial.printf("PM2.5 in ug/m3: %d\r\n", PM2);
|
||||
Serial.printf("PM2.5 in US AQI: %d\r\n", ag.pms5003.convertPm25ToUsAqi(PM2));
|
||||
#else
|
||||
if (ag.getBoardType() == BOARD_OUTDOOR_MONITOR_V1_3) {
|
||||
if (ag.getBoardType() == OPEN_AIR_OUTDOOR)
|
||||
{
|
||||
PM2 = ag.pms5003t_1.getPm25Ae();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
PM2 = ag.pms5003.getPm25Ae();
|
||||
}
|
||||
|
||||
|
||||
Serial.printf("PM2.5 in ug/m3: %d\r\n", PM2);
|
||||
if (ag.getBoardType() == BOARD_OUTDOOR_MONITOR_V1_3) {
|
||||
if (ag.getBoardType() == OPEN_AIR_OUTDOOR)
|
||||
{
|
||||
Serial.printf("PM2.5 in US AQI: %d\r\n",
|
||||
ag.pms5003t_1.convertPm25ToUsAqi(PM2));
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.printf("PM2.5 in US AQI: %d\r\n",
|
||||
ag.pms5003.convertPm25ToUsAqi(PM2));
|
||||
}
|
||||
@ -61,8 +75,10 @@ void loop() {
|
||||
delay(5000);
|
||||
}
|
||||
|
||||
void failedHandler(String msg) {
|
||||
while (true) {
|
||||
void failedHandler(String msg)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Serial.println(msg);
|
||||
delay(1000);
|
||||
}
|
||||
|
Reference in New Issue
Block a user