The Basic Builduino

Tags: General

At the end of the month I’m presenting a 70 minute session at CodeStock titled “Getting Started with Arduino”.  The demo project I plan on using at that session is a simple build status indicator – a device that can talk to a continuous integration server and display a physical indication of whether the build is succeeding or failing.  I’ve named this little project, the “Builduino.”

I chose this demo project for a few good reasons:

  1. the electronics are simple – I don’t want (nor am I able) to turn the session into an electronics tutorial;
  2. the hardware relies on an Arduino shield, which is part of the platform I want to demonstrate;
  3. the functionality of the code will be familiar to most of the audience – we’ll be doing simple HTTP, nothing fancy.  This will allow me to highlight core differences in developing for the “smaller” processor, such as a threadless environment, without having to introduce a lot of unrelated concepts;
  4. the device is something most .NET developers (whom I assume will make up most of the audience) can relate to.  In fact, it’s downright useful to them;
  5. the device is something saleable.  That is, I believe it could be the basis for a billable project or sales good;
  6. the device can operate without a PC attached.  This is probably my most important requirement, as it demonstrates that the Arduino is quite capable all on its own, and spotlights the biggest difference between this hardware platform and many of the others (such as Phidgets).

My next few posts here will outline that project.  I’ll be using them to help organize my ideas and presentation ideas.  Feedback is warmly welcomed!

Gearing Up

The hardware kit for this project includes the following items:

  • 1 Arduino Duemilanove (note that the ATMEGA168 processors work fine with this project)
  • 1 Arduino Ethernet Shield
  • 1 solderless breadboard
  • 1 green LED
  • 1 red LED
  • 1 push button
  • patch cables
  • 1 330 Ohm resistor

You’ll need a standard networking setup – any home network should do fine – and access to a continuous integration server; I’ll be using Team City in this series of posts, but any server that provides a feed will suffice.

Design

The device will be as simple as we can make it:

  • It will periodically query the CI server for a build feed
  • If the build is failing, the red LED will activate
  • If the build is passing, the greed LED will activate
  • While the device is processing a new feed, the active LED will flash
  • If the CI server is not available, the red LED will blink steadily
  • The device will query the server immediately whenever the push button is pressed

Build the Device

First, mount the Ethernet shield onto the Arduino board, as shown below.  Be careful not to force the shield pins, they are designed to stand off of the Arduino by about half an inch:

Installing the Ethernet Shield

Set the Arduino / Ethernet sammich aside and grab the breadboard.  Mount the green and red LEDs, with the  positive poles to the right, placing them a few rows apart.  Mount the push button in the center of the breadboard, close to the left end.

Wire up the sammich and breadboard using the diagram below (created using Fritzing):

builduino_bb

  • digital pin 3 connects to the green LED’s positive pole;
  • digital pin 4 connects to the red LED’s positive pole;
  • digital pin 8 connects to one of the pushbutton’s poles;
  • the resistor connects the same pushbutton pole to ground;
  • the Arduino 5V source connects to the pushbutton’s other pole.

Connect the Ethernet shield to your network, and connect the Arduino to your PC with the USB cable.

 

Programming

Here are a list of external libraries used by this project; please make sure you have them installed before trying the code.

Library URL Purpose
Ethernet http://www.arduino.cc/en/Main/ArduinoEthernetShield Provides code support for the ethernet shield.
Metro http://www.arduino.cc/playground/Code/Metro Wraps timer tracking in a simple object.

