mirror of
https://github.com/airgradienthq/arduino.git
synced 2025-06-26 16:21:33 +02:00
Compare commits
49 Commits
Author | SHA1 | Date | |
---|---|---|---|
f46c66a77f | |||
9c8ae315a0 | |||
3ef438412f | |||
ce1373141a | |||
aceecde7b6 | |||
6926abd6f7 | |||
15dec40dfc | |||
4a36cf0c13 | |||
ecc92a6824 | |||
3d243cb8ca | |||
471448a0f1 | |||
ea3e976232 | |||
87f2463233 | |||
49c7877ec3 | |||
be1a9778e6 | |||
ed1d45cea1 | |||
db31b39ce2 | |||
d92d312b0c | |||
6837529096 | |||
b94ae9eff0 | |||
1810c0f355 | |||
eb0f45750d | |||
9ae8fb2355 | |||
512509c2e2 | |||
66815f590c | |||
f60e9bbe3e | |||
f361e3c9a9 | |||
e76dcf07c8 | |||
e6fe489be7 | |||
9ddb606a00 | |||
cd5ee2da18 | |||
4c42a9ddc8 | |||
78b1b0975c | |||
d99881aa46 | |||
df937fe65f | |||
dc742d3c92 | |||
a7b2ad526f | |||
bb804b9f6a | |||
1a00073cf6 | |||
469d07a2d6 | |||
6cf5e31843 | |||
3f1da6387b | |||
99b4858f1d | |||
4374c980ec | |||
ded7637b06 | |||
6a79ab6b5b | |||
7baff75524 | |||
d421c94647 | |||
8fcf257726 |
11
.github/workflows/check.yml
vendored
11
.github/workflows/check.yml
vendored
@ -22,9 +22,9 @@ jobs:
|
||||
board_options: "JTAGAdapter=default,CDCOnBoot=cdc,PartitionScheme=default,CPUFreq=160,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=verbose,EraseFlash=none"
|
||||
core: "esp32:esp32@2.0.11"
|
||||
exclude:
|
||||
- example: "BASIC_v4"
|
||||
- example: "BASIC"
|
||||
fqbn: "esp32:esp32:esp32c3"
|
||||
- example: "ONE_I-9PSL"
|
||||
- example: "ONE"
|
||||
fqbn: "esp8266:esp8266:d1_mini"
|
||||
- example: "Open_Air"
|
||||
fqbn: "esp8266:esp8266:d1_mini"
|
||||
@ -40,6 +40,13 @@ jobs:
|
||||
WiFiManager@2.0.16-rc.2
|
||||
Arduino_JSON@0.2.0
|
||||
U8g2@2.34.22
|
||||
# In some cases, actions/checkout@v4 will check out a detached HEAD; for
|
||||
# example, this happens on pull request events, where an hypothetical
|
||||
# PR merge commit is checked out. This tends to confuse
|
||||
# `arduino-cli lib install --git-url`, making it fail with errors such as:
|
||||
# Error installing Git Library: Library install failed: object not found
|
||||
# Create and check out a dummy branch to work around this issue.
|
||||
- run: git checkout -b check
|
||||
- run: bin/arduino-cli --verbose lib install --git-url .
|
||||
env:
|
||||
ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL: "true"
|
||||
|
@ -35,7 +35,6 @@ If you have any questions or problems, check out [our forum](https://forum.airgr
|
||||
- [Sensirion Core](https://github.com/Sensirion/arduino-core/)
|
||||
- [Sensirion I2C SGP41](https://github.com/Sensirion/arduino-i2c-sgp41)
|
||||
- [Sensirion I2C SHT](https://github.com/Sensirion/arduino-sht)
|
||||
- [PMS](https://github.com/fu-hsi/pms)
|
||||
|
||||
## License
|
||||
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
|
@ -249,6 +249,8 @@ public:
|
||||
if ((retCode == 200) || (retCode == 429)) {
|
||||
serverFailed = false;
|
||||
return true;
|
||||
} else {
|
||||
Serial.printf("Post response failed code: %d\r\n", retCode);
|
||||
}
|
||||
serverFailed = true;
|
||||
return false;
|
||||
@ -382,7 +384,9 @@ bool hasSensorS8 = true;
|
||||
bool hasSensorPMS = true;
|
||||
bool hasSensorSHT = true;
|
||||
int pmFailCount = 0;
|
||||
AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL, updateServerConfiguration);
|
||||
int getCO2FailCount = 0;
|
||||
AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL,
|
||||
updateServerConfiguration);
|
||||
AgSchedule serverSchedule(SERVER_SYNC_INTERVAL, sendDataToServer);
|
||||
AgSchedule dispSchedule(DISP_UPDATE_INTERVAL, dispHandler);
|
||||
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update);
|
||||
@ -453,6 +457,9 @@ void loop() {
|
||||
}
|
||||
|
||||
updateWiFiConnect();
|
||||
|
||||
/** Read PMS on loop */
|
||||
ag.pms5003.handle();
|
||||
}
|
||||
|
||||
static void sendPing() {
|
||||
@ -591,7 +598,7 @@ static void updateServerConfiguration(void) {
|
||||
Serial.printf("abcDays config: %d days(%d hours)\r\n",
|
||||
agServer.getCo2AbcDaysConfig(), newHour);
|
||||
int curHour = ag.s8.getAbcPeriod();
|
||||
Serial.printf("Current config: %d (hours)\r\n", ag.s8.getAbcPeriod());
|
||||
Serial.printf("Current config: %d (hours)\r\n", curHour);
|
||||
if (curHour == newHour) {
|
||||
Serial.println("set 'abcDays' ignored");
|
||||
} else {
|
||||
@ -610,12 +617,22 @@ static void updateServerConfiguration(void) {
|
||||
}
|
||||
|
||||
static void co2Update() {
|
||||
co2Ppm = ag.s8.getCo2();
|
||||
Serial.printf("CO2 index: %d\r\n", co2Ppm);
|
||||
int value = ag.s8.getCo2();
|
||||
if (value >= 0) {
|
||||
co2Ppm = value;
|
||||
getCO2FailCount = 0;
|
||||
Serial.printf("CO2 index: %d\r\n", co2Ppm);
|
||||
} else {
|
||||
getCO2FailCount++;
|
||||
Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount);
|
||||
if (getCO2FailCount >= 3) {
|
||||
co2Ppm = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pmUpdate() {
|
||||
if (ag.pms5003.readData()) {
|
||||
if (ag.pms5003.isFailed() == false) {
|
||||
pm25 = ag.pms5003.getPm25Ae();
|
||||
Serial.printf("PMS2.5: %d\r\n", pm25);
|
||||
pmFailCount = 0;
|
||||
|
@ -162,7 +162,7 @@ public:
|
||||
config.inUSAQI = false;
|
||||
memset(config.models, 0, sizeof(config.models));
|
||||
memset(config.mqttBrokers, 0, sizeof(config.mqttBrokers));
|
||||
config.useRGBLedBar = UseLedBarOff;
|
||||
config.useRGBLedBar = UseLedBarCO2;
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
@ -243,15 +243,7 @@ public:
|
||||
uint8_t ledBarMode = UseLedBarOff;
|
||||
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;
|
||||
}
|
||||
ledBarMode = parseLedBarMode(mode);
|
||||
}
|
||||
|
||||
/** Get model */
|
||||
@ -338,6 +330,8 @@ public:
|
||||
if ((retCode == 200) || (retCode == 429)) {
|
||||
serverFailed = false;
|
||||
return true;
|
||||
} else {
|
||||
Serial.printf("Post response failed code: %d\r\n", retCode);
|
||||
}
|
||||
serverFailed = true;
|
||||
return false;
|
||||
@ -429,13 +423,12 @@ public:
|
||||
*/
|
||||
void showServerConfig(void) {
|
||||
Serial.println("Server configuration: ");
|
||||
Serial.printf(" inF: %s\r\n", config.inF ? "true" : "false");
|
||||
Serial.printf(" inUSAQI: %s\r\n",
|
||||
config.inUSAQI ? "true" : "false");
|
||||
Serial.printf(" useRGBLedBar: %d\r\n", (int)config.useRGBLedBar);
|
||||
Serial.printf(" Model: %s\r\n", config.models);
|
||||
Serial.printf(" Mqtt Broker: %s\r\n", config.mqttBrokers);
|
||||
Serial.printf(" S8 calib period: %d\r\n", co2AbcCalib);
|
||||
Serial.printf("inF: %s\r\n", config.inF ? "true" : "false");
|
||||
Serial.printf("inUSAQI: %s\r\n", config.inUSAQI ? "true" : "false");
|
||||
Serial.printf("useRGBLedBar: %d\r\n", (int)config.useRGBLedBar);
|
||||
Serial.printf("Model: %s\r\n", config.models);
|
||||
Serial.printf("MQTT Broker: %s\r\n", config.mqttBrokers);
|
||||
Serial.printf("S8 calibration period: %d\r\n", co2AbcCalib);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -445,6 +438,24 @@ public:
|
||||
*/
|
||||
UseLedBar getLedBarMode(void) { return (UseLedBar)config.useRGBLedBar; }
|
||||
|
||||
/**
|
||||
* @brief Return the name of the led bare mode.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getLedBarModeName(void) {
|
||||
UseLedBar ledBarMode = getLedBarMode();
|
||||
if (ledBarMode == UseLedBarOff) {
|
||||
return String("off");
|
||||
} else if (ledBarMode == UseLedBarPM) {
|
||||
return String("pm");
|
||||
} else if (ledBarMode == UseLedBarCO2) {
|
||||
return String("co2");
|
||||
} else {
|
||||
return String("off");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the Country
|
||||
*
|
||||
@ -484,6 +495,7 @@ private:
|
||||
if (EEPROM.readBytes(0, &config, sizeof(config)) != sizeof(config)) {
|
||||
config.inF = false;
|
||||
config.inUSAQI = false;
|
||||
config.useRGBLedBar = UseLedBarCO2; // default use LED bar for CO2
|
||||
memset(config.models, 0, sizeof(config.models));
|
||||
memset(config.mqttBrokers, 0, sizeof(config.mqttBrokers));
|
||||
|
||||
@ -514,6 +526,21 @@ private:
|
||||
EEPROM.commit();
|
||||
Serial.println("Save config");
|
||||
}
|
||||
|
||||
UseLedBar parseLedBarMode(String mode) {
|
||||
UseLedBar ledBarMode = UseLedBarOff;
|
||||
if (mode == "co2") {
|
||||
ledBarMode = UseLedBarCO2;
|
||||
} else if (mode == "pm") {
|
||||
ledBarMode = UseLedBarPM;
|
||||
} else if (mode == "off") {
|
||||
ledBarMode = UseLedBarOff;
|
||||
} else {
|
||||
ledBarMode = UseLedBarOff;
|
||||
}
|
||||
|
||||
return ledBarMode;
|
||||
}
|
||||
};
|
||||
AgServer agServer;
|
||||
|
||||
@ -566,19 +593,19 @@ public:
|
||||
/** init client */
|
||||
client = esp_mqtt_client_init(&config);
|
||||
if (client == NULL) {
|
||||
Serial.println("mqtt client init failed");
|
||||
Serial.println("MQTT client init failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Register event */
|
||||
if (esp_mqtt_client_register_event(client, MQTT_EVENT_ANY,
|
||||
mqtt_event_handler, NULL) != ESP_OK) {
|
||||
Serial.println("mqtt client register event failed");
|
||||
Serial.println("MQTT client register event failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (esp_mqtt_client_start(client) != ESP_OK) {
|
||||
Serial.println("mqtt client start failed");
|
||||
Serial.println("MQTT client start failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -669,6 +696,7 @@ static int dispSmState = APP_SM_NORMAL; /** Save LED SM */
|
||||
static int tvocIndex = -1;
|
||||
static int tvocRawIndex = -1;
|
||||
static int noxIndex = -1;
|
||||
static int noxRawIndex = -1;
|
||||
static int co2Ppm = -1;
|
||||
static int pm25 = -1;
|
||||
static int pm01 = -1;
|
||||
@ -702,6 +730,7 @@ static void webServerInit(void);
|
||||
static String getServerSyncData(bool localServer);
|
||||
static void createMqttTask(void);
|
||||
static void factoryConfigReset(void);
|
||||
static void wdgFeedUpdate(void);
|
||||
|
||||
/** Init schedule */
|
||||
bool hasSensorS8 = true;
|
||||
@ -710,7 +739,12 @@ bool hasSensorSGP = true;
|
||||
bool hasSensorSHT = true;
|
||||
int pmFailCount = 0;
|
||||
uint32_t factoryBtnPressTime = 0;
|
||||
String mdnsModelName = "";
|
||||
String mdnsModelName = "I-9PSL";
|
||||
int getCO2FailCount = 0;
|
||||
uint32_t addToDashboardTime;
|
||||
bool isAddToDashboard = true;
|
||||
bool offlineMode = false;
|
||||
|
||||
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, displayAndLedBarUpdate);
|
||||
AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL,
|
||||
updateServerConfiguration);
|
||||
@ -719,6 +753,7 @@ AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update);
|
||||
AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, pmUpdate);
|
||||
AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumUpdate);
|
||||
AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, tvocUpdate);
|
||||
AgSchedule wdgFeedSchedule(60000, wdgFeedUpdate);
|
||||
|
||||
void setup() {
|
||||
EEPROM.begin(512);
|
||||
@ -736,15 +771,19 @@ void setup() {
|
||||
u8g2.begin();
|
||||
|
||||
/** Show boot display */
|
||||
Serial.println("Firmware Version: "+ag.getVersion());
|
||||
displayShowText("One V9", "FW Ver: " + ag.getVersion(), "");
|
||||
Serial.println("Firmware Version: " + ag.getVersion());
|
||||
displayShowText("AirGradient ONE", "FW Version: ", ag.getVersion());
|
||||
|
||||
boardInit();
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
|
||||
/** Init sensor */
|
||||
boardInit();
|
||||
|
||||
/** Init AirGradient server */
|
||||
agServer.begin();
|
||||
if (agServer.getLedBarMode() == UseLedBarOff) {
|
||||
ag.ledBar.setEnable(false);
|
||||
}
|
||||
|
||||
/** Run LED test on start up */
|
||||
displayShowText("Press now for", "LED test &", "offline mode");
|
||||
@ -794,7 +833,11 @@ void setup() {
|
||||
dispSmHandler(APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED);
|
||||
ledSmHandler(APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED);
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
} else {
|
||||
ag.ledBar.setEnable(agServer.getLedBarMode() != UseLedBarOff);
|
||||
}
|
||||
} else {
|
||||
offlineMode = true;
|
||||
}
|
||||
|
||||
/** Show display Warning up */
|
||||
@ -828,11 +871,20 @@ void loop() {
|
||||
tvocSchedule.run();
|
||||
}
|
||||
|
||||
if (offlineMode) {
|
||||
wdgFeedSchedule.run();
|
||||
}
|
||||
|
||||
/** Check for handle WiFi reconnect */
|
||||
updateWiFiConnect();
|
||||
|
||||
/** factory reset handle */
|
||||
factoryConfigReset();
|
||||
|
||||
/** Read PMS on loop */
|
||||
if (hasSensorPMS) {
|
||||
ag.pms5003.handle();
|
||||
}
|
||||
}
|
||||
|
||||
static void setTestColor(char color) {
|
||||
@ -909,8 +961,18 @@ static void ledTest2Min(void) {
|
||||
}
|
||||
|
||||
static void co2Update(void) {
|
||||
co2Ppm = ag.s8.getCo2();
|
||||
Serial.printf("CO2 index: %d\r\n", co2Ppm);
|
||||
int value = ag.s8.getCo2();
|
||||
if (value >= 0) {
|
||||
co2Ppm = value;
|
||||
getCO2FailCount = 0;
|
||||
Serial.printf("CO2 (ppm): %d\r\n", co2Ppm);
|
||||
} else {
|
||||
getCO2FailCount++;
|
||||
Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount);
|
||||
if (getCO2FailCount >= 3) {
|
||||
co2Ppm = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void showNr(void) { Serial.println("Serial nr: " + getDevId()); }
|
||||
@ -1016,7 +1078,7 @@ void webServerMetricsGet(void) {
|
||||
add_metric_point("", String(tvocIndex));
|
||||
}
|
||||
if (tvocRawIndex >= 0) {
|
||||
add_metric("tvoc_raw_index",
|
||||
add_metric("tvoc_raw",
|
||||
"The raw input value to the Total Volatile Organic Compounds "
|
||||
"(TVOC) index as measured by the AirGradient SGP sensor",
|
||||
"gauge");
|
||||
@ -1029,6 +1091,13 @@ void webServerMetricsGet(void) {
|
||||
"gauge");
|
||||
add_metric_point("", String(noxIndex));
|
||||
}
|
||||
if (noxRawIndex >= 0) {
|
||||
add_metric("nox_raw",
|
||||
"The raw input value to the Nitrous Oxide (NOx) index as "
|
||||
"measured by the AirGradient SGP sensor",
|
||||
"gauge");
|
||||
add_metric_point("", String(noxRawIndex));
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSensorSHT) {
|
||||
@ -1036,7 +1105,7 @@ void webServerMetricsGet(void) {
|
||||
add_metric("temperature",
|
||||
"The ambient temperature as measured by the AirGradient SHT "
|
||||
"sensor, in degrees Celsius",
|
||||
"gauge", "degc");
|
||||
"gauge", "celsius");
|
||||
add_metric_point("", String(temp));
|
||||
}
|
||||
if (hum >= 0) {
|
||||
@ -1063,7 +1132,7 @@ void webServerHandler(void *param) {
|
||||
static void webServerInit(void) {
|
||||
String host = "airgradient_" + getDevId();
|
||||
if (!MDNS.begin(host)) {
|
||||
Serial.println("Init MDNS failed");
|
||||
Serial.println("Init mDNS failed");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1071,13 +1140,11 @@ static void webServerInit(void) {
|
||||
// Make it possible to query this device from Prometheus/OpenMetrics.
|
||||
webServer.on("/metrics", HTTP_GET, webServerMetricsGet);
|
||||
webServer.begin();
|
||||
MDNS.addService("http", "tcp", 80);
|
||||
if (agServer.getModelName().isEmpty() != true) {
|
||||
MDNS.addServiceTxt("http", "_tcp", "model", agServer.getModelName());
|
||||
mdnsModelName = agServer.getModelName();
|
||||
}
|
||||
MDNS.addServiceTxt("http", "_tcp", "serialno", getDevId());
|
||||
MDNS.addServiceTxt("http", "_tcp", "fw_ver", ag.getVersion());
|
||||
MDNS.addService("_airgradient", "_tcp", 80);
|
||||
MDNS.addServiceTxt("_airgradient", "_tcp", "model", mdnsModelName);
|
||||
MDNS.addServiceTxt("_airgradient", "_tcp", "serialno", getDevId());
|
||||
MDNS.addServiceTxt("_airgradient", "_tcp", "fw_ver", ag.getVersion());
|
||||
MDNS.addServiceTxt("_airgradient", "_tcp", "vendor", "AirGradient");
|
||||
|
||||
if (xTaskCreate(webServerHandler, "webserver", 1024 * 4, NULL, 5, NULL) !=
|
||||
pdTRUE) {
|
||||
@ -1108,18 +1175,33 @@ static String getServerSyncData(bool localServer) {
|
||||
root["pm10"] = pm10;
|
||||
}
|
||||
if (pm03PCount >= 0) {
|
||||
root["pm003_count"] = pm03PCount;
|
||||
if (localServer) {
|
||||
root["pm003Count"] = pm03PCount;
|
||||
} else {
|
||||
root["pm003_count"] = pm03PCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasSensorSGP) {
|
||||
if (tvocIndex >= 0) {
|
||||
root["tvoc_index"] = tvocIndex;
|
||||
if (localServer) {
|
||||
root["tvocIndex"] = tvocIndex;
|
||||
} else {
|
||||
root["tvoc_index"] = tvocIndex;
|
||||
}
|
||||
}
|
||||
if (tvocRawIndex >= 0) {
|
||||
root["tvoc_raw"] = tvocRawIndex;
|
||||
}
|
||||
if (noxIndex >= 0) {
|
||||
root["nox_index"] = noxIndex;
|
||||
if (localServer) {
|
||||
root["noxIndex"] = noxIndex;
|
||||
} else {
|
||||
root["nox_index"] = noxIndex;
|
||||
}
|
||||
}
|
||||
if (noxRawIndex >= 0) {
|
||||
root["nox_raw"] = noxRawIndex;
|
||||
}
|
||||
}
|
||||
if (hasSensorSHT) {
|
||||
@ -1132,6 +1214,11 @@ static String getServerSyncData(bool localServer) {
|
||||
}
|
||||
root["boot"] = bootCount;
|
||||
|
||||
if (localServer) {
|
||||
root["ledMode"] = agServer.getLedBarModeName();
|
||||
root["firmwareVersion"] = ag.getVersion();
|
||||
}
|
||||
|
||||
return JSON.stringify(root);
|
||||
}
|
||||
|
||||
@ -1152,9 +1239,9 @@ static void createMqttTask(void) {
|
||||
String topic = "airgradient/readings/" + getDevId();
|
||||
if (agMqtt.publish(topic.c_str(), syncData.c_str(),
|
||||
syncData.length())) {
|
||||
Serial.println("Mqtt sync success");
|
||||
Serial.println("MQTT sync success");
|
||||
} else {
|
||||
Serial.println("Mqtt sync failure");
|
||||
Serial.println("MQTT sync failure");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1218,6 +1305,13 @@ static void factoryConfigReset(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static void wdgFeedUpdate(void) {
|
||||
ag.watchdog.reset();
|
||||
Serial.println();
|
||||
Serial.println("External watchdog feed");
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
static void sendPing() {
|
||||
JSONVar root;
|
||||
root["wifi"] = WiFi.RSSI();
|
||||
@ -1317,7 +1411,7 @@ static void displayShowDashboard(String err) {
|
||||
u8g2.drawStr(105, 10, strBuf);
|
||||
}
|
||||
} else {
|
||||
Serial.println("Disp show error: " + err);
|
||||
Serial.println("Display show error: " + err);
|
||||
int strWidth = u8g2.getStrWidth(err.c_str());
|
||||
u8g2.drawStr((126 - strWidth) / 2, 10, err.c_str());
|
||||
|
||||
@ -1463,7 +1557,7 @@ static void connectToWifi() {
|
||||
ledSmState = APP_SM_WIFI_MANAGER_STA_CONNECTING;
|
||||
});
|
||||
|
||||
displayShowText("Connecting to", "config WiFi", "...");
|
||||
displayShowText("Connecting to", "WiFi", "...");
|
||||
wifiManager.autoConnect(wifiSSID.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT);
|
||||
xTaskCreate(
|
||||
[](void *obj) {
|
||||
@ -1734,25 +1828,28 @@ static void updateServerConfiguration(void) {
|
||||
if (hasSensorS8) {
|
||||
co2Calibration();
|
||||
} else {
|
||||
Serial.println("CO2 S8 not available, calib ignored");
|
||||
Serial.println("CO2 S8 not available, calibration ignored");
|
||||
}
|
||||
}
|
||||
|
||||
// Update LED bar
|
||||
ag.ledBar.setEnable(agServer.getLedBarMode() != UseLedBarOff);
|
||||
|
||||
if (agServer.getCo2AbcDaysConfig() > 0) {
|
||||
if (hasSensorS8) {
|
||||
int newHour = agServer.getCo2AbcDaysConfig() * 24;
|
||||
Serial.printf("abcDays config: %d days(%d hours)\r\n",
|
||||
Serial.printf("Requested abcDays setting: %d days (%d hours)\r\n",
|
||||
agServer.getCo2AbcDaysConfig(), newHour);
|
||||
int curHour = ag.s8.getAbcPeriod();
|
||||
Serial.printf("Current config: %d (hours)\r\n", ag.s8.getAbcPeriod());
|
||||
Serial.printf("Current S8 abcDays setting: %d (hours)\r\n", curHour);
|
||||
if (curHour == newHour) {
|
||||
Serial.println("set 'abcDays' ignored");
|
||||
Serial.println("'abcDays' unchanged");
|
||||
} else {
|
||||
if (ag.s8.setAbcPeriod(agServer.getCo2AbcDaysConfig() * 24) ==
|
||||
false) {
|
||||
Serial.println("Set S8 abcDays period calib failed");
|
||||
Serial.println("Set S8 abcDays period failed");
|
||||
} else {
|
||||
Serial.println("Set S8 abcDays period calib success");
|
||||
Serial.println("Set S8 abcDays period success");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -1777,17 +1874,12 @@ static void updateServerConfiguration(void) {
|
||||
mqttTask = NULL;
|
||||
}
|
||||
if (agMqtt.begin(mqttUri)) {
|
||||
Serial.println("Connect to new mqtt broker success");
|
||||
Serial.println("Connect to MQTT broker successful");
|
||||
createMqttTask();
|
||||
} else {
|
||||
Serial.println("Connect to new mqtt broker failed");
|
||||
Serial.println("Connect to MQTT broker failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (mdnsModelName != agServer.getModelName()) {
|
||||
MDNS.addServiceTxt("http", "_tcp", "model", agServer.getModelName());
|
||||
mdnsModelName = agServer.getModelName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2088,7 +2180,16 @@ static void dispSmHandler(int sm) {
|
||||
break;
|
||||
}
|
||||
case APP_SM_SENSOR_CONFIG_FAILED: {
|
||||
displayShowDashboard("Add to Dashboard");
|
||||
uint32_t ms = (uint32_t)(millis() - addToDashboardTime);
|
||||
if (ms >= 5000) {
|
||||
addToDashboardTime = millis();
|
||||
if (isAddToDashboard) {
|
||||
displayShowDashboard("Add to Dashboard");
|
||||
} else {
|
||||
displayShowDashboard(getDevId());
|
||||
}
|
||||
isAddToDashboard = !isAddToDashboard;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case APP_SM_NORMAL: {
|
||||
@ -2110,9 +2211,11 @@ static void sensorLedColorHandler(void) {
|
||||
case UseLedBarPM:
|
||||
setRGBledPMcolor(pm25);
|
||||
break;
|
||||
case UseLedBarOff:
|
||||
ag.ledBar.clear();
|
||||
break;
|
||||
default:
|
||||
ag.ledBar.setColor(0, 0, 0, ag.ledBar.getNumberOfLeds() - 1);
|
||||
ag.ledBar.setColor(0, 0, 0, ag.ledBar.getNumberOfLeds() - 2);
|
||||
ag.ledBar.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -2187,11 +2290,13 @@ static void tvocUpdate(void) {
|
||||
tvocIndex = ag.sgp41.getTvocIndex();
|
||||
tvocRawIndex = ag.sgp41.getTvocRaw();
|
||||
noxIndex = ag.sgp41.getNoxIndex();
|
||||
noxRawIndex = ag.sgp41.getNoxRaw();
|
||||
|
||||
Serial.println();
|
||||
Serial.printf(" TVOC index: %d\r\n", tvocIndex);
|
||||
Serial.printf("TVOC raw index: %d\r\n", tvocRawIndex);
|
||||
Serial.printf(" NOx index: %d\r\n", noxIndex);
|
||||
Serial.printf("TVOC index: %d\r\n", tvocIndex);
|
||||
Serial.printf("TVOC raw: %d\r\n", tvocRawIndex);
|
||||
Serial.printf("NOx index: %d\r\n", noxIndex);
|
||||
Serial.printf("NOx raw: %d\r\n", noxRawIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2199,21 +2304,21 @@ static void tvocUpdate(void) {
|
||||
*
|
||||
*/
|
||||
static void pmUpdate(void) {
|
||||
if (ag.pms5003.readData()) {
|
||||
if (ag.pms5003.isFailed() == false) {
|
||||
pm01 = ag.pms5003.getPm01Ae();
|
||||
pm25 = ag.pms5003.getPm25Ae();
|
||||
pm10 = ag.pms5003.getPm10Ae();
|
||||
pm03PCount = ag.pms5003.getPm03ParticleCount();
|
||||
|
||||
Serial.println();
|
||||
Serial.printf(" PMS0.1: %d\r\n", pm01);
|
||||
Serial.printf(" PMS2.5: %d\r\n", pm25);
|
||||
Serial.printf(" PMS10.0: %d\r\n", pm10);
|
||||
Serial.printf("PMS3.0 Count: %d\r\n", pm03PCount);
|
||||
Serial.printf("PM1 ug/m3: %d\r\n", pm01);
|
||||
Serial.printf("PM2.5 ug/m3: %d\r\n", pm25);
|
||||
Serial.printf("PM10 ug/m3: %d\r\n", pm10);
|
||||
Serial.printf("PM0.3 Count: %d\r\n", pm03PCount);
|
||||
pmFailCount = 0;
|
||||
} else {
|
||||
pmFailCount++;
|
||||
Serial.printf("PM read failed: %d\r\n", pmFailCount);
|
||||
Serial.printf("PMS read failed: %d\r\n", pmFailCount);
|
||||
if (pmFailCount >= 3) {
|
||||
pm01 = -1;
|
||||
pm25 = -1;
|
||||
@ -2245,10 +2350,15 @@ static void tempHumUpdate(void) {
|
||||
temp = ag.sht.getTemperature();
|
||||
hum = ag.sht.getRelativeHumidity();
|
||||
|
||||
Serial.printf("Temperature: %0.2f\r\n", temp);
|
||||
Serial.printf(" Humidity: %d\r\n", hum);
|
||||
Serial.printf("Temperature in C: %0.2f\r\n", temp);
|
||||
Serial.printf("Relative Humidity: %d\r\n", hum);
|
||||
|
||||
// Update compensation temperature and humidity for SGP41
|
||||
if (hasSensorSGP) {
|
||||
ag.sgp41.setCompensationTemperatureHumidity(temp, hum);
|
||||
}
|
||||
} else {
|
||||
Serial.println("Measure SHT failed");
|
||||
Serial.println("SHT read failed");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ enum {
|
||||
phone */
|
||||
APP_SM_WIFI_MANAGER_STA_CONNECTING, /** After SSID and PW entered and OK
|
||||
clicked, connection to WiFI network is
|
||||
attempted*/
|
||||
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 */
|
||||
@ -77,9 +77,9 @@ enum {
|
||||
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 reachabFirmware nodele but there is some
|
||||
configuration issue to be fixed on the server
|
||||
side */
|
||||
APP_SM_SENSOR_CONFIG_FAILED, /** Server is reachabFirmware nodele but there is
|
||||
some configuration issue to be fixed on the
|
||||
server side */
|
||||
APP_SM_NORMAL,
|
||||
};
|
||||
|
||||
@ -244,15 +244,7 @@ public:
|
||||
uint8_t ledBarMode = UseLedBarOff;
|
||||
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;
|
||||
}
|
||||
ledBarMode = parseLedBarMode(mode);
|
||||
}
|
||||
|
||||
/** Get model */
|
||||
@ -339,6 +331,8 @@ public:
|
||||
if ((retCode == 200) || (retCode == 429)) {
|
||||
serverFailed = false;
|
||||
return true;
|
||||
} else {
|
||||
Serial.printf("Post response failed code: %d\r\n", retCode);
|
||||
}
|
||||
serverFailed = true;
|
||||
return false;
|
||||
@ -430,13 +424,12 @@ public:
|
||||
*/
|
||||
void showServerConfig(void) {
|
||||
Serial.println("Server configuration: ");
|
||||
Serial.printf(" inF: %s\r\n", config.inF ? "true" : "false");
|
||||
Serial.printf(" inUSAQI: %s\r\n",
|
||||
config.inUSAQI ? "true" : "false");
|
||||
Serial.printf(" useRGBLedBar: %d\r\n", (int)config.useRGBLedBar);
|
||||
Serial.printf(" Model: %s\r\n", config.models);
|
||||
Serial.printf(" Mqtt Broker: %s\r\n", config.mqttBrokers);
|
||||
Serial.printf(" S8 calib period: %d\r\n", co2AbcCalib);
|
||||
Serial.printf("inF: %s\r\n", config.inF ? "true" : "false");
|
||||
Serial.printf("inUSAQI: %s\r\n", config.inUSAQI ? "true" : "false");
|
||||
Serial.printf("useRGBLedBar: %d\r\n", (int)config.useRGBLedBar);
|
||||
Serial.printf("Model: %s\r\n", config.models);
|
||||
Serial.printf("MQTT Broker: %s\r\n", config.mqttBrokers);
|
||||
Serial.printf("S8 calibration period: %d\r\n", co2AbcCalib);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -446,6 +439,24 @@ public:
|
||||
*/
|
||||
UseLedBar getLedBarMode(void) { return (UseLedBar)config.useRGBLedBar; }
|
||||
|
||||
/**
|
||||
* @brief Return the name of the led bare mode.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getLedBarModeName(void) {
|
||||
UseLedBar ledBarMode = getLedBarMode();
|
||||
if (ledBarMode == UseLedBarOff) {
|
||||
return String("off");
|
||||
} else if (ledBarMode == UseLedBarPM) {
|
||||
return String("pm");
|
||||
} else if (ledBarMode == UseLedBarCO2) {
|
||||
return String("co2");
|
||||
} else {
|
||||
return String("off");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the Country
|
||||
*
|
||||
@ -515,6 +526,20 @@ private:
|
||||
EEPROM.commit();
|
||||
Serial.println("Save config");
|
||||
}
|
||||
|
||||
UseLedBar parseLedBarMode(String mode) {
|
||||
UseLedBar ledBarMode = UseLedBarOff;
|
||||
if (mode == "co2") {
|
||||
ledBarMode = UseLedBarCO2;
|
||||
} else if (mode == "pm") {
|
||||
ledBarMode = UseLedBarPM;
|
||||
} else if (mode == "off") {
|
||||
ledBarMode = UseLedBarOff;
|
||||
} else {
|
||||
ledBarMode = UseLedBarOff;
|
||||
}
|
||||
return ledBarMode;
|
||||
}
|
||||
};
|
||||
AgServer agServer;
|
||||
|
||||
@ -567,19 +592,19 @@ public:
|
||||
/** init client */
|
||||
client = esp_mqtt_client_init(&config);
|
||||
if (client == NULL) {
|
||||
Serial.println("mqtt client init failed");
|
||||
Serial.println("MQTT client init failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Register event */
|
||||
if (esp_mqtt_client_register_event(client, MQTT_EVENT_ANY,
|
||||
mqtt_event_handler, NULL) != ESP_OK) {
|
||||
Serial.println("mqtt client register event failed");
|
||||
Serial.println("MQTT client register event failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (esp_mqtt_client_start(client) != ESP_OK) {
|
||||
Serial.println("mqtt client start failed");
|
||||
Serial.println("MQTT client start failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -666,7 +691,8 @@ WebServer webServer;
|
||||
int tvocIndex = -1;
|
||||
int tvocRawIndex = -1;
|
||||
int noxIndex = -1;
|
||||
int co2Ppm = 0;
|
||||
int noxRawIndex = -1;
|
||||
int co2Ppm = -1;
|
||||
|
||||
int pm25_1 = -1;
|
||||
int pm01_1 = -1;
|
||||
@ -700,7 +726,8 @@ const int targetCount = 20;
|
||||
enum {
|
||||
FW_MODE_PST, /** PMS5003T, S8 and SGP41 */
|
||||
FW_MODE_PPT, /** PMS5003T_1, PMS5003T_2, SGP41 */
|
||||
FW_MODE_PP /** PMS5003T_1, PMS5003T_2 */
|
||||
FW_MODE_PP, /** PMS5003T_1, PMS5003T_2 */
|
||||
FW_MDOE_PS /** PMS5003T, S8 */
|
||||
};
|
||||
int fw_mode = FW_MODE_PST;
|
||||
|
||||
@ -720,6 +747,7 @@ static void webServerInit(void);
|
||||
static String getServerSyncData(bool localServer);
|
||||
static void createMqttTask(void);
|
||||
static void factoryConfigReset(void);
|
||||
static void wdgFeedUpdate(void);
|
||||
|
||||
bool hasSensorS8 = true;
|
||||
bool hasSensorPMS1 = true;
|
||||
@ -727,12 +755,16 @@ bool hasSensorPMS2 = true;
|
||||
bool hasSensorSGP = true;
|
||||
uint32_t factoryBtnPressTime = 0;
|
||||
String mdnsModelName = "";
|
||||
int getCO2FailCount = 0;
|
||||
bool offlineMode = false;
|
||||
|
||||
AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL,
|
||||
updateServerConfiguration);
|
||||
AgSchedule serverSchedule(SERVER_SYNC_INTERVAL, sendDataToServer);
|
||||
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update);
|
||||
AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, pmUpdate);
|
||||
AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, tvocUpdate);
|
||||
AgSchedule wdgFeedSchedule(60000, wdgFeedUpdate);
|
||||
|
||||
void setup() {
|
||||
EEPROM.begin(512);
|
||||
@ -771,6 +803,8 @@ void setup() {
|
||||
ledSmHandler(APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED);
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
}
|
||||
} else {
|
||||
offlineMode = true;
|
||||
}
|
||||
|
||||
ledSmHandler(APP_SM_NORMAL);
|
||||
@ -798,6 +832,17 @@ void loop() {
|
||||
updateWiFiConnect();
|
||||
|
||||
factoryConfigReset();
|
||||
|
||||
if (hasSensorPMS1) {
|
||||
ag.pms5003t_1.handle();
|
||||
}
|
||||
if (hasSensorPMS2) {
|
||||
ag.pms5003t_2.handle();
|
||||
}
|
||||
|
||||
if (offlineMode) {
|
||||
wdgFeedSchedule.run();
|
||||
}
|
||||
}
|
||||
|
||||
void sendPing() {
|
||||
@ -906,45 +951,92 @@ void boardInit(void) {
|
||||
failedHandler("Init I2C failed");
|
||||
}
|
||||
|
||||
Serial.println("Firmware Version: "+ag.getVersion());
|
||||
Serial.println("Firmware Version: " + ag.getVersion());
|
||||
|
||||
ag.watchdog.begin();
|
||||
ag.button.begin();
|
||||
ag.statusLed.begin();
|
||||
|
||||
/** detect sensor: PMS5003, PMS5003T, SGP41 and S8 */
|
||||
/**
|
||||
* Serial1 and Serial0 is use for connect S8 and PM sensor or both PM
|
||||
*/
|
||||
bool serial1Available = true;
|
||||
bool serial0Available = true;
|
||||
|
||||
if (ag.s8.begin(Serial1) == false) {
|
||||
hasSensorS8 = false;
|
||||
|
||||
Serial.println("CO2 S8 sensor not found");
|
||||
Serial.println("Can not detect S8 run mode 'PPT'");
|
||||
fw_mode = FW_MODE_PPT;
|
||||
|
||||
/** De-initialize Serial1 */
|
||||
Serial1.end();
|
||||
delay(200);
|
||||
Serial.println("Can not detect S8 on Serial1, try on Serial0");
|
||||
/** Check on other port */
|
||||
if (ag.s8.begin(Serial0) == false) {
|
||||
hasSensorS8 = false;
|
||||
|
||||
Serial.println("CO2 S8 sensor not found");
|
||||
Serial.println("Can not detect S8 run mode 'PPT'");
|
||||
fw_mode = FW_MODE_PPT;
|
||||
|
||||
Serial0.end();
|
||||
delay(200);
|
||||
} else {
|
||||
Serial.println("Found S8 on Serial0");
|
||||
serial0Available = false;
|
||||
}
|
||||
} else {
|
||||
Serial.println("Found S8 on Serial1");
|
||||
serial1Available = false;
|
||||
}
|
||||
|
||||
if (ag.sgp41.begin(Wire) == false) {
|
||||
hasSensorSGP = false;
|
||||
Serial.println("SGP sensor not found");
|
||||
|
||||
Serial.println("Can not detect SGP run mode 'PP'");
|
||||
fw_mode = FW_MODE_PP;
|
||||
}
|
||||
|
||||
if (ag.pms5003t_1.begin(Serial0) == false) {
|
||||
hasSensorPMS1 = false;
|
||||
Serial.println("PMS1 sensor not found");
|
||||
|
||||
if (ag.pms5003t_2.begin(Serial1) == false) {
|
||||
hasSensorPMS2 = false;
|
||||
Serial.println("PMS2 sensor not found");
|
||||
if (hasSensorS8 == false) {
|
||||
fw_mode = FW_MODE_PP;
|
||||
Serial.println("Can not detect SGP run mode 'O-1PP'");
|
||||
} else {
|
||||
Serial.println("Can not detect SGP run mode 'O-1PS'");
|
||||
fw_mode = FW_MDOE_PS;
|
||||
}
|
||||
}
|
||||
|
||||
if (fw_mode != FW_MODE_PST) {
|
||||
/** Try to find the PMS on other difference port with S8 */
|
||||
if (fw_mode == FW_MODE_PST) {
|
||||
bool pmInitSuccess = false;
|
||||
if (serial0Available) {
|
||||
if (ag.pms5003t_1.begin(Serial0) == false) {
|
||||
hasSensorPMS1 = false;
|
||||
Serial.println("PMS1 sensor not found");
|
||||
} else {
|
||||
serial0Available = false;
|
||||
pmInitSuccess = true;
|
||||
Serial.println("Found PMS 1 on Serial0");
|
||||
}
|
||||
}
|
||||
if (pmInitSuccess == false) {
|
||||
if (serial1Available) {
|
||||
if (ag.pms5003t_1.begin(Serial1) == false) {
|
||||
hasSensorPMS1 = false;
|
||||
Serial.println("PMS1 sensor not found");
|
||||
} else {
|
||||
serial1Available = false;
|
||||
Serial.println("Found PMS 1 on Serial1");
|
||||
}
|
||||
}
|
||||
}
|
||||
hasSensorPMS2 = false; // Disable PM2
|
||||
} else {
|
||||
if (ag.pms5003t_1.begin(Serial0) == false) {
|
||||
hasSensorPMS1 = false;
|
||||
Serial.println("PMS1 sensor not found");
|
||||
} else {
|
||||
Serial.println("Found PMS 1 on Serial0");
|
||||
}
|
||||
if (ag.pms5003t_2.begin(Serial1) == false) {
|
||||
hasSensorPMS2 = false;
|
||||
Serial.println("PMS2 sensor not found");
|
||||
} else {
|
||||
Serial.println("Found PMS 2 on Serial1");
|
||||
}
|
||||
}
|
||||
|
||||
@ -956,6 +1048,21 @@ void boardInit(void) {
|
||||
}
|
||||
|
||||
Serial.printf("Firmware Mode: %s\r\n", getFwMode(fw_mode));
|
||||
switch (fw_mode) {
|
||||
case FW_MODE_PP:
|
||||
mdnsModelName = "O-1PP";
|
||||
break;
|
||||
case FW_MODE_PPT:
|
||||
mdnsModelName = "O-1PPT";
|
||||
break;
|
||||
case FW_MODE_PST:
|
||||
mdnsModelName = "O-1PST";
|
||||
break;
|
||||
case FW_MDOE_PS:
|
||||
mdnsModelName = "0-1PS";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void failedHandler(String msg) {
|
||||
@ -968,7 +1075,7 @@ void failedHandler(String msg) {
|
||||
void co2Calibration(void) {
|
||||
/** Count down for co2CalibCountdown secs */
|
||||
for (int i = 0; i < SENSOR_CO2_CALIB_COUNTDOWN_MAX; i++) {
|
||||
Serial.printf("Start CO2 calib after %d sec\r\n",
|
||||
Serial.printf("Start CO2 calibration after %d sec\r\n",
|
||||
SENSOR_CO2_CALIB_COUNTDOWN_MAX - i);
|
||||
delay(1000);
|
||||
}
|
||||
@ -976,16 +1083,16 @@ void co2Calibration(void) {
|
||||
if (ag.s8.setBaselineCalibration()) {
|
||||
Serial.println("Calibration success");
|
||||
delay(1000);
|
||||
Serial.println("Wait for calib finish...");
|
||||
Serial.println("Wait for calibration to finish...");
|
||||
int count = 0;
|
||||
while (ag.s8.isBaseLineCalibrationDone() == false) {
|
||||
delay(1000);
|
||||
count++;
|
||||
}
|
||||
Serial.printf("Calib finish after %d sec\r\n", count);
|
||||
Serial.printf("Calibration finished after %d sec\r\n", count);
|
||||
delay(2000);
|
||||
} else {
|
||||
Serial.println("Calibration failure!!!");
|
||||
Serial.println("Calibration failure");
|
||||
delay(2000);
|
||||
}
|
||||
}
|
||||
@ -1007,7 +1114,7 @@ static void updateWiFiConnect(void) {
|
||||
lastRetry = millis();
|
||||
WiFi.reconnect();
|
||||
|
||||
Serial.printf("Re-Connect WiFi\r\n");
|
||||
Serial.printf("Re-Connect to WiFi\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1019,11 +1126,13 @@ static void tvocUpdate(void) {
|
||||
tvocIndex = ag.sgp41.getTvocIndex();
|
||||
tvocRawIndex = ag.sgp41.getTvocRaw();
|
||||
noxIndex = ag.sgp41.getNoxIndex();
|
||||
noxRawIndex = ag.sgp41.getNoxRaw();
|
||||
|
||||
Serial.println();
|
||||
Serial.printf(" TVOC index: %d\r\n", tvocIndex);
|
||||
Serial.printf("TVOC raw index: %d\r\n", tvocRawIndex);
|
||||
Serial.printf(" NOx index: %d\r\n", noxIndex);
|
||||
Serial.printf("TVOC index: %d\r\n", tvocIndex);
|
||||
Serial.printf("TVOC raw: %d\r\n", tvocRawIndex);
|
||||
Serial.printf("NOx index: %d\r\n", noxIndex);
|
||||
Serial.printf("NOx raw: %d\r\n", noxRawIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1033,7 +1142,7 @@ static void tvocUpdate(void) {
|
||||
static void pmUpdate(void) {
|
||||
bool pmsResult_1 = false;
|
||||
bool pmsResult_2 = false;
|
||||
if (hasSensorPMS1 && ag.pms5003t_1.readData()) {
|
||||
if (hasSensorPMS1 && (ag.pms5003t_1.isFailed() == false)) {
|
||||
pm01_1 = ag.pms5003t_1.getPm01Ae();
|
||||
pm25_1 = ag.pms5003t_1.getPm25Ae();
|
||||
pm10_1 = ag.pms5003t_1.getPm10Ae();
|
||||
@ -1044,12 +1153,12 @@ static void pmUpdate(void) {
|
||||
pmsResult_1 = true;
|
||||
|
||||
Serial.println();
|
||||
Serial.printf("[1] PMS0.1: %d\r\n", pm01_1);
|
||||
Serial.printf("[1] PMS2.5: %d\r\n", pm25_1);
|
||||
Serial.printf("[1] PMS10.0: %d\r\n", pm10_1);
|
||||
Serial.printf("[1]PMS3.0 Count: %d\r\n", pm03PCount_1);
|
||||
Serial.printf("[1] Temperature: %0.2f\r\n", temp_1);
|
||||
Serial.printf("[1] Humidity: %d\r\n", hum_1);
|
||||
Serial.printf("[1] PM1 ug/m3: %d\r\n", pm01_1);
|
||||
Serial.printf("[1] PM2.5 ug/m3: %d\r\n", pm25_1);
|
||||
Serial.printf("[1] PM10 ug/m3: %d\r\n", pm10_1);
|
||||
Serial.printf("[1] PM3.0 Count: %d\r\n", pm03PCount_1);
|
||||
Serial.printf("[1] Temperature in C: %0.2f\r\n", temp_1);
|
||||
Serial.printf("[1] Relative Humidity: %d\r\n", hum_1);
|
||||
} else {
|
||||
pm01_1 = -1;
|
||||
pm25_1 = -1;
|
||||
@ -1059,7 +1168,7 @@ static void pmUpdate(void) {
|
||||
hum_1 = -1;
|
||||
}
|
||||
|
||||
if (hasSensorPMS2 && ag.pms5003t_2.readData()) {
|
||||
if (hasSensorPMS2 && (ag.pms5003t_2.isFailed() == false)) {
|
||||
pm01_2 = ag.pms5003t_2.getPm01Ae();
|
||||
pm25_2 = ag.pms5003t_2.getPm25Ae();
|
||||
pm10_2 = ag.pms5003t_2.getPm10Ae();
|
||||
@ -1070,12 +1179,12 @@ static void pmUpdate(void) {
|
||||
pmsResult_2 = true;
|
||||
|
||||
Serial.println();
|
||||
Serial.printf("[2] PMS0.1: %d\r\n", pm01_2);
|
||||
Serial.printf("[2] PMS2.5: %d\r\n", pm25_2);
|
||||
Serial.printf("[2] PMS10.0: %d\r\n", pm10_2);
|
||||
Serial.printf("[2]PMS3.0 Count: %d\r\n", pm03PCount_2);
|
||||
Serial.printf("[2] Temperature: %0.2f\r\n", temp_2);
|
||||
Serial.printf("[2] Humidity: %d\r\n", hum_2);
|
||||
Serial.printf("[2] PM1 ug/m3: %d\r\n", pm01_2);
|
||||
Serial.printf("[2] PM2.5 ug/m3: %d\r\n", pm25_2);
|
||||
Serial.printf("[2] PM10 ug/m3: %d\r\n", pm10_2);
|
||||
Serial.printf("[2] PM3.0 Count: %d\r\n", pm03PCount_2);
|
||||
Serial.printf("[2] Temperature in C: %0.2f\r\n", temp_2);
|
||||
Serial.printf("[2] Relative Humidity: %d\r\n", hum_2);
|
||||
} else {
|
||||
pm01_2 = -1;
|
||||
pm25_2 = -1;
|
||||
@ -1136,11 +1245,40 @@ static void pmUpdate(void) {
|
||||
pm2hum = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSensorSGP) {
|
||||
float temp;
|
||||
float hum;
|
||||
if (pmsResult_1 && pmsResult_2) {
|
||||
temp = (temp_1 + temp_2) / 2.0f;
|
||||
hum = (hum_1 + hum_2) / 2.0f;
|
||||
} else {
|
||||
if (pmsResult_1) {
|
||||
temp = temp_1;
|
||||
hum = hum_1;
|
||||
}
|
||||
if (pmsResult_2) {
|
||||
temp = temp_2;
|
||||
hum = hum_2;
|
||||
}
|
||||
}
|
||||
ag.sgp41.setCompensationTemperatureHumidity(temp, hum);
|
||||
}
|
||||
}
|
||||
|
||||
static void co2Update(void) {
|
||||
co2Ppm = ag.s8.getCo2();
|
||||
Serial.printf("CO2 index: %d\r\n", co2Ppm);
|
||||
int value = ag.s8.getCo2();
|
||||
if (value >= 0) {
|
||||
co2Ppm = value;
|
||||
getCO2FailCount = 0;
|
||||
Serial.printf("CO2 ppm: %d\r\n", co2Ppm);
|
||||
} else {
|
||||
getCO2FailCount++;
|
||||
Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount);
|
||||
if (getCO2FailCount >= 3) {
|
||||
co2Ppm = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void updateServerConfiguration(void) {
|
||||
@ -1151,25 +1289,25 @@ static void updateServerConfiguration(void) {
|
||||
if (hasSensorS8) {
|
||||
co2Calibration();
|
||||
} else {
|
||||
Serial.println("CO2 S8 not available, calib ignored");
|
||||
Serial.println("CO2 S8 not available, calibration ignored");
|
||||
}
|
||||
}
|
||||
|
||||
if (agServer.getCo2AbcDaysConfig() > 0) {
|
||||
if (hasSensorS8) {
|
||||
int newHour = agServer.getCo2AbcDaysConfig() * 24;
|
||||
Serial.printf("abcDays config: %d days(%d hours)\r\n",
|
||||
Serial.printf("Requested abcDays setting: %d days (%d hours)\r\n",
|
||||
agServer.getCo2AbcDaysConfig(), newHour);
|
||||
int curHour = ag.s8.getAbcPeriod();
|
||||
Serial.printf("Current config: %d (hours)\r\n", ag.s8.getAbcPeriod());
|
||||
Serial.printf("Current S8 abcDays setting: %d (hours)\r\n", curHour);
|
||||
if (curHour == newHour) {
|
||||
Serial.println("set 'abcDays' ignored");
|
||||
Serial.println("'abcDays' unchanged");
|
||||
} else {
|
||||
if (ag.s8.setAbcPeriod(agServer.getCo2AbcDaysConfig() * 24) ==
|
||||
false) {
|
||||
Serial.println("Set S8 abcDays period calib failed");
|
||||
Serial.println("Set S8 abcDays period failed");
|
||||
} else {
|
||||
Serial.println("Set S8 abcDays period calib success");
|
||||
Serial.println("Set S8 abcDays period success");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1186,17 +1324,12 @@ static void updateServerConfiguration(void) {
|
||||
mqttTask = NULL;
|
||||
}
|
||||
if (agMqtt.begin(mqttUri)) {
|
||||
Serial.println("Connect to new mqtt broker success");
|
||||
Serial.println("Connect to MQTT broker successful");
|
||||
createMqttTask();
|
||||
} else {
|
||||
Serial.println("Connect to new mqtt broker failed");
|
||||
Serial.println("Connect to MQTT broker failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (mdnsModelName != agServer.getModelName()) {
|
||||
MDNS.addServiceTxt("http", "_tcp", "model", agServer.getModelName());
|
||||
mdnsModelName = agServer.getModelName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1311,6 +1444,8 @@ static const char *getFwMode(int mode) {
|
||||
return "FW_MODE_PPT";
|
||||
case FW_MODE_PP:
|
||||
return "FW_MODE_PP";
|
||||
case FW_MDOE_PS:
|
||||
return "FW_MODE_PS";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -1323,6 +1458,180 @@ void webServerMeasureCurrentGet(void) {
|
||||
webServer.send(200, "application/json", getServerSyncData(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends metrics in Prometheus/OpenMetrics format to the currently connected
|
||||
* webServer client.
|
||||
*
|
||||
* For background, see:
|
||||
* https://prometheus.io/docs/instrumenting/exposition_formats/
|
||||
*/
|
||||
void webServerMetricsGet(void) {
|
||||
String response;
|
||||
String current_metric_name;
|
||||
const auto add_metric = [&](const String &name, const String &help,
|
||||
const String &type, const String &unit = "") {
|
||||
current_metric_name = "airgradient_" + name;
|
||||
if (!unit.isEmpty())
|
||||
current_metric_name += "_" + unit;
|
||||
response += "# HELP " + current_metric_name + " " + help + "\n";
|
||||
response += "# TYPE " + current_metric_name + " " + type + "\n";
|
||||
if (!unit.isEmpty())
|
||||
response += "# UNIT " + current_metric_name + " " + unit + "\n";
|
||||
};
|
||||
const auto add_metric_point = [&](const String &labels, const String &value) {
|
||||
response += current_metric_name + "{" + labels + "} " + value + "\n";
|
||||
};
|
||||
|
||||
add_metric("info", "AirGradient device information", "info");
|
||||
add_metric_point("airgradient_serial_number=\"" + getDevId() +
|
||||
"\",airgradient_device_type=\"" + ag.getBoardName() +
|
||||
"\",airgradient_library_version=\"" + ag.getVersion() +
|
||||
"\"",
|
||||
"1");
|
||||
|
||||
add_metric("config_ok",
|
||||
"1 if the AirGradient device was able to successfully fetch its "
|
||||
"configuration from the server",
|
||||
"gauge");
|
||||
add_metric_point("", agServer.isConfigFailed() ? "0" : "1");
|
||||
|
||||
add_metric(
|
||||
"post_ok",
|
||||
"1 if the AirGradient device was able to successfully send to the server",
|
||||
"gauge");
|
||||
add_metric_point("", agServer.isServerFailed() ? "0" : "1");
|
||||
|
||||
add_metric(
|
||||
"wifi_rssi",
|
||||
"WiFi signal strength from the AirGradient device perspective, in dBm",
|
||||
"gauge", "dbm");
|
||||
add_metric_point("", String(WiFi.RSSI()));
|
||||
|
||||
if (hasSensorS8 && co2Ppm >= 0) {
|
||||
add_metric("co2",
|
||||
"Carbon dioxide concentration as measured by the AirGradient S8 "
|
||||
"sensor, in parts per million",
|
||||
"gauge", "ppm");
|
||||
add_metric_point("", String(co2Ppm));
|
||||
}
|
||||
|
||||
float temp = -1001;
|
||||
float hum = -1;
|
||||
int pm01 = -1;
|
||||
int pm25 = -1;
|
||||
int pm10 = -1;
|
||||
int pm03PCount = -1;
|
||||
if (hasSensorPMS1 && hasSensorPMS2) {
|
||||
temp = (temp_1 + temp_2) / 2.0f;
|
||||
hum = (hum_1 + hum_2) / 2.0f;
|
||||
pm01 = (pm01_1 + pm01_2) / 2;
|
||||
pm25 = (pm25_1 + pm25_2) / 2;
|
||||
pm10 = (pm10_1 + pm10_2) / 2;
|
||||
pm03PCount = (pm03PCount_1 + pm03PCount_2) / 2;
|
||||
} else {
|
||||
if (hasSensorPMS1) {
|
||||
temp = temp_1;
|
||||
hum = hum_1;
|
||||
pm01 = pm01_1;
|
||||
pm25 = pm25_1;
|
||||
pm10 = pm10_1;
|
||||
pm03PCount = pm03PCount_1;
|
||||
}
|
||||
if (hasSensorPMS2) {
|
||||
temp = temp_2;
|
||||
hum = hum_2;
|
||||
pm01 = pm01_2;
|
||||
pm25 = pm25_2;
|
||||
pm10 = pm10_2;
|
||||
pm03PCount = pm03PCount_2;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSensorPMS1 || hasSensorPMS2) {
|
||||
if (pm01 >= 0) {
|
||||
add_metric("pm1",
|
||||
"PM1.0 concentration as measured by the AirGradient PMS "
|
||||
"sensor, in micrograms per cubic meter",
|
||||
"gauge", "ugm3");
|
||||
add_metric_point("", String(pm01));
|
||||
}
|
||||
if (pm25 >= 0) {
|
||||
add_metric("pm2d5",
|
||||
"PM2.5 concentration as measured by the AirGradient PMS "
|
||||
"sensor, in micrograms per cubic meter",
|
||||
"gauge", "ugm3");
|
||||
add_metric_point("", String(pm25));
|
||||
}
|
||||
if (pm10 >= 0) {
|
||||
add_metric("pm10",
|
||||
"PM10 concentration as measured by the AirGradient PMS "
|
||||
"sensor, in micrograms per cubic meter",
|
||||
"gauge", "ugm3");
|
||||
add_metric_point("", String(pm10));
|
||||
}
|
||||
if (pm03PCount >= 0) {
|
||||
add_metric("pm0d3",
|
||||
"PM0.3 concentration as measured by the AirGradient PMS "
|
||||
"sensor, in number of particules per 100 milliliters",
|
||||
"gauge", "p100ml");
|
||||
add_metric_point("", String(pm03PCount));
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSensorSGP) {
|
||||
if (tvocIndex >= 0) {
|
||||
add_metric("tvoc_index",
|
||||
"The processed Total Volatile Organic Compounds (TVOC) index "
|
||||
"as measured by the AirGradient SGP sensor",
|
||||
"gauge");
|
||||
add_metric_point("", String(tvocIndex));
|
||||
}
|
||||
if (tvocRawIndex >= 0) {
|
||||
add_metric("tvoc_raw",
|
||||
"The raw input value to the Total Volatile Organic Compounds "
|
||||
"(TVOC) index as measured by the AirGradient SGP sensor",
|
||||
"gauge");
|
||||
add_metric_point("", String(tvocRawIndex));
|
||||
}
|
||||
if (noxIndex >= 0) {
|
||||
add_metric("nox_index",
|
||||
"The processed Nitrous Oxide (NOx) index as measured by the "
|
||||
"AirGradient SGP sensor",
|
||||
"gauge");
|
||||
add_metric_point("", String(noxIndex));
|
||||
}
|
||||
if (noxRawIndex >= 0) {
|
||||
add_metric("nox_raw",
|
||||
"The raw input value to the Nitrous Oxide (NOx) index as "
|
||||
"measured by the AirGradient SGP sensor",
|
||||
"gauge");
|
||||
add_metric_point("", String(noxRawIndex));
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSensorPMS1 || hasSensorPMS2) {
|
||||
if (temp > -1001) {
|
||||
add_metric("temperature",
|
||||
"The ambient temperature as measured by the AirGradient SHT "
|
||||
"sensor, in degrees Celsius",
|
||||
"gauge", "celcius");
|
||||
add_metric_point("", String(temp));
|
||||
}
|
||||
if (hum >= 0) {
|
||||
add_metric(
|
||||
"humidity",
|
||||
"The relative humidity as measured by the AirGradient SHT sensor",
|
||||
"gauge", "percent");
|
||||
add_metric_point("", String(hum));
|
||||
}
|
||||
}
|
||||
|
||||
response += "# EOF\n";
|
||||
webServer.send(200,
|
||||
"application/openmetrics-text; version=1.0.0; charset=utf-8",
|
||||
response);
|
||||
}
|
||||
|
||||
void webServerHandler(void *param) {
|
||||
for (;;) {
|
||||
webServer.handleClient();
|
||||
@ -1337,14 +1646,14 @@ static void webServerInit(void) {
|
||||
}
|
||||
|
||||
webServer.on("/measures/current", HTTP_GET, webServerMeasureCurrentGet);
|
||||
// Make it possible to query this device from Prometheus/OpenMetrics.
|
||||
webServer.on("/metrics", HTTP_GET, webServerMetricsGet);
|
||||
webServer.begin();
|
||||
MDNS.addService("http", "tcp", 80);
|
||||
if (mdnsModelName != agServer.getModelName()) {
|
||||
MDNS.addServiceTxt("http", "_tcp", "model", agServer.getModelName());
|
||||
mdnsModelName = agServer.getModelName();
|
||||
}
|
||||
MDNS.addServiceTxt("http", "_tcp", "serialno", getDevId());
|
||||
MDNS.addServiceTxt("http", "_tcp", "fw_ver", ag.getVersion());
|
||||
MDNS.addService("_airgradient", "tcp", 80);
|
||||
MDNS.addServiceTxt("airgradient", "_tcp", "model", mdnsModelName);
|
||||
MDNS.addServiceTxt("airgradient", "_tcp", "serialno", getDevId());
|
||||
MDNS.addServiceTxt("airgradient", "_tcp", "fw_ver", ag.getVersion());
|
||||
MDNS.addServiceTxt("airgradient", "_tcp", "vendor", "AirGradient");
|
||||
|
||||
if (xTaskCreate(webServerHandler, "webserver", 1024 * 4, NULL, 5, NULL) !=
|
||||
pdTRUE) {
|
||||
@ -1379,7 +1688,11 @@ static String getServerSyncData(bool localServer) {
|
||||
root["pm10"] = pm10_1;
|
||||
}
|
||||
if (pm03PCount_1 >= 0) {
|
||||
root["pm003_count"] = pm03PCount_1;
|
||||
if (localServer) {
|
||||
root["pm003Count"] = pm03PCount_1;
|
||||
} else {
|
||||
root["pm003_count"] = pm03PCount_1;
|
||||
}
|
||||
}
|
||||
if (temp_1 > -1001) {
|
||||
root["atmp"] = ag.round2(temp_1);
|
||||
@ -1398,7 +1711,11 @@ static String getServerSyncData(bool localServer) {
|
||||
root["pm10"] = pm10_2;
|
||||
}
|
||||
if (pm03PCount_2 >= 0) {
|
||||
root["pm003_count"] = pm03PCount_2;
|
||||
if (localServer) {
|
||||
root["pm003Count"] = pm03PCount_2;
|
||||
} else {
|
||||
root["pm003_count"] = pm03PCount_2;
|
||||
}
|
||||
}
|
||||
if (temp_2 > -1001) {
|
||||
root["atmp"] = ag.round2(temp_2);
|
||||
@ -1412,13 +1729,24 @@ static String getServerSyncData(bool localServer) {
|
||||
if ((fw_mode == FW_MODE_PPT) || (fw_mode == FW_MODE_PST)) {
|
||||
if (hasSensorSGP) {
|
||||
if (tvocIndex >= 0) {
|
||||
root["tvoc_index"] = tvocIndex;
|
||||
if (localServer) {
|
||||
root["tvocIndex"] = tvocIndex;
|
||||
} else {
|
||||
root["tvoc_index"] = tvocIndex;
|
||||
}
|
||||
}
|
||||
if (tvocRawIndex >= 0) {
|
||||
root["tvoc_raw"] = tvocRawIndex;
|
||||
}
|
||||
if (noxIndex >= 0) {
|
||||
root["nox_index"] = noxIndex;
|
||||
if (localServer) {
|
||||
root["noxIndex"] = noxIndex;
|
||||
} else {
|
||||
root["nox_index"] = noxIndex;
|
||||
}
|
||||
}
|
||||
if (noxRawIndex >= 0) {
|
||||
root["nox_raw"] = noxRawIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1428,7 +1756,11 @@ static String getServerSyncData(bool localServer) {
|
||||
root["pm01"] = ag.round2((pm01_1 + pm01_2) / 2.0);
|
||||
root["pm02"] = ag.round2((pm25_1 + pm25_2) / 2.0);
|
||||
root["pm10"] = ag.round2((pm10_1 + pm10_2) / 2.0);
|
||||
root["pm003_count"] = ag.round2((pm03PCount_1 + pm03PCount_2) / 2.0);
|
||||
if (localServer) {
|
||||
root["pm003Count"] = ag.round2((pm03PCount_1 + pm03PCount_2) / 2.0);
|
||||
} else {
|
||||
root["pm003_count"] = ag.round2((pm03PCount_1 + pm03PCount_2) / 2.0);
|
||||
}
|
||||
root["atmp"] = ag.round2((temp_1 + temp_2) / 2.0);
|
||||
root["rhum"] = ag.round2((hum_1 + hum_2) / 2.0);
|
||||
}
|
||||
@ -1436,7 +1768,11 @@ static String getServerSyncData(bool localServer) {
|
||||
root["channels"]["1"]["pm01"] = pm01_1;
|
||||
root["channels"]["1"]["pm02"] = pm25_1;
|
||||
root["channels"]["1"]["pm10"] = pm10_1;
|
||||
root["channels"]["1"]["pm003_count"] = pm03PCount_1;
|
||||
if (localServer) {
|
||||
root["channels"]["1"]["pm003Count"] = pm03PCount_1;
|
||||
} else {
|
||||
root["channels"]["1"]["pm003_count"] = pm03PCount_1;
|
||||
}
|
||||
root["channels"]["1"]["atmp"] = ag.round2(temp_1);
|
||||
root["channels"]["1"]["rhum"] = hum_1;
|
||||
}
|
||||
@ -1444,12 +1780,21 @@ static String getServerSyncData(bool localServer) {
|
||||
root["channels"]["2"]["pm01"] = pm01_2;
|
||||
root["channels"]["2"]["pm02"] = pm25_2;
|
||||
root["channels"]["2"]["pm10"] = pm10_2;
|
||||
root["channels"]["2"]["pm003_count"] = pm03PCount_2;
|
||||
if (localServer) {
|
||||
root["channels"]["2"]["pm003Count"] = pm03PCount_2;
|
||||
} else {
|
||||
root["channels"]["2"]["pm003_count"] = pm03PCount_2;
|
||||
}
|
||||
root["channels"]["2"]["atmp"] = ag.round2(temp_2);
|
||||
root["channels"]["2"]["rhum"] = hum_2;
|
||||
}
|
||||
}
|
||||
|
||||
if (localServer) {
|
||||
root["ledMode"] = agServer.getLedBarModeName();
|
||||
root["firmwareVersion"] = ag.getVersion();
|
||||
}
|
||||
|
||||
return JSON.stringify(root);
|
||||
}
|
||||
|
||||
@ -1541,6 +1886,13 @@ static void factoryConfigReset(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static void wdgFeedUpdate(void) {
|
||||
ag.watchdog.reset();
|
||||
Serial.println();
|
||||
Serial.println("External watchdog feed");
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
static void mqtt_event_handler(void *handler_args, esp_event_base_t base,
|
||||
int32_t event_id, void *event_data) {
|
||||
esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t)event_data;
|
||||
|
@ -25,7 +25,7 @@ void setup()
|
||||
if (ag.s8.begin(&Serial) == false)
|
||||
{
|
||||
#else
|
||||
if (ag.s8.begin(Serial1) == false)
|
||||
if (ag.s8.begin(Serial0) == false)
|
||||
{
|
||||
#endif
|
||||
failedHandler("SenseAir S8 init failed");
|
||||
|
@ -10,8 +10,8 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
#ifdef ESP8266
|
||||
AirGradient ag = AirGradient(DIY_BASIC);
|
||||
#else
|
||||
// AirGradient ag = AirGradient(ONE_INDOOR);
|
||||
AirGradient ag = AirGradient(OPEN_AIR_OUTDOOR);
|
||||
AirGradient ag = AirGradient(ONE_INDOOR);
|
||||
// AirGradient ag = AirGradient(OPEN_AIR_OUTDOOR);
|
||||
#endif
|
||||
|
||||
void failedHandler(String msg);
|
||||
@ -35,42 +35,56 @@ void setup() {
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t lastRead = 0;
|
||||
void loop() {
|
||||
int PM2;
|
||||
bool readResul = false;
|
||||
#ifdef ESP8266
|
||||
if (ag.pms5003.readData()) {
|
||||
PM2 = ag.pms5003.getPm25Ae();
|
||||
Serial.printf("PM2.5 in ug/m3: %d\r\n", PM2);
|
||||
Serial.printf("PM2.5 in US AQI: %d\r\n",
|
||||
ag.pms5003.convertPm25ToUsAqi(PM2));
|
||||
}
|
||||
#else
|
||||
if (ag.getBoardType() == OPEN_AIR_OUTDOOR) {
|
||||
if (ag.pms5003t_1.readData()) {
|
||||
PM2 = ag.pms5003t_1.getPm25Ae();
|
||||
readResul = true;
|
||||
}
|
||||
} else {
|
||||
if (ag.pms5003.readData()) {
|
||||
PM2 = ag.pms5003.getPm25Ae();
|
||||
readResul = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (readResul) {
|
||||
Serial.printf("PM2.5 in ug/m3: %d\r\n", PM2);
|
||||
if (ag.getBoardType() == OPEN_AIR_OUTDOOR) {
|
||||
Serial.printf("PM2.5 in US AQI: %d\r\n",
|
||||
ag.pms5003t_1.convertPm25ToUsAqi(PM2));
|
||||
} else {
|
||||
uint32_t ms = (uint32_t)(millis() - lastRead);
|
||||
if (ms >= 5000) {
|
||||
lastRead = millis();
|
||||
#ifdef ESP8266
|
||||
if (ag.pms5003.isFailed() == false) {
|
||||
PM2 = ag.pms5003.getPm25Ae();
|
||||
Serial.printf("PM2.5 in ug/m3: %d\r\n", PM2);
|
||||
Serial.printf("PM2.5 in US AQI: %d\r\n",
|
||||
ag.pms5003.convertPm25ToUsAqi(PM2));
|
||||
} else {
|
||||
Serial.println("PMS sensor failed");
|
||||
}
|
||||
#else
|
||||
if (ag.getBoardType() == OPEN_AIR_OUTDOOR) {
|
||||
if (ag.pms5003t_1.isFailed() == false) {
|
||||
PM2 = ag.pms5003t_1.getPm25Ae();
|
||||
readResul = true;
|
||||
}
|
||||
} else {
|
||||
if (ag.pms5003.isFailed() == false) {
|
||||
PM2 = ag.pms5003.getPm25Ae();
|
||||
readResul = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
delay(5000);
|
||||
if (readResul) {
|
||||
Serial.printf("PM2.5 in ug/m3: %d\r\n", PM2);
|
||||
if (ag.getBoardType() == OPEN_AIR_OUTDOOR) {
|
||||
Serial.printf("PM2.5 in US AQI: %d\r\n",
|
||||
ag.pms5003t_1.convertPm25ToUsAqi(PM2));
|
||||
} else {
|
||||
Serial.printf("PM2.5 in US AQI: %d\r\n",
|
||||
ag.pms5003.convertPm25ToUsAqi(PM2));
|
||||
}
|
||||
} else {
|
||||
Serial.println("PMS sensor failed");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (ag.getBoardType() == OPEN_AIR_OUTDOOR) {
|
||||
ag.pms5003t_1.handle();
|
||||
} else {
|
||||
ag.pms5003.handle();
|
||||
}
|
||||
}
|
||||
|
||||
void failedHandler(String msg) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
name=AirGradient Air Quality Sensor
|
||||
version=3.0.6
|
||||
version=3.0.8
|
||||
author=AirGradient <support@airgradient.com>
|
||||
maintainer=AirGradient <support@airgradient.com>
|
||||
sentence=ESP32-C3 / ESP8266 library for air quality monitor measuring PM, CO2, Temperature, TVOC and Humidity with OLED display.
|
||||
|
@ -1,6 +1,6 @@
|
||||
#include "AirGradient.h"
|
||||
|
||||
#define AG_LIB_VER "3.0.6"
|
||||
#define AG_LIB_VER "3.0.8"
|
||||
|
||||
AirGradient::AirGradient(BoardType type)
|
||||
: pms5003(type), pms5003t_1(type), pms5003t_2(type), s8(type), sgp41(type),
|
||||
|
@ -35,6 +35,7 @@ void LedBar::begin(void) {
|
||||
this->_bsp->LED.rgbNum, this->_bsp->LED.pin, NEO_GRB + NEO_KHZ800);
|
||||
pixel()->begin();
|
||||
pixel()->clear();
|
||||
pixel()->show();
|
||||
|
||||
this->_isBegin = true;
|
||||
|
||||
@ -111,9 +112,13 @@ void LedBar::setColor(uint8_t red, uint8_t green, uint8_t blue) {
|
||||
|
||||
/**
|
||||
* @brief Call to turn LED on/off base on the setting color
|
||||
*
|
||||
*
|
||||
*/
|
||||
void LedBar::show(void) {
|
||||
// Ignore update the LED if LED bar disabled
|
||||
if (enabled == false) {
|
||||
return;
|
||||
}
|
||||
if (pixel()->canShow()) {
|
||||
pixel()->show();
|
||||
}
|
||||
@ -121,6 +126,18 @@ void LedBar::show(void) {
|
||||
|
||||
/**
|
||||
* @brief Set all LED to off color (r,g,b) = (0,0,0)
|
||||
*
|
||||
*
|
||||
*/
|
||||
void LedBar::clear(void) { pixel()->clear(); }
|
||||
|
||||
void LedBar::setEnable(bool enable) {
|
||||
if (this->enabled != enable) {
|
||||
if (enable == false) {
|
||||
pixel()->clear();
|
||||
pixel()->show();
|
||||
}
|
||||
}
|
||||
this->enabled = enable;
|
||||
}
|
||||
|
||||
bool LedBar::isEnabled(void) { return enabled; }
|
||||
|
@ -23,8 +23,11 @@ public:
|
||||
int getNumberOfLeds(void);
|
||||
void show(void);
|
||||
void clear(void);
|
||||
void setEnable(bool enable);
|
||||
bool isEnabled(void);
|
||||
|
||||
private:
|
||||
bool enabled = true;
|
||||
const BoardDef *_bsp;
|
||||
bool _isBegin = false;
|
||||
uint8_t _ledState = 0;
|
||||
|
426
src/PMS/PMS.cpp
426
src/PMS/PMS.cpp
@ -1,164 +1,300 @@
|
||||
#include "PMS.h"
|
||||
#include "../Main/BoardDef.h"
|
||||
|
||||
bool PMS::begin(Stream *stream) {
|
||||
_stream = stream;
|
||||
/**
|
||||
* @brief Init and check that sensor has connected
|
||||
*
|
||||
* @param stream UART stream
|
||||
* @return true Sucecss
|
||||
* @return false Failure
|
||||
*/
|
||||
bool PMSBase::begin(Stream *stream) {
|
||||
this->stream = stream;
|
||||
|
||||
DATA data;
|
||||
if (readUntil(data, 5000)) {
|
||||
return true;
|
||||
failed = true;
|
||||
lastRead = 0; // To read buffer on handle without wait after 1.5sec
|
||||
|
||||
this->stream->flush();
|
||||
|
||||
// Run and check sensor data for 4sec
|
||||
while (1) {
|
||||
handle();
|
||||
if (failed == false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
delay(1);
|
||||
uint32_t ms = (uint32_t)(millis() - lastRead);
|
||||
if (ms >= 4000) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Standby mode. For low power consumption and prolong the life of the sensor.
|
||||
void PMS::sleep() {
|
||||
uint8_t command[] = {0x42, 0x4D, 0xE4, 0x00, 0x00, 0x01, 0x73};
|
||||
_stream->write(command, sizeof(command));
|
||||
}
|
||||
|
||||
// Operating mode. Stable data should be got at least 30 seconds after the
|
||||
// sensor wakeup from the sleep mode because of the fan's performance.
|
||||
void PMS::wakeUp() {
|
||||
uint8_t command[] = {0x42, 0x4D, 0xE4, 0x00, 0x01, 0x01, 0x74};
|
||||
_stream->write(command, sizeof(command));
|
||||
}
|
||||
|
||||
// Active mode. Default mode after power up. In this mode sensor would send
|
||||
// serial data to the host automatically.
|
||||
void PMS::activeMode() {
|
||||
uint8_t command[] = {0x42, 0x4D, 0xE1, 0x00, 0x01, 0x01, 0x71};
|
||||
_stream->write(command, sizeof(command));
|
||||
_mode = MODE_ACTIVE;
|
||||
}
|
||||
|
||||
// Passive mode. In this mode sensor would send serial data to the host only for
|
||||
// request.
|
||||
void PMS::passiveMode() {
|
||||
uint8_t command[] = {0x42, 0x4D, 0xE1, 0x00, 0x00, 0x01, 0x70};
|
||||
_stream->write(command, sizeof(command));
|
||||
_mode = MODE_PASSIVE;
|
||||
}
|
||||
|
||||
// Request read in Passive Mode.
|
||||
void PMS::requestRead() {
|
||||
if (_mode == MODE_PASSIVE) {
|
||||
uint8_t command[] = {0x42, 0x4D, 0xE2, 0x00, 0x00, 0x01, 0x71};
|
||||
_stream->write(command, sizeof(command));
|
||||
/**
|
||||
* @brief Check and read sensor data then update variable.
|
||||
* Check result from method @isFailed before get value
|
||||
*/
|
||||
void PMSBase::handle() {
|
||||
uint32_t ms;
|
||||
if (lastRead == 0) {
|
||||
lastRead = millis();
|
||||
if (lastRead == 0) {
|
||||
lastRead = 1;
|
||||
}
|
||||
} else {
|
||||
ms = (uint32_t)(millis() - lastRead);
|
||||
/**
|
||||
* The PMS in Active mode sends an update data every 1 second. If we read
|
||||
* exactly every 1 sec then we may or may not get an update (depending on
|
||||
* timing tolerances). Hence we read every 2.5 seconds and expect 2 ..3
|
||||
* updates,
|
||||
*/
|
||||
if (ms < 2500) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool result = false;
|
||||
char buf[32];
|
||||
int bufIndex;
|
||||
int step = 0;
|
||||
int len = 0;
|
||||
int bcount = 0;
|
||||
|
||||
// Non-blocking function for parse response.
|
||||
bool PMS::read(DATA &data) {
|
||||
_data = &data;
|
||||
loop();
|
||||
|
||||
return _status == STATUS_OK;
|
||||
}
|
||||
|
||||
// Blocking function for parse response. Default timeout is 1s.
|
||||
bool PMS::readUntil(DATA &data, uint16_t timeout) {
|
||||
_data = &data;
|
||||
uint32_t start = millis();
|
||||
do {
|
||||
loop();
|
||||
if (_status == STATUS_OK) {
|
||||
while (stream->available()) {
|
||||
char value = stream->read();
|
||||
switch (step) {
|
||||
case 0: {
|
||||
if (value == 0x42) {
|
||||
step = 1;
|
||||
bufIndex = 0;
|
||||
buf[bufIndex++] = value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/** Relax task to avoid watchdog reset */
|
||||
delay(1);
|
||||
} while (millis() - start < timeout);
|
||||
|
||||
return _status == STATUS_OK;
|
||||
}
|
||||
|
||||
void PMS::loop() {
|
||||
_status = STATUS_WAITING;
|
||||
if (_stream->available()) {
|
||||
uint8_t ch = _stream->read();
|
||||
|
||||
switch (_index) {
|
||||
case 0:
|
||||
if (ch != 0x42) {
|
||||
return;
|
||||
}
|
||||
_calculatedChecksum = ch;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
if (ch != 0x4D) {
|
||||
_index = 0;
|
||||
return;
|
||||
}
|
||||
_calculatedChecksum += ch;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
_calculatedChecksum += ch;
|
||||
_frameLen = ch << 8;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
_frameLen |= ch;
|
||||
// Unsupported sensor, different frame length, transmission error e.t.c.
|
||||
if (_frameLen != 2 * 9 + 2 && _frameLen != 2 * 13 + 2) {
|
||||
_index = 0;
|
||||
return;
|
||||
}
|
||||
_calculatedChecksum += ch;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (_index == _frameLen + 2) {
|
||||
_checksum = ch << 8;
|
||||
} else if (_index == _frameLen + 2 + 1) {
|
||||
_checksum |= ch;
|
||||
|
||||
if (_calculatedChecksum == _checksum) {
|
||||
_status = STATUS_OK;
|
||||
|
||||
// Standard Particles, CF=1.
|
||||
_data->PM_SP_UG_1_0 = makeWord(_payload[0], _payload[1]);
|
||||
_data->PM_SP_UG_2_5 = makeWord(_payload[2], _payload[3]);
|
||||
_data->PM_SP_UG_10_0 = makeWord(_payload[4], _payload[5]);
|
||||
|
||||
// Atmospheric Environment.
|
||||
_data->PM_AE_UG_1_0 = makeWord(_payload[6], _payload[7]);
|
||||
_data->PM_AE_UG_2_5 = makeWord(_payload[8], _payload[9]);
|
||||
_data->PM_AE_UG_10_0 = makeWord(_payload[10], _payload[11]);
|
||||
|
||||
// Total particles count per 100ml air
|
||||
_data->PM_RAW_0_3 = makeWord(_payload[12], _payload[13]);
|
||||
_data->PM_RAW_0_5 = makeWord(_payload[14], _payload[15]);
|
||||
_data->PM_RAW_1_0 = makeWord(_payload[16], _payload[17]);
|
||||
_data->PM_RAW_2_5 = makeWord(_payload[18], _payload[19]);
|
||||
_data->PM_RAW_5_0 = makeWord(_payload[20], _payload[21]);
|
||||
_data->PM_RAW_10_0 = makeWord(_payload[22], _payload[23]);
|
||||
|
||||
// Formaldehyde concentration (PMSxxxxST units only)
|
||||
_data->AMB_HCHO = makeWord(_payload[24], _payload[25]) / 1000;
|
||||
|
||||
// Temperature & humidity (PMSxxxxST units only)
|
||||
_data->AMB_TMP = makeWord(_payload[20], _payload[21]);
|
||||
_data->AMB_HUM = makeWord(_payload[22], _payload[23]);
|
||||
}
|
||||
|
||||
_index = 0;
|
||||
return;
|
||||
case 1: {
|
||||
if (value == 0x4d) {
|
||||
step = 2;
|
||||
buf[bufIndex++] = value;
|
||||
// Serial.println("Got 0x4d");
|
||||
} else {
|
||||
_calculatedChecksum += ch;
|
||||
uint8_t payloadIndex = _index - 4;
|
||||
|
||||
// Payload is common to all sensors (first 2x6 bytes).
|
||||
if (payloadIndex < sizeof(_payload)) {
|
||||
_payload[payloadIndex] = ch;
|
||||
step = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
buf[bufIndex++] = value;
|
||||
if (bufIndex >= 4) {
|
||||
len = toValue(&buf[2]);
|
||||
if (len != 28) {
|
||||
// Serial.printf("Got good bad len %d\r\n", len);
|
||||
len += 4;
|
||||
step = 3;
|
||||
} else {
|
||||
// Serial.println("Got good len");
|
||||
step = 4;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
bufIndex++;
|
||||
if (bufIndex >= len) {
|
||||
step = 0;
|
||||
// Serial.println("Bad lengh read all buffer");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
buf[bufIndex++] = value;
|
||||
if (bufIndex >= 32) {
|
||||
result |= validate(buf);
|
||||
step = 0;
|
||||
// Serial.println("Got data");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
_index++;
|
||||
// Reduce core panic: delay 1 ms each 32bytes data
|
||||
bcount++;
|
||||
if ((bcount % 32) == 0) {
|
||||
delay(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (result) {
|
||||
lastRead = millis();
|
||||
if (lastRead == 0) {
|
||||
lastRead = 1;
|
||||
}
|
||||
failed = false;
|
||||
} else {
|
||||
if (ms > 5000) {
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check that PMS send is failed or disconnected
|
||||
*
|
||||
* @return true Failed
|
||||
* @return false No problem
|
||||
*/
|
||||
bool PMSBase::isFailed(void) { return failed; }
|
||||
|
||||
/**
|
||||
* @brief Read PMS 0.1 ug/m3 with CF = 1 PM estimates
|
||||
*
|
||||
* @return uint16_t
|
||||
*/
|
||||
uint16_t PMSBase::getRaw0_1(void) { return toValue(&package[4]); }
|
||||
|
||||
/**
|
||||
* @brief Read PMS 2.5 ug/m3 with CF = 1 PM estimates
|
||||
*
|
||||
* @return uint16_t
|
||||
*/
|
||||
uint16_t PMSBase::getRaw2_5(void) { return toValue(&package[6]); }
|
||||
|
||||
/**
|
||||
* @brief Read PMS 10 ug/m3 with CF = 1 PM estimates
|
||||
*
|
||||
* @return uint16_t
|
||||
*/
|
||||
uint16_t PMSBase::getRaw10(void) { return toValue(&package[8]); }
|
||||
|
||||
/**
|
||||
* @brief Read PMS 0.1 ug/m3
|
||||
*
|
||||
* @return uint16_t
|
||||
*/
|
||||
uint16_t PMSBase::getPM0_1(void) { return toValue(&package[10]); }
|
||||
|
||||
/**
|
||||
* @brief Read PMS 2.5 ug/m3
|
||||
*
|
||||
* @return uint16_t
|
||||
*/
|
||||
uint16_t PMSBase::getPM2_5(void) { return toValue(&package[12]); }
|
||||
|
||||
/**
|
||||
* @brief Read PMS 10 ug/m3
|
||||
*
|
||||
* @return uint16_t
|
||||
*/
|
||||
uint16_t PMSBase::getPM10(void) { return toValue(&package[14]); }
|
||||
|
||||
/**
|
||||
* @brief Get numnber concentrations over 0.3 um/0.1L
|
||||
*
|
||||
* @return uint16_t
|
||||
*/
|
||||
uint16_t PMSBase::getCount0_3(void) { return toValue(&package[16]); }
|
||||
|
||||
/**
|
||||
* @brief Get numnber concentrations over 0.5 um/0.1L
|
||||
*
|
||||
* @return uint16_t
|
||||
*/
|
||||
uint16_t PMSBase::getCount0_5(void) { return toValue(&package[18]); }
|
||||
|
||||
/**
|
||||
* @brief Get numnber concentrations over 1.0 um/0.1L
|
||||
*
|
||||
* @return uint16_t
|
||||
*/
|
||||
uint16_t PMSBase::getCount1_0(void) { return toValue(&package[20]); }
|
||||
|
||||
/**
|
||||
* @brief Get numnber concentrations over 2.5 um/0.1L
|
||||
*
|
||||
* @return uint16_t
|
||||
*/
|
||||
uint16_t PMSBase::getCount2_5(void) { return toValue(&package[22]); }
|
||||
|
||||
/**
|
||||
* @brief Get numnber concentrations over 5.0 um/0.1L (only PMS5003)
|
||||
*
|
||||
* @return uint16_t
|
||||
*/
|
||||
uint16_t PMSBase::getCount5_0(void) { return toValue(&package[24]); }
|
||||
|
||||
/**
|
||||
* @brief Get numnber concentrations over 10.0 um/0.1L (only PMS5003)
|
||||
*
|
||||
* @return uint16_t
|
||||
*/
|
||||
uint16_t PMSBase::getCount10(void) { return toValue(&package[26]); }
|
||||
|
||||
/**
|
||||
* @brief Get temperature (only PMS5003T)
|
||||
*
|
||||
* @return uint16_t
|
||||
*/
|
||||
uint16_t PMSBase::getTemp(void) { return toValue(&package[24]); }
|
||||
|
||||
/**
|
||||
* @brief Get humidity (only PMS5003T)
|
||||
*
|
||||
* @return uint16_t
|
||||
*/
|
||||
uint16_t PMSBase::getHum(void) { return toValue(&package[26]); }
|
||||
|
||||
/**
|
||||
* @brief Convert PMS2.5 to US AQI unit
|
||||
*
|
||||
* @param pm02
|
||||
* @return int
|
||||
*/
|
||||
int PMSBase::pm25ToAQI(int pm02) {
|
||||
if (pm02 <= 12.0)
|
||||
return ((50 - 0) / (12.0 - .0) * (pm02 - .0) + 0);
|
||||
else if (pm02 <= 35.4)
|
||||
return ((100 - 50) / (35.4 - 12.0) * (pm02 - 12.0) + 50);
|
||||
else if (pm02 <= 55.4)
|
||||
return ((150 - 100) / (55.4 - 35.4) * (pm02 - 35.4) + 100);
|
||||
else if (pm02 <= 150.4)
|
||||
return ((200 - 150) / (150.4 - 55.4) * (pm02 - 55.4) + 150);
|
||||
else if (pm02 <= 250.4)
|
||||
return ((300 - 200) / (250.4 - 150.4) * (pm02 - 150.4) + 200);
|
||||
else if (pm02 <= 350.4)
|
||||
return ((400 - 300) / (350.4 - 250.4) * (pm02 - 250.4) + 300);
|
||||
else if (pm02 <= 500.4)
|
||||
return ((500 - 400) / (500.4 - 350.4) * (pm02 - 350.4) + 400);
|
||||
else
|
||||
return 500;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert two byte value to uint16_t value
|
||||
*
|
||||
* @param buf bytes array (must be >= 2)
|
||||
* @return uint16_t
|
||||
*/
|
||||
uint16_t PMSBase::toValue(char *buf) { return (buf[0] << 8) | buf[1]; }
|
||||
|
||||
/**
|
||||
* @brief Validate package data
|
||||
*
|
||||
* @param buf Package buffer
|
||||
* @return true Success
|
||||
* @return false Failed
|
||||
*/
|
||||
bool PMSBase::validate(char *buf) {
|
||||
uint16_t sum = 0;
|
||||
for (int i = 0; i < 30; i++) {
|
||||
sum += buf[i];
|
||||
}
|
||||
if (sum == toValue(&buf[30])) {
|
||||
for (int i = 0; i < 32; i++) {
|
||||
package[i] = buf[i];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -1,75 +1,43 @@
|
||||
#ifndef _PMS_BASE_H_
|
||||
#define _PMS_BASE_H_
|
||||
#ifndef _PMS5003_BASE_H_
|
||||
#define _PMS5003_BASE_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 PMSBase {
|
||||
public:
|
||||
static const uint16_t SINGLE_RESPONSE_TIME = 1000;
|
||||
static const uint16_t TOTAL_RESPONSE_TIME = 1000 * 10;
|
||||
static const uint16_t STEADY_RESPONSE_TIME = 1000 * 30;
|
||||
|
||||
// static const uint16_t BAUD_RATE = 9600;
|
||||
|
||||
struct DATA {
|
||||
// Standard Particles, CF=1
|
||||
uint16_t PM_SP_UG_1_0;
|
||||
uint16_t PM_SP_UG_2_5;
|
||||
uint16_t PM_SP_UG_10_0;
|
||||
|
||||
// Atmospheric environment
|
||||
uint16_t PM_AE_UG_1_0;
|
||||
uint16_t PM_AE_UG_2_5;
|
||||
uint16_t PM_AE_UG_10_0;
|
||||
|
||||
// Raw particles count (number of particles in 0.1l of air
|
||||
uint16_t PM_RAW_0_3;
|
||||
uint16_t PM_RAW_0_5;
|
||||
uint16_t PM_RAW_1_0;
|
||||
uint16_t PM_RAW_2_5;
|
||||
uint16_t PM_RAW_5_0;
|
||||
uint16_t PM_RAW_10_0;
|
||||
|
||||
// Formaldehyde (HCHO) concentration in mg/m^3 - PMSxxxxST units only
|
||||
uint16_t AMB_HCHO;
|
||||
|
||||
// Temperature & humidity - PMSxxxxST units only
|
||||
int16_t AMB_TMP;
|
||||
uint16_t AMB_HUM;
|
||||
};
|
||||
|
||||
bool begin(Stream *stream);
|
||||
void sleep();
|
||||
void wakeUp();
|
||||
void activeMode();
|
||||
void passiveMode();
|
||||
void handle();
|
||||
bool isFailed(void);
|
||||
uint16_t getRaw0_1(void);
|
||||
uint16_t getRaw2_5(void);
|
||||
uint16_t getRaw10(void);
|
||||
uint16_t getPM0_1(void);
|
||||
uint16_t getPM2_5(void);
|
||||
uint16_t getPM10(void);
|
||||
uint16_t getCount0_3(void);
|
||||
uint16_t getCount0_5(void);
|
||||
uint16_t getCount1_0(void);
|
||||
uint16_t getCount2_5(void);
|
||||
|
||||
void requestRead();
|
||||
bool read(DATA &data);
|
||||
bool readUntil(DATA &data, uint16_t timeout = SINGLE_RESPONSE_TIME);
|
||||
/** For PMS5003 */
|
||||
uint16_t getCount5_0(void);
|
||||
uint16_t getCount10(void);
|
||||
|
||||
/** For PMS5003T*/
|
||||
uint16_t getTemp(void);
|
||||
uint16_t getHum(void);
|
||||
|
||||
int pm25ToAQI(int pm02);
|
||||
|
||||
private:
|
||||
enum STATUS { STATUS_WAITING, STATUS_OK };
|
||||
enum MODE { MODE_ACTIVE, MODE_PASSIVE };
|
||||
Stream *stream;
|
||||
char package[32];
|
||||
int packageIndex;
|
||||
bool failed = false;
|
||||
uint32_t lastRead;
|
||||
|
||||
uint8_t _payload[50];
|
||||
Stream *_stream;
|
||||
DATA *_data;
|
||||
STATUS _status;
|
||||
MODE _mode = MODE_ACTIVE;
|
||||
|
||||
uint8_t _index = 0;
|
||||
uint16_t _frameLen;
|
||||
uint16_t _checksum;
|
||||
uint16_t _calculatedChecksum;
|
||||
|
||||
void loop();
|
||||
char Char_PM2[10];
|
||||
uint16_t toValue(char *buf);
|
||||
bool validate(char *buf);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif /** _PMS5003_BASE_H_ */
|
||||
|
@ -1,6 +1,5 @@
|
||||
#include "PMS5003.h"
|
||||
#include "Arduino.h"
|
||||
#include "PMSUtils.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <SoftwareSerial.h>
|
||||
@ -83,48 +82,33 @@ bool PMS5003::begin(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read all package data then call to @ref getPMxxx to get the target
|
||||
* data
|
||||
*
|
||||
* @return true Success
|
||||
* @return false Failure
|
||||
*/
|
||||
bool PMS5003::readData(void) {
|
||||
if (this->isBegin() == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return pms.readUntil(pmsData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read PM1.0 must call this function after @ref readData success
|
||||
*
|
||||
* @return int PM1.0 index
|
||||
*/
|
||||
int PMS5003::getPm01Ae(void) { return pmsData.PM_AE_UG_1_0; }
|
||||
int PMS5003::getPm01Ae(void) { return pms.getPM0_1(); }
|
||||
|
||||
/**
|
||||
* @brief Read PM2.5 must call this function after @ref readData success
|
||||
*
|
||||
* @return int PM2.5 index
|
||||
*/
|
||||
int PMS5003::getPm25Ae(void) { return pmsData.PM_AE_UG_2_5; }
|
||||
int PMS5003::getPm25Ae(void) { return pms.getPM2_5(); }
|
||||
|
||||
/**
|
||||
* @brief Read PM10.0 must call this function after @ref readData success
|
||||
*
|
||||
* @return int PM10.0 index
|
||||
*/
|
||||
int PMS5003::getPm10Ae(void) { return pmsData.PM_AE_UG_10_0; }
|
||||
int PMS5003::getPm10Ae(void) { return pms.getPM10(); }
|
||||
|
||||
/**
|
||||
* @brief Read PM3.0 must call this function after @ref readData success
|
||||
* @brief Read PM0.3 must call this function after @ref readData success
|
||||
*
|
||||
* @return int PM3.0 index
|
||||
* @return int PM0.3 index
|
||||
*/
|
||||
int PMS5003::getPm03ParticleCount(void) { return pmsData.PM_RAW_0_3; }
|
||||
int PMS5003::getPm03ParticleCount(void) { return pms.getCount0_3(); }
|
||||
|
||||
/**
|
||||
* @brief Convert PM2.5 to US AQI
|
||||
@ -132,7 +116,7 @@ int PMS5003::getPm03ParticleCount(void) { return pmsData.PM_RAW_0_3; }
|
||||
* @param pm25 PM2.5 index
|
||||
* @return int PM2.5 US AQI
|
||||
*/
|
||||
int PMS5003::convertPm25ToUsAqi(int pm25) { return pm25ToAQI(pm25); }
|
||||
int PMS5003::convertPm25ToUsAqi(int pm25) { return pms.pm25ToAQI(pm25); }
|
||||
|
||||
/**
|
||||
* @brief Check device initialized or not
|
||||
@ -163,3 +147,17 @@ void PMS5003::end(void) {
|
||||
#endif
|
||||
AgLog("De-initialize");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check and read PMS sensor data. This method should be callack from
|
||||
* loop process to continoue check sensor data if it's available
|
||||
*/
|
||||
void PMS5003::handle(void) { pms.handle(); }
|
||||
|
||||
/**
|
||||
* @brief Get sensor status
|
||||
*
|
||||
* @return true No problem
|
||||
* @return false Communication timeout or sensor has removed
|
||||
*/
|
||||
bool PMS5003::isFailed(void) { return pms.isFailed(); }
|
||||
|
@ -17,8 +17,8 @@ public:
|
||||
bool begin(HardwareSerial &serial);
|
||||
#endif
|
||||
void end(void);
|
||||
|
||||
bool readData(void);
|
||||
void handle(void);
|
||||
bool isFailed(void);
|
||||
int getPm01Ae(void);
|
||||
int getPm25Ae(void);
|
||||
int getPm10Ae(void);
|
||||
@ -28,7 +28,7 @@ public:
|
||||
private:
|
||||
bool _isBegin = false;
|
||||
BoardType _boardDef;
|
||||
PMS pms;
|
||||
PMSBase pms;
|
||||
const BoardDef *bsp;
|
||||
#if defined(ESP8266)
|
||||
Stream *_debugStream;
|
||||
@ -36,9 +36,6 @@ private:
|
||||
#else
|
||||
HardwareSerial *_serial;
|
||||
#endif
|
||||
// Conplug_PMS5003T *pms;
|
||||
PMS::DATA pmsData;
|
||||
|
||||
bool begin(void);
|
||||
bool isBegin(void);
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
#include "PMS5003T.h"
|
||||
#include "Arduino.h"
|
||||
#include "PMSUtils.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <SoftwareSerial.h>
|
||||
@ -79,19 +78,21 @@ bool PMS5003T::begin(void) {
|
||||
|
||||
#if ARDUINO_USB_CDC_ON_BOOT // Serial used for USB CDC
|
||||
if (this->_serial == &Serial0) {
|
||||
AgLog("Init Serial0");
|
||||
_serial->begin(9600, SERIAL_8N1);
|
||||
#else
|
||||
if (this->_serial == &Serial) {
|
||||
#endif
|
||||
AgLog("Init Serial");
|
||||
this->_serial->begin(9600, SERIAL_8N1, bsp->Pms5003.uart_rx_pin,
|
||||
bsp->Pms5003.uart_tx_pin);
|
||||
#endif
|
||||
} else {
|
||||
/** Share with sensor air s8*/
|
||||
if (bsp->SenseAirS8.supported == false) {
|
||||
AgLog("Board [%d] PMS5003T_2 not supported", this->_boardDef);
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Share with sensor air s8*/
|
||||
AgLog("Init Serialx");
|
||||
this->_serial->begin(9600, SERIAL_8N1, bsp->SenseAirS8.uart_rx_pin,
|
||||
bsp->SenseAirS8.uart_tx_pin);
|
||||
@ -106,48 +107,33 @@ bool PMS5003T::begin(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read all package data then call to @ref getPMxxx to get the target
|
||||
* data
|
||||
*
|
||||
* @return true Success
|
||||
* @return false Failure
|
||||
*/
|
||||
bool PMS5003T::readData(void) {
|
||||
if (this->isBegin() == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return pms.readUntil(pmsData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read PM1.0 must call this function after @ref readData success
|
||||
*
|
||||
* @return int PM1.0 index
|
||||
*/
|
||||
int PMS5003T::getPm01Ae(void) { return pmsData.PM_AE_UG_1_0; }
|
||||
int PMS5003T::getPm01Ae(void) { return pms.getPM0_1(); }
|
||||
|
||||
/**
|
||||
* @brief Read PM2.5 must call this function after @ref readData success
|
||||
*
|
||||
* @return int PM2.5 index
|
||||
*/
|
||||
int PMS5003T::getPm25Ae(void) { return pmsData.PM_AE_UG_2_5; }
|
||||
int PMS5003T::getPm25Ae(void) { return pms.getPM2_5(); }
|
||||
|
||||
/**
|
||||
* @brief Read PM10.0 must call this function after @ref readData success
|
||||
*
|
||||
* @return int PM10.0 index
|
||||
*/
|
||||
int PMS5003T::getPm10Ae(void) { return pmsData.PM_AE_UG_10_0; }
|
||||
int PMS5003T::getPm10Ae(void) { return pms.getPM10(); }
|
||||
|
||||
/**
|
||||
* @brief Read PM3.0 must call this function after @ref readData success
|
||||
* @brief Read PM 0.3 Count must call this function after @ref readData success
|
||||
*
|
||||
* @return int PM3.0 index
|
||||
* @return int PM 0.3 Count index
|
||||
*/
|
||||
int PMS5003T::getPm03ParticleCount(void) { return pmsData.PM_RAW_0_3; }
|
||||
int PMS5003T::getPm03ParticleCount(void) { return pms.getCount0_3(); }
|
||||
|
||||
/**
|
||||
* @brief Convert PM2.5 to US AQI
|
||||
@ -155,7 +141,7 @@ int PMS5003T::getPm03ParticleCount(void) { return pmsData.PM_RAW_0_3; }
|
||||
* @param pm25 PM2.5 index
|
||||
* @return int PM2.5 US AQI
|
||||
*/
|
||||
int PMS5003T::convertPm25ToUsAqi(int pm25) { return pm25ToAQI(pm25); }
|
||||
int PMS5003T::convertPm25ToUsAqi(int pm25) { return pms.pm25ToAQI(pm25); }
|
||||
|
||||
/**
|
||||
* @brief Get temperature, Must call this method after @ref readData() success
|
||||
@ -163,7 +149,7 @@ int PMS5003T::convertPm25ToUsAqi(int pm25) { return pm25ToAQI(pm25); }
|
||||
* @return float Degree Celcius
|
||||
*/
|
||||
float PMS5003T::getTemperature(void) {
|
||||
float temp = pmsData.AMB_TMP;
|
||||
float temp = pms.getTemp();
|
||||
return correctionTemperature(temp / 10.0f);
|
||||
}
|
||||
|
||||
@ -173,7 +159,7 @@ float PMS5003T::getTemperature(void) {
|
||||
* @return float Percent (%)
|
||||
*/
|
||||
float PMS5003T::getRelativeHumidity(void) {
|
||||
float hum = pmsData.AMB_HUM;
|
||||
float hum = pms.getHum();
|
||||
return correctionRelativeHumidity(hum / 10.0f);
|
||||
}
|
||||
|
||||
@ -211,6 +197,30 @@ void PMS5003T::end(void) {
|
||||
AgLog("De-initialize");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check and read PMS sensor data. This method should be callack from
|
||||
* loop process to continoue check sensor data if it's available
|
||||
*/
|
||||
void PMS5003T::handle(void) { pms.handle(); }
|
||||
|
||||
/**
|
||||
* @brief Get sensor status
|
||||
*
|
||||
* @return true No problem
|
||||
* @return false Communication timeout or sensor has removed
|
||||
*/
|
||||
bool PMS5003T::isFailed(void) { return pms.isFailed(); }
|
||||
|
||||
/**
|
||||
* @brief Correct the PMS5003T relactive humidity
|
||||
*
|
||||
* @param inHum Input humidity
|
||||
* @return float Corrected humidity
|
||||
*/
|
||||
float PMS5003T::correctionRelativeHumidity(float inHum) {
|
||||
return inHum * 1.259 + 7.34;
|
||||
float hum = inHum * 1.259 + 7.34;
|
||||
if (hum > 100.0f) {
|
||||
hum = 100.0f;
|
||||
}
|
||||
return hum;
|
||||
}
|
||||
|
@ -19,7 +19,8 @@ public:
|
||||
#endif
|
||||
void end(void);
|
||||
|
||||
bool readData(void);
|
||||
void handle(void);
|
||||
bool isFailed(void);
|
||||
int getPm01Ae(void);
|
||||
int getPm25Ae(void);
|
||||
int getPm10Ae(void);
|
||||
@ -40,10 +41,8 @@ private:
|
||||
#else
|
||||
HardwareSerial *_serial;
|
||||
#endif
|
||||
|
||||
PMSBase pms;
|
||||
bool begin(void);
|
||||
PMS pms;
|
||||
PMS::DATA pmsData;
|
||||
bool isBegin(void);
|
||||
float correctionTemperature(float inTemp);
|
||||
float correctionRelativeHumidity(float inHum);
|
||||
|
@ -1,26 +0,0 @@
|
||||
#include "PMSUtils.h"
|
||||
|
||||
/**
|
||||
* @brief Convert PM2.5 to US AQI
|
||||
*
|
||||
* @param pm02
|
||||
* @return int
|
||||
*/
|
||||
int pm25ToAQI(int pm02) {
|
||||
if (pm02 <= 12.0)
|
||||
return ((50 - 0) / (12.0 - .0) * (pm02 - .0) + 0);
|
||||
else if (pm02 <= 35.4)
|
||||
return ((100 - 50) / (35.4 - 12.0) * (pm02 - 12.0) + 50);
|
||||
else if (pm02 <= 55.4)
|
||||
return ((150 - 100) / (55.4 - 35.4) * (pm02 - 35.4) + 100);
|
||||
else if (pm02 <= 150.4)
|
||||
return ((200 - 150) / (150.4 - 55.4) * (pm02 - 55.4) + 150);
|
||||
else if (pm02 <= 250.4)
|
||||
return ((300 - 200) / (250.4 - 150.4) * (pm02 - 150.4) + 200);
|
||||
else if (pm02 <= 350.4)
|
||||
return ((400 - 300) / (350.4 - 250.4) * (pm02 - 250.4) + 300);
|
||||
else if (pm02 <= 500.4)
|
||||
return ((500 - 400) / (500.4 - 350.4) * (pm02 - 350.4) + 400);
|
||||
else
|
||||
return 500;
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
#ifndef _PMS_UTILS_H_
|
||||
#define _PMS_UTILS_H_
|
||||
|
||||
int pm25ToAQI(int pm02);
|
||||
|
||||
#endif /** _PMS_UTILS_H_ */
|
@ -228,7 +228,7 @@ int16_t S8::getCo2(void) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int16_t co2 = 0;
|
||||
int16_t co2 = -1;
|
||||
|
||||
// Ask CO2 value
|
||||
sendCommand(MODBUS_FUNC_READ_INPUT_REGISTERS, MODBUS_IR4, 0x0001);
|
||||
@ -649,11 +649,25 @@ bool S8::init(int txPin, int rxPin, uint32_t baud) {
|
||||
uart->begin(baud);
|
||||
this->_uartStream = uart;
|
||||
#else
|
||||
#if ARDUINO_USB_CDC_ON_BOOT
|
||||
/** The 'Serial0' can ont configure tx, rx pin, only use as default */
|
||||
if (_serial == &Serial0) {
|
||||
AgLog("Init on 'Serial0'");
|
||||
_serial->begin(baud, SERIAL_8N1);
|
||||
} else {
|
||||
AgLog("Init on 'Serialx'");
|
||||
this->_serial->begin(baud, SERIAL_8N1, rxPin, txPin);
|
||||
}
|
||||
this->_uartStream = this->_serial;
|
||||
#else
|
||||
AgLog("Init on 'Serialx'");
|
||||
this->_serial->begin(baud, SERIAL_8N1, rxPin, txPin);
|
||||
this->_uartStream = this->_serial;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/** Check communication by get firmware version */
|
||||
delay(100);
|
||||
char fwVers[11];
|
||||
this->_isBegin = true;
|
||||
this->getFirmwareVersion(fwVers);
|
||||
@ -712,7 +726,7 @@ uint8_t S8::uartReadBytes(uint8_t max_bytes, uint32_t timeout_ms) {
|
||||
|
||||
#if defined(ESP32)
|
||||
// Relax 5ms to avoid watchdog reset
|
||||
vTaskDelay(pdMS_TO_TICKS(5));
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
#endif
|
||||
}
|
||||
return nb;
|
||||
|
@ -91,7 +91,7 @@ void Sgp41::handle(void) {
|
||||
if (getRawSignal(srawVoc, srawNox)) {
|
||||
nox = noxAlgorithm()->process(srawNox);
|
||||
tvoc = vocAlgorithm()->process(srawVoc);
|
||||
AgLog("Polling SGP41 success: tvoc: %d, nox: %d", tvoc, nox);
|
||||
// AgLog("Polling SGP41 success: tvoc: %d, nox: %d", tvoc, nox);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -121,9 +121,10 @@ void Sgp41::_handle(void) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
if (getRawSignal(srawVoc, srawNox)) {
|
||||
tvocRaw = srawVoc;
|
||||
noxRaw = srawNox;
|
||||
nox = noxAlgorithm()->process(srawNox);
|
||||
tvoc = vocAlgorithm()->process(srawVoc);
|
||||
AgLog("Polling SGP41 success: tvoc: %d, nox: %d", tvoc, nox);
|
||||
// AgLog("Polling SGP41 success: tvoc: %d, nox: %d", tvoc, nox);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -249,3 +250,23 @@ bool Sgp41::_noxConditioning(void) {
|
||||
* @return int
|
||||
*/
|
||||
int Sgp41::getTvocRaw(void) { return tvocRaw; }
|
||||
|
||||
/**
|
||||
* @brief Get NOX raw value
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int Sgp41::getNoxRaw(void) { return noxRaw; }
|
||||
|
||||
/**
|
||||
* @brief Set compasation temperature and humidity to calculate TVOC and NOx
|
||||
* index
|
||||
*
|
||||
* @param temp Temperature
|
||||
* @param hum Humidity
|
||||
*/
|
||||
void Sgp41::setCompensationTemperatureHumidity(float temp, float hum) {
|
||||
defaultT = static_cast<uint16_t>((temp + 45) * 65535 / 175);
|
||||
defaultRh = static_cast<uint16_t>(hum * 65535 / 100);
|
||||
AgLog("Update: defaultT: %d, defaultRh: %d", defaultT, defaultRh);
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ public:
|
||||
int getTvocIndex(void);
|
||||
int getNoxIndex(void);
|
||||
int getTvocRaw(void);
|
||||
int getNoxRaw(void);
|
||||
void setCompensationTemperatureHumidity(float temp, float hum);
|
||||
|
||||
private:
|
||||
bool onConditioning = true;
|
||||
@ -39,6 +41,7 @@ private:
|
||||
int tvoc = 0;
|
||||
int tvocRaw;
|
||||
int nox = 0;
|
||||
int noxRaw;
|
||||
#if defined(ESP8266)
|
||||
uint32_t conditioningPeriod;
|
||||
uint8_t conditioningCount;
|
||||
|
Reference in New Issue
Block a user