This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revision | |||
| en:iot-open:practical:hardware:sut:stm32:iot_6 [2024/05/03 21:04] – ktokarz | en:iot-open:practical:hardware:sut:stm32:iot_6 [2024/05/04 11:10] (current) – ktokarz | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | ====== STM_IoT_6: BLE Communication with characteristics ===== | ||
| + | This scenario presents how to create the Bluetooth Low Energy server device and corresponding client device. The server can be the sensor device which responds to the client with the results of the measurements. This can also be the output device, which we can control writing the data to. The client connects to a server and reads the data. This scenario presents the use of the concept of services and characteristics. | ||
| + | ===== Prerequisites ===== | ||
| + | It is necessary to understand the principles of the Bluetooth Low Energy protocol with concepts of services, characteristics and descriptors. We will use in this scenario the knowledge of the advertising process so making the [[en: | ||
| + | |||
| + | ===== Suggested Readings and Knowledge Resources ===== | ||
| + | * [[en: | ||
| + | * [[en: | ||
| + | * [[en: | ||
| + | |||
| + | ===== Hands-on Lab Scenario ===== | ||
| + | This scenario is intended to be implemented using two BLE laboratory nodes. One of them is a server, while the second is a client. Here we will present the simplest implementation to be extended in further scenarios. | ||
| + | < | ||
| + | The Nucleo STM32WB55 development boards which are used in our STM laboratory don't have the BLE firmware flashed by default. If you would like to test this scenario on your own board please refer to the STM documentation on the flashing process ((https:// | ||
| + | </ | ||
| + | |||
| + | ==== Task to be implemented ==== | ||
| + | **Task 1.** Implement a program that operates as the BLE server which advertises itself and allows us to connect to. | ||
| + | \\ | ||
| + | **Task 2.** Implement a client device, to read the exemplary data from a server. | ||
| + | |||
| + | ==== Start ==== | ||
| + | You can use the beacon and simple client programs from [[en: | ||
| + | |||
| + | ==== Steps ==== | ||
| + | We will present the lab in a few steps. We begin with a simple program which advertises the device with a single service containing a single characteristic. It allows us to establish a connection with a client and, if successfully connected, responds with simple text data. The program returns automatically to the advertisement procedure after disconnecting. | ||
| + | |||
| + | === Step 1 === | ||
| + | Let's begin with a simple program which advertises a single service. The code should start with including Arduino and BLE libraries. | ||
| + | <code c> | ||
| + | #include < | ||
| + | #include < | ||
| + | </ | ||
| + | We need variables for the BLE device class object and data transport class object. | ||
| + | < | ||
| + | HCISharedMemTransportClass HCISharedMemTransport; | ||
| + | BLELocalDevice BLEObj(& | ||
| + | BLELocalDevice& | ||
| + | </ | ||
| + | |||
| + | Any service or characteristic is identified with a unique UUID. If you use the standard service, like eg. Health Thermometer or Binary Sensor Service you can use a 16-bit UUID defined by Bluetooth SIG in the document ((https:// | ||
| + | < | ||
| + | Example of the standard UUIDs for service and characteristic: | ||
| + | Health Thermometer Service - 0x1809 | ||
| + | Temperature Measurement Characteristic - 0x2A1C | ||
| + | </ | ||
| + | While none of the standard UUIDs fits your device you need to define your own UUID. It must be 128-bit long and can be randomly generated with the tool available on the website https:// | ||
| + | The service UUID and characteristic UUID must differ. Although they can be completely different in many implementations the UUIDs of characteristics grouped under one service differ on one digit. | ||
| + | <code c> | ||
| + | #define SERVICE_UUID | ||
| + | #define CHARACTERISTIC_UUID " | ||
| + | </ | ||
| + | We create the service and characteristic objects with defined UUIDs | ||
| + | <code c> | ||
| + | // BLE Service - custam 128-bit UUID | ||
| + | BLEService pService(SERVICE_UUID); | ||
| + | |||
| + | // BLE Characteristic - custom 128-bit UUID, read and writable by central | ||
| + | BLECharacteristic pCharacteristic(CHARACTERISTIC_UUID, | ||
| + | </ | ||
| + | Data for reading or writing is organised using characteristics. In this example, we create one characteristic with reading and writing enabled, and the initial data as the "SUT STM" text. All is done in the setup() function. | ||
| + | The function creates and initialises the BLE device instance named "SUT STM BLE". It creates a service with the characteristic, | ||
| + | |||
| + | <code c> | ||
| + | void setup() { | ||
| + | | ||
| + | // begin initialization | ||
| + | BLE.begin(); | ||
| + | |||
| + | // set advertised local name and service UUID: | ||
| + | BLE.setLocalName(" | ||
| + | BLE.setAdvertisedService(pService); | ||
| + | |||
| + | // add the characteristic to the service | ||
| + | pService.addCharacteristic(pCharacteristic); | ||
| + | |||
| + | // add service | ||
| + | BLE.addService(pService); | ||
| + | |||
| + | // set the initial value for the characeristic: | ||
| + | pCharacteristic.writeValue(" | ||
| + | |||
| + | // start advertising | ||
| + | BLE.advertise(); | ||
| + | } | ||
| + | </ | ||
| + | < | ||
| + | Although it is possible to have many services in one device, due to the limited size of the advertisement packet, not all service UUIDs can be broadcast. Even more, service UUID does not have to appear in the advertising frame, but its presence makes it possible to scan nearby devices by their functionality, | ||
| + | </ | ||
| + | |||
| + | In the loop function, we need to call a function waiting for the connection of the client (central device). | ||
| + | <code c> | ||
| + | void loop() { | ||
| + | // listen for BLE central device to connect: | ||
| + | BLE.central(); | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | === Step 2 === | ||
| + | In this step, we will analyse the behaviour of the client. The client software is much more complex than the server. It is because the server, called also the central device, in many circumstances is a more powerful device than the peripheral. Some parts of the software are implemented as callback functions because they handle reactions on the data coming asynchronously from the server. The diagram presents the algorithm of the client and data coming from the server. | ||
| + | {{ en: | ||
| + | |||
| + | === Step 3 === | ||
| + | While we have analysed the client' | ||
| + | Add the libraries to the platformio.ini. | ||
| + | <code c> | ||
| + | lib_deps = | ||
| + | stm32duino/ | ||
| + | arduino-libraries/ | ||
| + | </ | ||
| + | And import libraries into the cpp file. | ||
| + | <code c> | ||
| + | #include " | ||
| + | #include " | ||
| + | #include " | ||
| + | </ | ||
| + | Next, we define the UUIDs for remote service and a characteristic. Notice they must match the ones defined in the server. | ||
| + | <code c> | ||
| + | // The remote service we wish to connect to. | ||
| + | #define REMOTE_SERVICE_UUID " | ||
| + | // The characteristic of the remote service we are interested in. | ||
| + | #define REMOTE_CHARACTERISTIC_UUID " | ||
| + | </ | ||
| + | < | ||
| + | We intentionally specified here the same UUIDs as in ESP32 BLE scenarios. If both platforms are physically available in the same laboratory, you can try to connect different hardware platforms with BLE. | ||
| + | </ | ||
| + | Some global class objects and variables will be needed for our software. | ||
| + | <code c> | ||
| + | // Variables | ||
| + | HCISharedMemTransportClass HCISharedMemTransport; | ||
| + | BLELocalDevice BLEObj(& | ||
| + | BLELocalDevice& | ||
| + | BLECharacteristic remoteCharacteristic; | ||
| + | </ | ||
| + | We need to add and configure the LCD to be able to observe the results. | ||
| + | <code c> | ||
| + | // LCD class (Constructor uses STM port numbering) | ||
| + | const int rs = PC5, en = PB11, d4 = PB12, d5 = PB13, d6 = PB14, d7 = PB15; | ||
| + | LiquidCrystal lcd(rs, en, d4, d5, d6, d7); | ||
| + | </ | ||
| + | |||
| + | In the setup() function we initialise LCD and Bluetooth and start scanning for the device having the service UUID we want to connect. | ||
| + | |||
| + | <code c> | ||
| + | void setup() { | ||
| + | // initialise LCD | ||
| + | lcd.begin(16, | ||
| + | lcd.clear(); | ||
| + | | ||
| + | // initialise the BLE hardware | ||
| + | BLE.begin(); | ||
| + | |||
| + | // start scanning for peripherals | ||
| + | int ret = 1; | ||
| + | do | ||
| + | { | ||
| + | ret = BLE.scanForUuid(REMOTE_SERVICE_UUID); | ||
| + | if (ret == 0) | ||
| + | { | ||
| + | BLE.end(); | ||
| + | BLE.begin(); | ||
| + | } | ||
| + | } while(ret == 0); | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | In a loop() function we check if a peripheral with UUID we are interested in was discovered. If so we check its name. If the remote device' | ||
| + | |||
| + | <code c> | ||
| + | void loop() { | ||
| + | // check if a peripheral has been discovered | ||
| + | BLEDevice peripheral = BLE.available(); | ||
| + | |||
| + | if (peripheral) { | ||
| + | if (peripheral.localName() == "SUT STM BLE") { | ||
| + | |||
| + | // display remote name | ||
| + | lcd.setCursor(0, | ||
| + | lcd.print(peripheral.localName()); | ||
| + | |||
| + | // stop scanning | ||
| + | int ret = 1; | ||
| + | do | ||
| + | { | ||
| + | ret = BLE.stopScan(); | ||
| + | if (ret == 0) | ||
| + | { | ||
| + | BLE.end(); | ||
| + | BLE.begin(); | ||
| + | } | ||
| + | } while(ret == 0); | ||
| + | |||
| + | // display the characteristic value | ||
| + | display_characteristic(peripheral); | ||
| + | } | ||
| + | |||
| + | // peripheral disconnected, | ||
| + | int ret = 1; | ||
| + | do | ||
| + | { | ||
| + | ret = BLE.scanForUuid(REMOTE_SERVICE_UUID); | ||
| + | if (ret == 0) | ||
| + | { | ||
| + | BLE.end(); | ||
| + | BLE.begin(); | ||
| + | } | ||
| + | } while(ret == 0); | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | The function reads the characteristic value in some steps: | ||
| + | - Connects to the remote device. | ||
| + | - Discovers attributes of the remote service. | ||
| + | - Retrieves the characteristic with specified UUID. | ||
| + | - Checks if the remote device is properly connected. | ||
| + | - Checks if the remote characteristic can be read. | ||
| + | - Reads the text and displays it on LCD. | ||
| + | - Disconnects from the remote server. | ||
| + | |||
| + | <code c> | ||
| + | void display_characteristic(BLEDevice peripheral) { | ||
| + | | ||
| + | // connect to the peripheral | ||
| + | if (peripheral.connect()) { | ||
| + | | ||
| + | // discover peripheral attributes | ||
| + | if (peripheral.discoverAttributes()) { | ||
| + | | ||
| + | // retrieve the remote characteristic to read | ||
| + | remoteCharacteristic = peripheral.characteristic(REMOTE_CHARACTERISTIC_UUID); | ||
| + | if (remoteCharacteristic) { | ||
| + | |||
| + | // if the peripheral is connected display the value | ||
| + | if (peripheral.connected()) { | ||
| + | |||
| + | // check if the characteristic can be read | ||
| + | if (remoteCharacteristic.canRead()){ | ||
| + | char char_text[16]; | ||
| + | remoteCharacteristic.readValue(char_text, | ||
| + | int i=remoteCharacteristic.valueLength(); | ||
| + | lcd.setCursor(0, | ||
| + | lcd.write(char_text, | ||
| + | } // if (remoteCharacteristic.canRead()) | ||
| + | } // if (peripheral.connected()) | ||
| + | } // if (remoteCharacteristic) | ||
| + | peripheral.disconnect(); | ||
| + | } // if (peripheral.discoverAttributes()) | ||
| + | } // if (peripheral.connect()) | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| + | ==== Result validation ==== | ||
| + | You should be able to read the name of the remote device in the first line of the LCD. After establishing the connection, the characteristic value should appear on the display' | ||
| + | ===== FAQ ===== | ||
| + | |||
| + | **What types of data can be sent with the characteristics? | ||
| + | \\ | ||
| + | **What if I need to read or write more data than a single text?**: It is possible to implement many services in one device and add many characteristics to one service. Theoretically, | ||
| + | \\ | ||
| + | **What is the maximum length of the data in the characteristics? | ||
| + | \\ | ||
| + | **Can I send data between SoCs coming from different vendors?**: That's what the specifications of the network protocols are defined for. If devices are in their transmission range (i.e. in one physical laboratory site) you can implement a BLE server on one type of SoC and a BLE client on another. | ||
| + | \\ | ||
| + | |||
| + | <WRAP noprint> | ||
| + | ===== Project information ===== | ||
| + | {{: | ||
| + | This Intellectual Output was implemented under the Erasmus+ KA2.\\ | ||
| + | Project IOT-OPEN.EU Reloaded – Education-based strengthening of the European universities, | ||
| + | Project number: 2022-1-PL01-KA220-HED-000085090. | ||
| + | |||
| + | **__Erasmus+ Disclaimer__**\\ | ||
| + | This project has been funded with support from the European Commission. \\ | ||
| + | This publication reflects the views of only the author, and the Commission cannot be held responsible for any use that may be made of the information contained therein. | ||
| + | |||
| + | **__Copyright Notice__**\\ | ||
| + | This content was created by the IOT-OPEN.EU Reloaded consortium, 2022, | ||
| + | The content is Copyrighted and distributed under CC BY-NC [[https:// | ||
| + | <figure label> | ||
| + | {{: | ||
| + | </ | ||
| + | </ | ||