# Introduction
This document will provide an introduction to Bluetooth and some
information about the Bluetooth stack on linux and some tools that are
useful.
# Overview
## Bluetooth classic
### History
Developed in the late 90’s and 1998 the Bluetooth Special Interest Group
(Bluetooth SIG) was formed among a consortium of companies to push the
standard. Bluetooth v2.0 solved a lot of issues and upped the maximum
throughput to 3Mbps. In v2.1 one level of security were introduced by
means of pairing devices. Bluetooth v3.0 introduced a way for paired
devices to send data in higher speed via another medium (wifi or phy),
this never got traction and is very seldom used. In Bluetooth v4.0 the
Bluetooth low energy (Bluetooth LE) was introduced and now there were two different way
of sending radio signals (devices could have one or the other or support
for both techniques, dual mode).
Bluetooth classic is sometimes referred to as BR/EDR which is the
modulation schemes it supports.
Bluetooth classic is mainly used for audio streaming.
### Technique
Bluetooth classic uses the 2.4GHz ISM band. The band extends from
2400MHz to 2483.5MHz. This band is divided into 78 channels by Bluetooth
classic, spaced 1MHz apart from 2402 to 2480MHz.
By using an adaptive frequency hopping algorithm, the goal is to avoid
interference from other devices using the same spectra as baby monitors,
wi-fi and microwave ovens.
### Protocol stack

Classic profiles
There is a large set of Bluetooth profiles, the most common are.
- **A2DP** - Advanced Audio Distribution Profile is a profile that
allows streaming audio from a source to a sink. For example, when
you stream music from your iPhone to your car, this is done using
the A2DP profile.
- **HFP** - Hands-Free Profile used in Bluetooth headsets
- **SPP** - Serial Port Profile emulates serial ports over Bluetooth
to provide a simple substitute for existing RS-232
- **PBAP** - Phone Book Access Profile allows access to a phone's
Phone Book, for example to display in a car to allow dialing
- **MAP** - Message Access Profile allows exchange of messages between
devices, for example to read SMS from a phone
- **AVRCP** - A/V Remote Control Profile allows a Bluetooth device to
act as a remote control, for example controlling video playback
- **PAN** – Personal Area Network Profile used for sharing networking
## Bluetooth Low Energy (Bluetooth LE)
### History
Sometimes called Bluetooth Smart was introduced in Bluetooth 4.0.
It started in a Nokia project (2001) called Wibree before being adopted
by the Bluetooth SIG.
### Bluetooth LE Protocoll stack

