// Sky color logger.

// Takes a photograph of the sky every second, then finds and stores the average color value.
// Each minute, average the one-second averages to find the average sky color over the last minute.
// Upload the result to Pachube (http://www.pachube.com/) as a hexadecimal color value.
// Built with the EEML Library for Processing, available at http://www.eeml.org/library/
import processing.video.*;
import eeml.*;

DataOut dataOut;
Capture cam;

int pixelCount;
color averageColor;
color minuteAverageColor;
int lastMinute;

int minuteAverageRed = 0;
int minuteAverageGreen = 0;
int minuteAverageBlue = 0;
int averageRed = 0;
int averageGreen = 0;
int averageBlue = 0;
int sampleCount = 0;

int[] averages; // Stores recent averages.

void setup() {
 size(640, 480);
 noStroke();
 frameRate(2);

 // Set up communication with Pachube.
 String feedURL = "http://www.pachube.com/api/feeds/3929.xml";
 String apiKey = "YOUR PACHUBE API KEY HERE";
 dataOut = new DataOut(this, feedURL, apiKey);
 dataOut.addData(0, "sky,color");

 // Set up the camera.
 String[] devices = Capture.list();
 println(devices);
 cam = new Capture(this, 320, 240, devices[1]);
 cam.frameRate(1); // Read 1 frame per second.
 pixelCount = cam.width * cam.height;

 // Take note of the time. We will upload once per minute.
 lastMinute = minute();

 // Store recent averages in an array.
 averages = new int[240];

 // Fill the averages array with white to start
 for(int i = 0; i < averages.length; i++) averages[i] = 255;
}

void draw() {
 background(0);

 // Show the live camera feed in the top left.
 fill(0);
 image(cam, 0, 0);

 // Show the latest average color in the top right.
 fill(averageColor);
 rect(320, 0, 320, 240);

 // Show the last minute average in the bottom left.
 // This is what was actually sent to Pachube.
 fill(minuteAverageColor);
 rect(0, 240, 320, 240);

 // Draw the recent averages as horizontal lines in the bottom right.
 // The latest values are pushed onto the top of the stack.
 for(int i = 0; i < averages.length; i++) {
   stroke(averages[i]);
   strokeWeight(1);
   line(320, 240 + i, 640, 240 + i);
 }
 noStroke();

 // Aggregate the averages and send to Pachube every minute.
 if((minute() != lastMinute) && (sampleCount > 0)) {

   // Find the aggregated average for each minute.
   minuteAverageRed = minuteAverageRed / sampleCount;
   minuteAverageGreen = minuteAverageGreen / sampleCount;
   minuteAverageBlue = minuteAverageBlue / sampleCount;

   minuteAverageColor = color(minuteAverageRed, minuteAverageGreen, minuteAverageBlue);

   // Convert to hex and send to Pachube.
   println("Sending sky value " + hex(minuteAverageColor, 6) + " to Pachube.");
   dataOut.update(0, hex(minuteAverageColor, 6));
   int response = dataOut.updatePachube();
   println("Pachube response: " + response); // Should be 200 if successful; 401 if unauthorized; 404 if feed doesn't exist.

   // Shift values in the averages array over one, and then add the latest to the beginning.
   for(int i = averages.length - 1; i > 0; i--) averages[i] = averages[i - 1];
   averages[0] = minuteAverageColor;

   // Reset the minute averages.
   minuteAverageRed = 0;
   minuteAverageGreen = 0;
   minuteAverageBlue = 0;

   // Reset the sample count.
   sampleCount = 0;

   // Reset the minute mark.
   lastMinute = minute();
 }
}

void captureEvent(Capture cam) {
 // Get the latest camera data.
 cam.read();
 cam.loadPixels();

 // Reset the averages for this frame.
 averageRed = 0;
 averageGreen = 0;
 averageBlue = 0;

 // Average the pixels.
 int pixelColor;

 for (int i = 0; i < pixelCount; i++) {
   pixelColor = cam.pixels[i];

   // Extract the RGB values.
   averageRed += (pixelColor >> 16) & 0xff;
   averageGreen += (pixelColor >> 8) & 0xff;
   averageBlue += pixelColor & 0xff;
 }

 // Take the average.
 averageRed = averageRed / pixelCount;
 averageGreen = averageGreen / pixelCount;
 averageBlue = averageBlue / pixelCount;

 // Note this average for the aggregated average taken at the minute mark.
 // It would be simpler not to store every single frame's average, and instead to average
 // a minute's worth of pixel data (e.g. average = averageChannel / (pixelCount * sampleCount))
 // but for the sake of the display it's useful to have average data from the latest frame as well.
 minuteAverageRed += averageRed;
 minuteAverageGreen += averageGreen;
 minuteAverageBlue += averageBlue;

 averageColor = color(averageRed, averageGreen, averageBlue);

 // Iterate the sample count... this is what we use to take the average
 // at the minute mark. With the camera at 1 FPS, it should always be 60, but
 // in case of timing issues it's worth keeping track.
 sampleCount++;
}
