2009-09-27

Feeding data to Pachube from Arduino

After having done the Arduino Home monitor I wanted to put data on a webpage and do some graphics. Alex suggested putting it at Pachube, as he also has some of his house data there and he had an invitation "to offer". (Thanks Alex!).
Patcube can be feed with data by your application from time to time or Pachube's server requests data from time to time to your "Web-enabled-application". Since I wanted to keep my arduino serving my "human-readable-webpages", I thought of creating a "virtual file" that would Pachube would fetch to get the data. On their Tutorial pages, Pachube has a sample application for feeding data from an Arduino to Pachube. The Tutorial example posts data to the Pachube server from time to time. Since I still wanted to keep my normal webserver and check the temperature online I decided to go the other way and create a comma separated values (csv) page that Pachube would fetch.

This was a bit more difficult than I expected. Before to any access to port 80, the Arduino would reply with my info webpage, but now I had to determine which webpage was accessed. I had to parse the GET command from the browser/server and provide the requested page.
After implementing this "virtual file system" I had another hurdle to overcome, Pachube was always complaining that my CSV page didn't have the correct format. After reading the quickstart a few more times and the http/html specification I added the correct header and an empty line at the end, and it finally Pachube started collecting data correctly.

Finally I wanted to increase the precision of the analog to digital converter (ADC) of the Arduino, specially when reading the LM35 temperature. The LM35 outputs 10mV per degree Centigrade so it will output a full volt at 100 degrees Centigrade, in order to increase the resolution of the ADC the reference voltage must be lowered to close the value in range.
The Arduino provides the option of using either an internal reference (of 1.1V), an external reference (selected externaly) or the default supply voltage (5V in the Arduino Duemilanove). The internal reference is 1.1V so it would beter to use it for the LM35, but for the Light sensor the best is the 5V reference.
So I needed to change reference between conversions, there are a couple of problems when trying to change reference. When default is selected there is a low impedance connection between the reference and the ADC, so when the reference is changed the ATMega168 datasheet says that at least one measurement will be incorrect (I do two for safety). The problem with the internal reference is that it has a high impedance connection to the ADC, so any change takes more time to charge referece input of the ADC, the problem is that the datasheet does not say how much... so I change the reference to internal first and wait for the next webpage request and do the temperature conversion first, this increases the precision of the measurements if the webpage requests are not too frequent.

/*
* Web Server
* based on the original webserver example.
* processes the GET command from the http client.
* supports two pages, one readable for humans /info.html other in CSV for computers /csv.html
*/
/*
*==== Typical HTTP request ====
*GET / HTTP/1.0[CRLF]
*Host: www.google.com[CRLF]
*Connection: close[CRLF]
*User-Agent: Web-sniffer/1.0.29 (+http://web-sniffer.net/)[CRLF]
*Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7[CRLF]
*Cache-Control: no[CRLF]
*Accept-Language: de,en;q=0.7,en-us;q=0.3[CRLF]
*Referer: http://web-sniffer.net/[CRLF]
*[CRLF]
*==== Typical HTTP request ====
*/

#include <ethernet.h>
#define DEBUG

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 2, 253 };
//These were not needed, i.e. without them the ethernet shield works ok with my router
//byte gateway[] = { 192, 168, 2, 254 };
//byte subnet[] = { 255, 255, 255, 0 };

// handle requests at port 80
Server server(80);

// values of temperature and light
long tmprtr,lght;

char cmmnd; /* received command = {G|P} for GET or PUT */
char args[20]; /* requested file,arguments */
char host[20]; /* host ip address*/

void setup()
{
Ethernet.begin(mac, ip);
server.begin();
#ifdef DEBUG
Serial.begin(9600); // serial port for debug
#endif
}

//
// Get and convert analog input values
//
void get_values(void)
{
tmprtr=((long)analogRead(0)*1100)/1024;
analogReference(DEFAULT);
analogRead(1); /* discard value */
analogRead(1); /* discard value */
lght=(long)analogRead(1)*1000/1024;
analogReference(INTERNAL);
analogRead(0); /* discard value - see ATMEGA168 datasheet on change reference */
analogRead(0); /* discard value - for safety*/
}

