Compare commits

...

58 Commits
3.0.6 ... 3.0.9

Author SHA1 Message Date
3889aa660e Merge pull request #88 from airgradienthq/develop
Bugfix and add example
2024-03-24 20:20:22 +07:00
efe68a54a4 update version 3.0.8 to 3.0.9 2024-03-24 20:04:48 +07:00
a960d086e1 Update instruction comment and clean code. 2024-03-24 08:51:39 +07:00
3537a3012c fix: WiFi connect after LedBar test with button. And update comments 2024-03-24 08:51:14 +07:00
d255e6ad04 Update workflows for OneOpenAir 2024-03-24 07:45:32 +07:00
e47096feac Limit show CO2 index within 4 character number (max = 9999) 2024-03-24 07:43:40 +07:00
063612e08f fix: Ledbar off in offline mode 2024-03-24 07:40:26 +07:00
7cfa722684 update show log log message and fix bug 2024-03-23 17:44:11 +07:00
53285ab4ff Combine One and OpenAir into one: Just worked 2024-03-23 16:58:17 +07:00
f46c66a77f Merge pull request #87 from airgradienthq/develop
fix: After factory reset LEDBar is off
2024-03-18 08:48:28 +07:00
9c8ae315a0 fix: After factory reset LEDBar is off 2024-03-18 08:45:16 +07:00
3ef438412f Merge pull request #85 from airgradienthq/develop
Develop: optimize and bugfix
2024-03-16 12:00:20 +07:00
ce1373141a Updated version number 2024-03-16 11:13:04 +07:00
aceecde7b6 Format code 2024-03-16 10:02:59 +07:00
6926abd6f7 Standardize result of /measures/current for OpenAir 2024-03-16 10:02:46 +07:00
15dec40dfc Merge remote-tracking branch 'origin/master' into develop 2024-03-16 09:11:25 +07:00
4a36cf0c13 Merge pull request #83 from austvik/master
Standardize result of /measures/current
2024-03-16 09:08:53 +07:00
ecc92a6824 Merge pull request #81 from dechamps/celsius
Fix OpenMetrics typo: celcius -> celsius
2024-03-16 09:07:49 +07:00
3d243cb8ca Revert: version 3.0.7 2024-03-16 08:52:50 +07:00
471448a0f1 Prevent reboot in offline mode 2024-03-16 08:50:43 +07:00
ea3e976232 update next version 3.0.7 2024-03-16 08:21:44 +07:00
87f2463233 Update comment 2024-03-16 08:21:14 +07:00
49c7877ec3 Standardize result of /measures/current
Makes it easier for integrations which talks both to the cloud and to the
Cloud API to support the same format for reading current measures.

In practice this adds the firmware version and led mode to the output, and
changes the writing of pm003Count, tvocIndex and noxIndex to use the same
spelling as for the documented cloud API.
2024-03-15 19:22:00 +01:00
be1a9778e6 Fix: PMS Read Failed 2024-03-14 21:17:43 +07:00
ed1d45cea1 Fix OpenMetrics typo: celcius -> celsius
See #78
2024-03-10 11:02:04 +00:00
db31b39ce2 Merge pull request #80 from airgradienthq/develop
Develop
2024-03-10 12:01:35 +07:00
d92d312b0c add openmetrics 2024-03-10 11:57:52 +07:00
6837529096 add compensation temperature and humidity for SGP41 2024-03-10 11:20:52 +07:00
b94ae9eff0 Merge remote-tracking branch 'origin/master' into develop 2024-03-10 10:10:58 +07:00
1810c0f355 Merge pull request #78 from gouthamve/better-openmetrics-names
[ONE/Prometheus] Use full unit in temperature metric
2024-03-10 09:58:09 +07:00
eb0f45750d Merge pull request #79 from austvik/master
Fix MDNS Service Discovery:
2024-03-10 09:53:25 +07:00
9ae8fb2355 fix: Completely turn off LEDbar 2024-03-10 09:34:04 +07:00
512509c2e2 Print HTTP Response in logs in case of error 2024-03-10 08:44:49 +07:00
66815f590c Merge remote-tracking branch 'origin/develop' into develop 2024-03-10 08:27:40 +07:00
f60e9bbe3e Fix MDNS Service Discovery:
- Underscore before names per https://github.com/espressif/arduino-esp32/issues/962
- Only one service per port

The combination of both changes is needed to make the service discoverable in OpenHAB

The removal of the published http service is maybe something you don't want,
but as long as it doesn't serve web pages it is maybe OK?
2024-03-08 23:37:12 +01:00
f361e3c9a9 [ONE/Prometheus] Use full unit in temperature metric
Please see: https://prometheus.io/docs/practices/naming/#base-units

Also, thanks a lot for this support, I had my own exporter that I can now delete :)

