ArduinoBLE

Für den kleinen Seeed Studio XIAO-NRF52840 brauche ich die ArduinoBLE Library für die Nutzung der BLE Dienste. Diese Library ist sehr einfach in der Anwendung.
Hier dokumentiere ich wie ich sie anwende und welche Erfahrung ich damit machte.
BLE GATT Service
BLE Brief
Es gibt sehr viele und gute Information über BLuetooth LE, so dass ich davon ausgehe jeder kann sich schlau machen so wie er kann und will. Hier sieht man eine Variante mit ArduinoBLE die man ohne grosses Lesen, Lernen und Wissen zum laufen bringt. Dann hat man eine Basis zum Lernen und Spielen.
Um Bluetooth LE zu benutzen hat man verschiedene Möglichkeiten. Man kann mit Broadcasting verbindungslos Informationen versenden, verbindungsorientiert mit GATT Services oder mit Mesh many to many Daten austauschen. Mein erster BLE Versuch habe ich connected orientiert mit GATT Service und der ArduinoBLE Library unternommen. Nachfolgend sind die Ergebnisse dokumentiert. Weil ich nicht wusste wie ich die Daten formatieren soll, habe ich den Binary Sensor Service BSS von Bluetooth implementiert (siehe den BSS Blog).
Ein Problem beim Implementieren von Bluetooth Lösungen ist die Tatsache, dass man einen BLE Partner braucht, der entweder zuhört oder Daten sendet. Um am Anfang es zu vereinfachen steht auf den Smartphone eine NFR Connect for Mobile App von Nordic Semiconductor zur Verfügung.
Es wird empfohlen, gewisse GATT Services immer zu implementiere. Im ArduinoBLE sind diese versteckt.
- Generic Access Profile (GAP) service.
- Name and appearance characteristics within the GAP service.
Später werde ich darauf zurückkommen.
UUID (universally unique identifier)
Eine UUID ist ein universell eindeutige ID , die garantiert überall zu jeder Zeit eindeutig ist“ (Bluetooth 4.2 spec, Vol 3, Part B, Section 2.5.1 UUID). Eine UUID ist ein 128-Bit-Wert. Es gibt von der Bluetooth SIG reservierte UUIDs, die im Allgemeinen durch ihre 16-Bit-Aliase dargestellt werden. Diese Aliasnamen dienen der Vereinfachung und stellen einen 128-Bit-Wert dar, der wie folgt berechnet wird:
128-Bit-Wert = 16-Bit-Wert * 2^96 + BluetoothBaseUUID
wobei die BluetoothBaseUUID 00000000-0000-1000-8000-00805F9B34FB lautet Beispiele sind:
0x180F -> Batteriedienst-UUID (128-Bit-UUID: 0000180F-0000-1000-8000-00805F9B34FB)
0x2A00 -> Gerätenamen-Charakteristik UUID (128-Bit UUID: 00002A00-0000-1000-8000-00805F9B34FB)
Da mein Sensor BLE Standard verwendet und ich nicht beabsichtige ein verkaufsfähiges Produkt zu erstellen, verwende ich überall die 16-Bit Aliases.
BLE Advertising
BLE Brief
Mit advertising macht sich ein Bluetooth Gerät in seinem bluetooth Umfeld bemerkbar. Damit können Central den Peripheral erkennen, sich mit ihm Verbinden und/ oder Daten austauschen. Sobald ein Client sich mit dem Peripheral verbunden hat, ist advertising nicht mehr nötig. Advertising kann man aber auch zum Broadcasten von kleineren Daten verwenden, In meinem Anwendung werden beide Verfahren angewendet.
Das Advertising-Management mit -Security verschieben wir auf später, da es als eigenes Thema zusammen mit Power Management behandelt werden muss.
Advertising Packet Payload Format
Unabhängig davon, ob du Legacy- oder Extended-Advertising-Pakete verwenden, ist das Format des Payloads gleich.
Es ist wie folgt aufgebaut:

