incubate!(bang) has officially started back up - check incubatebang.com for details
JD Warren is a Ruby on Rails developer and extensive hardware hacker. Prior to joining isotope|eleven, he was a builder focusing on remodeling apartments and rental houses. In his spare time, JD builds robots, electronic circuits, and is a technical writer with work in several online and print publications and a recent book titled "Arduino Robotics".
Today I am going to walk through our recent continuous integration Traffic light notifier project that we just finished at the office. This project stemmed from my company's desire to immediately know if a developer has broken a software project, and what better way to do that than to have a huge red light flashing in your face. We connected an old salvaged traffic light fixture to our Jenkins CI-server that monitors the testing status of all of our current software projects. If all our tests are passing, the light stays green, if any test fails the light turns red to provide a visual notification of a problem. While Jenkins is running a test suite on any project, the yellow light will flash to let us know of the activity.
So how does one connect a 48" tall traffic light to a continuous integration server? With a Ruby script, an Arduino, and a few relays of course.
The Ruby code will create a serial connection with the Arduino to send data,
then create a web connection with the CI server to request the build status data
via our CI server's built in API. A quick look through the returned data will
give us a chance to see if there are any problems – if so, we'll send a signal
to the Arduino to change the light status, otherwise it stays green. The Ruby
script requires 3 gem dependencies to run: faraday, json, and serialport – all
available from rubygems.org (eg. gem install faraday).
# Isotope11 continous integration server Traffic-light
# Ruby script to monitor json output from Jenkins CI-server, and output the status of projects to a Traffic-light.
# If all builds are passing, the light is green.
# If a job is currently building, the yellow light flashes.
# If any job is failing, the red light is turned on and green turned off.
require "serialport"
require "json"
require "faraday"
require "net/http"
# create a new Serial port for the Arduino Uno which uses port /dev/ttyACM0. Older Arduinos should use /dev/ttyUSB0
sp = SerialPort.new("/dev/ttyACM0", 9600)
# wait for connection
sleep(1)
# create a new Faraday connection with the Jenkins server to read the status of each job
conn = Faraday.new('http://your_Jenkins_server_address.com')
puts 'go to loop'
loop do
begin
# grab the json from the jenkins api
response = conn.get('/api/json')
# parse the response into a list of jobs that are being monitored
jobs = JSON.parse(response.body)["jobs"]
# search each job to see if it contains either "anime" (building) or "red" (failing)
should_blink = jobs.detect{|j| j["color"] =~ /anime/ }
should_red = jobs.detect{|j| j["color"] =~ /red/ }
rescue
# if no response, assume server is down – turn on Red and Yellow lights solid
server_down = true
end
# check results of job colors
if should_blink
# something is building... flash yellow light!
puts "Something is building... flash yellow light!"
sp.write("1")
else
# nothing is building... turn yellow light Off.
#sp.write("2")
end
if should_red
# something is red... turn On red light!
puts "Something is broken... turn On red light!"
sp.write("3")
else
# nothing is red... turn On green light.
sp.write("4")
end
if server_down
sp.write("5")
end
# wait 5 seconds
sleep(5)
end
# close serial data line
sp.close
The Arduino board is fitted inside of the traffic light housing, and mounts to a perforated prototyping board from Radio Shack using some male-pin headers. Above the Arduino, are two small PC mount relays capable of switching up to 1 amp at 120vac – perfect for some low wattage light bulbs. The relay coils are controlled using a 5v signal, and only consume about 90mA at that voltage level, so we can use the Arduino's onboard 5v regulator to power the relay coils. Unfortunately, we cannot simply drive the relays directly from an Arduino pin because it can only supply around 40mA per pin and the inductive switching properties present in a relay might cause damage to the Arduino. Instead, we can use 2 small N-type signal transistors (either bjt or mosfet) to interface between each relay and the Arduino output pin. Building the relay board might require some hands-on tinkering, but is a rewarding task when complete (circuit schematic file included).
The Arduino code is simple, basically listening on the serial port for 1 of about 5 signals. If the Arduino detects a recognized serial byte, it will carry out a function to control the Traffic lights - there is no extra fluff, just what is needed. If you are having trouble locating an old Traffic light, or would like to build a smaller desktop version of the notifier, you can do so with only an Arduino and a few LEDs (red, yellow, and green) - you don't even have to solder anything!
// Isotope11 CI-server traffic light
// Arduino Uno with 2 relays (SPDT) attached to pins 4 and 7
// isotope11.com 2-9-12
// declare variables and output pins:
int inByte; // create a variable to hold the serial input byte
long lastTx = 0; // create a “long” variable type to hold the millisecond timer value
int yellow_light = 4; // create an output variable attached to pin 4
int red_green_light = 7; // create an output variable attached to pin 7
void setup() {
Serial.begin(9600); // start Arduino serial monitor at 9600bps
pinMode(yellow_light, OUTPUT); // set up pin 4 as an output
pinMode(red_green_light, OUTPUT); // set up pin 7 as an output
}
void loop() {
// check serial buffer
if (Serial.available() > 0){
inByte = Serial.read(); // read serial byte
Serial.println(inByte); // print serial byte
lastTx = millis(); // set the lastTx time-stamp variable equal to the current system timer value
// the serial bits “49” - “53” are detected when the numeric buttons “1” – “5” are pressed on the keyboard.
switch(inByte){
case 49: // if serial value received is "49" (number 1), blink yellow light
digitalWrite(yellow_light, HIGH);
delay(1000);
digitalWrite(yellow_light, LOW);
break;
case 50: // if serial value is "50" (number 2), turn yellow light off
digitalWrite(yellow_light, LOW);
break;
case 51: // if serial value is "51" (number 3), turn red light on (green off)
digitalWrite(red_green_light, HIGH);
break;
case 52: // if serial value is "52" (number 4), turn green light on (red off)
digitalWrite(red_green_light, LOW);
break;
case 53: // if serial value is "53" (number 5), turn green and yellow lights on solid (api error)
digitalWrite(red_green_light, LOW);
digitalWrite(yellow_light, HIGH);
}
}
else {
if ((millis() - lastTx) > 10000) {
// it has been more than 10 seconds (10000 milliseconds) since any serial information has been received
// assume there is a break in the PC connection, and turn red and yellow lights on solid.
digitalWrite(red_green_light, HIGH);
digitalWrite(yellow_light, HIGH);
}
}
}
The github repository is here.
A form that allows you to create 2 models at once can be quite useful. For example, let's say you have a User and Image model with a has_many/belongs_to relationship. In your new User form, it is ideal to attach an Image to the User and upload them with a single form submission. To do this, we need to allow the User model to accept attributes for the Image that will be created with it... enter accepts_nested_attributes_for. With a few easy steps, you can create a complex form in no time.
First, edit the User model with a has_many association and the accepts_nested_attributes_for tag for the model you would like to include (Image in this case): app/models/user.rb
class User < ActiveRecord::Base
has_many :images
accepts_nested_attributes_for :images
end
Make sure your Image model has a belongs_to association:
app/models/image.rb
class Image < ActiveRecord::Base
belongs_to :user
end
Now, add the fields for Image in the new User form:
app/views/user/new.html.erb
<% form_for @user do |f| %>
<%= f.label "Name" %>
<%= f.text_field :name %>
<% f.fields_for :images do |i| %>
<%= i.label "Image" %>
<%= i.file_field :attachment %>
<% end %>
<% end %>
As you can see from the form above, the fields beginning with "f." will be passed to User, and the fields_for :images, denoted by "i." will be passed to Image.
Lastly, you will want to create an empty Image model in the controller for the form to use, that is associated with the User:
app/controllers/user/users_controller.rb
class UsersController < ApplicationController
def new
@user = User.new
@image = @user.images.build
end
end
Obviously, your Users controller would need a standard Create method (not shown), but other than the above code, you need not add anything to the controller - rails takes care of the rest. And that's it! you should now be uploading mutliple models using the same form.