Developing for Sailfish OS: Working Bluetooth
the
Bluetooth
Bluetooth technology allows you to create a wireless connection, it's possible to transfer any data. There are a number of tasks for which the Bluetooth is a common decision, for example, transfer files from one device to another, connect to Bluetooth headsets or remote control scanners and printers.
the
a sample Application
We will consider the use of Bluetooth technology on the example of realization of the application for the exchange lines. Let's call it bluetooth messenger. The app will work in two modes: server and client. The server will register the Bluetooth service and to respond to the client connection. The client will look for the created service to connect to it and transfer data. As a result, you must have two devices running Sailfish OS.
![]() |
![]() |
In the end, the app will work as follows:
-
the
- the Client looks for the server with the registered service. the
- Found the server sends a string. the
- the Server takes a string, displays it on the screen. the
- Accepted line is unfolding, and is transmitted back to the client. the
- the Client displays the detailed line on the screen and disconnects from the server.
![]() |
![]() |
The implementation of this application will allow full coverage of the necessary tools to establish communication between two devices and exchange data between them.
the
Granting elevated privileges to the application
To communicate with the Bluetooth application sometimes require elevated privileges (to search for services, change the visibility settings for the pairing devices). In the absence of elevated part of the functionality will not be available, so please give them an application running with Bluetooth.
To debug you must start the application by using the devel-su with the flag p. This allows you to run the application with elevated privileges, and debugging output will be available in the console.
the
devel-su -p /usr/bin/bluetooth-messenger
In order to run the application with elevated privileges by clicking on the icon, you need to make some settings in the source files of the project. First, the application executable need to run via the invoker. Invoker finds the main function of the application and runs it with arguments passed to it. This is configured in .desktop the project file the following line:
the
Exec=invoker --type=silica-qt5 -s /usr/bin/bluetooth-messenger
Secondly, you need to create a file with a name corresponding to the name of the executable file in the directory /usr/share/mapplauncherd/privileges.d/ and put the line:
the
/usr/bin/bluetooth-messenger
The comma at the end of the lines necessarily need. So when you click on the application icon, the user will run it with elevated privileges.
the
Manage Bluetooth state
First we need to understand how it is possible to control the state of Bluetooth. To do this, use the D-Bus system, the interaction with which is described in a previous article. Using this system, we have the ability to turn on and off Bluetooth and set the visibility to other devices.
To activate Bluetooth, you need to use the service net.connman. On the interface net.connman at /net/connman/technology/bluetooth has a method SetProperty, by which it is possible to set the value of the property Powered, which is responsible for the Bluetooth enabled or not. Sets the property as follows:
the
QDBusInterface bluetoothInterface("net.connman", "/net/connman/technology/bluetooth"
"net.connman.Technology", QDBusConnection::systemBus(), this);
bluetoothInterface.call("SetProperty", "Powered", QVariant::fromValue(QDBusVariant(true)));
Create an instance of QDBusInterface using the previously listed service, path and interface. Then in the interface method to call SetProperty with two arguments: the property name and value.
After turning on Bluetooth will be useful to set the visibility to other devices. For this we use the service org.bluez. First, you must obtain the path corresponding to the current device. To do this, the root path on the interface org.bluez.Manager called method DefaultAdapter containing the output arguments of the current adapter, which later we will use to set the visibility.
the
adapterListInterface QDBusInterface("org.bluez", "/", "org.bluez.Manager",
QDBusConnection::systemBus(), this);
QVariant adapterPath = adapterListInterface.call("DefaultAdapter").arguments().at(0);
After receiving the way to set the visibility you must use the method SetProperty on the interface org.bluez.Adapter to set the following properties:
the
DiscoverableTimeout – time in seconds (unsigned int), during which the device will be possible to discover after discovery has been enabled. If set to 0, the detection is triggered without a timer.
Discoverable – depending on true or false enables or disables detection.
Unlimited time detection, include the following lines:
the
bluetoothAdapter QDBusInterface("org.bluez", adapterPath.value < QDBusObjectPath > ().path(),
"org.bluez.Adapter", QDBusConnection::systemBus(), this);
bluetoothAdapter.call("SetProperty", "DiscoverableTimeout", QVariant::fromValue(QDBusVariant(0U)));
bluetoothAdapter.call("SetProperty", "Discoverable", QVariant::fromValue(QDBusVariant(true)));
It should be noted that for the configuration options of visibility, the application must be started with elevated privileges.
the
Check Bluetooth service
First we need to create a server and register it service. This service will accept messages from clients and reply to them. To solve this problem we create a class MessengerServer, the header file which will contain the following:
the
class MessengerServer : public QObject {
Q_OBJECT
public:
explicit MessengerServer(QObject *parent = 0);
~MessengerServer();
Q_INVOKABLE void startServer();
Q_INVOKABLE void stopServer();
signals:
void messageReceived(QString message);
private:
QBluetoothServer *bluetoothServer;
QBluetoothServiceInfo serviceInfo;
QBluetoothSocket *socket;
const QString SERVICE_UUID = "1f2d6c5b-6a86-4b30-8b4e-3990043d73f1";
private slots:
void clientConnected();
void clientDisconnected();
void readSocket();
};
Now take a closer look at the components and content of the methods of this class.
The device may notify other devices search for Bluetooth through the registration service. This class is used QBluetoothServer. It can be used to create a Bluetooth server and register on the service, which will inform the device that it is.
QBluetoothServer contains a set of methods to install the server on the device and check service. In particular are of interest:
the
-
the
- the Constructor QBluetoothServer(QBluetoothServiceInfo::Protocol serverType, QObject* parent) – used to initialize the server, takes as arguments the Protocol and the parent QObject. In our example we will use the RFCOMM Protocol. the
- the Method listen(const QBluetoothAddress&address, quint16 port) – starts the wiretap inbound connections for the given address and port. the
- Signal error(QBluetoothServer::Error error) is invoked when an error occurs, the server (Bluetooth off, the service is already registered, etc.), where the argument is available the error itself. the
- Signal newConnection() – called when a new connection request.
Other methods are used to stop the server, obtaining the current address or port, check status and others. They are not so interesting and about which you can read in the official documentation.
After we picked up the server, you must register the service. Service is a description of any service, to perform certain duties. Describes the service with the QBluetoothServiceInfo by setting the attributes of the currently selected methods. To solve above problems we use the method startServer():
the
bluetoothServer = new QBluetoothServer(QBluetoothServiceInfo::RfcommProtocol, this);
connect(bluetoothServer, &QBluetoothServer::newConnection,
this, &MessengerServer::clientConnected);
QBluetoothAddress bluetoothAddress = QBluetoothLocalDevice().address();
bluetoothServer->listen(bluetoothAddress);
The first line we create a server that the Protocol uses RFCOMM. Then connect the signal of the new connection with slot in our class. Then turn on listening to our address, which creates an instance of the current device from which the extracted address and transmit to the listen(). Thus, we set the server.
Registration service requires more code to specify all required for its operation parameters:
the
serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceName, "BT message sender");
serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceDescription,
"Example message sender");
serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceProvider, "fruct.org");
serviceInfo.setServiceUuid(QBluetoothUuid(SERVICE_UUID));
Here set the name of the service description, service provider (e.g. company name) and unique identifiers (this Appendix contains the constant string and is specified in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, where x is a hexadecimal number). The first three attributes allow you to obtain a basic understanding about the service while the fourth can be used by devices to search for a particular service.
the
QBluetoothServiceInfo::Sequence classId;
classId << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::SerialPort));
serviceInfo.setAttribute(QBluetoothServiceInfo::BluetoothProfileDescriptorList, classId);
the classId.prepend(QVariant::fromValue(QBluetoothUuid(SERVICE_UUID)));
serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId);
The design of this kind uses a sequence of (QBluetoothServiceInfo::Sequence) to set other attributes. In this case, we set the unique identifier of the service. Therefore, the server lets you know about what services it provides.
the
QBluetoothServiceInfo::Sequence publicBrowse;
publicBrowse << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::PublicBrowseGroup));
serviceInfo.setAttribute(QBluetoothServiceInfo::BrowseGroupList, publicBrowse);
These lines set the group is public search, which will allow the devices to freely find this service. Otherwise, the service will not be found.
the
QBluetoothServiceInfo::Sequence protocol;
QBluetoothServiceInfo::protocolDescriptorList Sequence;
protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Rfcomm))
<< QVariant::fromValue(quint8(bluetoothServer->serverPort()));
protocolDescriptorList.append(QVariant::fromValue(protocol));
serviceInfo.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList);
Here for access to the service, set the RFCOMM Protocol, is similar to that used by the server.
the
serviceInfo.by registerservice(bluetoothAddress);
Finally, we perform the registration of the created service to the address previously acquired and used by the server. Since then, the service will be visible when searching for other Bluetooth devices.
the
incoming links
Now, when the service is registered and the application is ready to accept incoming connections, you need to handle them. As mentioned earlier, the application server needs to accept a string from the client, deploy it and send it back.
When the client connects to the server we create socket, represented as instances of QBluetoothSocket, which can be obtained by calling method nextPendingConnection() class instance QBluetoothServer. The socket has a set of signals that allows you to monitor its status, the most useful of which are:
the
connected() – called when creating a connection on a socket.
disconnected() – called when the connection is broken.
error(QBluetoothSocket::SocketError error) – called when an error occurs in the argument is its type.
readyRead() – called when the socket is new data available for reading.
Use them for incoming connections. Previously, we attached a signal newConnection() to the slot clientConnected(), consider its implementation.
the
void MessengerServer::clientConnected() {
//...
socket = bluetoothServer->nextPendingConnection();
connect(socket, &QBluetoothSocket::disconnected, this, &MessengerServer::clientDisconnected);
}
Object QBluetoothSocket is the heir QIODevice, as a consequence, it is available methods for reading lines, symbols, the selected number of characters, etc. Methods to read (and methods for writing) is used QByteArray that allows to pass not only strings, but also any other data in bytes. It is thus possible to transfer any type of data regardless of the content.
In our example to handle incoming messages, we connect a signal readyRead() method readSocket() whose code looks like the following:
the
void MessengerServer::readSocket() {
//...
const QString message = QString::fromUtf8(socket->readLine().trimmed());
emit messageReceived(message);
QString reversedMessage;
for (int i = message.size() - 1; i > = 0; i--) {
reversedMessage.append(message.at(i));
}
socket- > write(reversedMessage.toUtf8());
}
To read the data in the byte array we use the method readLine(), and then convert the read line to a string, expand it and send it back using the method write(), converting back to a byte array. Thus we have implemented a server capable of receiving a line from any other device via Bluetooth and return it in expanded form.
the
Search
Now that the server is implemented, running and waiting for incoming connections, you must connect to it. How is it possible to find a device that provides the service? First, you need to search for services available on the visible Bluetooth devices and then connect to it.
The header file has the following contents:
the
class MessengerClient : public QObject {
Q_OBJECT
public:
explicit MessengerClient(QObject *parent = 0);
~MessengerClient();
Q_INVOKABLE void startDiscovery(const QString &messageToSend);
Q_INVOKABLE void stopDiscovery();
private:
const QString SERVICE_UUID = "1f2d6c5b-6a86-4b30-8b4e-3990043d73f1";
QString message;
QBluetoothSocket *socket = NULL;
QBluetoothDeviceDiscoveryAgent* discoveryAgent;
QBluetoothDeviceInfo device;
QBluetoothLocalDevice localDevice;
void requestPairing(const QBluetoothAddress &address);
void startClient(const QBluetoothAddress &address);
void stopClient();
signals:
void messageReceived(QString message);
void clientStatusChanged(QString text);
private slots:
void deviceDiscovered(const QBluetoothDeviceInfo &deviceInfo);
void pairingFinished(const QBluetoothAddress &address, QBluetoothLocalDevice::Pairing pairing);
void pairingError(QBluetoothLocalDevice::Error error);
void socketConnected();
void deviceSearchFinished();
void readSocket();
};
Consider the components that are required to implement the search service and send messages.
For search services, the Qt library provides the class QBluetoothServiceDiscoveryAgent. It allows you to automatically test all devices for a specific service that we are looking at the UUID. Further, when the service object of this class initiates the appropriate signal with which we can process the search result. It should be noted that the use of this class requires that the application was launched with elevated privileges. The class contains the following methods are of interest to us:
the
setUuidFilter(const QBluetoothUuid &uuid) – sets the UUID of the service with which you want to find. There is also a similar method to install multiple UUID.
setRemoteAddress(const QBluetoothAddress &address) – sets the address of the device on which you want to find a service. Can be used if you exactly know the address of the device you want to locate.
start() – starts the search for services.
stop() – stop search services.
discoveredServices() – returns a list of found services.
clear() – clears the list of found services.
To handle the result of useful signals:
the
serviceDiscovered(const QBluetoothServiceInfo &info) – called when the discovery service, information about it is passed with the argument.
finished() is invoked at the completion of the search.
error(QBluetoothServiceDiscoveryAgent::Error error) is invoked when an error occurs.
To search for the particular service it is necessary to establish a method setUuidFilter() filter by UUID that we used when registering the service and the method start() to begin the search. After that, when detected, our service will be initiated by signal serviceDiscovered(). An instance of QBluetoothServiceInfo contains information about the service (name, UUID, information about the device on which it is registered, etc.). An instance of this class we will use to connect to the service, which will be referred to hereinafter.
Specifically, in our example, we will consider another class that does not require elevated privileges – QBluetoothDeviceDiscoveryAgent. With its help it is possible to search devices, not services, and it does not require elevated privileges. For each found device will see the services registered on the device, and if there is our service, we believe service is found and will continue to connect to it.
QBluetoothDeviceDiscoveryAgent consists of a small number of methods to search for devices. Most useful are the following:
the
start() – starts the search for devices.
stop() – stops the device search.
discoveredDevices() – returns a list of all devices found.
error() – returns type of the last encountered when searching for errors. There is also a signal that will be triggered immediately after the occurrence of the error with the error type as argument.
errorText() – returns the text of the last error that occurred.
Also in case the device is immediately initiated by signal deviceDiscovered(const QBluetoothDeviceInfo &info), which can be used to handle the result.
Information about the found devices are presented in the form of an object QDeviceInfo. From this object you can extract the data using special techniques. The most interesting are:
the
-
the
- address() – the mac address of the found device. Is used when search for all devices except iOS and macOS.
deviceUuid() – the unique ID of the found device. Only used when searching for devices on macOS and iOS.
name() is the name of the found device.
serviceUuids() – the list of unique identifiers of registered services.
Now that we know how to search for services on the Bluetooth devices try to find our own service. In the constructor initialize the object to search for devices:
the
MessengerClient::MessengerClient(QObject *parent) : QObject(parent) {
//...
discoveryAgent = new QBluetoothDeviceDiscoveryAgent(localDevice.address());
connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered,
this, &MessengerClient::deviceDiscovered);
connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished,
this, &MessengerClient::deviceSearchFinished);
//...
}
First, create an instance of QBluetoothDeviceDiscoveryAgent that as argument to pass the address of the current Bluetooth device. Then join the two signals of the object to our current: deviceDiscovered() to handle the new device is found and finished() to handle the completion of the search.
Method to start the search contains the following lines:
the
void MessengerClient::startDiscovery(const QString &messageToSend) {
//...
this->message = messageToSend;
discoveryAgent- > start();
//...
}
Here we save the message you want to convey and begin searching for devices.
For treatment of found devices, use the slot deviceDiscovered(), to which we have already connected the signal:
the
void MessengerClient::deviceDiscovered(const QBluetoothDeviceInfo &deviceInfo) {
//...
if (deviceInfo.serviceUuids().contains(QBluetoothUuid(SERVICE_UUID))) {
emit clientStatusChanged(QStringLiteral("Device found"));
discoveryAgent->stop();
requestPairing(deviceInfo.address());
}
}
As mentioned earlier we look at the list of unique identifiers of services to search for in it was our. When you first found the device that provides the desired service, we complete the search and call the method for the pairing between the devices.
the
Pairing
Pairing devices is an important aspect in the interoperability of devices using Bluetooth. This implies that the two devices establish a relationship of trust and available to them a wider range of interaction opportunities (e.g., remote control). Specifically, in our example, pairing is not required, but we install it to parse as is done in the General case. Installation pairing requires elevated privileges.
the
pairingStatus(const QBluetoothAddress &address) allows to retrieve the status of the pairing between the current device and the device address. Returns one of the values:
requestPairing(const QBluetoothAddress &address, Pairing pairing) – requests a change of status pairing with a device (the second argument passed to the Paired sensor is paired or Unpaired to break).
and signals:
the
-
the
- pairingFinished(const QBluetoothAddress &address, QBluetoothLocalDevice::Pairing pairing) – is returned upon successful change of the status of the pairing. the
- error(QBluetoothLocalDevice::Error error) – return error when changing the status of the pairing (including the abolition of sentences about the pair on one of the devices).
Address of the remote device we can get by calling method address() instance of QBluetoothDeviceInfo, will continue to use it when you install the pairing and connection to the service. Now let's try to establish the pairing between the two devices. To get started, add a connection to the signals in the constructor of the client class:
the
connect(&localDevice, &QBluetoothLocalDevice::pairingFinished,
this, &MessengerClient::pairingFinished);
connect(&localDevice, &QBluetoothLocalDevice::error, this, &MessengerClient::pairingError);
An instance of QBluetoothLocalDevice in this case is the field class. Slot pairingFinished() contains a line that starts a client startClient(address) and pairingError() debug output.
To install mate, we implemented the method requestPairing() with the following contents:
the
void MessengerClient::requestPairing(const QBluetoothAddress &address) {
//...
if (localDevice.pairingStatus(address) == QBluetoothLocalDevice::Paired) {
startClient(address);
} else {
localDevice.requestPairing(address, QBluetoothLocalDevice::Paired);
}
}
If the device is already paired, simply initiate a connection to the server, otherwise the requested pairing. As a result, when the successful completion of pairing, also triggers a connection to the server, and on error, notify user about the problem.
the
Connect to server
An instance of the class QBluetoothDeviceInfo corresponding to the found device has a method to get the address, which is enough to connect to the service. To do this, use QBluetoothSocket, simply create an instance of this class using the constructor, passing it the RFCOMM Protocol and call the method connectToService(), which as arguments the address of the instance QBluetoothDeviceInfo and the port on which you want to connect. To establish a connection with the service, you must specify the port to 1.
Now consider the pairing, transmitting and receiving data using a socket. The client uses the same QBluetoothSocket on the server that allows us to use the previously discussed signals to implement handlers and methods for writing data to the socket. The method startClient() establishes the connection with the device providing the service, by using the socket:
the
void MessengerClient::startClient(const QBluetoothDeviceInfo &deviceInfo) {
//...
socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol, this);
connect(socket, &QBluetoothSocket::connected, this, &MessengerClient::socketConnected);
connect(socket, &QBluetoothSocket::readyRead, this, &MessengerClient::readSocket);
socket- > connectToService(deviceInfo.address(), 1);
}
Create an instance of the socket with the RFCOMM Protocol and connect its signals to slots in our class. Then calling the method connectToService() to connect to another device. It should be noted that if we used the class QBluetoothServiceInfo, which allows you to obtain information about the found services in the form of copies of QBluetoothServiceInfo, it would suffice to invoke the method connectToService() with one argument, the host information about the service.
The method socketConnected() is invoked when the connection to the socket inside it, we send data to the server:
the
void MessengerClient::socketConnected() {
//...
socket- > write(message.toUtf8());
}
Here we use the same class of the socket on the server, so that we can transmit any data in the byte array.
As we know, the server code allows you to get a row to expand it and return to us for processing incoming messages, we joined the slot readSocket() with the signal readyRead(). This slot looks as follows:
the
void MessengerClient::readSocket() {
//...
QString receivedMessage = QString::fromUtf8(socket->readLine().trimmed());
emit messageReceived(receivedMessage);
}
the
Result
In the end, we had covered most of the functionality required to implement the server and client to transfer data of any kind between them. Also reviewed how search devices. Material, mentioned in the article, it is sufficient to implement the transfer of any data between the two devices. Code of the sample application available at GitHub.
Technical questions can be discussed on channel Russian-speaking community of Sailfish OS in Telegram or Facebook page.
Author: Sergey Averkiev
Комментарии
Отправить комментарий