# Me and the Devil BlueZ: Reading BLE sensors from C++

# Me and the Devil Bluez: Reading BLE sensors from C++

In our last article, I shared how to interact with Bluetooth Low Energy devices from a Raspberry Pi with Linux, using DBus and BlueZ. I did a step-by-step walkthrough on how to talk to a BLE device using a command line tool, so we had a clear picture of the sequence of operations that had to be performed to interact with the device. Then, I repeated the process but focused on the DBus messages that have to be exchanged to achieve that interaction.

Now, it is time to put that knowledge into practice and implement an application that connects to the RP2 BLE sensor that we created in our second article and reads the value of the… temperature. (Yep, we will switch to noise sometime soon. Please, bear with me.)

Ready to start? Let's get cracking!

## Setup

The application that we will be developing in this article is going to run on a Raspberry Pi 4B, our collecting station. You can use most other models, but I strongly recommend you connect it to your network using an ethernet cable and disable your WiFi. Otherwise, it might interfere with the Bluetooth communications.

I will do all my development using Visual Studio Code on my MacBook Pro and connect via SSH to the Raspberry Pi (RPi). The whole project will be held in the RPi, and I will compile it and run it there. You will need the Remote - SSH extension installed in Visual Studio Code for this to work, and the first time you connect to the RPi, it will take some time to set it up. If you use Emacs, TRAMP is available out of the box.

We also need some software installed on the RPi. At the very least, we will need `git` and `CMake`, because that is the build system that I will be using for the project. The C++ compiler (g++) is installed by default in Raspberry Pi OS, but you can install `Clang` if you prefer to use LLVM.

```sh
sudo apt-get install git git-flow cmake
```

In any case, we will need to install `sdbus-c++`. That is the library that allows us to interact with DBus using C++ bindings. There are several alternatives, but sdbus-c++ is properly maintained and has good documentation.

```sh
sudo apt-get install libsdbus-c++-{bin,dev,doc}
```

## Initial project

I am going to write this project from scratch, so I want to be sure that you and I start with the same set of files. I am going to begin with a trivial `main.cpp` file, and then I will create the seed for the build instructions that we will use to produce the executable throughout this episode.

### Initial main.cpp

Our initial `main.cpp` file is just going to print a message:

```cpp
#include 

int main(int argc, char *argv])
{
    std::cout << "Noise Collector BLE" << std::endl;

    return 0;
}
```

### Basic project

And now we should create a `CMakeLists.txt` file with the minimal build instructions for this project:

```cmake
cmake_minimum_required(VERSION 3.5)
project(NoiseCollectorBLE CXX)
add_executable(${PROJECT_NAME} main.cpp)
```

Before we move forward, we are going to check that it all works fine:

```sh
mkdir build
cmake -S . -B build
cmake --build build
./build/NoiseCollectorBLE
```

## Talk to DBus from C++

### Send the first message

Now that we have set the foundations of the project, we can send our first message to DBus. A good one to start with is the one we use to query if the Bluetooth radio is on or off.

1.  Let's start by adding the library to the project using CMake's `find_package` command:
    
    ```cmake
    find_package(sdbus-c++ REQUIRED)
    ```
2.  The library must be linked to our binary:
    
    ```cmake
    target_link_libraries(${PROJECT_NAME} PRIVATE SDBusCpp::sdbus-c++)
    ```
3.  And we enforce the usage of the C++17 standard because it is required by the library:
    
    ```cmake
    set(CMAKE_CXX_STANDARD 17)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    ```
4.  With the library in place, let's create the skeleton to implement our BLE sensor. We first create the `BleSensor.h` file:
    
    ```cpp
    #ifndef BLE_SENSOR_H
    #define BLE_SENSOR_H
    
    class BleSensor
    {
    };
    
    #endif //  BLE_SENSOR_H
    ```
5.  We add a constructor and a method that will take care of all the steps required to scan for and connect to the sensor:
    
    ```cpp
    public:
        BleSensor();
        void scanAndConnect();
    ```
