We have previously documented how to interface two sensors: an LM34 temperature and a Maxbotix-EZ distance with Sensorpedia. For this guide we will be switching things up a bit. Instead of using a Make Controller to communicate with sensors, we will be using another popular DIY/tinkerer microcontroller: the Arduino (duemilanove). Instead of pushing our data to Twitter, we will now be generating an Atom feed and serving it ourselves.
The color sensor we will be working with is the ADJD-S371. First, a note on voltage requirements. The color sensor is designed to operate at 3.3VDC with a maximum operating voltage of 3.6VDC. We’ll want to be sure to use the 3.3[V] supply from the Arduino board. Proper calibration of the ADJD-S371 will be left as an exercise for the reader (a half ping-pong works reasonably well as an integrator) .


The wires were connected as follows:
- SCL - serial clock to Arduino analog input 5
- SDA - the serial data line to analog input 4
- SLEEP was tied to GND
- LED was connected to pin 1
- XCLK - the external clock left floating
For this guide, we will be using C to program our Arduino and C# instead of Python for our server.
First we will need to read in our sensor and output its values over serial. We will be using the wonderful ADJD-S371 code published by Arduino user Marcus to output R,G,B tuples:
//ADJD settings #define I2C_ADDRESS 0x74 // 7bit #define REG_CAP_RED 0x06 #define REG_CAP_GREEN 0x07 #define REG_CAP_BLUE 0x08 #define REG_CAP_CLEAR 0x09 #define REG_INT_RED_LO 0x0A #define REG_INT_RED_HI 0x0B #define REG_INT_GREEN_LO 0x0C #define REG_INT_GREEN_HI 0x0D #define REG_INT_BLUE_LO 0x0E #define REG_INT_BLUE_HI 0x0F #define REG_INT_CLEAR_LO 0x10 #define REG_INT_CLEAR_HI 0x11 #define REG_DATA_RED_LO 0x40 #define REG_DATA_RED_HI 0x41 #define REG_DATA_GREEN_LO 0x42 #define REG_DATA_GREEN_HI 0x43 #define REG_DATA_BLUE_LO 0x44 #define REG_DATA_BLUE_HI 0x45 #define REG_DATA_CLEAR_LO 0x46 #define REG_DATA_CLEAR_HI 0x47 #define redPin 3 #define greenPin 5 #define bluePin 6 #define ledPin 11 #define clearCalPin 9 float redFactor=1; float blueFactor=1; float greenFactor=1; //initial darkLevel; int calibrationDarkness = 0; byte calibrationRed = 5; byte calibrationGreen = 5; byte calibrationBlue = 5; void setup(void) { Serial.begin(9600); pinMode(redPin, OUTPUT); pinMode(greenPin, OUTPUT); pinMode(bluePin, OUTPUT); pinMode(ledPin, OUTPUT); pinMode(clearCalPin, INPUT); setupADJD(); // sensor gain setting (Avago app note 5330) // CAPs are 4bit (higher value will result in lower output) set_register(REG_CAP_RED, 0x01); set_register(REG_CAP_GREEN, 0x01); set_register(REG_CAP_BLUE, 0x01); set_register(REG_CAP_CLEAR, 0x01); //calibrate led digitalWrite(redPin,HIGH); digitalWrite(bluePin,HIGH); digitalWrite(greenPin,HIGH); int ledGain = getColorGain(); set_gain(REG_INT_RED_LO,ledGain); set_gain(REG_INT_GREEN_LO,ledGain); set_gain(REG_INT_BLUE_LO,ledGain); performMeasurement(); int red=get_readout(REG_DATA_RED_LO); int green=get_readout(REG_DATA_GREEN_LO); int blue=get_readout(REG_DATA_BLUE_LO); digitalWrite(redPin,0); digitalWrite(bluePin,0); digitalWrite(greenPin,0); int m=2000; //bigger anyway m=min(m,red); m=min(m,green); m=min(m,blue); redFactor=((float)m*255.0)/(1000*(float)red); greenFactor=((float)m*255.0)/(1000*(float)green); blueFactor=((float)m*255.0)/(1000*(float)blue); } void loop() { digitalWrite(redPin,0); digitalWrite(bluePin,0); digitalWrite(greenPin,0); int clearGain = getClearGain(); set_gain(REG_INT_CLEAR_LO,clearGain); int colorGain = getColorGain(); set_gain(REG_INT_RED_LO,colorGain); set_gain(REG_INT_GREEN_LO,colorGain); set_gain(REG_INT_BLUE_LO,colorGain); int cc = 0; int red=0; int green=0; int blue=0; for (int i=0; i<4 ;i ++) { performMeasurement(); cc +=get_readout(REG_DATA_CLEAR_LO); red +=get_readout(REG_DATA_RED_LO); green +=get_readout(REG_DATA_GREEN_LO); blue +=get_readout(REG_DATA_BLUE_LO); } cc/=4; red /=4; green /=4; blue /=4; float rv = (float)red*redFactor; float gv = (float)green*greenFactor; float bv = (float)blue*blueFactor; int r = rv; int g = gv; int b = bv; analogWrite(redPin,r); analogWrite(bluePin,g); analogWrite(greenPin, b); Serial.print(r); Serial.print(","); Serial.print(g); Serial.print(","); Serial.println(b); //delay(60000); delay(1000); } int getClearGain() { int gainFound = 0; int upperBox=4096; int lowerBox = 0; int half; while (!gainFound) { half = ((upperBox-lowerBox)/2)+lowerBox; //no further halfing possbile if (half==lowerBox) { gainFound=1; } else { set_gain(REG_INT_CLEAR_LO,half); performMeasurement(); int halfValue = get_readout(REG_DATA_CLEAR_LO); if (halfValue>1000) { upperBox=half; } else if (halfValue<1000) { lowerBox=half; } else { gainFound=1; } } } return half; } int getColorGain() { int gainFound = 0; int upperBox=4096; int lowerBox = 0; int half; while (!gainFound) { half = ((upperBox-lowerBox)/2)+lowerBox; //no further halfing possbile if (half==lowerBox) { gainFound=1; } else { set_gain(REG_INT_RED_LO,half); set_gain(REG_INT_GREEN_LO,half); set_gain(REG_INT_BLUE_LO,half); performMeasurement(); int halfValue = 0; halfValue=max(halfValue,get_readout(REG_DATA_RED_LO)); halfValue=max(halfValue,get_readout(REG_DATA_GREEN_LO)); halfValue=max(halfValue,get_readout(REG_DATA_BLUE_LO)); if (halfValue>1000) { upperBox=half; } else if (halfValue<1000) { lowerBox=half; } else { gainFound=1; } } } return half; }
The above Arduino code provides us with R,G,B tuples. We will now use a C# application to read in these values from the serial port, generate an Atom feed, and serve it up. We will use the open source WebFeeds library to generate an Atom document to hold our sensor data. You can download the Visual Studio solution and pre-compiled DLL from the aforementioned website. This server is quite generic, and could easily serve any other sort of data from a serial port–just change the feed metadata.
//We'll need a few things using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Text; using System.IO; using System.IO.Ports; using System.Net; using System.Xml; using System.Xml.Serialization; using System.Runtime.InteropServices; using System.Threading; using WebFeeds; using WebFeeds.Feeds; using WebFeeds.Feeds.Atom; namespace LightSensorAtomServer{ class MainProgram { // Create the serial port with basic settings private SerialPort port = new SerialPort("COM16", 9600, Parity.None, 8, StopBits.One); //the serial port we will use to read in data. note that the COM port must be that which our data is coming in on. something like COM5 for windows, or /dev/ttyUSB0 for POSIX static void Main(string[] args){ new MainProgram(); //instantiate the program } MainProgram(){ SerialPortStart(); //initialize the serial port startServer(); //start up our HTTP server, which will in turn be responsible for calling methods to read in data, generate an Atom feed, and serve it } public void startServer(){ HttpListener listener = new HttpListener(); //create a new HTTP listener listener.Prefixes.Add("http://*:80/"); //make it listen to all requests on the standard web port, 80 listener.Start(); //start listening Console.WriteLine("Listening..."); for (; ; ) //run the server forever { HttpListenerContext context = listener.GetContext(); //gives us access to information about requests new Thread(new Worker(context, writeFeed(ReadData())).ProcessRequest).Start(); //make a new thread to respond, pass it the context AND the feed we created. Note that we are reading in data and then writing a feed. Effect: We wait for the latest data before serving } } class Worker{ private HttpListenerContext context; //a ListenerContext local to this class and thus thread private byte[] byteFeedArray; //this will hold our feed output as a byte array public Worker(HttpListenerContext context, byte[] byteFeedArray){//set our instance variables according to the parameters passed in this.context = context; //set our HTTPListenerContext this.byteFeedArray = byteFeedArray; //set our data feed to the output generated above } public void ProcessRequest(){ //answer our web request string msg = context.Request.HttpMethod + " " + context.Request.Url; Console.WriteLine(msg); //output the request to the console so we can spot strange requests if (context.Request.HttpMethod == "GET") { //if they are requesting the data with a GET request context.Response.ContentType = "application/xhtml+xml"; //since we are outputting Atom, set the MIME type of the data output to XML context.Response.ContentLength64 = byteFeedArray.Length; //set our content length context.Response.OutputStream.Write(byteFeedArray, 0, byteFeedArray.Length); //write out our entire feed byte array context.Response.OutputStream.Close(); //close the request, we're done serving to this request } else {} //we're not allowing other HTTP verbs } } public byte[] writeFeed(string data) { //this function is responsible for actually creating and outputting the byte array of our feed from data and stored variables byte[] byteFeedArray; //a byte array to store our finished feed array int streamPosition; //We will store the output of WebFeeds in a MemoryStream, and use this streamPosition variable to help us keep track of where we are in reading it out AtomFeed sensorFeed = new AtomFeed(); //This AtomFeed object is the real deal. It will be the one serialized and outputted to our final byte array string feedTitle = "Color Light Sensor Data Feed"; //set the title for our feed string feedSubtitle = "A series of RGB values."; //set the subtitle for our feed string feedAuthorName = "Chris Tomkins-Tinch"; //set the feed author name string feedAuthorUri = "http://www.christomkinstinch.com"; double feedLat = 35.9302; //set the feed lat double feedLon = -84.3112; //...and lon string entryTitle = "Light Color Data"; //set the entry title (we'll only have one for now) string entryContent = data; //set the data to that passed in as a parameter double entryLat = 35.9302; //set the entry lat double entryLon = -84.3112;//...and lon sensorFeed = generateFeed(System.Guid.NewGuid().ToString(), feedTitle, feedSubtitle, feedAuthorName, feedAuthorUri, feedLat.ToString(), feedLon.ToString());//pass all of the above local variables to the function that will output a new AtomFeed with all of the needed metadata, set our local sensorFeed to this output XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();//make a Serializer so we can add some namespaces to our feed namespaces.Add("georss", "http://www.georss.org/georss"); //and the georss namespace sensorFeed.AddNamespaces(namespaces); //add this namespaces to the sensor feed sensorFeed.Entries.Add(generateEntry(System.Guid.NewGuid().ToString(), entryTitle, entryContent, entryLat.ToString(), entryLon.ToString()));//pass all of the above local variables to the function that will output a new AtomEntry with all of the needed elements and added to the feed we made above MemoryStream memoryStream = new MemoryStream(); //make a new MemoryStream to hold the output of the XML Serializer FeedSerializer.SerializeXml(sensorFeed, memoryStream, null, true); //serialize our AtomFeed (sensorFeed) and put the output into our memory stream memoryStream.Seek(0, SeekOrigin.Begin);// prepare to start navigating and outputting from our memory stream. set the position to the beginning of the stream. byteFeedArray = new byte[memoryStream.Length]; //initialize our feed byte array to be the same length as our memory stream streamPosition = memoryStream.Read(byteFeedArray, 0, 20);// Read the first 20 bytes from the stream. while (streamPosition < memoryStream.Length) {// Read the remaining bytes, byte by byte. byteFeedArray[streamPosition++] = Convert.ToByte(memoryStream.ReadByte()); } return byteFeedArray; //output the final byte array. this contains the serialized xml of our AtomFeed which has been converted from its memory stream location to a byte array. now we can serve it. } public AtomFeed generateFeed(string feedUuid, string feedTitle, string feedSubtitle, string feedAuthorName, string feedAuthorUri string feedLat, string feedLon){ AtomFeed feed = new AtomFeed(); //an AtomFeed from the JSONFX WebFeeds lib XmlDocument xmlDoc = new XmlDocument(); //an XmlDocument so we can make XML elements feed.Updated = new AtomDate(DateTime.UtcNow); //set the feed updated time to the current time feed.Title = feedTitle; //set the feed title to that specified by the parameter feed.ID = feedUuid; //set the feed universally unique identifier to that specified by the parameter AtomPerson author = new AtomPerson(); //make a new AtomPerson construct author.Name = feedAuthorName; //add the author name specified in the parameter to the author construct author.Uri = feedAuthorUri; //add the author URL specified in the parameter to the author construct feed.Authors.Add(author); //add the author construct to the feed. this will generate the author element and child elements of name, url, etc. AtomLink feedLink = new AtomLink("http://www.example.com/sensor/feed/url");//add a link construct to our feed. this represents where we can go to retrieve this feed feed.Links.Add(feedLink); //add the feed link to our feed feed.Categories.Add(new AtomCategory("light-color")); //add a category construct feed.Categories.Add(new AtomCategory("color")); // and another feed.Categories.Add(new AtomCategory("arduino"));// and one more XmlElement georssPoint = xmlDoc.CreateElement("georss", "point", "http://www.georss.org/georss"); //make a new XML element using the XmlDocument we defined above. specify its namespace as georss, and add the georss namespace to that link. WebFeeds will take care of consolidating like namespaces and adding them to the feed level of the outputted document georssPoint.InnerText = feedLat + " " + feedLon; //make a space delimited string to represent our location, per the GeoRSS simple spec feed.ElementExtensions.Add(georssPoint); //add our newly minted GeoRSS XmlElement to our feed return feed; //return our feed. we aren't done with it yet, but now we have added all of the feed-level metadata } public AtomEntry generateEntry(string entryUuid, string entryTitle, string entryContent, string entryLat, string entryLon) {//Make and return a simple Atom entry AtomEntry entry = new AtomEntry(); //an AtomEntry from the JSONFX WebFeeds lib XmlDocument xmlDoc = new XmlDocument(); //an XmlDocument so we can make XML elements entry.Updated = new AtomDate(DateTime.UtcNow); //set the entry updated time to the current time entry.Title = entryTitle;//set the entry title to that specified by the parameter entry.ID = entryUuid;//set the feed universally unique identifier to that specified by the parameter entry.Content = entryContent;//set the feed content to that specified by the parameter, in this case our data XmlElement georssPoint = xmlDoc.CreateElement("georss", "point", "http://www.georss.org/georss");//make a new XML element using the XmlDocument we defined above. specify its namespace as georss, and add the georss namespace to that link. WebFeeds will take care of consolidating like namespaces and adding them to the feed level of the outputted document georssPoint.InnerText = entryLat + " " + entryLon;//make a space delimited string to represent our location, per the GeoRSS simple spec entry.ElementExtensions.Add(georssPoint);//add our newly minted GeoRSS XmlElement to our entry return entry;//return our entry object. now it can be added to a feed } public void SerialPortStart() { port.Open();//begin communication } public string ReadData() { char tempChar; //a local byte variable to hold output from the serial buffer string serialOutputString = ""; //a local variable to hold the string we will buid from the serial output port.DiscardInBuffer();//wipe the serial buffer so we know this is a new reading while (serialOutputString.IndexOf("\n") == -1){ //wait for an end-of-line character, so we know our next reading is complete tempChar = (char)port.ReadChar(); //read in a char serialOutputString = serialOutputString + tempChar; //and add the byte to our string Console.WriteLine(serialOutputString); } serialOutputString = port.ReadLine(); serialOutputString = serialOutputString.Replace("\n", "").Replace("\r", ""); //once we're here we have reached an end-of-line and are now our of the loop. strip the newline characters Console.WriteLine(serialOutputString); //write the values to the console since they're nice to see. return serialOutputString; //return the serial output. we're finished reading from the serial port for now } } }
We can go to http://localhost/ and view the resulting XML:
<?xml version="1.0" encoding="utf-8"?> <feed xmlns:georss="http://www.georss.org/georss" xmlns="http://www.w3.org/2005/Atom"> <georss:point>35.9302 -84.3112</georss:point> <author> <name>Chris Tomkins-Tinch</name> <uri>http://www.christomkintinch.com/</uri> </author> <category term="light-color" /> <category term="color" /> <category term="arduino" /> <link href="http://www.example.com/sensor/feed/url" /> <id>07d9f330-3b50-436a-a28d-b41ba0b52f03</id> <title>Color Light Sensor Data Feed</title> <updated>2009-02-20T19:02:29Z</updated> <entry> <georss:point>35.9302 -84.3112</georss:point> <id>270d3cd7-23c7-4785-baf0-45b528ecda4c</id> <title>Light Color Data</title> <updated>2009-02-19T19:02:29Z</updated> <content>127,127,127</content> </entry> </feed>
Once this is deployed to a publicly-accessible web server, users will be able to subscribe to the feed at the server’s IP address. This Atom feed will be understood by any modern browser or feed reader:

