Building your own build status indicator with GoLang and RPi3

A few months ago I had been participating in the Docker Maze challenge, where I was awarded a Raspberry PI 3. The package was lying on my desk for more time than I care to admit, but finally I decided on a project which seemed interesting to me. I was going to make a build status indicator. Whenever one of my projects would fail, I would turn on some external peripheral that would let me know that something broke.

Connecting the relays

So over a few weeks I’ve ordered a 4-channel relay, an 8-channel relay, bought a bread board and bunches of pin cables. The Raspberry Pi 3 Model B has a 40 pin GPIO header. Substracting GND (8x), PWR(4x) and UART(2x) and reserved (2x) ports, there are 24 directly usable pins. Some have I2C and SPI modes available, but they are directly usable as a normal GPIO. So, in theory, you could run 24 relays with one RPi3.

The RPi2/3 pinouts

I installed the latest Raspbian onto the miniSD card and wired up the whole thing via the bread board. I’m pretty sure that there’s some good practice I should follow in regards to how I should wire the thing - but to be honest, I didn’t have a single electronics class in about two decades. This is my “final” state:

Wiring between the RPi3 and the relays

Both relays connect with the 5V VCC, GND, on each side of the pin header. Individual relays are controlled by GPIO pins (1-1). I connected the 4-channel relay to GPIO pins 26, 19, 13 and 6. The 8-channel one is connected to 24, 23, 18, 25, 12, 16, 21 and GPIO pin 20 respectively.

How to use the GPIO ports

The steps to use a GPIO pin are very simple:

  1. Export pin for usage/addressing,
  2. Set the direction of the pin (in = reading, out = writing),
  3. Set the value of the pin (0 = low, 1 = high)

To activate a relay you need to set individual pins to “output” direction and “low” value.

GPIO Pin interface

I created a GPIO_Pin interface which handles setting the direction of the pins, and the value to trigger individual relays.

type GPIO_Pin struct {
  Name string

func (r GPIO_Pin) Filename() string {
  return "/sys/class/gpio/gpio" + r.Name
func (r GPIO_Pin) write(where, what string) GPIO_Pin {
  filename := r.Filename() + "/" + where
  ioutil.WriteFile(filename, []byte(what), 0666)
  return r
func (r GPIO_Pin) Output() GPIO_Pin {
  return r.write("direction", "out")
func (r GPIO_Pin) High() GPIO_Pin {
  return r.write("value", "1")
func (r GPIO_Pin) Low() GPIO_Pin {
  return r.write("value", "0")

I return GPIO_Pin structs so the the calls can be chained like this: pin.Output().Low(). It makes the code a bit more concise as well.

Exporting the pins

As you’ll notice, the GPIO_Pin only handles setting the direction and the value of individual pins, but doesn’t export them. I created a GPIO struct which provides a Pin constructor, which also takes care of exporting the requested pin.

type GPIO struct{}

func (r GPIO) Pin(name string) GPIO_Pin {
  pin := GPIO_Pin{name}
  filename := pin.Filename()
  if _, err := os.Stat(filename); os.IsNotExist(err) {
    // export gpio pin
    ioutil.WriteFile("/sys/class/gpio/export", []byte(pin.Name), 0666)
  return pin

Until the pin is exported, the location /sys/class/gpio/gpio[pin] doesn’t exist. The Pin function checks for the existence of this location, and if it doesn’t exists, triggers the export. I’m pretty sure you need the latest Raspbian for this to work. This bash snippet should illustrate what I’m doing here with Go code.

#   Exports pin to userspace
echo "18" > /sys/class/gpio/export                  

# Sets pin 18 as an output
echo "out" > /sys/class/gpio/gpio18/direction

# Sets pin 18 to low
echo "0" > /sys/class/gpio/gpio18/value

# Sets pin 18 to high
echo "1" > /sys/class/gpio/gpio18/value

Source: Bash Script Control of GPIO Ports

Putting everything together

I decided to start small by animating the LED indicator lights on the relay boards. I created a loop that would cycle through all of the leds on the 4ch and the 8ch relays.

func main() {
  pins1 := []string{"26", "19", "13", "6"}
  pins2 := []string{"24", "23", "18", "25", "12", "16", "21", "20"}

  i := 0
  gpio := GPIO{}
  for i < 40 {
    i1 := (i) % len(pins1)

    i2 := (i) % len(pins2)

    i3 := (i + 4) % len(pins2)

    time.Sleep(200 * time.Millisecond)


Three relays are active with LED indicator on each loop iteration. The 4channel relay only lights up one led and moves to the next one in the next iteration, while the 8channel relay lights up the Nth, and the Nth+4. Whatever, I better show you a video here.

Running Go from Docker

You can run this whole thing in Docker if you’d like. You can mount the /sys volume without adding any additional privileges to the container. It’s generally a good idea to avoid --privileged in favor of --cap-add if you need something specific. This works on the RPi without problems:

docker run -it --rm=true -v /sys:/sys \
           -v $(pwd):$WORKDIR -w $WORKDIR \
           apicht/rpi-golang go run main.go

Wrapping up

Using GPIO pins on the Raspberry PI is qute easy, and writing this interaction in Go is done without complications. I still have a lot of work to do however to expose these GPIO ports to information from our Buildkite CI. It seems I will have to write an API or two to achieve this - it’s a good thing that I wrote API Foundations in Go.

While I have you here...

It would be great if you buy one of my books:

I promise you'll learn a lot more if you buy one. Buying a copy supports me writing more about similar topics. Say thank you and buy my books.

Feel free to send me an email if you want to book my time for consultancy/freelance services. I'm great at APIs, Go, Docker, VueJS and scaling services, among many other things.