Air

Air Quality detector code

Below is the messy Arduino Yun test code with API keys removed:


/*
Yun Air Quality sensor and SD card datalogger

*/

#include
#include
#include

//temp holder for reading back from progmem
char buffer[60];

#define runtemperature
#define runparticle
////#define uploaddatainparticlefunction
#define uploadnormally

//moved from password.h for testing

const char APIKEY[] PROGMEM=""; // replace your xively api key here
const char FEEDID[] PROGMEM=""; // replace your feed ID
const char USERAGENT[] PROGMEM=""; // user agent is the project name

//temboo stuff
//const char TEMBOO_ACCOUNT[] PROGMEM= ""; // your Temboo account name
//const char TEMBOO_APP_KEY_NAME[] PROGMEM= "AirQuality"; // your Temboo app key name
//const char TEMBOO_APP_KEY[] PROGMEM= ""; // your Temboo app key

/*** SUBSTITUTE YOUR VALUES BELOW: ***/

// Note that for additional security and reusability, you could
// use #define statements to specify these values in a .h file.
//progmem means the static var is kept in the flash and not moved to the ram at runtime, saves memory.
//const char TWITTER_ACCESS_TOKEN[] PROGMEM = "";
//const char TWITTER_ACCESS_TOKEN_SECRET[] PROGMEM = "";
//const char TWITTER_API_KEY[] PROGMEM = "";
//const char TWITTER_API_SECRET[] PROGMEM = "";

//xively stuff
const unsigned long postingInterval = 60000;
unsigned long lastRequest = 0;
String xdataString = "";
double lastmeasuredtemp = 0;
float lastmeasuredtemp2 = 0.0;
unsigned long lastlowpulseoccupancy =0;
float lastratio =0.0;
float lastconcentration=0.0;
unsigned long lastlowpulseoccupancy2 =0;
float lastratio2 =0.0;
float lastconcentration2=0.0;

//int concentration2average=0;
double sampletime = 0.0;
double sampletime2 = 0.0;

//rolling average code
const int numReadings=20;
float readings[numReadings];
int index = 0;
float total = 0;
float average = 0;
float readings2[numReadings];
int index2 = 0;
float total2 = 0;
float average2 = 0;

//Particle PPD42 detctor
//JST Pin 1 (Black Wire) => Arduino GND
//JST Pin 3 (Red wire) => Arduino 5VDC
//JST Pin 4 (Yellow wire) => Arduino Digital Pin 8
int pin = 8; //digital pin for reading 1um particles.
int pin2 =7; //digital pin for reading 2.5um particles
unsigned long duration;
unsigned long starttime;
unsigned long sampletime_ms = 30000; //should be 30000; but 3 secondsish to process other stuff
unsigned long lowpulseoccupancy = 0;
float ratio = 0;
float concentration = 0;
int counter=0;
unsigned long duration2;
unsigned long starttime2;
unsigned long lowpulseoccupancy2 = 0;
float ratio2 = 0;
float concentration2 = 0;
int counter2=0;
int particleupload=0;
int tweetsent=0;

//when using mac usb port
//float voltin = 4400.0; //4.2v
//float calibrate = 1.0;

//when using charger
float voltin = 4600.0; //4.39v
float calibrate = 2.0;

