Carey Payette
Published © GPL3+

Making a Smart Bracelet using Custom BTLE GATT Services

Making a Smart Bracelet using Custom BTLE GATT Services, Arduino, and interacting with it via Windows IoT Core

IntermediateFull instructions provided5,383
Making a Smart Bracelet using Custom BTLE GATT Services

Things used in this project

Story

Read more

Schematics

Fritzing Diagram

This is the hardware setup of the Flora and BTLE module

Code

customGATTService.ino - Custom GATT Service Configuration

C/C++
This sketch is used to configure the Flora Bluefruit LE Module with a Custom GATT Service
#include <Arduino.h>
#include <SPI.h>
#if not defined (_VARIANT_ARDUINO_DUE_X_) && not defined (_VARIANT_ARDUINO_ZERO_)
#include <SoftwareSerial.h>
#endif

#include "Adafruit_BLE.h"
#include "Adafruit_BluefruitLE_UART.h"

#define BLUEFRUIT_HWSERIAL_NAME Serial1
#define BLUEFRUIT_UART_MODE_PIN 12// we won't be changing modes
#define BUFSIZE 128 // Size of the read buffer for incoming data
#define VERBOSE_MODE true// If set to 'true' enables debug output

// Create the bluefruit object

// Flora uses hardware serial, which does not need the RTS/CTS pins
Adafruit_BluefruitLE_UART ble(BLUEFRUIT_HWSERIAL_NAME, BLUEFRUIT_UART_MODE_PIN);

// A small helper
void error(const __FlashStringHelper*err) {
Serial.println(err);
while (1);
}

// The service and characteristic index information 
int32_t gattServiceId;
int32_t gattNotifiableCharId;
int32_t gattWritableResponseCharId;
int32_t gattWritableNoResponseCharId;
int32_t gattReadableCharId;

void setup(void)
{
//remove these 2 lines if not debugging - nothing will start until you open the serial window
while (!Serial); // required for Flora & Micro
delay(500);

boolean success;

Serial.begin(115200);
Serial.println(F("Adafruit Custom GATT Service Example"));
Serial.println(F("---------------------------------------------------"));

randomSeed(micros());

/* Initialise the module */
Serial.print(F("Initialising the Bluefruit LE module: "));

if ( !ble.begin(VERBOSE_MODE) )
{
error(F("Couldn't find Bluefruit, make sure it's in CoMmanD mode & check wiring?"));
}
Serial.println( F("OK!") );

/* Perform a factory reset to make sure everything is in a known state */
Serial.println(F("Performing a factory reset: "));
if (! ble.factoryReset() ){
 error(F("Couldn't factory reset"));
}

/* Disable command echo from Bluefruit */
ble.echo(false);

Serial.println("Requesting Bluefruit info:");
/* Print Bluefruit information */
ble.info();

// this line is particularly required for Flora, but is a good idea
// anyways for the super long lines ahead!
ble.setInterCharWriteDelay(5); // 5 ms


/* Add the Custom GATT Service definition */
/* Service ID should be 1 */
Serial.println(F("Adding the Custom GATT Service definition: "));
success = ble.sendCommandWithIntReply( F("AT+GATTADDSERVICE=UUID128=00-77-13-12-11-00-00-00-00-00-AB-BA-0F-A1-AF-E1"), &gattServiceId);
if (! success) {
error(F("Could not add Custom GATT service"));
}

/* Add the Readable/Notifiable characteristic - this characteristic will be set every time the color of the LED is changed */
/* Characteristic ID should be 1 */
/* 0x00FF00 == RGB HEX of GREEN */
Serial.println(F("Adding the Notifiable characteristic: "));
success = ble.sendCommandWithIntReply( F("AT+GATTADDCHAR=UUID128=00-68-42-01-14-88-59-77-42-42-AB-BA-0F-A1-AF-E1,PROPERTIES=0x12,MIN_LEN=1, MAX_LEN=3, VALUE=0x00FF00"), &gattNotifiableCharId);
if (! success) {
error(F("Could not add Custom Notifiable characteristic"));
}

/* Add the Writable characteristic - an external device writes to this characteristic to change the LED color */
/* Characteristic ID should be 2 */
Serial.println(F("Adding the Writable with Response characteristic: "));
success = ble.sendCommandWithIntReply( F("AT+GATTADDCHAR=UUID128=00-69-42-03-00-77-12-10-13-42-AB-BA-0F-A1-AF-E1,PROPERTIES=0x08,MIN_LEN=1, MAX_LEN=3, VALUE=0x00FF00"), &gattWritableNoResponseCharId);
if (! success) {
error(F("Could not add Custom Writable characteristic"));
}

/* Add the Readable characteristic - external devices can query the current LED color using this characteristic */
/* Characteristic ID should be 3 */
Serial.println(F("Adding the Readable characteristic: "));
success = ble.sendCommandWithIntReply( F("AT+GATTADDCHAR=UUID128=00-70-42-04-00-77-12-10-13-42-AB-BA-0F-A1-AF-E1,PROPERTIES=0x02,MIN_LEN=1, MAX_LEN=3, VALUE=0x00FF00"), &gattReadableCharId);
if (! success) {
error(F("Could not add Custom Readable characteristic"));
}

/* Add the Custom GATT Service to the advertising data */
//0x1312 from AT+GATTLIST - 16 bit svc id
Serial.print(F("Adding Custom GATT Service UUID to the advertising payload: "));
ble.sendCommandCheckOK( F("AT+GAPSETADVDATA=02-01-06-03-02-12-13") );

/* Reset the device for the new service setting changes to take effect */
Serial.print(F("Performing a SW reset (service changes require a reset): "));
ble.reset();

Serial.println();
}