Fire up Arduino 18 or later and create a new sketch:

 

   1: /* Builduino.pde
   2:  
   3:  author
   4:  jdac  Jim Christopher  jim@codeowls.com
   5:  
   6:  description:
   7:  fully configurable network device for monitoring TeamCity feeds 
   8:  
   9:  libraries:
  10:  Ethernet
  11:  Metro
  12:  */
  13:  
  14: #include "EEPROM.h"
  15: #include "Ethernet.h"
  16: #include "Metro.h"
  17:  
  18: // pin symbols
  19: #define SUCCESS_PIN 3
  20: #define FAIL_PIN 4
  21: #define FORCE_PIN 8
  22: #define FEED_BUFFER_SIZE 32
  23:  
  24: // build states
  25: #define BUILD_FAILED (-1)
  26: #define BUILD_PASSED (1)
  27: #define CONNECTION_ERROR (0)
  28:  
  29: // build state macros
  30: #define IS_FAILED( x ) ( 0 > (x) )
  31: #define IS_SUCCESS( x ) ( 0 < (x) )
  32: #define IS_ERROR( x ) ( 0 == (x) )
  33:  
  34: /*CHANGE THIS TO YOUR OWN UNIQUE VALUE.  The MAC number should be
  35:  * different from any other devices on your network or you'll have
  36:  * problems receiving packets. */
  37: static uint8_t mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
  38:  
  39: /* CHANGE THIS TO MATCH YOUR HOST NETWORK.  Most home networks are in
  40:  * the 192.168.0.XXX or 192.168.1.XXX subrange.  Pick an address
  41:  * that's not in use and isn't going to be automatically allocated by
  42:  * DHCP from your router. */
  43: static uint8_t ip[] = { 192, 168, 1, 64 };
  44:  
  45: /* CHANGE THESE TO MATCH YOUR HOST NETWORK */
  46: byte gateway[] = { 192,168,1,1 };
  47: byte subnet[] = { 255,255,255,0};
  48:  
  49: /* CHANGE THIS TO MATCH YOUR CI SERVER IP */
  50: byte server[] = { 192,168,1,110 };
  51:  
  52: /* CHANGE THE 80 TO YOUR CI SERVER FEED PORT */
  53: Client client( server, 80 );
  54:  
  55: // a buffer to hold a window on the incoming CI feed
  56: char feedBuffer[32];
  57: // the current state of the active indicator pin with
  58: //  regard to the current build status
  59: int buildStatusPinState = LOW;
  60: // the current state of the active indicator pin during
  61: //  feed queries and processing
  62: int feedCheckPinState = LOW;
  63: // the last known state of the toggle button
  64: int lastToggleButtonState = LOW;
  65: // the current build state
  66: int buildStatus = CONNECTION_ERROR;
  67:  
  68: // timers, specified in milliseconds
  69: //   check the CI feed, 10 second interval
  70: Metro feedCheckMetro( 10000 );    
  71: //   feed check blink indicator, 100 millisecond toggle
  72: Metro feedCheckIndicatorMetro( 100, 1 );
  73: //    CI server unavailable blink indicator, 500 millisecond toggle
  74: Metro connectFailureMetro( 500, 1 );
  75:  
  76: // indicator of when a feed is being requested and processe
  77: boolean isRequestingFeed = false;
  78:  
  79: /* utility method to NULL-out the feed buffer
  80:  */
  81: void resetFeedBuffer()
  82: { 
  83:   for( int c = 0; c < FEED_BUFFER_SIZE; ++c )
  84:   {
  85:     feedBuffer[c] = NULL;
  86:   }
  87: }
  88:  
  89: /* changes the active build status LED state
  90:  * so that it flashes on or off
  91:  */
  92: void toggleFeedCheckIndicator()
  93: {
  94:   if( 1 == feedCheckIndicatorMetro.check() )  
  95:   {
  96:     feedCheckPinState = LOW == feedCheckPinState ? HIGH : LOW;
  97:  
  98:     if( IS_SUCCESS( buildStatus ) )
  99:     {
 100:       digitalWrite( SUCCESS_PIN, feedCheckPinState );
 101:     }
 102:     else if( IS_FAILED( buildStatus ) )
 103:     {
 104:       digitalWrite( FAIL_PIN, feedCheckPinState );
 105:     }
 106:   }
 107: }
 108:  
 109: /* connect to configured TeamCity feed URL,
 110:  * and request build status feed
 111:  */
 112: void requestTeamCityFeed()
 113: {
 114:   // short-circuit if we are already requesting a feed
 115:   if( isRequestingFeed )
 116:   {
 117:     return;
 118:   }
 119:  
 120:   // update states to indicate that we are actively requesting 
 121:   //  a feed
 122:   isRequestingFeed = true;
 123:   resetFeedBuffer();
 124:   feedCheckPinState = LOW;
 125:  
 126:   Serial.println( "connecting...");
 127:   
 128:   // connect to the CI server
 129:   if (client.connect()) 
 130:   {
 131:     Serial.println("connected");
 132:     
 133:     // issue a simle HTTP GET for the feed URL
 134:     client.println("GET /feed.html HTTP/1.0");
 135:     client.println();
 136:   } 
 137:   else 
 138:   {
 139:     // connection has failed
 140:     Serial.println("connection failed");
 141:     
 142:     // update states to indicate that the connection failed 
 143:     isRequestingFeed = false;
 144:     buildStatus = CONNECTION_ERROR;
 145:     
 146:     // reset the feed query timer
 147:     feedCheckMetro.reset();
 148:   }
 149:  
 150: }
 151:  
 152: /* roll the existing feed buffer, dropping the oldest character
 153:  * and appending the newest character
 154:  *
 155:  * the feed buffer contains only a short window of data from the 
 156:  * CI server for performance and memory considerations
 157:  */
 158: void updateFeedBuffer( char c )
 159: {
 160:   for( int i = 0; i < FEED_BUFFER_SIZE - 2; ++i )
 161:   {
 162:     feedBuffer[ i ] = feedBuffer[i + 1];
 163:   }
 164:  
 165:   feedBuffer[ FEED_BUFFER_SIZE - 2] = c;    
 166: }
 167:  
 168: /* searches the current feed buffer for specific
 169:  * build markers.
 170:  *
 171:  * this is Team City specific processing.  the Team City
 172:  * feed comes back with "Compilation failed" on a failed build
 173:  * and "Success" on a passed build.
 174:  *
 175:  * the feed comes over as XML packed in an RSS feed, which 
 176:  * explains the &lt;'s in the search strings
 177:  */
 178:  int checkFeedBufferForBuildStateUpdate()
 179: {
 180:   if( NULL != strstr( feedBuffer, "failed&lt;" ) )
 181:   {
 182:     Serial.print( "detected failed build: " );
 183:     Serial.println( feedBuffer );
 184:     
 185:     buildStatus = BUILD_FAILED;
 186:     return 1; 
 187:   }
 188:   if( NULL != strstr( feedBuffer, "Success&lt;" ) )
 189:   {
 190:     Serial.print( "detected passed build: " );
 191:     Serial.println( feedBuffer );
 192:     
 193:     buildStatus = BUILD_PASSED;
 194:     return 1;
 195:   }
 196:  
 197:   return 0;
 198: }
 199:  
 200: /* ensures that the build status LEDs match the pass/fail 
 201:  * state of the build
 202:  */
 203: void updateBuildStateIndicator()
 204: {
 205:   if( IS_FAILED( buildStatus ) )
 206:   {
 207:     Serial.println( "setting failure build status pin" );
 208:     digitalWrite( FAIL_PIN, HIGH );
 209:     digitalWrite( SUCCESS_PIN, LOW );
 210:   }
 211:   else if( IS_SUCCESS( buildStatus ) )
 212:   {
 213:     Serial.println( "setting success build status pin" );
 214:     digitalWrite( FAIL_PIN, LOW );
 215:     digitalWrite( SUCCESS_PIN, HIGH );
 216:   }  
 217: }
 218:  
 219: /* processes any incoming CI server feed, checking for build status
 220:  * indicators.
 221:  *
 222:  * note that this call will not return until there are no bytes
 223:  * available for processing from the ethernet shield
 224:  */
 225: void processTeamCityFeed()
 226: {
 227:   // short-circuit if there is no data available
 228:   if (! client.available()) {
 229:     return;
 230:   }
 231:  
 232:   // process until no data is available
 233:   while( client.available() )
 234:   {
 235:     // make sure to toggle the feed indicator
 236:     toggleFeedCheckIndicator();
 237:  
 238:     // read the next character ...
 239:     char c = client.read();
 240:  
 241:     // ... rotate the feed buffer with the new character ...
 242:     updateFeedBuffer( c );
 243:     
 244:     // short-circuit unless the incoming character is a semi-colon;
 245:     if( ';' !=c )
 246:     {
 247:       continue;
 248:     }
 249:  
 250:     // check the feed for specific status markers (failed, success)
 251:     if( checkFeedBufferForBuildStateUpdate() )
 252:     {
 253:       // ... if the build status was found, reset states to 
 254:       //  indicate current build status and restart the 
 255:       //  feed check timer
 256:       isRequestingFeed = false;
 257:       updateBuildStateIndicator();
 258:       client.stop();
 259:       feedCheckMetro.reset();
 260:     }
 261:   }
 262:  
 263: }
 264:  
 265: /* makes any necessary state changes to the red LED to indicate a 
 266:  * CI server connection failure
 267:  */
 268: void toggleConnectFailure()
 269: {
 270:   if( 1 == connectFailureMetro.check() )  
 271:   {
 272:     buildStatusPinState = LOW == buildStatusPinState ? HIGH : LOW;
 273:  
 274:     if( IS_ERROR( buildStatus ) )
 275:     {
 276:       digitalWrite( FAIL_PIN, buildStatusPinState );
 277:       digitalWrite( SUCCESS_PIN, LOW );
 278:     }
 279:   } 
 280: }
 281:  
 282: /* checks if the user has pressed the toggle button to force a feed query.
 283:  * 
 284:  * due to the wiring design this will be indicated by a rising edge on the 
 285:  * FORCE_PIN digital input
 286:  */
 287: boolean isTriggerButtonToggled()
 288: {
 289:   // query the state of the toggle button
 290:   int state = digitalRead( FORCE_PIN );
 291:  
 292:   boolean result = ( lastToggleButtonState == LOW && state == HIGH ) ;
 293:     lastToggleButtonState = state;
 294:     
 295:     return result;
 296: }
 297:  
 298: void setup()
 299: {
 300:   // configure digital pin modes
 301:   pinMode( SUCCESS_PIN, OUTPUT );
 302:   pinMode( FAIL_PIN, OUTPUT );
 303:   pinMode( FORCE_PIN, INPUT );
 304:  
 305:  
 306:   // setup the Ethernet library 
 307:   Ethernet.begin(mac, ip, gateway, subnet);
 308:  
 309:   // set up serial logging
 310:   Serial.begin(9600);
 311:  
 312:   // turn off all indicators
 313:   digitalWrite( 3, LOW );
 314:   digitalWrite( 4, LOW );
 315:  
 316:   // request first CI feed
 317:   requestTeamCityFeed();
 318: }
 319:  
 320: void loop()
 321: {
 322:   // check if the connect failure LED indicator state
 323:   //  requires an update
 324:   toggleConnectFailure();
 325:   
 326:   // check if the trigger was pressed
 327:   boolean trigger = isTriggerButtonToggled();
 328:  
 329:   if( trigger ) 
 330:   {
 331:     Serial.println( "trigger activated!" );  
 332:   }
 333:   
 334:   // check if the feed needs to be queried
 335:   if( 1 == feedCheckMetro.check() || trigger )
 336:   {
 337:     requestTeamCityFeed();
 338:   }
 339:   
 340:   // process any incoming feed data that has become available
 341:   processTeamCityFeed();
 342: }