void setup() {

// Initialize the Bridge and the Serial
Bridge.begin();
Serial.begin(9600);
FileSystem.begin();

//set perticle detector pin for inputs
pinMode(8,INPUT);
starttime = millis();

//initialise rolling average array to 0
for (int thisReading =0; thisReading readings[thisReading]=0.0;

// while(!Serial); // wait for Serial port to connect.
Serial.println(F("Filesystem datalogger\n"));

}

void loop () {
//only bother to check temp and upload every so often
if (counter>30)
{
counter=0;
// make a string that start with a timestamp for assembling the data to log:
String dataString;
dataString += getDate();
dataString += ",";
dataString += getTime();
dataString += ",";

#ifdef runtemperature
// read three sensors and append to the string:
for (int analogPin = 0; analogPin double sensor = analogRead(analogPin);
if(analogPin==0) {
sensor=converttotemp(sensor);
lastmeasuredtemp=sensor;
dataString += String(sensor);
}

if (analogPin ==1) {
dataString += ","; // separate the values with a comma

lastmeasuredtemp2 = readTemp2();
dataString += String(lastmeasuredtemp2);

}
}
#endif

#ifdef runparticle
dataString += ",Concentration:,"; // separate the values with a comma
dataString += String(lastconcentration);
dataString += ",Sampletime:,"; // separate the values with a comma
dataString += String(sampletime);
dataString += ",Concentration2:,"; // separate the values with a comma
dataString += String(lastconcentration2);
dataString += ",Sampletime2:,"; // separate the values with a comma
dataString += String(sampletime2);
dataString += String(",");

//read string from flash progmem
//strcpy_P(buffer, Test1);
//dataString += String(buffer);
#endif

// open the file. note that only one file can be open at a time,
// so you have to close this one before opening another.
// The FileSystem card is mounted at the following "/mnt/FileSystema1"
File dataFile = FileSystem.open("/mnt/sda1/arduino/www/datalog.txt", FILE_APPEND);

// if the file is available, write to it:
if (dataFile) {
dataFile.println(dataString);
dataFile.close();
// print to the serial port too:
Serial.println("\n");
Serial.println(dataString);
}
// if the file isn't open, pop up an error:
else {
Serial.println(F("error opening datalog.txt"));
}

writetotweetfile("/mnt/sda1/concentration2.txt", String(lastconcentration2), getlevel(lastconcentration2,20,45));
writetotweetfile("/mnt/sda1/temperature.txt", String(lastmeasuredtemp), "");
writetotweetfile("/mnt/sda1/concentration.txt", String(lastconcentration), getlevel(lastconcentration,2200,3200));

#ifdef uploadnormally
//send data to xively
updateData();
sendData();
#endif

/*if (tweetsent==0)
{
//sendtweet("Oh-ho...I broke again. What happened while I was gone?...anyone there?");
checkdatetime();
tweetsent=1;
}*/

}

counter++;

#ifdef runparticle
//check the particle sensor
particledetect();
particledetect2();

#ifdef uploaddatainparticlefunction
if (particleupload>1)
{
//send data to xively
updateData();
sendData();
particleupload=0;
}
#endif
#endif

// delay(15000);

}

String getlevel(float value, int low , int high)
{
if (value {
return "low";
}
else if (value {
return "medium";
}
else if (value>high)
{
return "high";
}
return "error";
}

void writetotweetfile(char* path, String value, String level)
{
File dataFile = FileSystem.open(path, FILE_WRITE);

// if the file is available, write to it:
if (dataFile) {
if (level=="")
{
dataFile.println(value);
}
else
{
dataFile.println(level + " (" + value + ") ");
}
dataFile.close();
// print to the serial port too:
// Serial.println("\n");
// Serial.println(dataString);
}
// if the file isn't open, pop up an error:
else {
Serial.println(F("error opening file"));
}
}

/*void sendtweet(String message){
Serial.println(F("Sending a tweet"));
Process p;
p.begin(F("/mnt/sda1/yuntwitter.py"));
p.addParameter(message);
p.run();

}*/

/*void checkdatetime()
{
Process date;
int hours, minutes, seconds;

Serial.println(F("Getting the time"));

if (!date.running()) {
date.begin("date");
date.addParameter("+%T");
date.run();
}

//if there's a result from the date process, parse it:
while (date.available()>0) {
// get the result of the date process (should be hh:mm:ss):
String timeString = date.readString();

// find the colons:
int firstColon = timeString.indexOf(":");
int secondColon= timeString.lastIndexOf(":");

// get the substrings for hour, minute second:
String hourString = timeString.substring(0, firstColon);
String minString = timeString.substring(firstColon+1, secondColon);
String secString = timeString.substring(secondColon+1);

sendtweet("I'm Alive! Time is:" + hourString + ":" + minString + ":" + secString);
}
}*/

//particle sensor stuff
void particledetect() {
duration = pulseIn(pin, LOW);
lowpulseoccupancy = lowpulseoccupancy+duration;

if ((millis()-starttime) > sampletime_ms)
{
sampletime=millis()-starttime;
//subtract the last reading
total=total-readings[index];

ratio = lowpulseoccupancy/(sampletime_ms*10.0); // Integer percentage 0=>100
concentration = 1.1*pow(ratio,3)-3.8*pow(ratio,2)+520*ratio+0.62; // using spec sheet curve. Concentration is the no of particles per 0.1 cubic feet.
Serial.print(F("\nParticle: LPO: "));
Serial.print(lowpulseoccupancy);
lastlowpulseoccupancy=lowpulseoccupancy;
Serial.print(",");
Serial.print(F("Ratio: "));
Serial.print(ratio);
lastratio=ratio;
Serial.print(",");
Serial.println(F("Concentration: "));
Serial.print(concentration);

//add data to the averaging array
readings[index]=concentration;
total=total+readings[index];
index++;

//if at end of averaging array
if(index>=numReadings)
index=0;
average=total/numReadings;
lastconcentration=average;

//lastconcentration=concentration;
lowpulseoccupancy = 0;
starttime = millis();

//comment these out unless testing

particleupload=particleupload+1;
// #ifdef uploaddatainparticlefunction
//send data to xively
// updateData();
// sendData();
//#endif
}
}

//particle sensor stuff
//keeping separate function as the pins are processed at different times due to measuring the LPO. Maybe make this more efficent later?
void particledetect2() {
duration2 = pulseIn(pin2, LOW);
lowpulseoccupancy2 = lowpulseoccupancy2+duration2;

if ((millis()-starttime2) > sampletime_ms)
{
sampletime2=millis()-starttime2;
//subtract the last reading
total2=total2-readings2[index2];

ratio2 = lowpulseoccupancy2/(sampletime_ms*10.0); // Integer percentage 0=>100
concentration2 = 1.1*pow(ratio2,3)-3.8*pow(ratio2,2)+520*ratio2+0.62; // using spec sheet curve. Concentration is the no of particles per 0.1 cubic feet.
Serial.print(F("\nParticle: LPO2: "));
Serial.print(lowpulseoccupancy2);
lastlowpulseoccupancy2=lowpulseoccupancy2;
Serial.print(",");
Serial.print(F("Ratio2: "));
Serial.print(ratio2);
lastratio2=ratio2;
Serial.print(",");
Serial.println(F("Concentration2: "));
Serial.print(concentration2);

//add data to the averaging array
readings2[index2]=concentration2;
total2=total2+readings2[index2];
index2++;

//if at end of averaging array
if(index2>=numReadings)
index2=0;
average2=total2/numReadings;
lastconcentration2=average2;

//lastconcentration=concentration;
lowpulseoccupancy2 = 0;
starttime2 = millis();

//comment these out unless testing

particleupload=particleupload+1;

// #ifdef uploaddatainparticlefunction
//send data to xively
// updateData();
// sendData();
//#endif
}
}

//get an average value for the light detection test
float readTemp2() { // Read analog value n times and avarage over those n times
int KelvinC=273;
float _sensorValue = analogRead(1);
Serial.print(F("\nRAW Temp sensor2: "));
Serial.print(_sensorValue);

//Reads the input and converts it to Kelvin degrees
//float _Kelvin = analogRead(1) * 0.004882812 * 100;
float _Kelvin = (((voltin * _sensorValue)/1024.0)/10.0); //input v =4.18 ~ use 4180, use 4450,1024,4420

//Converts Kelvin to Celsius minus 2.5 degrees error
float _Celsius = _Kelvin - 273.15 + calibrate;
Serial.print(F("\nC Temp sensor2: "));
Serial.print(_Celsius);

//float _Kelvin = (((_sensorValue / 1023.0) * 5.0) * 100.0); // convert
//float _Celsius=_Kelvin-KelvinC;
return _Celsius;
}

//the function convert thermistor value to a temperature in degree c
double converttotemp(double RawADC)
{
double Temp;
Temp = log(10000.0*((1024.0/RawADC-1)));
// =log(10000.0/(1024.0/RawADC-1)) // for pull-up configuration
Temp = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * Temp * Temp ))* Temp );
Temp = Temp - 273.15+ 1.5; // Convert Kelvin to Celcius + ajust for calibration
//Temp = (Temp * 9.0)/ 5.0 + 32.0; // Convert Celcius to Fahrenheit
//return (int)Temp;
return Temp;
}