Signed-off-by: gouthamve <gouthamve@gmail.com>
2024-03-08 19:02:19 +01:00
e76dcf07c8 Updated log texts 2024-03-08 13:47:28 +07:00
e6fe489be7 Updated display texts 2024-03-08 13:29:17 +07:00
9ddb606a00 Merge pull request #77 from airgradienthq/develop
Turn all LED on while init
2024-03-07 21:57:16 +07:00
cd5ee2da18 Turn all LED on while init 2024-03-07 21:54:22 +07:00
4c42a9ddc8 Merge pull request #76 from airgradienthq/develop
Develop
2024-03-07 21:49:10 +07:00
78b1b0975c update: Connect to Dashboard show also S/N, remove test code 2024-03-07 21:48:13 +07:00
d99881aa46 update: Connect to Dashboard show also S/N 2024-03-07 21:44:46 +07:00
df937fe65f update mDNS servicce and attribute 2024-03-07 21:30:42 +07:00
dc742d3c92 Updated log messages and version number 2024-03-07 16:11:36 +07:00
a7b2ad526f Merge pull request #74 from airgradienthq/develop
fix: `O-1PS` not recognize without `SGP` sensor
2024-03-06 18:13:34 +07:00
bb804b9f6a fix: O-1PS not recognize without SGP sensor 2024-03-06 18:02:32 +07:00
1a00073cf6 Merge pull request #73 from airgradienthq/develop
Develop
2024-03-06 17:24:52 +07:00
469d07a2d6 fix: O-1PS not recognized 2024-03-06 17:20:55 +07:00
6cf5e31843 add nox_index to payload 2024-03-03 22:24:58 +07:00
3f1da6387b Update mDNS service model attribute 2024-03-03 22:03:23 +07:00
99b4858f1d Merge pull request #72 from airgradienthq/develop
Develop: update workflows build configure
2024-03-03 21:57:41 +07:00
4374c980ec Update workflows example build configure 2024-03-03 21:51:53 +07:00
ded7637b06 Update workflows example build configure 2024-03-03 21:47:50 +07:00
6a79ab6b5b Update workflows example build configure 2024-03-03 21:46:07 +07:00
7baff75524 Merge pull request #71 from dechamps/checkpr
Fix check workflow failing on pull requests
2024-03-03 21:38:01 +07:00
d421c94647 Merge branch 'master' into develop 2024-03-03 21:35:25 +07:00
8fcf257726 Fix check workflow failing on pull requests
This fixes the following check GitHub Actions workflow failure that
would otherwise occur on pull requests (but not on pushes/branches):

  Error installing Git Library: Library install failed: object not found
2024-02-29 23:52:36 +00:00
23 changed files with 4199 additions and 524 deletions

View File

@ -11,6 +11,7 @@ jobs:
- "TestCO2"
- "TestPM"
- "TestSht"
- "OneOpenAir"
fqbn:
- "esp8266:esp8266:d1_mini"
- "esp32:esp32:esp32c3"
@ -22,12 +23,14 @@ 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"
- example: "OneOpenAir"
fqbn: "esp8266:esp8266:d1_mini"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@ -40,6 +43,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"

View File

@ -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

View File

@ -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;

View File

@ -25,7 +25,7 @@ Important flashing settings:
- Flash frequency "80Mhz"
- Flash mode "QIO"
- Flash size "4MB"
- Partition scheme "Default 4MB with spiffs (1.2MB APP/1,5MB SPIFFS)"
- Partition scheme "Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS)"
- JTAG adapter "Disabled"
Configuration parameters, e.g. Celsius / Fahrenheit or PM unit (US AQI vs ug/m3)
@ -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
*
@ -482,12 +493,8 @@ private:
void loadConfig(void) {
if (EEPROM.readBytes(0, &config, sizeof(config)) != sizeof(config)) {
config.inF = false;
config.inUSAQI = false;
memset(config.models, 0, sizeof(config.models));
memset(config.mqttBrokers, 0, sizeof(config.mqttBrokers));
Serial.println("Load configure failed");
defaultConfig();
} else {
uint32_t sum = 0;
uint8_t *data = (uint8_t *)&config;
@ -514,6 +521,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 +588,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 +691,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 +725,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 +734,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 +748,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 +766,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");
@ -770,7 +804,7 @@ void setup() {
/**
* Send first data to ping server and get server configuration
*/
if (WiFi.status() == WL_CONNECTED) {
if (WiFi.isConnected()) {
webServerInit();
/** MQTT init */
@ -794,7 +828,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 +866,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 +956,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 +1073,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 +1086,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 +1100,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 +1127,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 +1135,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 +1170,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 +1209,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 +1234,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 +1300,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 +1406,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());
@ -1364,6 +1453,10 @@ static void displayShowDashboard(String err) {
/** Show CO2 value */
u8g2.setFont(u8g2_font_t0_22b_tf);
if (co2Ppm > 0) {
int val = 9999;
if (co2Ppm < 10000) {
val = co2Ppm;
}
sprintf(strBuf, "%d", co2Ppm);
} else {
sprintf(strBuf, "%s", "-");
@ -1463,7 +1556,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 +1827,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 +1873,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 +2179,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 +2210,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 +2289,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 +2303,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 +2349,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");
}
}

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,7 @@ Important flashing settings:
- Flash frequency "80Mhz"
- Flash mode "QIO"
- Flash size "4MB"
- Partition scheme "Default 4MB with spiffs (1.2MB APP/1,5MB SPIFFS)"
- Partition scheme "Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS)"
- JTAG adapter "Disabled"
If you have any questions please visit our forum at
@ -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;

View File

@ -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");

View File

@ -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) {

View File

@ -1,5 +1,5 @@
name=AirGradient Air Quality Sensor
version=3.0.6
version=3.0.9
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.

View File

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

View File

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

View File

@ -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;

View File

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

View File

@ -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_ */

View File

@ -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(); }

View File

@ -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);
};

View File

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

View File

@ -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);

View File

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

View File

@ -1,6 +0,0 @@
#ifndef _PMS_UTILS_H_
#define _PMS_UTILS_H_
int pm25ToAQI(int pm02);
#endif /** _PMS_UTILS_H_ */

View File

@ -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;

View File

@ -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);
}

View File

@ -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;