Bluetooth Sensors, Pt. 1

Introduction

Hello, world.

I’m always working on side-projects with some sort of gadget, but I don’t really have a record of any of them, so I figured I’d start a blog. This is just for me, for now, but if you’re reading this, hello!

Project

My wife and I bought our condo several years ago, and I’ve always been curious to map out the temperature and humidity of various regions of the building, specifically the basement. I’m going to document my work here, mostly to go through the exercise of documenting my work.

I’m going to set up a system of sensors around the house that report back to a gateway that uploads the data to a centralized server in some way.

Equipment

Gateway

At work awhile ago we were evaluating a few different SOMs (System on a Module) for our products. I bought some evaluation kits for personal use as well, since they seemed pretty cool. I bought both a Wandboard Quad and an Artik530 Starter Kit kit.

These are both fanless boards that have on-board Wi-Fi and Bluetooth radios. They are also both very easy to compile relatively recent Linux kernels for (4.0+), which is great for playing around with. For no particular reason, I found myself working with the Artik board a bit more.

Sensors

I’ve always loved working with various sensors (show me an engineer who doesn’t love sensors), and some that I like a lot are these Si7021 Humidity and Temperature sensors by Silicon Labs. They’re accurate, easy-to-use and have an available datasheet. We used these at a company that I started years ago, and they worked quite well.

I bought a couple of cheap Bluetooth Low Energy (BLE) radios off of Amazon (DSD Tech SH-HC-08. These aren’t super fancy, but seemed pretty easy to use. The documentation for these parts is a little suspect; when I first received these radios, I tried talking to them and found that the radio doesn’t respond to all of the commands listed in its datasheet. The AT/OK command/response works fine, but I haven’t been able to get AT+ADDR? to work.

I have some Adafruit Trinkets laying about that I’ll use to perform the sensor readings and to control the Bluetooth radios. They’re tiny and fairly cheap, but at the expense of limited pins (e.g. no UART interface, so for a serial port we’re going to have to bit-bang). These might end up needing to be replaced by something else, but I have them, so I might as well give them a shot.

Software

Gateway

System Software

I use Yocto quite a bit at work. Yocto is a tool (really: collection of tools) for generating custom Linux distributions. I know it reasonably well and used it to quickly get up and running on my Artik530 board. The reference distribution of Yocto is called poky. This is versioned according to alphabetical names; at the time of this writing, sumo is the most recent development branch.

Unfortunately, Samsung doesn’t maintain a Yocto layer for developing for their boards, but they do provide an SDK for compiling the kernel, u-boot and a couple of distros, so porting it to Yocto isn’t too bad. I scrounged around the internet and found some old layers that I was able to lean on, and pretty quickly was able to get an SD card image building.

User-space code

I’ve been wanting to play with a different language, so for no real reason I’ve chosen to use Go as the programming language to develop this project with. I’ve been hearing about it a bit at work, and one of my coworkers loves it. It also helps that he’s one of the most talented people I’ve had the privilege of working with (Michael Fogleman, blog).

Go is actually a nice choice because it’s very easy to cross-compile, and generates statically-linked binaries, so it doesn’t require a lot of toolchain structure to work with. The downside is that I don’t really know how to make it work with Yocto.

I plan to use some of the standard Linux utilities for managing Bluetooth connections, so that I don’t have to reinvent the wheel. I’m using BlueZ 5.48, mostly because that’s the version that is standard in Poky version sumo. BlueZ provides the bluetoothd daemon, which manages the Bluetooth hardware in the system, and also exposes a D-Bus interface for communicating with other system processes.

Fortunately, there is a Go library for communicating using D-Bus: dbus. There is even a nice library for communicating via D-Bus with BlueZ: go-bluetooth. go-bluetooth definitely seems like it could use some help; if I end up with useful Go, I should push that back upstream. One thing that jumps out to me is that there are calls to exec.Command, which I don’t love. I’d prefer this tool to be entirely dependent on the D-Bus communication. Perhaps these calls are there for reasons that aren’t immediately obvious to me.

Attempts at Go

Well, the first thing I want to do with Go is to write a small program that can list the Bluetooth adapters that are available. I’ve used the BlueZ tools to ensure that my system is set up correctly (I can turn the Bluetooth radio on and off, I can discover devices in the area). I note that I’ve included rfkill in my Linux distro – I should probably remove that because it’s not adding any value to me.

Reading through the BlueZ 5 API introduction and porting guide, I notice that things have changed a bit since I last used the BlueZ D-Bus interface: I used to be able to make a call to ListAdapters() and receive a list of Adapters that BlueZ was managing. It appears that this has been replaced by a much more generic interface ObjectManager.GetManagedObjects.

In reading through the go-bluetooth code, it appears that we want to instantiate an instance of the Manager class:

manager, err := api.NewManager()
Under the hood, this sets up a D-Bus connection to the BlueZ ObjectManager interface and a listener for ObjectManager events (for changes like Adapter added/removed). Also important is that the ObjectManager also makes a call to scan the current status of ObjectManager–without that call, we’d simply have to wait for something to happen. Initial conditions: not just for Differential Equations!

So at this point, the manager object will be receiving events and such, but our program still doesn’t have a list of Bluetooth adapters. To get that, we have to set up a callback for the events that the manager object is receiving:

// Note: err was defined above, so I can re-assign its value here.
err = api.On("adapter", emitter.NewCallback(func(ev emitter.Event) {
	adapterEvent := ev.GetData().(api.AdapterEvent)
	log.Printf("Found adapter: %s, Path: %s, Status: %d", adapterEvent.Name, adapterEvent.Path, adapterEvent.Status)
}))

This sets up an anonymous function as the callback for adapter events – this code will seemingly cast the argument from an Event to an AdapterEvent, which we can use to figure out what the event was.

Summary of Part 1

Ok, I am tired and will be signing off for today. Here is the final code that I am running:

package main

import (
	"github.com/godbus/dbus"
	"github.com/muka/go-bluetooth/api"
	"github.com/muka/go-bluetooth/bluez/profile"
	"github.com/muka/go-bluetooth/emitter"
	"log"
)

func main() {
	manager, err := api.NewManager()
	if err != nil {
		log.Fatalf("Failed to get new Manager: %s", err)
	}

	defer api.Exit()

	err = api.On("adapter", emitter.NewCallback(func(ev emitter.Event) {
		adapterEvent := ev.GetData().(api.AdapterEvent)
		log.Printf("Adapter: %s, Path: %s, Status: %d", adapterEvent.Name, adapterEvent.Path, adapterEvent.Status)
		if adapterEvent.Status == api.DeviceAdded {
			log.Printf("Adapter Added!")
		}
	}))

	err = manager.RefreshState()
	if err != nil {
		log.Fatalf("Failed to refresh Manager state: %s", err)
	}

	select {}
}
I am compiling this code with the following:
GOOS=linux GOARCH=arm GOARM=7 go build bluetooth.go

Next Steps

Next, I will set up this code to scan for devices. I also will need to make sure that my code can enable/disable the BlueZ adapter, in case the program starts while the interface is disabled.

After that, I’ll have to look for devices that expose a specific specific GATT profile, which I will be using for transferring data from my sensors to the gateway.