Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Concentration to AQI calculation for US and China. #12

Merged
merged 10 commits into from
Dec 23, 2024
172 changes: 170 additions & 2 deletions Adafruit_PM25AQI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
*/

#include "Adafruit_PM25AQI.h"
#include <math.h>

/*!
* @brief Instantiates a new PM25AQI class
Expand Down Expand Up @@ -104,17 +105,23 @@ bool Adafruit_PM25AQI::read(PM25_AQI_Data *data) {
return false;
}

memcpy((void *)data->raw, (void *)buffer, 32);

// Check that start byte is correct!
if (buffer[0] != 0x42) {
if (buffer[0] != 0x42 || buffer[1] != 0x4d) {
data->startbyte_fail = 1;
return false;
} else {
data->startbyte_fail = 0;
}

// get checksum ready
for (uint8_t i = 0; i < 30; i++) {
sum += buffer[i];
}

// The data comes in endian'd, this solves it so it works on all platforms
// The data comes in big endian (MSB first), this solves it so it works on all
// platforms
uint16_t buffer_u16[15];
for (uint8_t i = 0; i < 15; i++) {
buffer_u16[i] = buffer[2 + i * 2 + 1];
Expand All @@ -124,10 +131,171 @@ bool Adafruit_PM25AQI::read(PM25_AQI_Data *data) {
// put it into a nice struct :)
memcpy((void *)data, (void *)buffer_u16, 30);

data->datasum = sum;
jiangyi1985 marked this conversation as resolved.
Show resolved Hide resolved
if (sum != data->checksum) {
data->checksum_fail = 1;
return false;
} else {
data->checksum_fail = 0;
}

data->version = buffer[28];
data->error_code = buffer[29];

// convert concentration to AQI
data->aqi_pm25_us = pm25_aqi_us(data->pm25_env);
tyeth marked this conversation as resolved.
Show resolved Hide resolved
data->aqi_pm25_china = pm25_aqi_china(data->pm25_env);
data->aqi_pm100_us = pm100_aqi_us(data->pm100_env);
data->aqi_pm100_china = pm100_aqi_china(data->pm100_env);

// success!
return true;
}

/*!
* @brief Get AQI of PM2.5 in US standard
* @param concentration
* the environmental concentration of pm2.5 in ug/m3
* @return AQI number. 0 to 500 for valid calculation. 99999 for out of range.
*/
uint16_t Adafruit_PM25AQI::pm25_aqi_us(float concentration) {
float c;
float AQI;
c = (floor(10 * concentration)) / 10;
if (c < 0)
AQI = 0;
else if (c >= 0 && c < 12.1f) {
AQI = linear(50, 0, 12, 0, c);
} else if (c >= 12.1f && c < 35.5f) {
AQI = linear(100, 51, 35.4f, 12.1f, c);
} else if (c >= 35.5f && c < 55.5f) {
AQI = linear(150, 101, 55.4f, 35.5f, c);
} else if (c >= 55.5f && c < 150.5f) {
AQI = linear(200, 151, 150.4f, 55.5f, c);
} else if (c >= 150.5f && c < 250.5f) {
AQI = linear(300, 201, 250.4f, 150.5f, c);
} else if (c >= 250.5f && c < 350.5f) {
AQI = linear(400, 301, 350.4f, 250.5f, c);
} else if (c >= 350.5f && c < 500.5f) {
AQI = linear(500, 401, 500.4f, 350.5f, c);
} else {
AQI = 99999; //
}
return round(AQI);
}

/*!
* @brief Get AQI of PM10 in US standard
* @param concentration
* the environmental concentration of pm10 in ug/m3
* @return AQI number. 0 to 500 for valid calculation. 99999 for out of range.
*/
uint16_t Adafruit_PM25AQI::pm100_aqi_us(float concentration) {
float c;
float AQI;
c = concentration;
if (c < 0)
AQI = 0;
else if (c < 55) {
AQI = linear(50, 0, 55, 0, c);
tyeth marked this conversation as resolved.
Show resolved Hide resolved
} else if (c < 155) {
AQI = linear(100, 51, 155, 55, c);
} else if (c < 255) {
AQI = linear(150, 101, 255, 155, c);
} else if (c < 355) {
AQI = linear(200, 151, 355, 255, c);
} else if (c < 425) {
AQI = linear(300, 201, 425, 355, c);
} else if (c < 505) {
AQI = linear(400, 301, 505, 425, c);
} else if (c < 605) {
AQI = linear(500, 401, 605, 505, c);
} else {
AQI = 99999; //
}
return round(AQI);
}