//
// Begin of HTML page, fixed page title
//
void http_head(Client & client)
{
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println();
client.println("<html>");
client.println("<title>Arduino Home monitor</title>");
client.println("<body>");
}

//
// End of HTML page
//
void http_end(Client & client)
{
client.println("</html>");
client.println("</body>");
}
//
// reply to root request or /info.html
//
void http_root(Client & client)
{
http_head(client);
client.print("Temperature is ");
client.print(tmprtr / 10);
client.print(".");
client.print(tmprtr % 10);
client.println("<br />");
client.print("Light at home is ");
client.print(lght/10);
client.print("%");
client.println("<br />");
http_end(client);
}
//
// reply to /csv.html
//
void http_csv(Client & client)
{
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/csv");
client.println();
client.print(tmprtr / 10);
client.print(".");
client.print(tmprtr % 10);
client.print(",");
client.println(lght/10);
client.println();
}
//
// Work on a http reply, check request file, produce output
//
void http_reply(Client & client)
{
#ifdef DEBUG
Serial.println("==== debug out ===");
Serial.println(cmmnd);
Serial.println(args);
Serial.println(host);
Serial.println("==== debug eot ===");
#endif
get_values();
if ((args[1]=='c') && (args[2]=='s') && (args[3]=='v')) http_csv(client);
else http_root(client);
}

void loop()
{
Client client = server.available();
#define LN_BUF 40
char inp_ln[LN_BUF]; /* input line, crop at 40 chars */
char chr; /* input char */
byte inp_ln_ptr,i;

if (client) {
inp_ln_ptr=0; /* line is empty */

while (client.connected()) { /* while client is connected process lines */
if (client.available()) { /* is there a char available */
chr = client.read(); /* get it */
if (chr == '\n' && inp_ln_ptr<2) {
http_reply(client); /* received a blank line, create return page */
break; /* exit while, !! find a more logical way to do this */
}
if (chr == '\n') { /* end of line */
inp_ln[inp_ln_ptr]=0; /* end of line */
Serial.println(inp_ln);
if ((inp_ln[0]=='G') && (inp_ln[1]=='E') && (inp_ln[2]=='T')) {
cmmnd='G';
// get arguments
for (i=0;i<sizeof(args);i++) {
args[i]=inp_ln[4+i];
if (args[i] == ' ') break;
}
}

if ((inp_ln[0]=='P') && (inp_ln[1]=='U') && (inp_ln[2]=='T')) {
cmmnd='P';
// get arguments
for (i=0;i<sizeof(args);i++) {
args[i]=inp_ln[4+i];
if (args[i] == ' ') break;
}
}
if ((inp_ln[0]=='H') && (inp_ln[1]=='o') && (inp_ln[2]=='s') && (inp_ln[3]=='t')) {
// get arguments
for (i=0;i<sizeof(host);i++) {
host[i]=inp_ln[5+i];
if ((host[i] == ' ') || (host[i] =='\n') ||(host[i] =='\r')) {
host[i]='\0';
break;
}
}
}
inp_ln_ptr=0; /* ptr ready for next line */
}
else if (inp_ln_ptr<LN_BUF-1) inp_ln[inp_ln_ptr++]=chr;
}
}
// give the web browser time to receive the data
delay(1);
client.stop();
}
}

When mounted horizontally the Ethernet shield and the voltage regulator produce enough heat to raise the temperature in the sensor to about 30 degrees Centigrade. When placed vertically convection keeps the heat away and the sensor reads more "normal" temperatures.
The Power supply of the Arduino is supplied by the router that has a USB port, this way an extra wall-adapter is not needed.
The pachube feed is here, where you can see both graphics light and temperature. It is also possible to embed the sensor readings in the blog layout...

No comments: