Merge branch 'develop' into feature/send-pms-sensor-fw-version-to-ag-cloud

This commit is contained in:
Phat Nguyen
2024-08-25 20:46:28 +07:00
21 changed files with 279 additions and 116 deletions

View File

@ -17,62 +17,77 @@ With the path "/measures/current" you can get the current air quality data.
http://airgradient_ecda3b1eaaaf.local/measures/current http://airgradient_ecda3b1eaaaf.local/measures/current
“ecda3b1eaaaf” being the serial number of your monitor “ecda3b1eaaaf” being the serial number of your monitor.
You get the following response: You get the following response:
~~~ ```json
{"wifi":-46, {
"serialno":"ecda3b1eaaaf", "wifi": -46,
"rco2":447, "serialno": "ecda3b1eaaaf",
"pm01":3, "rco2": 447,
"pm02":7, "pm01": 3,
"pm10":8, "pm02": 7,
"pm003Count":442, "pm10": 8,
"atmp":25.87, "pm003Count": 442,
"rhum":43, "atmp": 25.87,
"tvocIndex":100, "atmpCompensated": 24.47,
"tvoc_raw":33051, "rhum": 43,
"noxIndex":1, "rhumCompensated": 49,
"nox_raw":16307, "tvocIndex": 100,
"boot":6, "tvocRaw": 33051,
"ledMode":"pm", "noxIndex": 1,
"firmwareVersion":"3.0.10beta", "noxRaw": 16307,
"fwMode":"I-9PSL"} "boot": 6,
~~~ "bootCount": 6,
"ledMode": "pm",
"firmware": "3.1.3",
"model": "I-9PSL"
}
```
|Properties|Type|Explanation| | Properties | Type | Explanation |
|-|-|-| |------------------|--------|--------------------------------------------------------------------|
|serialno|String| Serial Number of the monitor| | `serialno` | String | Serial Number of the monitor |
|wifi|Number| WiFi signal strength| | `wifi` | Number | WiFi signal strength |
|pm01, pm02, pm10|Number| PM1, PM2.5 and PM10 in ug/m3| | `pm01` | Number | PM1 in ug/m3 |
|rco2|Number| CO2 in ppm| | `pm02` | Number | PM2.5 in ug/m3 |
|pm003Count|Number| Particle count per dL| | `pm10` | Number | PM10 in ug/m3 |
|atmp|Number| Temperature in Degrees Celcius| | `pm02Compensated` | Number | PM2.5 in ug/m3 with correction applied (from fw version 3.1.4 onwards) |
|rhum|Number| Relative Humidity| | `rco2` | Number | CO2 in ppm |
|tvocIndex|Number| Senisiron VOC Index| | `pm003Count` | Number | Particle count per dL |
|tvoc_raw|Number| VOC raw value| | `atmp` | Number | Temperature in Degrees Celsius |
|noxIndex|Number| Senisirion NOx Index| | `atmpCompensated` | Number | Temperature in Degrees Celsius with correction applied |
|nox_raw|Number| NOx raw value| | `rhum` | Number | Relative Humidity |
|boot|Number| Counts every measurement cycle. Low boot counts indicate restarts.| | `rhumCompensated` | Number | Relative Humidity with correction applied |
|ledMode|String| Current configuration of the LED mode| | `tvocIndex` | Number | Senisiron VOC Index |
|firmwareVersion|String| Current firmware version| | `tvocRaw` | Number | VOC raw value |
|fwMode|String| Current model name| | `noxIndex` | Number | Senisirion NOx Index |
| `noxRaw` | Number | NOx raw value |
| `boot` | Number | Counts every measurement cycle. Low boot counts indicate restarts. |
| `bootCount` | Number | Same as boot property. Required for Home Assistant compatability. Will be depreciated. |
| `ledMode` | String | Current configuration of the LED mode |
| `firmware` | String | Current firmware version |
| `model` | String | Current model name |
Compensated values apply correction algorithms to make the sensor values more accurate. Temperature and relative humidity correction is only applied on the outdoor monitor Open Air but the properties _compensated will still be send also for the indoor monitor AirGradient ONE.
#### Get Configuration Parameters (GET) #### Get Configuration Parameters (GET)
With the path "/config" you can get the current configuration. With the path "/config" you can get the current configuration.
~~~ ```json
{"country":"US", {
"pmStandard":"ugm3", "country": "US",
"ledBarMode":"pm", "pmStandard": "ugm3",
"displayMode":"on", "ledBarMode": "pm",
"abcDays":30, "displayMode": "on",
"tvocLearningOffset":12, "abcDays": 30,
"noxLearningOffset":12, "tvocLearningOffset": 12,
"mqttBrokerUrl":"", "noxLearningOffset": 12,
"temperatureUnit":"f", "mqttBrokerUrl": "",
"configurationControl":"both", "temperatureUnit": "f",
"postDataToAirGradient":true} "configurationControl": "both",
~~~ "postDataToAirGradient": true
}
```
#### Set Configuration Parameters (PUT) #### Set Configuration Parameters (PUT)
@ -82,24 +97,34 @@ Example to force CO2 calibration
```curl -X PUT -H "Content-Type: application/json" -d '{"co2CalibrationRequested":true}' http://airgradient_84fce612eff4.local/config ``` ```curl -X PUT -H "Content-Type: application/json" -d '{"co2CalibrationRequested":true}' http://airgradient_84fce612eff4.local/config ```
Example to set monitor to Celcius Example to set monitor to Celsius
```curl -X PUT -H "Content-Type: application/json" -d '{"temperatureUnit":"c"}' http://airgradient_84fce612eff4.local/config ``` ```curl -X PUT -H "Content-Type: application/json" -d '{"temperatureUnit":"c"}' http://airgradient_84fce612eff4.local/config ```
If you use command prompt on Windows, you need to escape the quotes:
``` -d "{\"param\":\"value\"}" ```
#### Avoiding Conflicts with Configuration on AirGradient Server #### Avoiding Conflicts with Configuration on AirGradient Server
If the monitor is setup on the AirGradient dashboard, it will also receive configurations from there. In case you do not want this, please set "configurationControl" to local. In case you set it to cloud and want to change it to local, you need to make a factory reset. If the monitor is set up on the AirGradient dashboard, it will also receive configurations from there. In case you do not want this, please set `configurationControl` to `local`. In case you set it to `cloud` and want to change it to `local`, you need to make a factory reset.
#### Configuration Parameters (GET/PUT) #### Configuration Parameters (GET/PUT)
|Properties|Type|Accepted Values|Example| | Properties | Description | Type | Accepted Values | Example |
|-|-|-|-| |-------------------------|:-------------------------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------|
|country|String| Country code as [ALPHA-2 notation](https://www.iban.com/country-codes) | {"country": "TH"}| | `country` | Country where the device is. | String | Country code as [ALPHA-2 notation](https://www.iban.com/country-codes) | {"country": "TH"} |
|pmStandard|String|ugm3 : ug/m3 <br> usaqi: USAQI | {"pmStandard": "ugm3"}| | `model` | Hardware identifier (only GET). | String | I-9PSL-DE | {"model": "I-9PSL-DE"} |
|ledBarMode|String|co2: LED bar displays CO2 <br> pm: LED bar displays PM <br> off: Turn off LED bar | {"ledBarMode": "off"}| | `pmStandard` | Particle matter standard used on the display. | String | `ugm3`: ug/m3 <br> `us-aqi`: USAQI | {"pmStandard": "ugm3"} |
|abcDays|Number|Number of days for CO2 automatic baseline balibration. Maximum 200 days. Default 8 days. | {"abcDays": 8}| | `ledBarMode` | Mode in which the led bar can be set. | String | `co2`: LED bar displays CO2 <br>`pm`: LED bar displays PM <br>`off`: Turn off LED bar | {"ledBarMode": "off"} |
|mqttBrokerUrl|String|MQTT broker URL. | {"mqttBrokerUrl":"mqtt://192.168.0.18:1883"} | | `displayBrightness` | Brightness of the Display. | Number | 0-100 | {"displayBrightness": 50} |
|temperatureUnit|String|c or C: Degree Celsius °C <br>f or F: Degree Fahrenheit °F | {"temperatureUnit": "c"}| | `ledBarBrightness` | Brightness of the LEDBar. | Number | 0-100 | {"ledBarBrightness": 40} |
|configurationControl|String|both : Accept local and cloud configuration <br>local : Accept only local configuration <br>cloud : Accept only cloud configuration | {"configurationControl": "both"}| | `abcDays` | Number of days for CO2 automatic baseline calibration. | Number | Maximum 200 days. Default 8 days. | {"abcDays": 8} |
|postDataToAirGradient|Boolean|Send data to AirGradient cloud: <br>true : Enabled <br>false: Disabled | {"postDataToAirGradient": true}| | `mqttBrokerUrl` | MQTT broker URL. | String | | {"mqttBrokerUrl": "mqtt://192.168.0.18:1883"} |
|co2CalibrationRequested|Boolean|Trigger CO2 calibration (400ppm) on monitor:<br>true : Calibration will be triggered | {"co2CalibrationRequested": true}| | `temperatureUnit` | Temperature unit shown on the display. | String | `c` or `C`: Degree Celsius °C <br>`f` or `F`: Degree Fahrenheit °F | {"temperatureUnit": "c"} |
|ledBarTestRequested|Boolean|Test LED bar:<br> true : LEDs will run test sequence | {"ledBarTestRequested": true}| | `configurationControl` | The configuration source of the device. | String | `both`: Accept local and cloud configuration <br>`local`: Accept only local configuration <br>`cloud`: Accept only cloud configuration | {"configurationControl": "both"} |
| `postDataToAirGradient` | Send data to AirGradient cloud. | Boolean | `true`: Enabled <br>`false`: Disabled | {"postDataToAirGradient": true} |
| `co2CalibrationRequested` | Can be set to trigger a calibration. | Boolean | `true`: CO2 calibration (400ppm) will be triggered | {"co2CalibrationRequested": true} |
| `ledBarTestRequested` | Can be set to trigger a test. | Boolean | `true` : LEDs will run test sequence | {"ledBarTestRequested": true} |
| `noxLearningOffset` | Set NOx learning gain offset. | Number | 0-720 (default 12) | {"noxLearningOffset": 12} |
| `tvocLearningOffset` | Set VOC learning gain offset. | Number | 0-720 (default 12) | {"tvocLearningOffset": 12} |
| `offlineMode` | Set monitor to run without WiFi. | Boolean | `false`: Disabled (default) <br> `true`: Enabled | {"offlineMode": true} |

View File

@ -126,6 +126,9 @@ void setup() {
openMetrics.setAirGradient(&ag); openMetrics.setAirGradient(&ag);
localServer.setAirGraident(&ag); localServer.setAirGraident(&ag);
/** Example set custom API root URL */
// apiClient.setApiRoot("https://example.custom.api");
/** Init sensor */ /** Init sensor */
boardInit(); boardInit();

View File

@ -126,6 +126,9 @@ void setup() {
openMetrics.setAirGradient(&ag); openMetrics.setAirGradient(&ag);
localServer.setAirGraident(&ag); localServer.setAirGraident(&ag);
/** Example set custom API root URL */
// apiClient.setApiRoot("https://example.custom.api");
/** Init sensor */ /** Init sensor */
boardInit(); boardInit();

View File

@ -127,6 +127,9 @@ void setup() {
openMetrics.setAirGradient(&ag); openMetrics.setAirGradient(&ag);
localServer.setAirGraident(&ag); localServer.setAirGraident(&ag);
/** Example set custom API root URL */
// apiClient.setApiRoot("https://example.custom.api");
/** Init sensor */ /** Init sensor */
boardInit(); boardInit();

View File

@ -163,6 +163,9 @@ void setup() {
openMetrics.setAirGradient(ag); openMetrics.setAirGradient(ag);
localServer.setAirGraident(ag); localServer.setAirGraident(ag);
/** Example set custom API root URL */
// apiClient.setApiRoot("https://example.custom.api");
/** Init sensor */ /** Init sensor */
boardInit(); boardInit();
@ -421,8 +424,8 @@ static void factoryConfigReset(void) {
} }
/** Reset WIFI */ /** Reset WIFI */
WiFi.enableSTA(true); // Incase offline mode Serial.println("Set wifi connect to 'airgradient' as default");
WiFi.disconnect(true, true); WiFi.begin("airgradient", "cleanair");
/** Reset local config */ /** Reset local config */
configuration.reset(); configuration.reset();

View File

@ -1,5 +1,5 @@
name=AirGradient Air Quality Sensor name=AirGradient Air Quality Sensor
version=3.1.4 version=3.1.5
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,7 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags # Name ,Type ,SubType ,Offset ,Size ,Flags
nvs, data, nvs, 0x9000, 0x5000, nvs ,data ,nvs ,0x9000 ,0x5000 ,
otadata, data, ota, 0xe000, 0x2000, otadata ,data ,ota ,0xe000 ,0x2000 ,
app0, app, ota_0, 0x10000, 0x1E0000, app0 ,app ,ota_0 ,0x10000 ,0x1E0000 ,
app1, app, ota_1, 0x1F0000,0x1E0000, app1 ,app ,ota_1 ,0x1F0000 ,0x1E0000 ,
spiffs, data, spiffs, 0x3D0000,0x20000, spiffs ,data ,spiffs ,0x3D0000 ,0x20000 ,
coredump, data, coredump,0x3F0000,0x10000, coredump ,data ,coredump ,0x3F0000 ,0x10000 ,

1 # Name # Name Type Type SubType SubType Offset Offset Size Size Flags
2 nvs nvs data data nvs nvs 0x9000 0x9000 0x5000 0x5000
3 otadata otadata data data ota ota 0xe000 0xe000 0x2000 0x2000
4 app0 app0 app app ota_0 ota_0 0x10000 0x10000 0x1E0000 0x1E0000
5 app1 app1 app app ota_1 ota_1 0x1F0000 0x1F0000 0x1E0000 0x1E0000
6 spiffs spiffs data data spiffs spiffs 0x3D0000 0x3D0000 0x20000 0x20000
7 coredump coredump data data coredump coredump 0x3F0000 0x3F0000 0x10000 0x10000

View File

@ -22,6 +22,7 @@ AgApiClient::~AgApiClient() {}
void AgApiClient::begin(void) { void AgApiClient::begin(void) {
getConfigFailed = false; getConfigFailed = false;
postToServerFailed = false; postToServerFailed = false;
logInfo("Init apiRoot: " + apiRoot);
logInfo("begin"); logInfo("begin");
} }
@ -44,9 +45,8 @@ bool AgApiClient::fetchServerConfiguration(void) {
return false; return false;
} }
String uri = String uri = apiRoot + "/sensors/airgradient:" +
"http://hw.airgradient.com/sensors/airgradient:" + ag->deviceId() + ag->deviceId() + "/one/config";
"/one/config";
/** Init http client */ /** Init http client */
#ifdef ESP8266 #ifdef ESP8266
@ -66,6 +66,10 @@ bool AgApiClient::fetchServerConfiguration(void) {
/** Get data */ /** Get data */
int retCode = client.GET(); int retCode = client.GET();
logInfo(String("GET: ") + uri);
logInfo(String("Return code: ") + String(retCode));
if (retCode != 200) { if (retCode != 200) {
client.end(); client.end();
getConfigFailed = true; getConfigFailed = true;
@ -112,18 +116,23 @@ bool AgApiClient::postToServer(String data) {
String uri = String uri =
"http://hw.airgradient.com/sensors/airgradient:" + ag->deviceId() + "http://hw.airgradient.com/sensors/airgradient:" + ag->deviceId() +
"/measures"; "/measures";
logInfo("Post uri: " + uri); // logInfo("Post uri: " + uri);
logInfo("Post data: " + data); // logInfo("Post data: " + data);
WiFiClient wifiClient; WiFiClient wifiClient;
HTTPClient client; HTTPClient client;
if (client.begin(wifiClient, uri.c_str()) == false) { if (client.begin(wifiClient, uri.c_str()) == false) {
logError("Init client failed");
return false; return false;
} }
client.addHeader("content-type", "application/json"); client.addHeader("content-type", "application/json");
int retCode = client.POST(data); int retCode = client.POST(data);
client.end(); client.end();
logInfo(String("POST: ") + uri);
logInfo(String("DATA: ") + data);
logInfo(String("Return code: ") + String(retCode));
if ((retCode == 200) || (retCode == 429)) { if ((retCode == 200) || (retCode == 429)) {
postToServerFailed = false; postToServerFailed = false;
return true; return true;
@ -177,3 +186,7 @@ bool AgApiClient::sendPing(int rssi, int bootCount) {
root["boot"] = bootCount; root["boot"] = bootCount;
return postToServer(JSON.stringify(root)); return postToServer(JSON.stringify(root));
} }
String AgApiClient::getApiRoot() const { return apiRoot; }
void AgApiClient::setApiRoot(const String &apiRoot) { this->apiRoot = apiRoot; }

View File

@ -20,6 +20,7 @@ class AgApiClient : public PrintLog {
private: private:
Configuration &config; Configuration &config;
AirGradient *ag; AirGradient *ag;
String apiRoot = "http://hw.airgradient.com";
bool getConfigFailed; bool getConfigFailed;
bool postToServerFailed; bool postToServerFailed;
@ -37,6 +38,8 @@ public:
bool isNotAvailableOnDashboard(void); bool isNotAvailableOnDashboard(void);
void setAirGradient(AirGradient *ag); void setAirGradient(AirGradient *ag);
bool sendPing(int rssi, int bootCount); bool sendPing(int rssi, int bootCount);
String getApiRoot() const;
void setApiRoot(const String &apiRoot);
}; };
#endif /** _AG_API_CLIENT_H_ */ #endif /** _AG_API_CLIENT_H_ */

View File

@ -264,12 +264,14 @@ bool Configuration::parse(String data, bool isLocal) {
jconfig[jprop_configurationControl]); jconfig[jprop_configurationControl]);
} }
/** Check to return result if configurationControl is 'cloud' */ /** Return failed if new "configurationControl" new and old is "cloud" */
if (ctrl == if (ctrl == String(CONFIGURATION_CONTROL_NAME [ConfigurationControl::ConfigurationControlCloud])) {
String(CONFIGURATION_CONTROL_NAME if(ctrl != lastCtrl) {
[ConfigurationControl::ConfigurationControlCloud])) { return true;
failedMessage = String(msg); } else {
return true; failedMessage = String(msg);
return false;
}
} }
} else { } else {
failedMessage = failedMessage =

View File

@ -304,6 +304,10 @@ void OledDisplay::showDashboard(const char *status) {
DISP()->drawStr(55, 27, "PM2.5"); DISP()->drawStr(55, 27, "PM2.5");
/** Draw PM2.5 value */ /** Draw PM2.5 value */
int pm25 = value.pm25_1;
if (config.hasSensorSHT) {
pm25 = ag->pms5003.compensated(pm25, value.Humidity);
}
DISP()->setFont(u8g2_font_t0_22b_tf); DISP()->setFont(u8g2_font_t0_22b_tf);
if (config.isPmStandardInUSAQI()) { if (config.isPmStandardInUSAQI()) {
if (utils::isValidPMS(value.pm25_1)) { if (utils::isValidPMS(value.pm25_1)) {
@ -338,7 +342,7 @@ void OledDisplay::showDashboard(const char *status) {
DISP()->drawStr(100, 39, strBuf); DISP()->drawStr(100, 39, strBuf);
/** Draw NOx label */ /** Draw NOx label */
DISP()->drawStr(85, 53, "NOx:"); DISP()->drawStr(100, 53, "NOx:");
if (utils::isValidNOx(value.NOx)) { if (utils::isValidNOx(value.NOx)) {
sprintf(strBuf, "%d", value.NOx); sprintf(strBuf, "%d", value.NOx);
} else { } else {
@ -360,6 +364,10 @@ void OledDisplay::showDashboard(const char *status) {
ag->display.setText(strBuf); ag->display.setText(strBuf);
/** Set PM */ /** Set PM */
int pm25 = value.pm25_1;
if(config.hasSensorSHT) {
pm25 = (int)ag->pms5003.compensated(pm25, value.Humidity);
}
ag->display.setCursor(0, 12); ag->display.setCursor(0, 12);
if (utils::isValidPMS(value.pm25_1)) { if (utils::isValidPMS(value.pm25_1)) {
snprintf(strBuf, sizeof(strBuf), "PM2.5:%d", value.pm25_1); snprintf(strBuf, sizeof(strBuf), "PM2.5:%d", value.pm25_1);

View File

@ -88,6 +88,13 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
} }
} }
if (config->hasSensorSHT && config->hasSensorPMS1) {
int pm25 = ag->pms5003.compensated(this->pm25_1, this->Humidity);
if (pm25 >= 0) {
root["pm02Compensated"] = pm25;
}
}
} else { } else {
if (config->hasSensorPMS1 && config->hasSensorPMS2) { if (config->hasSensorPMS1 && config->hasSensorPMS2) {
if (utils::isValidPMS(this->pm01_1) && utils::isValidPMS(this->pm01_2)) { if (utils::isValidPMS(this->pm01_1) && utils::isValidPMS(this->pm01_2)) {
@ -122,6 +129,11 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
} }
} }
} }
int pm25 = (ag->pms5003t_1.compensated(this->pm25_1, this->temp_1) +
ag->pms5003t_2.compensated(this->pm25_2, this->temp_2)) /
2;
root["pm02Compensated"] = pm25;
} }
if (fwMode == FW_MODE_O_1PS || fwMode == FW_MODE_O_1PST) { if (fwMode == FW_MODE_O_1PS || fwMode == FW_MODE_O_1PST) {
@ -159,6 +171,7 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
} }
} }
} }
root["pm02Compensated"] = ag->pms5003t_1.compensated(this->pm25_1, this->temp_1);
if (!localServer) { if (!localServer) {
root[json_prop_pmFirmware] = root[json_prop_pmFirmware] =
pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion()); pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion());
@ -199,6 +212,7 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
} }
} }
} }
root["pm02Compensated"] = ag->pms5003t_2.compensated(this->pm25_2, this->temp_2);
if(!localServer) { if(!localServer) {
root[json_prop_pmFirmware] = root[json_prop_pmFirmware] =
pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion()); pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion());
@ -239,6 +253,7 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
} }
} }
} }
root["pm02Compensated"] = ag->pms5003t_1.compensated(this->pm25_1, this->temp_1);
if(!localServer) { if(!localServer) {
root[json_prop_pmFirmware] = root[json_prop_pmFirmware] =
pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion()); pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion());
@ -276,6 +291,7 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
} }
} }
} }
root["pm02Compensated"] = ag->pms5003t_1.compensated(this->pm25_1, this->temp_1);
if(!localServer) { if(!localServer) {
root[json_prop_pmFirmware] = root[json_prop_pmFirmware] =
pms5003TFirmwareVersion(ag->pms5003t_2.getFirmwareVersion()); pms5003TFirmwareVersion(ag->pms5003t_2.getFirmwareVersion());
@ -316,6 +332,7 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
} }
} }
} }
root["channels"]["1"]["pm02Compensated"] = ag->pms5003t_1.compensated(this->pm25_1, this->temp_1);
// PMS5003T version // PMS5003T version
if(!localServer) { if(!localServer) {
@ -357,6 +374,7 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
} }
} }
} }
root["channels"]["2"]["pm02Compensated"] = ag->pms5003t_2.compensated(this->pm25_2, this->temp_2);
// PMS5003T version // PMS5003T version
if(!localServer) { if(!localServer) {
root["channels"]["2"][json_prop_pmFirmware] = root["channels"]["2"][json_prop_pmFirmware] =

View File

@ -41,7 +41,14 @@ String AirGradient::getVersion(void) { return GIT_VERSION; }
BoardType AirGradient::getBoardType(void) { return boardType; } BoardType AirGradient::getBoardType(void) { return boardType; }
double AirGradient::round2(double value) { double AirGradient::round2(double value) {
return (int)(value * 100 + 0.5) / 100.0; double ret;
if (value >= 0) {
ret = (int)(value * 100 + 0.5f);
} else {
ret = (int)(value * 100 - 0.5f);
}
return ret / 100;
} }
String AirGradient::getBoardName(void) { String AirGradient::getBoardName(void) {

View File

@ -15,7 +15,7 @@
#include "Main/utils.h" #include "Main/utils.h"
#ifndef GIT_VERSION #ifndef GIT_VERSION
#define GIT_VERSION "snapshot" #define GIT_VERSION "3.1.5-snap"
#endif #endif
/** /**

View File

@ -12,10 +12,14 @@
#define VALID_PMS_MIN (0) #define VALID_PMS_MIN (0)
#define INVALID_PMS (-1) #define INVALID_PMS (-1)
#define VALID_PMS03COUNT_MIN (0)
#define VALID_CO2_MAX (10000) #define VALID_CO2_MAX (10000)
#define VALID_CO2_MIN (0) #define VALID_CO2_MIN (0)
#define INVALID_CO2 (-1) #define INVALID_CO2 (-1)
#define VALID_NOX_MIN (0)
#define VALID_VOC_MIN (0)
#define INVALID_NOX (-1) #define INVALID_NOX (-1)
#define INVALID_VOC (-1) #define INVALID_VOC (-1)
@ -24,49 +28,49 @@ utils::utils(/* args */) {}
utils::~utils() {} utils::~utils() {}
bool utils::isValidTemperature(float value) { bool utils::isValidTemperature(float value) {
if (value >= VALID_TEMPERATURE_MIN && value <= VALID_TEMPERATURE_MAX) { if ((value >= VALID_TEMPERATURE_MIN) && (value <= VALID_TEMPERATURE_MAX)) {
return true; return true;
} }
return false; return false;
} }
bool utils::isValidHumidity(float value) { bool utils::isValidHumidity(float value) {
if (value >= VALID_HUMIDITY_MIN && value <= VALID_HUMIDITY_MAX) { if ((value >= VALID_HUMIDITY_MIN) && (value <= VALID_HUMIDITY_MAX)) {
return true; return true;
} }
return false; return false;
} }
bool utils::isValidCO2(int16_t value) { bool utils::isValidCO2(int16_t value) {
if (value >= VALID_CO2_MIN && value <= VALID_CO2_MAX) { if ((value >= VALID_CO2_MIN) && (value <= VALID_CO2_MAX)) {
return true; return true;
} }
return false; return false;
} }
bool utils::isValidPMS(int value) { bool utils::isValidPMS(int value) {
if (value >= VALID_PMS_MIN && value <= VALID_PMS_MAX) { if ((value >= VALID_PMS_MIN) && (value <= VALID_PMS_MAX)) {
return true; return true;
} }
return false; return false;
} }
bool utils::isValidPMS03Count(int value) { bool utils::isValidPMS03Count(int value) {
if (value >= 0) { if (value >= VALID_PMS03COUNT_MIN) {
return true; return true;
} }
return false; return false;
} }
bool utils::isValidNOx(int value) { bool utils::isValidNOx(int value) {
if (value > INVALID_NOX) { if (value >= VALID_NOX_MIN) {
return true; return true;
} }
return false; return false;
} }
bool utils::isValidVOC(int value) { bool utils::isValidVOC(int value) {
if (value > INVALID_VOC) { if (value >= VALID_VOC_MIN) {
return true; return true;
} }
return false; return false;

View File

@ -3,7 +3,7 @@
/** /**
* @brief Init and check that sensor has connected * @brief Init and check that sensor has connected
* *
* @param stream UART stream * @param stream UART stream
* @return true Sucecss * @return true Sucecss
* @return false Failure * @return false Failure
@ -86,7 +86,7 @@ void PMSBase::handle() {
case 2: { case 2: {
buf[bufIndex++] = value; buf[bufIndex++] = value;
if (bufIndex >= 4) { if (bufIndex >= 4) {
len = toValue(&buf[2]); len = toI16(&buf[2]);
if (len != 28) { if (len != 28) {
// Serial.printf("Got good bad len %d\r\n", len); // Serial.printf("Got good bad len %d\r\n", len);
len += 4; len += 4;
@ -152,98 +152,98 @@ bool PMSBase::isFailed(void) { return failed; }
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getRaw0_1(void) { return toValue(&package[4]); } uint16_t PMSBase::getRaw0_1(void) { return toU16(&package[4]); }
/** /**
* @brief Read PMS 2.5 ug/m3 with CF = 1 PM estimates * @brief Read PMS 2.5 ug/m3 with CF = 1 PM estimates
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getRaw2_5(void) { return toValue(&package[6]); } uint16_t PMSBase::getRaw2_5(void) { return toU16(&package[6]); }
/** /**
* @brief Read PMS 10 ug/m3 with CF = 1 PM estimates * @brief Read PMS 10 ug/m3 with CF = 1 PM estimates
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getRaw10(void) { return toValue(&package[8]); } uint16_t PMSBase::getRaw10(void) { return toU16(&package[8]); }
/** /**
* @brief Read PMS 0.1 ug/m3 * @brief Read PMS 0.1 ug/m3
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getPM0_1(void) { return toValue(&package[10]); } uint16_t PMSBase::getPM0_1(void) { return toU16(&package[10]); }
/** /**
* @brief Read PMS 2.5 ug/m3 * @brief Read PMS 2.5 ug/m3
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getPM2_5(void) { return toValue(&package[12]); } uint16_t PMSBase::getPM2_5(void) { return toU16(&package[12]); }
/** /**
* @brief Read PMS 10 ug/m3 * @brief Read PMS 10 ug/m3
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getPM10(void) { return toValue(&package[14]); } uint16_t PMSBase::getPM10(void) { return toU16(&package[14]); }
/** /**
* @brief Get numnber concentrations over 0.3 um/0.1L * @brief Get numnber concentrations over 0.3 um/0.1L
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getCount0_3(void) { return toValue(&package[16]); } uint16_t PMSBase::getCount0_3(void) { return toU16(&package[16]); }
/** /**
* @brief Get numnber concentrations over 0.5 um/0.1L * @brief Get numnber concentrations over 0.5 um/0.1L
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getCount0_5(void) { return toValue(&package[18]); } uint16_t PMSBase::getCount0_5(void) { return toU16(&package[18]); }
/** /**
* @brief Get numnber concentrations over 1.0 um/0.1L * @brief Get numnber concentrations over 1.0 um/0.1L
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getCount1_0(void) { return toValue(&package[20]); } uint16_t PMSBase::getCount1_0(void) { return toU16(&package[20]); }
/** /**
* @brief Get numnber concentrations over 2.5 um/0.1L * @brief Get numnber concentrations over 2.5 um/0.1L
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getCount2_5(void) { return toValue(&package[22]); } uint16_t PMSBase::getCount2_5(void) { return toU16(&package[22]); }
/** /**
* @brief Get numnber concentrations over 5.0 um/0.1L (only PMS5003) * @brief Get numnber concentrations over 5.0 um/0.1L (only PMS5003)
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getCount5_0(void) { return toValue(&package[24]); } uint16_t PMSBase::getCount5_0(void) { return toU16(&package[24]); }
/** /**
* @brief Get numnber concentrations over 10.0 um/0.1L (only PMS5003) * @brief Get numnber concentrations over 10.0 um/0.1L (only PMS5003)
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getCount10(void) { return toValue(&package[26]); } uint16_t PMSBase::getCount10(void) { return toU16(&package[26]); }
/** /**
* @brief Get temperature (only PMS5003T) * @brief Get temperature (only PMS5003T)
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getTemp(void) { return toValue(&package[24]); } int16_t PMSBase::getTemp(void) { return toI16(&package[24]); }
/** /**
* @brief Get humidity (only PMS5003T) * @brief Get humidity (only PMS5003T)
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getHum(void) { return toValue(&package[26]); } uint16_t PMSBase::getHum(void) { return toU16(&package[26]); }
/** /**
* @brief Get firmware version code * @brief Get firmware version code
@ -284,13 +284,58 @@ int PMSBase::pm25ToAQI(int pm02) {
return 500; return 500;
} }
/**
* @brief Correction PM2.5
*
* @param pm25 Raw PM2.5 value
* @param humidity Humidity value (%)
* @return int
*/
int PMSBase::compensated(int pm25, float humidity) {
float value;
if (humidity < 0) {
humidity = 0;
}
if (humidity > 100) {
humidity = 100;
}
if(pm25 < 30) {
value = (pm25 * 0.524f) - (humidity * 0.0862f) + 5.75f;
} else if(pm25 < 50) {
value = (0.786f * (pm25 / 20 - 3 / 2) + 0.524f * (1 - (pm25 / 20 - 3 / 2))) * pm25 - (0.0862f * humidity) + 5.75f;
} else if(pm25 < 210) {
value = (0.786f * pm25) - (0.0862f * humidity) + 5.75f;
} else if(pm25 < 260) {
value = (0.69f * (pm25/50 - 21/5) + 0.786f * (1 - (pm25/50 - 21/5))) * pm25 - (0.0862f * humidity * (1 - (pm25/50 - 21/5))) + (2.966f * (pm25/50 -21/5)) + (5.75f * (1 - (pm25/50 - 21/5))) + (8.84f * (1.e-4) * pm25* (pm25/50 - 21/5));
} else {
value = 2.966f + (0.69f * pm25) + (8.84f * (1.e-4) * pm25);
}
if(value < 0) {
value = 0;
}
return (int)value;
}
/** /**
* @brief Convert two byte value to uint16_t value * @brief Convert two byte value to uint16_t value
* *
* @param buf bytes array (must be >= 2) * @param buf bytes array (must be >= 2)
* @return uint16_t * @return int16_t
*/ */
uint16_t PMSBase::toValue(char *buf) { return (buf[0] << 8) | buf[1]; } int16_t PMSBase::toI16(char *buf) {
int16_t value = buf[0];
value = (value << 8) | buf[1];
return value;
}
uint16_t PMSBase::toU16(char *buf) {
uint16_t value = buf[0];
value = (value << 8) | buf[1];
return value;
}
/** /**
* @brief Validate package data * @brief Validate package data
@ -304,7 +349,7 @@ bool PMSBase::validate(char *buf) {
for (int i = 0; i < 30; i++) { for (int i = 0; i < 30; i++) {
sum += buf[i]; sum += buf[i];
} }
if (sum == toValue(&buf[30])) { if (sum == toU16(&buf[30])) {
for (int i = 0; i < 32; i++) { for (int i = 0; i < 32; i++) {
package[i] = buf[i]; package[i] = buf[i];
} }

View File

@ -24,12 +24,13 @@ public:
uint16_t getCount10(void); uint16_t getCount10(void);
/** For PMS5003T*/ /** For PMS5003T*/
uint16_t getTemp(void); int16_t getTemp(void);
uint16_t getHum(void); uint16_t getHum(void);
uint8_t getFirmwareVersion(void); uint8_t getFirmwareVersion(void);
uint8_t getErrorCode(void); uint8_t getErrorCode(void);
int pm25ToAQI(int pm02); int pm25ToAQI(int pm02);
int compensated(int pm25, float humidity);
private: private:
Stream *stream; Stream *stream;
@ -38,7 +39,8 @@ private:
bool failed = false; bool failed = false;
uint32_t lastRead; uint32_t lastRead;
uint16_t toValue(char *buf); int16_t toI16(char *buf);
uint16_t toU16(char* buf);
bool validate(char *buf); bool validate(char *buf);
}; };

View File

@ -121,6 +121,17 @@ int PMS5003::getPm03ParticleCount(void) {
*/ */
int PMS5003::convertPm25ToUsAqi(int pm25) { return pms.pm25ToAQI(pm25); } int PMS5003::convertPm25ToUsAqi(int pm25) { return pms.pm25ToAQI(pm25); }
/**
* @brief Correct PM2.5
*
* @param pm25 PM2.5 raw value
* @param humidity Humidity value
* @return float
*/
int PMS5003::compensated(int pm25, float humidity) {
return pms.compensated(pm25, humidity);
}
/** /**
* @brief Get sensor firmware version * @brief Get sensor firmware version
* *

View File

@ -24,6 +24,7 @@ public:
int getPm10Ae(void); int getPm10Ae(void);
int getPm03ParticleCount(void); int getPm03ParticleCount(void);
int convertPm25ToUsAqi(int pm25); int convertPm25ToUsAqi(int pm25);
int compensated(int pm25, float humidity);
int getFirmwareVersion(void); int getFirmwareVersion(void);
uint8_t getErrorCode(void); uint8_t getErrorCode(void);

View File

@ -164,6 +164,17 @@ float PMS5003T::getRelativeHumidity(void) {
return pms.getHum() / 10.0f; return pms.getHum() / 10.0f;
} }
/**
* @brief Correct PM2.5
*
* @param pm25 PM2.5 raw value
* @param humidity Humidity value
* @return float
*/
float PMS5003T::compensated(int pm25, float humidity) {
return pms.compensated(pm25, humidity);
}
/** /**
* @brief Get module(s) firmware version * @brief Get module(s) firmware version
* *

View File

@ -29,6 +29,7 @@ public:
int convertPm25ToUsAqi(int pm25); int convertPm25ToUsAqi(int pm25);
float getTemperature(void); float getTemperature(void);
float getRelativeHumidity(void); float getRelativeHumidity(void);
float compensated(int pm25, float humidity);
int getFirmwareVersion(void); int getFirmwareVersion(void);
uint8_t getErrorCode(void); uint8_t getErrorCode(void);