Controlling Blinkt! using Golang

In the previous article we looked at how I had become involved in an open source community project which centred around making available a Golang equivalent of Pimoroni's Python library. In this post I wanted to demonstrate use of the Golang library by porting one of the Pimoroni examples.

Solid Colours

One of the simplest examples in the Python examples is solid_colours.py. This example cycles the Blinkt through red, green and blue at predetermined intervals. Lets take a look.

Python

The python code to do this is really quite straight-forward, which makes it a really good example to start with if you're new to Golang. It allows you to focus on how to make Golang do the same without getting bogged down in complexity of the implementation.

while True:  
    if step == 0:
        set_all(128,0,0)
    if step == 1:
        set_all(0,128,0)
    if step == 2:
        set_all(0,0,128)

    step+=1
    step%=3
    show()
    time.sleep(0.5)

A simple loop incrementing a counter which uses a modulo function to choose which colour to display. A half second delay is placed within the loop so that we give our eyes long enough to perceive the change of colour.

Golang

So, we've seen our starting point, but the reason we're here is that we want to build this out in Golang. Lets start with the scaffolding - the bits we'll need regardless of the example:

package main

import . "github.com/alexellis/blinkt_go"

Nice and simple, we declare this package as main and import the Raspberry Pi Golang library that we looked at in the previous article. You'll notice that we are using dot notation in the import - this is not ideal, and is only really advised for testing code.

As this example is of limited complexity there are no functions required outside of main, so lets head straight into the body of the program. First we need to construct a new Blinkt object. Every program will need one of these as its the means by which you access the functions to manipulate the hardware.

blinkt := NewBlinkt()  

In the current version of the library initialising Blinkt! with a brightness value is optional. If we wanted to do this we would pass a value between 0.0 and 1.0 into the NewBlinkt() method:

initialBrightness := 0.5  
blinkt := NewBlinkt(initialBrightness)  

Next we need to think about the type of program we are creating, is it one that we wish to run and exit leaving the Blinkt! set to a desired state, or do we want to Blinkt! to reset on exit? Well, this particular example runs an infinite loop, which we'll have to break using CTRL+C, and at the point in which it breaks we want the Blinkt! to go dark. To do this we need to set SetClearOnExit() to true.

blinkt.SetClearOnExit(true)  
blinkt.Setup()  
Delay(100)  

Once we've set the Blinkt initialisation options we can call Setup() to initialise GPIO via the lower level WiringPi library. There's a little delay in there to allow time for Setup() to complete.

Lights On

Right, so we've successfully initialised a Blinkt object in Golang but nothing will happen until we tell the pixels what we want them to do. this is where the program starts to look similar to the earlier Python version.

step := 0

for {

    step = step % 3
    switch step {
    case 0:
        blinkt.SetAll(128, 0, 0)
    case 1:
        blinkt.SetAll(0, 128, 0)
    case 2:
        blinkt.SetAll(0, 0, 128)
    }

    step++
    blinkt.Show()
    Delay(500)

    }
}

Here we can see the SetAll() function that I talked about in the previous article being put to use. We have created three states, one for Red, one for Green and one for Blue, and we use an incremental counter with modulo arithmetic to carry us from state to state with a half second delay between states. It's important that Show() is called to apply the values set in SetAll() to the hardware.

Bringing it Together

We've now got a viable Golang program through which was can manipulate the Blinkt! hardware via GPIO.

package main

import . "github.com/alexellis/blinkt_go"

func main() {

    brightness := 0.5
    blinkt := NewBlinkt(brightness)

    blinkt.SetClearOnExit(true)
    blinkt.Setup()
    Delay(100)

    step := 0

    for {

        step = step % 3
        switch step {
        case 0:
            blinkt.SetAll(128, 0, 0)
        case 1:
            blinkt.SetAll(0, 128, 0)
        case 2:
            blinkt.SetAll(0, 0, 128)
        }

        step++
        blinkt.Show()
        Delay(500)

    }

}

Other methods

To help you achieve different an more impressive effects the library makes a number of of other methods available:

  • blinkt.Clear() - Pretty self-explanitory this one. It will set all the pixels to 0

  • blinkt.SetAll(r int, g int, b int) - As we've already seen this will set all the pixels to the RGB values passed in to the function.

  • blinkt.SetBrightness(brightness float64) - One of the newer functions this sets the brightness of all the pixels enabling dynamic manipulation during runtime.

  • blinkt.SetPixel(p int, r int, g int, b int) - Set the colour value of a single pixel. The pixels are zero indexed.

  • blinkt.SetPixelBrightness(p int, brightness float64) - Set the brightness of a single pixel. The pixels are zero indexed.

The nice feature here is that the final four each return a Blinkt object, which means we can chain the methods to set the colours and brightness in one hit. We could even add Show to the end:

switch step {  
    case 0:
        blinkt.SetAll(128, 0, 0).SetBrightness(0.5)
    case 1: 
        blinkt.SetBrightness(0.1).SetAll(0, 128, 0)
    case 2:
        blinkt.SetAll(0, 0, 128).SetBrightness(0.9).Show()
        }

Through this more granular control we are able to create all manner of intricate effects. Here's an example of blinkt.SetAll(128, 0, 0).SetBrightness() looping through different brightness values:

Docker

Now we've written our program to drive our Blinkt! why not make future invocations easy for ourselves by building a Docker image which we can then run as a container? We can then push the image up to the Docker hub, making it available to pull down and run on any device where Docker is available to us. The really nice part here is that Docker now supports multistage builds so with a little thought we can create smaller images containing only what we need at run time - the build stage components can be discarded post-build. For more on multistage builds see Docker Captain Alex Ellis' excellent article - so informative was this article that Docker use it within their documentation.

FROM resin/rpi-raspbian AS buildpart

RUN apt-get update && \  
    apt-get install -qy build-essential wiringpi git curl ca-certificates && \
    curl -sSLO https://storage.googleapis.com/golang/go1.7.5.linux-armv6l.tar.gz && \
    mkdir -p /usr/local/go && \
    tar -xvf go1.7.5.linux-armv6l.tar.gz -C /usr/local/go/ --strip-components=1

ENV PATH=$PATH:/usr/local/go/bin/  
ENV GOPATH=/go/

RUN mkdir -p /go/src/github.com/rgee0/blinkt_go_examples/app  
WORKDIR /go/src/github.com/rgee0/blinkt_go_examples/app

COPY app.go    .  
RUN go get -d -v  
RUN go build -o ./app

########End of build part########

FROM resin/rpi-raspbian  
RUN apt-get update &&  apt-get install -qy wiringpi --no-install-recommends && rm -rf /var/lib/apt/lists  
COPY --from=buildpart /go/src/github.com/rgee0/blinkt_go_examples/app/app /  
CMD ["./app"]  

We can then build this using:

docker build --tag rgee0/rpi_blinkt_go:solid_colours .  

The result will be a Docker image of 129MB, which is over 400MB smaller than the 550MB image we would typically have seen prior to multistage builds being supported. Much smaller then for when we want to push the image up to the Docker hub:

docker push rgee0/rpi_blinkt_go:solid_colours  

All that remains is to try running it. As we've already seen solid_colours lets run random_blink instead:

docker run -ti --privileged rgee0/rpi_blinkt_go:random_blink  
Show Comments