Merge pull request #247 from airgradienthq/fix/pms-read-data

Fix: PMS sensor read failed in case PM value is low
This commit is contained in:
AirGradient
2024-09-24 20:45:04 +07:00
committed by GitHub
11 changed files with 234 additions and 182 deletions

View File

@ -503,7 +503,7 @@ static void updateTvoc(void) {
}
static void updatePm(void) {
if (ag.pms5003.isFailed() == false) {
if (ag.pms5003.connected()) {
measurements.pm01_1 = ag.pms5003.getPm01Ae();
measurements.pm25_1 = ag.pms5003.getPm25Ae();
measurements.pm10_1 = ag.pms5003.getPm10Ae();

View File

@ -555,7 +555,7 @@ static void updateTvoc(void) {
}
static void updatePm(void) {
if (ag.pms5003.isFailed() == false) {
if (ag.pms5003.connected()) {
measurements.pm01_1 = ag.pms5003.getPm01Ae();
measurements.pm25_1 = ag.pms5003.getPm25Ae();
measurements.pm10_1 = ag.pms5003.getPm10Ae();

View File

@ -596,7 +596,7 @@ static void updateTvoc(void) {
}
static void updatePm(void) {
if (ag.pms5003.isFailed() == false) {
if (ag.pms5003.connected()) {
measurements.pm01_1 = ag.pms5003.getPm01Ae();
measurements.pm25_1 = ag.pms5003.getPm25Ae();
measurements.pm10_1 = ag.pms5003.getPm10Ae();

View File

@ -286,6 +286,11 @@ void loop() {
if (ag->isOne()) {
if (configuration.hasSensorPMS1) {
ag->pms5003.handle();
static bool pmsConnected = false;
if (pmsConnected != ag->pms5003.connected()) {
pmsConnected = ag->pms5003.connected();
Serial.printf("PMS sensor %s ", pmsConnected?"connected":"removed");
}
}
} else {
if (configuration.hasSensorPMS1) {
@ -992,7 +997,7 @@ static void updateTvoc(void) {
static void updatePm(void) {
bool restart = false;
if (ag->isOne()) {
if (ag->pms5003.isFailed() == false) {
if (ag->pms5003.connected()) {
measurements.pm01_1 = ag->pms5003.getPm01Ae();
measurements.pm25_1 = ag->pms5003.getPm25Ae();
measurements.pm10_1 = ag->pms5003.getPm10Ae();
@ -1022,7 +1027,7 @@ static void updatePm(void) {
} else {
bool pmsResult_1 = false;
bool pmsResult_2 = false;
if (configuration.hasSensorPMS1 && (ag->pms5003t_1.isFailed() == false)) {
if (configuration.hasSensorPMS1 && ag->pms5003t_1.connected()) {
measurements.pm01_1 = ag->pms5003t_1.getPm01Ae();
measurements.pm25_1 = ag->pms5003t_1.getPm25Ae();
measurements.pm10_1 = ag->pms5003t_1.getPm10Ae();
@ -1066,7 +1071,7 @@ static void updatePm(void) {
}
}
if (configuration.hasSensorPMS2 && (ag->pms5003t_2.isFailed() == false)) {
if (configuration.hasSensorPMS2 && ag->pms5003t_2.connected()) {
measurements.pm01_2 = ag->pms5003t_2.getPm01Ae();
measurements.pm25_2 = ag->pms5003t_2.getPm25Ae();
measurements.pm10_2 = ag->pms5003t_2.getPm10Ae();

View File

@ -44,7 +44,7 @@ void loop() {
if (ms >= 5000) {
lastRead = millis();
#ifdef ESP8266
if (ag.pms5003.isFailed() == false) {
if (ag.pms5003.connected()) {
PM2 = ag.pms5003.getPm25Ae();
Serial.printf("PM2.5 in ug/m3: %d\r\n", PM2);
Serial.printf("PM2.5 in US AQI: %d\r\n",
@ -54,12 +54,12 @@ void loop() {
}
#else
if (ag.getBoardType() == OPEN_AIR_OUTDOOR) {
if (ag.pms5003t_1.isFailed() == false) {
if (ag.pms5003t_1.connected()) {
PM2 = ag.pms5003t_1.getPm25Ae();
readResul = true;
}
} else {
if (ag.pms5003.isFailed() == false) {
if (ag.pms5003.connected()) {
PM2 = ag.pms5003.getPm25Ae();
readResul = true;
}

View File

@ -10,15 +10,13 @@
*/
bool PMSBase::begin(Stream *stream) {
Serial.printf("initializing PM sensor\n");
this->stream = stream;
failed = true;
failCount = 0;
lastRead = 0; // To read buffer on handle without wait after 1.5sec
_connected = false;
// empty first
int bytesCleared = 0;
while (this->stream->read() != -1) {
while (stream->read() != -1) {
bytesCleared++;
}
Serial.printf("cleared %d byte(s)\n", bytesCleared);
@ -26,145 +24,140 @@ bool PMSBase::begin(Stream *stream) {
// explicitly put the sensor into active mode, this seems to be be needed for the Cubic PM2009X
Serial.printf("setting active mode\n");
uint8_t activeModeCommand[] = { 0x42, 0x4D, 0xE1, 0x00, 0x01, 0x01, 0x71 };
size_t bytesWritten = this->stream->write(activeModeCommand, sizeof(activeModeCommand));
size_t bytesWritten = stream->write(activeModeCommand, sizeof(activeModeCommand));
Serial.printf("%d byte(s) written\n", bytesWritten);
// Run and check sensor data for 4sec
while (1) {
handle();
if (failed == false) {
Serial.printf("PM sensor initialized\n");
return true;
unsigned long lastInit = millis();
while (true) {
readPackage(stream);
if (_connected) {
break;
}
delay(10);
uint32_t ms = (uint32_t)(millis() - lastRead);
delay(1);
unsigned long ms = (unsigned long)(millis() - lastInit);
if (ms >= 4000) {
break;
}
}
Serial.printf("PM sensor initialization failed\n");
return false;
return _connected;
}
/**
* @brief Check and read sensor data then update variable.
* Check result from method @isFailed before get value
* @brief Read PMS package send to device each 1sec
*
* @param serial
*/
void PMSBase::handle() {
uint32_t ms;
if (lastRead == 0) {
lastRead = millis();
if (lastRead == 0) {
lastRead = 1;
void PMSBase::readPackage(Stream *serial) {
/** If readPackage has process as period larger than READ_PACKAGE_TIMEOUT,
* should be clear the lastPackage and readBufferIndex */
if (lastReadPackage) {
unsigned long ms = (unsigned long)(millis() - lastReadPackage);
if (ms >= READ_PACKGE_TIMEOUT) {
/** Clear buffer */
readBufferIndex = 0;
/** Disable check read package timeout */
lastPackage = 0;
Serial.println("Last process timeout, clear buffer and last handle package");
}
lastReadPackage = millis();
if (!lastReadPackage) {
lastReadPackage = 1;
}
} else {
ms = (uint32_t)(millis() - lastRead);
/**
* The PMS in Active mode sends an update data every 1 second. If we read
* exactly every 1 sec then we may or may not get an update (depending on
* timing tolerances). Hence we read every 2.5 seconds and expect 2 ..3
* updates,
*/
if (ms < 2500) {
return;
lastReadPackage = millis();
if (!lastReadPackage) {
lastReadPackage = 1;
}
}
bool result = false;
char buf[32];
int bufIndex;
int step = 0;
int len = 0;
int bcount = 0;
while (stream->available()) {
char value = stream->read();
switch (step) {
case 0: {
/** Count to call delay() to release the while loop MCU resource for avoid the
* watchdog time reset */
uint8_t delayCount = 0;
while (serial->available()) {
/** Get value */
uint8_t value = (uint8_t)serial->read();
/** Process receiving package... */
switch (readBufferIndex) {
case 0: /** Start byte 1 */
if (value == 0x42) {
step = 1;
bufIndex = 0;
buf[bufIndex++] = value;
readBuffer[readBufferIndex++] = value;
}
break;
}
case 1: {
case 1: /** Start byte 2 */
if (value == 0x4d) {
step = 2;
buf[bufIndex++] = value;
// Serial.println("Got 0x4d");
readBuffer[readBufferIndex++] = value;
} else {
step = 0;
readBufferIndex = 0;
}
break;
}
case 2: {
buf[bufIndex++] = value;
if (bufIndex >= 4) {
len = toI16(&buf[2]);
if (len != 28) {
// Serial.printf("Got good bad len %d\r\n", len);
len += 4;
step = 3;
} else {
// Serial.println("Got good len");
step = 4;
case 2: /** Frame length */
if (value == 0x00) {
readBuffer[readBufferIndex++] = value;
} else {
readBufferIndex = 0;
}
break;
case 3: /** Frame length */
if (value == 0x1C) {
readBuffer[readBufferIndex++] = value;
} else {
readBufferIndex = 0;
}
break;
default: /** Data */
{
readBuffer[readBufferIndex++] = value;
/** Check that received full bufer */
if (readBufferIndex >= sizeof(readBuffer)) {
/** validata package */
if (validate(readBuffer)) {
_connected = true; /** Set connected status */
/** Parse data */
parse(readBuffer);
/** Set last received package */
lastPackage = millis();
if (lastPackage == 0) {
lastPackage = 1;
}
}
/** Clear buffer index */
readBufferIndex = 0;
}
break;
}
case 3: {
bufIndex++;
if (bufIndex >= len) {
step = 0;
// Serial.println("Bad lengh read all buffer");
}
break;
}
case 4: {
buf[bufIndex++] = value;
if (bufIndex >= 32) {
result |= validate(buf);
step = 0;
// Serial.println("Got data");
}
break;
}
default:
break;
}
// Reduce core panic: delay 1 ms each 32bytes data
bcount++;
if ((bcount % 32) == 0) {
/** Avoid task watchdog timer reset... */
delayCount++;
if (delayCount >= 32) {
delayCount = 0;
delay(1);
}
}
if (result) {
lastRead = millis();
if (lastRead == 0) {
lastRead = 1;
}
failed = false;
} else {
if (ms > 5000) {
failed = true;
/** Check that sensor removed */
if (lastPackage) {
unsigned long ms = (unsigned long)(millis() - lastPackage);
if (ms >= READ_PACKGE_TIMEOUT) {
lastPackage = 0;
_connected = false;
}
}
}
/**
* @brief Check that PMS send is failed or disconnected
*
* @return true Failed
* @return false No problem
*/
bool PMSBase::isFailed(void) { return failed; }
/**
* @brief Increate number of fail
*
*
*/
void PMSBase::updateFailCount(void) {
if (failCount < failCountMax) {
@ -176,8 +169,8 @@ void PMSBase::resetFailCount(void) { failCount = 0; }
/**
* @brief Get number of fail
*
* @return int
*
* @return int
*/
int PMSBase::getFailCount(void) { return failCount; }
@ -188,112 +181,114 @@ int PMSBase::getFailCountMax(void) { return failCountMax; }
*
* @return uint16_t
*/
uint16_t PMSBase::getRaw0_1(void) { return toU16(&package[4]); }
uint16_t PMSBase::getRaw0_1(void) { return pms_raw0_1; }
/**
* @brief Read PMS 2.5 ug/m3 with CF = 1 PM estimates
*
* @return uint16_t
*/
uint16_t PMSBase::getRaw2_5(void) { return toU16(&package[6]); }
uint16_t PMSBase::getRaw2_5(void) { return pms_raw2_5; }
/**
* @brief Read PMS 10 ug/m3 with CF = 1 PM estimates
*
* @return uint16_t
*/
uint16_t PMSBase::getRaw10(void) { return toU16(&package[8]); }
uint16_t PMSBase::getRaw10(void) { return pms_raw10; }
/**
* @brief Read PMS 0.1 ug/m3
*
* @return uint16_t
*/
uint16_t PMSBase::getPM0_1(void) { return toU16(&package[10]); }
uint16_t PMSBase::getPM0_1(void) { return pms_pm0_1; }
/**
* @brief Read PMS 2.5 ug/m3
*
* @return uint16_t
*/
uint16_t PMSBase::getPM2_5(void) { return toU16(&package[12]); }
uint16_t PMSBase::getPM2_5(void) { return pms_pm2_5; }
/**
* @brief Read PMS 10 ug/m3
*
* @return uint16_t
*/
uint16_t PMSBase::getPM10(void) { return toU16(&package[14]); }
uint16_t PMSBase::getPM10(void) { return pms_pm10; }
/**
* @brief Get numnber concentrations over 0.3 um/0.1L
*
* @return uint16_t
*/
uint16_t PMSBase::getCount0_3(void) { return toU16(&package[16]); }
uint16_t PMSBase::getCount0_3(void) { return pms_count0_3; }
/**
* @brief Get numnber concentrations over 0.5 um/0.1L
*
* @return uint16_t
*/
uint16_t PMSBase::getCount0_5(void) { return toU16(&package[18]); }
uint16_t PMSBase::getCount0_5(void) { return pms_count0_5; }
/**
* @brief Get numnber concentrations over 1.0 um/0.1L
*
* @return uint16_t
*/
uint16_t PMSBase::getCount1_0(void) { return toU16(&package[20]); }
uint16_t PMSBase::getCount1_0(void) { return pms_count1_0; }
/**
* @brief Get numnber concentrations over 2.5 um/0.1L
*
* @return uint16_t
*/
uint16_t PMSBase::getCount2_5(void) { return toU16(&package[22]); }
uint16_t PMSBase::getCount2_5(void) { return pms_count2_5; }
bool PMSBase::connected(void) { return _connected; }
/**
* @brief Get numnber concentrations over 5.0 um/0.1L (only PMS5003)
*
* @return uint16_t
*/
uint16_t PMSBase::getCount5_0(void) { return toU16(&package[24]); }
uint16_t PMSBase::getCount5_0(void) { return pms_count5_0; }
/**
* @brief Get numnber concentrations over 10.0 um/0.1L (only PMS5003)
*
* @return uint16_t
*/
uint16_t PMSBase::getCount10(void) { return toU16(&package[26]); }
uint16_t PMSBase::getCount10(void) { return pms_count10; }
/**
* @brief Get temperature (only PMS5003T)
*
* @return uint16_t
*/
int16_t PMSBase::getTemp(void) { return toI16(&package[24]); }
int16_t PMSBase::getTemp(void) { return pms_temp; }
/**
* @brief Get humidity (only PMS5003T)
*
* @return uint16_t
*/
uint16_t PMSBase::getHum(void) { return toU16(&package[26]); }
uint16_t PMSBase::getHum(void) { return pms_hum; }
/**
* @brief Get firmware version code
*
* @return uint8_t
*
* @return uint8_t
*/
uint8_t PMSBase::getFirmwareVersion(void) { return package[28]; }
uint8_t PMSBase::getFirmwareVersion(void) { return pms_firmwareVersion; }
/**
* @brief Ge PMS5003 error code
*
* @return uint8_t
*
* @return uint8_t
*/
uint8_t PMSBase::getErrorCode(void) { return package[29]; }
uint8_t PMSBase::getErrorCode(void) { return pms_errorCode; }
/**
* @brief Convert PMS2.5 to US AQI unit
@ -322,12 +317,12 @@ int PMSBase::pm25ToAQI(int pm02) {
/**
* @brief Correction PM2.5
*
*
* Formula: https://www.airgradient.com/documentation/correction-algorithms/
*
*
* @param pm25 Raw PM2.5 value
* @param humidity Humidity value (%)
* @return int
* @return int
*/
int PMSBase::compensate(int pm25, float humidity) {
float value;
@ -351,7 +346,7 @@ int PMSBase::compensate(int pm25, float humidity) {
value = 2.966f + (0.69f * fpm25) + (8.84f * (1.e-4) * fpm25 * fpm25);
}
if(value < 0) {
if (value < 0) {
value = 0;
}
@ -364,13 +359,13 @@ int PMSBase::compensate(int pm25, float humidity) {
* @param buf bytes array (must be >= 2)
* @return int16_t
*/
int16_t PMSBase::toI16(char *buf) {
int16_t PMSBase::toI16(const uint8_t *buf) {
int16_t value = buf[0];
value = (value << 8) | buf[1];
return value;
}
uint16_t PMSBase::toU16(char *buf) {
uint16_t PMSBase::toU16(const uint8_t *buf) {
uint16_t value = buf[0];
value = (value << 8) | buf[1];
return value;
@ -383,16 +378,32 @@ uint16_t PMSBase::toU16(char *buf) {
* @return true Success
* @return false Failed
*/
bool PMSBase::validate(char *buf) {
bool PMSBase::validate(const uint8_t *buf) {
uint16_t sum = 0;
for (int i = 0; i < 30; i++) {
sum += buf[i];
}
if (sum == toU16(&buf[30])) {
for (int i = 0; i < 32; i++) {
package[i] = buf[i];
}
return true;
}
return false;
}
void PMSBase::parse(const uint8_t *buf) {
pms_raw0_1 = toU16(&buf[4]);
pms_raw2_5 = toU16(&buf[6]);
pms_raw10 = toU16(&buf[8]);
pms_pm0_1 = toU16(&buf[10]);
pms_pm2_5 = toU16(&buf[12]);
pms_pm10 = toU16(&buf[14]);
pms_count0_3 = toU16(&buf[16]);
pms_count0_5 = toU16(&buf[18]);
pms_count1_0 = toU16(&buf[20]);
pms_count2_5 = toU16(&buf[22]);
pms_count5_0 = toU16(&buf[24]);
pms_count10 = toU16(&buf[26]);
pms_temp = toU16(&buf[24]);
pms_hum = toU16(&buf[26]);
pms_firmwareVersion = buf[28];
pms_errorCode = buf[29];
}

View File

@ -11,8 +11,7 @@
class PMSBase {
public:
bool begin(Stream *stream);
void handle();
bool isFailed(void);
void readPackage(Stream *stream);
void updateFailCount(void);
void resetFailCount(void);
int getFailCount(void);
@ -27,6 +26,7 @@ public:
uint16_t getCount0_5(void);
uint16_t getCount1_0(void);
uint16_t getCount2_5(void);
bool connected(void);
/** For PMS5003 */
uint16_t getCount5_0(void);
@ -42,17 +42,48 @@ public:
int compensate(int pm25, float humidity);
private:
Stream *stream;
char package[32];
int packageIndex;
bool failed = false;
uint32_t lastRead;
static const uint8_t package_size = 32;
/** In normal package interval is 200-800ms, In case small changed on sensor
* it's will interval reach to 2.3sec
*/
const uint16_t READ_PACKGE_TIMEOUT = 3000; /** ms */
const int failCountMax = 10;
int failCount = 0;
int16_t toI16(char *buf);
uint16_t toU16(char* buf);
bool validate(char *buf);
uint8_t readBuffer[package_size];
uint8_t readBufferIndex = 0;
/**
* Save last time received package success. 0 to disable check package
* timeout.
*/
unsigned long lastPackage = 0;
bool _connected;
unsigned long lastReadPackage = 0;
uint16_t pms_raw0_1;
uint16_t pms_raw2_5;
uint16_t pms_raw10;
uint16_t pms_pm0_1;
uint16_t pms_pm2_5;
uint16_t pms_pm10;
uint16_t pms_count0_3;
uint16_t pms_count0_5;
uint16_t pms_count1_0;
uint16_t pms_count2_5;
uint16_t pms_count5_0;
uint16_t pms_count10;
int16_t pms_temp;
uint16_t pms_hum;
uint8_t pms_errorCode;
uint8_t pms_firmwareVersion;
int16_t toI16(const uint8_t *buf);
uint16_t toU16(const uint8_t *buf);
bool validate(const uint8_t *buf);
void parse(const uint8_t* buf);
};
#endif /** _PMS5003_BASE_H_ */

View File

@ -3,7 +3,6 @@
#include "../Main/utils.h"
#if defined(ESP8266)
#include <SoftwareSerial.h>
/**
* @brief Init sensor
*
@ -60,11 +59,10 @@ bool PMS5003::begin(void) {
}
#if defined(ESP8266)
bsp->Pms5003.uart_tx_pin;
SoftwareSerial *uart =
this->_serial =
new SoftwareSerial(bsp->Pms5003.uart_tx_pin, bsp->Pms5003.uart_rx_pin);
uart->begin(9600);
if (pms.begin(uart) == false) {
this->_serial->begin(9600);
if (pms.begin(this->_serial) == false) {
AgLog("PMS failed");
return false;
}
@ -145,6 +143,14 @@ int PMS5003::getFirmwareVersion(void) { return _ver; }
*/
uint8_t PMS5003::getErrorCode(void) { return pms.getErrorCode(); }
/**
* @brief Is sensor connect with device
*
* @return true Connected
* @return false Removed
*/
bool PMS5003::connected(void) { return pms.connected(); }
/**
* @brief Check device initialized or not
*
@ -179,15 +185,7 @@ void PMS5003::end(void) {
* @brief Check and read PMS sensor data. This method should be callack from
* loop process to continoue check sensor data if it's available
*/
void PMS5003::handle(void) { pms.handle(); }
/**
* @brief Get sensor status
*
* @return true No problem
* @return false Communication timeout or sensor has removed
*/
bool PMS5003::isFailed(void) { return pms.isFailed(); }
void PMS5003::handle(void) { pms.readPackage(this->_serial); }
void PMS5003::updateFailCount(void) {
pms.updateFailCount();

View File

@ -4,6 +4,9 @@
#include "../Main/BoardDef.h"
#include "PMS.h"
#include "Stream.h"
#ifdef ESP8266
#include <SoftwareSerial.h>
#endif
/**
* @brief The class define how to handle PMS5003 sensor bas on @ref PMS class
@ -18,7 +21,6 @@ public:
#endif
void end(void);
void handle(void);
bool isFailed(void);
void updateFailCount(void);
void resetFailCount(void);
int getFailCount(void);
@ -31,6 +33,7 @@ public:
int compensate(int pm25, float humidity);
int getFirmwareVersion(void);
uint8_t getErrorCode(void);
bool connected(void);
private:
bool _isBegin = false;
@ -41,6 +44,7 @@ private:
#if defined(ESP8266)
Stream *_debugStream;
const char *TAG = "PMS5003";
SoftwareSerial *_serial;
#else
HardwareSerial *_serial;
#endif

View File

@ -67,11 +67,10 @@ bool PMS5003T::begin(void) {
}
#if defined(ESP8266)
bsp->Pms5003.uart_tx_pin;
SoftwareSerial *uart =
this->_serial =
new SoftwareSerial(bsp->Pms5003.uart_tx_pin, bsp->Pms5003.uart_rx_pin);
uart->begin(9600);
if (pms.begin(uart) == false) {
this->_serial->begin(9600);
if (pms.begin(this->_serial) == false) {
AgLog("PMS failed");
return false;
}
@ -191,6 +190,14 @@ int PMS5003T::getFirmwareVersion(void) { return _ver; }
*/
uint8_t PMS5003T::getErrorCode(void) { return pms.getErrorCode(); }
/**
* @brief Is sensor connect to device
*
* @return true Connected
* @return false Removed
*/
bool PMS5003T::connected(void) { return pms.connected(); }
/**
* @brief Check device initialized or not
*
@ -222,15 +229,7 @@ void PMS5003T::end(void) {
* @brief Check and read PMS sensor data. This method should be callack from
* loop process to continoue check sensor data if it's available
*/
void PMS5003T::handle(void) { pms.handle(); }
/**
* @brief Get sensor status
*
* @return true No problem
* @return false Communication timeout or sensor has removed
*/
bool PMS5003T::isFailed(void) { return pms.isFailed(); }
void PMS5003T::handle(void) { pms.readPackage(this->_serial); }
void PMS5003T::updateFailCount(void) {
pms.updateFailCount();

View File

@ -6,6 +6,9 @@
#include "PMS5003TBase.h"
#include "Stream.h"
#include <HardwareSerial.h>
#ifdef ESP8266
#include <SoftwareSerial.h>
#endif
/**
* @brief The class define how to handle PMS5003T sensor bas on @ref PMS class
@ -21,7 +24,6 @@ public:
void end(void);
void handle(void);
bool isFailed(void);
void updateFailCount(void);
void resetFailCount(void);
int getFailCount(void);
@ -36,6 +38,7 @@ public:
int compensate(int pm25, float humidity);
int getFirmwareVersion(void);
uint8_t getErrorCode(void);
bool connected(void);
private:
bool _isBegin = false;
@ -47,6 +50,7 @@ private:
#if defined(ESP8266)
Stream *_debugStream;
const char *TAG = "PMS5003T";
SoftwareSerial *_serial;
#else
HardwareSerial *_serial;
#endif