Part 2 - Internet connected user-space device drivers

In this part 2 of 2-part series, I am going to show how to use Balena to make our USB detector an IoT device. Read part 1 here

Table of contents

  1. Basic Dockerization and Balena
  2. Passing C data structures to Golang
  3. Epilogue

Dockerization and Balena

I am following the steps for a BeagleBone Black from here, but you could follow any as long as you pick Go as the language of choice

  • After git clone, you can check out the basic starter app that the nice folks at Balena have written. It looks something like this -
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("hello, world (from go!)\n"))
	addr := ":80"
	fmt.Println("Example app listening on port ", addr)
	log.Fatal(http.ListenAndServe(addr, nil))
  • A simple http server that replies with a hello world no matter what you say.
  • Balena allows you to deploy using git which is kinda neat, because that means that you don't have to cross compile on your host machine
  • We are going to need the libusb-dev package on our BeagleBone Black. I simply created a script called and added it to my Docker file like below –
FROM balenalib/%%BALENA_MACHINE_NAME%%-golang:latest-build AS build

WORKDIR /go/src/

COPY /app ./

RUN ./

RUN go build

FROM balenalib/%%BALENA_MACHINE_NAME%%-debian:stretch

COPY --from=build /go/src/ .

CMD ./app
  • If you want to look at the Dockerfile before I added my changes, you can see it here
  • My file looks like the following –
echo "Installing dependencies"
apt-get update
apt-get install libusb-1.0-0-dev
  • Don't forget to make the executable using chmod +x
  • Another thing that I realised is that it's better to create an image with the dependencies installed rather than install it after you install the base image, but I am going to skip that right now under the guise of premature optimization.
  • Once you have deployed it, you should be able to see the logs on the console and a simple Hello World on port 80 of your local IP address. Balena gives an optional Public addres flag, that if you enable, you can access this over good ol Internet.

Passing C data structures to Golang

Structure padding means adding bytes between two types in a struct to make memory allignment possible. You can read more about the limitations of golang on the CGo wiki. More reading material that I found

  • Liam Kelly's blog post here
  • Dave Cheney's blog post here – aptly titled "Padding is hard" :)
  • This stackoverflow answer


I think a simpler way is to use something like protobuf or json. It's a bit more code, but it'll be a lot better than what I got working with our USB C code by adding attribute((aligned(8))) in the structure declaration.

On the Go side, we have a standard library for handling json, so that should be less tedious.


I have some more thoughts about building embedded systems using Go and C, namely -

  1. Goroutines and multicore archs for hardware I/O
  2. OTA updates and logging for network constained devices
  3. AWS IoT C SDK + Golang (AWS Go SDK doesn't support MQTT)
  4. Managing state machines

I'll be talking more about the above on this blog as I dive deeper.


Show Comments