Compare commits

...

44 Commits
3.0.4 ... 3.0.6

Author SHA1 Message Date
d78205aa20 Changed measurement and update interval for Open Air. Added fw version to logs. 2024-03-02 15:04:30 +07:00
c1228bbd06 Changed measurement and update intervalls 2024-03-02 14:05:00 +07:00
1eb43f684b Fix SHT read error. 2024-03-02 13:41:08 +07:00
4798e44cb7 MDNS replace board with model 2024-03-01 21:56:21 +07:00
a867e9af38 revert SENSOR_TEMP_HUM_UPDATE_INTERVAL value 2024-03-01 19:51:58 +07:00
a59d5a1bb8 Update check.yml
Adjusted new name for example files
2024-02-29 20:05:16 +07:00
b4d6006678 Changed PM polling frequency for Open Air to 2s 2024-02-29 18:30:52 +07:00
236c5bab84 Added offline mode after LED test. 2024-02-29 18:15:22 +07:00
852fdc4360 Renamed example file. 2024-02-29 17:58:58 +07:00
f7e85a92e8 Uped version Nr. Renamed examples. 2024-02-29 17:58:14 +07:00
e99fc2ecdc Merge pull request #69 from airgradienthq/develop
Update API naming
2024-02-29 15:26:53 +07:00
67785ed99b Update API naming 2024-02-29 15:20:19 +07:00
45ac4f116b Merge pull request #68 from airgradienthq/develop
Develop
2024-02-29 15:12:28 +07:00
173e3caf2f Update API naming 2024-02-29 15:07:23 +07:00
351af57591 move pm25ToAQI into PMSUtils 2024-02-29 14:45:44 +07:00
0bda7a1c4b Change pmPoll interval from 5s to 2s 2024-02-29 14:00:19 +07:00
9a31c107fa add get BoardName from AirGradient library 2024-02-29 13:58:48 +07:00
5449fa15ea Merge branch 'master' into develop 2024-02-29 13:50:46 +07:00
3e4e2affa8 Merge pull request #59 from dechamps/prometheus
Add support for Prometheus/OpenMetrics to One V9
2024-02-29 13:47:49 +07:00
d3a242a0b7 Merge branch 'master' into develop 2024-02-29 13:45:45 +07:00
e5e2887c4d Merge pull request #58 from dechamps/githubactions
Add compile check GitHub Actions workflow
2024-02-29 13:41:50 +07:00
4eda2e4cb5 Merge pull request #67 from airgradienthq/develop
Develop
2024-02-29 10:57:10 +07:00
fcee721d58 Merge pull request #66 from airgradienthq/feature/add-mdns-attributes
Add mDNS attribute
2024-02-29 10:56:48 +07:00
7d12e63e34 Merge pull request #65 from airgradienthq/feature/relative-humidity-pms5003t-formula
Feature/relative humidity pms5003t formula
2024-02-29 10:50:21 +07:00
8ff8b7929e Update correction relative humdity formula 2024-02-29 10:49:38 +07:00
66c53daed6 Implement relative humidity correction 2024-02-29 10:39:17 +07:00
5de3a34dd0 Add mDNS attribute 2024-02-29 10:22:05 +07:00
b94112e22a Merge pull request #64 from airgradienthq/feature/led-bar-color-for-pms-and-co2
Feature/led bar color for pms and co2
2024-02-29 09:38:13 +07:00
99e925e7bd Merge pull request #63 from airgradienthq/hotfix/build-fail-if-set-core-debug-level-to-verbose
fix: build fail if set `Core Debug Level` to `Verbose`, #57
2024-02-29 09:34:26 +07:00
d8cba0d346 fix: build fail if set Core Debug Level to Verbose, #57 2024-02-29 09:33:33 +07:00
39de897621 Merge pull request #56 from dechamps/includecap
Fix path capitalization
2024-02-29 09:05:30 +07:00
9f1a793848 Merge pull request #62 from airgradienthq/hotfix/compile-fail-cause-value-type
fix: BoardDef pin number invalid type, #51
2024-02-29 08:49:03 +07:00
be9ba88d52 fix: BoardDef pin number invalid type, #51 2024-02-29 08:45:38 +07:00
0084b6fb91 fix: update cloud sync json noxIndex by nox_index 2024-02-29 08:39:05 +07:00
75b579bafa Merge pull request #61 from airgradienthq/feature/factory-config-reset
add Factory RESET
2024-02-26 17:51:17 +07:00
cf5ff99d8a add Factory RESET 2024-02-26 17:48:22 +07:00
ea204d90b1 Merge pull request #60 from airgradienthq/bugfix/mqtt-sending-interval
fix: Mqtt sending interval
2024-02-26 15:56:30 +07:00
13f6c2c747 fix: Mqtt sending interval 2024-02-26 15:55:33 +07:00
760f827d0d Add support for Prometheus/OpenMetrics to One V9
This commit adds a new feature to the One V9 (ONE_I-9PSL) firmware:
support for exposing metrics to Prometheus (or any other ingestor
compatible with the OpenMetrics format).

With this change, the AirGradient device will make metrics available
on the standard HTTP /metrics endpoint, out-of-the-box, with no need to
do anything else. All the user has to do is add their device address as
a target to their scrape config on their Prometheus server.

For more information on Prometheus and OpenMetrics, see:

- https://prometheus.io/docs/instrumenting/exposition_formats/
- https://openmetrics.io/
- https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md

This obsoletes projects such as:

- ebfa8d0ac6/AirGradient-DIY
- https://forum.airgradient.com/t/prometheus-integration/1504
2024-02-25 17:51:58 +00:00
8c8e0d4dea Add compile check GitHub Actions workflow
This commit adds a GitHub Actions workflow that, on every push/pull
request, will check that every single example successfully compiles on
every board it supports. That is, it it will check compilation on:

- ESP8266 for BASIC_v4, TestCO2, TestPM and TestSht
- ESP32 for ONE_I-9PSL, Open_Air, TestCO2, TestPM and TestSht

This provides the first building block towards a Continuous Integration
(CI) pipeline and prevents build breakages from making it to master.

Ideally this should also run tests on the examples (i.e. verifying that
the example boots successfully and sends metrics), but for now this will
at least ensure the build is not obviously broken.
2024-02-25 12:12:16 +00:00
b749495bf4 Fix path capitalization
This fixes build breakage on case-sensitive filesystems (e.g. Linux)
with errors like:

  src/Display/Display.h:4:10: fatal error: ../main/BoardDef.h: No such file or directory
2024-02-24 21:43:03 +00:00
7e3eabf09f Remove old color set process for PM 2024-02-21 21:18:44 +07:00
e636876c9b Update .gitignore 2024-02-21 21:16:12 +07:00
68953d7390 Update LED for PM and CO2 2024-02-21 21:16:01 +07:00
20 changed files with 749 additions and 188 deletions

51
.github/workflows/check.yml vendored Normal file
View File