SLTV steht für „Length-Type-Value“ . Du kannst so viele dieser LTV haben, wie in die Payload eines Advertising packet passen.
Die Felder Länge und Typ sind obligatorisch und nehmen jeweils ein Byte ein.
Länge: Definiert die Länge der Felder Type + Value.
Typ: Die verschiedenen Typen sind im Core Specification Supplement-Dokument definiert, das Sie hier herunterladen können. Beispiele sind Device Name, Service UUID, Tx Power Level, etc.
Hier ist der Screenshot vom NRFConnect meiner Advertising data.
Wert: Dies beinhaltet die tatsächlichen Daten, die für den Scanner von Bedeutung sind. Dabei kann es sich beispielsweise um den tatsächlichen Gerätenamen, den Sensorwert usw. handeln.
Weil wir in der WPF Testsuit die Advertising daten nach dem localName filtern haben die Advertising Daten immer ein LTV localname. Um gleichwohl Andere Daten mitzusenden sollte der Local name nicht zu lang sein , in meinem Falle ist er „TIS.001“. ist das Datenpacket zu lang sendet die ArduinoBLE nichts.
Im nachfolgenden Code snipped ist der Code abgebildet welcher das NRF-Connect screenshot ergab.
//for Advertising it is important to have the variable in the global scope
uint8_t manufactData[10] = {0x01,0x02,0x03,0x04, 0x03,0x07,0x07,0x08, 0x09,0x0A};
uint8_t serviceData[16] = {0x01,0x02,0x03,0x04, 0x05,0x06,0x07,0x08, 0x09,0x0A,0x01,0x02, 0x09,0x0A,0x01,0x02};
uint8_t sensoreData[17] = {0x01,0x00,0x02,0x04, 0x09,0x08,0x07,0x06, 0x09,0x0A,0x01,0x02, 0x09,0x0A,0x01,0x02, 0x01};
void setup() {
/*
... I hide other statement for clarification
*/
// Advertising service data
BLEAdvertisingData scanData;
scanData.setLocalName("TIS.001");
//scanData.setFlags(BLEFlagsGeneralDiscoverable);
scanData.setAdvertisedServiceData(0x183B,sensoreData,sizeof(sensoreData));
BLE.setScanResponseData(scanData);
// Advertising manufacturer data
BLEAdvertisingData advData;
advData.setLocalName("TIS.001");
setManufacturerData("WIMO000001"); // is the first wimo modul of TIS-module => first window in the house
advData.setManufacturerData(0xFFFF, manufactData, sizeof(manufactData));
BLE.setAdvertisingData(advData);
BLE.setAdvertisedService(binarySensorService);
BLE.advertise();
/*
... I hide other statement for clarification
*/
}
Wenn wir den Code mit dem NRF-Connect Screenshot analysieren, dann können wir die LTV entsprechend auflösen und erhalten das nachfolgende Diagramm.

Die Advertising Service Data welche ich hier anwende haben nichts mit den BSS Spezifikation zu tun und sind ein reine Eigenerfindung. Ich advertise jedes mal wenn ein Wert wechselt eine Id, einen Zähler und 3 x32 Bit plus pro Sensortype die Anzahl . Damit die advertising Pakete gefiltert werden können sende ich zusätzlich den Lokal Name. Somit kann ich in der WPF-Testsuit beim AdvetisingWatcher das Filter LocalName mitgeben und bekomme nur diese Advertising mit.
Programmbeschreibung
Das Programm macht mehr oder weniger nichts anderes als Sensoren abzufragen und bei einer Änderung sie an einen Central sendet. Für die Kommunikation zwischen Peripheral und Central wird das Bluetooth Protokoll Binary Sensor Service umgesetzt. Im weiteren werden noch Advertising Daten broadcasted die nach einem individuellen Schema umgesetzt wurde.
Das Batterie und Power Management verschieben wir auf später, da es ein eigenes Thema mit entsprechendem Effort sein wird.