6.  In order to talk to BlueZ, we should create a proxy object. A proxy is a local object that allows us to interact with the remote DBus object. Creating the proxy instance without passing a connection to it means that the proxy will create its own connection automatically, and it will be a system bus connection.
    
    ```cpp
    private:
        std::unique_ptr bluezProxy;
    ```
7.  And we need to include the library:
    
    ```cpp
    #include 
    ```
8.  Let's create a `BleSensor.cpp` file for the implementation and include the header file that we have just created:
    
    ```cpp
    #include "BleSensor.h"
    ```
9.  That proxy requires the name of the service and a path to the instance that we want to talk to, so let's define both as constants inside of the constructor:
    
    ```cpp
    BleSensor::BleSensor()
    {
        const std::string SERVICE_BLUEZ { "org.bluez" };
        const std::string OBJECT_PATH { "/org/bluez/hci0" };
    
        bluezProxy = sdbus::createProxy(SERVICE_BLUEZ, OBJECT_PATH);
    }
    ```
10. Let's add the first step to our scanAndConnect method using a private function that we declare in the header:
    
    ```cpp
    bool getBluetoothStatus();
    ```
11. Following this, we write the implementation, where we use the proxy that we created before to send a message. We define a message to a method on an interface using the required parameters, which we learned using the introspectable interface and the DBus traces. The result is a *variant* that can be casted to the proper type using the overloaded `operator()`:
    
    ```cpp
    bool BleSensor::getBluetoothStatus()
    {
        const std::string METHOD_GET { "Get" };
        const std::string INTERFACE_PROPERTIES { "org.freedesktop.DBus.Properties" };
        const std::string INTERFACE_ADAPTER { "org.bluez.Adapter1" };
        const std::string PROPERTY_POWERED { "Powered" };
        sdbus::Variant variant;
    
        // Invoke a method that gets a property as a variant
        bluezProxy->callMethod(METHOD_GET)
            .onInterface(INTERFACE_PROPERTIES)
            .withArguments(INTERFACE_ADAPTER, PROPERTY_POWERED)
            .storeResultsTo(variant);
    
        return (bool)variant;
    }
    ```
12. We use this private method from our public one:
    
    ```cpp
    void BleSensor::scanAndConnect()
    {
        try
        {
            // Enable Bluetooth if not yet enabled
            if (getBluetoothStatus())
            {
                std::cout << "Bluetooth powered ON\n";
            } else
            {
                std::cout << "Powering bluetooth ON\n";
            }
        }
        catch(sdbus::Error& error)
        {
            std::cerr << "ERR: on scanAndConnect(): " << error.getName() << " with message " << error.getMessage() << std::endl;
        }
    }
    ```
13. And include the iostream header:
    
    ```cpp
    #include 
    ```
14. We need to add the source files to the project:
    
    ```cmake
    file(GLOB SOURCES "*.cpp")
    add_executable(${PROJECT_NAME} ${SOURCES})
    ```
15. Finally, we import the header that we have defined in the `main.cpp`, create an instance of the object, and invoke the method:
    
    ```cpp
    #include "BleSensor.h"
    
    int main(int argc, char *argv[])
    {
        std::cout << "Noise Collector BLE" << std::endl;
        BleSensor bleSensor;
        bleSensor.scanAndConnect();
    ```
16. We compile it with CMake and run it.

### Send a second message

Our first message queried the status of a property. We can also change things using messages, like the status of the Bluetooth radio:

1.  We declare a second private method in the header:
    
    ```cpp
    void setBluetoothStatus(bool enable);
    ```
2.  And we also add it to the implementation file –in this case, only the message without the constants:
    
    ```cpp
    void BleSensor::setBluetoothStatus(bool enable)
    {
        // Invoke a method that sets a property as a variant
        bluezProxy->callMethod(METHOD_SET)
            .onInterface(INTERFACE_PROPERTIES)
            .withArguments(INTERFACE_ADAPTER, PROPERTY_POWERED, sdbus::Variant(enable))
            // .dontExpectReply();
            .storeResultsTo();
    }
    ```