void loop(void)
{

}

AT+GATTLIST Output

Plain text
This is the output received when querying the Bluefruit for GATT Services after we have configured our custom service.
AT > AT+GATTLIST

<- ID=01,UUID=0x1312,UUID128=00-77-13-12-11-00-00-00-00-00-AB-BA-0F-A1-AF-E1
ID=01,UUID=0x4201,UUID128=00-68-42-01-14-88-59-77-42-42-AB-BA-0F-A1-AF-E1,PROPERTIES=0x12,MIN_LEN=1,MAX_LEN=3,VALUE=00-FF-00
ID=02,UUID=0x4203,UUID128=00-69-42-03-00-77-12-10-13-42-AB-BA-0F-A1-AF-E1,PROPERTIES=0x08,MIN_LEN=1,MAX_LEN=3,VALUE=00-FF-00
ID=03,UUID=0x4204,UUID128=00-70-42-04-00-77-12-10-13-42-AB-BA-0F-A1-AF-E1,PROPERTIES=0x02,MIN_LEN=1,MAX_LEN=3,VALUE=00-FF-00

Flora_BLE_LED.ino Sketch

C/C++
This is the sketch that is run on the Flora that polls the write characteristic for color changes and manages the color of the on-board NeoPixel
#include <Adafruit_NeoPixel.h>
#include <SPI.h>
#if not defined (_VARIANT_ARDUINO_DUE_X_) && not defined (_VARIANT_ARDUINO_ZERO_)
#include <SoftwareSerial.h>
#endif
#include "Adafruit_BLE.h"
#include "Adafruit_BluefruitLE_UART.h"
#include <String.h>

#define BLUEFRUIT_HWSERIAL_NAME Serial1
#define BLUEFRUIT_UART_MODE_PIN 12//we aren't using this because we aren't switching modes
#define BUFSIZE 128 // Size of the read buffer for incoming data
#define VERBOSE_MODE true// If set to 'true' enables debug output
#define NEO_PIXEL_PIN 8//On-board NeoPixel on Flora 2