Im Setup wird alles BLE Spezifische aufgesetzt und initialisiert. Danach ist eigentlich der Perifpheral im Loop bereit für Bluetooth Connection.
Der Loop macht fünf Sachen
- Prüfen ob ein Central connected hat und sich dies mit den StateManager merken
- Nach einem Intervall von 2 Sek. prüfen ob ein digitaler Input gewechselt hat
- Analogeingänge prüfen ob ein Wert eines Piezo Element anliegt. wird ein Wert gelesen, dann wird er im Ringbuffer gespeichert.
- Ist es der erste Wert im Ringbuffer, dann wird ein Timer gestartet um ein Werte Serie zu bilden.
- Ist der Timer abgelaufen, dann wird die Werte-Serie ausgewertet und der Ringbuffer geleert.
- Der Statemanager verwaltet die verschiedenen Stauts und zeigt sie mit der farbigen LED an
- Ist der Satus Connected wahr, dann werden bei Änderungen die Werte an den Central gesendet.
- Erfüllen die Werte in der Werte-Serie die Kriterien, dann wird eine Vibration oder Glasbruch gemeldet.
- Vibrationen werden nur bei geschlossenen Fenster ermittelt und nach einer gewissen Zeit zurück gesetzt.
- Glasbruch werden immer ermittelt und bleiben erhalten bis sie manuell zurückgesetzt werden.
// This are the BLE Peripheral Information
#define SYSID "TIS.001.XIAO-NRF52840"
#define MODEL "TIS.HOBRO.WINDOW"
#define SERIE "001.001.240525"
#define FWREV "ArduinoBLE V 1.3.6"
#define HWREV "X 1.0-0"
#define SWREV "X 0.0-4"
#define FIRMA "TIS-Switzerland"
// Appearance of BLE
#define WINDOW_APPEARANCE 0X0706 // Catecory => Access Control, dh. = zugang zum objekt
// BLE Services
#define DEVICEINFOSERVICEVALUE "180A"
#define BATTERYSERVICEVALUE "180F"
#define BINARYSENSORSERVICEVALUE "183B"
#define AUTOMATIONIOSERVICEVALUE "1815"
// BLE Characteristics
#define SYSIDCHARACTERVALUE "2A23"
#define MODELCHARACTERVALUE "2A24"
#define SERIECHARACTERVALUE "2A25"
#define FWREVCHARACTERVALUE "2A26"
#define HWREVCHARACTERVALUE "2A27"
#define SWREVCHARACTERVALUE "2A28"
#define FIRMACHARACTERVALUE "2A29"
// BLE Descriptor type
#define USERDESCRIPTION "2901"
#define PRESENTATIONFORMAT "2904"
#define BATTERYLEVELCHARAKTERVALUE "2A19"
#define BSSCONTROLPOINTCHARACTERVALUE "2B2B"
#define BSSRESPONSECHARACTERVALUE "2B2C"
// Common globals
#define CRITICALBATTERYLEVEL 0 // adjust level if battery is connected
#define MINPIEZOLEVEL 150 // the level must have to add to the serie of values
#define MINPIEZODELTA 25 // the value differs from brevious to store;
#define BLEADR001 51156894393645 // first XIAO for not forget
In der Definitiondatei habe ich mehr oder weniger alle UUID die ich benötige definiert. Am Anfang stehen meine Device Information. Ich habe denGATT deviceInformation Service genommen, weil man damit schon mal auf dem Central lesbare Informationen sieht.
// Bluetooth® Services
BLEService deviceInfoService( DEVICEINFOSERVICEVALUE );
BLEService batteryService( BATTERYSERVICEVALUE );
BLEService binarySensorService( BINARYSENSORSERVICEVALUE );
BLECharacteristic bssControlPointChar( BSSCONTROLPOINTCHARACTERVALUE, BLEWrite | BLENotify,256, *buffer);
BLECharacteristic bssResponseChar( BSSRESPONSECHARACTERVALUE, BLERead | BLENotify,256, *buffer);
BLEUnsignedCharCharacteristic batteryLevelChar( BATTERYLEVELCHARAKTERVALUE, BLERead | BLENotify);
Wir müssen zuerts die Services und die zugehörigen Characteristics deklarieren. Bei den Characteristics muss man festlegen ob der Central schreiben, lesen darf und ob er benachrichtigt werden kann.
if (!BLE.begin()) {
StateManager.SetState(error);
while (1);
}
binarySensor.InitNrOfSensors(8,0,4); // 4 ocl-shas, 4 ocl-glasbr. 0 human and 4 vibration
BLE.setLocalName( "TIS-WindowMonitor");
BLE.setDeviceName("TIS-WindowMonitor");
// Battery Service
batteryService.addCharacteristic(batteryLevelChar);
BLE.addService(batteryService);
batteryLevelChar.writeValue(oldBatteryLevel); // set initial value for this characteristic
// Sensor Service
binarySensorService.addCharacteristic(bssControlPointChar);
binarySensorService.addCharacteristic(bssResponseChar);
BLE.addService(binarySensorService);
// Device Info Service
BLECharacteristic sysIdCharacter = BLECharacteristic(SYSIDCHARACTERVALUE, BLERead, SYSID);
BLECharacteristic modelCharacter = BLECharacteristic(MODELCHARACTERVALUE, BLERead, MODEL);
BLECharacteristic serieCharacter = BLECharacteristic(SERIECHARACTERVALUE, BLERead, SERIE);
BLECharacteristic fwRevCharacter = BLECharacteristic(FWREVCHARACTERVALUE, BLERead, FWREV);
BLECharacteristic hwRevCharacter = BLECharacteristic(HWREVCHARACTERVALUE, BLERead, HWREV);
BLECharacteristic swRevCharacter = BLECharacteristic(SWREVCHARACTERVALUE, BLERead, SWREV);
BLECharacteristic firmaCharacter = BLECharacteristic(FIRMACHARACTERVALUE, BLERead, FIRMA);
deviceInfoService.addCharacteristic(sysIdCharacter);
deviceInfoService.addCharacteristic(modelCharacter);
deviceInfoService.addCharacteristic(serieCharacter);
deviceInfoService.addCharacteristic(fwRevCharacter);
deviceInfoService.addCharacteristic(hwRevCharacter);
deviceInfoService.addCharacteristic(swRevCharacter);
deviceInfoService.addCharacteristic(firmaCharacter);
BLE.addService(deviceInfoService);
BLE.setAppearance(WINDOW_APPEARANCE);
BLE.setEventHandler(BLEConnected, blePeripheralConnectHandler);
BLE.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler);
bssControlPointChar.setEventHandler(BLEWritten,bssControllPointWritten);
BLE.advertise(); // shout out I am here
Im SETUP müssen wir die Characteristics den GATT Services hinzufügen und anschliessend dem BLE Peripheral beifügen.
- Wir definieren die Namen damit wir im Central nach diesem Device suchen können. mit unterschiedlichen Namen bei setlocalname und setdevicename war das Auffinden nicht konsistent. Ich fand nicht heraus wann welcher im advertising gilt.
- Die DeviceInformation Characteristics werden direkt mit Werten geladen, darum müssen sie im setup erstellt werden.
- Anschliessend setzen wir die Appearance.
- Drei Eventhandler, um Connection, Disconnection mitzubekommen und der dritte, wenn der Central uns etwas sendet.
- Am Ende des SETUP starten wir mit dem advertise, damit uns jemand finden kann.
/// @brief This callback is excecuted whenever the character is written by the peripheral.
/// It does disassembling the datastream, build the coresponding response and semd
/// it to the charcateristic in fragments if needed.
/// @param central is the central that the characteristc belongs.
/// @param characteristic is the character of hte central that has been written
void bssControllPointWritten(BLEDevice central, BLECharacteristic characteristic) {
int sTyp[] = {PARVALTYPSOCL,PARVALTYPSHDE,PARVALTYPSVIP,PARVALTYPMOCL,PARVALTYPMHDE,PARVALTYPMVIP,240};
byte b[256] = {0};
byte *ptBuffer = &b[0];
int len = 0;
int cmd;
int idx ;
if (characteristic.valueLength() > 0) {
len = characteristic.readValue(ptBuffer, 256);
JoinFragment.Add(ptBuffer, len);
} else {
// Serial.println("Callback can't read");
return ;
}
int datalength = JoinFragment.GetLength();
if (JoinFragment.IsLast() && datalength > 0){
int result = binarySensor.DisasemblingControlCommand(0, JoinFragment.Data, datalength);
if (result > 0) {
cmd = result /10;
idx = result %10;
if (cmd == 1) binarySensor.CreateGetSensorStatusResponse(sTyp[idx], bssResponseChar);
else
if (cmd == 2) binarySensor.CreateSettingSensorResponse( sTyp[idx], bssResponseChar);
}
}
}
/// @brief If the central is connected then we set the state and then
/// we must start again the advertisment if we want to
/// broadcast our sensors by advertise data.
/// @param central The central that is connected.
void blePeripheralConnectHandler(BLEDevice central) {
StateManager.SetState(connected);
BLE.advertise();
}
void blePeripheralDisconnectHandler(BLEDevice central) {
// BLE.advertise();
StateManager.SetState(disconnected);
}
Der StateManager gibt mir den aktuellen Status des Peripheral. Damit wird die BuildinLED bedient und die BLE Connection aktualisiert. Somit kann ich die Sensoren abfragen unabhängig des BLE Dienstes. Die LED Farben werden anhand der Truetable priorisiert und gesetzt. Der StateManager verwaltet alle Status so dass keine globalen Variablen verwendet werden müssen.
/// @brief Sets the private property of different state and then depending of the property
/// the state of the applicateion is evaluatet. The code is based on a true tabelle
/// which says when should the buildin led have what color. the color shows you the current state of
/// the device.
/// @param state the property you want set.
/// @return not used yet.
int StatusOfWindow::SetState(windowState state){
ledColor color;
switch(state) {
case disconnected: isConnected = false; break;
case connected: isConnected = true; break;
case vibrationOff: _vibration = false; break;
case vibrationOn: _vibration = true; break;
case glassOk: _glassbroken = false; break;
case glassBreak: _glassbroken = true; break;
case batLow: _batteryLow = true; break;
case batOk: _batteryLow = false; break;
case closeLe: _OpenLeft = false; break;
case openLe: _OpenLeft = true; break;
case closeMi: _openMiddle = false; break;
case openMi: _openMiddle = true; break;
case closeRi: _openRight = false; break;
case openRi: _openRight = true; break;
case error: _error = true; break;
case errorOff: _error = false; break;
default: break;
}
if (_batteryLow || _error) color = red;
else if (_glassbroken) color = magenta;
else if ( _OpenLeft || _openMiddle || _openRight) color = green;
else if (_vibration) color = yellow;
else if (!isConnected) color = blue;
else color = none;
SetBoardLed(color);
return 0;
}