3.  As you can see, the calls to create and send the message use most of the same constants. The only new one is the `METHOD_SET`, used instead of `METHOD_GET`. We set that one inside of the method:
    
    ```cpp
    const std::string METHOD_SET { "Set" };
    ```
4.  And we make the other three static constants of the class. Prior to C++17, we would have had to declare them in the header and initialize them in the implementation, but since then, we can use `inline` to initialize them in place. That helps readability:
    
    ```cpp
    static const std::string INTERFACE_ADAPTER { "org.bluez.Adapter1" };
    static const std::string PROPERTY_POWERED { "Powered" };
    static const std::string INTERFACE_PROPERTIES { "org.freedesktop.DBus.Properties" };
    ```
5.  With the private method complete, we use it from the public one:
    
    ```cpp
    if (getBluetoothStatus())
    {
        std::cout << "Bluetooth powered ON\n";
    } else
    {
        std::cout << "Powering bluetooth ON\n";
        setBluetoothStatus(true);
    }
    ```
6.  The second message is ready and we can build and run the program. You can verify its effects using `bluetoothctl`.

## Deal with signals

The next thing we would like to do is to enable scanning for BLE devices, find the sensor that we care about, connect to it, and disable scanning. Obviously, when we start scanning, we don't get to know the available BLE devices right away. Some reply almost instantaneously, and some will answer a little later. DBus will send signals, asynchronous messages that are pushed to a given object, that we will listen to.

### Use messages that have a delayed response

1.  We are going to use a private method to enable and disable the scanning. The first thing to do is to have it declared in our header:
    
    ```cpp
    void enableScanning(bool enable);
    ```
2.  In the implementation file, the method is going to be similar to the ones we have defined before. Here, we don't have to worry about the reply because we have to wait for our sensor to show up:
    
    ```cpp
    void BleSensor::enableScanning(bool enable)
    {
        const std::string METHOD_START_DISCOVERY { "StartDiscovery" };
        const std::string METHOD_STOP_DISCOVERY { "StopDiscovery" };
    
        std::cout << (enable?"Start":"Stop") << " scanning\n";
        bluezProxy->callMethod(enable?METHOD_START_DISCOVERY:METHOD_STOP_DISCOVERY)
            .onInterface(INTERFACE_ADAPTER)
            .dontExpectReply();
    }
    ```
3.  We can then use that method in our public one to enable and disable scanning:
    
    ```cpp
    enableScanning(true);
    // Wait to be connected to the sensor
    enableScanning(false);
    ```
4.  We need to wait for the devices to answer, so let's add some delay between both calls:
    
    ```cpp
    // Wait to be connected to the sensor
    std::this_thread::sleep_for(std::chrono::seconds(10))
    ```
5.  And we add the headers for this new code:
    
    ```cpp
    #include 
    #include 
    ```
6.  If we build and run, we will see no errors but no results of our scanning, either. Yet.

### Subscribe to signals

In order to get the data of the devices that scanning for devices produces, we need to be listening to the signals sent that are broadcasted through the bus.

1.  We need to interact with a different DBus object so we need another proxy. Let's declare it in the header:
    
    ```cpp
    std::unique_ptr rootProxy;
    ```
2.  And instantiate it in the constructor:
    
    ```cpp
    rootProxy = sdbus::createProxy(SERVICE_BLUEZ, "/");
    ```
3.  Next, we define the private method that will take care of the subscription:
    
    ```cpp
    void subscribeToInterfacesAdded();
    ```
4.  The implementation is simple: We provide a closure to be called on a different thread every time we receive a signal that matches our parameters:
    
    ```cpp
    void BleSensor::subscribeToInterfacesAdded()
    {
        const std::string INTERFACE_OBJ_MGR { "org.freedesktop.DBus.ObjectManager" };
        const std::string MEMBER_IFACE_ADDED { "InterfacesAdded" };
    
        // Let's subscribe for the interfaces added signals (AddMatch)
        rootProxy->uponSignal(MEMBER_IFACE_ADDED).onInterface(INTERFACE_OBJ_MGR).call(interfaceAddedCallback);
        rootProxy->finishRegistration();
    }
    ```
