Today’s goal is to figure out my GATT strategy. I will make a way to discover the sensors' Bluetooth profiles and hopefully set up a quick demo of finding the sensors and reading an attribute.
One thing I’ve noticed since the last time I worked on this is that the Bluetooth radios only support one custom attribute, 0xffe0, which can transfer arbitrary data.
Just for the exercise of it, however, I am going to approach this as if I wanted to do this “The Right Way(tm)”.
As I mentioned last time, there are Generic Attributes defined for many things.. Here we see that there are a few attributes of interest:
This attribute seems to be trying to be very flexible; it allows for the payload to define a few things about the temperature measurement:
The Temperature Type attribute indicates that the measurement types are generally biological (the values indicate things like Toe, Mouth, Finger, etc…). To me this means we probably won’t want to use this attribute.
This attribute looks much simpler. It consists of one 16-bit integer that represents 0.01 degrees Celsius. It’s nice that it’s a fixed-point number, something that MCUs can easily work with.
This field is very similar to the one above (Temperature), except the name is explicitly calling out the units. I like that. Again, the attribute consists of one signed 16-bit integer, although the fixed-point is slightly less well documented, but it seems to be an exponent value of -1, meaning the value is in tenths of a degree Celsius.
Almost identical to the Temperature Celcius attribute, except the value represents a Fahrenheit temperature (obviously).
Given these fields, I am going to choose Temperature Celsius
. This is somewhat
arbitrary, but I guess if I had to justify it, I’d say I like the Celsius units
better than Fahrenheit, and I don’t see a need for 2 digits of decimal precision.
I’ve played with these a little bit before, but I don’t really remember anything about them.
I’ve hooked up the HC-08 to the Samsung Interface Board that came with my Artik530. I connected the Ground and Power, and remembered to reverse the Tx/Rx pins when connecting to UART0_RX and UART0_TX. After some trial and error, I realized that UART0 appears in the Linux devices under /dev/ttyAMA4.
By default, the HC-08 chip uses 9600 8N1 serial port configuration. I don’t see any
real need to change this for now. I also realized that there’s something funky going
on; I can’t seem to get screen
or picocom
working. However, I do have luck setting
up a cat /dev/ttyAMA4
and echo
-ing commands to the device. Very curious.
In any case, I am able to issue basic AT
commands to the device and can set its
name. I downloaded the BLE Scanner
app from the iOS App Store, and could immediately
see the Bluetooth radio–so far so good! I see the name and the FFE0 attribute. As
you might expect, I immediately tried to Read
and Write
that attribute. From the
iPhone side, I have to make sure to send a newline (0x0a) or carriage return (0x0d)
to flush the output buffer of the HC-08 radio. I tested both, and it seems that one
or the other will work fine. I wonder if there is some TTY shenanigans going on?
I also verify that I am able to send strings from the HC-08 to the phone. In this case, I can omit the line-endings completely and the values are still sent fine. I also note that the Notify portion of the attribute seems to be working (when I type into the HC-08 serial port, the phone receives the text without having to manually poll the value). This should be useful!
I am going to now detect devices that expose the 0xffe0
attribute. This means we’re
heading back to Go!
In my latest code, I was able to print devices as I found them (being careful to also
look at the BlueZ cached devices!). What I am going to do now is check those devices
to see whether they support the GATT UUID that I am looking for (0xffe0
).
I noticed that the muka Device1
class' Properties field contains a list of supported
UUIDs. This is promising! To make sure this is what I want, I added a bit of code to
print devices supported UUIDs on detection:
props, err := dev.GetProperties()
if err != nil {
log.Printf("Failed to read device properties: %s", err)
return false
}
log.Printf("Name: %s, Addr: %s, RSSI: %d", props.Name, props.Address, props.RSSI)
// We're going to look for the truncated UUID, which is a bit
// hacky
for _, uuid := range props.UUIDs {
log.Printf("\tUUID: %s", uuid)
}
Running this code on device detection leads to a nice list of supported GATT UUIDs for every discovered device!
All that’s left now is to write a bit of code to detect when we’ve found the UUID we care about. Instead of a snippet, I’ll just post whole code I have now:
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"
)
const CUSTOM_UUID = "ffe0"
func main() {
err := api.TurnOnBluetooth()
if err != nil {
log.Fatalf("Failed to enable bluetooth (%s)", err)
}
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 {
foundAdapter(adapterEvent.Name)
}
}))
err = manager.RefreshState()
if err != nil {
log.Fatalf("Failed to refresh Manager state: %s", err)
}
// Now get a list of the previously identified BlueZ devices and
// print their information to the log.
devices, err := api.GetDevices()
if err != nil {
log.Fatalf("Failed to get cached devices: %s", err)
}
log.Printf("======================================")
log.Printf("Cached Devices:")
log.Printf("======================================")
for _, dev := range devices {
showDeviceInfo(&dev)
}
log.Printf("======================================")
select {}
}
func foundAdapter(adapterId string) {
adapter := profile.NewAdapter1(adapterId)
// Make sure that the adapter is powered on. Note that because
// we made a call to api.TurnOnBluetooth() in main, we expect that
// rfkill has enabled the device.
if !adapter.Properties.Powered {
err := adapter.SetProperty("Powered", dbus.MakeVariant(true))
if err != nil {
log.Fatalf("Failed to set Powered property: %s", err)
}
}
// Now that the adapter is on, we can start scanning for Bluetooth
// devices.
err := adapter.StartDiscovery()
if err != nil {
log.Fatalf("Failed to start discovery: %s", err)
}
err = api.On("discovery", emitter.NewCallback(func (ev emitter.Event) {
discoveryEvent := ev.GetData().(api.DiscoveredDeviceEvent)
if discoveryEvent.Status == api.DeviceAdded {
showDeviceInfo(discoveryEvent.Device)
}
}))
}
func deviceIsSensor(dev *api.Device) bool {
props, err := dev.GetProperties()
if err != nil {
log.Printf("Failed to read device properties: %s", err)
return false
}
log.Printf("Name: %s, Addr: %s, RSSI: %d", props.Name, props.Address, props.RSSI)
// We're going to look for the truncated UUID, which is a bit
// hacky
for _, uuid := range props.UUIDs {
r := []rune(uuid)
substring := string(r[4:8])
if substring == CUSTOM_UUID {
return true
}
}
return false
}
func showDeviceInfo(dev *api.Device) {
if dev == nil {
return
}
if deviceIsSensor(dev) {
log.Printf("Found our sensor.")
}
}
I need to go finish some thing around the house, so that is all for tonight. For next time, I’ll be trying to send data from the HC-08 radios to the Artik side. This will form the basis of the data collection side. From there, I’ll add proper data reading from the Si7021 chips and write that to the HC-08, and receive it on the Artik.
After that, I’ll look at hooking it up to something like Amazon IoT so that I can do interesting things with it.