An Atom feed like this permits users to subscribe to your data feed using standard feed reading applications such as Google Reader, Bloglines, NewsGator, and others. Users can also take advantage of existing open source and commercial feed parsing libraries and utilities to parse this information and use it in their own applications. Sensorpedia makes it easy to index and discover new Atom data sources.







February 24th, 2009 at 8:45 am
[...] via Sensorpedia » Blog Archive » How To: Interface a light color sensor with Sensorpedia and serve Ato…. [...]
February 26th, 2009 at 4:28 pm
[...] Get Involved « How To: Interface a light color sensor with Sensorpedia and serve Atom! (guide 3) [...]
June 19th, 2009 at 11:05 am
[...] How To: Interface a light color sensor with Sensorpedia and serve Atom! [...]
February 3rd, 2010 at 6:59 pm
Chris,
Stumbled upon sensorpedia search for an answer to my problems with the code you borrowed from Arduino user Marcus to output R,G,B tuples:
Basically just like his example code your doesn’t compile…
In function ‘void setup()’:
error: ’setupADJD’ was not declared in this scope In function ‘void loop()’:
In function ‘int getClearGain()’:
In function ‘int getColorGain()’:
is what the compiler gives back as an error. Were you able to use this code verbatim for this project???
May 12th, 2010 at 7:26 pm
Matt,
You’re absolutely right about “setupADJD();” not being declared or defined in the setup loop.
It has been over a year since I worked on this project, and unfortunately I do not have the color sensor in front of me.
Just looking at the code, I wonder if a line comment may have been lost in posting it to the web for the “setupADJD();” line…