5.  The closure has to take as arguments the data that comes with a signal: a string for the path that points to an object in DBus and a dictionary of key/values, where the keys are strings and the values are dictionaries of strings and values:
    
    ```cpp
    auto interfaceAddedCallback = [this
    {
    };
    ```
6.  We will be doing more with the data later, but right now, displaying the thread id, the object path, and the device name, if it exists, will suffice. We use a regular expression to restrict our attention to the Bluetooth devices:
    
    ```cpp
    const std::regex DEVICE_INSTANCE_RE{"^/org/bluez/hci0-9]/dev(_[0-9A-F]{2}){6}$"};
    std::smatch match;
    std::cout << "(TID: " << std::this_thread::get_id() << ") ";
    if (std::regex_match(path, match, DEVICE_INSTANCE_RE)) {
        std::cout << "Device iface ";
    
        if (dictionary["org.bluez.Device1"].count("Name") == 1)
        {
            auto name = (std::string)(dictionary["org.bluez.Device1"].at("Name"));
            std::cout << name << " @ " << path << std::endl;
        } else
        {
            std::cout << " @ " << path << std::endl;
        }
    } else {
        std::cout << "*** UNEXPECTED SIGNAL ***";
    }
    ```
7.  And we add the header for regular expressions:
    
    ```cpp
    #include 
    ```
8.  We use the private method **before** we start scanning:
    
    ```cpp
    subscribeToInterfacesAdded();
    ```
9.  And we print the thread id in that same method:
    
    ```cpp
    std::cout << "(TID: " << std::this_thread::get_id() << ") ";
    ```
10. If you build and run this code, it should display information about the BLE devices that you have around you. You can show it to your friends and tell them that you are searching for spy microphones.

## Communicate with the sensor

Well, that looks like progress to me, but we are still missing the most important features: connecting to the BLE device and reading values from it.

We should connect to the device, if we find it, from the closure that we use in `subscribeToInterfacesAdded()`, and then, we should stop scanning. However, that closure and the method `scanAndConnect()` are running in different threads concurrently. When the closure connects to the device, it should *inform* the main thread, so it stops scanning. We are going to use a mutex to protect concurrent access to the data that is shared between those two threads and a conditional variable to let the other thread know when it has changed.

### Connect to the BLE device

1.  First, we are going to declare a private method to connect to a device by name:
    
    ```cpp
    void connectToDevice(sdbus::ObjectPath path);
    ```
2.  We will obtain that object path from the signals that tell us about the devices discovered while scanning. We will compare the name in the dictionary of properties of the signal with the name of the sensor that we are looking for. We'll receive that name through the constructor, so we need to change its declaration:
    
    ```cpp
    BleSensor(const std::string &sensor_name);
    ```
3.  And declare a field that will be used to hold the value:
    
    ```cpp
    const std::string deviceName;
    ```
4.  If we find the device, we will create a proxy to the object that represents it:
    
    ```cpp
    std::unique_ptr deviceProxy;
    ```
5.  We move to the implementation and start by adapting the constructor to initialize the new values using the preamble:
    
    ```cpp
    BleSensor::BleSensor(const std::string &sensor_name)
        : deviceProxy{nullptr}, deviceName{sensor_name}
    ```
6.  We then create the method:
    
    ```cpp
    void BleSensor::connectToDevice(sdbus::ObjectPath path)
    {
    }
    ```
7.  We create a proxy for the device that we have selected using the name:
    
    ```cpp
    deviceProxy = sdbus::createProxy(SERVICE_BLUEZ, path);
    ```
8.  And move the declaration of the service constant, which is now used in two places, to the header:
    
    ```cpp
    inline static const std::string SERVICE_BLUEZ{"org.bluez"};
    ```
9.  And send a message to connect to it:
    
    ```cpp
    deviceProxy->callMethodAsync(METHOD_CONNECT).onInterface(INTERFACE_DEVICE).uponReplyInvoke(connectionCallback);
    std::cout << "Connection method started" << std::endl;
    ```
