Using PurpleAir air quality data to blink a photon LED

Hello! I’m trying to get data on air quality in my town from PurpleAir (they have a standard API) and get my Photon to blink (red, yellow, green) depending on the AQI 2.5 reading of the sensor.
Is there any similar project I could use/modify to get this off the ground? Thank you so much!

@andreizimin
Here’s a simple (in progress) bit of code to query a particular purpleair sensor for pm2_5 particles using the HttpClient library. We’re lucky, inasmuch as purpleair.com doesn’t need https, but the HttpClient library uses Strings and I’d like to change that soon out of concern for memory fragmentation problems, so I haven’t gotten around to translate the obtained value into led colors. Also, you need to increase the buffer size to somewhere’s around 3072 bytes to avoid download errors (see HttpClient.h line 65 and change to char buffer[3072]; ) Please report back if you’re still working on this and get the led signalling sorted out as I am short of time lately.


#include "HttpClient.h"

unsigned long lastTime = 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;

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;

int loopInterval = 10000; //reports every 10 seconds
int purpleairSensor_ID = XXXXX; //replace int with sensorID you're interested in
char *purpleairSensorLabel;
char *pm2_5ValueStr;
char *lastseenStr;
int lastseenInt;
double pm2_5Double;

//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)


void setup()
{
	Serial.begin(115200);

    request.hostname = "www.purpleair.com";
    request.port = 80;
    char conditionsRequestPath[100];
    sprintf(conditionsRequestPath, "/json?show=%d", purpleairSensor_ID);

    request.path = conditionsRequestPath;
  
	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();
    
}//setup

void loop()
{
	time_now = millis();
	
    http.get(request, response, headers);
    Serial.print("Current Time: "); 
	Serial.println(Time.timeStr());    

	char thebodyStr[3080];
	response.body.toCharArray(thebodyStr, 3077);

	strtok(thebodyStr, ",");      //parse the string at each comma.
	strtok(NULL, ",");            
	strtok(NULL, ",");            
	strtok(NULL, ",");            
	purpleairSensorLabel = 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("  ");	 //(sensor seems to update every 2 minutes) 
	strtok(lastseenStr,":");
	lastseenStr = strtok(NULL,":");
	lastseenInt = atoi(lastseenStr);
	Serial.print(Time.format(lastseenInt, TIME_FORMAT_DEFAULT));
	Serial.println();
	Serial.println(purpleairSensorLabel);
	Serial.println(pm2_5ValueStr); 

    char abuffer[100];
    int parsed = sscanf(pm2_5ValueStr, "\"PM2_5Value\":\"%99[^\"]\",", abuffer);
    pm2_5Double = atof(abuffer);
    Serial.print("pm2_5Double = "); 
    Serial.println(pm2_5Double);
    Serial.println();
  	
	while (millis() < time_now + loopInterval) { Particle.process(); }    	
 
}//loop

1 Like

Just had to get the led indicator working due to our current awful air quality.

//purpleair.ino, very simple 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 "HttpClient.h"

unsigned long lastTime = 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;

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;
//!!!!!!
int loopInterval = 10000; //reports every 10 seconds, please increase after debug
int purpleairSensor_ID = XXXXX; //replace with sensorID you're interested in
//
char *purpleairSensorLabel;
char *pm2_5ValueStr;
char *lastseenStr;
int lastseenInt;
double pm2_5Double;
#define redPin D0
#define greenPin D1
#define bluePin D2
#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, 4000 /* 1 second */, LED_PRIORITY_IMPORTANT);
LEDStatus status3(myOrange, LED_PATTERN_FADE, 4000 /* 1 second */, LED_PRIORITY_IMPORTANT);
LEDStatus status4(RGB_COLOR_RED, LED_PATTERN_FADE, 4000 /* 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)


void setup()
{
	Serial.begin(115200);

	pinMode(redPin, OUTPUT);
	pinMode(greenPin, OUTPUT);
	pinMode(bluePin, OUTPUT);
	
    request.hostname = "www.purpleair.com";
    request.port = 80;
    char conditionsRequestPath[100];
    sprintf(conditionsRequestPath, "/json?show=%d", purpleairSensor_ID);

    request.path = conditionsRequestPath;

	//sync time to Pacific time
	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();
	
	//mirror onboard rgb led to external rgb led
	RGB.mirrorTo(redPin, greenPin, bluePin);
 
}//setup

void loop()
{
	time_now = millis();
	
    http.get(request, response, headers);
    Serial.print("Current Time: "); 
	Serial.println(Time.timeStr());    

	char thebodyStr[3080];
	response.body.toCharArray(thebodyStr, 3077);

	strtok(thebodyStr, ",");      //parse the string at each comma.
	strtok(NULL, ",");            
	strtok(NULL, ",");            
	strtok(NULL, ",");            
	purpleairSensorLabel = 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("  ");	 //(sensor seems to update every 2 minutes) 
	strtok(lastseenStr,":");
	lastseenStr = strtok(NULL,":");
	lastseenInt = atoi(lastseenStr);
	Serial.print(Time.format(lastseenInt, TIME_FORMAT_DEFAULT));
	Serial.println();
	Serial.println(purpleairSensorLabel);
	Serial.println(pm2_5ValueStr); 

    char abuffer[100];
    int parsed = sscanf(pm2_5ValueStr, "\"PM2_5Value\":\"%99[^\"]\",", abuffer);
    pm2_5Double = atof(abuffer);
    Serial.print("pm2_5Double = "); 
    Serial.println(pm2_5Double);

	//for debug only
	//pm2_5Double = 60.0;//2.0; 13.0;  40.0;  60.0

	//Now use onboard and external RGB LED to display pm2_5 value
	if (pm2_5Double <= 12.0) {
		Serial.println("AQ is GOOD (GREEN)");   
		status1.setActive(true);	
		}
	if ((pm2_5Double >12.0) && (pm2_5Double <= 35.4)) {
		Serial.println("AQ is MODERATE (YELLOW)");   
		status2.setActive(true);		
		}
	if ((pm2_5Double >35.4) && (pm2_5Double <= 55.4)) {
		Serial.println("AQ is UNHEALTHY FOR SENSITIVE PEOPLE (ORANGE)"); 		
		status3.setActive(true);		
		}
	if ((pm2_5Double >55.4) ) {
		Serial.println("AQ is UNHEALTHY (RED)");   		
		status4.setActive(true);		
		}

    Serial.println();	   	
	while (millis() < time_now + loopInterval) { Particle.process(); }    	
 
}//loop

EDIT: seems to work well on photon OS 1.4.4 but smts flakey on argon 1.4.4, dunno

1 Like