I have just built my first bluetooth low energy application (BLE) for a client and there are a few gotchas I would like to go over. As well as explain what exactly is Bluetooth Low Energy and how it is different from Bluetooth classic. Furthermore, discuss how you can program it on an ESP32 and in React Native for your next application. Then lastly go over some optimization for the data you are sending back and forth via BLE communication to allow for the sending the maximum amount of data possible.
Bluetooth Classic is the original form of bluetooth. Its ideal application is when an application needs to stream a large amount of data from one device to another. Such as a pair of headphones streaming music from a smartphone, or printing a file from a mobile phone to a Bluetooth enabled printer.
When a device is using Bluetooth Classic, it can only send data via point-to-point communication. Meaning it can only talk to one device at a time while in a data exchange. Its what results in a higher data transfer speed overall.
With a max transfer size of 512 bytes per transaction. BLE is definitely not made to handle big data transfers. But more ideal for connected IoT devices such as sensors or smart toggles.
In addition to data transfer via point-to-point communication like Bluetooth Classic, BLE also offers connecting via mesh networking. Which will allow a bluetooth application to pass on messages from one device to another until it reaches the correct destination.
BLE also offers data communication via broadcast. In which a bluetooth device will announce the data it holds to any device that will listen.
In the Arduino IDE, there is currently only support for point-to-point and broadcast communication modes. Mesh networking is not on any kind of roadmap for implementation but can be implemented via the ESP-IDF toolchain if needed for the ESP32 or you can also look at this example here.
A profile is the top level of the hierarchy in the bluetooth service stack. It can hold multiple services. An then a service can consist of multiple characteristics.
A service is a collection of data and associated behaviors to accomplish a particular goal. really a grouper for the characteristics that hold and send the actual data in the application.
I wondered why someone might need multiple services in an application, but according to this stack overflow answer you can have private services that are private and only visible on specific devices. Or grouping related characteristics together such as device information, battery health and heart data.
In this article, I am going to assume that you have use the Arduino IDE a little bit and have setup your ESP32 to work with the Arduino IDE to work correctly.
Then we will have to create an instance of "BLEServer", as well as define a string for device name and service UUID at top of our file.
The device name is what shows up in the bluetooth menu of other devices when they try to connect to our ESP32.
The service UUID is what wholes our characteristics for our application and we can also use to connect to the device without knowing the device name. Due to memory restrictions on the ESP32 we can only have one service running.
You can generate UUIDs using this tool here: https://www.uuidgenerator.net/ and we will be using it to generate IDs for our service and characteristics.
After we have defined our strings. we will move into the setup function of our code to initialize our BLEServer called "pServer" using the device name and service UUID we choose.
We can see the device in the list of bluetooth devices in the BLE Scanner app
and after connecting to our ESP32 we can see the service UUID we define in the source code (ab49b033-1163-48db-931c-9c2a3002ee1d)
Cool now that we can see our service is working correctly, lets setup some characteristics to send data back and forth between our ESP32 and the React native app we are going to build.
Using the UUID tool I mention before ( https://www.uuidgenerator.net) lets generate a UUID for our first characteristic and putt it at the top of our file. As well as an new variable for BLECharacteristic
Then for initializing a characteristic you need to define a callback class that will provide actions when your characteristic is used. The one I am going to show below is empty but you can create additional functionality if need for on value read or write.
1
2
3
4
class MyCallbacks: public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
}
};
I created a helper function to create characteristics more compactly and it is useful if you have a few characteristics that have the same requirements. It looks like the following:
For handling the Bluetooth communication we will use dotintent/react-native-ble-plx. But first this might be your first React Native project, so lets cover all the bases and show you how to configure react native on your computer.
I like using Android to develop applications for react native because it can be easier to generate builds for testing and I have a few android phones just laying around my apartment. So you can follow this guide provided by the react native wiki and we will all be on the same page: https://reactnative.dev/docs/environment-setup
Then you will need to put your android phone into developer mode. Usually, by going to the "about phone" section in your settings menu and clicking on the "build number" until your developer mode setting enables.
Under "Developer options" I would enable the following settings:
"Stay awake"
"USB debugging"
Now you can plug your android phone into your computer and start coding your first bluetooth application.
It will use the name you have provided and start generating a folder for you to use. I named my 'BLETest' so just 'cd' into that folder after it is done generating.
Run the following command so we can use expo to install our dependencies
1
npm i -g expo
We also have to go to our package.json and change the expo version to be less then expo 49 because some dependency conflicts occur due to the bluetooth package being installed.
Then run the following command to remove the old node modules installed when creating the package and install the new versions:
1
rm -rf node_modules && npm i
Now we can start writing some code to handle permissions for bluetooth before we install our bluetooth package. To the best of my knowledge this is not necessary for IOS, but we only need to handle the permissions for android. Luckily, I have found a react hook that can handle this for us. By visiting this gist by ivanstnsk on Github and copying it into our codebase and updating the PERMISSIONS_REQUEST to the following:
We can now request the correct permissions for bluetooth BLE on our android phone. Additionally, you can look at all the permissions you can request here at the following link if you need to request additional permissions for your application.
Then we can use it in the body of our App component like so:
This will skip the permission check on IOS and only check the permissions on android, as well we can wait for the permissions to become available and react to the change.
Now we can move on and we will install the bluetooth module with the following command:
1
expo install react-native-ble-plx
After which we should have the module installed to develop our Bluetooth application.
Going into our "App.tsx" file we can start writing our bluetooth code. By declaring an instance of our BleManager like this
1
2
3
import { BleManager } from 'react-native-ble-plx';
const bleManager = new BleManager();
We then can grab the strings of our Device Name and service UUID from the Arduino code and put them as strings in our React Native code:
Then we want to create some status state variables to track connections of the bluetooth devices, so that we know if a device has successfully connected or when we have lost connection:
Then in our application we can create a function for searching for our esp32 device. Then we can call this function when we know the device has the permissions for Bluetooth.
Then we can create a function for handling the connection to our ESP32 once our phone has found the device, and create the supporting state variables to hold the data once we have made a connection
Okay, Like all the other applications you can find on the internet we have created our basic BLE application. Lets go over some additional optimizations and use cases to increase the performance of our application using the following bulleted list:
Sending data to the BLE device from the mobile Application
Optimizing Connecting and reconnecting of devices
Searching for BLE Devices rather then a static device
Characteristics Optimization to allow the transmission of lots of data via BLE
Okay in addition to step count say there is some data on the phone that we want to send or sync with the ESP32. Maybe its heart rate or some other type of data. How we would we go about syncing that data with the ESP32.
Then add a new state variable for the heart rate and a use effect that will react to the change in value of the heart rate and send that off to the ESP32 device
We now should be able to send data to the ESP32 anytime the heart rate variable changes if a device is connected. So let's update the body of the app so that we can cause the heart rate variable to change and verify that the data is being received on the ESP32.
Afterwards, here is a video of testing the code on the devices:
It's a little hard to hold a mobile phone for recording as well as click on another one. But anyways Ta-da we are now sending data from the phone to the ESP32!
Moving on to optimizing the connection process for BLE. Often the problem I have when it comes to BLE from the examples I see online is that you can only connect to the ESP32 if the device has first booted up. I need to restart the ESP32 once the device has lost connection. Which is not always a practical solution. Luckily, I have found a way to make the ESP32 look for a device to connect once it has lost connection regardless if the device has just booted or not.
By adding service callbacks that will update a boolean with the state of the device's connection as in the following example.
So moving once again to searching for a BLE device rather then using a static service UUID and Device name. Because your application might require to connect to different devices or you have multiple of the same devices deployed into the field.
We will update our searchAndConnectToDevice function so that it no longer actually connects to a fixed device name rather it will add a listener that will scan the BLE devices its sees and keep track off them in a list. Using a ref for this said list will insure that the data persist between renders in our list but because of that we will manually have to update the screen with the changes in the list using an setInterval function.
As a further note; you can restrict the devices that appear in list to be only device with the correct service UUID devices by update the first array param to bleManager.startDeviceScan. Like the following:
And then once again Ta-da! We have a select menu for finding our bluetooth device by device name. Like all the other sections you can look at the code here:
Okay Our final and last section of this blog post. Finally, I have been writing this for weeks ha. But anyways let's make this app a little more useful. Lets turn this app into a Lighting app with two kinds of lights:
Lights that just turn on and off
Lights that can vary in brightness from 0-100 percent brightness.
In this application, I want to have four lights that just turn on and off. As well, four lights that vary in brightness. So that is eight pieces of data that will be communicated between the ESP32 and the React Native mobile application. However, characteristics that could be used to transmit each piece of these eight pieces of data would use too much memory on the ESP32. In my experience I have only been able to have four characteristics running at a time before the ESP32 runs out of memory and starts boot looping.
So we will have to compress the data down somehow... We can compress all this data down to two characteristics using the following methods.
For values that toggle on and off, we can compress those values into one integer value and run bitwise operations with bitwise mask to run operations such as toggling and checking if enabled.
for our example we can store all four of our lights in a four bit number like this:
'0000' - all lights are off
the mask would look as follows:
'1000' - light 1 mask
'0100' - light 2 mask
'0010' - light 3 mask
'0001' - light 4 mask
And to check if the light is enabled we can "(bitwise) AND (&)" the current state of all the lights with the mask (e.g. light 1):
(0000 & 1000)
Then compare that result with the mask to see if that light is enabled: (0000 & 1000) & 1000
For toggling a state for off to on, or on to off. We can use a bitwise exclusive or (^) between the current value and the bitwise mask to change the value around this is what it looks like in code:
Now we can transmit up to 20 bytes of toggles equaling 20bytes * 8bits = 160 toggles in this one characteristic. Which is quite a few lights to have in an apartment at once!
Now if we want control likes that vary in brightness, we can not pack all their values in to one integer value because the data is not that simple but we can compress them into an array and sending them back and forth using JSON.
It's actually quite simple to setup. We just install this ArduinoJSON library and we can unpack JSON data as the following in our ESP32 code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
std::string value = readValue(pAllLotCharacteristic, true);
JsonDocument doc;
DeserializationError error = deserializeJson(doc, value);
// Test if parsing succeeds.
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
return;
}
int v1 = doc[0];
int v2 = doc[1];
int v3 = doc[2];
int v4 = doc[3];
Serial.print("JSON Values: [");
Serial.print(v1);
Serial.print(",");
Serial.print(v2);
Serial.print(",");
Serial.print(v3);
Serial.print(",");
Serial.print(v4);
Serial.println("]");
And We can send it over the wire from the react Native code using the following:
Hello, I am Josh, I am a full-stack developer specializing in developing for Web and Mobile in React, React Native, Node.js, and Django. I used to work at Amazon in the advertising sales Performance department as a Frontend Engineer...
I have a degree from Purdue University in Computer Engineering with the use of my degree and passions I can offer support more than just Web and Mobile development. I can assist with any need related to hardware integration with internet-enabled devices or design needs in CAD and manufacturing.