Tutorial
In this page you’ll find all the information you need to get up and running with SimpleBLE.
Getting started
Your first step towards using SimpleBLE is to download and install the library following the instructions in the usage page. Once you have installed the library, you need to understand a few basic concepts about how SimpleBLE is organized.
To access all classes provided by SimpleBLE, you can go the easy route and include everything with just one single header file:
#include <simpleble/SimpleBLE.h>
In SimpleBLE, everything revolves around two main object types: A SimpleBLE::Adapter
representing a physical Bluetooth adapter, and a SimpleBLE::Peripheral
representing the
Bluetooth device you are communicating with. If you wish to be more specific and
only consume what you need, you have the following list of headers available.
Take into account that each header file will automatically include the ones
underneath in the list.
#include <simpleble/Adapter.h>
#include <simpleble/Peripheral.h>
#include <simpleble/Service.h>
#include <simpleble/Characteristic.h>
#include <simpleble/Descriptor.h>
One you have the correct header files included, you have two functions that should
act as a starting point for every program. The first one is SimpleBLE::Adapter::bluetooth_enabled()
,
which will let you know if Bluetooth is enabled and permissions have been given
to the running app. The second one is SimpleBLE::Adapter::get_adapters()
, which
will provide a list of all available adapters that can be used. The following
code snippet shows how to use these functions:
#include <simpleble/SimpleBLE.h>
int main(int argc, char** argv) {
if (!SimpleBLE::Adapter::bluetooth_enabled()) {
std::cout << "Bluetooth is not enabled" << std::endl;
return 1;
}
auto adapters = SimpleBLE::Adapter::get_adapters();
if (adapters.empty()) {
std::cout << "No Bluetooth adapters found" << std::endl;
return 1;
}
// Use the first adapter
auto adapter = adapters[0];
// Do something with the adapter
std::cout << "Adapter identifier: " << adapter.identifier() << std::endl;
std::cout << "Adapter address: " << adapter.address() << std::endl;
return 0;
}
The above code will print the identifier and address of the first adapter found
using SimpleBLE::Adapter::identifier()
and SimpleBLE::Adapter::address()
, respectively.
If you have more than one adapter, you can use the identifier to select the
one you want to use. The identifier is a string that uniquely identifies the
adapter within the operating system. The address is a string that represents
the MAC address of the adapter.
Scanning for peripherals
Once you have a list of available adapters, you can start scanning for
peripherals. To do so, you need to create an SimpleBLE::Adapter
object
and call the SimpleBLE::Adapter::scan_for()
method. This method will
return a list of SimpleBLE::Peripheral
objects that are in range
of the adapter.
// Get a list of all available adapters
std::vector<SimpleBLE::Adapter> adapters = SimpleBLE::Adapter::get_adapters();
// Get the first adapter
SimpleBLE::Adapter adapter = adapters[0];
// Scan for peripherals for 5000 milliseconds
adapter.scan_for(5000);
// Get the list of peripherals found
std::vector<SimpleBLE::Peripheral> peripherals = adapter.scan_get_results();
// Print the identifier of each peripheral
for (auto peripheral : peripherals) {
std::cout << "Peripheral identifier: " << peripheral.identifier() << std::endl;
std::cout << "Peripheral address: " << peripheral.address() << std::endl;
}
The above code will print the identifier and address of each peripheral found
using SimpleBLE::Peripheral::identifier()
and SimpleBLE::Peripheral::address()
, respectively.
Additionally, you can use SimpleBLE::Adapter::scan_start()
and
SimpleBLE::Adapter::scan_stop()
to start and stop scanning asynchronously.
This is useful if you want to scan for peripherals in the background while
performing other tasks. For this use case you can supply callback functions
to receive notifications for different scan-related events by calling
SimpleBLE::Adapter::set_callback_on_scan_start()
, SimpleBLE::Adapter::set_callback_on_scan_stop()
,
SimpleBLE::Adapter::set_callback_on_scan_updated()
and SimpleBLE::Adapter::set_callback_on_scan_found()
.
The following code snippet shows how to use these functions:
// Set the callback to be called when the scan starts
adapter.set_callback_on_scan_start([]() {
std::cout << "Scan started" << std::endl;
});
// Set the callback to be called when the scan stops
adapter.set_callback_on_scan_stop([]() {
std::cout << "Scan stopped" << std::endl;
});
// Set the callback to be called when the scan finds a new peripheral
adapter.set_callback_on_scan_found([](SimpleBLE::Peripheral peripheral) {
std::cout << "Peripheral found: " << peripheral.identifier() << std::endl;
});
// Set the callback to be called when a peripheral property has changed
adapter.set_callback_on_scan_updated([](SimpleBLE::Peripheral peripheral) {
std::cout << "Peripheral updated: " << peripheral.identifier() << std::endl;
});
// Start scanning for peripherals
adapter.scan_start();
// Wait for 5 seconds
std::this_thread::sleep_for(std::chrono::seconds(5));
// Stop scanning for peripherals
adapter.scan_stop();
Connecting to a peripheral
Once you have a list of peripherals, you can connect to one of them. To do so,
you need to create a SimpleBLE::Peripheral
object and call the
SimpleBLE::Peripheral::connect()
method.
// Scan for peripherals for 5000 milliseconds
std::vector<SimpleBLE::Peripheral> peripherals = adapter.scan_for(5000);
// Connect to the first peripheral
SimpleBLE::Peripheral peripheral = peripherals[0];
peripheral.connect();
Note
More coming soon, I swear. :P
Learn by example
To learn how to use SimpleBLE, please refer to the examples provided
in the repository. Those examples named with a _c
suffix are using
the C-wrapped version of the library and those examples with a _safe
suffix use the _noexcept_ version of the library.
The following list briefly describes each example provided:
list_adapters: List all available adapters.
scan (cpp) & scan (c): Scan for nearby BLE devices.
connect (cpp) & connect_safe (cpp) & connect (c): Connect to a BLE device and list its services and characteristics.
read: Read a characteristic’s value.
write: Write a characteristic’s value.
notify (cpp) & notify (c): Enable notifications on a characteristic.
Concurrency
When designing your application using SimpleBLE, concurrency is a key aspect that needs to be taken into account. This is because internally the library relies on a thread pool to handle asynchronous operations and poll the operating system’s Bluetooth stack, which can suffer from contention and potentially cause the program to crash or freeze if these threads are significantly delayed.
This can have an important effect when using SimpleBLE with UI applications, such as WxWidgets or Unity.
Layers and their responsibilities
External layer
SimpleBLE::Adapter
andSimpleBLE::Peripheral
classes.These objects hold a shared pointer to
SimpleBLE::AdapterBase
andSimpleBLE::PeripheralBase
respectively.
C-style wrapper layer
This layer is a C-style wrapper around the safe C++, designed to allow integration of SimpleBLE into other languages that have support for C bindings.
Safe layer
SimpleBLE::AdapterSafe
andSimpleBLE::PeripheralSafe
classes.These objects wrap all
SimpleBLE::Adapter
andSimpleBLE::Peripheral
objects and provide an interface that does not throw exceptions. Instead, it will return anstd::optional<T>
object if the function returns a value, or a boolean indicating whether the function succeeded if the original function did not return a value. The usage is functionally equivalent to their respective counterparts in the external layer.
API layer (OS-dependent)
SimpleBLE::AdapterBase
andSimpleBLE::PeripheralBase
classes.These classes specify the API of the library on top of which the external layer is actually wrapping.
Each OS target has to implement the full public API specified in the external layer, using private methods and properties for the specific requirements of each environment.
Two convenience classes,
SimpleBLE::AdapterBuilder
andSimpleBLE::PeripheralBuilder
are provided for the case of allowing access to private methods during the build process.