diff --git a/docs/local-server.md b/docs/local-server.md index 1bd0f655..1daede06 100644 --- a/docs/local-server.md +++ b/docs/local-server.md @@ -17,62 +17,77 @@ With the path "/measures/current" you can get the current air quality data. 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: -~~~ -{"wifi":-46, -"serialno":"ecda3b1eaaaf", -"rco2":447, -"pm01":3, -"pm02":7, -"pm10":8, -"pm003Count":442, -"atmp":25.87, -"rhum":43, -"tvocIndex":100, -"tvoc_raw":33051, -"noxIndex":1, -"nox_raw":16307, -"boot":6, -"ledMode":"pm", -"firmwareVersion":"3.0.10beta", -"fwMode":"I-9PSL"} -~~~ - -|Properties|Type|Explanation| -|-|-|-| -|serialno|String| Serial Number of the monitor| -|wifi|Number| WiFi signal strength| -|pm01, pm02, pm10|Number| PM1, PM2.5 and PM10 in ug/m3| -|rco2|Number| CO2 in ppm| -|pm003Count|Number| Particle count per dL| -|atmp|Number| Temperature in Degrees Celcius| -|rhum|Number| Relative Humidity| -|tvocIndex|Number| Senisiron VOC Index| -|tvoc_raw|Number| VOC raw value| -|noxIndex|Number| Senisirion NOx Index| -|nox_raw|Number| NOx raw value| -|boot|Number| Counts every measurement cycle. Low boot counts indicate restarts.| -|ledMode|String| Current configuration of the LED mode| -|firmwareVersion|String| Current firmware version| -|fwMode|String| Current model name| +```json +{ + "wifi": -46, + "serialno": "ecda3b1eaaaf", + "rco2": 447, + "pm01": 3, + "pm02": 7, + "pm10": 8, + "pm003Count": 442, + "atmp": 25.87, + "atmpCompensated": 24.47, + "rhum": 43, + "rhumCompensated": 49, + "tvocIndex": 100, + "tvocRaw": 33051, + "noxIndex": 1, + "noxRaw": 16307, + "boot": 6, + "bootCount": 6, + "ledMode": "pm", + "firmware": "3.1.3", + "model": "I-9PSL" +} +``` + +| Properties | Type | Explanation | +|------------------|--------|--------------------------------------------------------------------| +| `serialno` | String | Serial Number of the monitor | +| `wifi` | Number | WiFi signal strength | +| `pm01` | Number | PM1 in ug/m3 | +| `pm02` | Number | PM2.5 in ug/m3 | +| `pm10` | Number | PM10 in ug/m3 | +| `pm02Compensated` | Number | PM2.5 in ug/m3 with correction applied (from fw version 3.1.4 onwards) | +| `rco2` | Number | CO2 in ppm | +| `pm003Count` | Number | Particle count per dL | +| `atmp` | Number | Temperature in Degrees Celsius | +| `atmpCompensated` | Number | Temperature in Degrees Celsius with correction applied | +| `rhum` | Number | Relative Humidity | +| `rhumCompensated` | Number | Relative Humidity with correction applied | +| `tvocIndex` | Number | Senisiron VOC Index | +| `tvocRaw` | Number | VOC raw value | +| `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) With the path "/config" you can get the current configuration. -~~~ -{"country":"US", -"pmStandard":"ugm3", -"ledBarMode":"pm", -"displayMode":"on", -"abcDays":30, -"tvocLearningOffset":12, -"noxLearningOffset":12, -"mqttBrokerUrl":"", -"temperatureUnit":"f", -"configurationControl":"both", -"postDataToAirGradient":true} -~~~ +```json +{ + "country": "US", + "pmStandard": "ugm3", + "ledBarMode": "pm", + "displayMode": "on", + "abcDays": 30, + "tvocLearningOffset": 12, + "noxLearningOffset": 12, + "mqttBrokerUrl": "", + "temperatureUnit": "f", + "configurationControl": "both", + "postDataToAirGradient": true +} +``` #### 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 ``` -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 ``` + If you use command prompt on Windows, you need to escape the quotes: + + ``` -d "{\"param\":\"value\"}" ``` + #### 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) -|Properties|Type|Accepted Values|Example| -|-|-|-|-| -|country|String| Country code as [ALPHA-2 notation](https://www.iban.com/country-codes) | {"country": "TH"}| -|pmStandard|String|ugm3 : ug/m3
usaqi: USAQI | {"pmStandard": "ugm3"}| -|ledBarMode|String|co2: LED bar displays CO2
pm: LED bar displays PM
off: Turn off LED bar | {"ledBarMode": "off"}| -|abcDays|Number|Number of days for CO2 automatic baseline balibration. Maximum 200 days. Default 8 days. | {"abcDays": 8}| -|mqttBrokerUrl|String|MQTT broker URL. | {"mqttBrokerUrl":"mqtt://192.168.0.18:1883"} | -|temperatureUnit|String|c or C: Degree Celsius °C
f or F: Degree Fahrenheit °F | {"temperatureUnit": "c"}| -|configurationControl|String|both : Accept local and cloud configuration
local : Accept only local configuration
cloud : Accept only cloud configuration | {"configurationControl": "both"}| -|postDataToAirGradient|Boolean|Send data to AirGradient cloud:
true : Enabled
false: Disabled | {"postDataToAirGradient": true}| -|co2CalibrationRequested|Boolean|Trigger CO2 calibration (400ppm) on monitor:
true : Calibration will be triggered | {"co2CalibrationRequested": true}| -|ledBarTestRequested|Boolean|Test LED bar:
true : LEDs will run test sequence | {"ledBarTestRequested": true}| +| Properties | Description | Type | Accepted Values | Example | +|-------------------------|:-------------------------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------| +| `country` | Country where the device is. | String | Country code as [ALPHA-2 notation](https://www.iban.com/country-codes) | {"country": "TH"} | +| `model` | Hardware identifier (only GET). | String | I-9PSL-DE | {"model": "I-9PSL-DE"} | +| `pmStandard` | Particle matter standard used on the display. | String | `ugm3`: ug/m3
`us-aqi`: USAQI | {"pmStandard": "ugm3"} | +| `ledBarMode` | Mode in which the led bar can be set. | String | `co2`: LED bar displays CO2
`pm`: LED bar displays PM
`off`: Turn off LED bar | {"ledBarMode": "off"} | +| `displayBrightness` | Brightness of the Display. | Number | 0-100 | {"displayBrightness": 50} | +| `ledBarBrightness` | Brightness of the LEDBar. | Number | 0-100 | {"ledBarBrightness": 40} | +| `abcDays` | Number of days for CO2 automatic baseline calibration. | Number | Maximum 200 days. Default 8 days. | {"abcDays": 8} | +| `mqttBrokerUrl` | MQTT broker URL. | String | | {"mqttBrokerUrl": "mqtt://192.168.0.18:1883"} | +| `temperatureUnit` | Temperature unit shown on the display. | String | `c` or `C`: Degree Celsius °C
`f` or `F`: Degree Fahrenheit °F | {"temperatureUnit": "c"} | +| `configurationControl` | The configuration source of the device. | String | `both`: Accept local and cloud configuration
`local`: Accept only local configuration
`cloud`: Accept only cloud configuration | {"configurationControl": "both"} | +| `postDataToAirGradient` | Send data to AirGradient cloud. | Boolean | `true`: Enabled
`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)
`true`: Enabled | {"offlineMode": true} | diff --git a/examples/BASIC/BASIC.ino b/examples/BASIC/BASIC.ino index f212c6df..4099b712 100644 --- a/examples/BASIC/BASIC.ino +++ b/examples/BASIC/BASIC.ino @@ -126,6 +126,9 @@ void setup() { openMetrics.setAirGradient(&ag); localServer.setAirGraident(&ag); + /** Example set custom API root URL */ + // apiClient.setApiRoot("https://example.custom.api"); + /** Init sensor */ boardInit(); diff --git a/examples/DiyProIndoorV3_3/DiyProIndoorV3_3.ino b/examples/DiyProIndoorV3_3/DiyProIndoorV3_3.ino index 6b43a06c..df059b79 100644 --- a/examples/DiyProIndoorV3_3/DiyProIndoorV3_3.ino +++ b/examples/DiyProIndoorV3_3/DiyProIndoorV3_3.ino @@ -126,6 +126,9 @@ void setup() { openMetrics.setAirGradient(&ag); localServer.setAirGraident(&ag); + /** Example set custom API root URL */ + // apiClient.setApiRoot("https://example.custom.api"); + /** Init sensor */ boardInit(); diff --git a/examples/DiyProIndoorV4_2/DiyProIndoorV4_2.ino b/examples/DiyProIndoorV4_2/DiyProIndoorV4_2.ino index 4322238a..20be418b 100644 --- a/examples/DiyProIndoorV4_2/DiyProIndoorV4_2.ino +++ b/examples/DiyProIndoorV4_2/DiyProIndoorV4_2.ino @@ -127,6 +127,9 @@ void setup() { openMetrics.setAirGradient(&ag); localServer.setAirGraident(&ag); + /** Example set custom API root URL */ + // apiClient.setApiRoot("https://example.custom.api"); + /** Init sensor */ boardInit(); diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index 5f026cae..978c345b 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -163,6 +163,9 @@ void setup() { openMetrics.setAirGradient(ag); localServer.setAirGraident(ag); + /** Example set custom API root URL */ + // apiClient.setApiRoot("https://example.custom.api"); + /** Init sensor */ boardInit(); @@ -421,8 +424,8 @@ static void factoryConfigReset(void) { } /** Reset WIFI */ - WiFi.enableSTA(true); // Incase offline mode - WiFi.disconnect(true, true); + Serial.println("Set wifi connect to 'airgradient' as default"); + WiFi.begin("airgradient", "cleanair"); /** Reset local config */ configuration.reset(); diff --git a/library.properties b/library.properties index a1065af5..40d93380 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=AirGradient Air Quality Sensor -version=3.1.4 +version=3.1.5 author=AirGradient maintainer=AirGradient sentence=ESP32-C3 / ESP8266 library for air quality monitor measuring PM, CO2, Temperature, TVOC and Humidity with OLED display. diff --git a/partitions.csv b/partitions.csv index 080f491d..2854b60c 100644 --- a/partitions.csv +++ b/partitions.csv @@ -1,7 +1,7 @@ -# Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 0x9000, 0x5000, -otadata, data, ota, 0xe000, 0x2000, -app0, app, ota_0, 0x10000, 0x1E0000, -app1, app, ota_1, 0x1F0000,0x1E0000, -spiffs, data, spiffs, 0x3D0000,0x20000, -coredump, data, coredump,0x3F0000,0x10000, \ No newline at end of file +# Name ,Type ,SubType ,Offset ,Size ,Flags +nvs ,data ,nvs ,0x9000 ,0x5000 , +otadata ,data ,ota ,0xe000 ,0x2000 , +app0 ,app ,ota_0 ,0x10000 ,0x1E0000 , +app1 ,app ,ota_1 ,0x1F0000 ,0x1E0000 , +spiffs ,data ,spiffs ,0x3D0000 ,0x20000 , +coredump ,data ,coredump ,0x3F0000 ,0x10000 , diff --git a/src/AgApiClient.cpp b/src/AgApiClient.cpp index 1f7c83b2..fe582a43 100644 --- a/src/AgApiClient.cpp +++ b/src/AgApiClient.cpp @@ -22,6 +22,7 @@ AgApiClient::~AgApiClient() {} void AgApiClient::begin(void) { getConfigFailed = false; postToServerFailed = false; + logInfo("Init apiRoot: " + apiRoot); logInfo("begin"); } @@ -44,9 +45,8 @@ bool AgApiClient::fetchServerConfiguration(void) { return false; } - String uri = - "http://hw.airgradient.com/sensors/airgradient:" + ag->deviceId() + - "/one/config"; + String uri = apiRoot + "/sensors/airgradient:" + + ag->deviceId() + "/one/config"; /** Init http client */ #ifdef ESP8266 @@ -66,6 +66,10 @@ bool AgApiClient::fetchServerConfiguration(void) { /** Get data */ int retCode = client.GET(); + + logInfo(String("GET: ") + uri); + logInfo(String("Return code: ") + String(retCode)); + if (retCode != 200) { client.end(); getConfigFailed = true; @@ -112,18 +116,23 @@ bool AgApiClient::postToServer(String data) { String uri = "http://hw.airgradient.com/sensors/airgradient:" + ag->deviceId() + "/measures"; - logInfo("Post uri: " + uri); - logInfo("Post data: " + data); + // logInfo("Post uri: " + uri); + // logInfo("Post data: " + data); WiFiClient wifiClient; HTTPClient client; if (client.begin(wifiClient, uri.c_str()) == false) { + logError("Init client failed"); return false; } client.addHeader("content-type", "application/json"); int retCode = client.POST(data); client.end(); + logInfo(String("POST: ") + uri); + logInfo(String("DATA: ") + data); + logInfo(String("Return code: ") + String(retCode)); + if ((retCode == 200) || (retCode == 429)) { postToServerFailed = false; return true; @@ -177,3 +186,7 @@ bool AgApiClient::sendPing(int rssi, int bootCount) { root["boot"] = bootCount; return postToServer(JSON.stringify(root)); } + +String AgApiClient::getApiRoot() const { return apiRoot; } + +void AgApiClient::setApiRoot(const String &apiRoot) { this->apiRoot = apiRoot; } diff --git a/src/AgApiClient.h b/src/AgApiClient.h index 4b37bf3f..7e6037da 100644 --- a/src/AgApiClient.h +++ b/src/AgApiClient.h @@ -20,6 +20,7 @@ class AgApiClient : public PrintLog { private: Configuration &config; AirGradient *ag; + String apiRoot = "http://hw.airgradient.com"; bool getConfigFailed; bool postToServerFailed; @@ -37,6 +38,8 @@ class AgApiClient : public PrintLog { bool isNotAvailableOnDashboard(void); void setAirGradient(AirGradient *ag); bool sendPing(int rssi, int bootCount); + String getApiRoot() const; + void setApiRoot(const String &apiRoot); }; #endif /** _AG_API_CLIENT_H_ */ diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index 4518f244..99e00c1b 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -264,12 +264,14 @@ bool Configuration::parse(String data, bool isLocal) { jconfig[jprop_configurationControl]); } - /** Check to return result if configurationControl is 'cloud' */ - if (ctrl == - String(CONFIGURATION_CONTROL_NAME - [ConfigurationControl::ConfigurationControlCloud])) { - failedMessage = String(msg); - return true; + /** Return failed if new "configurationControl" new and old is "cloud" */ + if (ctrl == String(CONFIGURATION_CONTROL_NAME [ConfigurationControl::ConfigurationControlCloud])) { + if(ctrl != lastCtrl) { + return true; + } else { + failedMessage = String(msg); + return false; + } } } else { failedMessage = diff --git a/src/AgOledDisplay.cpp b/src/AgOledDisplay.cpp index 044fb8ec..8b07adaa 100644 --- a/src/AgOledDisplay.cpp +++ b/src/AgOledDisplay.cpp @@ -304,6 +304,10 @@ void OledDisplay::showDashboard(const char *status) { DISP()->drawStr(55, 27, "PM2.5"); /** 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); if (config.isPmStandardInUSAQI()) { if (utils::isValidPMS(value.pm25_1)) { @@ -338,7 +342,7 @@ void OledDisplay::showDashboard(const char *status) { DISP()->drawStr(100, 39, strBuf); /** Draw NOx label */ - DISP()->drawStr(85, 53, "NOx:"); + DISP()->drawStr(100, 53, "NOx:"); if (utils::isValidNOx(value.NOx)) { sprintf(strBuf, "%d", value.NOx); } else { @@ -360,6 +364,10 @@ void OledDisplay::showDashboard(const char *status) { ag->display.setText(strBuf); /** Set PM */ + int pm25 = value.pm25_1; + if(config.hasSensorSHT) { + pm25 = (int)ag->pms5003.compensated(pm25, value.Humidity); + } ag->display.setCursor(0, 12); if (utils::isValidPMS(value.pm25_1)) { snprintf(strBuf, sizeof(strBuf), "PM2.5:%d", value.pm25_1); diff --git a/src/AgValue.cpp b/src/AgValue.cpp index fc13dcb8..bca9aabd 100644 --- a/src/AgValue.cpp +++ b/src/AgValue.cpp @@ -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 { if (config->hasSensorPMS1 && config->hasSensorPMS2) { 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) { @@ -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) { root[json_prop_pmFirmware] = 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) { root[json_prop_pmFirmware] = 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) { root[json_prop_pmFirmware] = 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) { root[json_prop_pmFirmware] = 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 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 if(!localServer) { root["channels"]["2"][json_prop_pmFirmware] = diff --git a/src/AirGradient.cpp b/src/AirGradient.cpp index e0369820..46ed8747 100644 --- a/src/AirGradient.cpp +++ b/src/AirGradient.cpp @@ -41,7 +41,14 @@ String AirGradient::getVersion(void) { return GIT_VERSION; } BoardType AirGradient::getBoardType(void) { return boardType; } 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) { diff --git a/src/AirGradient.h b/src/AirGradient.h index 54abd918..e94b25ee 100644 --- a/src/AirGradient.h +++ b/src/AirGradient.h @@ -15,7 +15,7 @@ #include "Main/utils.h" #ifndef GIT_VERSION -#define GIT_VERSION "snapshot" +#define GIT_VERSION "3.1.5-snap" #endif /** diff --git a/src/Main/utils.cpp b/src/Main/utils.cpp index 386a520c..1c8175d4 100644 --- a/src/Main/utils.cpp +++ b/src/Main/utils.cpp @@ -12,10 +12,14 @@ #define VALID_PMS_MIN (0) #define INVALID_PMS (-1) +#define VALID_PMS03COUNT_MIN (0) + #define VALID_CO2_MAX (10000) #define VALID_CO2_MIN (0) #define INVALID_CO2 (-1) +#define VALID_NOX_MIN (0) +#define VALID_VOC_MIN (0) #define INVALID_NOX (-1) #define INVALID_VOC (-1) @@ -24,49 +28,49 @@ utils::utils(/* args */) {} utils::~utils() {} 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 false; } 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 false; } 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 false; } 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 false; } bool utils::isValidPMS03Count(int value) { - if (value >= 0) { + if (value >= VALID_PMS03COUNT_MIN) { return true; } return false; } bool utils::isValidNOx(int value) { - if (value > INVALID_NOX) { + if (value >= VALID_NOX_MIN) { return true; } return false; } bool utils::isValidVOC(int value) { - if (value > INVALID_VOC) { + if (value >= VALID_VOC_MIN) { return true; } return false; diff --git a/src/PMS/PMS.cpp b/src/PMS/PMS.cpp index dcb516ae..db7800d8 100644 --- a/src/PMS/PMS.cpp +++ b/src/PMS/PMS.cpp @@ -3,7 +3,7 @@ /** * @brief Init and check that sensor has connected - * + * * @param stream UART stream * @return true Sucecss * @return false Failure @@ -86,7 +86,7 @@ void PMSBase::handle() { case 2: { buf[bufIndex++] = value; if (bufIndex >= 4) { - len = toValue(&buf[2]); + len = toI16(&buf[2]); if (len != 28) { // Serial.printf("Got good bad len %d\r\n", len); len += 4; @@ -152,98 +152,98 @@ bool PMSBase::isFailed(void) { return failed; } * * @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 * * @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 * * @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 * * @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 * * @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 * * @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 * * @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 * * @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 * * @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 * * @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) * * @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) * * @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) * * @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) * * @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 @@ -284,13 +284,58 @@ int PMSBase::pm25ToAQI(int pm02) { 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 * * @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 @@ -304,7 +349,7 @@ bool PMSBase::validate(char *buf) { for (int i = 0; i < 30; i++) { sum += buf[i]; } - if (sum == toValue(&buf[30])) { + if (sum == toU16(&buf[30])) { for (int i = 0; i < 32; i++) { package[i] = buf[i]; } diff --git a/src/PMS/PMS.h b/src/PMS/PMS.h index 38bdb456..2083a12d 100644 --- a/src/PMS/PMS.h +++ b/src/PMS/PMS.h @@ -24,12 +24,13 @@ class PMSBase { uint16_t getCount10(void); /** For PMS5003T*/ - uint16_t getTemp(void); + int16_t getTemp(void); uint16_t getHum(void); uint8_t getFirmwareVersion(void); uint8_t getErrorCode(void); int pm25ToAQI(int pm02); + int compensated(int pm25, float humidity); private: Stream *stream; @@ -38,7 +39,8 @@ class PMSBase { bool failed = false; uint32_t lastRead; - uint16_t toValue(char *buf); + int16_t toI16(char *buf); + uint16_t toU16(char* buf); bool validate(char *buf); }; diff --git a/src/PMS/PMS5003.cpp b/src/PMS/PMS5003.cpp index 73252406..c8f658b1 100644 --- a/src/PMS/PMS5003.cpp +++ b/src/PMS/PMS5003.cpp @@ -121,6 +121,17 @@ int PMS5003::getPm03ParticleCount(void) { */ 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 * diff --git a/src/PMS/PMS5003.h b/src/PMS/PMS5003.h index 3ed63aa4..9d6c5df8 100644 --- a/src/PMS/PMS5003.h +++ b/src/PMS/PMS5003.h @@ -24,6 +24,7 @@ class PMS5003 { int getPm10Ae(void); int getPm03ParticleCount(void); int convertPm25ToUsAqi(int pm25); + int compensated(int pm25, float humidity); int getFirmwareVersion(void); uint8_t getErrorCode(void); diff --git a/src/PMS/PMS5003T.cpp b/src/PMS/PMS5003T.cpp index 1f48ebda..fba61334 100644 --- a/src/PMS/PMS5003T.cpp +++ b/src/PMS/PMS5003T.cpp @@ -164,6 +164,17 @@ float PMS5003T::getRelativeHumidity(void) { 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 * diff --git a/src/PMS/PMS5003T.h b/src/PMS/PMS5003T.h index 0a0b1dc5..4fc67946 100644 --- a/src/PMS/PMS5003T.h +++ b/src/PMS/PMS5003T.h @@ -29,6 +29,7 @@ class PMS5003T: public PMS5003TBase { int convertPm25ToUsAqi(int pm25); float getTemperature(void); float getRelativeHumidity(void); + float compensated(int pm25, float humidity); int getFirmwareVersion(void); uint8_t getErrorCode(void);