10. We define the constants that we are using:
    
    ```cpp
    const std::string INTERFACE_DEVICE{"org.bluez.Device1"};
    const std::string METHOD_CONNECT{"Connect"};
    ```
11. And the closure that will be invoked. The use of `this` in the capture specification allows access to the object instance. The code in the closure will be added below.
    
    ```cpp
    auto connectionCallback = [this
    {
    };
    ```
12. The private method can now be used to connect from the method `BleSensor::subscribeToInterfacesAdded()`. We were already extracting the name of the device, so now we use it to connect to it:
    
    ```cpp
    if (name == deviceName)
    {
        std::cout << "Connecting to " << name << std::endl;
        connectToDevice(path);
    }
    ```
13. We would like to stop scanning once we are connected to the device. This happens in two different threads, so we are going to use the producer-consumer concurrency design pattern to achieve the expected behavior. We define a few new fields –one for the mutex, one for the conditional variable, and one for a boolean flag:
    
    ```cpp
    std::mutex mtx;
    std::condition_variable cv;
    bool connected;
    ```
14. And we include the required headers:
    
    ```cpp
    #include 
    ```
15. They are initialized in the constructor preamble:
    
    ```cpp
    BleSensor::BleSensor(const std::string &sensor_name)
        : deviceProxy{nullptr}, deviceName{sensor_name},
          cv{}, mtx{}, connected{false}
    ```
16. We can then use these new fields in the `BleSensor::scanAndConnect()` method. First, we get a unique lock on the mutex before subscribing to notifications:
    
    ```cpp
    std::unique_lock lock(mtx);
    ```
17. Then, between the start and the stop of the scanning process, we wait for the conditional variable to be signaled. This is a more robust and reliable implementation than using the delay:
    
    ```cpp
    enableScanning(true);
    // Wait to be connected to the sensor
    cv.wait(lock, this
            { return connected; });
    enableScanning(false);
    ```
18. In the `connectionCallback`, we first deal with errors, in case they happen:
    
    ```cpp
    if (error != nullptr)
    {
        std::cerr << "Got connection error "
                  << error->getName() << " with message "
                  << error->getMessage() << std::endl;
        return;
    }
    ```
19. Then, we get a lock on the same mutex, change the flag, release the lock, and signal the other thread through the connection variable:
    
    ```cpp
    std::unique_lock lock(mtx);
    std::cout << "Connected!!!" << std::endl;
    connected = true;
    lock.unlock();
    cv.notify_one();
    std::cout << "Finished connection method call" << std::endl;
    ```
20. Finally, we change the initialization of the BleSensor in the main file to pass the sensor name:
    
    ```cpp
    BleSensor bleSensor { "RP2-SENSOR" };
    ```
21. If we compile and run what we have so far, we should be able to connect to the sensor. But if the sensor isn't there, it will wait indefinitely. If you have problems connecting to your device and get "le-connection-abort-by-local," use an ethernet cable instead of WiFi and disable it with `sudo ip link set wlan0 down`.

### Read from the sensor

Now that we have a connection to the BLE device, we will receive signals about other interfaces added. These are going to be the services, characteristics, and descriptors. If we want to read data from a characteristic, we have to find it –using its UUID for example– and use DBus's "Read" method to get its value. We already have a closure that is invoked every time a signal is received because an interface is added, but in this closure, we verify that the object path corresponds to a device, instead of to a Bluetooth attribute.

1.  We want to match the object path against the structure of a BLE attribute, but we want to do that only when the device is already connected. So, we surround the existing regular expression match:
    
    ```cpp
    if (!connected)
    {
        // Current code with regex goes here.
    }
    else
    {
    }
    ```
2.  In the *else* part, we add a different match:
    
    ```cpp
    if (std::regex_match(path, match, DEVICE_ATTRS_RE))
    {
    }
    else
    {
        std::cout << "Not a characteristic" << std::endl;
    }
    ```
