Compare commits

...

30 Commits
2.1.0 ... 2.4.6

Author SHA1 Message Date
d5432630fe Adjusted Arduino library version 2023-10-01 06:35:52 +07:00
356e76f10e Add PM1, PM10 and Particle Count 2023-09-09 17:06:05 +07:00
92f665d9ee Added SHT library for DIY BASIC to be compatible with newer SHT40 T/RH sensor 2023-08-21 14:12:52 +07:00
a42ceb3b77 Added SHT library for DIY Pro 4.2 to be compatible with newer SHT40 T/RH sensor
Removed old example files
2023-08-21 13:28:25 +07:00
1f364a3b74 Fixed configuration button behavior 2023-08-20 18:29:12 +07:00
37d1140eda Updated library version 2023-04-14 12:29:43 +07:00
63e1bcdc30 Updated outdoor C3 example to send channels as sub objects 2023-04-14 11:51:12 +07:00
c53517cadf Updated outdoor C3 example 2023-04-13 15:03:31 +07:00
6314e52770 Updated indoor examples 2023-04-13 09:33:26 +07:00
43f599a0a7 Corrected Arduino JSON version 2023-03-22 15:55:16 +07:00
cfc37d2d96 Added example for PCB v4.2 2023-03-11 06:53:37 +07:00
693d1f78aa Adjusted license to CC BY-SA 2023-03-09 07:35:49 +07:00
ef802593a6 Updated default display orientation 2023-02-26 16:57:08 +07:00
8ec543e9c1 Updated Arduino library version tag 2023-02-22 08:44:08 +07:00
9722eda5fa Set bus clock for u8g2 to 100kHz for increased stability.
Changed SenseAir CO2 request code to 0X04 ...
2023-02-22 08:37:56 +07:00
253d8a6810 Updated outdoor example (removed HEX from Chip ID) 2023-01-13 08:27:48 +07:00
6101429d30 Updated outdoor example 2023-01-13 06:56:17 +07:00
47c55ae0dd Updated example sketches 2022-12-20 07:59:52 +07:00
0511f5430f Added Display Rotation on Config Button 2022-12-10 10:36:31 +07:00
5481bd51c8 Added Outdoor Code Example 2022-12-09 14:51:19 +07:00
7d717cc01c Added PM01 and PM10 2022-11-15 14:35:03 +07:00
05b47e6b3d Fixed humidity reading 2022-11-15 11:05:54 +07:00
77ee1de9c1 Added example for DIY V3.7 Board with Push Button 2022-11-14 07:27:27 +07:00
183f62daf7 Adding additional parameters for Plantower PM sensors 2022-11-10 16:25:01 +07:00
5dd8a8be32 Testing Sensirion SGP41 libraries with hum/temp compensation 2022-11-07 07:04:26 +07:00
98ec0c4d10 Improved example code DIY Basic 2022-11-06 19:21:28 +07:00
724d041684 Improved formatting 2022-11-03 13:51:29 +07:00
afd4d16866 Adding CO2 Baseline Calibration Example 2022-11-03 13:49:52 +07:00
dc1a070ee3 Adjusted examples to not send error codes (e.g. -1) to server 2022-11-01 14:04:41 +07:00
84fe9c2575 Test with datapos shifts on CO2 readings 2022-10-31 08:43:13 +07:00
17 changed files with 1744 additions and 562 deletions

View File

