Created
September 28, 2020 03:08
-
-
Save bprobbins/e9fea2d0a690b948c81466975a2266d2 to your computer and use it in GitHub Desktop.
Particle Photon code to display PurpleAir sensor data on Digole SPI OLED
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Spurpleair.ino - code to display pm2_5 air concentration | |
//as reported by one selected purpleair community sensor | |
//Would be nice if possible to average over several sensors and also | |
//if use of Strings could be eliminated in HttpClient library | |
#include <Particle.h> | |
#include <clickButton.h> | |
#include <HttpClient.h> | |
#include <sampledata.h> | |
#include <math.h> | |
//#define _Digole_Serial_UART_ | |
#define _Digole_Serial_SPI_ | |
#include <DigoleGeo.h> | |
int FlashVersion = 5; | |
/******************/ | |
SYSTEM_MODE(MANUAL); | |
volatile bool reconnectInProgress = false; | |
volatile bool bootedNoCloud = false; | |
//for cloud connect retry | |
unsigned long lastTimeRetry = 0; | |
#define TWO_MINUTE_MILLIS (2 * 60 * 1000) | |
#define ONE_MINUTE_MILLIS (1 * 60 * 1000) | |
unsigned long lastTime = 0; | |
unsigned long lastTime2 = 0; | |
unsigned long time_now = 0; | |
//for resyncing Time | |
#define ONE_DAY_MILLIS (24 * 60 * 60 * 1000) | |
volatile int lastsyncTime = 0; | |
volatile int lastResetTime = 0; | |
enum displayStatus{dispBOTH,dispLCD,dispLED,dispNONE}; | |
HttpClient http; | |
http_header_t headers[] = { | |
{"Content-Type, application/json"}, | |
{ "User-agent", "www.purpleair.com HttpClient"}, | |
{ NULL, NULL } | |
}; | |
http_request_t request; | |
http_response_t response; | |
char conditionsRequestPath[20]; | |
/**********SET THESE VARIABLES*****************************************/ | |
int loopInterval = 120000;//reports every 2 minutes, please increase after debug | |
struct sensorInfo { int sensorID = <XXXXX>; char sensorCity[8] = <"7charname">; char sensorLabel[8] = <"7charname>">;}; | |
//easiest way I found to get the id is to click on the sensor | |
//of interest on the purpleair.com map, then note the 5 digit number | |
//after &select= in the resulting browser address bar | |
//Note: To obtain data from multiple sensors, use | |
//http://www.purpleair.com/json?show=<sensorID>|<sensorID>|<sensorID>|... | |
/***uncomment define below if static wifi address desired and set in setup()***/ | |
//#define ENABLE_STATIC_IP | |
IPAddress myAddress(192,168,1,11);//change as needed | |
IPAddress netmask(255,255,255,0); | |
IPAddress gateway(192,168,1,1); | |
IPAddress dns(192,168,1,1); | |
char IPasStr[16]; | |
//NOTE: one digole Serial Color OLED 160 X 120 1.8" V1 seems more stable at | |
//5v Photon VIN than at 3.3v from Photon, although another identical | |
//display doesn't seem to mind 3.3v | |
//PHOTON digole(A3, A5, A2) (CLK,DATA,SS) | |
DigoleSerialDisp digole(A3, A5, A2); //Hardware SPI setup | |
//uncomment one define below NONE, LRAPA, or AQandU | |
//#define conversion_NONE | |
#define conversion_LRAPA | |
//#define conversion_AQandU | |
//set as 0,1,2,or 3 for startup display desired | |
//dispBOTH,dispLCD,dispLED,dispNONE | |
int curdisplayStatus = 2; //startup LED display only | |
/********************************************************************/ | |
char conversionStr[7]; | |
sensorInfo sensorDATA; | |
char *sensorLabel; | |
char sensorStr[40]; | |
char *pm2_5ValueStr; | |
char *lastseenStr; | |
int lastseenInt; | |
double pm2_5Double; | |
char pm2_5[60] = "AQI not yet calculated"; | |
int rgbBrightness = 25; | |
enum aqiStatus{GREEN,YELLOW,ORANGE,RED,PURPLE}; | |
int state; | |
// below makes source filename accesible programmatically | |
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) | |
char gDeviceInfo[240]; | |
#define redPin D2 | |
#define greenPin D3 | |
#define bluePin D4 | |
#define selectPin D6 | |
//Colours defined as 8-bit value RRRG GGBB | |
#define VGA_BLACK 0x00 | |
#define VGA_WHITE 0x01 | |
#define VGA_BLUE 0x03 | |
#define VGA_DARKRED 0x44 | |
#define VGA_PURPLE 0x61 | |
#define VGA_RED 0x60 | |
#define VGA_GREEN 0x1C | |
#define VGA_LIME 0x14 | |
#define VGA_YELLOW 0xFC | |
#define VGA_ORANGE 0xCC | |
ClickButton selectBTN(selectPin, LOW); | |
int selectBTNRes = 0; | |
int max_x, max_y; | |
#define myOrange 0x00ff3c00 //to distinguish better from yellow | |
LEDStatus status1(RGB_COLOR_GREEN, LED_PATTERN_FADE, 4000 /* 1 second */, LED_PRIORITY_IMPORTANT); | |
LEDStatus status2(RGB_COLOR_YELLOW, LED_PATTERN_FADE, 3000 /* 1 second */, LED_PRIORITY_IMPORTANT); | |
LEDStatus status3(myOrange, LED_PATTERN_FADE, 2000 /* 1 second */, LED_PRIORITY_IMPORTANT); | |
LEDStatus status4(RGB_COLOR_RED, LED_PATTERN_FADE, 300 /* 1 second */, LED_PRIORITY_IMPORTANT); | |
LEDStatus status5(RGB_COLOR_MAGENTA, LED_PATTERN_FADE, 100 /* 1 second */, LED_PRIORITY_IMPORTANT); | |
//USAGE: bool daylightSavings = IsDST(Time.day(), Time.month(), Time.weekday()); | |
// Time.zone(daylightSavings? -7 : -8); //-7 in summer GMT-7 DST; -8 in winter GMT-8 and is PST not DST | |
bool IsDST(int dayOfMonth, int month, int dayOfWeek) { | |
// from @BulldogLowell on Particle forum | |
if (month < 3 || month > 11) { | |
return false; | |
} | |
if (month > 3 && month < 11) { | |
return true; | |
} | |
int previousSunday = dayOfMonth - (dayOfWeek - 1); | |
if (month == 3) { | |
return previousSunday >= 8; | |
} | |
return previousSunday <= 0; | |
}//bool IsDST(int dayOfMonth, int month, int dayOfWeek) | |
//Below two functions from: | |
//https://docs.google.com/document/d/15ijz94dXJ-YAZLi9iZ_RaBwrZ4KtYeCy08goGBwnbCU/edit# | |
double mycalcAQI(double Cp, double Ih, double Il, double BPh, double BPl) { | |
double a = (Ih - Il); | |
double b = (BPh - BPl); | |
double c = (Cp - BPl); | |
return round((a/b) * c + Il); | |
} | |
double myaqiFromPM(double pm) { | |
/* | |
Good 0 - 50 0.0 - 15.0 0.0 – 12.0 GREEN | |
Moderate 51 - 100 >15.0 - 40 12.1 – 35.4 YELLOW | |
Unhealthy for Sensitive Groups 101 – 150 >40 – 65 35.5 – 55.4 ORANGE | |
Unhealthy 151 – 200 > 65 – 150 55.5 – 150.4 RED | |
Very Unhealthy 201 – 300 > 150 – 250 150.5 – 250.4 PURPLE | |
Hazardous 301 – 400 > 250 – 350 250.5 – 350.4 | |
Hazardous 401 – 500 > 350 – 500 350.5 – 500 | |
EPA AQI values | |
Good (0–50, green) | |
Moderate (51–100, yellow) | |
Unhealthy for Sensitive Groups (101–150, orange) | |
Unhealthy (151–200, red) | |
Very Unhealthy (201–300, purple) | |
Hazardous (301–500, maroon) | |
*/ | |
if (pm > 350.5) { | |
return mycalcAQI(pm, 500, 401, 500, 350.5); | |
} else if (pm > 250.5) { | |
return mycalcAQI(pm, 400, 301, 350.4, 250.5); | |
} else if (pm > 150.5) { | |
return mycalcAQI(pm, 300, 201, 250.4, 150.5); | |
} else if (pm > 55.5) { | |
return mycalcAQI(pm, 200, 151, 150.4, 55.5); | |
} else if (pm > 35.5) { | |
return mycalcAQI(pm, 150, 101, 55.4, 35.5); | |
} else if (pm > 12.1) { | |
return mycalcAQI(pm, 100, 51, 35.4, 12.1); | |
} else if (pm >= 0) { | |
return mycalcAQI(pm, 50, 0, 12, 0); | |
} else { | |
// return "-" ; //undefined; | |
} | |
}//double myaqiFromPM(double pm) | |
void displayMain(double pm2_5Double, int curdisplayStatus) { | |
char pm2_5Str1[20]; | |
char pm2_5Str2[20]; | |
char pm2_5Str3[20]; | |
digole.clearScreen(); | |
//Now use onboard and external RGB LED to display pm2_5 value | |
if (pm2_5Double <= 50.0) { | |
snprintf(pm2_5Str1, sizeof(pm2_5Str1), "AQI %.1f",pm2_5Double); | |
snprintf(pm2_5Str2, sizeof(pm2_5Str2), "%s","GOOD"); | |
snprintf(pm2_5Str3, sizeof(pm2_5Str3), "%s",""); | |
status1.setActive(true); | |
digole.setTrueColor(0,255,0); | |
state = GREEN; | |
} | |
if ((pm2_5Double >=51.0) && (pm2_5Double <= 100.0)) { | |
snprintf(pm2_5Str1, sizeof(pm2_5Str1), "AQI %.1f",pm2_5Double); | |
snprintf(pm2_5Str2, sizeof(pm2_5Str2), "%s","MODERATE"); | |
snprintf(pm2_5Str3, sizeof(pm2_5Str3), "%s",""); | |
status2.setActive(true); | |
digole.setTrueColor(255,255,0); | |
state = YELLOW; | |
} | |
if ((pm2_5Double >=101) && (pm2_5Double <= 150)) { | |
snprintf(pm2_5Str1, sizeof(pm2_5Str1), "AQI %.1f",pm2_5Double); | |
snprintf(pm2_5Str2, sizeof(pm2_5Str2), "%s","UNHEALTHY FOR"); | |
snprintf(pm2_5Str3, sizeof(pm2_5Str3), "%s","SENSITIVE PEOPLE"); | |
status3.setActive(true); | |
digole.setTrueColor(255,165,0); | |
state = ORANGE; | |
} | |
if ((pm2_5Double >=151) && ( pm2_5Double <=200) ) { | |
snprintf(pm2_5Str1, sizeof(pm2_5Str1), "AQI %.1f",pm2_5Double); | |
snprintf(pm2_5Str2, sizeof(pm2_5Str2), "%s","UNHEALTHY"); | |
snprintf(pm2_5Str3, sizeof(pm2_5Str3), "%s",""); | |
status4.setActive(true); | |
digole.setTrueColor(255,0,0); | |
state = RED; | |
} | |
if ((pm2_5Double >=201) ) { | |
snprintf(pm2_5Str1, sizeof(pm2_5Str1), "AQI %.1f",pm2_5Double); | |
snprintf(pm2_5Str2, sizeof(pm2_5Str2), "%s","VERY UNHEALTHY"); | |
snprintf(pm2_5Str3, sizeof(pm2_5Str3), "%s"," "); | |
status5.setActive(true); | |
digole.setTrueColor(255,0,255); | |
state = PURPLE; | |
} | |
snprintf(pm2_5, sizeof(pm2_5), "%s %s %s", pm2_5Str1, | |
pm2_5Str2, pm2_5Str3); | |
Serial.println(pm2_5); | |
Serial.println(); | |
if ( (curdisplayStatus == dispBOTH) || (curdisplayStatus == dispLCD) ) { | |
digole.setFont(51); | |
digole.drawStr(0,0,pm2_5Str1); | |
digole.setFont(18); | |
digole.drawStr(1,2, pm2_5Str2); | |
digole.drawStr(1,3, pm2_5Str3); | |
int ellipse_x = 40; | |
int ellipse_y = 40; | |
digole.drawFilledEllipse(80, 93,30,30);//head | |
digole.setColor(VGA_WHITE); | |
digole.drawFilledEllipse(72, 88, 6,4);//r eye //95 | |
digole.drawFilledEllipse(92, 88, 6,4);//l eye | |
digole.setColor(VGA_BLACK); | |
digole.drawFilledEllipse(74,88, 4,2);//r eye | |
digole.drawFilledEllipse(94, 88, 4, 2);//l eye | |
digole.setColor(VGA_WHITE); | |
if (state == GREEN) { | |
digole.drawStr(13,4, ">"); | |
} | |
if (state == YELLOW) { | |
digole.drawStr(13,5, ">"); | |
} | |
if (state == ORANGE) { | |
digole.drawStr(13,6, ">"); | |
} | |
if (state == RED) { | |
digole.drawStr(13,7, ">"); | |
} | |
if (state == PURPLE) { | |
digole.drawStr(13,8, ">"); | |
} | |
digole.setFont(10); | |
digole.drawStr(0,8, sensorDATA.sensorCity); | |
digole.drawStr(0,9, sensorDATA.sensorLabel); | |
//for mouth expression | |
digole.setTrueColor(139,0,0); | |
switch (state) { | |
case GREEN: //expression very happy green | |
digole.setRotation(2); //same as digole.setRot180(); | |
digole.drawArc(80,32,20, -60,60,2); | |
break; | |
case YELLOW: //expression mildy unhappy yellow | |
digole.setRotation(2); //same as digole.setRot180(); | |
digole.drawArc(80,32,20,-15,15,2); //yellow | |
break; | |
case ORANGE: //expression unhappy orange | |
digole.drawArc(80,130,20,-35,35,2); | |
break; | |
case RED: //expression more unhappy red | |
digole.drawArc(80,130,20,-45,45,2); | |
break; | |
case PURPLE: //expression even more unhappy purple | |
digole.drawArc(80,130,20,-60, 60,2); | |
break; | |
}//switch | |
digole.setRotation(0); //same as digole.undoRotation(); | |
//display side legend | |
digole.setFont(18); | |
digole.setTrueColor(0,255,0); | |
digole.drawStr(16,4, "0"); | |
digole.setTrueColor(255,255,0); | |
digole.drawStr(15,5, "50"); | |
digole.setTrueColor(255,165,0); | |
digole.drawStr(14,6, "100"); | |
digole.setTrueColor(255,0,0); | |
digole.drawStr(14,7, "150"); | |
digole.setTrueColor(255,0,255); | |
digole.drawStr(14,8, "200"); | |
}//if ( (curdisplayStatus == dispBOTH) || (curdisplayStatus == dispLCD) ) | |
}//displayMain(double pm2_5Double, int curdisplayStatus) | |
int wdHandler() { | |
System.reset(); | |
return 1; | |
}//wdHandler() | |
// declare a global watchdog instance | |
ApplicationWatchdog wd(30000, wdHandler, 1536); | |
void setup() { | |
Serial.begin(115200); | |
Particle.connect(); | |
waitFor(Particle.connected, 30000); | |
Particle.process(); | |
if (Particle.connected()) { | |
while (millis()-lastTime < 500) {Particle.process();} | |
Particle.variable("pm2_5", pm2_5); | |
Particle.variable("deviceInfo",gDeviceInfo); | |
Particle.variable("conversionType",conversionStr); | |
Particle.variable("sensorStr", sensorStr); | |
while (millis()-lastTime < 500) {Particle.process();} | |
#ifdef ENABLE_STATIC_IP | |
uint8_t myFirstAddrByte = myAddress[0]; | |
uint8_t mySecondAddrByte = myAddress[1]; | |
uint8_t myThirdAddrByte = myAddress[2]; | |
uint8_t myLastAddrByte = myAddress[3]; | |
WiFi.setStaticIP(myAddress, netmask, gateway, dns); | |
//use the configured IP | |
WiFi.useStaticIP(); | |
#endif //ENABLE_STATIC_IP | |
lastTime = millis(); | |
while (millis()-lastTime < 500) {Particle.process();} | |
bool daylightSavings = IsDST(Time.day(), Time.month(), | |
Time.weekday()); | |
Time.zone(daylightSavings? -7 : -8); //-7 in summer GMT-7 DST; -8 in winter GMT-8 not DST | |
lastTime = millis(); | |
while (millis()-lastTime < 500) {Particle.process();} | |
lastResetTime = Time.now(); | |
lastsyncTime = Time.now(); | |
snprintf(IPasStr, sizeof(IPasStr),"%s", | |
WiFi.localIP().toString().c_str()); | |
snprintf(gDeviceInfo, sizeof(gDeviceInfo) | |
,"{\"Application\":\"%s\",\"Date\":\"%s\"," | |
"\"Time\":\"%s\",\"System_firmware\":\"%s\"," | |
"\"SSID\":\"%s\",\"IP\":\"%s\",\"RSSI\":%i," | |
"\"version\":%i}" | |
,__FILENAME__ | |
,__DATE__ | |
,(const char*)Time.timeStr() | |
,(const char*)System.version() // cast required for String | |
,(const char*)WiFi.SSID() // cast not required but always safe when in doubt if String or char* | |
,IPasStr | |
,(int8_t) WiFi.RSSI() | |
,FlashVersion | |
); | |
} //If (Particle.connected()) | |
else { //is not cloud connected | |
bootedNoCloud = true; | |
Particle.disconnect(); //make sure previous attempt to connect cleanly aborted | |
lastTime = millis(); | |
while (millis()-lastTime < 100) { Particle.process();} | |
}//else particle cloud not connected | |
digole.begin(); | |
digole.displayConfig(0); | |
digole.displayStartScreen(0); | |
lastTime = millis(); | |
while (millis()-lastTime < 500) {Particle.process();} | |
Particle.process(); | |
digole.clearScreen(); | |
lastTime = millis(); | |
while (millis()-lastTime < 500) {Particle.process();} | |
digole.disableCursor(); | |
digole.setBackLight(100); | |
pinMode(redPin, OUTPUT); | |
pinMode(greenPin, OUTPUT); | |
pinMode(bluePin, OUTPUT); | |
pinMode(selectPin, INPUT_PULLUP); | |
request.hostname = "www.purpleair.com"; | |
request.port = 80; | |
snprintf(conditionsRequestPath, sizeof(conditionsRequestPath), | |
"/json?show=%d", sensorDATA.sensorID); | |
request.path = conditionsRequestPath; | |
lastTime = millis(); | |
while (millis()-lastTime < 500) {Particle.process();} | |
lastResetTime = Time.now(); | |
lastsyncTime = Time.now(); | |
//mirror onboard rgb led to external rgb led | |
RGB.mirrorTo(redPin, greenPin, bluePin); | |
RGB.brightness(rgbBrightness); | |
// Setup button timers (all in milliseconds / ms) | |
selectBTN.debounceTime = 20;//30; // Debounce timer in ms | |
selectBTN.multiclickTime = 500; // Time limit for multi clicks | |
selectBTN.longClickTime = 1000; //time until "held-down clicks" register | |
lastTime = millis(); | |
while (millis()-lastTime < 500) {Particle.process();} | |
Particle.process(); | |
}//setup | |
void loop() | |
{ | |
Particle.process(); | |
//PET WATCHDOG | |
wd.checkin(); | |
Particle.process(); | |
Particle.process(); | |
// CLOUD RECONNECT CODE | |
//allows remote & button press response & reconnect (version 0.6.3) | |
//Also intended to attempt later cloud connection if booted when | |
//particle cloud is not available at initial boot | |
//Only tries cloud reconnect every 2 minutes until connected | |
unsigned long nowforRetry = millis(); //update for retry cloud connect | |
//only retry connect every 1 minute | |
if ((nowforRetry - lastTimeRetry) >= ONE_MINUTE_MILLIS) { | |
if(!Particle.connected()) { | |
lastTimeRetry = nowforRetry; | |
if (!reconnectInProgress) { | |
Particle.disconnect(); | |
lastTime = millis(); | |
while (millis()-lastTime < 100) { Particle.process();} | |
Particle.connect(); | |
reconnectInProgress = true; | |
Particle.process(); | |
if (Particle.connected) { | |
reconnectInProgress = false; | |
if (bootedNoCloud) { | |
//vars | |
while (millis()-lastTime < 500) {Particle.process();} | |
Particle.variable("pm2_5", pm2_5); | |
Particle.variable("deviceInfo",gDeviceInfo); | |
Particle.variable("conversionType",conversionStr); | |
Particle.variable("sensorStr", sensorStr); | |
while (millis()-lastTime < 500) {Particle.process();} | |
#ifdef ENABLE_STATIC_IP | |
uint8_t myFirstAddrByte = myAddress[0]; | |
uint8_t mySecondAddrByte = myAddress[1]; | |
uint8_t myThirdAddrByte = myAddress[2]; | |
uint8_t myLastAddrByte = myAddress[3]; | |
WiFi.setStaticIP(myAddress, netmask, gateway, dns); | |
WiFi.useStaticIP(); | |
#endif //ENABLE_STATIC_IP | |
snprintf(IPasStr, sizeof(IPasStr),"%s", | |
WiFi.localIP().toString().c_str()); | |
snprintf(gDeviceInfo, sizeof(gDeviceInfo) | |
,"{\"Application\":\"%s\",\"Date\":\"%s\"," | |
"\"Time\":\"%s\",\"System_firmware\":\"%s\"," | |
"\"SSID\":\"%s\",\"IP\":\"%s\",\"RSSI\":%i," | |
"\"version\":%i}" | |
,__FILENAME__ | |
,__DATE__ | |
,(const char*)Time.timeStr() | |
// cast required for String | |
,(const char*)System.version() | |
// cast not required but always safe when | |
//in doubt if String or char* | |
,(const char*)WiFi.SSID() | |
,IPasStr | |
,(int8_t) WiFi.RSSI() | |
,FlashVersion | |
); | |
Particle.variable("deviceInfo",gDeviceInfo); | |
bootedNoCloud = false; | |
}//if (bootedNoCloud) | |
}//if (Particle.connected) | |
}//if (!reconnectInProgress) | |
}//if(!Particle.connected()) | |
}//if ((nowforRetry - lastTimeRetry) >= ONE_MINUTE_MILLIS) | |
Particle.process(); | |
time_now = millis(); | |
http.get(request, response, headers); | |
Particle.process(); | |
if (response.status != 200) return; else { | |
Particle.process(); | |
Serial.print("Current Time: "); | |
Serial.println(Time.timeStr()); | |
char thebodyStr[3080]; | |
response.body.toCharArray(thebodyStr, 3077); | |
response.body = ""; | |
strtok(thebodyStr, ","); //parse the string at each comma. | |
strtok(NULL, ","); | |
strtok(NULL, ","); | |
strtok(NULL, ","); | |
sensorLabel = strtok(NULL, ","); | |
strtok(NULL, ","); | |
strtok(NULL, ","); | |
strtok(NULL, ","); | |
strtok(NULL, ","); | |
strtok(NULL, ","); | |
strtok(NULL, ","); | |
strtok(NULL, ","); | |
pm2_5ValueStr=strtok(NULL, ","); | |
lastseenStr=strtok(NULL,","); | |
Serial.print(lastseenStr); | |
Serial.print(" "); | |
strtok(lastseenStr,":"); | |
lastseenStr = strtok(NULL,":"); | |
lastseenInt = atoi(lastseenStr); | |
Serial.print(Time.format(lastseenInt, TIME_FORMAT_DEFAULT)); | |
Serial.println(); | |
strtok(sensorLabel,":"); | |
sensorLabel = strtok(NULL,":"); | |
Serial.println(sensorLabel); | |
char abuffer[100]; | |
int parsed = sscanf(pm2_5ValueStr, | |
"\"PM2_5Value\":\"%99[^\"]\",", abuffer); | |
snprintf(sensorStr,sizeof(sensorStr)-1, "%s", sensorLabel); | |
pm2_5Double = atof(abuffer); | |
Serial.print("pm2_5Double b4 conversion "); | |
Serial.println(pm2_5Double); | |
Serial.println(); | |
#ifdef conversion_LRAPA | |
//LRAPA "correction" | |
//0 - 65 µg/m³ range: | |
//LRAPA PM2.5 (µg/m³) = 0.5 x PA (PM2.5 CF=ATM) – 0.66 | |
if (pm2_5Double <=65.0) { | |
pm2_5Double = 0.5 * pm2_5Double - 0.66; | |
snprintf(conversionStr,sizeof(conversionStr), "%s", "LRAPA"); | |
} | |
#endif | |
#ifdef conversion_AQandU | |
//AQandU correction | |
//PM2.5 (µg/m³) = 0.778 x PA + 2.65 | |
if (pm2_5Double <=65.0) { | |
pm2_5Double = 0.778 * pm2_5Double + 2.65; | |
snprintf(conversionStr,sizeof(conversionStr), "%s", | |
"AQandU"); | |
} | |
#endif | |
#ifdef conversion_NONE | |
snprintf(conversionStr,sizeof(conversionStr), "%s", "NONE"); | |
#endif | |
//Now calc EPA AQI | |
pm2_5Double = myaqiFromPM(pm2_5Double); | |
Serial.println(); | |
Serial.print("AQI "); | |
Serial.println(pm2_5Double); | |
Serial.print("pm2_5 string = "); Serial.println(pm2_5); | |
snprintf(pm2_5, sizeof(pm2_5), "AQI %.1f", pm2_5Double); | |
Serial.println(pm2_5); | |
Serial.println(); | |
displayMain(pm2_5Double, curdisplayStatus); | |
}//if (response.status != 200) return; else | |
while (millis() < time_now + loopInterval) { | |
Particle.process(); | |
//BUTTON STUFF | |
selectBTN.Update(); | |
if (selectBTN.clicks != 0) selectBTNRes = selectBTN.clicks; | |
//brief switch to ground toggles select pin | |
//using clickButton library | |
if (selectBTNRes ==1) { //btn just pressed | |
if (curdisplayStatus < 3) {curdisplayStatus++;} | |
else | |
{ | |
curdisplayStatus = 0; | |
} | |
Serial.print("curdisplayStatusA = "); | |
Serial.println(curdisplayStatus); | |
switch (curdisplayStatus) { | |
case dispBOTH: //use both displays 0 | |
RGB.control(false); | |
RGB.brightness(rgbBrightness); | |
digole.setBackLight(100); | |
digole.clearScreen(); | |
displayMain(pm2_5Double, curdisplayStatus); | |
break; | |
case dispLCD: //LCD only 1 | |
RGB.control(true); | |
RGB.brightness(0); | |
digole.setBackLight(100); | |
digole.clearScreen(); | |
displayMain(pm2_5Double, curdisplayStatus); | |
break; | |
case dispLED: //LED only 2 (default) | |
RGB.control(false); | |
RGB.brightness(rgbBrightness); | |
digole.setBackLight(0); | |
digole.clearScreen(); | |
break; | |
case dispNONE: //NONE 3 | |
RGB.control(true); | |
RGB.brightness(rgbBrightness); | |
digole.setBackLight(0); | |
digole.clearScreen(); | |
break; | |
}//switch (curdisplayStatus) | |
selectBTNRes = 0; // btn press handled | |
Serial.print("curdisplayStatusB = "); | |
Serial.println(curdisplayStatus); | |
}//if (selectBTNRes ==1) | |
}//while (millis() < time_now + loopInterval) | |
unsigned long nowforSynch = millis(); //update for syncing Time daily | |
if ((nowforSynch - lastTime2) >= ONE_DAY_MILLIS) { | |
if (Particle.connected()) { | |
// Request daily time synchronization from the Particle Cloud | |
lastTime2 = nowforSynch; | |
Particle.syncTime(); | |
//??wait 1 second for time syncing | |
lastTime = millis(); | |
while ((Particle.syncTimePending()) || (millis()-lastTime < 1000)) | |
{ Particle.process();} | |
lastsyncTime = Time.now(); | |
lastTime = millis(); | |
while (millis()-lastTime < 500) {Particle.process();} | |
bool daylightSavings = IsDST(Time.day(), Time.month(), Time.weekday()); | |
//NorCal -7 in summer GMT-7 DST; -8 in winter GMT-8 not DST (PDT) | |
Time.zone(daylightSavings? -7 : -8); | |
}//if ((Particle.connected()) | |
}//if ((nowforSynch - lastTime2) >= ONE_DAY_MILLIS) | |
}//loop |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment