Compare commits

..

3 Commits
2.0.0 ... 2.1.0

Author SHA1 Message Date
90eee5d17f CO2 error handling in library
Small improvements in examples
2022-10-29 07:39:11 +07:00
d5c8af68a0 Fixed description 2022-10-02 07:59:36 +07:00
3c7180e642 Fixed display rotation and added example for SGP40 2022-10-02 07:38:44 +07:00
11 changed files with 361 additions and 44 deletions

View File

@ -105,7 +105,7 @@ int AirGradient::getPM2_Raw(){
pm02 = data.PM_AE_UG_2_5;
return pm02;
} else {
return 0;
return -1;
}
}
@ -589,45 +589,109 @@ void AirGradient::CO2_Init(int rx_pin,int tx_pin,int baudRate){
delay(10000);
}
}
const char* AirGradient::getCO2(int retryLimit) {
int ctr = 0;
int result_CO2 = getCO2_Raw();
while(result_CO2 == -1){
result_CO2 = getCO2_Raw();
if((ctr == retryLimit) || (result_CO2 == -1)){
Char_CO2[0] = 'N';
Char_CO2[1] = 'U';
Char_CO2[2] = 'L';
Char_CO2[3] = 'L';
return Char_CO2;
//const char* AirGradient::getCO2(int retryLimit) {
// int ctr = 0;
// int result_CO2 = getCO2_Raw();
// while(result_CO2 == -1){
// result_CO2 = getCO2_Raw();
// if((ctr == retryLimit) || (result_CO2 == -1)){
// Char_CO2[0] = 'N';
// Char_CO2[1] = 'U';
// Char_CO2[2] = 'L';
// Char_CO2[3] = 'L';
// return Char_CO2;
// }
// ctr++;
// }
// sprintf(Char_CO2,"%d", result_CO2);
// return Char_CO2;
//}
//int AirGradient::getCO2_Raw(){
// const byte CO2Command[] = {0xFE, 0X44, 0X00, 0X08, 0X02, 0X9F, 0X25};
// byte CO2Response[] = {0,0,0,0,0,0,0};
//
// _SoftSerial_CO2->write(CO2Command, 7);
// delay(100); //give the sensor a bit of time to respond
//
// if (_SoftSerial_CO2->available()){
// for (int i=0; i < 7; i++) {
// int byte = _SoftSerial_CO2->read();
// CO2Response[i] = byte;
// if (CO2Response[0] != 254) {
// return -1; //error code for debugging
// }
// }
// unsigned long val = CO2Response[3]*256 + CO2Response[4];
// return val;
// }
// else
// {
// return -2; //error code for debugging
// }
//}
int AirGradient::getCO2(int numberOfSamplesToTake) {
int successfulSamplesCounter = 0;
int co2AsPpmSum = 0;
for (int sample = 0; sample < numberOfSamplesToTake; sample++) {
int co2AsPpm = getCO2_Raw();
if (co2AsPpm > 300 && co2AsPpm < 10000) {
Serial.println("CO2 read success " + String(co2AsPpm));
successfulSamplesCounter++;
co2AsPpmSum += co2AsPpm;
} else {
Serial.println("CO2 read failed with " + String(co2AsPpm));
}
ctr++;
// without delay we get a few 10ms spacing, add some more
delay(250);
}
sprintf(Char_CO2,"%d", result_CO2);
return Char_CO2;
if (successfulSamplesCounter <= 0) {
// total failure
return -5;
}
Serial.println("# of CO2 reads that worked: " + String(successfulSamplesCounter));
Serial.println("CO2 reads sum " + String(co2AsPpmSum));
return co2AsPpmSum / successfulSamplesCounter;
}
int AirGradient::getCO2_Raw(){
// <<>>
int AirGradient::getCO2_Raw() {
while(_SoftSerial_CO2->available()) // flush whatever we might have
_SoftSerial_CO2->read();
const byte CO2Command[] = {0xFE, 0X44, 0X00, 0X08, 0X02, 0X9F, 0X25};
byte CO2Response[] = {0,0,0,0,0,0,0};
_SoftSerial_CO2->write(CO2Command, 7);
delay(100); //give the sensor a bit of time to respond
const int commandSize = 7;
if (_SoftSerial_CO2->available()){
for (int i=0; i < 7; i++) {
int byte = _SoftSerial_CO2->read();
CO2Response[i] = byte;
if (CO2Response[0] != 254) {
return -1; //error code for debugging
int numberOfBytesWritten = _SoftSerial_CO2->write(CO2Command, commandSize);
if (numberOfBytesWritten != commandSize) {
// failed to write request
return -2;
}
// attempt to read response
int timeoutCounter = 0;
while (_SoftSerial_CO2->available() < commandSize) {
timeoutCounter++;
if (timeoutCounter > 10) {
// timeout when reading response
return -3;
}
}
unsigned long val = CO2Response[3]*256 + CO2Response[4];
return val;
delay(50);
}
else
{
return -2; //error code for debugging
// we have 7 bytes ready to be read
for (int i=0; i < commandSize; i++) {
CO2Response[i] = _SoftSerial_CO2->read();
}
return CO2Response[3]*256 + CO2Response[4];
}
//END CO2 FUNCTIONS //

View File

@ -223,7 +223,7 @@ class AirGradient
void CO2_Init();
void CO2_Init(int,int);
void CO2_Init(int,int,int);
const char* getCO2(int retryLimit = 5);
int getCO2(int numberOfSamplesToTake = 5);
int getCO2_Raw();
SoftwareSerial *_SoftSerial_CO2;

View File

@ -24,7 +24,7 @@ MIT License
AirGradient ag = AirGradient();
void setup(){
Serial.begin(9600);
Serial.begin(115200);
ag.CO2_Init();
}

View File

@ -55,7 +55,7 @@ Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(8, 8, PIN,
NEO_GRB + NEO_KHZ800);
void setup() {
Serial.begin(9600);
Serial.begin(115200);
ag.CO2_Init();
matrix.begin();
matrix.setRotation(1); // change rotation

View File

@ -9,7 +9,7 @@ Kits (including a pre-soldered version) are available: https://www.airgradient.c
The codes needs the following libraries installed:
“WifiManager by tzapu, tablatronix” tested with version 2.0.11-beta
“U8g2” by oliver tested with version 2.32.15
"ESP8266 and ESP32 OLED driver for SSD1306 displays by ThingPulse, Fabrice Weinberg" tested with Version 4.1.0
Configuration:
Please set in the code below the configuration parameters.

View File

@ -1,7 +1,5 @@
/*
This is the code for the AirGradient DIY Air Quality Sensor with an ESP8266 Microcontroller.
It is a high quality sensor showing PM2.5, CO2, Temperature and Humidity on a small display and can send data over Wifi.
This is the code for the AirGradient DIY BASIC Air Quality Sensor with an ESP8266 Microcontroller with Sensirion SGP30 instead of the SHT30. Due to instabilities on the I2C line running both SHT30 and SGP30 at the same time is not recommended. We recommend to switch to the DIY_PRO board and use the Sensirion SGP40.
For build instructions please visit https://www.airgradient.com/open-airgradient/instructions/
@ -55,7 +53,7 @@ boolean connectWIFI=true;
String APIROOT = "http://hw.airgradient.com/";
void setup(){
Serial.begin(9600);
Serial.begin(115200);
display.init();
display.flipScreenVertically();

View File

@ -41,7 +41,7 @@ SGP30 SGP;
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
// Replace above if you have display on top left
//U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R3, /* reset=*/ U8X8_PIN_NONE);
//U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R2, /* reset=*/ U8X8_PIN_NONE);
// CONFIGURATION START

View File

@ -0,0 +1,255 @@
/*
This is the code for the AirGradient DIY PRO Air Quality Sensor with an ESP8266 Microcontroller with the SGP40 TVOC module from AirGradient.
It is a high quality sensor showing PM2.5, CO2, Temperature and Humidity on a small display and can send data over Wifi.
Build Instructions: https://www.airgradient.com/open-airgradient/instructions/diy-pro/
Kits (including a pre-soldered version) are available: https://www.airgradient.com/open-airgradient/kits/
The codes needs the following libraries installed:
“WifiManager by tzapu, tablatronix” tested with version 2.0.11-beta
“U8g2” by oliver tested with version 2.32.15
“DFRobot_SGP40” by DFRobot tested with Version 1.0.3
Configuration:
Please set in the code below the configuration parameters.
If you have any questions please visit our forum at https://forum.airgradient.com/
If you are a school or university contact us for a free trial on the AirGradient platform.
https://www.airgradient.com/
MIT License
*/
#include <AirGradient.h>
#include <WiFiManager.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>
//#include "SGP30.h"
#include <DFRobot_SGP40.h>
#include <U8g2lib.h>
AirGradient ag = AirGradient();
DFRobot_SGP40 sgp40;
// Display bottom right
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
// Replace above if you have display on top left
//U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R2, /* reset=*/ U8X8_PIN_NONE);
// CONFIGURATION START
//set to the endpoint you would like to use
String APIROOT = "http://hw.airgradient.com/";
// set to true to switch from Celcius to Fahrenheit
boolean inF = false;
// set to true if you want to connect to wifi. You have 60 seconds to connect. Then it will go into an offline mode.
boolean connectWIFI=true;
// CONFIGURATION END
unsigned long currentMillis = 0;
const int oledInterval = 5000;
unsigned long previousOled = 0;
const int sendToServerInterval = 10000;
unsigned long previoussendToServer = 0;
const int tvocInterval = 1000;
unsigned long previousTVOC = 0;
int TVOC = 0;
const int co2Interval = 5000;
unsigned long previousCo2 = 0;
int Co2 = 0;
const int pm25Interval = 5000;
unsigned long previousPm25 = 0;
int pm25 = 0;
const int tempHumInterval = 2500;
unsigned long previousTempHum = 0;
float temp = 0;
int hum = 0;
void setup()
{
Serial.begin(115200);
u8g2.begin();
updateOLED();
if (connectWIFI) {
connectToWifi();
}
updateOLED2("Warming up the", "sensors.", "");
sgp40.begin();
ag.CO2_Init();
ag.PMS_Init();
ag.TMP_RH_Init(0x44);
}
void loop()
{
currentMillis = millis();
updateTVOC();
updateOLED();
updateCo2();
updatePm25();
updateTempHum();
sendToServer();
}
void updateTVOC()
{
if (currentMillis - previousTVOC >= tvocInterval) {
previousTVOC += tvocInterval;
TVOC = sgp40.getVoclndex();
Serial.println(String(TVOC));
}
}
void updateCo2()
{
if (currentMillis - previousCo2 >= co2Interval) {
previousCo2 += co2Interval;
Co2 = ag.getCO2_Raw();
Serial.println(String(Co2));
}
}
void updatePm25()
{
if (currentMillis - previousPm25 >= pm25Interval) {
previousPm25 += pm25Interval;
pm25 = ag.getPM2_Raw();
Serial.println(String(pm25));
}
}
void updateTempHum()
{
if (currentMillis - previousTempHum >= tempHumInterval) {
previousTempHum += tempHumInterval;
TMP_RH result = ag.periodicFetchData();
temp = result.t;
hum = result.rh;
Serial.println(String(temp));
}
}
void updateOLED() {
if (currentMillis - previousOled >= oledInterval) {
previousOled += oledInterval;
String ln3;
String ln1 = "PM:" + String(pm25) + " CO2:" + String(Co2);
String ln2 = "AQI:" + String(PM_TO_AQI_US(pm25)) + " TVOC:" + String(TVOC);
if (inF) {
ln3 = "F:" + String((temp* 9 / 5) + 32) + " H:" + String(hum)+"%";
} else {
ln3 = "C:" + String(temp) + " H:" + String(hum)+"%";
}
updateOLED2(ln1, ln2, ln3);
}
}
void updateOLED2(String ln1, String ln2, String ln3) {
char buf[9];
u8g2.firstPage();
u8g2.firstPage();
do {
u8g2.setFont(u8g2_font_t0_16_tf);
u8g2.drawStr(1, 10, String(ln1).c_str());
u8g2.drawStr(1, 30, String(ln2).c_str());
u8g2.drawStr(1, 50, String(ln3).c_str());
} while ( u8g2.nextPage() );
}
void sendToServer() {
if (currentMillis - previoussendToServer >= sendToServerInterval) {
previoussendToServer += sendToServerInterval;
String payload = "{\"wifi\":" + String(WiFi.RSSI())
+ ", \"rco2\":" + String(Co2)
+ ", \"pm02\":" + String(pm25)
+ ", \"tvoc\":" + String(TVOC)
+ ", \"atmp\":" + String(temp)
+ ", \"rhum\":" + String(hum)
+ "}";
if(WiFi.status()== WL_CONNECTED){
Serial.println(payload);
String POSTURL = APIROOT + "sensors/airgradient:" + String(ESP.getChipId(), HEX) + "/measures";
Serial.println(POSTURL);
WiFiClient client;
HTTPClient http;
http.begin(client, POSTURL);
http.addHeader("content-type", "application/json");
int httpCode = http.POST(payload);
String response = http.getString();
Serial.println(httpCode);
Serial.println(response);
http.end();
}
else {
Serial.println("WiFi Disconnected");
}
}
}
// Wifi Manager
void connectToWifi() {
WiFiManager wifiManager;
//WiFi.disconnect(); //to delete previous saved hotspot
String HOTSPOT = "AG-" + String(ESP.getChipId(), HEX);
updateOLED2("60s to connect", "to Wifi Hotspot", HOTSPOT);
wifiManager.setTimeout(60);
WiFiManagerParameter custom_text("<p>This is just a text paragraph</p>");
wifiManager.addParameter(&custom_text);
WiFiManagerParameter parameter("parameterId", "Parameter Label", "default value", 40);
wifiManager.addParameter(&parameter);
Serial.println("Parameter 1:");
Serial.println(parameter.getValue());
if (!wifiManager.autoConnect((const char * ) HOTSPOT.c_str())) {
updateOLED2("booting into", "offline mode", "");
Serial.println("failed to connect and hit timeout");
delay(6000);
}
Serial.println("Parameter 2:");
Serial.println(parameter.getValue());
}
// Calculate PM2.5 US AQI
int PM_TO_AQI_US(int pm02) {
if (pm02 <= 12.0) return ((50 - 0) / (12.0 - .0) * (pm02 - .0) + 0);
else if (pm02 <= 35.4) return ((100 - 50) / (35.4 - 12.0) * (pm02 - 12.0) + 50);
else if (pm02 <= 55.4) return ((150 - 100) / (55.4 - 35.4) * (pm02 - 35.4) + 100);
else if (pm02 <= 150.4) return ((200 - 150) / (150.4 - 55.4) * (pm02 - 55.4) + 150);
else if (pm02 <= 250.4) return ((300 - 200) / (250.4 - 150.4) * (pm02 - 150.4) + 200);
else if (pm02 <= 350.4) return ((400 - 300) / (350.4 - 250.4) * (pm02 - 250.4) + 300);
else if (pm02 <= 500.4) return ((500 - 400) / (500.4 - 350.4) * (pm02 - 350.4) + 400);
else return 500;
};

View File

@ -25,7 +25,7 @@ MIT License
AirGradient ag = AirGradient();
void setup() {
Serial.begin(9600);
Serial.begin(115200);
ag.PMS_Init();
}

View File

@ -24,7 +24,7 @@ MIT License
AirGradient ag = AirGradient();
void setup(){
Serial.begin(9600);
Serial.begin(115200);
ag.TMP_RH_Init(0x44); //check for SHT sensor with address 0x44
}

View File

@ -1,9 +1,9 @@
name=AirGradient Air Quality Sensor
version=2.0.0
version=2.1.0
author=AirGradient <support@airgradient.com>
maintainer=AirGradient <support@airgradient.com>
sentence=ESP8266 library for an air quality sensor featuring PM2.5, CO2, Temperature and Humidity with OLED display.
paragraph=The library is very robust and works with the Plantower PMS5003 particle sensor, the Senseair S8 CO2 sensor and the SHT30/31 sensor for humidity and temperature. You can also connect an OLED display or send the air quality data to the AirGradient platform or any other backend. Kits with all components including a nice enclosure are available in our online shop.
sentence=ESP8266 library for an air quality sensor featuring PM2.5, CO2, Temperature, TVOC and Humidity with OLED display.
paragraph=Air quality monitoring library supporting the Plantower PMS5003 particle sensor, the Senseair S8 CO2 sensor and the SHT30/31 sensor for humidity and temperature. Kits with all components including a nice enclosure are available in our online shop. You can also connect an OLED display or send the air quality data to the AirGradient platform or any other backend. Optionally you can connect the Sensirion SGP4x TVOC module from AirGradient.
category=Sensors
url=https://www.airgradient.com/open-airgradient/instructions/
architectures=*