@ -109,6 +109,127 @@ int AirGradient::getPM2_Raw(){
}
}
int AirGradient::getPM1_Raw(){
int pm02;
DATA data;
requestRead();
if (readUntil(data)) {
pm02 = data.PM_AE_UG_1_0;
return pm02;
} else {
return -1;
}
}
int AirGradient::getPM10_Raw(){
int pm02;
DATA data;
requestRead();
if (readUntil(data)) {
pm02 = data.PM_AE_UG_10_0;
return pm02;
} else {
return -1;
}
}
int AirGradient::getPM0_3Count(){
int count;
DATA data;
requestRead();
if (readUntil(data)) {
count = data.PM_RAW_0_3;
return count;
} else {
return -1;
}
}
int AirGradient::getPM10_0Count(){
int count;
DATA data;
requestRead();
if (readUntil(data)) {
count = data.PM_RAW_10_0;
return count;
} else {
return -1;
}
}
int AirGradient::getPM5_0Count(){
int count;
DATA data;
requestRead();
if (readUntil(data)) {
count = data.PM_RAW_5_0;
return count;
} else {
return -1;
}
}
int AirGradient::getPM2_5Count(){
int count;
DATA data;
requestRead();
if (readUntil(data)) {
count = data.PM_RAW_2_5;
return count;
} else {
return -1;
}
}
int AirGradient::getPM1_0Count(){
int count;
DATA data;
requestRead();
if (readUntil(data)) {
count = data.PM_RAW_1_0;
return count;
} else {
return -1;
}
}
int AirGradient::getPM0_5Count(){
int count;
DATA data;
requestRead();
if (readUntil(data)) {
count = data.PM_RAW_0_5;
return count;
} else {
return -1;
}
}
int AirGradient::getAMB_TMP(){
int count;
DATA data;
requestRead();
if (readUntil(data)) {
count = data.PM_TMP;
return count;
} else {
return -1;
}
}
int AirGradient::getAMB_HUM(){
int count;
DATA data;
requestRead();
if (readUntil(data)) {
count = data.PM_HUM;
return count;
} else {
return -1;
}
}
// Private Methods /////////////////////////////////////////////////////////////
// Functions only available to other functions in this library
@ -248,6 +369,21 @@ void AirGradient::loop()
_data->PM_AE_UG_1_0 = makeWord(_payload[6], _payload[7]);
_data->PM_AE_UG_2_5 = makeWord(_payload[8], _payload[9]);
_data->PM_AE_UG_10_0 = makeWord(_payload[10], _payload[11]);
// Total particles count per 100ml air
_data->PM_RAW_0_3 = makeWord(_payload[12], _payload[13]);
_data->PM_RAW_0_5 = makeWord(_payload[14], _payload[15]);
_data->PM_RAW_1_0 = makeWord(_payload[16], _payload[17]);
_data->PM_RAW_2_5 = makeWord(_payload[18], _payload[19]);
_data->PM_RAW_5_0 = makeWord(_payload[20], _payload[21]);
_data->PM_RAW_10_0 = makeWord(_payload[22], _payload[23]);
// Formaldehyde concentration (PMSxxxxST units only)
_data->AMB_HCHO = makeWord(_payload[24], _payload[25]) / 1000;
// Temperature & humidity (PMSxxxxST units only)
_data->PM_TMP = makeWord(_payload[20], _payload[21]) / 10;
_data->PM_HUM = makeWord(_payload[22], _payload[23]) / 10;
}
_index = 0;
@ -589,48 +725,6 @@ 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;
// }
// 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;
@ -664,10 +758,12 @@ 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};
const byte CO2Command[] = {0XFE, 0X04, 0X00, 0X03, 0X00, 0X01, 0XD5, 0XC5};
byte CO2Response[] = {0,0,0,0,0,0,0};
int datapos = -1;
const int commandSize = 7;
const int commandSize = 8;
const int responseSize = 7;
int numberOfBytesWritten = _SoftSerial_CO2->write(CO2Command, commandSize);
@ -678,7 +774,7 @@ int AirGradient::getCO2_Raw() {
// attempt to read response
int timeoutCounter = 0;
while (_SoftSerial_CO2->available() < commandSize) {
while (_SoftSerial_CO2->available() < responseSize) {
timeoutCounter++;
if (timeoutCounter > 10) {
// timeout when reading response
@ -688,10 +784,15 @@ int AirGradient::getCO2_Raw() {
}
// we have 7 bytes ready to be read
for (int i=0; i < commandSize; i++) {
for (int i=0; i < responseSize; i++) {
CO2Response[i] = _SoftSerial_CO2->read();
if ((CO2Response[i] == 0xFE) && (datapos == -1)){
datapos = i;
}
return CO2Response[3]*256 + CO2Response[4];
Serial.print (CO2Response[i],HEX);
Serial.print (":");
}
return CO2Response[datapos + 3]*256 + CO2Response[datapos + 4];
}
//END CO2 FUNCTIONS //

View File

@ -186,6 +186,21 @@ class AirGradient
uint16_t PM_AE_UG_1_0;
uint16_t PM_AE_UG_2_5;
uint16_t PM_AE_UG_10_0;
// Raw particles count (number of particles in 0.1l of air
uint16_t PM_RAW_0_3;
uint16_t PM_RAW_0_5;
uint16_t PM_RAW_1_0;
uint16_t PM_RAW_2_5;
uint16_t PM_RAW_5_0;
uint16_t PM_RAW_10_0;
// Formaldehyde (HCHO) concentration in mg/m^3 - PMSxxxxST units only
uint16_t AMB_HCHO;
// Temperature & humidity - PMSxxxxST units only
int16_t PM_TMP;
uint16_t PM_HUM;
};
void PMS(Stream&);
@ -197,8 +212,22 @@ class AirGradient
void requestRead();
bool read_PMS(DATA& data);
bool readUntil(DATA& data, uint16_t timeout = SINGLE_RESPONSE_TIME);
const char* getPM2();
int getPM2_Raw();
int getPM1_Raw();
int getPM10_Raw();
int getPM0_3Count();
int getPM0_5Count();
int getPM1_0Count();
int getPM2_5Count();
int getPM5_0Count();
int getPM10_0Count();
int getAMB_TMP();
int getAMB_HUM();
//PMS VARIABLES PUBLIC_END
@ -252,7 +281,7 @@ class AirGradient
enum STATUS { STATUS_WAITING, STATUS_OK };
enum MODE { MODE_ACTIVE, MODE_PASSIVE };
uint8_t _payload[12];
uint8_t _payload[32];
Stream* _stream;
DATA* _data;
STATUS _PMSstatus;
@ -264,7 +293,9 @@ class AirGradient
uint16_t _calculatedChecksum;
SoftwareSerial *_SoftSerial_PMS;
void loop();
char Char_PM1[10];
char Char_PM2[10];
char Char_PM10[10];
//PMS VARIABLES PRIVATE END
//TMP_RH VARIABLES PRIVATE START

View File

@ -0,0 +1,127 @@
/*
This is the code for forced calibration of the SenseAir S8 sensor. The sensor also has a one-week automatic baseline calibration that should calibrate the sensor latest after one week.0
However if you need a faster calibration please proceed as following:
1. Flash this code
2. Bring the sensor outside into fresh air and leave it there for at least 10 minutes
3. Power on the sensor
4. Follow the instructions on the display
5. After the calibration has been done, flash back the previous code for AQ measurements
The codes needs the following libraries installed:
“S8_UART” by Josep Comas tested with version 1.0.1
“U8g2” by oliver tested with version 2.32.15
Many thanks to Josep Comas of the S8_UART library from which parts of below code are borrowed.
*/
#include <Arduino.h>
#include "s8_uart.h"
#include <U8g2lib.h>
/* BEGIN CONFIGURATION */
// Display
//U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);//for DIY PRO
U8G2_SSD1306_64X48_ER_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); //for DIY BASIC
#define DEBUG_BAUDRATE 115200
#if (defined USE_SOFTWARE_SERIAL || defined ARDUINO_ARCH_RP2040)
#define S8_RX_PIN 2
#define S8_TX_PIN 0
#else
#define S8_UART_PORT 1
#endif
#define COUNTDOWN (60) //seconds
/* END CONFIGURATION */
#ifdef USE_SOFTWARE_SERIAL
SoftwareSerial S8_serial(S8_RX_PIN, S8_TX_PIN);
#else
#if defined(ARDUINO_ARCH_RP2040)
REDIRECT_STDOUT_TO(Serial)
UART S8_serial(S8_TX_PIN, S8_RX_PIN, NC, NC);
#else
HardwareSerial S8_serial(S8_UART_PORT);
#endif
#endif
S8_UART *sensor_S8;
S8_sensor sensor;
void setup() {
Serial.begin(DEBUG_BAUDRATE);
u8g2.begin();
int i = 0;
while (!Serial && i < 50) {
delay(10);
i++;
}
S8_serial.begin(S8_BAUDRATE);
sensor_S8 = new S8_UART(S8_serial);
sensor_S8->get_firmware_version(sensor.firm_version);
int len = strlen(sensor.firm_version);
if (len == 0) {
Serial.println("SenseAir S8 CO2 sensor not found!");
updateOLED2("SenseAir", "not", "found");
while (1) { delay(1); };
}
Serial.println(">>> SenseAir S8 NDIR CO2 sensor <<<");
printf("Firmware version: %s\n", sensor.firm_version);
sensor.sensor_id = sensor_S8->get_sensor_ID();
Serial.print("Sensor ID: 0x"); printIntToHex(sensor.sensor_id, 4); Serial.println("");
Serial.println("Now, you put the sensor outside and wait.");
Serial.println("Countdown begins...");
unsigned int seconds = COUNTDOWN;
while (seconds > 0) {
printf("Time remaining: %d minutes %d seconds\n", seconds / 60, seconds % 60);
updateOLED2("Wait", "for", String(seconds) + " Sec");
delay(1000);
seconds--;
}
Serial.println("Time reamining: 0 minutes 0 seconds");
// Start manual calibration
Serial.println("Starting manual calibration...");
updateOLED2("Starting", "Manual", "Calibration");
delay(2000);
if (!sensor_S8->manual_calibration()) {
Serial.println("Error setting manual calibration!");
updateOLED2("Error", "Manual", "Calibration");
while (1) { delay(10); }
}
}
void loop() {
static unsigned int elapsed = 0;
delay(2000);
elapsed += 2;
// Check if background calibration is finished
sensor.ack = sensor_S8->get_acknowledgement();
if (sensor.ack & S8_MASK_CO2_BACKGROUND_CALIBRATION) {
printf("Manual calibration is finished. Elapsed: %u seconds\n", elapsed);
updateOLED2("Calibration", "finished", "");
while (1) { delay(10); }
} else {
Serial.println("Doing manual calibration...");
updateOLED2("Doing", "manual", "calibration");
}
}
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, 28, String(ln2).c_str());
u8g2.drawStr(1, 46, String(ln3).c_str());
} while ( u8g2.nextPage() );
}

View File

@ -17,7 +17,8 @@ https://www.airgradient.com/
Kits with all required components are available at https://www.airgradient.com/open-airgradient/shop/
MIT License
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
*/
#include <AirGradient.h>

View File

@ -21,7 +21,8 @@ If you have any questions please visit our forum at https://forum.airgradient.co
If you are a school or university contact us for a free trial on the AirGradient platform.
https://www.airgradient.com/
MIT License
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
*/
#include <AirGradient.h>