Code Highlights

The code manages a lot of state, but it’s not terribly complex.

Most of the code up to line 54 is for setting up the Ethernet shield to work on your local network.  This is described pretty well in the library documentation.  From line 55 to 66, the states that are tracked by the program are defined:

  • feedBuffer: this is a small character buffer offering a window on the feed stream coming through the Ethernet shield.  Whenever a new character is available from an Ethernet connection, this buffer gets rolled so that the last character in the buffer is the newest character from the Ethernet connection; see updateFeedBuffer on line 158.
  • buildStatusPinState: this state tracks whether the current build indicator LED should be HIGH or LOW; e.g., during a connection failure, whichever build status LED is being used needs to blink.
  • feedCheckPinState: this state tracks whether the current build indicator LED should be HIGH or LOW during CI feed processing.  When the device is checking the CI feed, we want the active LED to blink quickly; see toggleFeedCheckIndicator on line 92.
  • lastToggleButtonState: this state records the last known state of the toggle button.  It is used to detect when someone presses the button by noting a change in button signal state going from LOW to HIGH.  See isTriggerButtonToggled on line 287.
  • buildStatus: this is the last recorded CI feed status; it can be passing (1), failing (-1), or connection error (0).

The feed processing is incredibly simple.  The RSS feed from Team City will contain  the string “failure” on a failed build, and “Success” on a build that succeeded.  The processTeamCityFeed function on line 225 manages this process.

 

The Device in Action

Here’s a quick (and poorly taken) video of the device in action.  I put a broken project into Team City, resulting in an initial red LED indicator light.  I then fix the build, and after a few seconds the device checks the feed and changes the indicator to green because the build passes.

 

 

Builduino in Action!

Next Phase

The device is hard-coded with a static CI server address and network configuration; in an upcoming post I’ll address this by showing you how to turn this build indicator into a standalone, configurable appliance capable of being configured through a web browser.

Add a Comment