@ -0,0 +1,51 @@
on: [push, pull_request]
jobs:
compile:
strategy:
fail-fast: false
matrix:
example:
- "BASIC"
- "ONE"
- "Open_Air"
- "TestCO2"
- "TestPM"
- "TestSht"
fqbn:
- "esp8266:esp8266:d1_mini"
- "esp32:esp32:esp32c3"
include:
- fqbn: "esp8266:esp8266:d1_mini"
core: "esp8266:esp8266@3.1.2"
core_url: "https://arduino.esp8266.com/stable/package_esp8266com_index.json"
- fqbn: "esp32:esp32:esp32c3"
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"
fqbn: "esp32:esp32:esp32c3"
- example: "ONE_I-9PSL"
fqbn: "esp8266:esp8266:d1_mini"
- example: "Open_Air"
fqbn: "esp8266:esp8266:d1_mini"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run:
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh |
sh -s 0.35.3
- run: bin/arduino-cli --verbose core install '${{ matrix.core }}'
--additional-urls '${{ matrix.core_url }}'
- run: bin/arduino-cli --verbose lib install
WiFiManager@2.0.16-rc.2
Arduino_JSON@0.2.0
U8g2@2.34.22
- run: bin/arduino-cli --verbose lib install --git-url .
env:
ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL: "true"
- run: bin/arduino-cli --verbose compile 'examples/${{ matrix.example }}'
--fqbn '${{ matrix.fqbn }}' --board-options '${{ matrix.board_options }}'
# TODO: at this point it would be a good idea to run some smoke tests on
# the resulting image (e.g. that it boots successfully and sends metrics)
# but that would either require a high fidelity device emulator, or a
# "hardware lab" runner that is directly connected to a relevant device.

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
.vscode
*.DS_Store *.DS_Store
build
.vscode

View File