View File

@ -9,7 +9,8 @@ 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
"ESP8266 and ESP32 OLED driver for SSD1306 displays by ThingPulse, Fabrice Weinberg" tested with Version 4.1.0
“U8g2” by oliver tested with version 2.32.15
"Arduino-SHT" by Johannes Winkelmann Version 1.2.2
Configuration:
Please set in the code below the configuration parameters.
@ -29,26 +30,32 @@ MIT License
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>
#include <Wire.h>
#include "SSD1306Wire.h"
#include <U8g2lib.h>
#include "SHTSensor.h"
AirGradient ag = AirGradient();
SHTSensor sht;
U8G2_SSD1306_64X48_ER_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); //for DIY BASIC
SSD1306Wire display(0x3c, SDA, SCL);
// CONFIGURATION START
// set to true to switch PM2.5 from ug/m3 to US AQI
boolean inUSaqi = true;
//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. The display will show values only when the sensor has wifi connection
// PM2.5 in US AQI (default ug/m3)
boolean inUSAQI = 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;
@ -69,28 +76,24 @@ const int tempHumInterval = 2500;
unsigned long previousTempHum = 0;
float temp = 0;
int hum = 0;
int displaypage = 0;
String APIROOT = "http://hw.airgradient.com/";
long val;
void setup()
{
Serial.begin(115200);
display.init();
display.flipScreenVertically();
sht.init();
sht.setAccuracy(SHTSensor::SHT_ACCURACY_MEDIUM);
u8g2.setBusClock(100000);
u8g2.begin();
updateOLED();
if (connectWIFI) {
connectToWifi();
}
showTextRectangle("Init", String(ESP.getChipId(), HEX), true);
updateOLED2("Warm Up", "Serial#", String(ESP.getChipId(), HEX));
ag.CO2_Init();
ag.PMS_Init();
ag.TMP_RH_Init(0x44);
//ag.TMP_RH_Init(0x44);
}
@ -126,9 +129,19 @@ void updateTempHum()
{
if (currentMillis - previousTempHum >= tempHumInterval) {
previousTempHum += tempHumInterval;
TMP_RH result = ag.periodicFetchData();
temp = result.t;
hum = result.rh;
if (sht.readSample()) {
Serial.print("SHT:\n");
Serial.print(" RH: ");
Serial.print(sht.getHumidity(), 2);
Serial.print("\n");
Serial.print(" T: ");
Serial.print(sht.getTemperature(), 2);
Serial.print("\n");
temp = sht.getTemperature();
hum = sht.getHumidity();
} else {
Serial.print("Error in readSample()\n");
}
Serial.println(String(temp));
}
}
@ -137,56 +150,49 @@ void updateOLED() {
if (currentMillis - previousOled >= oledInterval) {
previousOled += oledInterval;
switch (displaypage) {
case 0:
if (inUSaqi) {
showTextRectangle("AQI", String(PM_TO_AQI_US(pm25)), false);
String ln1;
String ln2;
String ln3;
if (inUSAQI){
ln1 = "AQI:" + String(PM_TO_AQI_US(pm25)) ;
} else {
showTextRectangle("PM2", String(pm25), false);
ln1 = "PM: " + String(pm25) +"ug" ;
}
displaypage = 1;
break;
case 1:
showTextRectangle("CO2", String(Co2), false);
displaypage = 2;
break;
case 2:
ln2 = "CO2:" + String(Co2);
if (inF) {
showTextRectangle("F", String((temp * 9 / 5) + 32), false);
ln3 = String((temp* 9 / 5) + 32).substring(0,4) + " " + String(hum)+"%";
} else {
showTextRectangle("C", String(temp), false);
}
displaypage = 3;
break;
case 3:
showTextRectangle("Hum", String(hum)+"%", false);
displaypage = 0;
break;
ln3 = String(temp).substring(0,4) + " " + String(hum)+"%";
}
updateOLED2(ln1, ln2, ln3);
}
}
void showTextRectangle(String ln1, String ln2, boolean small) {
display.clear();
display.setTextAlignment(TEXT_ALIGN_LEFT);
if (small) {
display.setFont(ArialMT_Plain_16);
} else {
display.setFont(ArialMT_Plain_24);
}
display.drawString(32, 16, ln1);
display.drawString(32, 38, ln2);
display.display();
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, 28, String(ln2).c_str());
u8g2.drawStr(1, 46, 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)
+ (Co2 < 0 ? "" : ", \"rco2\":" + String(Co2))
+ (pm25 < 0 ? "" : ", \"pm02\":" + String(pm25))
+ ", \"atmp\":" + String(temp)
+ ", \"rhum\":" + String(hum)
+ (hum < 0 ? "" : ", \"rhum\":" + String(hum))
+ "}";
if(WiFi.status()== WL_CONNECTED){
@ -213,10 +219,12 @@ void sendToServer() {
void connectToWifi() {
WiFiManager wifiManager;
//WiFi.disconnect(); //to delete previous saved hotspot
String HOTSPOT = "AIRGRADIENT-" + String(ESP.getChipId(), HEX);
wifiManager.setTimeout(60);
String HOTSPOT = "AG-" + String(ESP.getChipId(), HEX);
updateOLED2("Connect", "Wifi AG-", String(ESP.getChipId(), HEX));
delay(2000);
wifiManager.setTimeout(90);
if (!wifiManager.autoConnect((const char * ) HOTSPOT.c_str())) {
showTextRectangle("offline", "mode", true);
updateOLED2("Booting", "offline", "mode");
Serial.println("failed to connect and hit timeout");
delay(6000);
}

View File

@ -1,156 +0,0 @@
/*
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/
Instructions on using the TVOC sensor (SGP30) instead of the Temperature / Humidity sensor (SHT3x).
https://www.airgradient.com/open-airgradient/instructions/tvoc-on-airgradient-diy-sensor/
The codes needs the following libraries installed:
"WifiManager by tzapu, tablatronix" tested with Version 2.0.3-alpha
"ESP8266 and ESP32 OLED driver for SSD1306 displays by ThingPulse, Fabrice Weinberg" tested with Version 4.1.0
"SGP30" by Rob Tillaart tested with Version 0.1.4
Configuration:
Set in the code below which sensor you are using and if you want to connect it to WiFi.
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 "SGP30.h"
SGP30 SGP;
#include <AirGradient.h>
#include <WiFiManager.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <Wire.h>
#include "SSD1306Wire.h"
AirGradient ag = AirGradient();
SSD1306Wire display(0x3c, SDA, SCL);
WiFiClient client;
// set sensors that you do not use to false
boolean hasPM=true;
boolean hasCO2=true;
boolean hasSHT=false;
boolean hasTVOC=true;
// set to true if you want to connect to wifi. The display will show values only when the sensor has wifi connection
boolean connectWIFI=true;
// change if you want to send the data to another server
String APIROOT = "http://hw.airgradient.com/";
void setup(){
Serial.begin(115200);
display.init();
display.flipScreenVertically();
showTextRectangle("Init", String(ESP.getChipId(),HEX),true);
if (hasTVOC) SGP.begin();
if (hasPM) ag.PMS_Init();
if (hasCO2) ag.CO2_Init();
if (hasSHT) ag.TMP_RH_Init(0x44);
if (connectWIFI) connectToWifi();
delay(2000);
}
void loop(){
if (hasTVOC) SGP.measure(false);
// create payload
String payload = "{\"wifi\":" + String(WiFi.RSSI()) + ",";
if (hasPM) {
int PM2 = ag.getPM2_Raw();
payload=payload+"\"pm02\":" + String(PM2);
showTextRectangle("PM2",String(PM2),false);
delay(3000);
}
if (hasTVOC) {
if (hasPM) payload=payload+",";
int TVOC = SGP.getTVOC();
payload=payload+"\"tvoc\":" + String(TVOC);
showTextRectangle("TVOC",String(TVOC),false);
delay(3000);
}
if (hasCO2) {
if (hasTVOC) payload=payload+",";
int CO2 = ag.getCO2_Raw();
payload=payload+"\"rco2\":" + String(CO2);
showTextRectangle("CO2",String(CO2),false);
delay(3000);
}
if (hasSHT) {
if (hasCO2 || hasPM) payload=payload+",";
TMP_RH result = ag.periodicFetchData();
payload=payload+"\"atmp\":" + String(result.t) + ",\"rhum\":" + String(result.rh);
showTextRectangle(String(result.t),String(result.rh)+"%",false);
delay(3000);
}
payload=payload+"}";
// send payload
if (connectWIFI){
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();
}
}
// DISPLAY
void showTextRectangle(String ln1, String ln2, boolean small) {
display.clear();
display.setTextAlignment(TEXT_ALIGN_LEFT);
if (small) {
display.setFont(ArialMT_Plain_16);
} else {
display.setFont(ArialMT_Plain_24);
}
display.drawString(32, 16, ln1);
display.drawString(32, 36, ln2);
display.display();
}
// Wifi Manager
void connectToWifi(){
WiFiManager wifiManager;
//WiFi.disconnect(); //to delete previous saved hotspot
String HOTSPOT = "AIRGRADIENT-"+String(ESP.getChipId(),HEX);
wifiManager.setTimeout(120);
if(!wifiManager.autoConnect((const char*)HOTSPOT.c_str())) {
Serial.println("failed to connect and hit timeout");
delay(3000);
ESP.restart();
delay(5000);
}
}

View File

@ -0,0 +1,212 @@
/*
Important: This code is only for the AirGradient DIY OUTDOOR.
Build Instructions: https://www.airgradient.com/open-airgradient/instructions/diy-outdoor/
Kits (including a pre-soldered version) are available: https://www.airgradient.com/open-airgradient/kits/
Configuration:
Install required libraries
Patch PMS library to accept temperature and humidity from PMS5003T
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/
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
*/
#include "PMS.h"
#include "SoftwareSerial.h"
#include <Wire.h>
#include <WiFiManager.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>
SoftwareSerial softSerial(D6, D5);
SoftwareSerial soft2(D3, D7);
PMS pms(softSerial);
PMS pms2(soft2);
PMS::DATA data;
PMS::DATA data2;
float pm1Value=0;
int pm1Position = 0;
float pm2Value=0;
int pm2Position = 0;
float temp_pm1 = 0;
float hum_pm1 = 0;
float temp_pm2 = 0;
float hum_pm2 = 0;
unsigned long currentMillis = 0;
const int pm1Interval = 5000;
unsigned long previousPm1 = 0;
const int pm2Interval = 5000;
unsigned long previousPm2 = 0;
String APIROOT = "http://hw.airgradient.com/";
void setup()
{
Serial.begin(115200);
Serial.println("Chip ID: "+String(ESP.getChipId()));
softSerial.begin(9600);
soft2.begin(9600);
Wire.begin();
pinMode(D7, OUTPUT);
connectToWifi();
}
void loop()
{
currentMillis = millis();
updatePm1();
updatePm2();
}
void updatePm1()
{
if (currentMillis - previousPm1 >= pm1Interval) {
digitalWrite(D7, HIGH);
delay(400);
digitalWrite(D7, LOW);
Serial.println("updatePm1: "+String(pm1Position));
previousPm1 += pm1Interval;
pms.requestRead();
if (pms.readUntil(data)){
Serial.println("success read");
int pm1 = data.PM_AE_UG_2_5;
temp_pm1 = data.AMB_TMP;
hum_pm1 = data.AMB_HUM;
Serial.print("PMS 1: PM 2.5 (ug/m3): ");
Serial.println(pm1);
Serial.print("PMS 1: Temp: ");
Serial.println(temp_pm1);
Serial.print("PMS 1: Hum: ");
Serial.println(hum_pm1);
Serial.println();
delay(1000);
pm1Value=pm1Value+pm1;
pm1Position++;
if (pm1Position==20) {
sendToServerPM1(pm1Value);
pm1Position=0;
pm1Value=0;
}
}
}
}
void updatePm2()
{
if (currentMillis - previousPm2 >= pm2Interval) {
Serial.println("updatePm2: "+String(pm2Position));
previousPm2 += pm2Interval;
pms2.requestRead();
if (pms2.readUntil(data2)){
int pm2 = data2.PM_AE_UG_2_5;
temp_pm2 = data2.AMB_TMP ;
hum_pm2 = data2.AMB_HUM;
Serial.print("PMS 2: PM 2.5 (ug/m3): ");
Serial.println(pm2);
Serial.print("PMS 2: Temp: ");
Serial.println(temp_pm2);
Serial.print("PMS 2: Hum: ");
Serial.println(hum_pm2);
Serial.println();
delay(1000);
pm2Value=pm2Value+pm2;
pm2Position++;
if (pm2Position==20) {
sendToServerPM2(pm2Value);
pm2Position=0;
pm2Value=0;
}
}
}
}
void sendToServerPM1(float pm1Value) {
String payload = "{\"wifi\":" + String(WiFi.RSSI())
+ ", \"pm02\":" + String(pm1Value/20)
+ ", \"atmp\":" + String(temp_pm1/10)
+ ", \"rhum\":" + String(hum_pm1/10)
+ "}";
if(WiFi.status()== WL_CONNECTED){
digitalWrite(D7, HIGH);
delay(300);
Serial.println(payload);
String POSTURL = APIROOT + "sensors/airgradient:" + String(ESP.getChipId()) + "-1/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();
digitalWrite(D7, LOW);
}
else {
Serial.println("WiFi Disconnected");
}
}
void sendToServerPM2(float pm2Value) {
String payload = "{\"wifi\":" + String(WiFi.RSSI())
+ ", \"pm02\":" + String(pm2Value/20)
+ ", \"atmp\":" + String(temp_pm2/10)
+ ", \"rhum\":" + String(hum_pm2/10)
+ "}";
if(WiFi.status()== WL_CONNECTED){
digitalWrite(D7, HIGH);
delay(300);
Serial.println(payload);
String POSTURL = APIROOT + "sensors/airgradient:" + String(ESP.getChipId()) + "-2/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();
digitalWrite(D7, LOW);
}
else {
Serial.println("WiFi Disconnected");
}
}
// Wifi Manager
void connectToWifi() {
WiFiManager wifiManager;
//WiFi.disconnect(); //to delete previous saved hotspot
String HOTSPOT = "AIRGRADIENT-" + String(ESP.getChipId());
wifiManager.setTimeout(60);
if (!wifiManager.autoConnect((const char * ) HOTSPOT.c_str())) {
Serial.println("failed to connect and hit timeout");
delay(6000);
}
}

View File

@ -0,0 +1,277 @@
/*
Important: This code is only for the DIY OUTDOOR OPEN AIR Presoldered Kit with the ESP-C3.
It is a high quality outdoor air quality sensor with dual PM2.5 modules and can send data over Wifi.
Kits 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
“pms” by Markusz Kakl version 1.1.0 (needs to be patched for 5003T model)
For built instructions and how to patch the PMS library: https://www.airgradient.com/open-airgradient/instructions/diy-open-air-presoldered-v11/
Note that below code only works with both PM sensor modules connected.
If you have any questions please visit our forum at https://forum.airgradient.com/
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
*/
#include "PMS.h"
#include <HardwareSerial.h>
#include <Wire.h>
#include <HTTPClient.h>
#include <WiFiManager.h>
#define DEBUG true
HTTPClient client;
PMS pms1(Serial0);
PMS::DATA data1;
float pm1Value01=0;
float pm1Value25=0;
float pm1Value10=0;
float pm1PCount=0;
float pm1temp = 0;
float pm1hum = 0;
PMS pms2(Serial1);
PMS::DATA data2;
float pm2Value01=0;
float pm2Value25=0;
float pm2Value10=0;
float pm2PCount=0;
float pm2temp = 0;
float pm2hum = 0;
int countPosition = 0;
int targetCount = 20;
String APIROOT = "http://hw.airgradient.com/";
int loopCount = 0;
void IRAM_ATTR isr() {
debugln("pushed");
}
// select board LOLIN C3 mini to flash
void setup() {
if (DEBUG) {
Serial.begin(115200);
// see https://github.com/espressif/arduino-esp32/issues/6983
Serial.setTxTimeoutMs(0); // <<<====== solves the delay issue
}
debug("starting ...");
debug("Serial Number: "+ getNormalizedMac());
// default hardware serial, PMS connector on the right side of the C3 mini on the Open Air
Serial0.begin(9600);
// second hardware serial, PMS connector on the left side of the C3 mini on the Open Air
Serial1.begin(9600, SERIAL_8N1, 0, 1);
// led
pinMode(10, OUTPUT);
// push button
pinMode(9, INPUT_PULLUP);
attachInterrupt(9, isr, FALLING);
pinMode(2, OUTPUT);
digitalWrite(2, LOW);
// give the PMSs some time to start
countdown(3);
connectToWifi();
sendPing();
switchLED(false);
}
void loop() {
if(WiFi.status()== WL_CONNECTED) {
if (pms1.readUntil(data1, 2000) && pms2.readUntil(data2, 2000)) {
pm1Value01=pm1Value01+data1.PM_AE_UG_1_0;
pm1Value25=pm1Value25+data1.PM_AE_UG_2_5;
pm1Value10=pm1Value10+data1.PM_AE_UG_10_0;
pm1PCount=pm1PCount+data1.PM_RAW_0_3;
pm1temp=pm1temp+data1.AMB_TMP;
pm1hum=pm1hum+data1.AMB_HUM;
pm2Value01=pm2Value01+data2.PM_AE_UG_1_0;
pm2Value25=pm2Value25+data2.PM_AE_UG_2_5;
pm2Value10=pm2Value10+data2.PM_AE_UG_10_0;
pm2PCount=pm2PCount+data2.PM_RAW_0_3;
pm2temp=pm2temp+data2.AMB_TMP;
pm2hum=pm2hum+data2.AMB_HUM;
countPosition++;
if (countPosition==targetCount) {
pm1Value01 = pm1Value01 / targetCount;
pm1Value25 = pm1Value25 / targetCount;
pm1Value10 = pm1Value10 / targetCount;
pm1PCount = pm1PCount / targetCount;
pm1temp = pm1temp / targetCount;
pm1hum = pm1hum / targetCount;
pm2Value01 = pm2Value01 / targetCount;
pm2Value25 = pm2Value25 / targetCount;
pm2Value10 = pm2Value10 / targetCount;
pm2PCount = pm2PCount / targetCount;
pm2temp = pm2temp / targetCount;
pm2hum = pm2hum / targetCount;
postToServer(pm1Value01, pm1Value25,pm1Value10,pm1PCount, pm1temp,pm1hum,pm2Value01, pm2Value25,pm2Value10,pm2PCount, pm2temp,pm2hum);
countPosition=0;
pm1Value01=0;
pm1Value25=0;
pm1Value10=0;
pm1PCount=0;
pm1temp=0;
pm1hum=0;
pm2Value01=0;
pm2Value25=0;
pm2Value10=0;
pm2PCount=0;
pm2temp=0;
pm2hum=0;
}
}
}
countdown(2);
}
void debug(String msg) {
if (DEBUG)
Serial.print(msg);
}
void debug(int msg) {
if (DEBUG)
Serial.print(msg);
}
void debugln(String msg) {
if (DEBUG)
Serial.println(msg);
}
void debugln(int msg) {
if (DEBUG)
Serial.println(msg);
}
void switchLED(boolean ledON) {
if (ledON) {
digitalWrite(10, HIGH);
} else {
digitalWrite(10, LOW);
}
}
void sendPing(){
String payload = "{\"wifi\":" + String(WiFi.RSSI())
+ ", \"boot\":" + loopCount
+ "}";
sendPayload(payload);
}
void postToServer(int pm1Value01, int pm1Value25, int pm1Value10, int pm1PCount, float pm1temp, float pm1hum,int pm2Value01, int pm2Value25, int pm2Value10, int pm2PCount, float pm2temp, float pm2hum) {
String payload = "{\"wifi\":" + String(WiFi.RSSI())
+ ", \"pm01\":" + String((pm1Value01+pm2Value01)/2)
+ ", \"pm02\":" + String((pm1Value25+pm2Value25)/2)
+ ", \"pm10\":" + String((pm1Value10+pm2Value10)/2)
+ ", \"pm003_count\":" + String((pm1PCount+pm2PCount)/2)
+ ", \"atmp\":" + String((pm1temp+pm2temp)/20)
+ ", \"rhum\":" + String((pm1hum+pm2hum)/20)
+ ", \"boot\":" + loopCount
+ ", \"channels\": {"
+ "\"1\":{"
+ "\"pm01\":" + String(pm1Value01)
+ ", \"pm02\":" + String(pm1Value25)
+ ", \"pm10\":" + String(pm1Value10)
+ ", \"pm003_count\":" + String(pm1PCount)
+ ", \"atmp\":" + String(pm1temp/10)
+ ", \"rhum\":" + String(pm1hum/10)
+ "}"
+ ", \"2\":{"
+ " \"pm01\":" + String(pm1Value01)
+ ", \"pm02\":" + String(pm2Value25)
+ ", \"pm10\":" + String(pm2Value10)
+ ", \"pm003_count\":" + String(pm2PCount)
+ ", \"atmp\":" + String(pm2temp/10)
+ ", \"rhum\":" + String(pm2hum/10)
+ "}"
+ "}"
+ "}";
sendPayload(payload);
}
void sendPayload(String payload) {
if(WiFi.status()== WL_CONNECTED){
switchLED(true);
String url = APIROOT + "sensors/airgradient:" + getNormalizedMac() + "/measures";
debugln(url);
debugln(payload);
client.setConnectTimeout(5 * 1000);
client.begin(url);
client.addHeader("content-type", "application/json");
int httpCode = client.POST(payload);
debugln(httpCode);
client.end();
resetWatchdog();
switchLED(false);
}
else {
debug("post skipped, not network connection");
}
loopCount++;
}
void countdown(int from) {
debug("\n");
while (from > 0) {
debug(String(from--));
debug(" ");
delay(1000);
}
debug("\n");
}
void resetWatchdog() {
digitalWrite(2, HIGH);
delay(20);
digitalWrite(2, LOW);
}
// Wifi Manager
void connectToWifi() {
WiFiManager wifiManager;
switchLED(true);
//WiFi.disconnect(); //to delete previous saved hotspot
String HOTSPOT = "AG-" + String(getNormalizedMac());
wifiManager.setTimeout(180);
if (!wifiManager.autoConnect((const char * ) HOTSPOT.c_str())) {
switchLED(false);
Serial.println("failed to connect and hit timeout");
delay(6000);
}
}
String getNormalizedMac() {
String mac = WiFi.macAddress();
mac.replace(":", "");
mac.toLowerCase();
return mac;
}

View File

@ -10,7 +10,6 @@ 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
“SGP30” by Rob Tilaart tested with Version 0.1.5
Configuration:
Please set in the code below the configuration parameters.
@ -20,7 +19,7 @@ If you have any questions please visit our forum at https://forum.airgradient.co
If you are a school or university contact us for a free trial on the AirGradient platform.
https://www.airgradient.com/
MIT License
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
*/
@ -30,12 +29,9 @@ MIT License
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>
#include "SGP30.h"
#include <U8g2lib.h>
AirGradient ag = AirGradient();
SGP30 SGP;
// Display bottom right
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
@ -66,10 +62,6 @@ 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;
@ -86,7 +78,7 @@ int hum = 0;
void setup()
{
Serial.begin(115200);
u8g2.setBusClock(100000);
u8g2.begin();
updateOLED();
@ -96,9 +88,6 @@ void setup()
updateOLED2("Warming up the", "sensors.", "");
Serial.println(SGP.begin());
SGP.GenericReset();
ag.CO2_Init();
ag.PMS_Init();
ag.TMP_RH_Init(0x44);
@ -108,7 +97,6 @@ void setup()
void loop()
{
currentMillis = millis();
updateTVOC();
updateOLED();
updateCo2();
updatePm25();
@ -116,16 +104,6 @@ void loop()
sendToServer();
}
void updateTVOC()
{
if (currentMillis - previousTVOC >= tvocInterval) {
previousTVOC += tvocInterval;
SGP.measure(true);
TVOC = SGP.getTVOC();
Serial.println(String(TVOC));
}
}
void updateCo2()
{
if (currentMillis - previousCo2 >= co2Interval) {
@ -160,8 +138,8 @@ void updateOLED() {
previousOled += oledInterval;
String ln3;
String ln1 = "PM:" + String(pm25) + " CO2:" + String(Co2);
String ln2 = "AQI:" + String(PM_TO_AQI_US(pm25)) + " TVOC:" + String(TVOC);
String ln1 = "PM:" + String(pm25) + " AQI:" + String(PM_TO_AQI_US(pm25)) ;
String ln2 = "CO2:" + String(Co2);
if (inF) {
ln3 = "F:" + String((temp* 9 / 5) + 32) + " H:" + String(hum)+"%";
@ -187,12 +165,12 @@ void updateOLED2(String ln1, String ln2, String ln3) {
void sendToServer() {
if (currentMillis - previoussendToServer >= sendToServerInterval) {
previoussendToServer += sendToServerInterval;
String payload = "{\"wifi\":" + String(WiFi.RSSI())
+ ", \"rco2\":" + String(Co2)
+ ", \"pm02\":" + String(pm25)
+ ", \"tvoc\":" + String(TVOC)
+ (Co2 < 0 ? "" : ", \"rco2\":" + String(Co2))
+ (pm25 < 0 ? "" : ", \"pm02\":" + String(pm25))
+ ", \"atmp\":" + String(temp)
+ ", \"rhum\":" + String(hum)
+ (hum < 0 ? "" : ", \"rhum\":" + String(hum))
+ "}";
if(WiFi.status()== WL_CONNECTED){

View File

@ -0,0 +1,424 @@
/*
Important: This code is only for the DIY PRO PCB Version 3.7 that has a push button mounted.
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-v37/
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
"Sensirion I2C SGP41" by Sensation Version 0.1.0
"Sensirion Gas Index Algorithm" by Sensation Version 3.2.1
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/
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
*/
#include <AirGradient.h>
#include <WiFiManager.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>
#include <EEPROM.h>
//#include "SGP30.h"
#include <SensirionI2CSgp41.h>
#include <NOxGasIndexAlgorithm.h>
#include <VOCGasIndexAlgorithm.h>
#include <U8g2lib.h>
AirGradient ag = AirGradient();
SensirionI2CSgp41 sgp41;
VOCGasIndexAlgorithm voc_algorithm;
NOxGasIndexAlgorithm nox_algorithm;
// time in seconds needed for NOx conditioning
uint16_t conditioning_s = 10;
// for peristent saving and loading
int addr = 4;
byte value;
// 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;
// PM2.5 in US AQI (default ug/m3)
boolean inUSAQI = false;
// Display Position
boolean displayTop = true;
// 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;
int NOX = 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;
int buttonConfig=4;
int lastState = LOW;
int currentState;
unsigned long pressedTime = 0;
unsigned long releasedTime = 0;
void setup() {
Serial.begin(115200);
Serial.println("Hello");
u8g2.begin();
//u8g2.setDisplayRotation(U8G2_R0);
EEPROM.begin(512);
delay(500);
buttonConfig = String(EEPROM.read(addr)).toInt();
setConfig();
updateOLED2("Press Button", "Now for", "Config Menu");
delay(2000);
currentState = digitalRead(D7);
if (currentState == HIGH)
{
updateOLED2("Entering", "Config Menu", "");
delay(3000);
lastState = LOW;
inConf();
}
if (connectWIFI)
{
connectToWifi();
}
updateOLED2("Warming Up", "Serial Number:", String(ESP.getChipId(), HEX));
sgp41.begin(Wire);
ag.CO2_Init();
ag.PMS_Init();
ag.TMP_RH_Init(0x44);
}
void loop() {
currentMillis = millis();
updateTVOC();
updateOLED();
updateCo2();
updatePm25();
updateTempHum();
sendToServer();
}
void inConf(){
setConfig();
currentState = digitalRead(D7);
if(lastState == LOW && currentState == HIGH) {
pressedTime = millis();
}
else if(lastState == HIGH && currentState == LOW) {
releasedTime = millis();
long pressDuration = releasedTime - pressedTime;
if( pressDuration < 1000 ) {
buttonConfig=buttonConfig+1;
if (buttonConfig>7) buttonConfig=0;
}
}
if (lastState == HIGH && currentState == HIGH){
long passedDuration = millis() - pressedTime;
if( passedDuration > 4000 ) {
// to do
// if (buttonConfig==4) {
// updateOLED2("Saved", "Release", "Button Now");
// delay(1000);
// updateOLED2("Starting", "CO2", "Calibration");
// delay(1000);
// Co2Calibration();
// } else {
updateOLED2("Saved", "Release", "Button Now");
delay(1000);
updateOLED2("Rebooting", "in", "5 seconds");
delay(5000);
EEPROM.write(addr, char(buttonConfig));
EEPROM.commit();
delay(1000);
ESP.restart();
// }
}
}
lastState = currentState;
delay(100);
inConf();
}
void setConfig() {
if (buttonConfig == 0) {
updateOLED2("Temp. in C", "PM in ug/m3", "Display Top");
u8g2.setDisplayRotation(U8G2_R2);
inF = false;
inUSAQI = false;
}
if (buttonConfig == 1) {
updateOLED2("Temp. in C", "PM in US AQI", "Display Top");
u8g2.setDisplayRotation(U8G2_R2);
inF = false;
inUSAQI = true;
}
if (buttonConfig == 2) {
updateOLED2("Temp. in F", "PM in ug/m3", "Display Top");
u8g2.setDisplayRotation(U8G2_R2);
inF = true;
inUSAQI = false;
}
if (buttonConfig == 3) {
updateOLED2("Temp. in F", "PM in US AQI", "Display Top");
u8g2.setDisplayRotation(U8G2_R2);
inF = true;
inUSAQI = true;
}
if (buttonConfig == 4) {
updateOLED2("Temp. in C", "PM in ug/m3", "Display Bottom");
u8g2.setDisplayRotation(U8G2_R0);
inF = false;
inUSAQI = false;
}
if (buttonConfig == 5) {
updateOLED2("Temp. in C", "PM in US AQI", "Display Bottom");
u8g2.setDisplayRotation(U8G2_R0);
inF = false;
inUSAQI = true;
}
if (buttonConfig == 6) {
updateOLED2("Temp. in F", "PM in ug/m3", "Display Bottom");
u8g2.setDisplayRotation(U8G2_R0);
inF = true;
inUSAQI = false;
}
if (buttonConfig == 7) {
updateOLED2("Temp. in F", "PM in US AQI", "Display Bottom");
u8g2.setDisplayRotation(U8G2_R0);
inF = true;
inUSAQI = true;
}
// to do
// if (buttonConfig == 8) {
// updateOLED2("CO2", "Manual", "Calibration");
// }
}
void updateTVOC()
{
uint16_t error;
char errorMessage[256];
uint16_t defaultRh = 0x8000;
uint16_t defaultT = 0x6666;
uint16_t srawVoc = 0;
uint16_t srawNox = 0;
uint16_t defaultCompenstaionRh = 0x8000; // in ticks as defined by SGP41
uint16_t defaultCompenstaionT = 0x6666; // in ticks as defined by SGP41
uint16_t compensationRh = 0; // in ticks as defined by SGP41
uint16_t compensationT = 0; // in ticks as defined by SGP41
delay(1000);
compensationT = static_cast<uint16_t>((temp + 45) * 65535 / 175);
compensationRh = static_cast<uint16_t>(hum * 65535 / 100);
if (conditioning_s > 0) {
error = sgp41.executeConditioning(compensationRh, compensationT, srawVoc);
conditioning_s--;
} else {
error = sgp41.measureRawSignals(compensationRh, compensationT, srawVoc,
srawNox);
}
if (currentMillis - previousTVOC >= tvocInterval) {
previousTVOC += tvocInterval;
TVOC = voc_algorithm.process(srawVoc);
NOX = nox_algorithm.process(srawNox);
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;
if (inUSAQI) {
ln1 = "AQI:" + String(PM_TO_AQI_US(pm25)) + " CO2:" + String(Co2);
} else {
ln1 = "PM:" + String(pm25) + " CO2:" + String(Co2);
}
String ln2 = "TVOC:" + String(TVOC) + " NOX:" + String(NOX);
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())
+ (Co2 < 0 ? "" : ", \"rco2\":" + String(Co2))
+ (pm25 < 0 ? "" : ", \"pm02\":" + String(pm25))
+ (TVOC < 0 ? "" : ", \"tvoc_index\":" + String(TVOC))
+ (NOX < 0 ? "" : ", \"nox_index\":" + String(NOX))
+ ", \"atmp\":" + String(temp)
+ (hum < 0 ? "" : ", \"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("90s to connect", "to Wifi Hotspot", HOTSPOT);
wifiManager.setTimeout(90);
if (!wifiManager.autoConnect((const char * ) HOTSPOT.c_str())) {
updateOLED2("booting into", "offline mode", "");
Serial.println("failed to connect and hit timeout");
delay(6000);
}
}
// 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

@ -0,0 +1,433 @@
/*
Important: This code is only for the DIY PRO PCB Version 3.7 that has a push button mounted.
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-v37/
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
"Sensirion I2C SGP41" by Sensation Version 0.1.0
"Sensirion Gas Index Algorithm" by Sensation Version 3.2.1
"Arduino-SHT" by Johannes Winkelmann Version 1.2.2
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/
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
*/
#include <AirGradient.h>
#include <WiFiManager.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>
#include <EEPROM.h>
#include "SHTSensor.h"
//#include "SGP30.h"
#include <SensirionI2CSgp41.h>
#include <NOxGasIndexAlgorithm.h>
#include <VOCGasIndexAlgorithm.h>
#include <U8g2lib.h>
AirGradient ag = AirGradient();
SensirionI2CSgp41 sgp41;
VOCGasIndexAlgorithm voc_algorithm;
NOxGasIndexAlgorithm nox_algorithm;
SHTSensor sht;
// time in seconds needed for NOx conditioning
uint16_t conditioning_s = 10;
// for peristent saving and loading
int addr = 4;
byte value;
// 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;
// PM2.5 in US AQI (default ug/m3)
boolean inUSAQI = false;
// Display Position
boolean displayTop = true;
// 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;
int NOX = 0;
const int co2Interval = 5000;
unsigned long previousCo2 = 0;
int Co2 = 0;
const int pmInterval = 5000;
unsigned long previousPm = 0;
int pm25 = 0;
int pm01 = 0;
int pm10 = 0;
int pm03PCount = 0;
const int tempHumInterval = 2500;
unsigned long previousTempHum = 0;
float temp = 0;
int hum = 0;
int buttonConfig=0;
int lastState = LOW;
int currentState;
unsigned long pressedTime = 0;
unsigned long releasedTime = 0;
void setup() {
Serial.begin(115200);
Serial.println("Hello");
u8g2.begin();
sht.init();
sht.setAccuracy(SHTSensor::SHT_ACCURACY_MEDIUM);
//u8g2.setDisplayRotation(U8G2_R0);
EEPROM.begin(512);
delay(500);
buttonConfig = String(EEPROM.read(addr)).toInt();
if (buttonConfig>3) buttonConfig=0;
delay(400);
setConfig();
Serial.println("buttonConfig: "+String(buttonConfig));
updateOLED2("Press Button", "Now for", "Config Menu");
delay(2000);
pinMode(D7, INPUT_PULLUP);
currentState = digitalRead(D7);
if (currentState == LOW)
{
updateOLED2("Entering", "Config Menu", "");
delay(3000);
lastState = HIGH;
setConfig();
inConf();
}
if (connectWIFI)
{
connectToWifi();
}
updateOLED2("Warming Up", "Serial Number:", String(ESP.getChipId(), HEX));
sgp41.begin(Wire);
ag.CO2_Init();
ag.PMS_Init();
ag.TMP_RH_Init(0x44);
}
void loop() {
currentMillis = millis();
updateTVOC();
updateOLED();
updateCo2();
updatePm();
updateTempHum();
sendToServer();
}
void inConf(){
setConfig();
currentState = digitalRead(D7);
if (currentState){
Serial.println("currentState: high");
} else {
Serial.println("currentState: low");
}
if(lastState == HIGH && currentState == LOW) {
pressedTime = millis();
}
else if(lastState == LOW && currentState == HIGH) {
releasedTime = millis();
long pressDuration = releasedTime - pressedTime;
if( pressDuration < 1000 ) {
buttonConfig=buttonConfig+1;
if (buttonConfig>3) buttonConfig=0;
}
}
if (lastState == LOW && currentState == LOW){
long passedDuration = millis() - pressedTime;
if( passedDuration > 4000 ) {
// to do
// if (buttonConfig==4) {
// updateOLED2("Saved", "Release", "Button Now");
// delay(1000);
// updateOLED2("Starting", "CO2", "Calibration");
// delay(1000);
// Co2Calibration();
// } else {
updateOLED2("Saved", "Release", "Button Now");
delay(1000);
updateOLED2("Rebooting", "in", "5 seconds");
delay(5000);
EEPROM.write(addr, char(buttonConfig));
EEPROM.commit();
delay(1000);
ESP.restart();
// }
}
}
lastState = currentState;
delay(100);
inConf();
}
void setConfig() {
if (buttonConfig == 0) {
updateOLED2("Temp. in C", "PM in ug/m3", "Long Press Saves");
u8g2.setDisplayRotation(U8G2_R0);
inF = false;
inUSAQI = false;
}
if (buttonConfig == 1) {
updateOLED2("Temp. in C", "PM in US AQI", "Long Press Saves");
u8g2.setDisplayRotation(U8G2_R0);
inF = false;
inUSAQI = true;
} else if (buttonConfig == 2) {
updateOLED2("Temp. in F", "PM in ug/m3", "Long Press Saves");
u8g2.setDisplayRotation(U8G2_R0);
inF = true;
inUSAQI = false;
} else if (buttonConfig == 3) {
updateOLED2("Temp. in F", "PM in US AQI", "Long Press Saves");
u8g2.setDisplayRotation(U8G2_R0);
inF = true;
inUSAQI = true;
}
// to do
// if (buttonConfig == 8) {
// updateOLED2("CO2", "Manual", "Calibration");
// }
}
void updateTVOC()
{
uint16_t error;
char errorMessage[256];
uint16_t defaultRh = 0x8000;
uint16_t defaultT = 0x6666;
uint16_t srawVoc = 0;
uint16_t srawNox = 0;
uint16_t defaultCompenstaionRh = 0x8000; // in ticks as defined by SGP41
uint16_t defaultCompenstaionT = 0x6666; // in ticks as defined by SGP41
uint16_t compensationRh = 0; // in ticks as defined by SGP41
uint16_t compensationT = 0; // in ticks as defined by SGP41
delay(1000);
compensationT = static_cast<uint16_t>((temp + 45) * 65535 / 175);
compensationRh = static_cast<uint16_t>(hum * 65535 / 100);
if (conditioning_s > 0) {
error = sgp41.executeConditioning(compensationRh, compensationT, srawVoc);
conditioning_s--;
} else {
error = sgp41.measureRawSignals(compensationRh, compensationT, srawVoc,
srawNox);
}
if (currentMillis - previousTVOC >= tvocInterval) {
previousTVOC += tvocInterval;
TVOC = voc_algorithm.process(srawVoc);
NOX = nox_algorithm.process(srawNox);
Serial.println(String(TVOC));
}
}
void updateCo2()
{
if (currentMillis - previousCo2 >= co2Interval) {
previousCo2 += co2Interval;
Co2 = ag.getCO2_Raw();
Serial.println(String(Co2));
}
}
void updatePm()
{
if (currentMillis - previousPm >= pmInterval) {
previousPm += pmInterval;
pm01 = ag.getPM1_Raw();
pm25 = ag.getPM2_Raw();
pm10 = ag.getPM10_Raw();
pm03PCount = ag.getPM0_3Count();
Serial.println(String(pm25));
}
}
void updateTempHum()
{
if (currentMillis - previousTempHum >= tempHumInterval) {
previousTempHum += tempHumInterval;
if (sht.readSample()) {
Serial.print("SHT:\n");
Serial.print(" RH: ");
Serial.print(sht.getHumidity(), 2);
Serial.print("\n");
Serial.print(" T: ");
Serial.print(sht.getTemperature(), 2);
Serial.print("\n");
temp = sht.getTemperature();
hum = sht.getHumidity();
} else {
Serial.print("Error in readSample()\n");
}
Serial.println(String(temp));
}
}
void updateOLED() {
if (currentMillis - previousOled >= oledInterval) {
previousOled += oledInterval;
String ln3;
String ln1;
if (inUSAQI) {
ln1 = "AQI:" + String(PM_TO_AQI_US(pm25)) + " CO2:" + String(Co2);
} else {
ln1 = "PM:" + String(pm25) + " CO2:" + String(Co2);
}
String ln2 = "TVOC:" + String(TVOC) + " NOX:" + String(NOX);
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())
+ (Co2 < 0 ? "" : ", \"rco2\":" + String(Co2))
+ (pm01 < 0 ? "" : ", \"pm01\":" + String(pm01))
+ (pm25 < 0 ? "" : ", \"pm02\":" + String(pm25))
+ (pm10 < 0 ? "" : ", \"pm10\":" + String(pm10))
+ (pm03PCount < 0 ? "" : ", \"pm003_count\":" + String(pm03PCount))
+ (TVOC < 0 ? "" : ", \"tvoc_index\":" + String(TVOC))
+ (NOX < 0 ? "" : ", \"nox_index\":" + String(NOX))
+ ", \"atmp\":" + String(temp)
+ (hum < 0 ? "" : ", \"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("90s to connect", "to Wifi Hotspot", HOTSPOT);
wifiManager.setTimeout(90);
if (!wifiManager.autoConnect((const char * ) HOTSPOT.c_str())) {
updateOLED2("booting into", "offline mode", "");
Serial.println("failed to connect and hit timeout");
delay(6000);
}
}
// 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

@ -1,255 +0,0 @@
/*
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

@ -10,7 +10,7 @@ The codes needs the following libraries installed:
"WifiManager by tzapu, tablatronix" tested with Version 2.0.5-alpha
"Adafruit_ILI9341" tested with Version 1.5.10
"Adafruit GFX library" tested with Version 1.10.12 (often automatically installed with above ILI9341 library)
"ArduinoJSON" by Benoit Blanchon tested with Version 6.18.5
"ArduinoJSON" by Benoit Blanchon tested with Version 5.13.5
Configuration:
Please set in the code below (line 90-) if you want to display the PM2.5 values in US AQI and temperature in F.
@ -21,7 +21,7 @@ If you have any questions please visit our forum at https://forum.airgradient.co
If you are a school or university contact us for a free trial on the AirGradient platform.
https://www.airgradient.com/
MIT License
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
*/
#include <WiFiManager.h>

View File

@ -17,7 +17,7 @@ https://www.airgradient.com/
Kits with all required components are available at https://www.airgradient.com/open-airgradient/shop/
MIT License
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
*/
#include <AirGradient.h>

View File

@ -17,7 +17,7 @@ Kits with all required components are available at https://www.airgradient.com/o
If you have any questions please visit our forum at https://forum.airgradient.com/
MIT License
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
*/
#include <AirGradient.h>

View File

@ -1,9 +1,9 @@
name=AirGradient Air Quality Sensor
version=2.1.0
version=2.4.6
author=AirGradient <support@airgradient.com>
maintainer=AirGradient <support@airgradient.com>
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.
paragraph=Air quality monitoring library supporting the Plantower PMS5003 particle sensor, the Senseair S8 CO2 sensor and the SHT3x/SHT4x 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=*