-
Notifications
You must be signed in to change notification settings - Fork 7
Bluetooth Device Integration Guide ‐ Getting Started by Gateway
This document briefly introduces the basic usage of Integrating devices through the gateway API through examples. For ease of understanding, we limit the scenario to a debugging scenario where one gateway corresponds to one BLE device.
Version | Update Description | Update Date |
---|---|---|
v0.1.0 | Basic functions | 2023-07-12 |
v0.1.1 |
|
2023-07-13 |
v0.1.2 |
|
2023-07-17 |
The HTTP API of the gateway has encapsulated Bluetooth debugging tools to facilitate users to quickly use the gateway API to debug Bluetooth devices via the UI.
cassiaDebuggerTool.mp4
Central Device (Gateway) | Peripheral Device (Device) | |
---|---|---|
Device Type | Bluetooth Gateway | Android Phone |
Application or Product Model | E1000 | BLE Peripheral Simulator |
Application or Firmware Version | 2.1.1.2303082218 | v1.1.3 |
BLE device integration, namely operating the following main operations on BLE devices through Cassia's API:
- Discover -> Connect -> Read and Write Data -> Receive Data -> Disconnect
- Fragmentation, data encoding and decoding, encryption and decryption, integrity checking involved in each process need to be implemented according to the specific GATT application protocol of the BLE device
The main API interactions generally involved are as follows:
/~https://github.com/AcaciaNetworks/ble-test-peripheral-android
Device integration generally falls into the following basic application scenarios, including the commonly used main APIs, which can be quickly familiarized through examples.
Scenario | Description | Example |
---|---|---|
Advertising Data Collection |
|
|
Issue Commands to Device |
|
|
Short Connection to Retrieve Device Data |
|
|
Long Connection to Retrieve Device Data |
|
|
- Short connection and long connection are similar, the following only takes long connection as an example
-
# Use the curl command or open the following URL in a browser curl -v 'http://10.100.99.117/gap/nodes?event=1&active=1&filter_name=Cassia*'
- Data Description
Data Description 10.100.99.117 Gateway IP Address Cassia* - Filter devices whose advertising packet name matches Cassia*
- The simulated device advertising name is Cassia Demo APP
- Data Description
- Advertising Data
{ "bdaddrs": [ { "bdaddr": "72:A2:28:A8:68:68", "bdaddrType": "random" } ], "chipId": 0, "evtType": 0, "rssi": -74, "adData": "0201020EFFFFFF1819F99CD17000000000000416372A5107161C2A00000E7E", "name": "(unknown)" }
- Scanning Response Data
{ "bdaddrs": [ { "bdaddr": "72:A2:28:A8:68:68", "bdaddrType": "random" } ], "scanData": "10094361737369612044656D6F20417070", "evtType": 4, "rssi": -73, "chipId": 0, "name": "Cassia Demo App" }
Sample Data Description 72:A2:28:A8:68:68 Device MAC Address random Device MAC Address Type, this parameter will be used when connecting chipId Gateway Bluetooth Chip ID used for scanning evtType Device Advertising Type rssi Advertising Signal Value adData Original Advertising Packet Content of the Device scanData Original Scanning Response Packet Content of the Device name Parsed Device Advertising Packet Name Field, if available
Refer specifically to APP Advertising Instructions > Services Data, the data contained in the advertising packet is as follows:
0201020EFFFFFF1819F99CD17000000000000416372A5107161C2A00000E7E
Data Type | Start Index | Type | Endianness | Example |
---|---|---|---|---|
Heart Rate | 22 | uint8 | - |
|
Temperature | 29 | uint16 | Big-endian |
|
- The adData/scanData field in Scannig SSE Event Data is a Hex string, which needs to be converted to a byte array.
- According to the device protocol description, parse the data in the advertising packet. The temperature needs to be divided by 100.
# Node.js
let buf = Buffer.from('0201020EFFFFFF1819F99CD17000000000000416372A5107161C2A00000E7E', 'hex');
let heartrate = buf.readUInt8(22);
let temperature = buf.readUInt16BE(29) / 100
# Need NodeJS >= v8.0.0
# yarn add eventsource@1.0.7
/**
* sample for scan BLE devices and parse data
* use router local API in this example
* to run the code, you should have a Cassia Router
*/
const EventSource = require('eventsource');
const qs = require('querystring');
/*
* replace it with your router ip adderss
* remember switching on local API in setting page
*/
const HOST = 'http://10.100.99.117';
/*
* scan devices
* refer: /~https://github.com/CassiaNetworks/CassiaSDKGuide/wiki/RESTful-API#scan-bluetooth-devices
* Sever-Sent Event(SSE) is used in scan, connection-state and notify of Cassia RESTful API,
* SSE spec: https://html.spec.whatwg.org/multipage/server-sent-events.html#the-eventsource-interface
* API will send ':keep-alive' every 30 seconds in SSE connection for user to check if the connection is active or not.
* User need to call Cassia RESTful API to reconnect SSE in case that the connection is termincated abnormally, such as keep-alive lost, socket error, network problem, etc.
* Nodejs library 'eventsource' handle the SSE reconnection automatically. For other lanuages, the reconnection may needs to be handled by users application.
*/
function openScanSse() {
const query = {
/*
* filter devices whose rssi is below -75, and name begins with 'Cassia',
* there are many other filters, you can find them in document
* use proper filters can significantly reduce traffic between Router and AC
*/
filter_rssi: -75,
filter_name: 'Cassia*',
/*
* use active scan, default is passive scan
* active scan makes devices response with data packet which usually contains device's name
*/
active: 1
};
const url = `${HOST}/gap/nodes?event=1&${qs.encode(query)}`;
const sse = new EventSource(url);
sse.on('error', function(error) {
console.error('open scan sse failed:', error);
});
/*
* if scan open successful, it will return like follow:
* 5.2.1 Data Description
*/
sse.on('message', function(message) {
let data = JSON.parse(message.data);
let adDataHex = data.adData;
if (!adDataHex) return;
// Parsing broadcast packet data
let deviceMac = data.bdaddrs[0].bdaddr;
let buf = Buffer.from(adDataHex, 'hex');
let heartrate = buf.readUInt8(22);
let temperature = buf.readUInt16BE(29) / 100
console.log({adDataHex, deviceMac, heartrate, temperature});
});
}
(async () => {
try {
openScanSse();
} catch(ex) {
console.error('fail:', ex);
}
})();
Running result
{ adDataHex: '0201020EFFFFFF1819F99CD17000000000000416372A5407161C2A00000E81',
deviceMac: '72:A2:28:A8:68:68',
heartrate: 84,
temperature: 37.13 }
// ...
scan.mp4
We use sending messages to the test device as an example, Alert Notification Service
-
Connect API Request
curl -L -X POST 'http://10.100.99.117/gap/nodes/72:A2:28:A8:68:68/connection' -H 'Content-Type: application/json' --data-raw '{ "type": "random" }'
- Field Description
Field Name Field Description 10.100.99.117 Gateway IP address 72:A2:28:A8:68:68 Device MAC address - Known from 6.2 Scanning API Data
type Address type - random
- Known from 6.2 Scanning API Data
Others Connect/Disconnect to a Target Device
- Field Description
-
Connect API Response
OK
- Error Code Description
Error Code Description OK Success Others Error Messages
- Error Code Description
-
curl -L -X GET 'http://10.100.99.117/gatt/nodes/72:A2:28:A8:68:68/services/characteristics/descriptors'
- Field Description
Field Name Field Description 72:A2:28:A8:68:68 Device MAC address - Known from 6.2 Data Format, Scanning
Others Discover API
- Field Description
-
- The remaining services are omitted, we only care about the Alert Notification Service UUID here
[ { "uuid": "00001811-0000-1000-8000-00805f9b34fb", "primary": true, "characteristics": [ { "descriptors": [ { "handle": 60, "uuid": "00002a46-0000-1000-8000-00805f9b34fb" }, { "handle": 61, "uuid": "00002902-0000-1000-8000-00805f9b34fb" } ], "handle": 60, "properties": 24, "uuid": "00002a46-0000-1000-8000-00805f9b34fb" } ], "handle": 58 } ]
-
Data Description
Service Name Service UUID Service Handle Alert Notification Service 00002a46-0000-1000-8000-00805f9b34fb 60 - According to the Alert Notification Servicedescription, we only need to care about the Handle corresponding to its Service UUID (because the current Cassia Write/Read API only supports the handle parameter)
- The Handle value here is 60, which is actually obtained from the above API return result
- Generally, the Handle value corresponding to the Service UUID of the same type of device is fixed, so it only needs to be obtained once during debugging, and can be directly fixed in the code
- If considering the compatibility of different model devices, it can currently be obtained through the Discover API
-
Write API Request
curl -L -X GET 'http://10.100.99.117/gatt/nodes/72:A2:28:A8:68:68/handle/60/value/050148656c6c6f2c2043617373696121'
- Field Description
Field Name Field Description 10.100.99.117 Gateway IP address 72:A2:28:A8:68:68 Peripheral Device MAC address handle Known from 7.2 Discover API value Alert Notification Service - 050148656c6c6f2c2043617373696121
- 05: categoryId, fixed
- 01: Number Of New Alert, fixed
- 48656c6c6f2c2043617373696121: utfs,
Hello, Cassia!
Others Write API - 050148656c6c6f2c2043617373696121
- Convert specified string to HEX string
# linux bash echo -n 'Hello, Cassia!' | xxd -p 48656c6c6f2c2043617373696121 # NodeJS Buffer.from('Hello, Cassia!').toString('hex')
- Field Description
-
Write API Response
OK
- Error Code Explanation
Error Code Explanation OK Success Others Error Messages
- Error Code Explanation
-
Disconnect API Request
curl -L -X DELETE 'http://10.100.99.117/gap/nodes/72:A2:28:A8:68:68/connection
- Field Description
Field Name Field Description 10.100.99.117 Gateway IP address 72:A2:28:A8:68:68 Peripheral Device MAC address Others Connect/Disconnect to a Target Device
- Field Description
-
Disconnect API Response
OK
- Error Code Explanation
Error Code Explanation OK Success Others Error Messages
- Error Code Explanation
# Need NodeJS >= v8.0.0
# yarn add request
/**
* connect BLE device, write handle value, disconnect device
*/
const request = require('request');
/*
* replace it with your router ip adderss
* remember switching on local API in setting page
*/
const HOST = 'http://10.100.99.117';
/*
* replace it with your device mac adderss
*/
const DEVICE_MAC = '72:A2:28:A8:68:68';
/*
* replace it with your device addr type
*/
const DEVICE_ADDR_TYPE = 'random';
/*
* replace it with your msg
*/
const ALERT_MESSAGE = 'Hello, Cassia!';
const ALERT_SERVICE_UUID = '00002a46-0000-1000-8000-00805f9b34fb';
function req(options) {
return new Promise((resolve, reject) => {
request(options, function (error, response) {
if (error) reject(error);
else if (response.statusCode !== 200) reject(response.body);
else resolve(response.body);
});
});
}
/*
* connect one device
* refer: /~https://github.com/CassiaNetworks/CassiaSDKGuide/wiki/RESTful-API#connectdisconnect-to-a-target-device
*/
function connect(deviceMac, addrType) {
let options = {
method: 'POST',
url: `${HOST}/gap/nodes/${deviceMac}/connection`,
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({timeout: 5000, type: addrType})
};
return req(options);
}
/*
* Read/Write the Value of a Specific Characteristic
* refer: /~https://github.com/CassiaNetworks/CassiaSDKGuide/wiki/RESTful-API#readwrite-the-value-of-a-specific-characteristic
*/
function write(deviceMac, handle, value) {
let options = {
method: 'GET',
url: `${HOST}/gatt/nodes/${deviceMac}/handle/${handle}/value/${value}`,
};
return req(options);
}
/*
* Read/Write the Value of a Specific Characteristic
* refer: /~https://github.com/CassiaNetworks/CassiaSDKGuide/wiki/RESTful-API#readwrite-the-value-of-a-specific-characteristic
*/
function discover(deviceMac) {
let options = {
method: 'GET',
url: `${HOST}/gatt/nodes/${deviceMac}/services/characteristics/descriptors`,
};
return req(options);
}
function getAlertChar(services) {
for (let index = 0; index < services.length; index++) {
let service = services[index];
let characteristics = service.characteristics;
let alertCharacteristic = characteristics.find(c => c.uuid === ALERT_SERVICE_UUID);
if (alertCharacteristic) {
return alertCharacteristic;
}
}
return null;
}
/*
* disconnect one device
* refer: /~https://github.com/CassiaNetworks/CassiaSDKGuide/wiki/RESTful-API#connectdisconnect-to-a-target-device
*/
function disconnect(deviceMac) {
let options = {
method: 'DELETE',
url: `${HOST}/gap/nodes/${deviceMac}/connection`,
};
return req(options);
}
(async () => {
try {
await connect(DEVICE_MAC, DEVICE_ADDR_TYPE);
console.log('connect device ok:', DEVICE_MAC);
let servicesJson = await discover(DEVICE_MAC);
console.log('discover services ok:', servicesJson);
let services = JSON.parse(servicesJson);
let alertChar = getAlertChar(services);
if (alertChar) {
console.log('found alert char:', JSON.stringify(alertChar));
let msgHex = Buffer.from(ALERT_MESSAGE).toString('hex');
msgHex = `0501${msgHex}`;
await write(DEVICE_MAC, alertChar.handle, msgHex);
console.log('write alert char ok:', alertChar.handle, msgHex);
}
await disconnect(DEVICE_MAC);
console.log('disconnect device ok:', DEVICE_MAC);
} catch(ex) {
console.error('fail:', ex);
}
})();
writeCmd.mp4
We use the real-time heart rate acquisition of the test device as an example, Heart Rate Service
-
Connect API Request
curl -L -X POST 'http://10.100.99.117/gap/nodes/72:A2:28:A8:68:68/connection' -H 'Content-Type: application/json' --data-raw '{ "type": "random" }'
- Field Description
Field Name Field Description 10.100.99.117 Gateway IP address 72:A2:28:A8:68:68 Device MAC address type Address Type - random
- 6.2 Scanning API Data
Others Connect/Disconnect to a Target Device
- Field Description
-
Connect API Response
OK
- Error Code Description
Error Code Description OK Success Others Error Messages
- Error Code Description
-
curl -L -X GET 'http://10.100.99.117/gatt/nodes/72:A2:28:A8:68:68/services/characteristics/descriptors'
- Field Explanation
Field Name Field Explanation 72:A2:28:A8:68:68 Device MAC address Others Discover API
- Field Explanation
-
- Other services are omitted, we only care about the Heart Rate Service UUID (00002a37-0000-1000-8000-00805f9b34fb) here.
[ { "uuid": "0000180d-0000-1000-8000-00805f9b34fb", "primary": true, "characteristics": [ { "descriptors": [ { "handle": 52, "uuid": "00002a37-0000-1000-8000-00805f9b34fb" }, { "handle": 53, "uuid": "00002902-0000-1000-8000-00805f9b34fb" } ], "handle": 52, "properties": 16, "uuid": "00002a37-0000-1000-8000-00805f9b34fb" } ], "handle": 50 } ]
-
Data Explanation
Service Name Service UUID Service Handle Data Heart Rate Service 00002a37-0000-1000-8000-00805f9b34fb 52 Actual reported real-time heart rate value Heart Rate Service CCCD 00002902-0000-1000-8000-00805f9b34fb 53 - Write 0100 to control Notification on
- Write 0000 to control Notification off
- The UUID of the Client Characteristic Configuration Descriptor (CCCD) is fixed in the Bluetooth specification. Its UUID is
00002902-0000-1000-8000-00805f9b34fb
- Generally, for the same type of device, the Handle value corresponding to its service UUID is fixed, so you only need to get it once during debugging, and it can be fixed in the code.
- If considering the compatibility of different model devices, it can currently be obtained through the Discover API.
According to the protocol explained in 7.2, we need to write 0100 to the Heart Rate Service CCCD to control the Notification on.
-
Write API Request
curl -L -X GET 'http://10.100.99.117/gatt/nodes/72:A2:28:A8:68:68/handle/53/value/0100'
- Field Explanation
Field Name Field Explanation 10.100.99.117 Gateway IP address 72:A2:28:A8:68:68 Device MAC address handle 7.2 Discover API Heart Rate Service CCCD UUID corresponding Handle value value Heart Rate Service - 0100: Turn on notification
Others Write API
- Field Explanation
-
Write API Response
OK
- Error Code Explanation
Error Code Explanation OK Success Others Error Messages
- Error Code Explanation
- Data Format
{ "value": "08560000", "handle": 52, "id": "63:B4:C5:46:5C:A2", "dataType": "notification" }
- Field Explanation
Field Name Field Explanation value Notification data handle Indicates which business data, here corresponds to Heart Rate id Device MAC address dataType Indicates data type, notification/indication type
- Field Explanation
# Need NodeJS >= v8.0.0
# yarn add eventsource@1.0.7
# yarn add request
/**
* connect BLE device, open notification, receive notification data
*/
const EventSource = require('eventsource');
const request = require('request');
/*
* replace it with your router ip adderss
* remember switching on local API in setting page
*/
const HOST = 'http://10.100.99.117';
/*
* replace it with your device mac adderss
*/
const DEVICE_MAC = '72:A2:28:A8:68:68';
/*
* replace it with your device addr type
*/
const DEVICE_ADDR_TYPE = 'random';
const HEART_RATE_UUID = '00002a37-0000-1000-8000-00805f9b34fb';
function req(options) {
return new Promise((resolve, reject) => {
request(options, function (error, response) {
if (error) reject(error);
else if (response.statusCode !== 200) reject(response.body);
else resolve(response.body);
});
});
}
/*
* Receive Notification and Indication
* refer: /~https://github.com/CassiaNetworks/CassiaSDKGuide/wiki/RESTful-API#receive-notification-and-indication
* Sever-Sent Event(SSE) is used in scan, connection-state and notify of Cassia RESTful API,
* SSE spec: https://html.spec.whatwg.org/multipage/server-sent-events.html#the-eventsource-interface
* API will send ':keep-alive' every 30 seconds in SSE connection for user to check if the connection is active or not.
* User need to call Cassia RESTful API to reconnect SSE in case that the connection is termincated abnormally, such as keep-alive lost, socket error, network problem, etc.
* Nodejs library 'eventsource' handle the SSE reconnection automatically. For other lanuages, the reconnection may needs to be handled by users application.
*/
function openNotifySse() {
const url = `${HOST}/gatt/nodes`;
const sse = new EventSource(url);
sse.on('error', error => {
console.error('open notify sse failed:', error);
});
sse.on('message', message => {
console.log('recevied notify sse message:', message);
let data = JSON.parse(message.data);
let deviceMac = data.id;
let valueHex = data.value;
let valueBuf = Buffer.from(valueHex, 'hex');
let heartrate = valueBuf.readUInt8(1);
console.log("heart rate: ", deviceMac, heartrate);
});
return Promise.resolve(sse);
}
/*
* connect one device
* refer: /~https://github.com/CassiaNetworks/CassiaSDKGuide/wiki/RESTful-API#connectdisconnect-to-a-target-device
*/
function connect(deviceMac, addrType) {
let options = {
method: 'POST',
url: `${HOST}/gap/nodes/${deviceMac}/connection`,
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({timeout: 5000, type: addrType, fail_retry_times: 2})
};
return req(options);
}
/*
* Read/Write the Value of a Specific Characteristic
* refer: /~https://github.com/CassiaNetworks/CassiaSDKGuide/wiki/RESTful-API#readwrite-the-value-of-a-specific-characteristic
*/
function write(deviceMac, handle, value) {
let options = {
method: 'GET',
url: `${HOST}/gatt/nodes/${deviceMac}/handle/${handle}/value/${value}`,
};
return req(options);
}
/*
* Read/Write the Value of a Specific Characteristic
* refer: /~https://github.com/CassiaNetworks/CassiaSDKGuide/wiki/RESTful-API#readwrite-the-value-of-a-specific-characteristic
*/
function discover(deviceMac) {
let options = {
method: 'GET',
url: `${HOST}/gatt/nodes/${deviceMac}/services/characteristics/descriptors`,
};
return req(options);
}
function getHandleByUuid(services, uuid, isCCCD=false) {
for (let index = 0; index < services.length; index++) {
let service = services[index];
let characteristics = service.characteristics;
let characteristic = characteristics.find(c => c.uuid === uuid);
if (!characteristic) continue;
if (!isCCCD) return characteristic.handle;
let descriptors = characteristic.descriptors;
let descriptor = descriptors.find(d => d.uuid.toLowerCase() === '00002902-0000-1000-8000-00805f9b34fb');
if (!descriptor) continue;
return descriptor.handle;
}
return 0;
}
(async () => {
try {
await openNotifySse();
console.log('open notify sse ok');
await connect(DEVICE_MAC, DEVICE_ADDR_TYPE);
console.log('connect device ok:', DEVICE_MAC);
let servicesJson = await discover(DEVICE_MAC);
console.log('discover services ok:', servicesJson);
let services = JSON.parse(servicesJson);
let handle = getHandleByUuid(services, HEART_RATE_UUID, true);
if (!handle) {
console.log('no heart rate service found');
await disconnect(DEVICE_MAC);
console.log('disconnect device ok:', DEVICE_MAC);
return;
}
console.log('found uuid handle ok:', HEART_RATE_UUID, handle);
let hex = '0100';
await write(DEVICE_MAC, handle, hex);
console.log('write alert char ok:', handle, hex);
} catch(ex) {
console.error('fail:', ex);
}
})();
monitorData.mp4
This document only covers a scenario with one gateway and one BLE device. Actual business scenarios may involve multiple gateways and multiple BLE devices, and the operations involved will be more complex. However, the method of using Cassia's API is consistent, and only needs to be combined and extended for specific business scenarios. Real business scenarios will generally involve the following operations.
Scenarios | Example |
---|---|
Connection Retry | For example, if the device is mobile and the signal is poor, connection fails |
Reconnection after Disconnection | Maintaining a long connection, such as heart rate monitoring |
Single Gateway Multiple Device Scenario | Connection speed requirements, concurrent data transmission requirements |
Multi-Gateway Scenario | Device movement, gateway preference |
Device Application Protocol |
|
Copyright © 2021 Cassia Networks, Inc.
RESTful API
-
Traffic Related API
- Scan Bluetooth Devices
- Filter Scanned Data based on Device MAC, RSSI, Name, and UUID
- Enhanced Scan Filter (v2.0 and above)
- Connect/Disconnect to a Target Device
- Batch Connect to a Target Device (v2.0 and above)
- Discover GATT Services and Characteristics
- Read/Write the Value of a Specific Characteristic
- Send Advertise Data
- Get Device Connection Status
- Receive Notification and Indication
- Get RSSI for BLE Connection (v2.0 and above)
-
Secure Pairing API
-
Router Auto-Selection API