Talking to the nRF8001 BLE chip via GoLang on Linux

July 10, 2015

There is very little documentaiton out there on the internet on how to integrate with BLE on anything but Anrdoid and iOS devices. This is supposed to serve as a guide targeted at the nRF8001 chip made by Nordic Semiconductors.

Setup

I’m using the Adafruit Bluefruit LE Breakout which is connected up to a Arduino Uno running the Adafruit callbackEcho demo.

Adafruit Bluefruit LE

On my linux machine i am running Debian Jessie with the inexpensive Pluggable USB Bluetooth LE adapter. It’s importnat to note that the libraries used later in this article are dependent on a Linux Kernel version 3.14 or greater. So Raspbian (aka Debian Wheezy) on a RaspberryPi will not work out the box without upgrading the Kernel or to Jessie.

I assume that you have a working GoLang environment. If not hope over here to get setup.

A bit of background

Bluetooth LE has a few concepts that we need to be familiar with. In BLE we have Peripherals like smart watches or fitness bands. Peripherals adverise Services like Heart Rate and/or Pedometer. These Services have Charateristics like Number of steps for a Pedometer.

A Central connects to a Perpheral, discovers its Services and Characteristics and is able to read/write or even recieve notifications of changes to those Characteristics. The Central talks to the Peripheral via the GATT protocol.

The Codes

luckily the guys at PayPal wrote this library for GATT in golang and wraps the existing bluetooth support built into the Kernel. It’s got some API docs and some Examples but very little else. So first we need to grab that library…

go get https://github.com/paypal/gatt

Next we’re going to step through the process of sending a value to our ble preripheral and becaue its running the echo example it should return the exact same thing in response. Caveat, this is sample code, it doesn’t handle errors etc, but should get your started.

Connecting

First we need to connect to the device by calling NewDevice and attach some handlers for ConnectionStateChange, Discovery, Connection and Disconnection of Peripherals.

import (
  "log"
  "github.com/paypal/gatt"
)

func main() {
  var DefaultClientOptions = []gatt.Option{
    gatt.LnxMaxConnections(1),
    gatt.LnxDeviceID(-1, false),
  }

  d, err := gatt.NewDevice(DefaultClientOptions...)
  if err != nil {
    log.Fatalf("Failed to open device, err: %s\n", err)
    return
  }

  d.Handle(
    gatt.PeripheralDiscovered(onPeriphDiscovered),
    gatt.PeripheralConnected(onPeriphConnected),
    gatt.PeripheralDisconnected(onPeriphDisconnected),
  )

  d.Init(onStateChanged)
  <-done
  log.Println("Done")
}

Scanning for devices

First of all when the bluetooth adapter is powered on we need to start it scanning.

func onStateChanged(d gatt.Device, s gatt.State) {
    log.Println("State:", s)
    switch s {
    case gatt.StatePoweredOn:
        log.Println("scanning...")
        d.Scan([]gatt.UUID{}, false)
        return
    default:
        d.StopScanning()
    }
}

Connecting to the right device

Once the scanning has started the onPeriperhalDiscovered function will be called for every BLE peripheral discovered. For us we want to stop scanning when we find the one called “UART” and connect to it.

func onPeriphDiscovered(p gatt.Peripheral, a *gatt.Advertisement, rssi int) {
  if (a.LocalName == "UART") {
    log.Printf("Preipheral Discovered: %s \n", p.Name())
    p.Device().StopScanning()
    p.Device().Connect(p);
  }
}

Discovering the services

Once we are connected, we need to discover the services. In this case we need to find the UART service which has the UUID of XXXXXX

var uartServiceId = gatt.MustParseUUID("6e400001-b5a3-f393-e0a9-e50e24dcca9e")
var uartServiceRXCharId = gatt.MustParseUUID("6e400002-b5a3-f393-e0a9-e50e24dcca9e")
var uartServiceTXCharId = gatt.MustParseUUID("6e400003-b5a3-f393-e0a9-e50e24dcca9e")

func onPeriphConnected(p gatt.Peripheral, err error) {
  log.Printf("Peripheral connected\n")

  services, err := p.DiscoverServices(nil)
  if err != nil {
    log.Printf("Failed to discover services, err: %s\n", err)
    return
  }

...

Discovering the characteistics

Once we are connected and have found the UART service we then need to find the RX and TX characteristics.

  for _, service := range services {

    if (service.UUID().Equal(uartServiceId)) {
      log.Printf("Service Found %s\n", service.Name())

      cs, _ := p.DiscoverCharacteristics(nil, service)

  ...

Notify on changes

A litte confusingly we are going to listen for notifications on the TX Characteristic. This is because TX is the Transmitted data from the Peripheral, not the data Transmitted from the Central to the Peripheral.

      for _, c := range cs {
        if (c.UUID().Equal(uartServiceTXCharId)) {
          log.Println("TX Characteristic Found")

          p.DiscoverDescriptors(nil, c)

          p.SetNotifyValue(c, func(c *gatt.Characteristic, b []byte, e error) {
              log.Printf("Got back %s\n", string(b))
          })
        }
      }

....

Write

Now that we’re ready show if data has been returned, we can try sending a value and the callbackEcho demo on the Arduino should send that same value back to us.

      for _, c := range cs {
          if (c.UUID().Equal(uartServiceRXCharId)) {
            log.Println("RX Characteristic Found")
            p.WriteCharacteristic(c, []byte{0x74}, true)
            log.Printf("Wrote %s\n", string([]byte{0x74}))
          }
      }
    }
  }
}

Gotcha’s

Before you can setup the Notfiy you must discover the descriptors. I made a silly assumption that i didn’t need to have done this and took me an hour or two to work out why i was getting an error when writing

Summary

That should be enough to do basic communication. You could now add to this a simple web page for viewing or writing values to a whole group of nRF8001 powered devices.

Full code here