Goodies
Goodies
Link auf Source
Develope Info
Windows 11 Pro, 64Bit HP Elitdesk
- Visual Studio Code 1.9.5
- Platform.IO Core 6
- C++
- ArduinoBLE Library
- BLE Device Nordic NRF52840 (Seeed Studio XIAO BLE 52840 )
- BSS nach Bluetooth Binary Sensor Service Specification V1.0 , Rev. 2019-07-02
Challenges
- Da meine Sensorerfassung auch laufen soll, wenn keine Connection vorhanden ist, habe ich einen StateManager gebaut der den Status des Peripheral festhält. Dabei stellte ich fest, wenn der Returnwert vom BLE.Central nicht verwendet wird, man ein komisches Verhalten bekommt, so dass man keine Daten senden kann. Obschon die Eventhandler connected melden kommt kene richtige Connection zustande. Sobald man den Returnwert im Loop verwendet klappt alles.
- ArduinoBLE Dokumentation erklärt nicht viel über das Advertising. Zusammen mit Windows Bluetooth LE ist es eine Herausforderung es zu verstehen.
- Wenn die Advertising Meldung zu gross ist, wird nichts gesendet.
- Um einen neuen Wert zu senden /übernehmen muss nach der Aktualisierung der globalen Variable noch BLE.Advertising aufgerufen werden.
- XIAO-NRF52840 kann keine GATT Daten mit 256Byte senden. Bei mir wurden nur 247 Byte übertragen. Daher habe ich die Meldungsgrösse auf 240Byte beschränkt. Somit konnte ich die BSS implementation mit Data splitting testen.
Lession learned
- Ich dachte wenn ich diesen Standard implementiere ist es einfacher den Peripheral zu programmieren. Die Specification gab mehr zu tun als erwartet.
- Die Seeed Studio XIAO Devices sind sehr heikel mit den USB im Fehlerfall. jeder muss anders behandelt werden.
Links
Release notes
- V1.0-0 BLE-BSS build 23.034 20224
Questions
- Hier sind Fragen aufgeführt, für die ich noch eine Antwort suche.
- Q-001: Warum muss central = BLE.central(); die Variable central verwendet werden damit das Connect richtig geht.?
- Q-002: Warum hat der XIAO-NRF52840 Gatt eine max. Writegrösse von 247 Byte?
Hier ended die Seite