3.  That code requires the regular expression declared in the method:
    
    ```cpp
    const std::regex DEVICE_ATTRS_RE{"^/org/bluez/hci\\d/dev(_0-9A-F]{2}){6}/service\\d{4}/char\\d{4}"};
    ```
4.  If the path matches the expression, we check if it has the UUID of the characteristic that we want to read:
    
    ```cpp
    std::cout << "Characteristic " << path << std::endl;
    if ((dictionary.count("org.bluez.GattCharacteristic1") == 1) &&
        (dictionary["org.bluez.GattCharacteristic1"].count("UUID") == 1))
    {
        auto name = (std::string)(dictionary["org.bluez.GattCharacteristic1"].at("UUID"));
        if (name == "00002a1c-0000-1000-8000-00805f9b34fb")
        {
        }
    }
    ```
5.  When we find the desired characteristic, we need to create (yes, you guessed it) a proxy to send messages to it.
    
    ```cpp
    tempAttrProxy = sdbus::createProxy(SERVICE_BLUEZ, path);
    std::cout << "<<>> " << path << std::endl;
    ```
6.  That proxy is stored in a field that we haven't declared yet. Let's do so in the header file:
    
    ```cpp
    std::unique_ptr tempAttrProxy;
    ```
7.  And we do an explicit initialization in the constructor preamble:
    
    ```cpp
    BleSensor::BleSensor(const std::string &sensor_name)
        : deviceProxy{nullptr}, tempAttrProxy{nullptr},
          cv{}, mtx{}, connected{false}, deviceName{sensor_name}
    ```
8.  Everything is ready to read, so let's declare a public method to do the reading:
    
    ```cpp
    void getValue();
    ```
9.  And a private method to send the DBus messages:
    
    ```cpp
    void readTemperature();
    ```
10. We implement the public method, just using the private method:
    
    ```cpp
    void BleSensor::getValue()
    {
        readTemperature();
    }
    ```
11. And we do the implementation on the private method:
    
    ```cpp
    void BleSensor::readTemperature()
    {
        tempAttrProxy->callMethod(METHOD_READ)
            .onInterface(INTERFACE_CHAR)
            .withArguments(args)
            .storeResultsTo(result);
    }
    ```
12. We define the constants that we used:
    
    ```cpp
    const std::string INTERFACE_CHAR{"org.bluez.GattCharacteristic1"};
    const std::string METHOD_READ{"ReadValue"};
    ```
13. And the variable that will be used to qualify the query to have a zero offset as well as the one to store the response of the method:
    
    ```cpp
    std::map args{{{"offset", sdbus::Variant{std::uint16_t{0}}}}};
    std::vector result;
    ```
14. The temperature starts on the second byte of the result (offset 1) and ends on the fifth, which in this case is the last one of the array of bytes. We can extract it:
    
    ```cpp
    std::cout << "READ: ";
    for (auto value : result)
    {
        std::cout << +value << " ";
    }
    std::vector number(result.begin() + 1, result.end());
    ```
15. Those bytes in ieee11073 format have to be transformed into a regular float, and we use a private method for that:
    
    ```cpp
    float valueFromIeee11073(std::vector binary);
    ```