### GAP
The Generic Access Profile (GAP) controls connections and advertising in
Bluetooth. GAP enables the visibility of devices and determines how
devices can interact with each other.
A device is either a peripheral or a central device. A peripheral device
is often a small, low power resource constrained device that can connect
to a much more powerful central device. Peripheral devices are typically
things like sensors, proximity tags etc.
Central devices are more powerful, usually a pc, tablet or mobile phone.
### Advertising and scan response data
A peripheral device can send 31 bytes of data in two ways. The
advertising data and the scan response data. Only the advertising data
is mandatory.
If the peripheral device supports ***scan response data*** the central
device can send out a ***scan response request*** and the peripheral
device will respond with the ***scan response data*** in which they can
fit additional data that might be interesting.
During advertising a peripheral device can send data to many central
devices.
When a central device connects to a peripheral one the advertising will
stop but the two devices can then exchange data in both directions using
GATT services and characteristics.
### GATT
Generic ATTribute profile is used for sending data back and forth using
services and characteristics. A generic protocol called Attribute
protocol (ATT) stores Services, characteristics and related data in a
lookup table using 16-bit IDs for each entry in the table.
A peripheral device can only be connected to one central device. A
central device can be connected to multiple peripheral devices.
A GATT service is identified by the unique id which can be 16-bit or
128-bit. A service consists of one or more characteristics. For example,
the Heart Rate Service 0x180D contains up to 3 characteristics which of
the first one is mandatory, **Heart Rate Measurement**, **Body Sensor
Location** and **Heart Rate Control Point**.
Each characteristic has its own 16-bit or 128-bit UUID. You can either
write or read to a characteristic which has a single data point (could
also be an array of related values such as x/y/z from an accelerometer).
# Installation guide
## Supported platforms
- i.MX 8
- CCpilot V1000
- CCpilot V1200
## Activate Bluetooth
Run the following commands in the terminal to enable Bluetooth.
```
sudo systemctl enable bluetooth-attach
sudo systemctl enable bluetooth
```
You can check the status of the Bluetooth adapter by running the following command
```
bluetoothctl show
```
## Setup pulseaudio for Bluetooth
If you need to connect Bluetooth speakers/headset to a v1x00 and perhaps have the ability to stream audio to a device you can do the following setup.
### PulseAudio service file
Create a file called **pulseaudio.service** in the **/etc/systemd/system/** folder.
Use nano or similar and paste in the text below
```
[Unit]
Description=Pulseaudio sound server
After=avahi-daemon.service network.target
[Service]
ExecStart=/usr/bin/pulseaudio --system --log-target=syslog
ExecReload=/bin/kill -HUP \$MAINPID
[Install]
WantedBy=multi-user.target
```
After saving it, run the following command to enable the service.
```
sudo systemctl enable pulseaudio
```
### Setup audio user
On v1x00 we need the following setup. Run as root.
```
groupadd pulse-access
usermod -G pulse-access -a root
usermod -G audio -a root
usermod -G pulse-access -a pulse
# User of printer group lp has access to bluetooth
usermod -G lp -a pulse
```
### Setup pulseaudio system.pa for Bluetooth
The config file /etc/pulse/system.pa
need the following two lines.
```
load-module module-bluetooth-policy
load-module module-bluetooth-discover
```
Add them last in the **system.pa** config file. Then reboot the device.
### Test audio via bluetoothctl
From the command line you will run some commands as the ccs user.
If everything above is setup correctly you can start the bluetoothctl by running
bluetoothctl
Type show
to get status of the Bluetooth adapter. If it isn't powered, use
power on
to power it on.
1. Scan. Use the command scan on
to start scanning for new devices
2. Add headphones. Make sure your headphones are in pairing mode. Use the command devices
to get a list of found devices.
Localize your headphones, I have a pair of HSX headphones and they show up as **Device FC:58:FA:F8:83:20 HSX 3.0** in the list.
By using the *mac-address* of your device, pair with the headphones using the command pair FC:58:FA:F8:83:20
. After pairing OK it is adviceable to trust the device.
Use the command trust FC:58:FA:F8:83:20
to trust the device. Now you should be able to connect to it
using the command connect FC:58:FA:F8:83:20
.
3. Add streaming device. Repeat the steps taken for the headphones but with your streaming device, usually a phone. Mine is called Device F8:E9:4E:A5:52:A1 Matss iPhone XS.
Using that mac-address instead, repeat the above steps with pairing, trusting and connecting.
4. Disable scanning with the command scan off
5. Test it. For example, running spotify you can stream music to the v1x00 which will play it back on the speaker/headphones.
#### Trouble shooting
If you cannot stream music, first check that both the speaker/headphones and the streaming device are paired, trusted and connected.
Still in the bluetoothctl tool.
Use the command info FC:58:FA:F8:83:20
but replace the mac-address with your devices mac addresses. For my headphones I get this info.
```
[HSX 3.0]# info FC:58:FA:F8:83:20
Device FC:58:FA:F8:83:20 (public)
Name: HSX 3.0
Alias: HSX 3.0
Class: 0x00240418
Icon: audio-card
Paired: yes
Trusted: yes
Blocked: no
Connected: yes
LegacyPairing: no
UUID: Serial Port (00001101-0000-1000-8000-00805f9b34fb)
UUID: Headset (00001108-0000-1000-8000-00805f9b34fb)
UUID: Audio Sink (0000110b-0000-1000-8000-00805f9b34fb)
UUID: A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb)
UUID: A/V Remote Control (0000110e-0000-1000-8000-00805f9b34fb)
UUID: Handsfree (0000111e-0000-1000-8000-00805f9b34fb)
UUID: PnP Information (00001200-0000-1000-8000-00805f9b34fb)
Modalias: bluetooth:v000ApFFFFdFFFF
```
Above you also see what profiles the headphones support, stated with different UUID's.
If the sound quality is bad make sure you are not in discovery mode. Run scan off
# BlueZ
BlueZ is the official Bluetooth stack for Linux and we are using version
5.50. The recommended API to communicate with the BlueZ stack from the
application is through D-Bus. It provides an API for handling the
different profiles. The daemons which handle most of the API is through
bluetoothd and obexd. For audio-related profiles you need pulseaudio
(which is a sound server system used in modern linux distribution) . You
need the *oFono* library for using the hands free profile (HFP) for
managing phone calls. For more information on the Bluez API, pulseaudio and oFono see the
chapter Further reading.

Above, the bluez protocol stack for Bluetooth classic
# D-Bus
D-Bus is a message-oriented middleware for inter process communication on
the same machine. It is divided into a system bus and a session bus (one
for each user session). A message bus daemon will be running which acts
like a router. Each application that uses D-Bus has a one to one
connection with the message bus daemon. One instance of this message bus
daemon runs the system bus, then you will have one instance per user
session running the session bus (applications in the user session can
communicate with one another). Applications register a unique service
name to route messages from one application to another.
Example from Qt documentation
One app register a service that implements a RPN calculator, also the
app provides an object that implements an interface.
The client app can then call methods on the object. Looks like this in
qt.
```c++
QDBusInterface remoteApp( "com.example.Calculator", "/Calculator Operations","org.mathematics.RPNCalculator" );
remoteApp.call( "PushOperand", 2 );
remoteApp.call( "PushOperand", 2 );
remoteApp.call( "ExecuteOperation", "+" );
QDBusReply reply = remoteApp.call( "PopOperand" );
if ( reply.isValid() )
printf( "%d", reply.value() ); // prints 4
```
There are four types of messages being sent on the message bus.
- Method call message
- Method return
- Error messages (return an exception caused by invoking a method)
- Signal messages (notification that a signal has been emitted)
Important elements are
- Services (collection of objects providing a specific feature)
- Objects (are attached to a service, has a path like
/cc/aux/backlight)
- Interfaces (implemented by objects, has properties, methods and
signals)
- Clients (applications that uses a D-Bus service)
You usually need the following for invoking a method.
- Address, where to find the daemon
- Bus name, system bus or session bus
- Object path, where to find the object
- Interface, the type of the object
- Method, the actual action to be performed

Figure showing how two applications are communicating via D-Bus.
## dbus-monitor
For debugging purposes it can be useful to see the very messages being transmitted over D-Bus.
To monitor the system bus you can use the command line tool
```
sudo dbus-monitor --system
```
And similar for the session bus
```
dbus-monitor --session
```
# Bluetooth code examples
In the following examples we use c++ with Qt and its D-Bus interface classes. You can of course use other programming languages.
## D-Bus and datatypes
Sometimes Qt doesn't know how to handle data coming via D-Bus.
Using the Q_DECLARE_METATYPE macro with common types you can get around this problem.
Below is two commonly recurring datatypes which are good to declare.
```c++
#ifndef TYPEDEFS_H
#define TYPEDEFS_H
/* define and declare custom types used with bluez D-Bus interfaces */
#include
#include
typedef QMap InterfacesMap;
typedef QMap ObjectsMap;
Q_DECLARE_METATYPE(InterfacesMap)
Q_DECLARE_METATYPE(ObjectsMap)
#endif // TYPEDEFS_H
```
You also need to call the *qDBusRegisterMetaType* function
```c++
/* Register meta types for D-Bus */
qDBusRegisterMetaType();
qDBusRegisterMetaType();
```
Documentation [The Qt D-Bus Type System](https://doc.qt.io/qt-5/qdbustypesystem.html)
## Important D-Bus services for BlueZ
### org.freedesktop.DBus.ObjectManager
This interface defines signals *InterfaceAdded* and *InterfaceRemoved*
This signals are emitted when Bluez discovers new devices and when devices are no longer known to bluez.
The method GetManagedObjects() is also useful as you can get all of the objects that a D-bus connected process possesses.
See example below [Get list of devices](#get-a-list-of-bluetooth-devices)
### org.freedesktop.DBus.Properties
This interface is useful for setting and reading properties. It also has a signal called *PropertiesChanged*.
Bluez device objects implements this interface so you can easily read and write properties and also react to properties being changed.
Look up org.bluez.Device1. See [Latest bluez api](https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/device-api.txt)
## Powering Bluetooth on and off
Having some defines variables for commonly used strings can be good practice like below.
```c++
const QString BLUEZ_DBUS_SERVICE = "org.bluez";
const QString BLUEZ_DBUS_PATH = "/org/bluez/hci0";
const QString BLUEZ_DBUS_IF = "org.bluez.Adapter1";
```
Using the above in code to power the Bluetooth adapter on and off by setting the property *Powered* to true or false.
With QDBusInterface you can create a reference to a remote object.
In this case it lives on the systemBus. You can check that the reference is valid by calling the `isValid()` function.
```c++
void BluetoothAdapter::powerOff() {
QDBusInterface ifc(
BLUEZ_DBUS_SERVICE,
BLUEZ_DBUS_PATH,
BLUEZ_DBUS_IF,
QDBusConnection::systemBus());
if (ifc.isValid()) {
ifc.setProperty("Powered", false);
}
}
```
## Scanning for devices
The org.bluez.Adapter1 has methods for starting and stopping discovery (which is essential scanning).
Below you have two code examples of starting and stopping discovery.
```c++
void BluetoothAdapter::startDiscovery() {
QDBusInterface adapter("org.bluez",
"/org/bluez/hci0",
"org.bluez.Adapter1",
QDBusConnection::systemBus());
QDBusReply reply = adapter.call("StartDiscovery");
if (!reply.isValid()) {
qWarning() << Q_FUNC_INFO << "Failed to start discovery: " << reply.error().message();
}
}
```
```c++
void BluetoothAdapter::stopDiscovery() {
QDBusInterface adapter("org.bluez",
"/org/bluez/hci0",
"org.bluez.Adapter1",
QDBusConnection::systemBus());
QDBusReply reply = adapter.call("StopDiscovery");
if (!reply.isValid()) {
qWarning() << Q_FUNC_INFO << "Failed to stop discovery: " << reply.error().message();
}
}
```
## Pair with device
To pair with a specific device you need to know its object path. If you are catching the InterfaceAdded like mentioned earlier you can keep a list of devices to pair with.
Your device will likely have another ID than mine **dev_F8_E9_4E_A5_52_A1**
It is a good idea to also set *Trusted* to true.
```c++
QDBusInterface *dev = new QDBusInterface("org.bluez",
"/org/bluez/hci0/dev_F8_E9_4E_A5_52_A1",
"org.bluez.Device1",
QDBusConnection::systemBus());
if (dev->property("Paired").toBool())
return;
else {
dev->asyncCall("Pair");
dev->setProperty("Trusted", true);
}
}
```
## How to catch changing properties
Below is an example of how to catch properties changing on any Bluetooth device known by bluez.
```c++
m_interface = new QDBusInterface("org.bluez",
"/org/bluez/hci0",
"org.bluez.Device1",
QDBusConnection::systemBus());
if (!m_interface->connection().connect("org.bluez",
"",
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
this,
SLOT(propertiesChanged(const QString, const QMap, const QStringList)))) {
qWarning() << Q_FUNC_INFO << "Failed to connect to D-Bus properties changed signal" << m_interface->lastError().name();
}
```
## Get a list of Bluetooth devices
Below an example of how to get all managed objects from the ObjectManager and pick out the interesting ones for Bluetooth.
You would problably want to add devices to some sort of list and for the adapter you might set powerOn. See code below.
```c++
QDBusInterface manager("org.bluez",
"/",
"org.freedesktop.DBus.ObjectManager",
QDBusConnection::systemBus());
QDBusReply>> reply;
reply = manager.call("GetManagedObjects");
if (!reply.isValid()) {
qWarning() << Q_FUNC_INFO << "Failed to connect to bluez: " << reply.error().message();
return;
}
auto objects = reply.value();
for (auto i = objects.begin(); i != objects.end(); ++i) {
auto ifaces = i.value();
for (auto j = ifaces.begin(); j != ifaces.end(); ++j) {
if (j.key() == "org.bluez.Device1")
addDevice(i.key());
else if (j.key() == "org.bluez.Adapter1")
powerOn();
}
}
```
## Control streaming of music
If a device supports the "Audio/Video Remove Control Profile Target" ("0000110c-0000-1000-8000-00805f9b34fb") you can control the playback via the media-api [Bluez media api](https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/media-api.txt)
Bluez will add an object like below on D-Bus (here the device has the mac address 48:26:2C:AC:77:B9)
**/org/bluez/hci0/dev_48_26_2C_AC_77_B9/player0**
Get the D-Bus interface with
```c++
m_objectPath = QDBusObjectPath("/org/bluez/hci0/dev_48_26_2C_AC_77_B9/player0");
p_mediaInterface = new QDBusInterface ("org.bluez",
m_objectPath.path(),
"org.bluez.MediaPlayer1",
QDBusConnection::systemBus());
```
For keeping a record of what is playing etc. Connect the propertiesChanged signal.
```c++
if(!p_mediaInterface->connection().connect("org.bluez",
m_objectPath.path(),
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
this,
SLOT(propertiesChanged(const QString, const QMap, const QStringList)))) {
qDebug() << "Failed to connect to D-Bus properties changed signal" << p_mediaInterface->lastError().name();
}
```
When the above is setup correctly you can simply control the playback by calling.
```c++
p_mediaInterface->call("Play");
p_mediaInterface->call("Stop");
p_mediaInterface->call("Pause");
p_mediaInterface->call("Next");
p_mediaInterface->call("Previous");
```
When a property changes you can possible read status and what track is playing etc.
```c++
void BluetoothMediaPlayer::propertiesChanged(const QString path, const QMap changedProperties, const QStringList invalidatedProperties)
{
Q_UNUSED(invalidatedProperties)
Q_UNUSED(path)
QMapIterator mapIter(changedProperties);
while(mapIter.hasNext()) {
mapIter.next();
if(mapIter.key() == "Status") {
setStatus(mapIter.value().toString());
}
if(mapIter.key() == "Track") {
QMap trackData = qdbus_cast >(mapIter.value());
if(!trackData.value("Artist").isNull()) setArtist(trackData.value("Artist").toString());
if(!trackData.value("Title").isNull()) setTitle(trackData.value("Title").toString());
if(!trackData.value("Album").isNull()) setAlbum(trackData.value("Album").toString());
if(!trackData.value("TrackNumber").isNull()) setTrackNumber(trackData.value("TrackNumber").toInt());
if(!trackData.value("NumberOfTracks").isNull()) setNumberOfTracks(trackData.value("NumberOfTracks").toInt());
}
}
}
```
## Serial port profile as client
Using SPP is like having two devices connected by a serial cable.
The QtBluetooth way of finding nearby devices is used here but could be replaced with the D-Bus version.
Qt has a subset of Bluetooth-classes. In this example we will use the **QBluetoothDeviceDiscoveryAgent** class to find nearby devices that supports SPP.
Below we have created a Discovery class will keep track of nearby devices and has slots for useful signals coming from the QBluetoothDeviceDiscoveryAgent.
discovery.h
```c++
#ifndef DISCOVERY_H
#define DISCOVERY_H
#include
#include
#include
#include
#include
#include "devicename.h"
class Discovery : public QObject
{
Q_OBJECT
Q_PROPERTY(bool scanning READ scanning NOTIFY scanningChanged)
Q_PROPERTY(QVariant devices READ devices NOTIFY devicesChanged)
Q_PROPERTY(QString info READ info NOTIFY infoChanged)
public:
explicit Discovery(QObject *parent = nullptr);
void setup();
bool scanning() const;
QString info() const;
QVariant devices();
Q_INVOKABLE void startSearch();
Q_INVOKABLE void sendBytes(long noOfBytes);
private slots:
void addDevice(const QBluetoothDeviceInfo &device);
void scanError(QBluetoothDeviceDiscoveryAgent::Error error);
void scanFinished();
void connectedToService();
signals:
void scanningChanged();
void devicesChanged();
void infoChanged();
private:
void setInfo(QString value);
QBluetoothDeviceDiscoveryAgent *m_deviceDiscoveryAgent;
QList m_devices;
QBluetoothSocket *m_btSocket;
QString m_info;
bool m_busy;
};
#endif // DISCOVERY_H
```
By setting it up like this we can catch discovered devices. When the socket connects our function *connectedToService* will be called.
```c++
void Discovery::setup()
{
m_deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &Discovery::addDevice);
connect(m_deviceDiscoveryAgent, static_cast(&QBluetoothDeviceDiscoveryAgent::error),
this, &Discovery::scanError);
connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished, this, &Discovery::scanFinished);
connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::canceled, this, &Discovery::scanFinished);
m_btSocket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol, this);
connect(m_btSocket, &QBluetoothSocket::connected, this, &Discovery::connectedToService);
}
```
To activate scanning you call
```c++
m_deviceDiscoveryAgent->start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethod::ClassicMethod);
```
The key is to find devices which supports the serial port profile. In the function addDevice we check the UUIDS for matching SerialPortProfile.
In the code below we try to connect to the first found Bluetooth device which supports the serial port profile.
```c++
void Discovery::addDevice(const QBluetoothDeviceInfo &device)
{
m_devices.append(new DeviceName(device));
QList services = device.serviceUuids();
QBluetoothUuid theId;
foreach(theId, services)
{
if (theId == QBluetoothUuid::SerialPort && m_busy == false)
{
qDebug() << "Found the unit!!! Connecting to " << device.name();
setInfo(tr("Found device support Serial Port %1").arg(device.name()));
m_btSocket->connectToService(device.address(), theId);
m_busy = true;
}
}
emit devicesChanged();
}
```
When connected via the socket you can start to send and receive bytes.
Like writing a message
```c++
char data[] = {"Hello SPP server!"};
m_btSocket->write(data);
```
Read more about QBluetoothSockets at [QBluetoothSocket](https://doc.qt.io/qt-5/qbluetoothsocket.html)
## Serial port profile as server
Using Bluez D-Bus API we can use the [profile-api](https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/profile-api.txt) to register that we can handle the SPP as server.
Using an adaptor class for the org.bluez.Profile1 interface is the easiest way.
Using the tool we can turn an XML description of the interface. Saved as Profile1.xml.
```xml
```
The .h file
```c++
/*
* This file was generated by qdbusxml2cpp version 0.8
* Command line was: qdbusxml2cpp Profile1.xml -a bluezprofile1adaptor
*
* qdbusxml2cpp is Copyright (C) 2020 The Qt Company Ltd.
*
* This is an auto-generated file.
* This file may have been hand-edited. Look for HAND-EDIT comments
* before re-generating it.
*/
#ifndef BLUEZPROFILE1ADAPTOR_H
#define BLUEZPROFILE1ADAPTOR_H
#include
#include
#include "typedefs.h"
QT_BEGIN_NAMESPACE
class QByteArray;
template class QList;
template class QMap;
class QString;
class QStringList;
class QVariant;
QT_END_NAMESPACE
/*
* Adaptor class for interface org.bluez.Profile1
*/
class Profile1Adaptor: public QDBusAbstractAdaptor
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.bluez.Profile1")
Q_CLASSINFO("D-Bus Introspection", ""
" \n"
" \n"
" \n"
" \n"
" \n"
" \n"
" \n"
" \n"
" \n"
" \n"
" \n"
" \n"
"")
public:
Profile1Adaptor(QObject *parent);
virtual ~Profile1Adaptor();
public: // PROPERTIES
public Q_SLOTS: // METHODS
void NewConnection(const QDBusObjectPath &device, const QDBusUnixFileDescriptor &fd, QVariantMap fd_properties);
void Release();
void RequestDisconnection(const QDBusObjectPath &device);
Q_SIGNALS: // SIGNALS
};
#endif
```
The .cpp file
```c++
/*
* This file was generated by qdbusxml2cpp version 0.8
* Command line was: qdbusxml2cpp Profile1.xml -a bluezprofile1adaptor
*
* qdbusxml2cpp is Copyright (C) 2020 The Qt Company Ltd.
*
* This is an auto-generated file.
* Do not edit! All changes made to it will be lost.
*/
#include "bluezprofile1adaptor.h"
#include
#include
#include
#include
#include
#include
#include
/*
* Implementation of adaptor class Profile1Adaptor
*/
Profile1Adaptor::Profile1Adaptor(QObject *parent)
: QDBusAbstractAdaptor(parent)
{
// constructor
setAutoRelaySignals(true);
}
Profile1Adaptor::~Profile1Adaptor()
{
// destructor
}
void Profile1Adaptor::NewConnection(const QDBusObjectPath &device, const QDBusUnixFileDescriptor &fd, QVariantMap fd_properties)
{
qDebug() << __PRETTY_FUNCTION__ << device.path();
// handle method call org.bluez.Profile1.NewConnection
QMetaObject::invokeMethod(parent(), "NewConnection", Q_ARG(QDBusObjectPath, device), Q_ARG(QDBusUnixFileDescriptor, fd), Q_ARG(QVariantMap, fd_properties));
}
void Profile1Adaptor::Release()
{
// handle method call org.bluez.Profile1.Release
QMetaObject::invokeMethod(parent(), "Release");
}
void Profile1Adaptor::RequestDisconnection(const QDBusObjectPath &device)
{
// handle method call org.bluez.Profile1.RequestDisconnection
QMetaObject::invokeMethod(parent(), "RequestDisconnection", Q_ARG(QDBusObjectPath, device));
}
```
Using the above adaptor class it is pretty straight forward to implement the functions as in the code below.
```c++
#ifndef SPP_SERVER_H
#define SPP_SERVER_H
#include
#include
#include "typedefs.h"
#include
#include
class SPP_server : public QObject
{
Q_OBJECT
Q_PROPERTY(QString message READ message NOTIFY newData)
Q_PROPERTY(QString info READ info NOTIFY newInfo)
public:
explicit SPP_server(QObject *parent = nullptr);
const QString message();
const QString info();
Q_INVOKABLE void sendMsg(QString value);
public slots:
void NewConnection(const QDBusObjectPath &device, const QDBusUnixFileDescriptor &fd, QVariantMap fd_properties);
void Release();
void RequestDisconnection(const QDBusObjectPath &device);
signals:
void newData();
void newInfo();
private:
void pollData();
void setMessage(QString value);
void setInfo(QString value);
int m_fileDescriptor;
QString m_message;
long m_receivedBytes;
QString m_info;
};
#endif // SPP_SERVER_H
```
and the .cpp part. From the api-description you can read about the properties being defined in args.
```c++
#include "SPP_Server.h"
#include
#include
#include
SPP_server::SPP_server(QObject *parent) : QObject(parent)
{
m_fileDescriptor = -1;
m_receivedBytes = 0;
QDBusInterface profileManager("org.bluez", "/org/bluez", "org.bluez.ProfileManager1", QDBusConnection::systemBus());
qDebug() << "SPP_server profilemanager calls";
if (profileManager.isValid()) {
QVariant theProfilePath{QVariant::fromValue(QDBusObjectPath("/example/spp"))};
// SerialPortServiceClass
QString server_uuid = "00001101-0000-1000-8000-00805f9b34fb";
QMap args;
args["AutoConnect"] = true;
args["Role"] = "server";
args["Name"] = "Our SPP";
args["Channel"] = QVariant::fromValue((uint16_t)8);
args["RequireAuthentication"] = false;
args["RequireAutharization"] = false;
// Call void RegisterProfile(object profile, string uuid, dict options)
QDBusMessage msg{profileManager.call("RegisterProfile", theProfilePath, server_uuid, args)};
if (msg.type() != QDBusMessage::ErrorMessage) {
qDebug() << "Registerprofile in SPP_server went well";
}
else
{
qDebug() << msg.errorMessage();
}
}
else
{
qDebug() << profileManager.lastError().message();
}
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &SPP_server::pollData);
timer->start(50);
}
const QString SPP_server::message()
{
return m_message;
}
void SPP_server::sendMsg(QString value)
{
// Write to client if we have a filedescriptor
if (m_fileDescriptor <= 0)
return;
QByteArray arr = value.toUtf8();
write(m_fileDescriptor, arr.data(), arr.length());
}
void SPP_server::setMessage(QString value)
{
if (value != m_message)
{
m_message = value;
emit newData();
}
}
const QString SPP_server::info()
{
return m_info;
}
void SPP_server::setInfo(QString value)
{
if (value != m_info)
{
m_info = value;
emit newInfo();
}
}
void SPP_server::NewConnection(const QDBusObjectPath &device, const QDBusUnixFileDescriptor &fd, QVariantMap fd_properties)
{
qDebug() << __PRETTY_FUNCTION__ << device.path();
if (fd.isValid())
{
// Take it or leave it?
if (m_fileDescriptor != -1)
close(m_fileDescriptor);
m_fileDescriptor = dup(fd.fileDescriptor());
}
}
void SPP_server::Release()
{
// Clean up stuff
qDebug() << __PRETTY_FUNCTION__;
}
void SPP_server::RequestDisconnection(const QDBusObjectPath &device)
{
qDebug() << __PRETTY_FUNCTION__ << device.path();
// Oh.. should disconnect here
if (m_fileDescriptor > 0)
{
close(m_fileDescriptor);
m_fileDescriptor = -1;
}
}
void SPP_server::pollData()
{
// Do we have a connection?
if (m_fileDescriptor <= 0)
return;
// Read data, if small enough, show it UI
char buf[65536];
int readCount = read(m_fileDescriptor, buf, sizeof(buf));
if (readCount > 0)
{
m_receivedBytes += readCount;
// Are we receiveing something big? maybe not good to show in ui as text
if (readCount < 400)
setMessage(QString::fromUtf8(buf, readCount));
// qDebug() << "Total received bytes : " << m_receivedBytes;
setInfo(tr("Total received bytes : %1").arg(m_receivedBytes));
}
qDebug() << "TimeStamp: " << QDateTime::currentMSecsSinceEpoch() << " Total received bytes : " << m_receivedBytes;
}
```
You need to register the spp_server to D-Bus for bluez to route calls to it.
in your main.cpp you need something like below.
```c++
SPP_server server;
Profile1Adaptor profadaptor(new Profile1Adaptor(&server));
QDBusConnection::systemBus().registerObject("/example/spp", &server);
```
## Send file to device
Files can be sent to a device if it supports the OBEX Object Push Profile. From the UUIDs property of a device you can
check if the OPP profile is among them "00001105-0000-1000-8000-00805f9b34fb".
To send a file you can use QBluetoothTransferRequest.
The receiving device might need some extra attributes about the file being transmitted. Added with the *setAttribute* fuction below.
```c++
void BluetoothTransfer::transferFile(QString address, QString fileName)
{
qDebug() << __PRETTY_FUNCTION__ << address << fileName;
QBluetoothAddress addr(address);
QBluetoothTransferRequest request(addr);
request.setAttribute(QBluetoothTransferRequest::DescriptionAttribute, QString("A simple txtfile"));
request.setAttribute(QBluetoothTransferRequest::NameAttribute, QString("testfile.txt"));
request.setAttribute(QBluetoothTransferRequest::TypeAttribute, "text/plain");
QFile *file = new QFile(fileName);
request.setAttribute(QBluetoothTransferRequest::LengthAttribute, QVariant(file->size()));
request.setAttribute(QBluetoothTransferRequest::TimeAttribute, 1);
qDebug() << "Size of file to send: " << QVariant(file->size());
// Ask the transfer manager to send it
QBluetoothTransferReply *reply = p_manager->put(request, file);
if (reply->error() == QBluetoothTransferReply::NoError) {
// Connect to the reply's signals to be informed about the status and do cleanups when done
QObject::connect(reply, SIGNAL(finished(QBluetoothTransferReply*)),
this, SLOT(transferFinished(QBluetoothTransferReply*)));
QObject::connect(reply, SIGNAL(error(QBluetoothTransferReply::TransferError)),
this, SLOT(transferError(QBluetoothTransferReply::TransferError)));
} else {
qWarning() << "Cannot push file" << reply->errorString();
}
}
```
By using *QObject::connect(...)* to connect the *finished* signal and *error* signal of **QBluetoothTransferReply** you can keep track of the transfer status.
## Read phonebook from mobile device
Similar to sending a file via the Object push profile, quering for phonebook contacts also uses the bluez obex (Object Exchange) under the hood.
The api can be viewed here [Bluez Obex-api](https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/obex-api.txt)
Using the address of the device you want to read phonebook from (Address is a property of the device-api).
We create a new OBEX session where we target pbap (phonebook access). If allowed by the device we will get a session path in return.
```c++
void BluetoothContacts::getContacts(QString deviceAddress)
{
QDBusInterface client("org.bluez.obex",
"/org/bluez/obex",
"org.bluez.obex.Client1",
QDBusConnection::sessionBus());
QMap args;
args["Target"] = "pbap";
QDBusReply reply = client.call("CreateSession", deviceAddress, args);
if (!reply.isValid()) {
qWarning() << "Failed to create session: " << reply.error();
return;
}
QString sessionPath = reply.value().path();
qDebug() << "session path:" << sessionPath;
listContacts(sessionPath);
}
```
In the listContacts function we create a QDBusInterface with the sessionPath as path argument.
```c++
void BluetoothContacts::listContacts(const QString &path)
{
QDBusInterface client("org.bluez.obex",
path,
"org.bluez.obex.PhonebookAccess1",
QDBusConnection::sessionBus());
QDBusReply reply1 = client.call("Select", "int", "PB");
if (!reply1.isValid()) {
qWarning() << "Failed to select internal phonebook: " << reply1.error();
return;
}
```
Using the *Select* function (also described in the obex-api) we target the internal phonebook.
Below we will then try to pull data from it but only the name and the phone number by providing FN and TEL for the filter.
```c++
QMap filterMap;
QStringList filters;
QStringList fields;
filters << "FN" << "TEL";
fields << "FN" << "TEL";
filterMap["Fields"] = fields;
QDBusReply reply = client.call("PullAll", "phonebook.vcf", filterMap);
if(!reply.isValid()) {
qWarning() << "Failed to pull all contacts: " << reply.error();
return;
}
m_phonebookTransferPath = reply.value().path();
```
If the above went well we store the path to the transfer session of the phonebook data.
By using a timer we can check the status of the ongoing transfer.
```c++
void BluetoothContacts::checkTransferStatus()
{
QDBusInterface sessionClient("org.bluez.obex",
m_phonebookTransferPath,
"org.bluez.obex.Transfer1",
QDBusConnection::sessionBus());
QString status = sessionClient.property("Status").toString();
// Eventually the status will be complete or empty "" when transfer is done
if(status == QString("complete") || status == "") {
qDebug() << "Emitting contactsTransferReady";
emit contactsTransferReady();
}
else if(status == QString("error")) {
qWarning() << "contacts transfer failed";
}
else {
QTimer::singleShot(100, this, SLOT(checkTransferStatus()));
}
}
```
When the transfer is done you can access the phonebook via a QFile object.
`QFile file(QDir::homePath() + "/phonebook.vcf");`
## Reading data from a Bluetooth LE sensor
Let's assume a real sensor example, such as the BT510 sensor from Laird

[Sentrius BT510 Laird sensor user guide](https://www.lairdconnect.com/documentation/sentrius-bt510-user-guide)
This is a beacon like sensor that transmit data to anyone nearby. No connect function call needed.
It does transmit data by utilizing the few bytes you can freely define in the 31 byte advertisement package for Bluetooth LE devices.
Looking into the guide of this sensor you get a grasp of how to interpret the data.
The key for using such a sensor is knowing that the data shows up in **ManufacturerData** property of the device.
From the bluez D-Bus [device-api](https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/device-api.txt)
**dict ManufacturerData [readonly, optional]**
*Manufacturer specific advertisement data.
Keys are 16 bits Manufacturer ID followed by its byte array value.*
The sensor can send temperature, battery voltage, movement detection and open/close status of a switch.
It also have some way of triggering alarms on different temperature levels.
This is encoded into the advertisement data.
Using D-Bus connect the propertiesChanged signal of the device. Notice that
**/org/bluez/hci0/dev_F8_E9_4E_A5_52_A1"** is the path to the device, replace with what you have.
```c++
void BluetoothLE_AdvertSensor::initDBus()
{
p_SensorDeviceInterface = new QDBusInterface ("org.bluez",
"/org/bluez/hci0/dev_F8_E9_4E_A5_52_A1",
"org.bluez.Device1",
QDBusConnection::systemBus());
if(!p_SensorDeviceInterface->connection().connect("org.bluez",
"/org/bluez/hci0/dev_F8_E9_4E_A5_52_A1",
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
this,
SLOT(propertiesChanged(const QString, const QMap, const QStringList)))) {
qDebug() << "Failed to connect to D-Bus properties changed signal" << p_SensorDeviceInterface->lastError().name();
}
}
```
And then look for the ManufacturerData
```c++
void BluetoothLE_AdvertSensor::propertiesChanged(const QString path, const QMap changedProperties, const QStringList invalidatedProperties)
{
Q_UNUSED(invalidatedProperties)
Q_UNUSED(path)
QMapIterator mapIter(changedProperties);
while(mapIter.hasNext()) {
mapIter.next();
// Advertised data is found in the ManufacturerData-property
if(mapIter.key() == "ManufacturerData") {
QMap theData = qdbus_cast >(mapIter.value());
QMapIterator dataIter(theData);
while (dataIter.hasNext())
{
dataIter.next();
// For this sensor, the interesting data appear if the key is 0x77 (Laird Company ID 1)
if (dataIter.key() == 0x77)
{
// Check the init-flag
if (!initSuccess)
setInitSuccess(true);
QByteArray arr = dataIter.value().toByteArray();
qDebug() << QDateTime::currentDateTime() << " Length: " << arr.length() << " New values: " << arr;
auto data = reinterpret_cast(arr.constData());
// arr[4] and arr[5] == Flags, current state of alarms etc
quint16 newState = data[4] + (data[5] << 8);
this->setState(newState);
// ex New values: "\x01\x00\x00\x00\x07\x81\xD6\xAB<\xB6\x01\xDE\x04\x03\x02v\x02&a\x1F\t\x00\x00\x02"
// Data value part of 4 bytes, the LSB is data[19]
quint32 theValue = data[19] + (data[20] << 8) + (data[21] << 16) + (data[22] << 24);
// arr[12] == Type of data in theValue below
switch(data[12])
{
case 1:
// Temperature
qDebug() << "We have a new temperature of " << theValue/100.0f;
this->setTemperature(theValue);
break;
case 2:
// Magnet .. has a state
qDebug() << "Door is " << (theValue ? "open" : "closed");
break;
case 3:
// Movement
qDebug() << "We have movement!";
break;
case 4:
// High temp alarm
qDebug() << "We have a new high temperature of " << theValue/100.0f;
this->setTemperature(theValue);
break;
case 5:
// High temp 2 alarm
qDebug() << "We have a new high temperature 2 of " << theValue/100.0f;
this->setTemperature(theValue);
break;
case 6:
// Alarm high temp clear
qDebug() << "Alarm high temp clear at " << theValue/100.0f;
this->setTemperature(theValue);
break;
case 7:
// Alarm low temp 1
qDebug() << "Alarm low temp 1 at " << theValue/100.0f;
this->setTemperature(theValue);
break;
case 8:
// Alarm low temp 2
qDebug() << "Alarm low temp 2 at " << theValue/100.0f;
this->setTemperature(theValue);
break;
case 9:
// Alarm low temp clear
qDebug() << "Alarm low temp clear at " << theValue/100.0f;
this->setTemperature(theValue);
break;
case 10:
// Alarm delta temp
qDebug() << "Alarm delta temp " << theValue/100.0f;
this->setTemperature(theValue);
break;
case 12:
// Battery good
qDebug() << "Battery good at " << theValue << "mV";
this->setBattery(theValue);
break;
case 13:
// Advertise on button
qDebug() << "Advertise on button at " << theValue << "mV";
this->setBattery(theValue);
break;
case 16:
// Battery bad
qDebug() << "Battery bad at " << theValue << "mV";
this->setBattery(theValue);
break;
}
}
}
}
else {
qDebug() << "Unknown property changed : " << mapIter.key();
}
}
}
```
Implementing setters and adding signals for showing data in UI will be the easy part.
## Reading data from heart rate sensor, Bluetooth LE
A heart sensor will implement the GATT service 0000180d-0000-1000-8000-00805f9b34fb (HRS - Heart rate service). This is how you
usually use Bluetooth LE devices. They expose what services they support via GATT. If you find
a device nearby supporting what you are looking for you try to connect to it.
You will use the [gatt-api](https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/gatt-api.txt)
to access the service and its characteristics.
With the help of GetManagedObjects (See link) you can check that your device has the
**org.bluez.GattService1** interface. You can also check that it has the **org.bluez.GattCharacteristic1**
interface for reading the different values exposed by the Heart rate service.
If you reference a characteristic you can read the UUID of the characteristic like below
```c++
QString HR_MEASUREMENT_CH_ID = "00002a37-0000-1000-8000-00805f9b34fb";
QDBusInterface* characteristic = new QDBusInterface ("org.bluez",
aPath,
"org.bluez.GattCharacteristic1",
QDBusConnection::systemBus());
QString uuid = characteristic->property("UUID").toString();
if (uuid ==this->HR_MEASUREMENT_CH_ID)
this->p_hrMeasurement = characteristic;
```
You also need a QDBusInterface to the device like seen before (dev_F8... should be replaced with your device)
```c++
p_heartrateDevice = new QDBusInterface ("org.bluez",
"/org/bluez/hci0/dev_F8_E9_4E_A5_52_A1",
"org.bluez.Device1",
QDBusConnection::systemBus());
```
You should call Connect on the heartRateDevice and wait for the Connected property to be true.
To make the sensor start updating its heart rate value you need to call.
```c++
QDBusReply reply = p_hrMeasurement->call("StartNotify");
```
## General attribute variable transfer using Bluetooth LE
A Bluetooth LE device can be either peripheral or central. A peripheral device is often some sort of sensor
and the central device is a more powerful device, usually a mobile phone or computer.
A peripheral device will start off by advertising what kind of services it provides. This communication is unidirectional.
To communicate with peripheral BLE device a central device will connect to it. When a connection has been establised only bidirectional
communication between the two devices is possible.
### Services and characteristics
If we want a custom behaviour, not implementing a known service, we start by defining our own service and characteristics.
In the example below we define three variables exposed in our service.
```c++
const QBluetoothUuid ccReadVariableService = QBluetoothUuid(QString("cccccccc-cc16-4841-be7b-cb2d53dee717"));
const QBluetoothUuid ccReadVariableCharacteristicGauge1 = QBluetoothUuid(QString("cc02391c-53b5-4ead-b1b9-d3063407e90b"));
const QBluetoothUuid ccReadVariableCharacteristicGauge2 = QBluetoothUuid(QString("cc89ba3a-3b03-4e66-9560-14be899d607d"));
const QBluetoothUuid ccWriteVariableCharacteristicString = QBluetoothUuid(QString("cc0433cc-b100-45e7-bd91-2c2d3d3f907f"));
```
You can use a online tool for generating random UUID's like we did above. An easy way for transferring data is to have one
characteristic per value. A more advanced way would be to implement serial communication via one readable variable and one writeable.
### Server part (the peripheral device)
Using some classes from QtBluetooth make this one easy.
1. Setup the device and start advertising our custom service
2. Handle new connection
3. Update the Gauge1 (Readable) value as fast as possible in a loop
4. Update the Gauge2 (Notify) value only when it changes
5. Check if the client writes to the ccWriteVariableCharacteristicString value
6. If the client (central device) disconnects, go back to advertising again.
The .h file for the server
```c++
#ifndef SERVER_H
#define SERVER_H
#include
#include
class server : public QObject
{
Q_OBJECT
public:
explicit server(QObject *parent = nullptr);
Q_INVOKABLE void setGauge1Value(qreal value);
Q_INVOKABLE void setGauge2Value(quint8 value);
Q_PROPERTY(QString info READ info NOTIFY infoChanged)
Q_PROPERTY(QString clientMsg READ clientMsg NOTIFY clientMessageChanged)
QString info() const;
QString clientMsg() const;
private:
void Setup();
void Value1Limiter();
void WriteValue1ToBT(qreal value);
void setInfo(QString info);
void setClientMsg(QString value);
void characteristicWritten(const QLowEnergyCharacteristic &c,
const QByteArray &value);
QLowEnergyController* leController;
QLowEnergyService* leService;
QLowEnergyAdvertisingData* leData;
QLowEnergyCharacteristic leCharacteristic1;
int m_value1MaxSendPerTimeout = 500;
int m_value1Counter = 0;
bool m_value1Sleep = false;
qreal m_value1LastSet;
QString m_info;
QString m_msgFromClient;
private slots:
void ControllerStateChanged(QLowEnergyController::ControllerState state);
signals:
void infoChanged();
void clientMessageChanged();
};
#endif // SERVER_H
```
Important bits in the cpp file.
We setup our service with the three variables we defined UUIDs.
```c++
void server::Setup()
{
//! [Advertising Data]
leData = new QLowEnergyAdvertisingData(); // advertisingData;
leData->setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityGeneral);
leData->setIncludePowerLevel(true);
leData->setServices(QList() << ccReadVariableService);
// Gauge1
QLowEnergyCharacteristicData charData;
charData.setUuid(ccReadVariableCharacteristicGauge1);
// qreal for first value
QByteArray arr1;
arr1.setNum(qreal(0));
charData.setValue(arr1);
// Read on this one... you can pull a new value about 20 times per second
charData.setProperties(QLowEnergyCharacteristic::Read);
const QLowEnergyDescriptorData clientConfig(QBluetoothUuid::ClientCharacteristicConfiguration,
QByteArray(2, 0));
charData.addDescriptor(clientConfig);
// Gauge 2, more of a state value in one single byte, use notify for this one
QLowEnergyCharacteristicData charData2;
charData2.setUuid(ccReadVariableCharacteristicGauge2);
charData2.setValue(QByteArray(1, 1));
charData2.setProperties(QLowEnergyCharacteristic::Notify);
const QLowEnergyDescriptorData clientConfig2(QBluetoothUuid::ClientCharacteristicConfiguration,
QByteArray(2, 0));
charData2.addDescriptor(clientConfig2);
// A short string to be set from client back to server (write)
QLowEnergyCharacteristicData charData3;
charData3.setUuid(ccWriteVariableCharacteristicString);
QByteArray arr3(50,0);
charData3.setValue(arr3);
charData3.setValueLength(0,50);
charData3.setProperties(QLowEnergyCharacteristic::Write);
const QLowEnergyDescriptorData clientConfig3(QBluetoothUuid::ClientCharacteristicConfiguration,
QByteArray(2, 0));
charData3.addDescriptor(clientConfig3);
// Setup the service, we are only using one service with three characteristics
QLowEnergyServiceData serviceData;
serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary);
serviceData.setUuid(ccReadVariableService);
serviceData.addCharacteristic(charData);
serviceData.addCharacteristic(charData2);
serviceData.addCharacteristic(charData3);
//! [Start Advertising]
leController = QLowEnergyController::createPeripheral();
leService = leController->addService(serviceData);
connect(leService, &QLowEnergyService::characteristicChanged, this, &server::characteristicWritten);
leController->startAdvertising(QLowEnergyAdvertisingParameters(), *leData,
*leData);
// Keep track of state changes
connect(leController, &QLowEnergyController::stateChanged,
this, &server::ControllerStateChanged);
connect(leController, static_cast(&QLowEnergyController::error),
this, [this](){
qDebug() << "leController got error";
});
// Keep a reference to the first characteristic
leCharacteristic1 = leService->characteristic(ccReadVariableCharacteristicGauge1);
}
```
To update the qreal-value (our first characteristic) we use this code
```c++
void server::WriteValue1ToBT(qreal value)
{
QByteArray arr;
arr.setNum(value);
leService->writeCharacteristic(leCharacteristic1, arr);
}
```
Updating our second value, another way of writing without having the reference to the characteristic. This value is also setup as a *Notify* so it behaves like an event to the client side.
```c++
void server::setGauge2Value(quint8 value)
{
QLowEnergyCharacteristic characteristic = leService->characteristic(ccReadVariableCharacteristicGauge2);
QByteArray arr(1,value);
leService->writeCharacteristic(characteristic, arr);
}
```
Our third variable is a string that is writeable from the client (so we don't have any write-code on the server part)
When the central device writes we can catch the value in:
```c++
void server::characteristicWritten(const QLowEnergyCharacteristic &c, const QByteArray &value)
{
// Something was written from client side
if (c.uuid() == ccWriteVariableCharacteristicString)
{
// Update msg from client
qDebug() << "Client message " << QString::fromUtf8(value);
setClientMsg(QString::fromUtf8(value));
}
}
```
Also in the setup we catch when the state of the low energy adaptor changes.
```c++
void server::ControllerStateChanged(QLowEnergyController::ControllerState state)
{
// State of leController changes
qDebug() << "ControllerStateChanged to state " << state;
// Restart advertising after a disconnect
if (state != QLowEnergyController::AdvertisingState && state != QLowEnergyController::ConnectedState)
{
// Start advertisting again
leController->startAdvertising(QLowEnergyAdvertisingParameters(), *leData,
*leData);
}
}
```
### Client part (the central device)
As in other examples we can use the QBluetoothDeviceDiscoveryAgent class and call our function addDevice when devices are discovered.
```c++
m_deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
m_deviceDiscoveryAgent->setLowEnergyDiscoveryTimeout(5000);
connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &Discovery::addDevice);
```
When starting to scan we supply the LowEnergyMethod to the agent.
```c++
void Discovery::startSearch()
{
// Find some Bluetooth LE devices
m_deviceDiscoveryAgent->start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethod::LowEnergyMethod);
emit scanningChanged();
}
```
We can filter out Bluetooth LE devices and check if the service uuid matches our custom *ccReadVariableService*.
If we have a match we call StartConnect on our Client class described after this code block.
```c++
void Discovery::addDevice(const QBluetoothDeviceInfo &device)
{
// If device is LowEnergy-device, add it to the list
if (device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) {
m_devices.append(new DeviceName(device));
QList services = device.serviceUuids();
QBluetoothUuid theId;
foreach(theId, services)
{
if (theId == ccReadVariableService)
{
m_client->setInfo("Found device " + device.name() );
m_client->StartConnect(device);
}
}
}
}
```
A simple client class is created.
The .h file
```c++
#ifndef CLIENT_H
#define CLIENT_H
#include
#include
#include
#include
#include "typedefs.h"
// Read data from a server using Bluetooth LE
class Client : public QObject
{
Q_OBJECT
Q_PROPERTY(qreal gauge1 READ gauge1 NOTIFY gauge1Changed)
Q_PROPERTY(quint8 gauge2 READ gauge2 NOTIFY gauge2Changed)
Q_PROPERTY(QString info READ info NOTIFY infoChanged)
Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged)
public:
explicit Client(QObject *parent = nullptr);
Q_INVOKABLE void boostConnection();
Q_INVOKABLE void sayHiToServer();
bool connected() const;
qreal gauge1() const;
quint8 gauge2() const;
QString info() const;
void StartConnect(const QBluetoothDeviceInfo &device);
void setInfo(QString info);
signals:
void gauge1Changed(); // The QML part will be interested
void gauge2Changed(); // The QML part will be interested
void infoChanged();
void connectedChanged();
private:
void setConnected(bool value);
void setGauge1(qreal value);
void setGauge2(quint8 value);
void serviceDiscovered(const QBluetoothUuid &);
void servicescanDone();
void gotError(QLowEnergyController::Error err);
void gotServiceError(QLowEnergyService::ServiceError err);
void serviceStateChanged(QLowEnergyService::ServiceState s);
void characteristicRead(const QLowEnergyCharacteristic &c,
const QByteArray &value);
void updateDataValues(const QLowEnergyCharacteristic &c,
const QByteArray &value);
void countTraffic();
void pollData();
qreal m_gauge1;
quint8 m_gauge2;
QString m_info;
bool m_connected;
bool m_pendingRead;
QBluetoothLocalDevice m_localDevice;
QLowEnergyController *m_control = nullptr;
QLowEnergyService *m_service = nullptr;
QLowEnergyDescriptor m_notificationDesc;
int m_recieveCounter=0;
int m_beginRecieveCounter=0;
bool m_serviceDiscovered = false;
int m_debugCounter = 0;
int m_sayHiCounter = 0;
int m_readTimeoutCounter = 0;
};
#endif // CLIENT_H
```
Important parts of cpp file
```c++
#include "client.h"
#include
#include
#include
#include
#include
#include
Client::Client(QObject *parent) : QObject(parent)
{
m_control = nullptr;
m_gauge1 = 0;
m_gauge2 = 0;
m_info = "Client information";
m_connected = false;
m_pendingRead = false;
m_localDevice.powerOn();
m_localDevice.setHostMode(QBluetoothLocalDevice::HostDiscoverable);
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &Client::pollData);
timer->start(10);
QTimer *timer2 = new QTimer(this);
connect(timer2, &QTimer::timeout, this, &Client::countTraffic);
timer2->start(1000);
}
void Client::sayHiToServer()
{
if (m_connected)
{
QStringList lst = {"Hi", "Hello","Godday","Morning","Whats up"};
int choose = QRandomGenerator::global()->bounded(0, lst.length());
QString str = QVariant(m_sayHiCounter++).toString() + " " + lst.at(choose);
qDebug() << "Say hi to server: " << str;
// Write string to server writeVariable
QLowEnergyCharacteristic characteristic = m_service->characteristic(ccWriteVariableCharacteristicString);
QByteArray arr = str.toUtf8();
m_service->writeCharacteristic(characteristic, arr, QLowEnergyService::WriteWithoutResponse);
}
}
qreal Client::gauge1() const
{
return m_gauge1;
}
quint8 Client::gauge2() const
{
return m_gauge2;
}
QString Client::info() const
{
return m_info;
}
bool Client::connected() const
{
return m_connected;
}
void Client::StartConnect(const QBluetoothDeviceInfo &device)
{
// Init
m_control = QLowEnergyController::createCentral(device);
// Connect some interesting parts
connect(m_control, &QLowEnergyController::discoveryFinished,
this, &Client::servicescanDone);
connect(m_control, &QLowEnergyController::serviceDiscovered,
this, &Client::serviceDiscovered);
connect(m_control, static_cast(&QLowEnergyController::error),
this, &Client::gotError);
connect(m_control, &QLowEnergyController::connected, this, [this]() {
setInfo("Controller connected. Search services...");
m_control->discoverServices();
});
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
m_serviceDiscovered = false;
setInfo("LowEnergy controller disconnected");
});
connect(m_control, &QLowEnergyController::connectionUpdated, this, [this](const QLowEnergyConnectionParameters para) {
// Never gets called?
qDebug() << "connectionUpdated: " << para.minimumInterval();
setInfo("connectionUpdated");
});
// Try to connect
qDebug() << "Connecting to device!!";
m_control->connectToDevice();
}
void Client::serviceDiscovered(const QBluetoothUuid &uuid)
{
// Check order
qDebug() << __PRETTY_FUNCTION__;
qDebug() << uuid;
}
void Client::servicescanDone()
{
qDebug() << __PRETTY_FUNCTION__;
// Delete old service if available
if (m_service) {
delete m_service;
m_service = nullptr;
}
m_service = m_control->createServiceObject(ccReadVariableService, this);
if (m_service)
{
connect(m_service, &QLowEnergyService::characteristicChanged, this, &Client::updateDataValues);
connect(m_service, &QLowEnergyService::stateChanged, this, &Client::serviceStateChanged);
connect(m_service, &QLowEnergyService::characteristicRead, this, &Client::characteristicRead);
connect(m_service, static_cast(&QLowEnergyService::error), this, &Client::gotServiceError);
m_service->discoverDetails();
}
else
{
setInfo("m_service null");
}
}
void Client::gotError(QLowEnergyController::Error err)
{
qDebug() << err;
setInfo("Failed to connect");
}
void Client::gotServiceError(QLowEnergyService::ServiceError err)
{
qDebug() << "Service error: " << err;
}
void Client::serviceStateChanged(QLowEnergyService::ServiceState s)
{
if (s == QLowEnergyService::ServiceDiscovered)
{
m_serviceDiscovered = true;
setConnected(true);
// Send notification start, needed for a characteristic with Notify
const QLowEnergyCharacteristic hrChar = m_service->characteristic(ccReadVariableCharacteristicGauge2);
m_notificationDesc = hrChar.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
if (m_notificationDesc.isValid())
m_service->writeDescriptor(m_notificationDesc, QByteArray::fromHex("0100"));
else
setInfo("m_notificationDesc not valid");
}
else
{
setConnected(false);
}
}
void Client::setGauge1(qreal value)
{
if (value != m_gauge1)
{
m_gauge1 = value;
emit gauge1Changed();
}
}
void Client::setGauge2(quint8 value)
{
if (value != m_gauge2)
{
m_gauge2 = value;
}
}
void Client::characteristicRead(const QLowEnergyCharacteristic &c, const QByteArray &value)
{
// I guess this is the answer from our polling of gauge1
if (c.uuid() == ccReadVariableCharacteristicGauge1)
{
// Only gets called once when connected (a read variable), but also if request read
setGauge1(value.toDouble());
m_recieveCounter++;
m_pendingRead = false;
}
}
void Client::updateDataValues(const QLowEnergyCharacteristic &c, const QByteArray &value)
{
// Depending on the type of c... update correct value
if (c.uuid() == ccReadVariableCharacteristicGauge2)
{
// Gets called everytime the value changes via notify, but not when first connected
setGauge2((quint8)value[0]);
}
}
void Client::countTraffic()
{
// Timer calls this once a second
if (m_recieveCounter > 0)
qDebug() << "Recieve count " << m_recieveCounter << " begin count " << m_beginRecieveCounter;
m_recieveCounter = 0;
m_beginRecieveCounter = 0;
}
void Client::pollData()
{
// if connected.. read data here
if (m_serviceDiscovered)
{
if (m_pendingRead == false || m_readTimeoutCounter > 100)
{
const QLowEnergyCharacteristic char1 = m_service->characteristic(ccReadVariableCharacteristicGauge1);
m_service->readCharacteristic(char1);
m_pendingRead = true;
m_beginRecieveCounter++;
m_readTimeoutCounter = 0;
}
else
m_readTimeoutCounter++;
}
}
void Client::setInfo(QString info)
{
if (info != m_info)
{
m_info = info;
emit infoChanged();
}
}
void Client::setConnected(bool value)
{
if (value != m_connected)
{
m_connected = value;
emit connectedChanged();
}
}
```
## D-Bus abstraction with Qt
If you have an XML-file describing a D-Bus interface you can use a
special tool called qdbusxml2cpp for generating a cpp class which
contains Qt signal and slots for the interface.
Generating the XML can be done via the dbus-send tool using this syntax.
```
dbus-send --session --print-reply --dest=service.name /obj/path org.freedesktop.DBus.Introspectable.Introspect > name.xml
```
# Bluetoothctl
Bluetoothctl is a command line tool to interact with the bluetoothd daemon and is
very useful for debugging and testing different scenarios. You can
access special menus for *gatt*, *scan* and *advertise,* which are used
for Bluetooth LE.

## Advertise menu

## Scan menu

## Gatt menu

# Obexctl
Obexctl is a command line tool for interacting with the obexd daemon which is used
for sending/receiving files via Bluetooth.

Note: Obex is not supported on Apple products, at least not iPhone.
# Further reading
1. [Introduction to Bluetooth Classic](https://www.argenox.com/library/bluetooth-classic/introduction-to-bluetooth-classic/)
2. [Introduction to Bluetooth LE](https://www.argenox.com/library/bluetooth-low-energy/introduction-to-bluetooth-low-energy-v4-0/)
3. [Introduction to Bluetooth LE - Developer Help](https://microchipdeveloper.com/wireless:ble-introduction)
4. [Qt D-Bus](https://doc.qt.io/qt-5/qtdbus-index.html)
5. [Performance Evaluation of Bluetooth LE: A Systematic Review](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5751532/)
6. [Pulseaudio and oFono](https://01.org/ofono)
7. [PulseAudio under the hood](https://gavv.github.io/articles/pulseaudio-under-the-hood/)
8. [Latest Bluez D-BUS API](https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc)
9. [Qt D-Bus chat example](https://doc.qt.io/qt-5/qtdbus-chat-example.html)