/*!
* @brief Get AQI of PM2.5 in China standard
* @param concentration
* the environmental concentration of pm2.5 in ug/m3
* @return AQI number. 0 to 500 for valid calculation. 99999 for out of range.
*/
uint16_t Adafruit_PM25AQI::pm25_aqi_china(float concentration) {
float c;
float AQI;
c = concentration;
if (c < 0)
AQI = 0;
else if (c <= 35) {
AQI = linear(50, 0, 35, 0, c);
} else if (c <= 75) {
AQI = linear(100, 51, 75, 35, c);
} else if (c <= 115) {
AQI = linear(150, 101, 115, 75, c);
} else if (c <= 150) {
AQI = linear(200, 151, 150, 115, c);
} else if (c <= 250) {
AQI = linear(300, 201, 250, 150, c);
} else if (c <= 350) {
AQI = linear(400, 301, 350, 250, c);
} else if (c <= 500) {
AQI = linear(500, 401, 500, 350, c);
} else {
AQI = 99999; //
}
return round(AQI);
}

/*!
* @brief Get AQI of PM10 in China standard
* @param concentration
* the environmental concentration of pm10 in ug/m3
* @return AQI number. 0 to 500 for valid calculation. 99999 for out of range.
*/
uint16_t Adafruit_PM25AQI::pm100_aqi_china(float concentration) {
float c;
float AQI;
c = concentration;
if (c < 0)
AQI = 0;
else if (c <= 50) {
AQI = linear(50, 0, 50, 0, c);
} else if (c <= 150) {
AQI = linear(100, 51, 150, 50, c);
} else if (c <= 250) {
AQI = linear(150, 101, 250, 150, c);
} else if (c <= 350) {
AQI = linear(200, 151, 350, 250, c);
} else if (c <= 420) {
AQI = linear(300, 201, 420, 350, c);
} else if (c <= 500) {
AQI = linear(400, 301, 500, 420, c);
} else if (c <= 600) {
AQI = linear(500, 401, 600, 500, c);
} else {
AQI = 99999; //
}
return round(AQI);
}

/*!
* @brief Linearly map a concentration value to its AQI level
* @param aqi_high max aqi of the calculating range
* @param aqi_low min aqi of the calculating range
* @param conc_high max concentration value (ug/m3) of the calculating range
* @param conc_low min concentration value (ug/m3) of the calculating range
* @param concentration
* the concentration value to be calculated
* @return Calculated AQI value
*/
float Adafruit_PM25AQI::linear(uint16_t aqi_high, uint16_t aqi_low,
float conc_high, float conc_low,
float concentration) {
float f;
f = ((concentration - conc_low) / (conc_high - conc_low)) *
(aqi_high - aqi_low) +
aqi_low;
return f;
}
34 changes: 32 additions & 2 deletions Adafruit_PM25AQI.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,31 @@ typedef struct PMSAQIdata {
particles_25um, ///< 2.5um Particle Count
particles_50um, ///< 5.0um Particle Count
particles_100um; ///< 10.0um Particle Count
uint16_t unused; ///< Unused
uint16_t checksum; ///< Packet checksum
uint16_t unused; ///< Unused (version + error code)

// when copy data from int8 array to this struct with memcpy, error_code need
jiangyi1985 marked this conversation as resolved.
Show resolved Hide resolved
// to be defined before version otherwise they are swapped. reason is that
// arduino is little endian and memcpy swap the bytes when copy int16 into the
// 2 int8s? will manually copy them instead
// uint8_t error_code; ///<
// uint8_t version; ///<

uint16_t checksum; ///< Packet checksum

// verbose infos:
uint16_t datasum; ///< the calculated sum of the data in the current packet
uint8_t startbyte_fail; ///< startbyte check fail?: 1 - fail, 0 - success
uint8_t checksum_fail; ///< checksum check fail?: 1 - fail, 0 - success
uint8_t version; ///< version number
uint8_t error_code; ///< error code
uint8_t raw[32]; ///< raw packet data

// AQI conversion results:
uint8_t aqi_pm25_us; ///< pm2.5 AQI of United States
uint8_t aqi_pm100_us; ///< pm10 AQI of United States
uint8_t aqi_pm25_china; ///< pm2.5 AQI of China
uint8_t aqi_pm100_china; ///< pm10 AQI of China

} PM25_AQI_Data;