16. That method is implemented by reversing the transformation that we did on [the second article of this series:
    
    ```cpp
    float BleSensor::valueFromIeee11073(std::vector binary)
    {
        float value = static_cast(binary0]) + static_cast(binary[1]) * 256.f + static_cast(binary[2]) * 256.f * 256.f;
        float exponent;
        if (binary[3] > 127)
        {
            exponent = static_cast(binary[3]) - 256.f;
        }
        else
        {
            exponent = static_cast(binary[3]);
        }
        return value * pow(10, exponent);
    }
    ```
17. That implementation requires including the math declaration:
    
    ```cpp
    #include 
    ```
18. We use the transformation after reading the value:
    
    ```cpp
    std::cout << "\nTemp: " << valueFromIeee11073(number);
    std::cout << std::endl;
    ```
19. And we use the public method in the main function. We should use the producer-consumer pattern here again to know when the proxy to the temperature characteristic is ready, but I have cut corners again for this initial implementation using a couple of delays to ensure that everything works fine.
    
    ```cpp
    std::this_thread::sleep_for(std::chrono::seconds(5));
    bleSensor.getValue();
    std::this_thread::sleep_for(std::chrono::seconds(5));
    ```
20. In order for this to work, the thread header must be included:
    
    ```cpp
    #include 
    ```
21. We build and run to check that a value can be read.

### Disconnect from the BLE sensor

Finally, we should disconnect from this device to leave things as we found them. If we don't, re-running the program won't work because the sensor will still be connected and busy.

1.  We declare a public method in the header to handle disconnections:
    
    ```cpp
    void disconnect();
    ```
2.  And a private one to send the corresponding DBus message:
    
    ```cpp
    void disconnectFromDevice();
    ```
3.  In the implementation, the private method sends the required message and creates a closure that gets invoked when the device gets disconnected:
    
    ```cpp
    void BleSensor::disconnectFromDevice()
    {
        const std::string INTERFACE_DEVICE{"org.bluez.Device1"};
        const std::string METHOD_DISCONNECT{"Disconnect"};
    
        auto disconnectionCallback = [this
        {
        };
    
        {
            deviceProxy->callMethodAsync(METHOD_DISCONNECT).onInterface(INTERFACE_DEVICE).uponReplyInvoke(disconnectionCallback);
            std::cout << "Disconnection method started" << std::endl;
        }
    }
    ```
4.  And that closure has to change the connected flag using exclusive access:
    
    ```cpp
    if (error != nullptr)
    {
        std::cerr << "Got disconnection error " << error->getName() << " with message " << error->getMessage() << std::endl;
        return;
    }
    std::unique_lock lock(mtx);
    std::cout << "Disconnected!!!" << std::endl;
    connected = false;
    deviceProxy = nullptr;
    lock.unlock();
    std::cout << "Finished connection method call" << std::endl;
    ```
5.  The private method is used from the public method:
    
    ```cpp
    void BleSensor::disconnect()
    {
        std::cout << "Disconnecting from device" << std::endl;
        disconnectFromDevice();
    }
    ```
6.  And the public method is used from the main function:
    
    ```cpp
    bleSensor.disconnect();
    ```
7.  Build and run to see the final result.

## Recap and future work

In this article, I have used C++ to write an application that reads data from a Bluetooth Low Energy sensor. I have realized that writing C++ is **not** like riding a bike. Many things have changed since I wrote my last C++ code that went into production, but I hope I did a decent job at using it for this task.
," caused by a "Connection Failed to be Established (0x3e)," when attempting to connect to the Bluetooth sensor. It happened often but not always. In the beginning, I didn't know if it was my code to blame, the library, or what. After catching exceptions everywhere, printing every message, capturing Bluetooth traces with `btmon`, and not finding much (although I did learn a few new things from Unix & Linux StackExchange, Stack Overflow and the Raspberry Pi forums), I suddenly realized that the culprit was the Raspberry Pi WiFi/Bluetooth chip. The symptom was an unreliable Bluetooth connection, but my sensor and the RPi were very close to each other and without any relevant interference from the environment. The root cause was sharing the radio frequency (RF) in the same chip (Broadcom BCM43438) with a relatively small antenna. I switched from the RPi3A+ to an RPi4B with an ethernet cable and WiFi disabled and, all of a sudden, things started to work.

Even though the implementation wasn't too complex and the proof of concept was passed, the hardware issue raised some concerns. It would only get worse if I talked to several sensors instead of just one. And that is exactly what we will do in future episodes to collect the data from the sensor and send it to a MongoDB Cluster with time series. I could still use a USB Bluetooth dongle and ignore the internal hardware. But before I take that road, I would like to work on the MQTT alternative and make a better informed decision. And that will be our next episode.

Stay curious, hack your code, and see you next time!

  [1]: https://images.contentstack.io/v3/assets/blt39790b633ee0d5a7/bltdc6a10ecba495cf2/658195252f46f765e48223bf/yogendra-singh-BxHnbYyNfTg-unsplash.jpg