// This function return a string with the time stamp
String getDate() {  
String result;
Process time;
// date is a command line utility to get the date and the time
// in different formats depending on the additional parameter
time.begin("date");
time.addParameter("+%Y%m%d"); // parameters: D for the complete date mm/dd/yy
// T for the time hh:mm:ss
time.run(); // run the command

// read the output of the command
while(time.available()>0) {
char c = time.read();
if(c != '\n')
result += c;
}

return result;
}

// This function return a string with the time stamp
String getTime() {
String result;
Process time;
// date is a command line utility to get the date and the time
// in different formats depending on the additional parameter
time.begin("date");
time.addParameter("+%T"); // parameters: D for the complete date mm/dd/yy
// T for the time hh:mm:ss
time.run(); // run the command

// read the output of the command
while(time.available()>0) {
char c = time.read();
if(c != '\n')
result += c;
}

return result;
}

//xively data test

void updateData() {
// convert the readings to a String to send it:
xdataString = "Temperature,";
//xdataString += random(10) + 20;
xdataString += lastmeasuredtemp;
// add pressure:
xdataString += "\nTemperature2,";
xdataString += lastmeasuredtemp2;

//particle sensor stuff
// add low pulse occupancy:
xdataString += "\nLowPulseOccupancy,";
xdataString += lastlowpulseoccupancy;

// add low pulse occupancy:
xdataString += "\nRatio,";
xdataString += lastratio;

// add low pulse occupancy:
xdataString += "\nConcentration,";
//rather than send just no of particles over 1um, send amount between 1um and 2.5um
xdataString += lastconcentration-lastconcentration2;

// add low pulse occupancy2:
xdataString += "\nConcentration2,";
xdataString += lastconcentration2;

}

