View - ARM Connected Community

Advanced BLE with
Table of Contents: Introduction
Apps
The purpose of this workshop is to help you create Custom GAP Advertising Packet
custom services on Bluetooth Low Energy (BLE) by Review
leveraging ARM® mbed™ tools. mbed BLE API
Check
In this workshop, we will cover how to send custom GAP Evothings
advertisements and how to create custom GATT Custom GATT Service and
services and characteristics. Characteristic
Review
The prerequisites for this class are having a mbed BLE API
BLE­enabled smart device (tablet or phone) and Check
mbed­enabled BLE board. Evothings API
We also assume that you have have a basic understanding of how BLE works. If you are not already familiar with these concepts or the mbed tools please use our previously posted docs: ​
Intro to BLE with ARM mbed​
and ​
Getting Started with mbed​
. The slides that accompany this document can be found here​
. Here is a permalink to the document in case you are viewing it offline: Introduction
http://goo.gl/H3Iip6 Apps
You will need the ​
Evothings Workbench​
on your laptop and the following apps on your BLE­enabled smart device: ●
LightBlue ●
●
Evothings Client ●
nRF Master Control panel Evothings Client Custom GAP Advertising Packet
Let’s start with something simple: we are going to modify the advertising data packet to broadcast some custom­formatted data. We’re going to do this by using the advertising data’s manufacturer­specific data field and encode our information into it. This type of transmission is useful because advertising does not require a connection to take place. Instead, a scanner would be able to read the information and interpret it appropriately on its side. Review
In a GAP instance the Advertising Data is broken down into the following blocks: We start with 31 bytes, then: 1. We need to add flags to tell the device about the type of advertisement we are sending and those will take up three bytes total (one for length, one for type, one for data). 2. After that two more bytes are used for the header of the AD struct that contains the data So our usable space goes from 31 to 28 to 26 bytes. This means we have 26B to use for the data we want to send over GAP. mbed BLE API
Let’s break down the program piece by piece. To import the project into the mbed compiler, follow the link here: http://developer.mbed.org/users/mbedAustin/code/BLE_EvothingsExample_GAP/ To start off, we will need the mbed header file and the header for the BLE devices: #include​​
"mbed.h"
#include​​
"BLEDevice.h"
Declare the BLE object: BLEDevice​ble;
Provide the name of the device: const​​
static​​
char​
DEVICE_NAME​
[]​
=​​
​
"ChangeMe!!"​
;​​
// change this
We now have up to 26­bytes of data to customize: const​​
static​​
uint8_t​​
AdvData​
[]​​
=​​
{​
0x01​
,​
0x02​
,​
0x03​
,​
0x04​
,​
0x05​
};​ ​
// example of hex data
The important part is to not exceed the 26­bytes in the advertising header. We can put less but not more. We can also use character data instead of hex: const​​
static​​
uint8_t​​
AdvData​
[]​​
=​​
{​
"ChangeThisData"​
};​
Try both to see the difference! // example of character data
​
Note​
: most BLE scanner programs will only display the hex representation of data, the characters may be displayed as the numbers that represent those letters. That concludes the setup. We now have to bring it all together in the main program. Start off by calling the initializer for the BLE baselayer: int​main​
(​
void)
{
ble​
.​
init​
();
Note​
: The ​
ble​
.​
init​
()​
should always be performed before doing any other BLE setup. Next we set up the advertising flags: ble​
.​
accumulateAdvertisingPayload​
(​
GapAdvertisingData​
::​
BREDR_NOT_SUPPORTED ​
|
GapAdvertisingData​
::​
LE_GENERAL_DISCOVERABLE ​
);
ble​
.​
setAdvertisingType​
(​
GapAdvertisingParams​
::​
ADV_CONNECTABLE_UNDIRECTED​
);
The second half sets the flag to put the advertisement in the ​
general discoverable​
mode and the last flag sets the type of advertisement to be a ​
connectable undirected advertisement​
. These are the flags that will cost us a total of three out of the 31 bytes. It is worth noting that the ​
ADV_CONNECTABLE_UNDIRECTED ​
flag could just as easily be ADV_NON_CONNECTABLE_UNDIRECTED​
if no connection is needed. We have chosen to use the connectable flag as some BLE apps will not display advertising data until a connection is established. We can then set up the payload. The header ​
MANUFACTURER_SPECIFIC_DATA​
is the point where we lose another two bytes of data. Once the header has announced the data we plug in the array we created earlier: ble​
.​
accumulateAdvertisingPayload​
(​
GapAdvertisingData​
::​
MANUFACTURER_SPECIFIC_DATA​
,
AdvData​
,​​
sizeof​
(​
AdvData​
));
Notice the ​
AdvData ​
variable is added to the ​
ble ​
device at this point. Set advertising interval and start advertising: ble​
.​
setAdvertisingInterval​
(​
160​
);​​
// 100ms; in multiples of 0.625ms.
ble​
.​
startAdvertising​
();
This will take care of the GAP advertising on the mbed side. Check
Load the program onto the board and bring up the Android or the nRF Master Control Panel app on ​
LightBlue app on iOS. It will automatically start scanning and we should ​
see a screen like this: The name is coming in as what it was set, the appropriate flags are set and the data we pushed into the manufacturer data is coming through. Similar information can be seen by the LightBlue app. Evothings
Go here to download the custom GAP application for Evothings: ​
http://goo.gl/tJP5EY​
. Download the files and drag the ​
index.html​
file into the Evothings Workbench. Next make sure the Evothings app is running on the smartphone and connected to the Evothings workbench on a computer. Click ​
RUN​
to run the code on the smartphone and watch for the custom data to be displayed! The code for the application is in the ​
app.js​
file. It is written in javascript and can be modified in real time. Try making a modification to the ​
app.js​
file, save the changes, and watch them load to the Evothings client on your phone! This demo is very simple but provides a starting point for more advanced programing. Custom GATT Service and Characteristic
Now let’s try a custom GATT service and characteristic to blink the LED on the mbed board. Unlike GAP, which operates on broadcasting one­to­many, GATT uses a one­to­one connection between the board and the phone. GATT does not send all the data at connection; it sends only a description of available services , and then ­ if requested ­ it will provide details about a service, like the characteristics each service has and the values of these characteristics. All information must be explicitly requested from the server by the client. To demonstrate this we will create a service with two characteristics and assign custom UUIDs to both the service and the characteristics. Review
A GATT server can have multiple services. Each service contains one or more characteristics, each with its own properties such as whether it can be read by the client or be written to by the client. Each characteristic has a single value of up to 512 bytes and can have zero or more descriptors. This can be seen below: We are going to create a custom GATT service by providing two characteristics, one for reading and one for writing, detailing their properties accordingly, and then putting both into the one service. mbed BLE API
The mbed program can be found here: http://developer.mbed.org/users/mbedAustin/code/BLE_EvothingsExample_GATT/ To get started with the mbed side there are a couple of headers we will need: #include​​
"mbed.h"
#include​​
"BLEDevice.h"
We will need a few declarations: a BLE object, the LED we will be turning on and off, the UUID for our custom service, a characteristic UUID for reading and a characteristic UUID for writing. We then provide a distinguishing name for our device that the application will be looking for. Finally, the UUID (unique identifier) needs to be declared. We chose 0xFFFF since it is designated for development instead of a particular service. BLEDevice​ble;
DigitalOut​led​
(​
LED1​
);
uint16_t​customServiceUUID ​
=​​
0xA000​
;​​
//⇐ service UUID
uint16_t​reachCharUUID
=​​
​
0xA001​
;​​
//⇐ read characteristic UUID
uint16_t​writeCharUUID
=​​
​
0xA002​
;​​
//⇐ write Characteristic UUID
const​​
static​​
char​
DEVICE_NAME​
[]​
=​​
​
"ChangeMe!!"​
;​​
// change this
static​​
const​​
uint16_t​uuid16_list​
[]​
=​​
​
{​
0xFFFF​
};​​
// Custom UUID, FF is reserved for
development
Note​
: if we change the name here we will also need to change it in the subsequent Evothings application ​
app.js​
(which will be covered later). Now that we have the UUIDs declared, set up the characteristics: 1. Start off by declaring the array variable for the read value ​
uint8_t​readValue​
[​
10​
]​
. 2. Next, declare the read only characteristic (​
ReadOnlyArrayGattCharacteristic​
). 3. Provide the initializer describing the type of the array and the number of elements in the array (​
<​
uint8_t​
,​​
sizeof​
(​
readValue​
)>​
). 4. The characteristic will be called “​
readChar​
” and initialized with the UUID variable for the read characteristic ​
readCharUUID​
and the pointer to the array that was just created (​
readValue​
) . 5. The same is done for the write characteristic, “writeValue”. This is what it looks like: static​​
uint8_t​readValue​
[​
10​
]​​
=​​
{​
0​
};
ReadOnlyArrayGattCharacteristic​
<​
uint8_t​
,​​
sizeof​
(​
readValue​
)>​readChar​
(​
readCharUUID​
,​readValue​
);
static​​
uint8_t​writeValue​
[​
10​
]​​
=​​
{​
0​
};
WriteOnlyArrayGattCharacteristic​
<​
uint8_t​
,​​
sizeof​
(​
writeValue​
)>​writeChar​
(​
writeCharUUID​
,
writeValue​
);
Now that both of the characteristics have been defined, we can move on to the custom service: 1. Initialize a GATT service by filling the characteristics array with references to the read and write characteristics. 2. Declare the GATT service 3. The deceleration includes the UUID, the characteristics array, and the number of characteristics included. GattCharacteristic​​
*​
characteristics​
[]​​
=​​
{&​
readChar​
,​​
&​
writeChar​
};
GattService​
customService​
(​
customServiceUUID​
,​characteristics​
,​​
sizeof​
(​
characteristics​
)
/​​
sizeof​
(​
GattCharacteristic​​
*));
We've established the service; we can now create other functions. First, since GATT is connection­based, we need a disconnection callback function. This functions restarts advertising after a disconnect occurs, so a device can find and reconnect to a lost board. If we don't include this function, we'll have to restart the board to be able to reconnect to it: void​disconnectionCallback​
(​
Gap​
::​
Handle_t​handle​
,​​
Gap​
::​
DisconnectionReason_t​reason)
{
ble​
.​
startAdvertising​
();
}
Next we need to create the write callback function so it can be called when the BLE board is written to. This callback makes sure the write operation triggering the callback (​
params​
->​
charHandle​
) is a write operation to the write characteristic (​
writeChar​
.​
getValueHandle​
()​
). This is not necessary when we only have one write characteristic, but is absolutely necessary when we have multiple ones on a device. The remainder of the code will print out the data written to the write characteristic: void​writeCharCallback​
(​
const​​
GattCharacteristicWriteCBParams​​
*​
params)
{
// check to see what characteristic was written, by handle
​
if​
​
(​
params​
->​
charHandle ​
==​writeChar​
.​
getValueHandle​
())​{
// toggle LED if only 1 byte is written
​
if​
​
(​
params​
->​
len ​
==​​
1​
)​{
led ​
=​​
params​
->​
data​
[​
0​
];
// print led toggle
​
(​
​
params​
->​
data​
[​
0​
]​​
==​​
0x00​
)​​
?​printf​
(​
"\n\rled on "​
)​​
:​printf​
(​
"\n\rled off "​
);
}
// print the data if more than 1 byte is written
​
else​{
​
printf​
(​
"\n\r Data received: length = %d, data = 0x"​
,​
params​
->​
len​
);
for​
​
(​
int​x​
=​
0​
;​x ​
<​​
params​
->​
len​
;​x​
++)​{
printf​
(​
"%x"​
,​
params​
->​
data​
[​
x​
]);
}
}
// update the readChar with the value of writeChar
​
ble​
.​
updateCharacteristicValue​
(​
readChar​
.​
getValueHandle​
(),​
params​
->​
data​
,​
params​
->​
len​
);
}
}
Start the main loop and initialize the BLE baselayer: int​main​
(​
void)
{
printf​
(​
"\n\r********* Starting Main Loop *********\n\r"​
);
ble​
.​
init​
();
Once that is done, set up the disconnection callback so that it is called if there is a disconnection and the write character callback when data is written: ble​
.​
onDisconnection​
(​
disconnectionCallback​
);
ble​
.​
onDataWritten​
(​
writeCharCallback​
);
Now we have to set up the advertising parameters: 1. First we set the flag that this advertising message is BLE only. 2. We then set the advertising type as connectable and undirected. 3. The payload can now be propagated with the chosen device name and the service’s UUIDs list. 4. Finally the advertising interval, in multiples of .625 ms (the time interval Bluetooth uses to send single packets), needs to be established. ble​
.​
accumulateAdvertisingPayload​
(​
GapAdvertisingData​
::​
BREDR_NOT_SUPPORTED ​
|
GapAdvertisingData​
::​
LE_GENERAL_DISCOVERABLE​
);​​
// BLE only, no classic BT
ble​
.​
setAdvertisingType​
(​
GapAdvertisingParams​
::​
ADV_CONNECTABLE_UNDIRECTED​
);​​
// advertising type
ble​
.​
accumulateAdvertisingPayload​
(​
GapAdvertisingData​
::​
COMPLETE_LOCAL_NAME​
,​​
(​
uint8_t
*)​
DEVICE_NAME​
,​​
sizeof​
(​
DEVICE_NAME​
));​​
// add name
ble​
.​
accumulateAdvertisingPayload​
(​
GapAdvertisingData​
::​
COMPLETE_LIST_16BIT_SERVICE_IDS​
,​​
(​
uint8_t
*)​uuid16_list​
,​​
sizeof​
(​
uuid16_list​
));​​
// UUIDs in advertising
packet
ble​
.​
setAdvertisingInterval​
(​
160​
);​​
/* 100ms; in multiples of 0.625ms. */
Add in the custom service: ble​
.​
addService​
(​
customService​
);​​
// Add our custom service to device
Now that everything is set up we can start advertising the connection: ble​
.​
startAdvertising​
();​​
// start advertising
​/ infinite loop waiting for BLE interrupt events
/
while​​
​
(​
true​
)​{
ble​
.​
waitForEvent​
();​​
//Save power
}
}​​
// end of mainloop
Once we have set up everything as we like it, compile it and program the board. Check
Use the nRF Master Control Panel app on Android or the ​
LightBlue app on iOS to see ​
if the Service and Characteristic UUIDs are being set as expected: Once we can see the service and its characteristics try writing a single byte of 0x00 or 0x01 to the write characteristic to see the LED flash on and off. Next try opening up the console connected to the board and see the debug messages print out: 1. When we toggle the LED it should print out the status of the LED. 2. When we write more than one byte it should print out the hex representation of the data we send. Keep in mind we can send no more than 10 bytes, unless we increase the size of the characteristic. Evothings API
The service we created and put on our board is interactive: we can read the LED’s status and change it. We do that using a custom­built app, designed to work on the Evothings client that was installed earlier. The Evothings app code can be found here: https://github.com/BlackstoneEngineering/evothings­examples/tree/development/experiments/
mbed­Evothings­CustomGATT As before, drag the ​
index.html​
file into the Evothings workbench: We can open the code with a variety of applications; in this case Notepad++ was utilized to view and edit.When the code is open in an editor, change the ​
MyDeviceName​
variable in the app.js​
file to match the device name given to the mbed board: // JavaScript code for the mbed ble scan app
// Short name for EasyBLE library.
var​easyble ​
=​evothings​
.​
easyble;
// Name of device to connect to
var​​
MyDeviceName​​
=​​
"ChangeMe!!"
Make sure to walk through the code to check that everything makes sense. On startup, the app will begin searching for the device with the set name. When it connects the message will change from “connecting” to “connected” and the toggle button will change to green. If we press the button, it will switch to red and LED1 on the board will turn on. The snippet that controls the toggle function can be seen below: app​
.​
toggle ​
=​​
function​
()
{
// console.log(GDevice.__services[2].__characteristics[0]['uuid'])
GDevice​
.​
readCharacteristic​
(
"0000a001-0000-1000-8000-00805f9b34fb",
function​
(​
win​
){
var​view ​
=​​
new​​
Uint8Array​
(​
win)
var​led ​
=​​
new​​
Uint8Array​
(​
1)
if​
(​
view​
[​
0​
]​​
==​ledON​
){
$​
(​
'#toggle'​
).​
removeClass​
(​
'green')
$​
(​
'#toggle'​
).​
addClass​
(​
'red')
led​
[​
0​
]​​
=​ledOFF;
}
else​​
if​​
(​
view​
[​
0​
]​​
==​ledOFF​
){
$​
(​
'#toggle'​
).​
removeClass​
(​
'red')
$​
(​
'#toggle'​
).​
addClass​
(​
'green')
led​
[​
0​
]​​
=​ledON;
}
GDevice​
.​
writeCharacteristic(
'0000a002-0000-1000-8000-00805f9b34fb',
led,
function​
(​
win​
){​
console​
.​
log​
(​
"led toggled successfully!"​
)},
function​
(​
fail​
){​
console​
.​
log​
(​
"led toggle failed: "​
+​
fail​
)})
},
function​
(​
fail​
){
console​
.​
log​
(​
"read char fail: "​
+​
fail​
);
}
);
}
Once we are ready we can run the program on a smartphone. App is scanning for the device. App is connected and LED is off. App is connected and LED is on. It may take a moment for the app to find the board. Once the connection is confirmed, press the toggle to see if the LED turns on. Below is a animation of what the board should look like when repeatedly pressing the toggle button on the app.