Adafruit_BluefruitLE_UART ble(BLUEFRUIT_HWSERIAL_NAME, BLUEFRUIT_UART_MODE_PIN);
String currentColorValue = "FF-FF-FF";
Adafruit_NeoPixel onBoardPixel = Adafruit_NeoPixel(1, NEO_PIXEL_PIN, NEO_GRB + NEO_KHZ800);
int redVal = 255;
int greenVal = 255;
int blueVal = 255;

// A small helper
void error(const __FlashStringHelper*err) {
Serial.println(err);
//Set Red LED
redVal = 255;
greenVal = 0;
blueVal = 0;
setPixelColor();
while (1);
}

void setup() {
//remove these 2 lines if not debugging
while (!Serial); // required for Flora & Micro
delay(500);

Serial.begin(115200);
Serial.println(F("Adafruit Flora BLE LED Example"));
Serial.println(F("---------------------------------------------------"));

randomSeed(micros());

if ( !ble.begin(VERBOSE_MODE) )
{
error(F("Couldn't find Bluefruit, make sure it's in CoMmanD mode & check wiring?"));
}
Serial.println( F("OK!") );

/* Disable command echo from Bluefruit */
ble.echo(false);

Serial.println("Requesting Bluefruit info:");
/* Print Bluefruit information */
ble.info();

// this line is particularly required for Flora, but is a good idea
// anyways for the super long lines ahead!
ble.setInterCharWriteDelay(5); // 5 ms
Serial.println();

onBoardPixel.begin();
setPixelColor();

}

void loop() {
bool hasChanged = false;
 
//check writable characteristic for a new color
ble.println("AT+GATTCHAR=2");
ble.readline();
if (strcmp(ble.buffer, "OK") == 0) {
// no data
return;
}
// Some data was found, its in the buffer
Serial.print(F("[Recv] ")); 
Serial.println(ble.buffer); //format FF-FF-FF R-G-B

String buffer = String(ble.buffer);
//check if the color should be changed
if(buffer!=currentColorValue){
hasChanged = true;
currentColorValue = buffer;
String redBuff = buffer.substring(0,2);
String greenBuff = buffer.substring(3,5);
String blueBuff = buffer.substring(6); 
const char* red = redBuff.c_str();
const char* green =greenBuff.c_str();
const char* blue = blueBuff.c_str();
redVal = strtoul(red,NULL,16);
greenVal = strtoul(green, NULL, 16);
blueVal = strtoul(blue,NULL,16);
setPixelColor();
}
ble.waitForOK();

if(hasChanged){
Serial.println("Notify of color change");
//write to notifiable characteristic
String notifyCommand = "AT+GATTCHAR=1,"+ currentColorValue;
ble.println( notifyCommand ); 
if ( !ble.waitForOK() )
{
error(F("Failed to get response from notify property update"));
}

//write to readable characteristic for current color
String readableCommand = "AT+GATTCHAR=3,"+ currentColorValue;
ble.println(readableCommand);

if ( !ble.waitForOK() )
{
error(F("Failed to get response from readable property update"));
}
}
delay(1000);
}

void setPixelColor() {
onBoardPixel.setPixelColor(0,onBoardPixel.Color(redVal, greenVal, blueVal));
onBoardPixel.show();
}

bthgattdump.exe Output from Windows IoT Core

Plain text
This is a snippet of the output received when running the bthgattdump.exe program on the Raspberry Pi. This snippet represents the Custom Service that we configured on the Flora Bluefruit LE module.
[Service] Handle=0x003a Type=00771312-1100-0000-0000-abba0fa1afe1
[Characteristic] Handle=0x003b ValueHandle=0x003c Type=00684201-1488-5977-4242-abba0fa1afe1 Properties=(Read/Notify)
[Value] [00FF00]
[Descriptor]Handle=0x003d Type=0x2902(Client Configuration)
[Value]No subscription
[Characteristic] Handle=0x003e ValueHandle=0x003f Type=00694203-0077-1210-1342-abba0fa1afe1 Properties=(Write)
[Characteristic] Handle=0x0040 ValueHandle=0x0041 Type=00704204-0077-1210-1342-abba0fa1afe1 Properties=(Read)
[Value] [00FF00]