// this method makes a HTTP connection to the xively server:
void sendData() {
// form the string for the API header parameter:
String apiString = "X-ApiKey: ";
//apiString += APIKEY;

strcpy_P(buffer, APIKEY);
apiString += buffer;

// form the string for the URL parameter:
String url = "https://api.xively.com/v2/feeds/";
//url += FEEDID;
strcpy_P(buffer, FEEDID);
url += buffer;

url += ".csv";

// Send the HTTP PUT request

// Is better to declare the Process here, so when the
// sendData function finishes the resources are immediately
// released. Declaring it global works too, BTW.
Process xively;
Serial.print("\n\nSending data... ");
xively.begin("curl");
xively.addParameter("-k");
xively.addParameter("--request");
xively.addParameter("PUT");
xively.addParameter("--data");
xively.addParameter(xdataString);
xively.addParameter("--header");
xively.addParameter(apiString);
xively.addParameter(url);
xively.run();
Serial.println("done!");

// If there's incoming data from the net connection,
// send it out the Serial:
while (xively.available()>0) {
char c = xively.read();
Serial.write(c);
}

}

Air Quality detector notes

Click here to view the Arduino code

Click here to view the Python average value calculation code

The basic circuit:

Notes:
-added a mq131 ozone sensor. Click here for more info.
-added a rolling average to smooth out the concentration graph which now shows trends rather than spikes.

-Tried using Temboo to send tweets, but after fixing the issues with the microprocessor running out of memory, I was getting errors back from Twitter.
Anyway, I'm going to try to move the twitter stuff onto the linux side of the board and then use bridge to control it from the arduino side. As per this article:
click
-Success...the above works for sending tweets using very little arduino side memory!

-Using cron to schedule tweet reports from the linino side of the board. The arduino code will write a file with the report details to the sd card, then using pyhon script to pick it up and send a tweet at a scheduled time.

-Enable cron on linino at startup:
/etc/init.d/cron start
/etc/init.d/cron enable
Or I just found the scheduled task menu on the Yun web interface:
Add something like: 20 11,16 * * * /mnt/sda1/cronjob.sh
and then create the cronjob.sh file through ssh with something like:
/mnt/sda1/yuntwitter.py "this is a scheduled tweet test"
And remember to chmod the file to allow it to run.

http://forum.arduino.cc/index.php?topic=200271.0

The data below will only display intermittently as I work on this.

Things planned:
device sends twitter alerts (high/low alerts), and daily summaries.
CO sensor.
Ozone sensor.
Nitrogen dioxide sensor.
Sulfur dioxide sensor.
Add more particle size differentiators.
Weatherproof box.
Solar panel to extend battery life?

-using the nano editor on a mac requires you to do this before it'll work:
http://ricochen.wordpress.com/2011/07/23/mac-os-x-lion-terminal-color-re...

-ordered a MQ131 Ozone sensor.

-I'll post the code shortly, when I get around to tidying it up.

Click here to view the Arduino code

Click here to view the Python average value calculation code