System Calls

What are System Calls?

Systems calls are the interface between userspace and kernelspace. They are essentially procedures belonging to the operating system that you can call when you want something done that can’t be done in userspace. This includes anything system related, accessing devices, files, managing processes and networking. On my 64bit machine with a 3.5.4 kernel there are 344 system calls (cat /usr/src/linux/arch/x86/syscalls/syscall_64.tbl | egrep -v ‘^#’ | egrep -v ‘^$’ | wc -l) .

Calling procedures is generally a simple affair, just push the current context onto the stack and jump to the new address. Calling a system call on the other hand is quite a bit more difficult. The kernel executes in a higher privileged mode on the CPU and userspace executes on a lower privilege mode. To transfer through to the higher privilege mode a CPU exception is triggered, which then launches in the higher mode, however as you can’t pass arguments through this mechanism they are placed in the CPU registers before the exception. Newer CPUs have implemented SYSENTER/SYSEXIT calls (or similar) that work differently and are designed to speed up the process.

You can see the system calls that a process makes while it is running by using strace. Strace runs a program in such a way that it can intercept all the system calls it makes and gather details about them. Watching a program’s system calls is a good way to see what it is doing, if its hanging on IO, if it is stuck in an infinite loop or if it has deadlocked.

Debugging with strace

Interpreting the output of strace can be daunting, the average binary does many things on startup and shutdown that you’ve probably never coded. Lets dissect an example of stracing the date command:

execve("/bin/date", ["date"], [/* 63 vars */]) = 0
brk(0)                                  = 0xabd000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f13febdf000

You can see here the initial execution of the date binary. This is done with the execv call, which asks the operating system to replace the current process with the binary you provided and begin execution.

You can see the next thing that happens is a call to brk(0), this is a way to grab the processes current program break. The program break is the location of the end of the data segment (actually its the first location after), moving the program break up has the effect of allocating more memory to the process, and moving it down has the effect of deallocating it. This is the way malloc allocates memory.

Then mmap is called to allocate 4096 bytes at a location of the kernels choosing (addr is NULL). Reads and writes are allowed, the memory is anonymous (not backed by a file) and private to this process. This initial memory is associated with the loading of dynamic libraries by the dynamic linker.

access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=143007, ...}) = 0
mmap(NULL, 143007, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f13febbc000
close(3)                                = 0

So this is the dynamic linker running trying to find all the libraries that out program requires. The first thing it does is try to load any libraries that may be listed in /etc/ld.so.preload, this is the same way the LD_PRELOAD environment variable works, except backed by a file.

Next it opens /etc/ld.so.cache, checks its file attributes, maps the file to memory, then closes the file. This file is created by the command ldconfig based on the file /etc/ld.so.conf and other files it includes (its common to include /etc/ld.so.conf.d/*.conf). The /etc/ld.so.cache file contains, in machine readable format, a list of all the libraries in the previously mentioned files and their location.

open("/lib64/librt.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`\"\240\2356\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=48128, ...}) = 0
mmap(0x369da00000, 2128984, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x369da00000
mprotect(0x369da07000, 2093056, PROT_NONE) = 0
mmap(0x369dc06000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x6000) = 0x369dc06000
close(3)                                = 0

Here we are loading the librt library. The librt library is a part of glibc which provides realtime extensions. You can see here that the open call returns with file descriptor 3. Then in the mmap call you can see it mapping the library with PROT_READ and PROT_EXEC permissions, keeping it private and denying writes. Loading things this way means that if another program loads the same library it can share the same page in physical ram. It then protects a portion of that libraries memory from any read or write. It maps another section of the file as read write, but not written to the file, and finally closes the file descriptor.

open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260\27\242\2346\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2076800, ...}) = 0
mmap(0x369ca00000, 3896632, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x369ca00000
mprotect(0x369cbad000, 2097152, PROT_NONE) = 0
mmap(0x369cdad000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1ad000) = 0x369cdad000
mmap(0x369cdb3000, 17720, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x369cdb3000
close(3)                                = 0

This is basically the same thing as the above library, except it is the libc library. The libc library provides the methods specified in the ANSI C and POSIX C libraries.

open("/lib64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\320k\340\2346\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=145176, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f13febbb000
mmap(0x369ce00000, 2208760, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x369ce00000
mprotect(0x369ce17000, 2093056, PROT_NONE) = 0
mmap(0x369d016000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16000) = 0x369d016000
mmap(0x369d018000, 13304, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x369d018000
close(3)                                = 0

Again, another library being loaded into memory. This time its the libpthread library, which provides POSIX thread support.

mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f13febba000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f13febb9000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f13febb8000

Here we are mapping three pages of memory using mmap. This memory is not backed by a file and is private to the process.

arch_prctl(ARCH_SET_FS, 0x7f13febb9700) = 0
mprotect(0x60d000, 4096, PROT_READ)     = 0
mprotect(0x369dc06000, 4096, PROT_READ) = 0
mprotect(0x369cdad000, 16384, PROT_READ) = 0
mprotect(0x369d016000, 4096, PROT_READ) = 0
mprotect(0x369c821000, 4096, PROT_READ) = 0
munmap(0x7f13febbc000, 143007)          = 0
set_tid_address(0x7f13febb99d0)         = 4110
set_robust_list(0x7f13febb99e0, 24)     = 0
rt_sigaction(SIGRTMIN, {0x369ce06720, [], SA_RESTORER|SA_SIGINFO, 0x369ce0f500}, NULL, 8) = 0
rt_sigaction(SIGRT_1, {0x369ce067b0, [], SA_RESTORER|SA_RESTART|SA_SIGINFO, 0x369ce0f500}, NULL, 8) = 0
rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
getrlimit(RLIMIT_STACK, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0

These lines are likely the setup of the threading for the application. arch_prctl is being used to set the FS register, which the pthread library uses to store a pointer to a struct containing the thread local information for that thread. mprotect is being called on all the memory that above was made protected from both reads and writes to make it read only. Some memory is unmapped to return it to the system with munmap. set_tid_address is called to set the tread id, and get the pid of the process (4110).

We then see a bunch of calls to rt_sigaction and a rt_sigprocmask which is basically plumbing all the signals needed for threading. Finally getrlimit is used to see that the limits on stack sizes are.

brk(0)                                  = 0xabd000
brk(0xade000)                           = 0xade000
brk(0)                                  = 0xade000

Malloc() isn’t a system call, instead it uses other calls such as brk() and mmap() to create memory. Because a calls to these methods are expensive libc batches them up. So malloc() when initially called allocates a bigger chunk of memory than you asked for, and as you ask for further bits it give you chunks of the same memory you allocated. What you can see here is more than likely a malloc() call checking the current program break (the end of the heap), then moving it ahead (to increase the heap size) then checking where it is again.

open("/usr/lib/locale/locale-archive", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=104997456, ...}) = 0
mmap(NULL, 104997456, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f13f8795000
close(3)                                = 0

So all the library loading and setup is now done. What we are doing here is loading the locales file. This will be done by most things that use libc, but its particularly important for the date command as date formatting changes drastically in different locales. Basically we open a file descriptor, stat it (usually to find size, permissions and such), map the file into memory, then closes the file descriptor. Because mmap has been used to get the file into memory we don’t need to use the read() and write() calls to access it. This will prevent an expensive system call, but the first time you access each page of the data a page fault will be generated, which could be just as expensive.

open("/etc/localtime", O_RDONLY)        = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=2183, ...}) = 0
fstat(3, {st_mode=S_IFREG|0644, st_size=2183, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f13febde000
read(3, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\4\0\0\0\4\0\0\0\0"..., 4096) = 2183
lseek(3, -1398, SEEK_CUR)               = 785
read(3, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\5\0\0\0\5\0\0\0\0"..., 4096) = 1398
lseek(3, 2182, SEEK_SET)                = 2182
close(3)                                = 0
munmap(0x7f13febde000, 4096)            = 0

Here we can see date is opening the /etc/localtime file. This file contains information about the current timezone (it is often a symlink to the correct file). You can see here, the file is opened, read, a couple of seeks happen (to find particular entries) and the file is read again from the new point. Finally having retrieved the required information from the file, it is closed and its memory is unmapped.

fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f13febde000</pre>
write(1, "Thu Sep 27 16:48:57 EST 2012\n", 29Thu Sep 27 16:48:57 EST 2012
 ) = 29

At this point the date command has all that is required to calculate the date, its text representation and display it. You’ll notice that there has been no call to the operating system to fetch the time, this is because modern x86 machines include a special method called vsyscalls that allows userspace programs to make certain system calls without ever context switching. One of the system calls implemented via this method is the one to get the current unix timestamp, so date doesn’t need to call into kernelspace for this.

close(1)                                = 0
munmap(0x7f13febde000, 4096)            = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

STDOUT and STDERR are closed and the process group is ended. The date command is now finished.

Simple Configuration Service

The Basics

So I’ve seen a few implementations of ‘config services’ being built in all sorts of {ruby,python,php,java} applications. It really gets on my nerves to see people implementing things that could be much simpler. As Engineers we should really strive to get the most simple product built in a flexible way. So here is my go at implementing a ‘config service’ (basically a key value store optimised for reads). This was originally tweeted in a set of four tweets (1, 2, 3, 4).

Tweet 1

A 'Config Service' that fits in a tweet:
<Location /config>
  DAV svn
  SVNPath /var/svn/config
  SVNAutoversioning on
</Location>

Simply place this in your apache server configuration. You’ll need to create the SVN repo, which can be done with ‘svnadmin create /var/svn/config’ then chmod it to be writiable and readable by apache. What we are doing here is turning on WebDAV (which lets us upload and make directories on the apache server) and turning on SVN backed autoversioning which means that every change we make will be recorded in the SVN repo.

Tweet 2

To get a variable:
curl -s http://config/config/namespace/key

This simply performs a GET request for a key. You can’t do this until you add some keys to the server.

Tweet 3

To change a variable:
echo "value" | curl -T- http://config/config/namespace/key

This creates/modifies a key. You will need to have the namespace created first, which is in the next tweet.

Tweet 4

Make a new namespace:
curl -XMKCOL http://config/config/namespace

This creates a namespace to put keys in.

Extensions

Users

The service above doesn’t support users, so anyone can modify the keys, and the svn logs look like this:

------------------------------------------------------------------------
r5 | (no author) | 2012-10-15 12:36:33 +1100 (Mon, 15 Oct 2012) | 2 lines

Autoversioning commit:  a non-deltaV client made a change to
/namespace/key
------------------------------------------------------------------------

All we need to do is require the user to authenticate when doing PUT requests to the server. You can use this method to restrict certain namespaces too. Generate a htpasswd file for the users and place it in /var/svn/users and change the code to read as follows.

<Location /config>
  DAV svn
  SVNPath /var/svn/config
  SVNAutoversioning on
  AuthName "Config Service"
  AuthType Basic
  AuthUserFile /var/svn/users
  <LimitExcept GET>
    require valid-user
  </Limit>
</Location>

Viewing the logs

Simply do this:

svn checkout http://config/config
cd config
svn log

You can also use this to make bulk/atomic changes in the same way you would make changes to a subversion repository.

Ansible Talk @ Infra Coders

Here are the notes that I used in my talk at Infrastructure Coders. Each section was also put on the screen as a ‘slide’. The configuration that I used in the demo is available at GitHub. A full video of the meetup is available on the Infrastructure Coders Youtube Channel, my talk starts at 25:05.

Ansible
--------

0. There is nothing in the hat
  - Start a RHEL install
    - Cmd line: console=ttyS0 ks=http://admin01/ns3.cfg
    - If you want to follow the demos grab the ansible config from my github
    - You will need to substitute hostnames in the ansible hosts file
    - You should copy the firewall config from ns2 (remove port 647 if paranoid)

1. The problem
  - Ansible is the combination of several functions
  - There was a plan to build config management on func
  - However func is a pain to setup
  - Puppet and Chef have a steep learning curve
  - Ansible was also built to simplfy complexrelease procedures
  - You need to know ruby to extend Puppet/Chef

2. Ansible
  - Designed so you can figure out how to use it in 15 minutes
  - Designed to be easy to setup
  - Doesn't require much to be installed on the managed host
  - Designed to do config management/deployment/ad hoc
  - Other people do security better, just use SSH
  - You can extend ansible in any language that can output JSON

3. Simple Ansible Demo
  - Ansible hosts file
  - Ansible can be run directly on the command line
    - Run cat /etc/redhat-release
    - Get info using the setup module
  - It can prompt for auth, or use key based auth
    - On the new machine show it prompting
    - Run the rhelsetup script on the new machine
    - Install vim-enhanced

4. Playbooks
  - This is the method of scripting Ansible
  - Done in YAML
  - Executed in order *gee thanks puppet*
  - Designed to be as easy as possible

5. Example playbook
  - Playbook for the name servers
    - https://github.com/smarthall/tildaslash.com/blob/master/playbooks/zones.play
    - Can have multiple plays in a book
    - Can serialise if you dont want all to be down at once
  - Template config for the name servers
    - https://github.com/smarthall/tildaslash.com/blob/master/playbooks/zones/named.conf.j2
  - Firewall install script
    - https://github.com/smarthall/tildaslash.com/blob/master/playbooks/firewall.play

6. My thoughts
  - Config management has been around a while, its going from art to science
  - Ansible covers more ground than puppet and chef do
  - Ansible doesn't compromise on simplicity to do that
  - I don't have to focus on the nodes, I can focus on services
  - There is something missing
    - Disk config is done in kickstarts
    - Network config can't be done by Ansible
    - Need to find a way to cover both with one

The playlist of all the videos is available at Youtube.

Jabberd2 lockup after authorisation

I recently spent forever debugging an issue where the server would lock up right after the client sent authentication details. What made it worse was that XMPP clients seem to have very poor error messages, so its near impossible figuring out what went wrong. That and Wireshark becomes much harder when using encrypted sessions. Finally though I managed to track it down and this page is mainly as a reminder to myself.

The problem is due to the fact I configured my server to not allow account registrations. I setup the following line in c2s.xml:

<id require-starttls='true'
        pemfile='/etc/jabberd/server.pem'
        password-change='true'
        instructions='Registrations not allowed...'
    >danielhall.me</id>

Followed by this in sm.xml:

<!--  
<auto-create/>
-->

Then I inserted all my users into the authreg table manually using queries like:

INSERT INTO authreg VALUES ('username', 'danielhall.me', 'sup@Hs3cr3tPassw0rd');

The problem here is that you’ve manually created the authorisation for the account, and not created the session. The best way to deal with this is to remove the comments around the auto-create tag in sm.xml. This isn’t a problem as users not in the authreg table will not even be able to reach the session manager
before their authorisation is rejected.

Arduino Traffic Lights

The completed Traffic Light
Please Note: The instructions here are provided ‘as is’ with no guarantee or warranty whatsoever. In no circumstances should they be used to build a traffic safety device. The traffic light device I built is a novelty and is used as such.

Materials

1 USB traffic light hubMaterials for a USB Traffic Light (search ebay for ‘traffic light hub’)
1 Atmega8u2 breakout board
1 each of 9mm Red Green and Orange LEDs
3 470 Ohm resistors
4 thin patch wires (preferably different colours)
1 USB mini cable
1 Pack of Black Sugru
Solder
Hot Glue
Corrugated Cardboard

Equiptment for the USB Traffic LightEquipment

A Dremel, or similar cutting tool
Hot Glue Gun
Soldering Iron
Wire Strippers
Wire Cutters
Spudger tool, or guitar pick, stanley knife or fingernails
Linux PC (with avr-gcc and dfu-programmer commands installed)

Details

1. Use the spudger to open the traffic lightThe Traffic Light once pulled apart

The weakest part of the traffic light is the stem but it is nearly impossible to open it from there. The approach that I took was to pry the top open a little, then pry the bottom open a little, then carefully pull the two halves apart at the same time. This ensures that you dont snap the plastic holding the stem together. If you don’t have four wires from the materials list, you can strip the USB cable and use those since it contains four differently coloured wires.

2. Gut the traffic lightThe traffic light after reordering the windows and removing hub board

Remove the USB hub circuit board. You can use this board later in another project or two, should you do you will probably want to cut off the LEDs. In my board the little plastic windows were in the wrong order for Australian traffic lights. Luckily this is easy to fix, simply pop them out and press them back in the correct place. Feel free to put the windows in any order that you wish, just remember to alter the program later before compiling and flashing it.

3. Make roomThe Traffic Light after cutting out the inside

Its a little hard to fit the Circuitry into the case with the two supports that held the hub in place, additionally you need a little extra space for the thickness of the cardboard. You can get all this by using a Dremel to remove the supports and some of the plastic around the USB sockets. Be careful to not hit the side of the case as I did on my first one as the plastic is thin and easily blemishes the outside. The photo shows one before modification on the left and after a fight with the Dremel on the left.

4. Make the cardboard circuit boardThe front of the cardboard board

The process of getting a circuit board built takes too long for a quick hack like this and I didn’t have any protoboard lying around. This all means that we get to make a crazy cardboard circuit board. The best thing about cardboard is that you can draw on it as you build it, and you can easily cut it to fit the interior of the device easily. Basically put the LEDs and resistors through the cardboard next to each other. For ease of wiring put align all the LEDs to have their long legs in the same direction. Wire each short leg to a resistor and the free legs of all the resistors to a single black wire. Finally wire The back of the cardboard boardeach of the long legs of the LEDs to a different coloured wire. Once this is done you should test each lead and LED to make sure it is correctly wired. You can do this by connecting ground to the black lead, and 3-5V to the coloured leads.

5. Connect to the Breakout board

Now we take out the SparkFun Atmega8U2 breakout and solder it to the LEDs on our cardboard board. This is pretty simple, basically you solder the wire from the resistors to the hole labelled GND, the green LED to PB7, the orange LED to PB6 and finally the red The breakout board showing the wires coming from the cardboard boardLED to PB5. You will likely want to cut the wires so that they reach the board where it will sit, and only have a few extra millimeters. If you leave too much room then you will have issues trying to put the wires inside the case, and of course if you don’t leave enough you wont be able to get the breakout board to sit in the right place.

6. Load the software on the device and test

You can find the code to build the firmware on my GitHub account. Provided that you have avr-gcc and dfu-programmer installed you should be able to simply clone that repository and type ‘make all’ inside it. If that refuses to work for some reason though, I have attached the output of compilation, a hex file (which is uploaded as a txt file to stop wordpress whining). You can download the hex file here: USBTrafficLight.hex. This hex file, or the one that’s output from programming can be programmed onto the device easily. First you put the device into bootloader mode by plugging it into the computer then hit the reset button. Finally you run the following commands:

dfu-programmer at90usb82 erase # Erase MCU
dfu-programmer at90usb82 flash USBTrafficLight.hex # Flash MCU
dfu-programmer at90usb82 reset # Reset MCU

You will have to unplug and plug the device back in to get it out of bootloader mode. Once you have programmed the device you should run a test to make sure it works. You can do this by writing characters to the virtual serial port the device creates. The following commands will do this for you:

echo 'g' > /dev/ttyACM0 # Should be green
echo 'o' > /dev/ttyACM0 # Should be orange
echo 'r' > /dev/ttyACM0 # Should be red

7. Cut a hole in the baseShowing the hole cut in the base of the back piece of the Traffic Light

This is the part I’m least happy with. Here you have to cut a hole in the base so that you can plug in a mini usb cable. I generally cut a rectangular hole in the back piece of the traffic light that is about the same size as the USB cable I’m using. This rectangle usually goes about halfway up the base, and goes right to the bottom while being a little bit wider then a USB mini cable. I also cut a small drill hole to allow access to the reset button for loading new firmware. Make sure you test that you can plug in the USB mini cable. Instead of cutting a hole in the base I’ve been thinking about building a USB cable into the device. This is difficult because the USB mini port on the breakout is SMD and the pins are not broken out. If you have any ideas on the best way to do this, please let me know in the comments.

8. Install the cardboard board and the breakout boardShowing how I glued in the cardboard board

The cardboard section should be easy to slide into the back case of the device. Should there be an issue making things fit you can always trim the cardboard, or pad it with paper. You should glue the cardboard into the back through the USB port hole where the hub was. This accomplishes two things, it attaches the cardboard to the back, and it partially fills the holes that we will later fill with Surgu, saving you a little Sugru. If you bought clear LEDs instead of diffused ones you may wish to glue something to diffuse the light to the front (I use baking paper, sometimes two layers). Install the breakout board by putting a little hot glue on the bottom, pushing it into place, plugging in the mini USB cable then finally wiggling it into the perfect position. You’ll also want to tie up your wires using a little electrical tape to make them easier to manage in the last step.

9. Install the front and patch holesThe Traffic Light USB port surrounded by Sugru to give it a nice clean look

Mould some Sugru into the holes left behind from where the USB hub was, and into the extra space around the USB mini plug. Make sure that you continuously test the USB mini plug to ensure you don’t add too much Sugru, also do not get any Sugru into the plug. You can use soapy water and rubbing to make the Sugru surface smooth if you wish. Then install the front of the case and leave the Sugru to set, which takes about 6-12 hours. Once the Surgu has set the device is ready to use.

Instructions for use

The interface to the device is implemented as a USB to Serial adapter, however since there is not serial interface, and the entire device is self contained we don’t have to implement the entire spec. A USB to Serial device is implemented by sending characters and control messages over the USB bus. However because there is no serial interface we can ignore all the control messages. The firmware above simply grabs the characters from the stream and acts on them. This means that the device will work regardless of the baud rate, stop bits or parity settings. On Linux and OSX this means that all you have to do to control the device is to echo characters to the character device. You can send ‘g’ for green, ‘o’ for orange, ‘r’ for red and any digit from 0-7 for all the possible light configurations. A simple test script for a Linux PC looks like this:

#!/bin/bash

while /bin/true; do
  echo 'g' > /dev/ttyACM0
  sleep 2
  echo 'o' > /dev/ttyACM0
  sleep 2
  echo 'r' > /dev/ttyACM0
  sleep 2
  echo 'a' > /dev/ttyACM0
  sleep 2
done

The completed Traffic Light