Package.appxmanifest - GATT Device Capability

XML
This capability is added to the Package.appxmanifest file in the universal application to be run on the Raspberry Pi.
<DeviceCapability Name="bluetooth.genericAttributeProfile">
<Device Id="any">
<Function Type="name:genericAccess" />
</Device>
</DeviceCapability>

MainPage.xaml - User Interface XAML

XML
MainPage.xaml code listing
<Page
x:Class="HelloBLE.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:HelloBLE"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Orientation="Vertical">
<TextBlock FontSize="25 " Margin="-1,30,0,0">Custom GATT Service Example</TextBlock>
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="20" Foreground="Blue">Adafruit Bluefruit Connection Status:</TextBlock>
<TextBlock x:Name="txtBTLEStatus" FontSize="20" Foreground="Red" Margin="5,0,0,0">Not Initialized</TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button x:Name="btnBlue" FontSize="20" Background="Blue" Foreground="White" Click="btnColor_Click">Blue</Button>
<Button x:Name="btnGreen" FontSize="20" Background="Green" Foreground="White" Click="btnColor_Click">Green</Button>
<Button x:Name="btnYellow" FontSize="20" Background="Yellow" Click="btnColor_Click">Yellow</Button>
<Button x:Name="btnOrange" FontSize="20" Background="Orange" Click="btnColor_Click">Orange</Button>
<Button x:Name="btnPurple" FontSize="20" Background="Purple" Foreground="White" Click="btnColor_Click">Purple</Button>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="20">Last Notification indicated Flora LED Hex Color As: </TextBlock>
<TextBlock x:Name="txtLastNotificationHex" FontSize="20" Margin="5,0,0,0">N/A</TextBlock>
</StackPanel>
<TextBlock x:Name="txtProgress" FontSize="20" Foreground="Green"></TextBlock>
<TextBlock x:Name="txtInfo" FontSize="20" Foreground="Red"></TextBlock>
<Button x:Name="btnRead" Click="btnRead_Click">Read Current Color Through the Read Characteristic</Button>
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="20">Current Read Value Is: </TextBlock>
<TextBlock x:Name="txtReadValue" FontSize="20" Margin="5,0,0,0">N/A</TextBlock>
</StackPanel>
</StackPanel>
</Grid>
</Page>

MainPage.xaml.cs - C# Implementation File

C#
Code Listing for MainPage.xaml.cs
using System;
using System.Linq;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Devices.Enumeration;
using Windows.Storage.Streams;
using Windows.UI;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;

