Visual Clarity Photography
Arduino Powered Flickr Meter
After viewing the Arduino powered internet meter at // vimeo.com/15312319, I decided to make my own, but with some improvements. Instead of checking gmail unread email counts like the video, I chose to write an interface to access Flickr using their REST protocol using python. Once I had the python code working on my laptop, I used google's app engine code.google.com/appengine/ to host my script, so that I didn't need to worry about needing a webserver to host the CGI executable.
I was also wanting to have the Arduino use DHCP to get it's IP address from my router, instead of having it hard-coded into the Arduino source code. A quick google search found 2 compatable DHCP client implementations, but I decided to use the implementation at blog.jordanterrell.com/post/Arduino-DHCP-Library-Version-.... I then based the rest of my sketch on their web client with DHCP client source code example.
An issue with the Arduino which I haven't been able to get around is that you cannot use a URL to access the server, you need to use an IP address. Because of this, additional code is required to go from the google search page to my google app engine application. I found an example at javagwt.blogspot.com/2010/10/arduino-ethernet-to-app-engi....
Once I had the Arduino connecting to my google app engine application, I just needed some glue logic to process the result returned by the script, to drive the panel meter, and some status LED's. The LED's show the following information:
- An IP address has been allocated to the Arduino / Arduino not connected to the network
- A connection has been made to the google app engine application
- An LED to indicate that the Arduino is participating in a 60 second delay to the next poll of the application.
The panel meter displays display counts up to 1000, so the 0.2 on the meter indicates that around 200 views have been recorded when this photograph was taken.
The source code loaded into the Arduino is as follows (sorry flickr removes formatting):
/*------------------------------------------------------------------------------
Arduino powered Flickr Meter
Jason Hilsdon, October 2010
Idea inspired from video by Matt Richardson (vimeo.com/15312319)
DHCP client library sourced from
blog.jordanterrell.com/post/Arduino-DHCP-Library-Version-...
Google App Engine connection code derived from
javagwt.blogspot.com/2010/10/arduino-ethernet-to-app-engi...
------------------------------------------------------------------------------*/
#include
#include "Dhcp.h"
#include
// hardware analog & digital pin assignments
int delayPin = 9; // delay LED blink
int noNetworkPin = 8; // not connected to network
int networkConnected = 7; // connected to network
int googleConnected = 6; // connected to google app engine application
int flickrMeter = 3; // pwm output
boolean delayStatus;
// network interface
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte server[] = { 66, 102, 11, 104 }; // IP address to Google
boolean ipAcquired = false;
int readData[4];
int idx;
boolean startData = false;
// client connection to google
Client client(server, 80);
void setup()
{
// enable serial port monitor
Serial.begin(9600);
// I/O pin assignments
pinMode(noNetworkPin, OUTPUT);
pinMode(networkConnected, OUTPUT);
pinMode(delayPin, OUTPUT);
pinMode(googleConnected, OUTPUT);
pinMode(flickrMeter, OUTPUT);
// set I/O pin default values
digitalWrite(networkConnected, LOW);
digitalWrite(noNetworkPin, HIGH);
digitalWrite(delayPin, LOW);
digitalWrite(googleConnected, LOW);
analogWrite(flickrMeter, 0);
delayStatus = false;
// wait until network interface has valid IP address
while(true)
{
if(nicInit())
{
Serial.print("connecting to server: ");
printArray(&Serial, ".", server, 4, 10);
googleConnect();
break;
}
else
{
Serial.println("Unable to initialize network interface ...");
delay(1000);
}
}
}
// main program loop
void loop()
{
for(;;)
{
if(ipAcquired)
{
if (client.available())
{
// flickr view count has pipe symbol
// for prefix & suffix characters
// (to allow for parsing from HTML header)
char c = client.read();
char * cp = &c; // pointer to allow atoi()
if (c == '|')
{
if (startData)
{
startData = false;
idx--;
}
else
{
idx = 0;
startData = true;
return;
}
}
if (startData)
{
readData[idx] = atoi(cp);
idx++;
}
}
// if we have all the data from the web request,
// process it
if (!client.connected())
{
int viewCount = 0;
if (idx == 0)
{
// < 10 read count
viewCount = readData[0];
}
if (idx == 1)
{
// println();
}
// method to connect to google app engine to get result to process by Arduino
void googleConnect()
{
while(true)
{
if (client.connect())
{
digitalWrite(googleConnected, HIGH);
client.println("GET /service/currentvalue?point=test&email=gmail@gmail.com HTTP/1.1");
client.println("Host:googleappengineapp.appspot.com"); //here is your app engine url
client.println("Accept-Language:en-us,en;q=0.5");
client.println("Accept-Encoding:gzip,deflate");
client.println("Connection:close");
client.println("Cache-Control:max-age=0");
client.println();
break;
}
else
{
digitalWrite(googleConnected, LOW);
Serial.println("connection failed");
delay(1000);
}
}
}
// Method to calculate PWM value to send to analog port.
// PWM has 255 possible values, need to convert max value of 1000
// down to a value 0 ~ 255.
int PVMValue(int value)
{
return round((value / 1000.0) * 255.0);
}
// function to provide delay of minutes, with a 1 second blink
void Sleep(int seconds, boolean blink)
{
for(int i=0; i < seconds; i++)
{
if (blink)
{
Serial.println("Sleeping ...");
if (delayStatus)
{
digitalWrite(delayPin, LOW);
delayStatus = false;
}
else
{
digitalWrite(delayPin, HIGH);
delayStatus = true;
}
}
delay(1000);
}
}
Google app engine script is as follows (note won't compile due to flickr removing formatting):
import time
import string
import flickr
import hashlib
signatureParams = {}
def getData():
return flickr._dopost(signatureParams["method"], auth=True, date=signatureParams["date"])
def genSignature():
keys = signatureParams.keys()
keys.sort()
signature = flickr.API_SECRET
for k in keys:
signature = signature + k + signatureParams[k]
return hashlib.md5(signature).hexdigest()
def getDate():
utctoday = time.gmtime()
return "%s-%s-%s" % (utctoday.tm_year, string.zfill(utctoday.tm_mon, 2), string.zfill(utctoday.tm_mday, 2))
def main():
flickr.API_KEY="Flickr API Key goes here"
flickr.API_SECRET="Flickr API Key secret goes here"
token = flickr.userToken()
signatureParams["auth_token"] = token
signatureParams["api_key"] = flickr.API_KEY
signatureParams["method"] = "flickr.stats.getTotalViews"
signatureParams["date"] = getDate()
signatureParams["signature"] = genSignature()
data = getData()
print 'Content-Type: text/plain'
print ''
print "|" + str(data.rsp.stats.total.views) + "|"
if __name__ == "__main__":
main()
Photo Blog: Visual Clarity Photography
iOS Wallpaper Blog: iOS Wallpaper
Arduino Powered Flickr Meter
After viewing the Arduino powered internet meter at // vimeo.com/15312319, I decided to make my own, but with some improvements. Instead of checking gmail unread email counts like the video, I chose to write an interface to access Flickr using their REST protocol using python. Once I had the python code working on my laptop, I used google's app engine code.google.com/appengine/ to host my script, so that I didn't need to worry about needing a webserver to host the CGI executable.
I was also wanting to have the Arduino use DHCP to get it's IP address from my router, instead of having it hard-coded into the Arduino source code. A quick google search found 2 compatable DHCP client implementations, but I decided to use the implementation at blog.jordanterrell.com/post/Arduino-DHCP-Library-Version-.... I then based the rest of my sketch on their web client with DHCP client source code example.
An issue with the Arduino which I haven't been able to get around is that you cannot use a URL to access the server, you need to use an IP address. Because of this, additional code is required to go from the google search page to my google app engine application. I found an example at javagwt.blogspot.com/2010/10/arduino-ethernet-to-app-engi....
Once I had the Arduino connecting to my google app engine application, I just needed some glue logic to process the result returned by the script, to drive the panel meter, and some status LED's. The LED's show the following information:
- An IP address has been allocated to the Arduino / Arduino not connected to the network
- A connection has been made to the google app engine application
- An LED to indicate that the Arduino is participating in a 60 second delay to the next poll of the application.
The panel meter displays display counts up to 1000, so the 0.2 on the meter indicates that around 200 views have been recorded when this photograph was taken.
The source code loaded into the Arduino is as follows (sorry flickr removes formatting):
/*------------------------------------------------------------------------------
Arduino powered Flickr Meter
Jason Hilsdon, October 2010
Idea inspired from video by Matt Richardson (vimeo.com/15312319)
DHCP client library sourced from
blog.jordanterrell.com/post/Arduino-DHCP-Library-Version-...
Google App Engine connection code derived from
javagwt.blogspot.com/2010/10/arduino-ethernet-to-app-engi...
------------------------------------------------------------------------------*/
#include
#include "Dhcp.h"
#include
// hardware analog & digital pin assignments
int delayPin = 9; // delay LED blink
int noNetworkPin = 8; // not connected to network
int networkConnected = 7; // connected to network
int googleConnected = 6; // connected to google app engine application
int flickrMeter = 3; // pwm output
boolean delayStatus;
// network interface
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte server[] = { 66, 102, 11, 104 }; // IP address to Google
boolean ipAcquired = false;
int readData[4];
int idx;
boolean startData = false;
// client connection to google
Client client(server, 80);
void setup()
{
// enable serial port monitor
Serial.begin(9600);
// I/O pin assignments
pinMode(noNetworkPin, OUTPUT);
pinMode(networkConnected, OUTPUT);
pinMode(delayPin, OUTPUT);
pinMode(googleConnected, OUTPUT);
pinMode(flickrMeter, OUTPUT);
// set I/O pin default values
digitalWrite(networkConnected, LOW);
digitalWrite(noNetworkPin, HIGH);
digitalWrite(delayPin, LOW);
digitalWrite(googleConnected, LOW);
analogWrite(flickrMeter, 0);
delayStatus = false;
// wait until network interface has valid IP address
while(true)
{
if(nicInit())
{
Serial.print("connecting to server: ");
printArray(&Serial, ".", server, 4, 10);
googleConnect();
break;
}
else
{
Serial.println("Unable to initialize network interface ...");
delay(1000);
}
}
}
// main program loop
void loop()
{
for(;;)
{
if(ipAcquired)
{
if (client.available())
{
// flickr view count has pipe symbol
// for prefix & suffix characters
// (to allow for parsing from HTML header)
char c = client.read();
char * cp = &c; // pointer to allow atoi()
if (c == '|')
{
if (startData)
{
startData = false;
idx--;
}
else
{
idx = 0;
startData = true;
return;
}
}
if (startData)
{
readData[idx] = atoi(cp);
idx++;
}
}
// if we have all the data from the web request,
// process it
if (!client.connected())
{
int viewCount = 0;
if (idx == 0)
{
// < 10 read count
viewCount = readData[0];
}
if (idx == 1)
{
// println();
}
// method to connect to google app engine to get result to process by Arduino
void googleConnect()
{
while(true)
{
if (client.connect())
{
digitalWrite(googleConnected, HIGH);
client.println("GET /service/currentvalue?point=test&email=gmail@gmail.com HTTP/1.1");
client.println("Host:googleappengineapp.appspot.com"); //here is your app engine url
client.println("Accept-Language:en-us,en;q=0.5");
client.println("Accept-Encoding:gzip,deflate");
client.println("Connection:close");
client.println("Cache-Control:max-age=0");
client.println();
break;
}
else
{
digitalWrite(googleConnected, LOW);
Serial.println("connection failed");
delay(1000);
}
}
}
// Method to calculate PWM value to send to analog port.
// PWM has 255 possible values, need to convert max value of 1000
// down to a value 0 ~ 255.
int PVMValue(int value)
{
return round((value / 1000.0) * 255.0);
}
// function to provide delay of minutes, with a 1 second blink
void Sleep(int seconds, boolean blink)
{
for(int i=0; i < seconds; i++)
{
if (blink)
{
Serial.println("Sleeping ...");
if (delayStatus)
{
digitalWrite(delayPin, LOW);
delayStatus = false;
}
else
{
digitalWrite(delayPin, HIGH);
delayStatus = true;
}
}
delay(1000);
}
}
Google app engine script is as follows (note won't compile due to flickr removing formatting):
import time
import string
import flickr
import hashlib
signatureParams = {}
def getData():
return flickr._dopost(signatureParams["method"], auth=True, date=signatureParams["date"])
def genSignature():
keys = signatureParams.keys()
keys.sort()
signature = flickr.API_SECRET
for k in keys:
signature = signature + k + signatureParams[k]
return hashlib.md5(signature).hexdigest()
def getDate():
utctoday = time.gmtime()
return "%s-%s-%s" % (utctoday.tm_year, string.zfill(utctoday.tm_mon, 2), string.zfill(utctoday.tm_mday, 2))
def main():
flickr.API_KEY="Flickr API Key goes here"
flickr.API_SECRET="Flickr API Key secret goes here"
token = flickr.userToken()
signatureParams["auth_token"] = token
signatureParams["api_key"] = flickr.API_KEY
signatureParams["method"] = "flickr.stats.getTotalViews"
signatureParams["date"] = getDate()
signatureParams["signature"] = genSignature()
data = getData()
print 'Content-Type: text/plain'
print ''
print "|" + str(data.rsp.stats.total.views) + "|"
if __name__ == "__main__":
main()
Photo Blog: Visual Clarity Photography
iOS Wallpaper Blog: iOS Wallpaper