Skip to content

Instantly share code, notes, and snippets.

@bprobbins
Created September 28, 2020 03:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bprobbins/e9fea2d0a690b948c81466975a2266d2 to your computer and use it in GitHub Desktop.
Save bprobbins/e9fea2d0a690b948c81466975a2266d2 to your computer and use it in GitHub Desktop.
Particle Photon code to display PurpleAir sensor data on Digole SPI OLED
//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