@ -50,7 +50,7 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */ #define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
#define SENSOR_CO2_UPDATE_INTERVAL 5000 /** ms */ #define SENSOR_CO2_UPDATE_INTERVAL 5000 /** ms */
#define SENSOR_PM_UPDATE_INTERVAL 5000 /** ms */ #define SENSOR_PM_UPDATE_INTERVAL 5000 /** ms */
#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 5000 /** ms */ #define SENSOR_TEMP_HUM_UPDATE_INTERVAL 2000 /** ms */
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */ #define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
#define WIFI_HOTSPOT_PASSWORD_DEFAULT \ #define WIFI_HOTSPOT_PASSWORD_DEFAULT \
"cleanair" /** default WiFi AP password \ "cleanair" /** default WiFi AP password \
@ -115,7 +115,7 @@ public:
* @return true Success * @return true Success
* @return false Failure * @return false Failure
*/ */
bool pollServerConfig(String id) { bool fetchServerConfiguration(String id) {
String uri = String uri =
"http://hw.airgradient.com/sensors/airgradient:" + id + "/one/config"; "http://hw.airgradient.com/sensors/airgradient:" + id + "/one/config";
@ -368,10 +368,10 @@ static bool wifiHasConfig = false; /** */
static void boardInit(void); static void boardInit(void);
static void failedHandler(String msg); static void failedHandler(String msg);
static void co2Calibration(void); static void co2Calibration(void);
static void serverConfigPoll(void); static void updateServerConfiguration(void);
static void co2Poll(void); static void co2Update(void);
static void pmPoll(void); static void pmUpdate(void);
static void tempHumPoll(void); static void tempHumUpdate(void);
static void sendDataToServer(void); static void sendDataToServer(void);
static void dispHandler(void); static void dispHandler(void);
static String getDevId(void); static String getDevId(void);
@ -382,12 +382,12 @@ bool hasSensorS8 = true;
bool hasSensorPMS = true; bool hasSensorPMS = true;
bool hasSensorSHT = true; bool hasSensorSHT = true;
int pmFailCount = 0; int pmFailCount = 0;
AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL, serverConfigPoll); AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL, updateServerConfiguration);
AgSchedule serverSchedule(SERVER_SYNC_INTERVAL, sendDataToServer); AgSchedule serverSchedule(SERVER_SYNC_INTERVAL, sendDataToServer);
AgSchedule dispSchedule(DISP_UPDATE_INTERVAL, dispHandler); AgSchedule dispSchedule(DISP_UPDATE_INTERVAL, dispHandler);
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Poll); AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update);
AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, pmPoll); AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, pmUpdate);
AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumPoll); AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumUpdate);
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
@ -413,7 +413,7 @@ void setup() {
wifiHasConfig = true; wifiHasConfig = true;
sendPing(); sendPing();
agServer.pollServerConfig(getDevId()); agServer.fetchServerConfiguration(getDevId());
if (agServer.isCo2Calib()) { if (agServer.isCo2Calib()) {
co2Calibration(); co2Calibration();
} }
@ -576,8 +576,8 @@ static void co2Calibration(void) {
} }
} }
static void serverConfigPoll(void) { static void updateServerConfiguration(void) {
if (agServer.pollServerConfig(getDevId())) { if (agServer.fetchServerConfiguration(getDevId())) {
if (agServer.isCo2Calib()) { if (agServer.isCo2Calib()) {
if (hasSensorS8) { if (hasSensorS8) {
co2Calibration(); co2Calibration();
@ -609,12 +609,12 @@ static void serverConfigPoll(void) {
} }
} }
static void co2Poll() { static void co2Update() {
co2Ppm = ag.s8.getCo2(); co2Ppm = ag.s8.getCo2();
Serial.printf("CO2 index: %d\r\n", co2Ppm); Serial.printf("CO2 index: %d\r\n", co2Ppm);
} }
void pmPoll() { void pmUpdate() {
if (ag.pms5003.readData()) { if (ag.pms5003.readData()) {
pm25 = ag.pms5003.getPm25Ae(); pm25 = ag.pms5003.getPm25Ae();
Serial.printf("PMS2.5: %d\r\n", pm25); Serial.printf("PMS2.5: %d\r\n", pm25);
@ -628,7 +628,7 @@ void pmPoll() {
} }
} }
static void tempHumPoll() { static void tempHumUpdate() {
if (ag.sht.measure()) { if (ag.sht.measure()) {
temp = ag.sht.getTemperature(); temp = ag.sht.getTemperature();
hum = ag.sht.getRelativeHumidity(); hum = ag.sht.getRelativeHumidity();

View File

@ -90,14 +90,15 @@ enum {
#define WIFI_CONNECT_RETRY_MS 10000 /** ms */ #define WIFI_CONNECT_RETRY_MS 10000 /** ms */
#define LED_BAR_COUNT_INIT_VALUE (-1) /** */ #define LED_BAR_COUNT_INIT_VALUE (-1) /** */
#define LED_BAR_ANIMATION_PERIOD 100 /** ms */ #define LED_BAR_ANIMATION_PERIOD 100 /** ms */
#define DISP_UPDATE_INTERVAL 5000 /** ms */ #define DISP_UPDATE_INTERVAL 2500 /** ms */
#define SERVER_CONFIG_UPDATE_INTERVAL 30000 /** ms */ #define SERVER_CONFIG_UPDATE_INTERVAL 15000 /** ms */
#define SERVER_SYNC_INTERVAL 60000 /** ms */ #define SERVER_SYNC_INTERVAL 60000 /** ms */
#define MQTT_SYNC_INTERVAL 60000 /** ms */
#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */ #define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */ #define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
#define SENSOR_CO2_UPDATE_INTERVAL 5000 /** ms */ #define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */
#define SENSOR_PM_UPDATE_INTERVAL 5000 /** ms */ #define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 5000 /** ms */ #define SENSOR_TEMP_HUM_UPDATE_INTERVAL 2000 /** ms */
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */ #define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
#define WIFI_HOTSPOT_PASSWORD_DEFAULT \ #define WIFI_HOTSPOT_PASSWORD_DEFAULT \
"cleanair" /** default WiFi AP password \ "cleanair" /** default WiFi AP password \
@ -153,13 +154,27 @@ public:
} }
/** /**
* @brief Get server configuration * @brief Reset local config into default value.
*
*/
void defaultReset(void) {
config.inF = false;
config.inUSAQI = false;
memset(config.models, 0, sizeof(config.models));
memset(config.mqttBrokers, 0, sizeof(config.mqttBrokers));
config.useRGBLedBar = UseLedBarOff;
saveConfig();
}
/**
* @brief Fetch server configuration, if get sucessed and configuratrion
* parameter has changed store into local storage
* *
* @param id Device ID * @param id Device ID
* @return true Success * @return true Success
* @return false Failure * @return false Failure
*/ */
bool pollServerConfig(String id) { bool fetchServerConfiguration(String id) {
String uri = String uri =
"http://hw.airgradient.com/sensors/airgradient:" + id + "/one/config"; "http://hw.airgradient.com/sensors/airgradient:" + id + "/one/config";
@ -631,6 +646,7 @@ public:
int connectionFailedCount(void) { return connectFailedCount; } int connectionFailedCount(void) { return connectFailedCount; }
}; };
AgMqtt agMqtt; AgMqtt agMqtt;
static TaskHandle_t mqttTask = NULL;
/** Create airgradient instance for 'ONE_INDOOR' board */ /** Create airgradient instance for 'ONE_INDOOR' board */
AirGradient ag(ONE_INDOOR); AirGradient ag(ONE_INDOOR);
@ -665,7 +681,7 @@ static String wifiSSID = "";
static void boardInit(void); static void boardInit(void);
static void failedHandler(String msg); static void failedHandler(String msg);
static void serverConfigPoll(void); static void updateServerConfiguration(void);
static void co2Calibration(void); static void co2Calibration(void);
static void setRGBledPMcolor(int pm25Value); static void setRGBledPMcolor(int pm25Value);
static void ledSmHandler(int sm); static void ledSmHandler(int sm);
@ -675,15 +691,17 @@ static void sensorLedColorHandler(void);
static void appLedHandler(void); static void appLedHandler(void);
static void appDispHandler(void); static void appDispHandler(void);
static void updateWiFiConnect(void); static void updateWiFiConnect(void);
static void updateDispLedBar(void); static void displayAndLedBarUpdate(void);
static void tvocPoll(void); static void tvocUpdate(void);
static void pmPoll(void); static void pmUpdate(void);
static void sendDataToServer(void); static void sendDataToServer(void);
static void tempHumPoll(void); static void tempHumUpdate(void);
static void co2Poll(void); static void co2Update(void);
static void showNr(void); static void showNr(void);
static void webServerInit(void); static void webServerInit(void);
static String getServerSyncData(bool localServer); static String getServerSyncData(bool localServer);
static void createMqttTask(void);
static void factoryConfigReset(void);
/** Init schedule */ /** Init schedule */
bool hasSensorS8 = true; bool hasSensorS8 = true;
@ -691,13 +709,16 @@ bool hasSensorPMS = true;
bool hasSensorSGP = true; bool hasSensorSGP = true;
bool hasSensorSHT = true; bool hasSensorSHT = true;
int pmFailCount = 0; int pmFailCount = 0;
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, updateDispLedBar); uint32_t factoryBtnPressTime = 0;
AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL, serverConfigPoll); String mdnsModelName = "";
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, displayAndLedBarUpdate);
AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL,
updateServerConfiguration);
AgSchedule serverSchedule(SERVER_SYNC_INTERVAL, sendDataToServer); AgSchedule serverSchedule(SERVER_SYNC_INTERVAL, sendDataToServer);
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Poll); AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update);
AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, pmPoll); AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, pmUpdate);
AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumPoll); AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumUpdate);
AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, tvocPoll); AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, tvocUpdate);
void setup() { void setup() {
EEPROM.begin(512); EEPROM.begin(512);
@ -715,7 +736,8 @@ void setup() {
u8g2.begin(); u8g2.begin();
/** Show boot display */ /** Show boot display */
displayShowText("One V9", "Lib Ver: " + ag.getVersion(), ""); Serial.println("Firmware Version: "+ag.getVersion());
displayShowText("One V9", "FW Ver: " + ag.getVersion(), "");
delay(DISPLAY_DELAY_SHOW_CONTENT_MS); delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
/** Init sensor */ /** Init sensor */
@ -725,7 +747,7 @@ void setup() {
agServer.begin(); agServer.begin();
/** Run LED test on start up */ /** Run LED test on start up */
displayShowText("Press now for", "LED test", ""); displayShowText("Press now for", "LED test &", "offline mode");
bool test = false; bool test = false;
uint32_t stime = millis(); uint32_t stime = millis();
while (1) { while (1) {
@ -741,9 +763,9 @@ void setup() {
} }
if (test) { if (test) {
ledTest(); ledTest();
} else {
connectToWifi();
} }
/** WIFI connect */
connectToWifi();
/** /**
* Send first data to ping server and get server configuration * Send first data to ping server and get server configuration
@ -754,6 +776,7 @@ void setup() {
/** MQTT init */ /** MQTT init */
if (agServer.getMqttBroker().isEmpty() == false) { if (agServer.getMqttBroker().isEmpty() == false) {
if (agMqtt.begin(agServer.getMqttBroker())) { if (agMqtt.begin(agServer.getMqttBroker())) {
createMqttTask();
Serial.println("MQTT client init success"); Serial.println("MQTT client init success");
} else { } else {
Serial.println("MQTT client init failure"); Serial.println("MQTT client init failure");
@ -766,7 +789,7 @@ void setup() {
Serial.println(WiFi.localIP()); Serial.println(WiFi.localIP());
/** Get first connected to wifi */ /** Get first connected to wifi */
agServer.pollServerConfig(getDevId()); agServer.fetchServerConfiguration(getDevId());
if (agServer.isConfigFailed()) { if (agServer.isConfigFailed()) {
dispSmHandler(APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED); dispSmHandler(APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED);
ledSmHandler(APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED); ledSmHandler(APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED);
@ -797,6 +820,7 @@ void loop() {
} }
if (hasSensorSHT) { if (hasSensorSHT) {
delay(100);
tempHumSchedule.run(); tempHumSchedule.run();
} }
@ -806,6 +830,9 @@ void loop() {
/** Check for handle WiFi reconnect */ /** Check for handle WiFi reconnect */
updateWiFiConnect(); updateWiFiConnect();
/** factory reset handle */
factoryConfigReset();
} }
static void setTestColor(char color) { static void setTestColor(char color) {
@ -881,7 +908,7 @@ static void ledTest2Min(void) {
} }
} }
static void co2Poll(void) { static void co2Update(void) {
co2Ppm = ag.s8.getCo2(); co2Ppm = ag.s8.getCo2();
Serial.printf("CO2 index: %d\r\n", co2Ppm); Serial.printf("CO2 index: %d\r\n", co2Ppm);
} }
@ -892,6 +919,141 @@ void webServerMeasureCurrentGet(void) {
webServer.send(200, "application/json", getServerSyncData(true)); 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));
}
if (hasSensorPMS) {
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_index",
"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 (hasSensorSHT) {
if (temp > -1001) {
add_metric("temperature",
"The ambient temperature as measured by the AirGradient SHT "
"sensor, in degrees Celsius",
"gauge", "degc");
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) { void webServerHandler(void *param) {
for (;;) { for (;;) {
webServer.handleClient(); webServer.handleClient();
@ -906,8 +1068,16 @@ static void webServerInit(void) {
} }
webServer.on("/measures/current", HTTP_GET, webServerMeasureCurrentGet); 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(); webServer.begin();
MDNS.addService("http", "tcp", 80); 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());
if (xTaskCreate(webServerHandler, "webserver", 1024 * 4, NULL, 5, NULL) != if (xTaskCreate(webServerHandler, "webserver", 1024 * 4, NULL, 5, NULL) !=
pdTRUE) { pdTRUE) {
@ -949,7 +1119,7 @@ static String getServerSyncData(bool localServer) {
root["tvoc_raw"] = tvocRawIndex; root["tvoc_raw"] = tvocRawIndex;
} }
if (noxIndex >= 0) { if (noxIndex >= 0) {
root["noxIndex"] = noxIndex; root["nox_index"] = noxIndex;
} }
} }
if (hasSensorSHT) { if (hasSensorSHT) {
@ -965,6 +1135,89 @@ static String getServerSyncData(bool localServer) {
return JSON.stringify(root); return JSON.stringify(root);
} }
static void createMqttTask(void) {
if (mqttTask) {
vTaskDelete(mqttTask);
mqttTask = NULL;
}
xTaskCreate(
[](void *param) {
for (;;) {
delay(MQTT_SYNC_INTERVAL);
/** Send data */
if (agMqtt.isConnected()) {
String syncData = getServerSyncData(false);
String topic = "airgradient/readings/" + getDevId();
if (agMqtt.publish(topic.c_str(), syncData.c_str(),
syncData.length())) {
Serial.println("Mqtt sync success");
} else {
Serial.println("Mqtt sync failure");
}
}
}
},
"mqtt-task", 1024 * 3, NULL, 6, &mqttTask);
if (mqttTask == NULL) {
Serial.println("Creat mqttTask failed");
}
}
static void factoryConfigReset(void) {
if (ag.button.getState() == ag.button.BUTTON_PRESSED) {
if (factoryBtnPressTime == 0) {
factoryBtnPressTime = millis();
} else {
uint32_t ms = (uint32_t)(millis() - factoryBtnPressTime);
if (ms >= 2000) {
// Show display message: For factory keep for x seconds
// Count display.
displayShowText("Factory reset", "keep pressed", "for 8 sec");
int count = 7;
while (ag.button.getState() == ag.button.BUTTON_PRESSED) {
delay(1000);
displayShowText("Factory reset", "keep pressed",
"for " + String(count) + " sec");
count--;
// ms = (uint32_t)(millis() - factoryBtnPressTime);
if (count == 0) {
/** Stop MQTT task first */
if (mqttTask) {
vTaskDelete(mqttTask);
mqttTask = NULL;
}
/** Disconnect WIFI */
WiFi.disconnect();
wifiManager.resetSettings();
/** Reset local config */
agServer.defaultReset();
displayShowText("Factory reset", "successful", "");
delay(3000);
ESP.restart();
}
}
/** Show current content cause reset ignore */
factoryBtnPressTime = 0;
appDispHandler();
}
}
} else {
if (factoryBtnPressTime != 0) {
/** Restore last display content */
appDispHandler();
}
factoryBtnPressTime = 0;
}
}
static void sendPing() { static void sendPing() {
JSONVar root; JSONVar root;
root["wifi"] = WiFi.RSSI(); root["wifi"] = WiFi.RSSI();
@ -1287,20 +1540,80 @@ static String getNormalizedMac() {
} }
static void setRGBledCO2color(int co2Value) { static void setRGBledCO2color(int co2Value) {
if (co2Value >= 300 && co2Value < 800) { if (co2Value <= 400) {
setRGBledColor('g'); /** G; 1 */
} else if (co2Value >= 800 && co2Value < 1000) { ag.ledBar.setColor(0, 255, 0, ag.ledBar.getNumberOfLeds() - 1);
setRGBledColor('y'); } else if (co2Value <= 700) {
} else if (co2Value >= 1000 && co2Value < 1500) { /** GG; 2 */
setRGBledColor('o'); ag.ledBar.setColor(0, 255, 0, ag.ledBar.getNumberOfLeds() - 1);
} else if (co2Value >= 1500 && co2Value < 2000) { ag.ledBar.setColor(0, 255, 0, ag.ledBar.getNumberOfLeds() - 2);
setRGBledColor('r'); } else if (co2Value <= 1000) {
} else if (co2Value >= 2000 && co2Value < 3000) { /** YYY; 3 */
setRGBledColor('p'); ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 1);
} else if (co2Value >= 3000 && co2Value < 10000) { ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 2);
setRGBledColor('z'); ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 3);
} else { } else if (co2Value <= 1333) {
setRGBledColor('n'); /** YYYY; 4 */
ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 1);
ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 2);
ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 3);
ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 4);
} else if (co2Value <= 1666) {
/** YYYYY; 5 */
ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 1);
ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 2);
ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 3);
ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 4);
ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 5);
} else if (co2Value <= 2000) {
/** RRRRRR; 6 */
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 1);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 2);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 3);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 4);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 5);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 6);
} else if (co2Value <= 2666) {
/** RRRRRRR; 7 */
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 1);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 2);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 3);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 4);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 5);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 6);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 7);
} else if (co2Value <= 3333) {
/** RRRRRRRR; 8 */
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 1);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 2);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 3);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 4);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 5);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 6);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 7);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 8);
} else if (co2Value <= 4000) {
/** RRRRRRRRR; 9 */
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 1);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 2);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 3);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 4);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 5);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 6);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 7);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 8);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 9);
} else { /** > 4000 */
/* PRPRPRPRP; 9 */
ag.ledBar.setColor(153, 153, 0, ag.ledBar.getNumberOfLeds() - 1);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 2);
ag.ledBar.setColor(153, 153, 0, ag.ledBar.getNumberOfLeds() - 3);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 4);
ag.ledBar.setColor(153, 153, 0, ag.ledBar.getNumberOfLeds() - 5);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 6);
ag.ledBar.setColor(153, 153, 0, ag.ledBar.getNumberOfLeds() - 7);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 8);
ag.ledBar.setColor(153, 153, 0, ag.ledBar.getNumberOfLeds() - 9);
} }
} }
@ -1415,8 +1728,8 @@ static void failedHandler(String msg) {
/** /**
* @brief Send data to server * @brief Send data to server
*/ */
static void serverConfigPoll(void) { static void updateServerConfiguration(void) {
if (agServer.pollServerConfig(getDevId())) { if (agServer.fetchServerConfiguration(getDevId())) {
if (agServer.isCo2Calib()) { if (agServer.isCo2Calib()) {
if (hasSensorS8) { if (hasSensorS8) {
co2Calibration(); co2Calibration();
@ -1458,12 +1771,23 @@ static void serverConfigPoll(void) {
String mqttUri = agServer.getMqttBroker(); String mqttUri = agServer.getMqttBroker();
if (mqttUri != agMqtt.getUri()) { if (mqttUri != agMqtt.getUri()) {
agMqtt.end(); agMqtt.end();
if (mqttTask != NULL) {
vTaskDelete(mqttTask);
mqttTask = NULL;
}
if (agMqtt.begin(mqttUri)) { if (agMqtt.begin(mqttUri)) {
Serial.println("Connect to new mqtt broker success"); Serial.println("Connect to new mqtt broker success");
createMqttTask();
} else { } else {
Serial.println("Connect to new mqtt broker failed"); Serial.println("Connect to new mqtt broker failed");
} }
} }
if (mdnsModelName != agServer.getModelName()) {
MDNS.addServiceTxt("http", "_tcp", "model", agServer.getModelName());
mdnsModelName = agServer.getModelName();
}
} }
} }
@ -1506,18 +1830,81 @@ static void co2Calibration(void) {
* @param pm25Value PMS2.5 value * @param pm25Value PMS2.5 value
*/ */
static void setRGBledPMcolor(int pm25Value) { static void setRGBledPMcolor(int pm25Value) {
if (pm25Value >= 0 && pm25Value < 10) if (pm25Value <= 5) {
setRGBledColor('g'); /** G; 1 */
if (pm25Value >= 10 && pm25Value < 35) ag.ledBar.setColor(0, 255, 0, ag.ledBar.getNumberOfLeds() - 1);
setRGBledColor('y'); } else if (pm25Value <= 10) {
if (pm25Value >= 35 && pm25Value < 55) /** GG; 2 */
setRGBledColor('o'); ag.ledBar.setColor(0, 255, 0, ag.ledBar.getNumberOfLeds() - 1);
if (pm25Value >= 55 && pm25Value < 150) ag.ledBar.setColor(0, 255, 0, ag.ledBar.getNumberOfLeds() - 2);
setRGBledColor('r'); } else if (pm25Value <= 20) {
if (pm25Value >= 150 && pm25Value < 250) /** YYY; 3 */
setRGBledColor('p'); ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 1);
if (pm25Value >= 250 && pm25Value < 1000) ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 2);
setRGBledColor('p'); ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 3);
} else if (pm25Value <= 35) {
/** YYYY; 4 */
ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 1);
ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 2);
ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 3);
ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 4);
} else if (pm25Value <= 45) {
/** YYYYY; 5 */
ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 1);
ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 2);
ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 3);
ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 4);
ag.ledBar.setColor(255, 255, 0, ag.ledBar.getNumberOfLeds() - 5);
} else if (pm25Value <= 55) {
/** RRRRRR; 6 */
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 1);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 2);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 3);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 4);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 5);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 6);
} else if (pm25Value <= 65) {
/** RRRRRRR; 7 */
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 1);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 2);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 3);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 4);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 5);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 6);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 7);
} else if (pm25Value <= 150) {
/** RRRRRRRR; 8 */
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 1);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 2);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 3);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 4);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 5);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 6);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 7);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 8);
} else if (pm25Value <= 250) {
/** RRRRRRRRR; 9 */
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 1);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 2);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 3);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 4);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 5);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 6);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 7);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 8);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 9);
} else { /** > 250 */
/* PRPRPRPRP; 9 */
ag.ledBar.setColor(153, 153, 0, ag.ledBar.getNumberOfLeds() - 1);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 2);
ag.ledBar.setColor(153, 153, 0, ag.ledBar.getNumberOfLeds() - 3);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 4);
ag.ledBar.setColor(153, 153, 0, ag.ledBar.getNumberOfLeds() - 5);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 6);
ag.ledBar.setColor(153, 153, 0, ag.ledBar.getNumberOfLeds() - 7);
ag.ledBar.setColor(255, 0, 0, ag.ledBar.getNumberOfLeds() - 8);
ag.ledBar.setColor(153, 153, 0, ag.ledBar.getNumberOfLeds() - 9);
}
} }
static void singleLedAnimation(uint8_t r, uint8_t g, uint8_t b) { static void singleLedAnimation(uint8_t r, uint8_t g, uint8_t b) {
@ -1785,8 +2172,10 @@ static void updateWiFiConnect(void) {
* @brief APP display and LED handler * @brief APP display and LED handler
* *
*/ */
static void updateDispLedBar(void) { static void displayAndLedBarUpdate(void) {
appDispHandler(); if (factoryBtnPressTime == 0) {
appDispHandler();
}
appLedHandler(); appLedHandler();
} }
@ -1794,7 +2183,7 @@ static void updateDispLedBar(void) {
* @brief Update tvocIndexindex * @brief Update tvocIndexindex
* *
*/ */
static void tvocPoll(void) { static void tvocUpdate(void) {
tvocIndex = ag.sgp41.getTvocIndex(); tvocIndex = ag.sgp41.getTvocIndex();
tvocRawIndex = ag.sgp41.getTvocRaw(); tvocRawIndex = ag.sgp41.getTvocRaw();
noxIndex = ag.sgp41.getNoxIndex(); noxIndex = ag.sgp41.getNoxIndex();
@ -1809,7 +2198,7 @@ static void tvocPoll(void) {
* @brief Update PMS data * @brief Update PMS data
* *
*/ */
static void pmPoll(void) { static void pmUpdate(void) {
if (ag.pms5003.readData()) { if (ag.pms5003.readData()) {
pm01 = ag.pms5003.getPm01Ae(); pm01 = ag.pms5003.getPm01Ae();
pm25 = ag.pms5003.getPm25Ae(); pm25 = ag.pms5003.getPm25Ae();
@ -1844,21 +2233,13 @@ static void sendDataToServer(void) {
resetWatchdog(); resetWatchdog();
} }
if (agMqtt.isConnected()) {
String topic = "airgradient/readings/" + getDevId();
if (agMqtt.publish(topic.c_str(), syncData.c_str(), syncData.length())) {
Serial.println("Mqtt sync success");
} else {
Serial.println("Mqtt sync failure");
}
}
bootCount++; bootCount++;
} }
/** /**
* @brief Update temperature and humidity value * @brief Update temperature and humidity value
*/ */
static void tempHumPoll(void) { static void tempHumUpdate(void) {
if (ag.sht.measure()) { if (ag.sht.measure()) {
temp = ag.sht.getTemperature(); temp = ag.sht.getTemperature();
@ -1873,10 +2254,6 @@ static void tempHumPoll(void) {
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, static void mqtt_event_handler(void *handler_args, esp_event_base_t base,
int32_t event_id, void *event_data) { int32_t event_id, void *event_data) {
ESP_LOGD(TAG,
"Event dispatched from event loop base=%s, event_id=%" PRIi32 "",
base, event_id);
esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t)event_data; esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t)event_data;
esp_mqtt_client_handle_t client = event->client; esp_mqtt_client_handle_t client = event->client;
int msg_id; int msg_id;

View File

@ -77,7 +77,7 @@ enum {
APP_SM_SERVER_LOST, /** Connected to WiFi network but the server cannot be APP_SM_SERVER_LOST, /** Connected to WiFi network but the server cannot be
reached through the internet, e.g. blocked by firewall reached through the internet, e.g. blocked by firewall
*/ */
APP_SM_SENSOR_CONFIG_FAILED, /** Server is reachable but there is some APP_SM_SENSOR_CONFIG_FAILED, /** Server is reachabFirmware nodele but there is some
configuration issue to be fixed on the server configuration issue to be fixed on the server
side */ side */
APP_SM_NORMAL, APP_SM_NORMAL,
@ -85,17 +85,20 @@ enum {
#define LED_FAST_BLINK_DELAY 250 /** ms */ #define LED_FAST_BLINK_DELAY 250 /** ms */
#define LED_SLOW_BLINK_DELAY 1000 /** ms */ #define LED_SLOW_BLINK_DELAY 1000 /** ms */
#define LED_SHORT_BLINK_DELAY 500 /** ms */
#define LED_LONG_BLINK_DELAY 2000 /** ms */
#define WIFI_CONNECT_COUNTDOWN_MAX 180 /** sec */ #define WIFI_CONNECT_COUNTDOWN_MAX 180 /** sec */
#define WIFI_CONNECT_RETRY_MS 10000 /** ms */ #define WIFI_CONNECT_RETRY_MS 10000 /** ms */
#define LED_BAR_COUNT_INIT_VALUE (-1) /** */ #define LED_BAR_COUNT_INIT_VALUE (-1) /** */
#define LED_BAR_ANIMATION_PERIOD 100 /** ms */ #define LED_BAR_ANIMATION_PERIOD 100 /** ms */
#define DISP_UPDATE_INTERVAL 5000 /** ms */ #define DISP_UPDATE_INTERVAL 5000 /** ms */
#define SERVER_CONFIG_UPDATE_INTERVAL 30000 /** ms */ #define SERVER_CONFIG_UPDATE_INTERVAL 15000 /** ms */
#define SERVER_SYNC_INTERVAL 60000 /** ms */ #define SERVER_SYNC_INTERVAL 60000 /** ms */
#define MQTT_SYNC_INTERVAL 60000 /** ms */
#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */ #define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */ #define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
#define SENSOR_CO2_UPDATE_INTERVAL 5000 /** ms */ #define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */
#define SENSOR_PM_UPDATE_INTERVAL 5000 /** ms */ #define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 5000 /** ms */ #define SENSOR_TEMP_HUM_UPDATE_INTERVAL 5000 /** ms */
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */ #define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
#define WIFI_HOTSPOT_PASSWORD_DEFAULT \ #define WIFI_HOTSPOT_PASSWORD_DEFAULT \
@ -152,6 +155,19 @@ public:
loadConfig(); loadConfig();
} }
/**
* @brief Reset local config into default value.
*
*/
void defaultReset(void) {
config.inF = false;
config.inUSAQI = false;
memset(config.models, 0, sizeof(config.models));
memset(config.mqttBrokers, 0, sizeof(config.mqttBrokers));
config.useRGBLedBar = UseLedBarOff;
saveConfig();
}
/** /**
* @brief Get server configuration * @brief Get server configuration
* *
@ -159,7 +175,7 @@ public:
* @return true Success * @return true Success
* @return false Failure * @return false Failure
*/ */
bool pollServerConfig(String id) { bool fetchServerConfiguration(String id) {
String uri = String uri =
"http://hw.airgradient.com/sensors/airgradient:" + id + "/one/config"; "http://hw.airgradient.com/sensors/airgradient:" + id + "/one/config";
@ -631,6 +647,7 @@ public:
int connectionFailedCount(void) { return connectFailedCount; } int connectionFailedCount(void) { return connectFailedCount; }
}; };
AgMqtt agMqtt; AgMqtt agMqtt;
static TaskHandle_t mqttTask = NULL;
/** Create airgradient instance for 'OPEN_AIR_OUTDOOR' board */ /** Create airgradient instance for 'OPEN_AIR_OUTDOOR' board */
AirGradient ag(OPEN_AIR_OUTDOOR); AirGradient ag(OPEN_AIR_OUTDOOR);
@ -692,25 +709,30 @@ void failedHandler(String msg);
void co2Calibration(void); void co2Calibration(void);
static String getDevId(void); static String getDevId(void);
static void updateWiFiConnect(void); static void updateWiFiConnect(void);
static void tvocPoll(void); static void tvocUpdate(void);
static void pmPoll(void); static void pmUpdate(void);
static void sendDataToServer(void); static void sendDataToServer(void);
static void co2Poll(void); static void co2Update(void);
static void serverConfigPoll(void); static void updateServerConfiguration(void);
static const char *getFwMode(int mode); static const char *getFwMode(int mode);
static void showNr(void); static void showNr(void);
static void webServerInit(void); static void webServerInit(void);
static String getServerSyncData(bool localServer); static String getServerSyncData(bool localServer);
static void createMqttTask(void);
static void factoryConfigReset(void);
bool hasSensorS8 = true; bool hasSensorS8 = true;
bool hasSensorPMS1 = true; bool hasSensorPMS1 = true;
bool hasSensorPMS2 = true; bool hasSensorPMS2 = true;
bool hasSensorSGP = true; bool hasSensorSGP = true;
AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL, serverConfigPoll); uint32_t factoryBtnPressTime = 0;
String mdnsModelName = "";
AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL,
updateServerConfiguration);
AgSchedule serverSchedule(SERVER_SYNC_INTERVAL, sendDataToServer); AgSchedule serverSchedule(SERVER_SYNC_INTERVAL, sendDataToServer);
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Poll); AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update);
AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, pmPoll); AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, pmUpdate);
AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, tvocPoll); AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, tvocUpdate);
void setup() { void setup() {
EEPROM.begin(512); EEPROM.begin(512);
@ -734,6 +756,7 @@ void setup() {
/** MQTT init */ /** MQTT init */
if (agServer.getMqttBroker().isEmpty() == false) { if (agServer.getMqttBroker().isEmpty() == false) {
if (agMqtt.begin(agServer.getMqttBroker())) { if (agMqtt.begin(agServer.getMqttBroker())) {
createMqttTask();
Serial.println("MQTT client init success"); Serial.println("MQTT client init success");
} else { } else {
Serial.println("MQTT client init failure"); Serial.println("MQTT client init failure");
@ -743,7 +766,7 @@ void setup() {
wifiHasConfig = true; wifiHasConfig = true;
sendPing(); sendPing();
agServer.pollServerConfig(getDevId()); agServer.fetchServerConfiguration(getDevId());
if (agServer.isConfigFailed()) { if (agServer.isConfigFailed()) {
ledSmHandler(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); delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
@ -773,6 +796,8 @@ void loop() {
} }
} }
updateWiFiConnect(); updateWiFiConnect();
factoryConfigReset();
} }
void sendPing() { void sendPing() {
@ -793,14 +818,6 @@ static void sendDataToServer(void) {
resetWatchdog(); resetWatchdog();
} }
if (agMqtt.isConnected()) {
String topic = "airgradient/readings/" + getDevId();
if (agMqtt.publish(topic.c_str(), syncData.c_str(), syncData.length())) {
Serial.println("Mqtt sync success");
} else {
Serial.println("Mqtt sync failure");
}
}
loopCount++; loopCount++;
} }
@ -889,6 +906,8 @@ void boardInit(void) {
failedHandler("Init I2C failed"); failedHandler("Init I2C failed");
} }
Serial.println("Firmware Version: "+ag.getVersion());
ag.watchdog.begin(); ag.watchdog.begin();
ag.button.begin(); ag.button.begin();
ag.statusLed.begin(); ag.statusLed.begin();
@ -936,7 +955,7 @@ void boardInit(void) {
} }
} }
Serial.printf("Firmware node: %s\r\n", getFwMode(fw_mode)); Serial.printf("Firmware Mode: %s\r\n", getFwMode(fw_mode));
} }
void failedHandler(String msg) { void failedHandler(String msg) {
@ -996,7 +1015,7 @@ static void updateWiFiConnect(void) {
* @brief Update tvocIndexindex * @brief Update tvocIndexindex
* *
*/ */
static void tvocPoll(void) { static void tvocUpdate(void) {
tvocIndex = ag.sgp41.getTvocIndex(); tvocIndex = ag.sgp41.getTvocIndex();
tvocRawIndex = ag.sgp41.getTvocRaw(); tvocRawIndex = ag.sgp41.getTvocRaw();
noxIndex = ag.sgp41.getNoxIndex(); noxIndex = ag.sgp41.getNoxIndex();
@ -1011,7 +1030,7 @@ static void tvocPoll(void) {
* @brief Update PMS data * @brief Update PMS data
* *
*/ */
static void pmPoll(void) { static void pmUpdate(void) {
bool pmsResult_1 = false; bool pmsResult_1 = false;
bool pmsResult_2 = false; bool pmsResult_2 = false;
if (hasSensorPMS1 && ag.pms5003t_1.readData()) { if (hasSensorPMS1 && ag.pms5003t_1.readData()) {
@ -1119,13 +1138,13 @@ static void pmPoll(void) {
} }
} }
static void co2Poll(void) { static void co2Update(void) {
co2Ppm = ag.s8.getCo2(); co2Ppm = ag.s8.getCo2();
Serial.printf("CO2 index: %d\r\n", co2Ppm); Serial.printf("CO2 index: %d\r\n", co2Ppm);
} }
static void serverConfigPoll(void) { static void updateServerConfiguration(void) {
if (agServer.pollServerConfig(getDevId())) { if (agServer.fetchServerConfiguration(getDevId())) {
/** Only support CO2 S8 sensor on FW_MODE_PST */ /** Only support CO2 S8 sensor on FW_MODE_PST */
if (fw_mode == FW_MODE_PST) { if (fw_mode == FW_MODE_PST) {
if (agServer.isCo2Calib()) { if (agServer.isCo2Calib()) {
@ -1162,12 +1181,22 @@ static void serverConfigPoll(void) {
String mqttUri = agServer.getMqttBroker(); String mqttUri = agServer.getMqttBroker();
if (mqttUri != agMqtt.getUri()) { if (mqttUri != agMqtt.getUri()) {
agMqtt.end(); agMqtt.end();
if (mqttTask != NULL) {
vTaskDelete(mqttTask);
mqttTask = NULL;
}
if (agMqtt.begin(mqttUri)) { if (agMqtt.begin(mqttUri)) {
Serial.println("Connect to new mqtt broker success"); Serial.println("Connect to new mqtt broker success");
createMqttTask();
} else { } else {
Serial.println("Connect to new mqtt broker failed"); Serial.println("Connect to new mqtt broker failed");
} }
} }
if (mdnsModelName != agServer.getModelName()) {
MDNS.addServiceTxt("http", "_tcp", "model", agServer.getModelName());
mdnsModelName = agServer.getModelName();
}
} }
} }
@ -1310,6 +1339,12 @@ static void webServerInit(void) {
webServer.on("/measures/current", HTTP_GET, webServerMeasureCurrentGet); webServer.on("/measures/current", HTTP_GET, webServerMeasureCurrentGet);
webServer.begin(); webServer.begin();
MDNS.addService("http", "tcp", 80); 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());
if (xTaskCreate(webServerHandler, "webserver", 1024 * 4, NULL, 5, NULL) != if (xTaskCreate(webServerHandler, "webserver", 1024 * 4, NULL, 5, NULL) !=
pdTRUE) { pdTRUE) {
@ -1376,13 +1411,13 @@ static String getServerSyncData(bool localServer) {
if ((fw_mode == FW_MODE_PPT) || (fw_mode == FW_MODE_PST)) { if ((fw_mode == FW_MODE_PPT) || (fw_mode == FW_MODE_PST)) {
if (hasSensorSGP) { if (hasSensorSGP) {
if (tvocIndex > 0) { if (tvocIndex >= 0) {
root["tvoc_index"] = tvocIndex; root["tvoc_index"] = tvocIndex;
} }
if (tvocRawIndex >= 0) { if (tvocRawIndex >= 0) {
root["tvoc_raw"] = tvocRawIndex; root["tvoc_raw"] = tvocRawIndex;
} }
if (noxIndex > 0) { if (noxIndex >= 0) {
root["nox_index"] = noxIndex; root["nox_index"] = noxIndex;
} }
} }
@ -1418,12 +1453,96 @@ static String getServerSyncData(bool localServer) {
return JSON.stringify(root); return JSON.stringify(root);
} }
static void createMqttTask(void) {
if (mqttTask) {
vTaskDelete(mqttTask);
mqttTask = NULL;
}
xTaskCreate(
[](void *param) {
for (;;) {
delay(MQTT_SYNC_INTERVAL);
/** Send data */
if (agMqtt.isConnected()) {
String syncData = getServerSyncData(false);
String topic = "airgradient/readings/" + getDevId();
if (agMqtt.publish(topic.c_str(), syncData.c_str(),
syncData.length())) {
Serial.println("Mqtt sync success");
} else {
Serial.println("Mqtt sync failure");
}
}
}
},
"mqtt-task", 1024 * 3, NULL, 6, &mqttTask);
if (mqttTask == NULL) {
Serial.println("Creat mqttTask failed");
}
}
static void factoryConfigReset(void) {
if (ag.button.getState() == ag.button.BUTTON_PRESSED) {
if (factoryBtnPressTime == 0) {
factoryBtnPressTime = millis();
} else {
uint32_t ms = (uint32_t)(millis() - factoryBtnPressTime);
if (ms >= 2000) {
Serial.println("Factory reset keep presssed for 8 sec");
uint32_t ledTime = millis();
bool ledOn = true;
ag.statusLed.setOn();
while (ag.button.getState() == ag.button.BUTTON_PRESSED) {
ms = (uint32_t)(millis() - ledTime);
if (ms >= LED_SHORT_BLINK_DELAY) {
ledTime = millis();
ag.statusLed.setToggle();
}
ms = (uint32_t)(millis() - factoryBtnPressTime);
if (ms > 10000) {
ag.statusLed.setOff();
/** Stop MQTT task first */
if (mqttTask) {
vTaskDelete(mqttTask);
mqttTask = NULL;
}
/** Disconnect WIFI */
WiFi.disconnect();
wifiManager.resetSettings();
/** Reset local config */
agServer.defaultReset();
Serial.println("Factory successful");
ledBlinkDelay(LED_LONG_BLINK_DELAY);
ledBlinkDelay(LED_LONG_BLINK_DELAY);
ledBlinkDelay(LED_LONG_BLINK_DELAY);
ESP.restart();
}
}
/** Show current content cause reset ignore */
factoryBtnPressTime = 0;
ag.statusLed.setOff();
}
}
} else {
if (factoryBtnPressTime != 0) {
ag.statusLed.setOff();
}
factoryBtnPressTime = 0;
}
}
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, static void mqtt_event_handler(void *handler_args, esp_event_base_t base,
int32_t event_id, void *event_data) { int32_t event_id, void *event_data) {
ESP_LOGD(TAG,
"Event dispatched from event loop base=%s, event_id=%" PRIi32 "",
base, event_id);
esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t)event_data; esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t)event_data;
esp_mqtt_client_handle_t client = event->client; esp_mqtt_client_handle_t client = event->client;
int msg_id; int msg_id;

View File

@ -1,5 +1,5 @@
name=AirGradient Air Quality Sensor name=AirGradient Air Quality Sensor
version=3.0.4 version=3.0.6
author=AirGradient <support@airgradient.com> author=AirGradient <support@airgradient.com>
maintainer=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. 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" #include "AirGradient.h"
#define AG_LIB_VER "3.0.4" #define AG_LIB_VER "3.0.6"
AirGradient::AirGradient(BoardType type) AirGradient::AirGradient(BoardType type)
: pms5003(type), pms5003t_1(type), pms5003t_2(type), s8(type), sgp41(type), : pms5003(type), pms5003t_1(type), pms5003t_2(type), s8(type), sgp41(type),
@ -40,3 +40,7 @@ BoardType AirGradient::getBoardType(void) { return boardType; }
double AirGradient::round2(double value) { double AirGradient::round2(double value) {
return (int)(value * 100 + 0.5) / 100.0; return (int)(value * 100 + 0.5) / 100.0;
} }
String AirGradient::getBoardName(void) {
return String(getBoardDefName(boardType));
}

View File

@ -107,6 +107,13 @@ public:
*/ */
String getVersion(void); String getVersion(void);
/**
* @brief Get the Board Name object
*
* @return String
*/
String getBoardName(void);
/** /**
* @brief Round double value with for 2 decimal * @brief Round double value with for 2 decimal
* *

View File

@ -1,7 +1,7 @@
#ifndef _AIR_GRADIENT_OLED_H_ #ifndef _AIR_GRADIENT_OLED_H_
#define _AIR_GRADIENT_OLED_H_ #define _AIR_GRADIENT_OLED_H_
#include "../main/BoardDef.h" #include "../Main/BoardDef.h"
#include <Arduino.h> #include <Arduino.h>
#include <Wire.h> #include <Wire.h>

View File

@ -335,6 +335,19 @@ const BoardDef *getBoardDef(BoardType def) {
return &bsps[def]; return &bsps[def];
} }
/**
* @brief Get the Board Name
*
* @param type BoarType
* @return const char*
*/
const char *getBoardDefName(BoardType type) {
if (type >= _BOARD_MAX) {
return NULL;
}
return bsps[type].name;
}
#if defined(ESP8266) #if defined(ESP8266)
#define bspPrintf(c, ...) \ #define bspPrintf(c, ...) \
if (_debug != nullptr) { \ if (_debug != nullptr) { \

View File

@ -76,13 +76,14 @@ struct BoardDef {
/** Watchdog */ /** Watchdog */
struct { struct {
const uint8_t resetPin; const int resetPin;
const bool supported; const bool supported;
} WDG; } WDG;
const char *name; const char *name;
}; };
const BoardDef *getBoardDef(BoardType def); const BoardDef *getBoardDef(BoardType def);
const char *getBoardDefName(BoardType type);
void printBoardDef(Stream *_debug); void printBoardDef(Stream *_debug);
#endif /** _AIR_GRADIENT_BOARD_DEF_H_ */ #endif /** _AIR_GRADIENT_BOARD_DEF_H_ */

View File

@ -1,5 +1,6 @@
#include "PMS5003.h" #include "PMS5003.h"
#include "Arduino.h" #include "Arduino.h"
#include "PMSUtils.h"
#if defined(ESP8266) #if defined(ESP8266)
#include <SoftwareSerial.h> #include <SoftwareSerial.h>
@ -63,7 +64,8 @@ bool PMS5003::begin(void) {
#if defined(ESP8266) #if defined(ESP8266)
bsp->Pms5003.uart_tx_pin; bsp->Pms5003.uart_tx_pin;
SoftwareSerial *uart = new SoftwareSerial(bsp->Pms5003.uart_tx_pin, bsp->Pms5003.uart_rx_pin); SoftwareSerial *uart =
new SoftwareSerial(bsp->Pms5003.uart_tx_pin, bsp->Pms5003.uart_rx_pin);
uart->begin(9600); uart->begin(9600);
if (pms.begin(uart) == false) { if (pms.begin(uart) == false) {
AgLog("PMS failed"); AgLog("PMS failed");
@ -81,31 +83,6 @@ bool PMS5003::begin(void) {
return true; return true;
} }
/**
* @brief Convert PM2.5 to US AQI
*
* @param pm02
* @return int
*/
int PMS5003::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 Read all package data then call to @ref getPMxxx to get the target * @brief Read all package data then call to @ref getPMxxx to get the target
* data * data
@ -155,7 +132,7 @@ int PMS5003::getPm03ParticleCount(void) { return pmsData.PM_RAW_0_3; }
* @param pm25 PM2.5 index * @param pm25 PM2.5 index
* @return int PM2.5 US AQI * @return int PM2.5 US AQI
*/ */
int PMS5003::convertPm25ToUsAqi(int pm25) { return this->pm25ToAQI(pm25); } int PMS5003::convertPm25ToUsAqi(int pm25) { return pm25ToAQI(pm25); }
/** /**
* @brief Check device initialized or not * @brief Check device initialized or not

View File

@ -2,8 +2,8 @@
#define _AIR_GRADIENT_PMS5003_H_ #define _AIR_GRADIENT_PMS5003_H_
#include "../Main/BoardDef.h" #include "../Main/BoardDef.h"
#include "Stream.h"
#include "PMS.h" #include "PMS.h"
#include "Stream.h"
/** /**
* @brief The class define how to handle PMS5003 sensor bas on @ref PMS class * @brief The class define how to handle PMS5003 sensor bas on @ref PMS class
@ -41,6 +41,5 @@ private:
bool begin(void); bool begin(void);
bool isBegin(void); bool isBegin(void);
int pm25ToAQI(int pm02);
}; };
#endif /** _AIR_GRADIENT_PMS5003_H_ */ #endif /** _AIR_GRADIENT_PMS5003_H_ */

View File

@ -1,5 +1,6 @@
#include "PMS5003T.h" #include "PMS5003T.h"
#include "Arduino.h" #include "Arduino.h"
#include "PMSUtils.h"
#if defined(ESP8266) #if defined(ESP8266)
#include <SoftwareSerial.h> #include <SoftwareSerial.h>
@ -105,31 +106,6 @@ bool PMS5003T::begin(void) {
return true; return true;
} }
/**
* @brief Convert PM2.5 to US AQI
*
* @param pm02
* @return int
*/
int PMS5003T::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 Read all package data then call to @ref getPMxxx to get the target * @brief Read all package data then call to @ref getPMxxx to get the target
* data * data
@ -179,7 +155,7 @@ int PMS5003T::getPm03ParticleCount(void) { return pmsData.PM_RAW_0_3; }
* @param pm25 PM2.5 index * @param pm25 PM2.5 index
* @return int PM2.5 US AQI * @return int PM2.5 US AQI
*/ */
int PMS5003T::convertPm25ToUsAqi(int pm25) { return this->pm25ToAQI(pm25); } int PMS5003T::convertPm25ToUsAqi(int pm25) { return pm25ToAQI(pm25); }
/** /**
* @brief Get temperature, Must call this method after @ref readData() success * @brief Get temperature, Must call this method after @ref readData() success
@ -197,8 +173,8 @@ float PMS5003T::getTemperature(void) {
* @return float Percent (%) * @return float Percent (%)
*/ */
float PMS5003T::getRelativeHumidity(void) { float PMS5003T::getRelativeHumidity(void) {
float temp = pmsData.AMB_HUM; float hum = pmsData.AMB_HUM;
return temp / 10.0f; return correctionRelativeHumidity(hum / 10.0f);
} }
/** /**
@ -234,3 +210,7 @@ void PMS5003T::end(void) {
#endif #endif
AgLog("De-initialize"); AgLog("De-initialize");
} }
float PMS5003T::correctionRelativeHumidity(float inHum) {
return inHum * 1.259 + 7.34;
}

View File

@ -1,10 +1,10 @@
#ifndef _PMS5003T_H_ #ifndef _PMS5003T_H_
#define _PMS5003T_H_ #define _PMS5003T_H_
#include <HardwareSerial.h>
#include "../Main/BoardDef.h" #include "../Main/BoardDef.h"
#include "PMS.h" #include "PMS.h"
#include "Stream.h" #include "Stream.h"
#include <HardwareSerial.h>
/** /**
* @brief The class define how to handle PMS5003T sensor bas on @ref PMS class * @brief The class define how to handle PMS5003T sensor bas on @ref PMS class
@ -42,11 +42,11 @@ private:
#endif #endif
bool begin(void); bool begin(void);
int pm25ToAQI(int pm02);
PMS pms; PMS pms;
PMS::DATA pmsData; PMS::DATA pmsData;
bool isBegin(void); bool isBegin(void);
float correctionTemperature(float inTemp); float correctionTemperature(float inTemp);
float correctionRelativeHumidity(float inHum);
}; };
#endif /** _PMS5003T_H_ */ #endif /** _PMS5003T_H_ */

26
src/PMS/PMSUtils.cpp Normal file
View File

@ -0,0 +1,26 @@
#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;
}

6
src/PMS/PMSUtils.h Normal file
View File

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

View File

@ -1,7 +1,7 @@
#ifndef _S8_H_ #ifndef _S8_H_
#define _S8_H_ #define _S8_H_
#include "../main/BoardDef.h" #include "../Main/BoardDef.h"
#include "Arduino.h" #include "Arduino.h"
/** /**

View File

@ -1,7 +1,7 @@
#ifndef _AIR_GRADIENT_SGP4X_H_ #ifndef _AIR_GRADIENT_SGP4X_H_
#define _AIR_GRADIENT_SGP4X_H_ #define _AIR_GRADIENT_SGP4X_H_
#include "../main/BoardDef.h" #include "../Main/BoardDef.h"
#include <Arduino.h> #include <Arduino.h>
#include <Wire.h> #include <Wire.h>

View File

@ -1,7 +1,7 @@
#ifndef _SHT_H_ #ifndef _SHT_H_
#define _SHT_H_ #define _SHT_H_
#include "../main/BoardDef.h" #include "../Main/BoardDef.h"
#include <Arduino.h> #include <Arduino.h>
#include <Wire.h> #include <Wire.h>