namespace HelloBLE
{
public sealed partial class MainPage : Page
{

private GattCharacteristic _notifyCharacteristic;
private GattCharacteristic _writeCharacteristic;
private GattCharacteristic _readCharacteristic;
 
public MainPage()
{
this.InitializeComponent();
btnBlue.IsEnabled = false;
btnGreen.IsEnabled= false;
btnYellow.IsEnabled = false;
btnOrange.IsEnabled = false;
btnPurple.IsEnabled = false;
btnRead.IsEnabled = false;
SetupBLE();
}

public async void SetupBLE()
{
txtProgress.Text = "Obtaining BTLE Info...";
var query = BluetoothLEDevice.GetDeviceSelector();
var deviceList = await DeviceInformation.FindAllAsync(query);
int count = deviceList.Count();

if(count > 0)
{
//Assumes default name of the Adafruit Bluefruit LE
var deviceInfo = deviceList.Where(x => x.Name == "Adafruit Bluefruit LE").FirstOrDefault();
if(deviceInfo!=null)
{
var bleDevice = await BluetoothLEDevice.FromIdAsync(deviceInfo.Id);
var deviceServices = bleDevice.GattServices;

txtProgress.Text = "Retrieving service and GATT characteristics...";
var deviceSvc = deviceServices.Where(svc => svc.AttributeHandle == 0x003a).FirstOrDefault();
if (deviceSvc != null)
{
var characteristics = deviceSvc.GetAllCharacteristics();
_notifyCharacteristic = characteristics.Where(x => x.AttributeHandle == 0x003b).FirstOrDefault();
_writeCharacteristic = characteristics.Where(x => x.AttributeHandle == 0x003e).FirstOrDefault();
_readCharacteristic = characteristics.Where(x => x.AttributeHandle == 0x0040).FirstOrDefault();
_notifyCharacteristic.ValueChanged += NotifyCharacteristic_ValueChanged;
await _notifyCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify);

txtProgress.Text = "Bluetooth LE Device service and characteristics initialized";
txtBTLEStatus.Text = "Initialized";
txtBTLEStatus.Foreground = new SolidColorBrush(Colors.Green);
btnBlue.IsEnabled = true;
btnGreen.IsEnabled= true;
btnYellow.IsEnabled = true;
btnOrange.IsEnabled = true;
btnPurple.IsEnabled = true;
btnRead.IsEnabled = true;
}
else
{
txtInfo.Text = "Custom GATT Service Not Found on the Bluefruit";
}
}
else
{
txtInfo.Text = "Adafruit Bluefruit LE not found, is it paired ??";
}
} 
}

private void NotifyCharacteristic_ValueChanged(GattCharacteristic sender, 
GattValueChangedEventArgs args)
{
//notification that the NeoPixel color has changed, update the UI with the new value
byte[] bArray = new byte[args.CharacteristicValue.Length];
DataReader.FromBuffer(args.CharacteristicValue).ReadBytes(bArray);

var color = Color.FromArgb(0,bArray[0], bArray[1], bArray[2]);
string result = color.ToString();

//remove alpha channel from string (only rgb was returned)
result = result.Remove(1, 2);

Dispatcher.RunAsync(CoreDispatcherPriority.Low, () => { txtLastNotificationHex.Text = result; });

}


private async void btnColor_Click(object sender, RoutedEventArgs e)
{
//Change the color of the NeoPixel by writing the hex color bytes to the Write characteristic
//that is currently being monitored by our Flora sketch
Button btnColor = (Button)sender;
var color = ((SolidColorBrush)btnColor.Background).Color;

var writer = new Windows.Storage.Streams.DataWriter();
writer.WriteBytes(new byte[] { color.R, color.G, color.B });

txtProgress.Text = "Writing color to Writable GATT characteristic ...";
await _writeCharacteristic.WriteValueAsync(writer.DetachBuffer());

txtProgress.Text = "Writable GATT characteristic written";

}

private async void btnRead_Click(object sender, RoutedEventArgs e)
{
// Read the current color of the NeoPixel by querying the Read characteristic
txtProgress.Text = "Reading GATT characteristic";
var result = await _readCharacteristic.ReadValueAsync(BluetoothCacheMode.Uncached);
if(result.Status== GattCommunicationStatus.Success)
{
txtProgress.Text = "Characteristic has been read";
 
byte[] bArray = new byte[result.Value.Length];
DataReader.FromBuffer(result.Value).ReadBytes(bArray);

var color = Color.FromArgb(0, bArray[0], bArray[1], bArray[2]);
string colorString = color.ToString();
//remove alpha channel from string (only rgb was returned)
colorString = colorString.Remove(1, 2);
txtReadValue.Text = colorString;
}
else
{
txtProgress.Text = "ERROR";
txtInfo.Text = "There was a problem reading the GATT characteristic";
}
}
}
}

Github

https://github.com/falafelsoftware/Custom-GATT-Service-Example

Credits

Carey Payette

Carey Payette

13 projects • 134 followers
Sr. Software Engineer at Independent

Comments