/*!
Expand All @@ -56,6 +79,13 @@ class Adafruit_PM25AQI {
bool begin_UART(Stream *theStream);
bool read(PM25_AQI_Data *data);

uint16_t pm25_aqi_us(float concentration);
uint16_t pm25_aqi_china(float concentration);
uint16_t pm100_aqi_us(float concentration);
uint16_t pm100_aqi_china(float concentration);
float linear(uint16_t aqi_high, uint16_t aqi_low, float conc_high,
float conc_low, float concentration);

private:
Adafruit_I2CDevice *i2c_dev = NULL;
Stream *serial_dev = NULL;
Expand Down
37 changes: 33 additions & 4 deletions examples/PM25_test/PM25_test.ino
jiangyi1985 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,24 @@ void loop() {

if (! aqi.read(&data)) {
Serial.println("Could not read from AQI");

//print verbose info
// Serial.println("FAIL reading. Error info:");
// printInfo(data);
// Serial.println();

delay(500); // try again in a bit!
return;
}
Serial.println("AQI reading success");

Serial.println();
Serial.println(F("---------------------------------------"));
Serial.println(F("Concentration Units (standard)"));
Serial.println(F("---------------------------------------"));
Serial.print(F("PM 1.0: ")); Serial.print(data.pm10_standard);
Serial.print(F("\t\tPM 2.5: ")); Serial.print(data.pm25_standard);
Serial.print(F("\t\tPM 10: ")); Serial.println(data.pm100_standard);
Serial.println(F("Concentration Units (environmental)"));
Serial.println(F("---------------------------------------"));
Serial.println(F("Concentration Units (environmental)"));
Serial.print(F("PM 1.0: ")); Serial.print(data.pm10_env);
Serial.print(F("\t\tPM 2.5: ")); Serial.print(data.pm25_env);
Serial.print(F("\t\tPM 10: ")); Serial.println(data.pm100_env);
Expand All @@ -67,7 +71,32 @@ void loop() {
Serial.print(F("Particles > 5.0um / 0.1L air:")); Serial.println(data.particles_50um);
Serial.print(F("Particles > 10 um / 0.1L air:")); Serial.println(data.particles_100um);
Serial.println(F("---------------------------------------"));

Serial.println(F("AQI"));
Serial.print(F("PM2.5 AQI US: ")); Serial.print(data.aqi_pm25_us);
Serial.print(F("\tPM10 AQI US: ")); Serial.println(data.aqi_pm100_us);
// Serial.print(F("PM2.5 AQI China: ")); Serial.print(data.aqi_pm25_china);
// Serial.print(F("\tPM10 AQI China: ")); Serial.println(data.aqi_pm100_china);
Serial.println(F("---------------------------------------"));
Serial.println();

delay(1000);
}

void printInfo(PM25_AQI_Data data)
{
Serial.print(F("startbyte_fail: ")); Serial.println(data.startbyte_fail);
Serial.print(F("checksum_fail: ")); Serial.println(data.checksum_fail);
Serial.print(F("framelen: ")); Serial.println(data.framelen);
Serial.print(F("version: ")); Serial.println(data.version);
Serial.print(F("error_code: ")); Serial.println(data.error_code);
Serial.print(F("checksum: ")); Serial.println(data.checksum);
Serial.print(F("datasum: ")); Serial.println(data.datasum);
Serial.println(F("raw data: "));
for(int i = 0; i < sizeof(data.raw); i++)
{
Serial.print(data.raw[i]);
Serial.print("\t");
if((i+1)%2==0)
Serial.println();
}
}