Easy IoT: An Internet of Things build status lamp
Contrary to popular belief, internet connected hardware doesn’t have to be complicated or expensive. No exotic embedded WiFi boards needed here. You’ll just need a few standard bits from your spares box to amaze your friends with your own internet connected CI status lamp!
Lamp control is by a single Atmel ATtiny2313 which communicates to a host computer via Bluetooth. Commands to change the lamp pattern can be received from anywhere on Earth via XMPP messages, greatly simplifying deployment of the device to installations behind routers and firewalls. At its heart this project provides a general purpose XMPP to serial interface that can be used as the basis for other lightweight internet connected projects.
Background
Most software development teams now practice continuous integration in order to maintain software quality. This provides instant feedback of the state of the project, highlighting any problems early so they can be fixed as soon as possible. Most of the time this feedback is provided through a web interface such as Jenkins which can be viewed on the developer’s computer or, as is often the case, on a Big Screen in view of the development area. Often this central screen also cycles through displaying other useful and informative platform metrics, but you might have to wait a minute or more to see the build state. How much better to have some dedicated hardware!
And so this was built – a three lamp signal stack that immediately communicates the state of the build and whether a build is in progress. There are three lamp states:
- Green solid: the last build passed and the build is stable, ready for deployment
- Red flashing: the last build has failed and the build is broken. Someone better take a look at it! The transition to the failed state is heralded by the sounding of an old British Telecom type 33A buzzer to add to the gravity of the situation.
- Orange pulsing: there’s a build currently in progress. Bets can now be placed on the outcome.
The hardware
The hardware is simply an Atmel ATtiny2313 with its UART connected to a Bluetooth serial module. It uses four output lines to control the three lamps plus the buzzer. The lamp lines use the 2313’s PWM outputs via some hefty power transistors to provide the pleasant fading effect you can see in the images above.
The ATtiny accepts simple commands on the serial port to control the lamps. These consist of two characters each. The first selects the output target: r=red, o=orange, g=green, b=buzzer, while the second selects the state: 0=off, 1=on, f=flash, p=pulse (the buzzer ignores this parameter and will only sound once for two seconds whatever you ask it to do). So for example, the command ‘rf’ will start the red lamp flashing. If the command is executed successfully then the ATtiny will reply with a cheerful ‘OK’, otherwise ‘ERROR’.
The signal stack is from one of the many fine suppliers on Aliexpress and has performed splendidly, apart from the bulb life which was shocking. I wanted to use good old fashioned incandescent lamps in this design to evoke a ye olde tyme feeling of factory machinery. Indeed, they do produce a lovely glow, get warm and don’t have that harsh on/off that LEDs have. However if you intend to go down this path too, then don’t. Take it from me and stick to LEDs. Here’s why:
- You’ll need a power supply capable of a couple of amps to drive them. These lamps are nominally 5W@12V (~400mA) each, but in reality varied hugely. Incandescents also have a much lower resistance when cold, resulting in starting currents over 10 times the steady state current.
- You’ll need some big transistors to handle this current too, and they’ll get warm. And by warm I mean hot. You’ll get better performance by using a FET, but either way there’ll still be a lot of current flowing for not a lot of light output.
- The bulbs will fail. The bulbs that came with the signal stack were terrible and two had failed within a month of installation (although clearly this was exacerbated because our build is too reliable, resulting in the green light being permanently lit, ahem). So the green bulb has been replaced by three white LEDs and an 82Ω resistor in series, while the red bulb has been replaced with the one from my car indicator, but that’s ok because I can’t turn left anyway.
The software
Unfortunately our Jenkins installation is many miles away and on the other side of a firewall from the lamp hardware. So how do we get the data to it? We could periodically poll the server from the office, say every minute, to keep up with the current build state. But this is terribly inefficient as 99.98%* of the time the state won’t have changed. Also the pipeline can sometimes run really quickly and so the lamps could be way behind the actual state of the build or miss it altogether. So I’ve opted to use Jabber/XMPP messaging to push the current state of the build to the hardware as soon as it changes. This ensures the delay is as short as possible (typically only one or two seconds) and also works behind routers and firewalls without having to get involved with any port forwarding that your corporate IT department isn’t going to be sympathetic to.
This also give you the option to use your own XMPP server, such as Openfire, or one of the many public (and free!) servers which solves probably the biggest obstacle to implementing this. Despite the convenience of using a 3rd party messaging implementation, I’m running my own Openfire instance as I want to control who can message the system and I also want to drop undelivered messages if the client is offline, but these issues could also be managed by maintaining a whitelist of control addresses and getting the client to drop messages with timestamps older than a few seconds.
The same machine that drives our Big Screen also runs a Java XMPP client that uses the Smack XMPP library to listen for the control messages. After command execution the XMPP client returns the response from the ATtiny2313 back in a response message. This is quite useful during development as it enables limited remote diagnostics as you can see that the hardware is still connected and responding. The serial link that controls the hardware is accessed by calling an external C program (serial control is best handled natively). These two programs form a general purpose XMPP to serial interface that can be used as the basis for other IoT-on-the-cheap™ projects. The code for the client can be found on Github together with the firmware for the lamp hardware.
Integration with Jenkins
I’ve added the Notification Plugin to our Jenkins builds which sends the result of the build to a URL via a piece of JSON, similar to this:
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "name":"website_build", "url":"job/website_build/", "build": { "full_url":"http://jenkins1/job/website_build/198/", "number":198, "phase":"FINISHED", "status":"SUCCESS", "url":"job/website_build/198/" } } |
The build->phase value is one of ‘STARTED’ or ‘FINISHED’, while build->status is one of ‘FAILURE’, ‘ABORTED’ or ‘SUCCESS’. You can see that these values can easily be used directly to set the state of the lamps. In the case that you’ve implemented a pipeline in Jenkins it’s a bit less straightforward. Jenkins pipelines are created by each job triggering the next job, so there’s no single ‘pipeline’ object. To untangle what’s happening with the pipeline from the bits of JSON fired after each job, some logic has to be implemented along these lines:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// this relies on the jobs running as a pipeline // running jobs individually can result in odd lamp states $state = null; // set the 'building' state when the first pipeline job starts if($status->build->phase === 'STARTED' && $status->name === 'first_pipeline_job') { $state = 'building'; } if($status->build->phase === 'FINISHED') { // if anything fails then the pipeline stops // so we can confidently set the fail state if($status->build->status === 'FAILURE') { $state = 'failed'; } // similarly for aborted jobs if($status->build->status === 'ABORTED') { $state = 'aborted'; } // if the final pipeline job passes then we can set the pass state if($status->build->status === 'SUCCESS' && $status->name === 'last_pipeline_job') { $state = 'passed'; } } |
You can then use something like the XMPPHP library to send the XMPP control message to the lamp hardware.
*made up but plausible statistic