Clean code and add comments

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

View File

@@ -32,67 +32,337 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
#include <Arduino_JSON.h> #include <Arduino_JSON.h>
#include <ESP8266HTTPClient.h> #include <ESP8266HTTPClient.h>
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <U8g2lib.h>
#include <WiFiClient.h> #include <WiFiClient.h>
#include <WiFiManager.h> #include <WiFiManager.h>
typedef struct { #define WIFI_CONNECT_COUNTDOWN_MAX 180 /** sec */
bool inF; /** Temperature unit */ #define WIFI_CONNECT_RETRY_MS 10000 /** ms */
bool inUSAQI; /** PMS standard */ #define LED_BAR_COUNT_INIT_VALUE (-1) /** */
uint8_t ledBarMode; /** @ref UseLedBar*/ #define LED_BAR_ANIMATION_PERIOD 100 /** ms */
char model[16]; /** Model string value, Just define, don't know how much #define DISP_UPDATE_INTERVAL 5000 /** ms */
memory usage */ #define SERVER_CONFIG_UPDATE_INTERVAL 30000 /** ms */
char mqttBroker[128]; /** Mqtt broker link */ #define SERVER_SYNC_INTERVAL 60000 /** ms */
uint32_t _check; /** Checksum configuration data */ #define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */
} ServerConfig_t; #define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
static ServerConfig_t serverConfig; #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 Serial.printf("[AgSchedule] handle 0x%08x, period: %d(ms)\r\n",
String APIROOT = "http://hw.airgradient.com/"; (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. private:
// Then it will go into an offline mode. void (*handler)(void);
boolean connectWIFI = true; 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; /** Init http client */
unsigned long previousOled = 0; WiFiClient wifiClient;
bool co2CalibrationRequest = false; HTTPClient client;
uint32_t serverConfigLoadTime = 0; if (client.begin(wifiClient, uri) == false) {
String HOSTPOT = ""; configFailed = true;
return false;
}
const int sendToServerInterval = 60000; /** Get */
const int pollServerConfigInterval = 30000; int retCode = client.GET();
const int co2CalibCountdown = 5; /** Seconds */ if (retCode != 200) {
unsigned long previoussendToServer = 0; client.end();
configFailed = true;
return false;
}
const int co2Interval = 5000; /** clear failed */
unsigned long previousCo2 = 0; configFailed = false;
int Co2 = 0;
const int pm25Interval = 5000; /** Get response string */
unsigned long previousPm25 = 0; String respContent = client.getString();
int pm25 = 0; client.end();
Serial.println("Get server config: " + respContent);
const int tempHumInterval = 2500; /** Parse JSON */
unsigned long previousTempHum = 0; JSONVar root = JSON.parse(respContent);
float temp = 0; if (JSON.typeof(root) == "undefined") {
int hum = 0; /** JSON invalid */
long val; return false;
}
void failedHandler(String msg); /** Get "country" */
void boardInit(void); if (JSON.typeof_(root["country"]) == "string") {
void getServerConfig(void); String country = root["country"];
void co2Calibration(void); 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() { void setup() {
Serial.begin(115200); Serial.begin(115200);
@@ -103,85 +373,53 @@ void setup() {
/** Board init */ /** Board init */
boardInit(); boardInit();
/** Init AirGradient server */
agServer.begin();
/** Show boot display */ /** Show boot display */
displayShowText("Basic v4", "Lib:" + ag.getVersion(), ""); displayShowText("DIY basic", "Lib:" + ag.getVersion(), "");
delay(2000); delay(2000);
if (connectWIFI) { /** WiFi connect */
connectToWifi(); connectToWifi();
if (WiFi.status() == WL_CONNECTED) {
wifiHasConfig = true;
sendPing();
agServer.pollServerConfig(getDevId());
if (agServer.isCo2Calib()) {
co2Calibration();
}
} }
/** Show display */ /** Show display */
displayShowText("Warm Up", "Serial#", String(ESP.getChipId(), HEX)); displayShowText("Warm Up", "Serial#", String(ESP.getChipId(), HEX));
delay(10000); delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
getServerConfig();
} }
void loop() { void loop() {
currentMillis = millis(); configSchedule.run();
updateOLED(); serverSchedule.run();
updateCo2(); dispSchedule.run();
updatePm25(); co2Schedule.run();
updateTempHum(); pmsSchedule.run();
sendToServer(); tempHumSchedule.run();
getServerConfig();
updateWiFiConnect();
} }
void updateCo2() { static void sendPing() {
if (currentMillis - previousCo2 >= co2Interval) { JSONVar root;
previousCo2 += co2Interval; root["wifi"] = WiFi.RSSI();
Co2 = ag.s8.getCo2(); root["boot"] = 0;
Serial.println(String(Co2));
}
}
void updatePm25() { // delay(1500);
if (currentMillis - previousPm25 >= pm25Interval) { if (agServer.postToServer(getDevId(), JSON.stringify(root))) {
previousPm25 += pm25Interval; // Ping Server succses
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 { } else {
ln1 = "PM :" + String(pm25) + " ug"; // Ping server failed
}
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);
} }
// delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
} }
void displayShowText(String ln1, String ln2, String ln3) { void displayShowText(String ln1, String ln2, String ln3) {
@@ -198,58 +436,24 @@ void displayShowText(String ln1, String ln2, String ln3) {
ag.display.show(); 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 // Wifi Manager
void connectToWifi() { void connectToWifi() {
WiFiManager wifiManager; WiFiManager wifiManager;
// WiFi.disconnect(); //to delete previous saved hotspot wifiSSID = "AG-" + String(ESP.getChipId(), HEX);
String HOTSPOT = "AG-" + String(ESP.getChipId(), HEX);
// displayShowText("Connect", "AG-", String(ESP.getChipId(), HEX));
delay(2000);
// wifiManager.setTimeout(90);
wifiManager.setConfigPortalBlocking(false); wifiManager.setConfigPortalBlocking(false);
wifiManager.setConfigPortalTimeout(180); wifiManager.setConfigPortalTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
wifiManager.autoConnect(HOTSPOT.c_str(), wifiApPass.c_str()); wifiManager.autoConnect(wifiSSID.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT);
uint32_t lastTime = millis(); uint32_t lastTime = millis();
int count = 179; int count = WIFI_CONNECT_COUNTDOWN_MAX;
displayShowText("180 sec", "SSID:",HOTSPOT); displayShowText(String(WIFI_CONNECT_COUNTDOWN_MAX) + " sec",
"SSID:", wifiSSID);
while (wifiManager.getConfigPortalActive()) { while (wifiManager.getConfigPortalActive()) {
wifiManager.process(); wifiManager.process();
uint32_t ms = (uint32_t)(millis() - lastTime); uint32_t ms = (uint32_t)(millis() - lastTime);
if (ms >= 1000) { if (ms >= 1000) {
lastTime = millis(); lastTime = millis();
displayShowText(String(count) + " sec", "SSID:",HOTSPOT); displayShowText(String(count) + " sec", "SSID:", wifiSSID);
count--; count--;
// Timeout // Timeout
@@ -261,18 +465,11 @@ void connectToWifi() {
if (!WiFi.isConnected()) { if (!WiFi.isConnected()) {
displayShowText("Booting", "offline", "mode"); displayShowText("Booting", "offline", "mode");
Serial.println("failed to connect and hit timeout"); Serial.println("failed to connect and hit timeout");
delay(6000); delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
} }
} }
void failedHandler(String msg) { static void boardInit(void) {
while (true) {
Serial.println(msg);
delay(1000);
}
}
void boardInit(void) {
/** Init SHT sensor */ /** Init SHT sensor */
if (ag.sht.begin(Wire) == false) { if (ag.sht.begin(Wire) == false) {
failedHandler("SHT init failed"); failedHandler("SHT init failed");
@@ -293,152 +490,18 @@ void boardInit(void) {
ag.display.setTextColor(1); ag.display.setTextColor(1);
} }
void showConfig(void) { static void failedHandler(String msg) {
Serial.println("Server configuration: "); while (true) {
Serial.printf(" inF: %s\r\n", serverConfig.inF ? "true" : "false"); Serial.println(msg);
Serial.printf(" inUSAQI: %s\r\n", delay(1000);
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;
} }
} }
void getServerConfig(void) { static void co2Calibration(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) {
/** Count down for co2CalibCountdown secs */ /** 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", displayShowText("CO2 calib", "after",
String(co2CalibCountdown - i) + " sec"); String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec");
delay(1000); delay(1000);
} }
@@ -452,9 +515,110 @@ void co2Calibration(void) {
count++; count++;
} }
displayShowText("Finish", "after", String(count) + " sec"); displayShowText("Finish", "after", String(count) + " sec");
delay(2000); delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
} else { } else {
displayShowText("Calib", "failure!!!", ""); 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

View 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;
}

View 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;
}
}

View File

@@ -75,63 +75,320 @@ enum {
APP_SM_NORMAL, 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_COUNTDOWN_MAX 180 /** sec */
#define WIFI_HOTSPOT_PASSWORD_DEFAULT "cleanair" #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(BOARD_OUTDOOR_MONITOR_V1_3); /**
* @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;
// time in seconds needed for NOx conditioning /**
uint16_t conditioning_s = 10; * @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();
String APIROOT = "http://hw.airgradient.com/"; Serial.printf("[AgSchedule] handle 0x%08x, period: %d(ms)\r\n",
(unsigned int)handler, period);
typedef struct { /** Update period time */
bool inF; /** Temperature unit */ count = millis();
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;
// set to true if you want to connect to wifi. You have 60 seconds to connect. private:
// Then it will go into an offline mode. void (*handler)(void);
boolean connectWIFI = true; 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 int ledSmState = APP_SM_NORMAL; static int ledSmState = APP_SM_NORMAL;
static bool serverFailed = false;
static bool configFailed = false;
static bool wifiHasConfig = false;
int loopCount = 0; int loopCount = 0;
WiFiManager wifiManager; /** wifi manager instance */ WiFiManager wifiManager; /** wifi manager instance */
static bool wifiHasConfig = false;
static String wifiSSID = "";
unsigned long currentMillis = 0; int tvocIndex = -1;
int noxIndex = -1;
const int oledInterval = 5000; int co2Ppm = 0;
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 pm25 = -1; int pm25 = -1;
int pm01 = -1; int pm01 = -1;
int pm10 = -1; int pm10 = -1;
@@ -139,156 +396,107 @@ int pm03PCount = -1;
float temp; float temp;
int hum; int hum;
bool co2CalibrationRequest = false;
uint32_t serverConfigLoadTime = 0;
String HOTSPOT = "";
// const int tempHumInterval = 2500;
// unsigned long previousTempHum = 0;
void boardInit(void); void boardInit(void);
void failedHandler(String msg); void failedHandler(String msg);
void getServerConfig(void);
void co2Calibration(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() { void setup() {
if (DEBUG) {
Serial.begin(115200); Serial.begin(115200);
}
/** Board init */ /** Board init */
boardInit(); boardInit();
delay(500); /** Server init */
agServer.begin();
countdown(3); /** WiFi connect */
if (connectWIFI) {
connectToWifi(); connectToWifi();
}
if (WiFi.status() == WL_CONNECTED) { if (WiFi.isConnected()) {
wifiHasConfig = true;
sendPing(); sendPing();
Serial.println(F("WiFi connected!"));
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
getServerConfig(); agServer.pollServerConfig(getDevId());
if (configFailed) { if (agServer.isConfigFailed()) {
ledSmHandler(APP_SM_SENSOR_CONFIG_FAILED); ledSmHandler(APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED);
delay(5000); delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
}
} }
ledSmHandler(APP_SM_NORMAL); ledSmHandler(APP_SM_NORMAL);
} }
void loop() { void loop() {
currentMillis = millis(); configSchedule.run();
updateTVOC(); serverSchedule.run();
updateCo2(); co2Schedule.run();
updatePm(); pmsSchedule.run();
sendToServer(); tvocSchedule.run();
getServerConfig(); updateWiFiConnect();
}
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();
}
}
} }
void sendPing() { void sendPing() {
String payload = JSONVar root;
"{\"wifi\":" + String(WiFi.RSSI()) + ", \"boot\":" + loopCount + "}"; root["wifi"] = WiFi.RSSI();
if (postToServer(payload)) { root["boot"] = loopCount;
if (agServer.postToServer(getDevId(), JSON.stringify(root))) {
ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNNECTED); ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNNECTED);
} else { } else {
ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNECT_FAILED); ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNECT_FAILED);
} }
delay(5000); delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
} }
bool postToServer(String &payload) { static void sendDataToServer(void) {
String POSTURL = APIROOT + JSONVar root;
"sensors/airgradient:" + String(getNormalizedMac()) + root["wifi"] = WiFi.RSSI();
"/measures"; if (co2Ppm >= 0) {
WiFiClient client; root["rco2"] = co2Ppm;
HTTPClient http; }
if (pm01 >= 0) {
root["pm01"] = pm01;
}
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;
ag.statusLed.setOn(); // NOTE Need determine offline mode to reset watchdog timer
if (agServer.postToServer(getDevId(), JSON.stringify(root))) {
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(); resetWatchdog();
}
loopCount++; loopCount++;
} else {
Serial.println("WiFi Disconnected");
}
}
}
void countdown(int from) {
debug("\n");
while (from > 0) {
debug(String(from--));
debug(" ");
delay(1000);
}
debug("\n");
} }
void resetWatchdog() { void resetWatchdog() {
@@ -302,7 +510,7 @@ bool wifiMangerClientConnected(void) {
// Wifi Manager // Wifi Manager
void connectToWifi() { void connectToWifi() {
HOTSPOT = "airgradient-" + String(getNormalizedMac()); wifiSSID = "airgradient-" + String(getNormalizedMac());
wifiManager.setConfigPortalBlocking(false); wifiManager.setConfigPortalBlocking(false);
wifiManager.setTimeout(WIFI_CONNECT_COUNTDOWN_MAX); wifiManager.setTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
@@ -320,7 +528,7 @@ void connectToWifi() {
/** Wifi set connect: ssid, password */ /** Wifi set connect: ssid, password */
ledSmHandler(APP_SM_WIFI_MANAGER_STA_CONNECTING); 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( xTaskCreate(
[](void *obj) { [](void *obj) {
@@ -357,33 +565,13 @@ void connectToWifi() {
} }
/** Show display wifi connect result failed */ /** Show display wifi connect result failed */
ag.statusLed.setOff();
delay(2000);
if (WiFi.isConnected() == false) { if (WiFi.isConnected() == false) {
ledSmHandler(APP_SM_WIFI_MANAGER_CONNECT_FAILED); 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 getNormalizedMac() {
String mac = WiFi.macAddress(); String mac = WiFi.macAddress();
mac.replace(":", ""); 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) { void co2Calibration(void) {
/** Count down for co2CalibCountdown secs */ /** Count down for co2CalibCountdown secs */
for (int i = 0; i < co2CalibCountdown; i++) { for (int i = 0; i < SENSOR_CO2_CALIB_COUNTDOWN_MAX; i++) {
Serial.printf("Start CO2 calib after %d sec\r\n", co2CalibCountdown - i); Serial.printf("Start CO2 calib after %d sec\r\n",
SENSOR_CO2_CALIB_COUNTDOWN_MAX - i);
delay(1000); 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) { void ledSmHandler(int sm) {
if (sm > APP_SM_NORMAL) { if (sm > APP_SM_NORMAL) {
return; return;
@@ -620,21 +734,22 @@ void ledSmHandler(int sm) {
} }
case APP_SM_WIFI_OK_SERVER_CONNNECTED: { case APP_SM_WIFI_OK_SERVER_CONNNECTED: {
ag.statusLed.setOff(); ag.statusLed.setOff();
ag.statusLed.setOn();
delay(50); /** two time slow blink, then off */
ag.statusLed.setOff(); for (int i = 0; i < 2; i++) {
delay(950); ledBlinkDelay(LED_SLOW_BLINK_DELAY);
ag.statusLed.setOn(); }
delay(50);
ag.statusLed.setOff(); ag.statusLed.setOff();
break; break;
} }
case APP_SM_WIFI_MANAGER_CONNECT_FAILED: { case APP_SM_WIFI_MANAGER_CONNECT_FAILED: {
/** Three time fast blink then 2 sec pause. Repeat 3 times */
ag.statusLed.setOff(); ag.statusLed.setOff();
for (int j = 0; j < 3; j++) { for (int j = 0; j < 3; j++) {
for (int i = 0; i < 3 * 2; i++) { for (int i = 0; i < 3; i++) {
ag.statusLed.setToggle(); ledBlinkDelay(LED_FAST_BLINK_DELAY);
delay(100);
} }
delay(2000); delay(2000);
} }
@@ -644,9 +759,8 @@ void ledSmHandler(int sm) {
case APP_SM_WIFI_OK_SERVER_CONNECT_FAILED: { case APP_SM_WIFI_OK_SERVER_CONNECT_FAILED: {
ag.statusLed.setOff(); ag.statusLed.setOff();
for (int j = 0; j < 3; j++) { for (int j = 0; j < 3; j++) {
for (int i = 0; i < 4 * 2; i++) { for (int i = 0; i < 4; i++) {
ag.statusLed.setToggle(); ledBlinkDelay(LED_FAST_BLINK_DELAY);
delay(100);
} }
delay(2000); delay(2000);
} }
@@ -656,9 +770,8 @@ void ledSmHandler(int sm) {
case APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED: { case APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED: {
ag.statusLed.setOff(); ag.statusLed.setOff();
for (int j = 0; j < 3; j++) { for (int j = 0; j < 3; j++) {
for (int i = 0; i < 5 * 2; i++) { for (int i = 0; i < 5; i++) {
ag.statusLed.setToggle(); ledBlinkDelay(LED_FAST_BLINK_DELAY);
delay(100);
} }
delay(2000); delay(2000);
} }

View File

@@ -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. 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 CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
*/ */
#include <AirGradient.h> #include <AirGradient.h>
#ifdef ESP8266 #ifdef ESP8266
AirGradient ag = AirGradient(BOARD_DIY_PRO_INDOOR_V4_2); AirGradient ag = AirGradient(DIY_BASIC);
// AirGradient ag = AirGradient(BOARD_DIY_BASIC_KIT);
#else #else
// AirGradient ag = AirGradient(BOARD_ONE_INDOOR_MONITOR_V9_0); /** Create airgradient instance for 'OPEN_AIR_OUTDOOR' board */
AirGradient ag = AirGradient(BOARD_OUTDOOR_MONITOR_V1_3); AirGradient ag = AirGradient(OPEN_AIR_OUTDOOR);
#endif #endif
void failedHandler(String msg); void failedHandler(String msg);
void setup() { void setup()
{
Serial.begin(115200); Serial.begin(115200);
/** Init CO2 sensor */ /** Init CO2 sensor */
#ifdef ESP8266 #ifdef ESP8266
if (ag.s8.begin(&Serial) == false) { if (ag.s8.begin(&Serial) == false)
{
#else #else
if (ag.s8.begin(Serial1) == false) { if (ag.s8.begin(Serial1) == false)
{
#endif #endif
failedHandler("SenseAir S8 init failed"); failedHandler("SenseAir S8 init failed");
} }
} }
void loop() { void loop()
int CO2 = ag.s8.getCo2(); {
Serial.printf("CO2: %d\r\n", CO2); int co2Ppm = ag.s8.getCo2();
Serial.printf("CO2: %d\r\n", co2Ppm);
delay(5000); delay(5000);
} }
void failedHandler(String msg) { void failedHandler(String msg)
while (true) { {
while (true)
{
Serial.println(msg); Serial.println(msg);
delay(1000); delay(1000);
} }

View File

@@ -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 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> #include <AirGradient.h>
#ifdef ESP8266 #ifdef ESP8266
AirGradient ag = AirGradient(BOARD_DIY_PRO_INDOOR_V4_2); AirGradient ag = AirGradient(DIY_BASIC);
// AirGradient ag = AirGradient(BOARD_DIY_BASIC_KIT);
#else #else
// AirGradient ag = AirGradient(BOARD_ONE_INDOOR_MONITOR_V9_0); // AirGradient ag = AirGradient(ONE_INDOOR);
AirGradient ag = AirGradient(BOARD_OUTDOOR_MONITOR_V1_3); AirGradient ag = AirGradient(OPEN_AIR_OUTDOOR);
#endif #endif
void failedHandler(String msg); void failedHandler(String msg);
void setup() { void setup()
{
Serial.begin(115200); Serial.begin(115200);
#ifdef ESP8266 #ifdef ESP8266
if(ag.pms5003.begin(&Serial) == false) { if (ag.pms5003.begin(&Serial) == false)
{
failedHandler("Init PMS5003 failed"); failedHandler("Init PMS5003 failed");
} }
#else #else
if (ag.getBoardType() == BOARD_OUTDOOR_MONITOR_V1_3) { if (ag.getBoardType() == OPEN_AIR_OUTDOOR)
if(ag.pms5003t_1.begin(Serial0) == false) { {
if (ag.pms5003t_1.begin(Serial0) == false)
{
failedHandler("Init PMS5003T failed"); failedHandler("Init PMS5003T failed");
} }
} else { }
if(ag.pms5003.begin(Serial0) == false) { else
{
if (ag.pms5003.begin(Serial0) == false)
{
failedHandler("Init PMS5003T failed"); failedHandler("Init PMS5003T failed");
} }
} }
#endif #endif
} }
void loop() { void loop()
{
int PM2; int PM2;
#ifdef ESP8266 #ifdef ESP8266
PM2 = ag.pms5003.getPm25Ae(); PM2 = ag.pms5003.getPm25Ae();
Serial.printf("PM2.5 in ug/m3: %d\r\n", PM2); 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)); Serial.printf("PM2.5 in US AQI: %d\r\n", ag.pms5003.convertPm25ToUsAqi(PM2));
#else #else
if (ag.getBoardType() == BOARD_OUTDOOR_MONITOR_V1_3) { if (ag.getBoardType() == OPEN_AIR_OUTDOOR)
{
PM2 = ag.pms5003t_1.getPm25Ae(); PM2 = ag.pms5003t_1.getPm25Ae();
} else { }
else
{
PM2 = ag.pms5003.getPm25Ae(); PM2 = ag.pms5003.getPm25Ae();
} }
Serial.printf("PM2.5 in ug/m3: %d\r\n", PM2); 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", Serial.printf("PM2.5 in US AQI: %d\r\n",
ag.pms5003t_1.convertPm25ToUsAqi(PM2)); ag.pms5003t_1.convertPm25ToUsAqi(PM2));
} else { }
else
{
Serial.printf("PM2.5 in US AQI: %d\r\n", Serial.printf("PM2.5 in US AQI: %d\r\n",
ag.pms5003.convertPm25ToUsAqi(PM2)); ag.pms5003.convertPm25ToUsAqi(PM2));
} }
@@ -61,8 +75,10 @@ void loop() {
delay(5000); delay(5000);
} }
void failedHandler(String msg) { void failedHandler(String msg)
while (true) { {
while (true)
{
Serial.println(msg); Serial.println(msg);
delay(1000); delay(1000);
} }

View File

@@ -1,6 +1,6 @@
#include "AirGradient.h" #include "AirGradient.h"
#define AG_LIB_VER "3.0.0" #define AG_LIB_VER "2.5.0"
AirGradient::AirGradient(BoardType type) AirGradient::AirGradient(BoardType type)
: pms5003(type), pms5003t_1(type), pms5003t_2(type), s8(type), sht(type), sgp41(type), : pms5003(type), pms5003t_1(type), pms5003t_2(type), s8(type), sht(type), sgp41(type),

View File

@@ -1,18 +1,22 @@
#ifndef _AIR_GRADIENT_H_ #ifndef _AIR_GRADIENT_H_
#define _AIR_GRADIENT_H_ #define _AIR_GRADIENT_H_
#include "bsp/BoardDef.h" #include "main/BoardDef.h"
#include "bsp/LedBar.h" #include "main/HardwareWatchdog.h"
#include "bsp/PushButton.h" #include "main/LedBar.h"
#include "bsp/StatusLed.h" #include "main/PushButton.h"
#include "bsp/HardwareWatchdog.h" #include "main/StatusLed.h"
#include "co2/s8.h" #include "s8/s8.h"
#include "display/oled.h" #include "display/oled.h"
#include "pm/pms5003.h" #include "pms/pms5003.h"
#include "pm/pms5003t.h" #include "pms/pms5003t.h"
#include "sgp/sgp41.h" #include "sgp41/sgp41.h"
#include "sht/sht4x.h" #include "sht4x/sht4x.h"
/**
* @brief Class with define all the sensor has supported by Airgradient. Each
* sensor usage must be init before use.
*/
class AirGradient { class AirGradient {
public: public:
AirGradient(BoardType type); AirGradient(BoardType type);
@@ -21,7 +25,13 @@ public:
* @brief Plantower PMS5003 sensor * @brief Plantower PMS5003 sensor
*/ */
PMS5003 pms5003; PMS5003 pms5003;
/**
* @brief Plantower PMS5003T sensor: connect to PM1 connector on outdoor board.
*/
PMS5003T pms5003t_1; PMS5003T pms5003t_1;
/**
* @brief Plantower PMS5003T sensor: connect to PM2 connector on outdoor board
*/
PMS5003T pms5003t_2; PMS5003T pms5003t_2;
/** /**
@@ -30,18 +40,18 @@ public:
S8 s8; S8 s8;
/** /**
* @brief Temperature and humidity sensor * @brief SHT41 Temperature and humidity sensor
*/ */
Sht sht; Sht sht;
/** /**
* @brief TVOC and NOx sensor * @brief SGP41 TVOC and NOx sensor
* *
*/ */
Sgp41 sgp41; Sgp41 sgp41;
/** /**
* @brief Display * @brief OLED Display
* *
*/ */
Display display; Display display;
@@ -55,18 +65,43 @@ public:
* @brief LED * @brief LED
*/ */
StatusLed statusLed; StatusLed statusLed;
/**
* @brief RGB LED array
*
*/
LedBar ledBar; LedBar ledBar;
/** /**
* @brief Hardware watchdog * @brief External hardware watchdog
*/ */
HardwareWatchdog watchdog; HardwareWatchdog watchdog;
/**
* @brief Get I2C SDA pin has of board supported
*
* @return int Pin number if -1 invalid
*/
int getI2cSdaPin(void); int getI2cSdaPin(void);
/**
* @brief Get I2C SCL pin has of board supported
*
* @return int Pin number if -1 invalid
*/
int getI2cSclPin(void); int getI2cSclPin(void);
/**
* @brief Get the Board Type
*
* @return BoardType @ref BoardType
*/
BoardType getBoardType(void); BoardType getBoardType(void);
/**
* @brief Get the library version string
*
* @return String
*/
String getVersion(void); String getVersion(void);
private: private:

View File

@@ -3,7 +3,7 @@
#include "../library/Adafruit_SSD1306_Wemos_OLED/Adafruit_SSD1306.h" #include "../library/Adafruit_SSD1306_Wemos_OLED/Adafruit_SSD1306.h"
#define disp(func) \ #define disp(func) \
if (this->_boardType == BOARD_DIY_BASIC_KIT) { \ if (this->_boardType == DIY_BASIC) { \
((Adafruit_SSD1306 *)(this->oled))->func; \ ((Adafruit_SSD1306 *)(this->oled))->func; \
} else { \ } else { \
((Adafruit_SH110X *)(this->oled))->func; \ ((Adafruit_SH110X *)(this->oled))->func; \
@@ -19,7 +19,18 @@ void Display::begin(TwoWire &wire, Stream &debugStream) {
Display::Display(BoardType type) : _boardType(type) {} Display::Display(BoardType type) : _boardType(type) {}
/**
* @brief Initialize display, should be call this function before call of ther,
* if not it's always return failure.
*
* @param wire TwoWire instance, Must be initialized
*/
void Display::begin(TwoWire &wire) { void Display::begin(TwoWire &wire) {
if (_isBegin) {
AgLog("Initialized, call end() then try again");
return;
}
this->_bsp = getBoardDef(this->_boardType); this->_bsp = getBoardDef(this->_boardType);
if ((this->_bsp == nullptr) || (this->_bsp->I2C.supported == false) || if ((this->_bsp == nullptr) || (this->_bsp->I2C.supported == false) ||
(this->_bsp->OLED.supported == false)) { (this->_bsp->OLED.supported == false)) {
@@ -28,67 +39,106 @@ void Display::begin(TwoWire &wire) {
} }
/** Init OLED */ /** Init OLED */
if (this->_boardType == BOARD_DIY_BASIC_KIT) { if (this->_boardType == DIY_BASIC) {
AgLog("Init Adafruit_SSD1306"); AgLog("Init Adafruit_SSD1306");
Adafruit_SSD1306 *_oled = new Adafruit_SSD1306(); Adafruit_SSD1306 *_oled = new Adafruit_SSD1306();
_oled->begin(wire, SSD1306_SWITCHCAPVCC, this->_bsp->OLED.addr); _oled->begin(wire, SSD1306_SWITCHCAPVCC, this->_bsp->OLED.addr);
this->oled = _oled; this->oled = _oled;
} else { } else {
AgLog("Init Adafruit_SH1106G"); AgLog("Init Adafruit_SH1106G");
Adafruit_SH1106G *_oled = new Adafruit_SH1106G(this->_bsp->OLED.width, this->_bsp->OLED.height, &wire); Adafruit_SH1106G *_oled = new Adafruit_SH1106G(
this->_bsp->OLED.width, this->_bsp->OLED.height, &wire);
_oled->begin(this->_bsp->OLED.addr, false); _oled->begin(this->_bsp->OLED.addr, false);
this->oled = _oled; this->oled = _oled;
} }
this->_isInit = true; this->_isBegin = true;
disp(clearDisplay()); disp(clearDisplay());
AgLog("Init"); AgLog("Initialize");
} }
/**
* @brief Clear display buffer
*
*/
void Display::clear(void) { void Display::clear(void) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return; return;
} }
disp(clearDisplay()); disp(clearDisplay());
} }
/**
* @brief Invert display color
*
* @param i 0: black, other is white
*/
void Display::invertDisplay(uint8_t i) { void Display::invertDisplay(uint8_t i) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return; return;
} }
disp(invertDisplay(i)); disp(invertDisplay(i));
} }
/**
* @brief Send display frame buffer to OLED
*
*/
void Display::show() { void Display::show() {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return; return;
} }
disp(display()); disp(display());
} }
/**
* @brief Set display contract
*
* @param value Contract (0;255);
*/
void Display::setContrast(uint8_t value) { void Display::setContrast(uint8_t value) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return; return;
} }
disp(setContrast(value)); disp(setContrast(value));
} }
/**
* @brief Draw pixel into display frame buffer, call show to draw to
* display(OLED)
*
* @param x X Position
* @param y Y Position
* @param color Color (0: black, other white)
*/
void Display::drawPixel(int16_t x, int16_t y, uint16_t color) { void Display::drawPixel(int16_t x, int16_t y, uint16_t color) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return; return;
} }
disp(drawPixel(x, y, color)); disp(drawPixel(x, y, color));
} }
/**
* @brief Set text size, it's scale default font instead of point to multiple
* font has define for special size
*
* @param size Size of text (default = 1)
*/
void Display::setTextSize(int size) { void Display::setTextSize(int size) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return; return;
} }
disp(setTextSize(size)); disp(setTextSize(size));
} }
/**
* @brief Move draw cursor into new position
*
* @param x X Position
* @param y Y Position
*/
void Display::setCursor(int16_t x, int16_t y) { void Display::setCursor(int16_t x, int16_t y) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return; return;
} }
disp(setCursor(x, y)); disp(setCursor(x, y));
@@ -100,7 +150,7 @@ void Display::setCursor(int16_t x, int16_t y) {
* @param color 0:black, 1: While * @param color 0:black, 1: While
*/ */
void Display::setTextColor(uint16_t color) { void Display::setTextColor(uint16_t color) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return; return;
} }
disp(setTextColor(color)); disp(setTextColor(color));
@@ -113,66 +163,120 @@ void Display::setTextColor(uint16_t color) {
* @param backGroundColor Text background color * @param backGroundColor Text background color
*/ */
void Display::setTextColor(uint16_t foreGroundColor, uint16_t backGroundColor) { void Display::setTextColor(uint16_t foreGroundColor, uint16_t backGroundColor) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return; return;
} }
disp(setTextColor(foreGroundColor, backGroundColor)); disp(setTextColor(foreGroundColor, backGroundColor));
} }
/**
* @brief Draw text to display framebuffer, call show() to draw to display
* (OLED)
*
* @param text String
*/
void Display::setText(String text) { void Display::setText(String text) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return; return;
} }
disp(print(text)); disp(print(text));
} }
/**
* @brief Draw bitmap into display framebuffer, call show() to draw to display
* (OLED)
*
* @param x X Position
* @param y Y Position
* @param bitmap Bitmap buffer
* @param w Bitmap width
* @param h Bitmap hight
* @param color Bitmap color
*/
void Display::drawBitmap(int16_t x, int16_t y, const uint8_t bitmap[], void Display::drawBitmap(int16_t x, int16_t y, const uint8_t bitmap[],
int16_t w, int16_t h, uint16_t color) { int16_t w, int16_t h, uint16_t color) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return; return;
} }
disp(drawBitmap(x, y, bitmap, w, h, color)); disp(drawBitmap(x, y, bitmap, w, h, color));
} }
/**
* @brief Set text to display framebuffer, call show() to draw into to display
* (OLED)
*
* @param text Character buffer
*/
void Display::setText(const char text[]) { void Display::setText(const char text[]) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return; return;
} }
disp(print(text)); disp(print(text));
} }
/**
* @brief Draw line to display framebuffer, call show() to draw to
* display(OLED)
*
* @param x0 Start X position
* @param y0 Start Y position
* @param x1 End X Position
* @param y1 End Y Position
* @param color Color (0: black, otherwise white)
*/
void Display::drawLine(int x0, int y0, int x1, int y1, uint16_t color) { void Display::drawLine(int x0, int y0, int x1, int y1, uint16_t color) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return; return;
} }
disp(drawLine(x0, y0, x1, y1, color)); disp(drawLine(x0, y0, x1, y1, color));
} }
/**
* @brief Draw circle to display framebuffer,
*
* @param x
* @param y
* @param r
* @param color
*/
void Display::drawCircle(int x, int y, int r, uint16_t color) { void Display::drawCircle(int x, int y, int r, uint16_t color) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return; return;
} }
disp(drawCircle(x, y, r, color)); disp(drawCircle(x, y, r, color));
} }
void Display::drawRect(int x0, int y0, int x1, int y1, uint16_t color) { void Display::drawRect(int x0, int y0, int x1, int y1, uint16_t color) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return; return;
} }
disp(drawRect(x0, y0, x1, y1, color)); disp(drawRect(x0, y0, x1, y1, color));
} }
bool Display::checkInit(void) { bool Display::isBegin(void) {
if (this->_isInit) { if (this->_isBegin) {
return true; return true;
} }
AgLog("OLED is not init"); AgLog("Display not-initialized");
return false; return false;
} }
void Display::setRotation(uint8_t r) { void Display::setRotation(uint8_t r) {
if (checkInit() == false) { if (isBegin() == false) {
return; return;
} }
disp(setRotation(r)); disp(setRotation(r));
} }
void Display::end(void) {
if (this->_isBegin == false) {
return;
}
_isBegin = false;
if (this->_boardType == DIY_BASIC) {
delete ((Adafruit_SSD1306 *)(this->oled));
} else {
delete ((Adafruit_SH110X *)(this->oled));
}
AgLog("De-initialize");
}

View File

@@ -1,10 +1,14 @@
#ifndef _AIR_GRADIENT_OLED_H_ #ifndef _AIR_GRADIENT_OLED_H_
#define _AIR_GRADIENT_OLED_H_ #define _AIR_GRADIENT_OLED_H_
#include "../bsp/BoardDef.h" #include "../main/BoardDef.h"
#include <Arduino.h> #include <Arduino.h>
#include <Wire.h> #include <Wire.h>
/**
* @brief The class define how to handle the OLED display on Airgradient has
* attached or support OLED display like: ONE-V9, Basic-V4
*/
class Display { class Display {
public: public:
const uint16_t COLOR_WHILTE = 1; const uint16_t COLOR_WHILTE = 1;
@@ -15,10 +19,11 @@ public:
#endif #endif
Display(BoardType type); Display(BoardType type);
void begin(TwoWire &wire); void begin(TwoWire &wire);
void end(void);
void clear(void); // .clear void clear(void);
void invertDisplay(uint8_t i); void invertDisplay(uint8_t i);
void show(); // .show() void show();
void setContrast(uint8_t value); void setContrast(uint8_t value);
void drawPixel(int16_t x, int16_t y, uint16_t color); void drawPixel(int16_t x, int16_t y, uint16_t color);
@@ -39,14 +44,14 @@ private:
BoardType _boardType; BoardType _boardType;
const BoardDef *_bsp = nullptr; const BoardDef *_bsp = nullptr;
void *oled; void *oled;
bool _isInit = false; bool _isBegin = false;
#if defined(ESP8266) #if defined(ESP8266)
const char *TAG = "oled"; const char *TAG = "oled";
Stream *_debugStream = nullptr; Stream *_debugStream = nullptr;
#else #else
#endif #endif
bool checkInit(void); bool isBegin(void);
}; };
#endif /** _AIR_GRADIENT_OLED_H_ */ #endif /** _AIR_GRADIENT_OLED_H_ */

View File

@@ -3,9 +3,9 @@
#include "esp32-hal-log.h" #include "esp32-hal-log.h"
#endif #endif
const BoardDef bsps[BOARD_DEF_MAX] = { const BoardDef bsps[_BOARD_MAX] = {
/** BOARD_DIY_BASIC_KIT */ /** DIY_BASIC */
[BOARD_DIY_BASIC_KIT] = [DIY_BASIC] =
{ {
.SenseAirS8 = .SenseAirS8 =
{ {
@@ -17,7 +17,7 @@ const BoardDef bsps[BOARD_DEF_MAX] = {
.supported = false, .supported = false,
#endif #endif
}, },
.PMS5003 = .Pms5003 =
{ {
.uart_tx_pin = 14, .uart_tx_pin = 14,
.uart_rx_pin = 12, .uart_rx_pin = 12,
@@ -70,10 +70,10 @@ const BoardDef bsps[BOARD_DEF_MAX] = {
.resetPin = -1, .resetPin = -1,
.supported = false, .supported = false,
}, },
.name = "BOARD_DIY_BASIC_KIT", .name = "DIY_BASIC",
}, },
/** BOARD_DIY_PRO_INDOOR_V4_2 */ /** DIY_PRO_INDOOR_V4_2 */
[BOARD_DIY_PRO_INDOOR_V4_2] = [DIY_PRO_INDOOR_V4_2] =
{ {
.SenseAirS8 = .SenseAirS8 =
{ {
@@ -85,7 +85,7 @@ const BoardDef bsps[BOARD_DEF_MAX] = {
.supported = false, .supported = false,
#endif #endif
}, },
.PMS5003 = .Pms5003 =
{ {
.uart_tx_pin = 14, .uart_tx_pin = 14,
.uart_rx_pin = 12, .uart_rx_pin = 12,
@@ -144,10 +144,10 @@ const BoardDef bsps[BOARD_DEF_MAX] = {
.resetPin = -1, .resetPin = -1,
.supported = false, .supported = false,
}, },
.name = "BOARD_DIY_PRO_INDOOR_V4_2", .name = "DIY_PRO_INDOOR_V4_2",
}, },
/** BOARD_ONE_INDOOR_MONITOR_V9_0 */ /** ONE_INDOOR */
[BOARD_ONE_INDOOR_MONITOR_V9_0] = [ONE_INDOOR] =
{ {
.SenseAirS8 = .SenseAirS8 =
{ {
@@ -160,7 +160,7 @@ const BoardDef bsps[BOARD_DEF_MAX] = {
#endif #endif
}, },
/** Use UART0 don't use define pin number */ /** Use UART0 don't use define pin number */
.PMS5003 = .Pms5003 =
{ {
.uart_tx_pin = -1, .uart_tx_pin = -1,
.uart_rx_pin = -1, .uart_rx_pin = -1,
@@ -232,10 +232,10 @@ const BoardDef bsps[BOARD_DEF_MAX] = {
.supported = true, .supported = true,
#endif #endif
}, },
.name = "BOARD_ONE_INDOOR_MONITOR_V9_0", .name = "ONE_INDOOR",
}, },
/** BOARD_OUTDOOR_MONITOR_V1_3 */ /** OPEN_AIR_OUTDOOR */
[BOARD_OUTDOOR_MONITOR_V1_3] = { [OPEN_AIR_OUTDOOR] = {
.SenseAirS8 = .SenseAirS8 =
{ {
.uart_tx_pin = 1, .uart_tx_pin = 1,
@@ -247,7 +247,7 @@ const BoardDef bsps[BOARD_DEF_MAX] = {
#endif #endif
}, },
/** Use UART0 don't use define pin number */ /** Use UART0 don't use define pin number */
.PMS5003 = .Pms5003 =
{ {
.uart_tx_pin = -1, .uart_tx_pin = -1,
.uart_rx_pin = -1, .uart_rx_pin = -1,
@@ -319,7 +319,7 @@ const BoardDef bsps[BOARD_DEF_MAX] = {
.supported = true, .supported = true,
#endif #endif
}, },
.name = "BOARD_OUTDOOR_MONITOR_V1_3", .name = "OPEN_AIR_OUTDOOR",
}}; }};
/** /**
@@ -329,7 +329,7 @@ const BoardDef bsps[BOARD_DEF_MAX] = {
* @return const BoardDef* * @return const BoardDef*
*/ */
const BoardDef *getBoardDef(BoardType def) { const BoardDef *getBoardDef(BoardType def) {
if (def >= BOARD_DEF_MAX) { if (def >= _BOARD_MAX) {
return NULL; return NULL;
} }
return &bsps[def]; return &bsps[def];
@@ -356,7 +356,7 @@ void printBoardDef(Stream *_debug) {
} }
#endif #endif
for (int i = 0; i < BOARD_DEF_MAX; i++) { for (int i = 0; i < _BOARD_MAX; i++) {
bspPrintf("Board name: %s", bsps[i].name); bspPrintf("Board name: %s", bsps[i].name);
bspPrintf("\tSensor CO2 S8:"); bspPrintf("\tSensor CO2 S8:");
bspPrintf("\t\tSupported: %d", bsps[i].SenseAirS8.supported); bspPrintf("\t\tSupported: %d", bsps[i].SenseAirS8.supported);
@@ -366,10 +366,10 @@ void printBoardDef(Stream *_debug) {
} }
bspPrintf("\tSensor PMS5003:"); bspPrintf("\tSensor PMS5003:");
bspPrintf("\t\tSupported: %d", bsps[i].PMS5003.supported); bspPrintf("\t\tSupported: %d", bsps[i].Pms5003.supported);
if (bsps[i].PMS5003.supported) { if (bsps[i].Pms5003.supported) {
bspPrintf("\t\tUART Tx: %d", bsps[i].PMS5003.uart_tx_pin); bspPrintf("\t\tUART Tx: %d", bsps[i].Pms5003.uart_tx_pin);
bspPrintf("\t\tUART Rx: %d", bsps[i].PMS5003.uart_rx_pin); bspPrintf("\t\tUART Rx: %d", bsps[i].Pms5003.uart_rx_pin);
} }
bspPrintf("\tI2C"); bspPrintf("\tI2C");

View File

@@ -13,14 +13,21 @@
#define AgLog(c, ...) log_i(c, ##__VA_ARGS__) #define AgLog(c, ...) log_i(c, ##__VA_ARGS__)
#endif #endif
/**
* @brief Define Airgradient supported board type
*/
enum BoardType { enum BoardType {
BOARD_DIY_BASIC_KIT = 0x00, DIY_BASIC = 0x00,
BOARD_DIY_PRO_INDOOR_V4_2 = 0x01, DIY_PRO_INDOOR_V4_2 = 0x01,
BOARD_ONE_INDOOR_MONITOR_V9_0 = 0x02, ONE_INDOOR = 0x02,
BOARD_OUTDOOR_MONITOR_V1_3 = 0x03, OPEN_AIR_OUTDOOR = 0x03,
BOARD_DEF_MAX _BOARD_MAX
}; };
/**
* @brief Board definitions
*
*/
struct BoardDef { struct BoardDef {
/** Board Support CO2 SenseS8 */ /** Board Support CO2 SenseS8 */
struct { struct {
@@ -34,7 +41,7 @@ struct BoardDef {
const int uart_tx_pin; /** UART tx pin */ const int uart_tx_pin; /** UART tx pin */
const int uart_rx_pin; /** UART rx pin */ const int uart_rx_pin; /** UART rx pin */
const bool supported; /** Is BSP supported for this sensor */ const bool supported; /** Is BSP supported for this sensor */
} PMS5003; } Pms5003;
/** I2C Bus */ /** I2C Bus */
struct { struct {

View File

@@ -5,6 +5,10 @@
#include "BoardDef.h" #include "BoardDef.h"
/**
* @brief The class define how to control external watchdog on ONE-V9 and
* Outdoor
*/
class HardwareWatchdog { class HardwareWatchdog {
public: public:
HardwareWatchdog(BoardType type); HardwareWatchdog(BoardType type);

View File

@@ -18,7 +18,7 @@ LedBar::LedBar(BoardType type) : _boardType(type) {}
* *
*/ */
void LedBar::begin(void) { void LedBar::begin(void) {
if (this->_isInit) { if (this->_isBegin) {
return; return;
} }
@@ -36,9 +36,9 @@ void LedBar::begin(void) {
pixel()->begin(); pixel()->begin();
pixel()->clear(); pixel()->clear();
this->_isInit = true; this->_isBegin = true;
AgLog("Init"); AgLog("Initialize");
} }
/** /**
@@ -64,7 +64,7 @@ void LedBar::setColor(uint8_t red, uint8_t green, uint8_t blue, int ledNum) {
* @param brightness Brightness (0 - 255) * @param brightness Brightness (0 - 255)
*/ */
void LedBar::setBrighness(uint8_t brightness) { void LedBar::setBrighness(uint8_t brightness) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return; return;
} }
pixel()->setBrightness(brightness); pixel()->setBrightness(brightness);
@@ -76,15 +76,15 @@ void LedBar::setBrighness(uint8_t brightness) {
* @return int Number of LED * @return int Number of LED
*/ */
int LedBar::getNumberOfLed(void) { int LedBar::getNumberOfLed(void) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return 0; return 0;
} }
return this->_bsp->LED.rgbNum; return this->_bsp->LED.rgbNum;
} }
bool LedBar::checkInit(void) { bool LedBar::isBegin(void) {
if (this->_isInit) { if (this->_isBegin) {
return true; return true;
} }
AgLog("LED is not initialized"); AgLog("LED is not initialized");
@@ -92,7 +92,7 @@ bool LedBar::checkInit(void) {
} }
bool LedBar::ledNumInvalid(int ledNum) { bool LedBar::ledNumInvalid(int ledNum) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return true; return true;
} }

View File

@@ -5,6 +5,10 @@
#include "BoardDef.h" #include "BoardDef.h"
/**
* @brief The class define how to handle the RGB LED bar
*
*/
class LedBar { class LedBar {
public: public:
#if defined(ESP8266) #if defined(ESP8266)
@@ -22,7 +26,7 @@ public:
private: private:
const BoardDef *_bsp; const BoardDef *_bsp;
bool _isInit = false; bool _isBegin = false;
uint8_t _ledState = 0; uint8_t _ledState = 0;
BoardType _boardType; BoardType _boardType;
void *pixels = nullptr; void *pixels = nullptr;
@@ -31,7 +35,7 @@ private:
const char *TAG = "LED"; const char *TAG = "LED";
#else #else
#endif #endif
bool checkInit(void); bool isBegin(void);
bool ledNumInvalid(int ledNum); bool ledNumInvalid(int ledNum);
}; };

View File

@@ -15,7 +15,8 @@ void PushButton::begin(Stream &debugStream) {
* *
*/ */
void PushButton::begin(void) { void PushButton::begin(void) {
if (this->_isInit) { if (this->_isBegin) {
AgLog("Initialized, call end() then try again");
return; return;
} }
@@ -25,14 +26,14 @@ void PushButton::begin(void) {
return; return;
} }
if (this->_boardType == BOARD_DIY_PRO_INDOOR_V4_2) { if (this->_boardType == DIY_PRO_INDOOR_V4_2) {
pinMode(this->_bsp->SW.pin, INPUT_PULLUP); pinMode(this->_bsp->SW.pin, INPUT_PULLUP);
} else { } else {
pinMode(this->_bsp->SW.pin, INPUT); pinMode(this->_bsp->SW.pin, INPUT);
} }
this->_isInit = true; this->_isBegin = true;
AgLog("Init"); AgLog("Initialize");
} }
/** /**
@@ -41,7 +42,7 @@ void PushButton::begin(void) {
* @return PushButton::State * @return PushButton::State
*/ */
PushButton::State PushButton::getState(void) { PushButton::State PushButton::getState(void) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return State::BUTTON_RELEASED; return State::BUTTON_RELEASED;
} }
@@ -64,8 +65,8 @@ String PushButton::toString(PushButton::State state) {
return "Released"; return "Released";
} }
bool PushButton::checkInit(void) { bool PushButton::isBegin(void) {
if (this->_isInit) { if (this->_isBegin) {
return true; return true;
} }
AgLog("Switch not initialized"); AgLog("Switch not initialized");

View File

@@ -4,6 +4,10 @@
#include "BoardDef.h" #include "BoardDef.h"
#include <Arduino.h> #include <Arduino.h>
/**
* @brief The class define how to handle the Push button
*
*/
class PushButton { class PushButton {
public: public:
/** /**
@@ -26,7 +30,7 @@ private:
/** Board type */ /** Board type */
BoardType _boardType; BoardType _boardType;
/** Is inititalize flag */ /** Is inititalize flag */
bool _isInit = false; bool _isBegin = false;
/** Special variable for ESP8266 */ /** Special variable for ESP8266 */
#if defined(ESP8266) #if defined(ESP8266)
@@ -37,7 +41,7 @@ private:
/** Method */ /** Method */
bool checkInit(void); bool isBegin(void);
}; };
#endif /** _AIR_GRADIENT_SW_H_ */ #endif /** _AIR_GRADIENT_SW_H_ */

View File

@@ -15,6 +15,10 @@ void StatusLed::begin(Stream &debugStream) {
* *
*/ */
void StatusLed::begin(void) { void StatusLed::begin(void) {
if (this->_isBegin) {
AgLog("Initialized, call end() then try again");
return;
}
bsp = getBoardDef(this->boardType); bsp = getBoardDef(this->boardType);
if ((bsp == nullptr) || (bsp->LED.supported == false)) { if ((bsp == nullptr) || (bsp->LED.supported == false)) {
AgLog("Board not support StatusLed"); AgLog("Board not support StatusLed");
@@ -25,9 +29,9 @@ void StatusLed::begin(void) {
digitalWrite(bsp->LED.pin, !bsp->LED.onState); digitalWrite(bsp->LED.pin, !bsp->LED.onState);
this->state = LED_OFF; this->state = LED_OFF;
this->isInit = true; this->_isBegin = true;
AgLog("Init"); AgLog("Initialize");
} }
/** /**
@@ -35,7 +39,7 @@ void StatusLed::begin(void) {
* *
*/ */
void StatusLed::setOn(void) { void StatusLed::setOn(void) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return; return;
} }
digitalWrite(bsp->LED.pin, bsp->LED.onState); digitalWrite(bsp->LED.pin, bsp->LED.onState);
@@ -48,7 +52,7 @@ void StatusLed::setOn(void) {
* *
*/ */
void StatusLed::setOff(void) { void StatusLed::setOff(void) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return; return;
} }
digitalWrite(bsp->LED.pin, !bsp->LED.onState); digitalWrite(bsp->LED.pin, !bsp->LED.onState);
@@ -88,11 +92,24 @@ String StatusLed::toString(StatusLed::State state) {
return "Off"; return "Off";
} }
bool StatusLed::checkInit(void) { bool StatusLed::isBegin(void) {
if (this->isInit == false) { if (this->_isBegin == false) {
AgLog("No-Initialized"); AgLog("Not-Initialized");
return false; return false;
} }
return true; return true;
} }
void StatusLed::end(void) {
if (_isBegin == false) {
return;
}
#if defined(ESP8266)
_debugStream = nullptr;
#endif
setOff();
_isBegin = false;
AgLog("De-initialize");
}

View File

@@ -4,6 +4,10 @@
#include "BoardDef.h" #include "BoardDef.h"
#include <Arduino.h> #include <Arduino.h>
/**
* @brief The class define how to handle the LED
*
*/
class StatusLed { class StatusLed {
public: public:
enum State { enum State {
@@ -17,6 +21,7 @@ public:
#else #else
#endif #endif
void begin(void); void begin(void);
void end(void);
void setOn(void); void setOn(void);
void setOff(void); void setOff(void);
void setToggle(void); void setToggle(void);
@@ -26,7 +31,7 @@ public:
private: private:
const BoardDef *bsp = nullptr; const BoardDef *bsp = nullptr;
BoardType boardType; BoardType boardType;
bool isInit = false; bool _isBegin = false;
State state; State state;
#if defined(ESP8266) #if defined(ESP8266)
Stream *_debugStream; Stream *_debugStream;
@@ -34,7 +39,7 @@ private:
#else #else
#endif #endif
bool checkInit(void); bool isBegin(void);
}; };
#endif /** _STATUS_LED_H_ */ #endif /** _STATUS_LED_H_ */

View File

@@ -1,7 +1,5 @@
#include "PMS.h" #include "PMS.h"
// PMS::PMS(Stream &stream) { this->_stream = &stream; }
bool PMS::begin(Stream *stream) { bool PMS::begin(Stream *stream) {
_stream = stream; _stream = stream;

View File

@@ -3,6 +3,11 @@
#include <Arduino.h> #include <Arduino.h>
/**
* @brief Class define how to handle plantower PMS sensor it's upport for
* PMS5003 and PMS5003T series. The data @ref AMB_TMP and @ref AMB_HUM only
* valid on PMS5003T
*/
class PMS { class PMS {
public: public:
static const uint16_t SINGLE_RESPONSE_TIME = 1000; static const uint16_t SINGLE_RESPONSE_TIME = 1000;
@@ -38,7 +43,7 @@ public:
uint16_t AMB_HUM; uint16_t AMB_HUM;
}; };
bool begin(Stream* stream); bool begin(Stream *stream);
void sleep(); void sleep();
void wakeUp(); void wakeUp();
void activeMode(); void activeMode();

View File

@@ -43,32 +43,27 @@ PMS5003::PMS5003(BoardType def) : _boardDef(def) {}
* @return false Failure * @return false Failure
*/ */
bool PMS5003::begin(void) { bool PMS5003::begin(void) {
if (this->_isInit) { if (this->_isBegin) {
AgLog("Initialized, call end() then try again");
return true; return true;
} }
#if defined(ESP32) #if defined(ESP32)
// if (this->_serial != &Serial) {
// AgLog("Hardware serial must be Serial(0)");
// return false;
// }
#endif #endif
this->bsp = getBoardDef(this->_boardDef); this->bsp = getBoardDef(this->_boardDef);
if (bsp == NULL) { if (bsp == NULL) {
AgLog("Board [%d] not supported", this->_boardDef); AgLog("Board [%d] not supported", this->_boardDef);
return false; return false;
} }
if (bsp->PMS5003.supported == false) { if (bsp->Pms5003.supported == false) {
AgLog("Board [%d] PMS50035003 not supported", this->_boardDef); AgLog("Board [%d] PMS50035003 not supported", this->_boardDef);
return false; return false;
} }
#if defined(ESP8266) #if defined(ESP8266)
bsp->PMS5003.uart_tx_pin; bsp->Pms5003.uart_tx_pin;
SoftwareSerial *uart = SoftwareSerial *uart = new SoftwareSerial(bsp->Pms5003.uart_tx_pin, bsp->Pms5003.uart_rx_pin);
new SoftwareSerial(bsp->PMS5003.uart_tx_pin, bsp->PMS5003.uart_rx_pin);
uart->begin(9600); uart->begin(9600);
if (pms.begin(uart) == false) { if (pms.begin(uart) == false) {
AgLog("PMS failed"); AgLog("PMS failed");
@@ -82,7 +77,7 @@ bool PMS5003::begin(void) {
} }
#endif #endif
this->_isInit = true; this->_isBegin = true;
return true; return true;
} }
@@ -119,7 +114,7 @@ int PMS5003::pm25ToAQI(int pm02) {
* @return false Failure * @return false Failure
*/ */
bool PMS5003::readData(void) { bool PMS5003::readData(void) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return false; return false;
} }
@@ -168,10 +163,26 @@ int PMS5003::convertPm25ToUsAqi(int pm25) { return this->pm25ToAQI(pm25); }
* @return true Initialized * @return true Initialized
* @return false No-initialized * @return false No-initialized
*/ */
bool PMS5003::checkInit(void) { bool PMS5003::isBegin(void) {
if (this->_isInit == false) { if (this->_isBegin == false) {
AgLog("No initialized"); AgLog("Not-initialized");
return false; return false;
} }
return true; return true;
} }
/**
* @brief De-initialize sensor
*/
void PMS5003::end(void) {
if (_isBegin == false) {
return;
}
_isBegin = false;
#if defined(ESP8266)
_debugStream = NULL;
#else
delete _serial;
#endif
AgLog("De-initialize");
}

View File

@@ -1,10 +1,13 @@
#ifndef _AIR_GRADIENT_PMS5003_H_ #ifndef _AIR_GRADIENT_PMS5003_H_
#define _AIR_GRADIENT_PMS5003_H_ #define _AIR_GRADIENT_PMS5003_H_
#include "../bsp/BoardDef.h" #include "../main/BoardDef.h"
#include "Stream.h" #include "Stream.h"
#include "PMS.h" #include "PMS.h"
/**
* @brief The class define how to handle PMS5003 sensor bas on @ref PMS class
*/
class PMS5003 { class PMS5003 {
public: public:
PMS5003(BoardType def); PMS5003(BoardType def);
@@ -13,6 +16,7 @@ public:
#else #else
bool begin(HardwareSerial &serial); bool begin(HardwareSerial &serial);
#endif #endif
void end(void);
bool readData(void); bool readData(void);
int getPm01Ae(void); int getPm01Ae(void);
@@ -22,7 +26,7 @@ public:
int convertPm25ToUsAqi(int pm25); int convertPm25ToUsAqi(int pm25);
private: private:
bool _isInit = false; bool _isBegin = false;
BoardType _boardDef; BoardType _boardDef;
PMS pms; PMS pms;
const BoardDef *bsp; const BoardDef *bsp;
@@ -36,7 +40,7 @@ private:
PMS::DATA pmsData; PMS::DATA pmsData;
bool begin(void); bool begin(void);
bool checkInit(void); bool isBegin(void);
int pm25ToAQI(int pm02); int pm25ToAQI(int pm02);
}; };
#endif /** _AIR_GRADIENT_PMS5003_H_ */ #endif /** _AIR_GRADIENT_PMS5003_H_ */

View File

@@ -43,7 +43,7 @@ PMS5003T::PMS5003T(BoardType def) : _boardDef(def) {}
* @return false Failure * @return false Failure
*/ */
bool PMS5003T::begin(void) { bool PMS5003T::begin(void) {
if (this->_isInit) { if (this->_isBegin) {
return true; return true;
} }
@@ -60,15 +60,15 @@ bool PMS5003T::begin(void) {
return false; return false;
} }
if (bsp->PMS5003.supported == false) { if (bsp->Pms5003.supported == false) {
AgLog("Board [%d] PMS5003 not supported", this->_boardDef); AgLog("Board [%d] PMS5003 not supported", this->_boardDef);
return false; return false;
} }
#if defined(ESP8266) #if defined(ESP8266)
bsp->PMS5003.uart_tx_pin; bsp->Pms5003.uart_tx_pin;
SoftwareSerial *uart = SoftwareSerial *uart =
new SoftwareSerial(bsp->PMS5003.uart_tx_pin, bsp->PMS5003.uart_rx_pin); new SoftwareSerial(bsp->Pms5003.uart_tx_pin, bsp->Pms5003.uart_rx_pin);
uart->begin(9600); uart->begin(9600);
if (pms.begin(uart) == false) { if (pms.begin(uart) == false) {
AgLog("PMS failed"); AgLog("PMS failed");
@@ -82,8 +82,8 @@ bool PMS5003T::begin(void) {
if (this->_serial == &Serial) { if (this->_serial == &Serial) {
#endif #endif
AgLog("Init Serial"); AgLog("Init Serial");
this->_serial->begin(9600, SERIAL_8N1, bsp->PMS5003.uart_rx_pin, this->_serial->begin(9600, SERIAL_8N1, bsp->Pms5003.uart_rx_pin,
bsp->PMS5003.uart_tx_pin); bsp->Pms5003.uart_tx_pin);
} else { } else {
if (bsp->SenseAirS8.supported == false) { if (bsp->SenseAirS8.supported == false) {
AgLog("Board [%d] PMS5003T_2 not supported", this->_boardDef); AgLog("Board [%d] PMS5003T_2 not supported", this->_boardDef);
@@ -101,7 +101,7 @@ bool PMS5003T::begin(void) {
} }
#endif #endif
this->_isInit = true; this->_isBegin = true;
return true; return true;
} }
@@ -138,7 +138,7 @@ int PMS5003T::pm25ToAQI(int pm02) {
* @return false Failure * @return false Failure
*/ */
bool PMS5003T::readData(void) { bool PMS5003T::readData(void) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return false; return false;
} }
@@ -207,9 +207,9 @@ float PMS5003T::getRelativeHumidity(void) {
* @return true Initialized * @return true Initialized
* @return false No-initialized * @return false No-initialized
*/ */
bool PMS5003T::checkInit(void) { bool PMS5003T::isBegin(void) {
if (this->_isInit == false) { if (this->_isBegin == false) {
AgLog("No initialized"); AgLog("Not-initialized");
return false; return false;
} }
return true; return true;
@@ -221,3 +221,16 @@ float PMS5003T::correctionTemperature(float inTemp) {
} }
return inTemp * 1.181f - 5.113f; return inTemp * 1.181f - 5.113f;
} }
void PMS5003T::end(void) {
if (_isBegin == false) {
return;
}
_isBegin = false;
#if defined(ESP8266)
_debugStream = NULL;
#else
delete _serial;
#endif
AgLog("De-initialize");
}

View File

@@ -2,10 +2,13 @@
#define _PMS5003T_H_ #define _PMS5003T_H_
#include <HardwareSerial.h> #include <HardwareSerial.h>
#include "../bsp/BoardDef.h" #include "../main/BoardDef.h"
#include "PMS.h" #include "PMS.h"
#include "Stream.h" #include "Stream.h"
/**
* @brief The class define how to handle PMS5003T sensor bas on @ref PMS class
*/
class PMS5003T { class PMS5003T {
public: public:
PMS5003T(BoardType def); PMS5003T(BoardType def);
@@ -14,6 +17,7 @@ public:
#else #else
bool begin(HardwareSerial &serial); bool begin(HardwareSerial &serial);
#endif #endif
void end(void);
bool readData(void); bool readData(void);
int getPm01Ae(void); int getPm01Ae(void);
@@ -25,7 +29,7 @@ public:
float getRelativeHumidity(void); float getRelativeHumidity(void);
private: private:
bool _isInit = false; bool _isBegin = false;
bool _isSleep = false; bool _isSleep = false;
BoardType _boardDef; BoardType _boardDef;
@@ -41,7 +45,7 @@ private:
int pm25ToAQI(int pm02); int pm25ToAQI(int pm02);
PMS pms; PMS pms;
PMS::DATA pmsData; PMS::DATA pmsData;
bool checkInit(void); bool isBegin(void);
float correctionTemperature(float inTemp); float correctionTemperature(float inTemp);
}; };

View File

@@ -6,7 +6,7 @@
#endif #endif
/** /**
* @brief Construct a new Sense Air S 8:: Sense Air S 8 object * @brief Construct a new Sense Air S8:: Sense Air S8 object
* *
* @param def * @param def
*/ */
@@ -18,7 +18,7 @@ S8::S8(BoardType def) : _boardDef(def) {}
* @return true = success, otherwise is failure * @return true = success, otherwise is failure
*/ */
bool S8::begin(void) { bool S8::begin(void) {
if (this->_isInit) { if (this->_isBegin) {
AgLog("Initialized, Call end() then try again"); AgLog("Initialized, Call end() then try again");
return true; return true;
} }
@@ -27,8 +27,8 @@ bool S8::begin(void) {
} }
/** /**
* @brief Init sensor has print debug log, if class create without serial debug * @brief Init S8 sensor, this methos should be call before other, if not it's
* before it's override last define * always return the failure status
* *
* @param _debugStream Serial print debug log, NULL if don't use * @param _debugStream Serial print debug log, NULL if don't use
* @return true = success, otherwise is failure * @return true = success, otherwise is failure
@@ -56,13 +56,12 @@ bool S8::begin(HardwareSerial &serial) {
* *
*/ */
void S8::end(void) { void S8::end(void) {
if (this->_isInit == false) { if (this->_isBegin == false) {
AgLog("Senor is not initialized");
return; return;
} }
// Deinit // Deinit
AgLog("De-Inititlized"); AgLog("De-Inititlize");
} }
/** /**
@@ -71,7 +70,7 @@ void S8::end(void) {
* @param firmver String buffer, len = 10 char * @param firmver String buffer, len = 10 char
*/ */
void S8::getFirmwareVersion(char firmver[]) { void S8::getFirmwareVersion(char firmver[]) {
if (this->isInit() == false) { if (this->isBegin() == false) {
return; return;
} }
@@ -103,7 +102,7 @@ void S8::getFirmwareVersion(char firmver[]) {
* @return int32_t Return ID * @return int32_t Return ID
*/ */
int32_t S8::getSensorTypeId(void) { int32_t S8::getSensorTypeId(void) {
if (this->isInit() == false) { if (this->isBegin() == false) {
return -1; return -1;
} }
@@ -149,7 +148,7 @@ int32_t S8::getSensorTypeId(void) {
* @return int32_t ID * @return int32_t ID
*/ */
int32_t S8::getSensorId(void) { int32_t S8::getSensorId(void) {
if (this->isInit() == false) { if (this->isBegin() == false) {
return -1; return -1;
} }
@@ -195,7 +194,7 @@ int32_t S8::getSensorId(void) {
* @return int16_t * @return int16_t
*/ */
int16_t S8::getMemoryMapVersion(void) { int16_t S8::getMemoryMapVersion(void) {
if (this->isInit() == false) { if (this->isBegin() == false) {
return -1; return -1;
} }
@@ -225,7 +224,7 @@ int16_t S8::getMemoryMapVersion(void) {
* @return int16_t (PPM), -1 if invalid. * @return int16_t (PPM), -1 if invalid.
*/ */
int16_t S8::getCo2(void) { int16_t S8::getCo2(void) {
if (this->isInit() == false) { if (this->isBegin() == false) {
return -1; return -1;
} }
@@ -286,7 +285,7 @@ bool S8::isBaseLineCalibrationDone(void) {
* @return int16_t PWM * @return int16_t PWM
*/ */
int16_t S8::getOutputPWM(void) { int16_t S8::getOutputPWM(void) {
if (this->isInit() == false) { if (this->isBegin() == false) {
return -1; return -1;
} }
@@ -318,7 +317,7 @@ int16_t S8::getOutputPWM(void) {
* @return int16_t Hour * @return int16_t Hour
*/ */
int16_t S8::getCalibPeriodABC(void) { int16_t S8::getCalibPeriodABC(void) {
if (this->isInit() == false) { if (this->isBegin() == false) {
return -1; return -1;
} }
@@ -350,7 +349,7 @@ int16_t S8::getCalibPeriodABC(void) {
* @return false Failure * @return false Failure
*/ */
bool S8::setCalibPeriodABC(int16_t period) { bool S8::setCalibPeriodABC(int16_t period) {
if (this->isInit() == false) { if (this->isBegin() == false) {
return false; return false;
} }
@@ -391,7 +390,7 @@ bool S8::setCalibPeriodABC(int16_t period) {
* @return false Failure * @return false Failure
*/ */
bool S8::manualCalib(void) { bool S8::manualCalib(void) {
if (this->isInit() == false) { if (this->isBegin() == false) {
return false; return false;
} }
@@ -416,7 +415,7 @@ bool S8::manualCalib(void) {
* @return int16_t Flags * @return int16_t Flags
*/ */
int16_t S8::getAcknowledgement(void) { int16_t S8::getAcknowledgement(void) {
if (this->isInit() == false) { if (this->isBegin() == false) {
return -1; return -1;
} }
@@ -446,7 +445,7 @@ int16_t S8::getAcknowledgement(void) {
* @return false Failure * @return false Failure
*/ */
bool S8::clearAcknowledgement(void) { bool S8::clearAcknowledgement(void) {
if (this->isInit() == false) { if (this->isBegin() == false) {
return false; return false;
} }
@@ -480,7 +479,7 @@ bool S8::clearAcknowledgement(void) {
* @return int16_t Alarm status * @return int16_t Alarm status
*/ */
int16_t S8::getAlarmStatus(void) { int16_t S8::getAlarmStatus(void) {
if (this->isInit() == false) { if (this->isBegin() == false) {
return -1; return -1;
} }
@@ -509,7 +508,7 @@ int16_t S8::getAlarmStatus(void) {
* @return S8::Status Sensor status * @return S8::Status Sensor status
*/ */
S8::Status S8::getStatus(void) { S8::Status S8::getStatus(void) {
if (this->isInit() == false) { if (this->isBegin() == false) {
return (Status)0; return (Status)0;
} }
@@ -538,7 +537,7 @@ S8::Status S8::getStatus(void) {
* @return int16_t Output status * @return int16_t Output status
*/ */
int16_t S8::getOutputStatus(void) { int16_t S8::getOutputStatus(void) {
if (this->isInit() == false) { if (this->isBegin() == false) {
return -1; return -1;
} }
@@ -569,7 +568,7 @@ int16_t S8::getOutputStatus(void) {
* @return false Failure * @return false Failure
*/ */
bool S8::sendSpecialCommand(CalibrationSpecialComamnd command) { bool S8::sendSpecialCommand(CalibrationSpecialComamnd command) {
if (this->isInit() == false) { if (this->isBegin() == false) {
return false; return false;
} }
@@ -656,16 +655,16 @@ bool S8::init(int txPin, int rxPin, uint32_t baud) {
/** Check communication by get firmware version */ /** Check communication by get firmware version */
char fwVers[11]; char fwVers[11];
this->_isInit = true; this->_isBegin = true;
this->getFirmwareVersion(fwVers); this->getFirmwareVersion(fwVers);
if (strlen(fwVers) == 0) { if (strlen(fwVers) == 0) {
this->_isInit = false; this->_isBegin = false;
return false; return false;
} }
AgLog("Firmware version: %s", fwVers); AgLog("Firmware version: %s", fwVers);
AgLog("Sensor successfully initialized. Heating up for 10s"); AgLog("Sensor successfully initialized. Heating up for 10s");
this->_isInit = true; this->_isBegin = true;
this->_lastInitTime = millis(); this->_lastInitTime = millis();
return true; return true;
} }
@@ -676,11 +675,11 @@ bool S8::init(int txPin, int rxPin, uint32_t baud) {
* @return true Initialized * @return true Initialized
* @return false No-Initialized * @return false No-Initialized
*/ */
bool S8::isInit(void) { bool S8::isBegin(void) {
if (this->_isInit) { if (this->_isBegin) {
return true; return true;
} }
AgLog("Sensor no-initialized"); AgLog("Sensor not-initialized");
return false; return false;
} }

View File

@@ -1,9 +1,12 @@
#ifndef _S8_H_ #ifndef _S8_H_
#define _S8_H_ #define _S8_H_
#include "../bsp/BoardDef.h" #include "../main/BoardDef.h"
#include "Arduino.h" #include "Arduino.h"
/**
* @brief The class define how to handle the senseair S8 sensor (CO2 sensor)
*/
class S8 { class S8 {
public: public:
const int S8_BAUDRATE = const int S8_BAUDRATE =
@@ -86,7 +89,7 @@ private:
#if defined(ESP32) #if defined(ESP32)
HardwareSerial *_serial; HardwareSerial *_serial;
#endif #endif
bool _isInit = false; bool _isBegin = false;
uint32_t _lastInitTime; uint32_t _lastInitTime;
bool isCalib = false; bool isCalib = false;
@@ -95,7 +98,7 @@ private:
bool init(const BoardDef *bsp); bool init(const BoardDef *bsp);
bool init(int txPin, int rxPin); bool init(int txPin, int rxPin);
bool init(int txPin, int rxPin, uint32_t baud); bool init(int txPin, int rxPin, uint32_t baud);
bool isInit(void); bool isBegin(void);
void uartWriteBytes(uint8_t size); // Send bytes to sensor void uartWriteBytes(uint8_t size); // Send bytes to sensor
uint8_t uint8_t

View File

@@ -7,6 +7,11 @@
#define vocAlgorithm() ((VOCGasIndexAlgorithm *)(this->_vocAlgorithm)) #define vocAlgorithm() ((VOCGasIndexAlgorithm *)(this->_vocAlgorithm))
#define noxAlgorithm() ((NOxGasIndexAlgorithm *)(this->_noxAlgorithm)) #define noxAlgorithm() ((NOxGasIndexAlgorithm *)(this->_noxAlgorithm))
/**
* @brief Construct a new Sgp 4 1:: Sgp 4 1 object
*
* @param type Board type @ref BoardType
*/
Sgp41::Sgp41(BoardType type) : _boardType(type) {} Sgp41::Sgp41(BoardType type) : _boardType(type) {}
/** /**
@@ -18,9 +23,13 @@ Sgp41::Sgp41(BoardType type) : _boardType(type) {}
* @return false Failure * @return false Failure
*/ */
bool Sgp41::begin(TwoWire &wire) { bool Sgp41::begin(TwoWire &wire) {
if (this->_isInit) { /** Ignore next step if initialized */
if (this->_isBegin) {
AgLog("Initialized, call end() then try again");
return true; return true;
} }
/** Check that board has supported this sensor */
if (this->boardSupported() == false) { if (this->boardSupported() == false) {
return false; return false;
} }
@@ -55,8 +64,8 @@ bool Sgp41::begin(TwoWire &wire) {
conditioningCount = 0; conditioningCount = 0;
#endif #endif
this->_isInit = true; this->_isBegin = true;
AgLog("Init"); AgLog("Initialize");
return true; return true;
} }
@@ -89,6 +98,10 @@ void Sgp41::handle(void) {
} }
#else #else
/**
* @brief Handle the sensor conditioning and run time udpate value, This method
* must not call, it's called on private task
*/
void Sgp41::_handle(void) { void Sgp41::_handle(void) {
/** NOx conditionning */ /** NOx conditionning */
uint16_t err; uint16_t err;
@@ -115,18 +128,26 @@ void Sgp41::_handle(void) {
} }
#endif #endif
/**
* @brief De-Initialize sensor
*/
void Sgp41::end(void) { void Sgp41::end(void) {
if (this->_isInit == false) { if (this->_isBegin == false) {
return; return;
} }
#ifdef ESP32 #ifdef ESP32
vTaskDelete(pollTask); vTaskDelete(pollTask);
#else
_debugStream = nullptr;
#endif #endif
this->_isInit = false; bsp = NULL;
this->_isBegin = false;
delete sgpSensor(); delete sgpSensor();
delete vocAlgorithm();
delete noxAlgorithm();
AgLog("De-Init"); AgLog("De-initialize");
} }
/** /**
@@ -153,6 +174,12 @@ int Sgp41::getNoxIndex(void) {
return nox; return nox;
} }
/**
* @brief Check that board has supported sensor
*
* @return true Supported
* @return false Not-supported
*/
bool Sgp41::boardSupported(void) { bool Sgp41::boardSupported(void) {
if (this->bsp == nullptr) { if (this->bsp == nullptr) {
this->bsp = getBoardDef(this->_boardType); this->bsp = getBoardDef(this->_boardType);
@@ -165,25 +192,19 @@ bool Sgp41::boardSupported(void) {
return true; return true;
} }
int Sgp41::sdaPin(void) { /**
if (this->bsp) { * @brief Get raw signal
return this->bsp->I2C.sda_pin; *
} * @param raw_voc Raw VOC output
AgLog("sdaPin(): board not supported I2C"); * @param row_nox Raw NOx output
return -1; * @param defaultRh
} * @param defaultT
* @return true Success
int Sgp41::sclPin(void) { * @return false Failure
if (this->bsp) { */
return this->bsp->I2C.scl_pin;
}
AgLog("sdlPin(): board not supported I2C");
return -1;
}
bool Sgp41::getRawSignal(uint16_t &raw_voc, uint16_t &row_nox, bool Sgp41::getRawSignal(uint16_t &raw_voc, uint16_t &row_nox,
uint16_t defaultRh, uint16_t defaultT) { uint16_t defaultRh, uint16_t defaultT) {
if (this->checkInit() == false) { if (this->isBegin() == false) {
return false; return false;
} }
@@ -195,43 +216,25 @@ bool Sgp41::getRawSignal(uint16_t &raw_voc, uint16_t &row_nox,
} }
/** /**
* @brief This command turns the hotplate off and stops the measurement. * @brief Check that sensor is initialized
* Subsequently, the sensor enters the idle mode. *
* @return true Initialized
* @return false Not-initialized
*/
bool Sgp41::isBegin(void) {
if (this->_isBegin) {
return true;
}
AgLog("Sensor not-initialized");
return false;
}
/**
* @brief Handle nox conditioning process
* *
* @return true Success * @return true Success
* @return false Failure * @return false Failure
*/ */
bool Sgp41::turnHeaterOff(void) {
if (this->checkInit() == false) {
return false;
}
if (sgpSensor()->turnHeaterOff() == 0) {
return true;
}
return false;
}
bool Sgp41::getSerialNumber(uint16_t serialNumbers[],
uint8_t serialNumberSize) {
if (this->checkInit() == false) {
return false;
}
if (sgpSensor()->getSerialNumber(serialNumbers) == 0) {
return true;
}
return false;
}
bool Sgp41::checkInit(void) {
if (this->_isInit) {
return true;
}
AgLog("Sensor no-initialized");
return false;
}
bool Sgp41::_noxConditioning(void) { bool Sgp41::_noxConditioning(void) {
uint16_t err; uint16_t err;
uint16_t srawVoc; uint16_t srawVoc;

View File

@@ -1,10 +1,15 @@
#ifndef _AIR_GRADIENT_SGP4X_H_ #ifndef _AIR_GRADIENT_SGP4X_H_
#define _AIR_GRADIENT_SGP4X_H_ #define _AIR_GRADIENT_SGP4X_H_
#include "../bsp/BoardDef.h" #include "../main/BoardDef.h"
#include <Arduino.h> #include <Arduino.h>
#include <Wire.h> #include <Wire.h>
/**
* @brief The class define how to handle Sensirion sensor SGP41 (VOC and NOx
* sensor)
*
*/
class Sgp41 { class Sgp41 {
public: public:
Sgp41(BoardType type); Sgp41(BoardType type);
@@ -22,7 +27,7 @@ public:
private: private:
bool onConditioning = true; bool onConditioning = true;
bool ready = false; bool ready = false;
bool _isInit = false; bool _isBegin = false;
void *_sensor; void *_sensor;
void *_vocAlgorithm; void *_vocAlgorithm;
void *_noxAlgorithm; void *_noxAlgorithm;
@@ -40,14 +45,10 @@ private:
#else #else
TaskHandle_t pollTask; TaskHandle_t pollTask;
#endif #endif
bool checkInit(void); bool isBegin(void);
bool boardSupported(void); bool boardSupported(void);
int sdaPin(void);
int sclPin(void);
bool getRawSignal(uint16_t &raw_voc, uint16_t &raw_nox, bool getRawSignal(uint16_t &raw_voc, uint16_t &raw_nox,
uint16_t defaultRh = 0x8000, uint16_t defaultT = 0x6666); uint16_t defaultRh = 0x8000, uint16_t defaultT = 0x6666);
bool turnHeaterOff(void);
bool getSerialNumber(uint16_t serialNumbers[], uint8_t serialNumberSize);
bool _noxConditioning(void); bool _noxConditioning(void);
}; };

View File

@@ -1,210 +0,0 @@
#include "sht4x.h"
#include "../library/SensirionSHT4x/src/SensirionI2CSht4x.h"
#define shtSensor() ((SensirionI2CSht4x *)(this->_sensor))
#if defined(ESP8266)
bool Sht::begin(TwoWire &wire, Stream &debugStream) {
this->_debugStream = &debugStream;
return this->begin(wire);
}
#else
#endif
Sht::Sht(BoardType type) : _boardType(type) {}
/**
* @brief Init sensor, Ifthis funciton not call the other funtion call will
* always return false or value invalid
*
* @param wire TwoWire instance, Must be initialized
* @return true Success
* @return false Failure
*/
bool Sht::begin(TwoWire &wire) {
if (this->_isInit) {
return true;
}
if (this->boardSupported() == false) {
return false;
}
this->_sensor = new SensirionI2CSht4x();
shtSensor()->begin(wire, SHT40_I2C_ADDR_44);
if (shtSensor()->softReset() != 0) {
AgLog("Reset sensor fail, look like sensor is not on I2C bus");
return false;
}
delay(10);
this->_isInit = true;
AgLog("Init");
return true;
}
void Sht::end(void) {
if (this->_isInit == false) {
return;
}
this->_isInit = false;
delete shtSensor();
}
/**
* @brief Get temperature degrees celsius
*
* @return float value <= 256.0f is invalid, That mean sensor has issue or
* communication to sensor not worked as well.
*/
float Sht::getTemperature(void) {
float temperature;
float humidity;
if (this->measureMediumPrecision(temperature, humidity)) {
return temperature;
}
return -256.0f;
}
/**
* @brief Get humidity
*
* @return float Percent(0 - 100), value < 0 is invalid.
*/
float Sht::getRelativeHumidity(void) {
float temperature;
float humidity;
if (this->measureMediumPrecision(temperature, humidity)) {
return humidity;
}
return -1.0f;
}
bool Sht::boardSupported(void) {
if (this->_bsp == NULL) {
this->_bsp = getBoardDef(this->_boardType);
}
if ((this->_bsp == NULL) || (this->_bsp->I2C.supported == false)) {
AgLog("Board not supported");
return false;
}
return true;
}
int Sht::sdaPin(void) {
if (this->_bsp) {
return this->_bsp->I2C.sda_pin;
}
AgLog("sdaPin(): board not supported I2C");
return -1;
}
int Sht::sclPin(void) {
if (this->_bsp) {
return this->_bsp->I2C.scl_pin;
}
AgLog("sdlPin(): board not supported I2C");
return -1;
}
bool Sht::checkInit(void) {
if (this->_isInit) {
return true;
}
AgLog("Sensor no-initialized");
return false;
}
bool Sht::measureHighPrecision(float &temperature, float &humidity) {
if (this->checkInit() == false) {
return false;
}
if (shtSensor()->measureHighPrecision(temperature, humidity) == 0) {
return true;
}
return false;
}
bool Sht::measureMediumPrecision(float &temperature, float &humidity) {
if (this->checkInit() == false) {
return false;
}
if (shtSensor()->measureMediumPrecision(temperature, humidity) == 0) {
return true;
}
return false;
}
bool Sht::measureLowestPrecision(float &temperature, float &humidity) {
if (this->checkInit() == false) {
return false;
}
if (shtSensor()->measureLowestPrecision(temperature, humidity) == 0) {
return true;
}
return false;
}
bool Sht::activateHighestHeaterPowerShort(float &temperature, float &humidity) {
if (this->checkInit() == false) {
return false;
}
if (shtSensor()->activateHighestHeaterPowerShort(temperature, humidity) ==
0) {
return true;
}
return false;
}
bool Sht::activateMediumHeaterPowerLong(float &temperature, float &humidity) {
if (this->checkInit() == false) {
return false;
}
if (shtSensor()->activateMediumHeaterPowerLong(temperature, humidity) == 0) {
return true;
}
return false;
}
bool Sht::activateLowestHeaterPowerLong(float &temperature, float &humidity) {
if (this->checkInit() == false) {
return false;
}
if (shtSensor()->activateLowestHeaterPowerLong(temperature, humidity) == 0) {
return true;
}
return false;
}
bool Sht::getSerialNumber(uint32_t &serialNumber) {
if (this->checkInit() == false) {
return false;
}
if (shtSensor()->serialNumber(serialNumber) == 0) {
return true;
}
return false;
}
bool Sht::softReset(void) {
if (this->checkInit() == false) {
return false;
}
if (shtSensor()->softReset() == 0) {
return true;
}
return false;
}

165
src/sht4x/sht4x.cpp Normal file
View File

@@ -0,0 +1,165 @@
#include "sht4x.h"
#include "../library/SensirionSHT4x/src/SensirionI2CSht4x.h"
/** Cast _sensor to SensirionI2CSht4x */
#define shtSensor() ((SensirionI2CSht4x *)(this->_sensor))
#if defined(ESP8266)
/**
* @brief Init sensor, Ifthis funciton not call the other funtion call will
* always return false or value invalid
*
* @param wire wire TwoWire instance, Must be initialized
* @param debugStream Point to debug Serial to print debug log
* @return true Sucecss
* @return false Failure
*/
bool Sht::begin(TwoWire &wire, Stream &debugStream) {
this->_debugStream = &debugStream;
return this->begin(wire);
}
#else
#endif
/**
* @brief Construct a new Sht:: Sht object
*
* @param type Board type @ref BoardType
*/
Sht::Sht(BoardType type) : _boardType(type) {}
/**
* @brief Init sensor, Ifthis funciton not call the other funtion call will
* always return false or value invalid
*
* @param wire TwoWire instance, Must be initialized
* @return true Success
* @return false Failure
*/
bool Sht::begin(TwoWire &wire) {
/** Ignore next step if sensor has intiialized */
if (this->_isBegin) {
AgLog("Initialized, call end() then try again");
return true;
}
/** Check sensor has supported on board */
if (this->boardSupported() == false) {
return false;
}
/** Create new SensirionI2CSht4x and init */
this->_sensor = new SensirionI2CSht4x();
shtSensor()->begin(wire, SHT40_I2C_ADDR_44);
if (shtSensor()->softReset() != 0) {
AgLog("Reset sensor fail, look like sensor is not on I2C bus");
return false;
}
delay(10);
this->_isBegin = true;
AgLog("Initialize");
return true;
}
/**
* @brief De-initialize SHT41 sensor
*
*/
void Sht::end(void) {
if (this->_isBegin == false) {
return;
}
this->_isBegin = false;
_bsp = NULL;
delete shtSensor();
#if defined(ESP8266)
_debugStream = nullptr;
#endif
AgLog("De-initialize");
}
/**
* @brief Get temperature degrees celsius
*
* @return float value <= 256.0f is invalid, That mean sensor has issue or
* communication to sensor not worked as well.
*/
float Sht::getTemperature(void) {
float temperature;
float humidity;
if (this->measureMediumPrecision(temperature, humidity)) {
return temperature;
}
return -256.0f;
}
/**
* @brief Get humidity
*
* @return float Percent(0 - 100), value < 0 is invalid.
*/
float Sht::getRelativeHumidity(void) {
float temperature;
float humidity;
if (this->measureMediumPrecision(temperature, humidity)) {
return humidity;
}
return -1.0f;
}
/**
* @brief Check sensor has supported by board
*
* @return true Supported
* @return false Not supported
*/
bool Sht::boardSupported(void) {
if (this->_bsp == NULL) {
this->_bsp = getBoardDef(this->_boardType);
}
if ((this->_bsp == NULL) || (this->_bsp->I2C.supported == false)) {
AgLog("Board not supported");
return false;
}
return true;
}
/**
* @brief Check that sensor has initialized
*
* @return true Initialized
* @return false Not-initialized
*/
bool Sht::isBegin(void) {
if (this->_isBegin) {
return true;
}
AgLog("Sensor not-initialized");
return false;
}
/**
* @brief Ge SHT41 temperature and humidity value with medium meaure precision
*
* @param temperature Read out temperarure
* @param humidity Read humidity
* @return true Success
* @return false Failure
*/
bool Sht::measureMediumPrecision(float &temperature, float &humidity) {
if (this->isBegin() == false) {
return false;
}
if (shtSensor()->measureMediumPrecision(temperature, humidity) == 0) {
return true;
}
return false;
}

View File

@@ -4,8 +4,12 @@
#include <Arduino.h> #include <Arduino.h>
#include <Wire.h> #include <Wire.h>
#include "../bsp/BoardDef.h" #include "../main/BoardDef.h"
/**
* @brief The class with define how to handle the Sensirion sensor SHT41
* (temperature and humidity sensor).
*/
class Sht { class Sht {
public: public:
#if defined(ESP8266) #if defined(ESP8266)
@@ -15,13 +19,12 @@ public:
Sht(BoardType type); Sht(BoardType type);
bool begin(TwoWire &wire); bool begin(TwoWire &wire);
void end(void); void end(void);
float getTemperature(void); float getTemperature(void);
float getRelativeHumidity(void); float getRelativeHumidity(void);
private: private:
BoardType _boardType; BoardType _boardType;
bool _isInit = false; bool _isBegin = false; /** Flag indicate that sensor initialized or not */
void *_sensor; void *_sensor;
const BoardDef *_bsp = NULL; const BoardDef *_bsp = NULL;
#if defined(ESP8266) #if defined(ESP8266)
@@ -29,21 +32,9 @@ private:
const char *TAG = "SHT4x"; const char *TAG = "SHT4x";
#else #else
#endif #endif
bool checkInit(void); bool isBegin(void);
bool boardSupported(void); bool boardSupported(void);
int sdaPin(void);
int sclPin(void);
bool measureHighPrecision(float &temperature, float &humidity);
bool measureMediumPrecision(float &temperature, float &humidity); bool measureMediumPrecision(float &temperature, float &humidity);
bool measureLowestPrecision(float &temperature, float &humidity);
bool activateHighestHeaterPowerShort(float &temperature, float &humidity);
bool activateMediumHeaterPowerLong(float &temperature, float &humidity);
bool activateLowestHeaterPowerLong(float &temperature, float &humidity);
bool getSerialNumber(uint32_t &serialNumber);
bool softReset(void);
}; };
#endif /** _AIR_GRADIENT_SHT_H_ */ #endif /** _AIR_GRADIENT_SHT_H_ */