Здравствуйте! Данная статья является продолжением цикла статей, посвященных разработке для мобильной платформы Sailfish OS. На этот раз речь пойдёт об использовании Bluetooth для установки соединения между двумя устройствами и передачи данных.

Bluetooth


Технология Bluetooth позволяет создавать беспроводное соединение, благодаря которому возможна передача абсолютно любых данных. Существует целый ряд задач для которых Bluetooth является общепринятым решением, например, передача файлов с одного устройства на другое, подключение к Bluetooth гарнитурам или дистанционное управление сканерами и принтерами.

Приложение-пример


Будем рассматривать использование технологии Bluetooth на примере реализации приложения для обмена строками. Назовём его bluetooth-messenger. Приложение будет работать в двух режимах: сервер и клиент. Сервер будет регистрировать Bluetooth сервис и реагировать на подключение клиента. Клиент же будет искать созданный сервис, подключаться к нему и передавать данные. Как следствие, необходимо наличие двух устройств, работающих под управлением Sailfish OS.

В итоге приложение будет работать следующим образом:

  1. Клиент ищет сервер с зарегистрированным сервисом.
  2. Найденному серверу передаёт строку.
  3. Сервер принимает строку, отображает её на экране.
  4. Принятая строка разворачивается и передаётся обратно клиенту.
  5. Клиент отображает развёрнутую строку на экране и отключается от сервера.


Реализация данного приложения позволит полностью осветить необходимые инструменты для установки связи между двумя устройствами и обмена данными между ними.

Предоставление повышенных привилегий приложению


Для взаимодействия с Bluetooth приложению иногда требуются повышенные привилегии (для поиска сервисов, изменения настроек видимости, для сопряжения устройств). При отсутствии повышенных привилегий часть функционала будет недоступна, поэтому рекомендуем выдавать их приложению, работающему с Bluetooth.

Для отладки необходимо запускать приложение с помощью devel-su с флагом -p. Это позволяет запускать приложение с повышенными привилегиями, а отладочный вывод будет доступен в консоли.

devel-su -p /usr/bin/bluetooth-messenger

Для того, чтобы запустить приложение с повышенными привилегиями по нажатию на иконку, необходимо сделать некоторые настройки в исходных файлах проекта. Во-первых, исполняемый файл приложения нужно запускать с помощью invoker. Invoker находит главную функцию приложения и запускает её с переданными ему аргументами. Это настраивается в .desktop файле проекта следующей строчкой:

Exec=invoker --type=silica-qt5 -s /usr/bin/bluetooth-messenger

Во-вторых, необходимо создать файл с названием, соответствующим названию исполняемого файла, в директории /usr/share/mapplauncherd/privileges.d/ и поместить туда строчку:

/usr/bin/bluetooth-messenger,

Запятая в конце строчки обязательно нужна. Таким образом при нажатии на иконку приложения, пользователь запустит его с повышенными привилегиями.

Управление состоянием Bluetooth


Для начала необходимо понять, каким образом возможно управлять состоянием Bluetooth. Для этого следует использовать систему D-Bus, взаимодействие с которой было описано в одной из предыдущих статей. С помощью данной системы мы имеем возможность включать и выключать питание Bluetooth и настраивать видимость для других устройств.

Для включения Bluetooth необходимо использовать сервис net.connman. На интерфейсе net.connman по пути /net/connman/technology/bluetooth есть метод SetProperty, с помощью которого возможно установить значение свойства Powered, которое отвечает за то включен Bluetooth или нет. Устанавливается свойство следующим образом:

QDBusInterface bluetoothInterface("net.connman", "/net/connman/technology/bluetooth",
  "net.connman.Technology", QDBusConnection::systemBus(), this);
bluetoothInterface.call("SetProperty", "Powered", QVariant::fromValue(QDBusVariant(true)));

Создаём экземпляр QDBusInterface с использованием перечисленных ранее сервиса, пути и интерфейса. Затем на интерфейсе вызываем метод SetProperty с двумя аргументами: названием свойства и значением.

После включения Bluetooth будет полезно настроить видимость для других устройств. Для этого используем сервис org.bluez. Во-первых необходимо получить путь, соответствующий текущему устройству. Для этого по корневому пути на интерфейсе org.bluez.Manager вызываем метод DefaultAdapter, содержащий в выходных аргументах путь к текущему адаптеру, который впоследствии мы будем использовать для установки видимости.

QDBusInterface adapterListInterface("org.bluez", "/", "org.bluez.Manager",
  QDBusConnection::systemBus(), this);
QVariant adapterPath = adapterListInterface.call("DefaultAdapter").arguments().at(0);

После получения пути для установки видимости необходимо использовать метод SetProperty на интерфейсе org.bluez.Adapter для установки следующих свойств:

  • DiscoverableTimeout – время в секундах (unsigned int), в течении которого устройство будет возможно обнаружить после включения обнаружения. Если установлено значение 0, то обнаружение запускается без таймера.
  • Discoverable – в зависимости от значения true или false включает или выключает обнаружение.

Неограниченное по времени обнаружение включаем следующими строчками:

QDBusInterface bluetoothAdapter("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)));

Следует заметить, что для настройки опций видимости необходимо, чтобы приложение было запущено с повышенными привилегиями.

Регистрация сервиса Bluetooth


Для начала нам необходимо создать сервер и зарегистрировать на нём сервис. Данный сервис будет принимать сообщения от клиентов и отвечать им. Для решения данной задачи мы создадим класс MessengerServer, заголовочный файл которого будет содержать следующее:

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();
};

Теперь более детально рассмотрим компоненты и содержание методов данного класса.

Устройство может оповестить другие устройства, осуществляющие поиск по Bluetooth путём регистрации сервиса. Для этого используется класс QBluetoothServer. С его помощью можно создать Bluetooth сервер и зарегистрировать на нём сервис, который будет сообщать устройствам, что он из себя представляет.

QBluetoothServer содержит набор методов для установки сервера на устройстве и регистрации сервиса. В частности представляют интерес:

  • Конструктор QBluetoothServer(QBluetoothServiceInfo::Protocol serverType, QObject* parent) – служит для инициализации сервера, принимает в качестве аргументов протокол и родительский QObject. В нашем примере будем использовать протокол RFCOMM.
  • Метод listen(const QBluetoothAddress& address, quint16 port) – начинает прослушку входящих подключений по переданным адресу и порту.
  • Сигнал error(QBluetoothServer::Error error) – вызывается при возникновении ошибок сервера (Bluetooth выключен, сервис уже зарегистрирован и др.), где в качестве аргумента доступна сама ошибка.
  • Сигнал newConnection() – вызывается при новом запросе на подключение.

Остальные методы служат для остановки сервера, получения текущего адреса или порта, проверки статуса и другие. Они не настолько интересны, и о них вы можете почитать в официальной документации.

После того как мы подняли сервер, необходимо зарегистрировать сервис. Сервис представляет собой описание какой-либо службы, выполняющей определённые обязанности. Описывается сервис с помощью объекта QBluetoothServiceInfo путём установки атрибутов выделенными методами. Для решения поставленной выше задачи используем метод startServer():

bluetoothServer = new QBluetoothServer(QBluetoothServiceInfo::RfcommProtocol, this);
connect(bluetoothServer, &QBluetoothServer::newConnection,
  this, &MessengerServer::clientConnected);
QBluetoothAddress bluetoothAddress = QBluetoothLocalDevice().address();
bluetoothServer->listen(bluetoothAddress);

Первой строчкой мы создаём сервер, который в качестве протокола использует RFCOMM. Затем соединяем сигнал о новом подключении со слотом нашего класса. После этого включаем прослушивание на нашем адресе, для чего создаём экземпляр текущего устройства, из которого извлекаем его адрес и передаём методу listen(). Таким образом мы устанавливаем сервер.

Регистрация сервиса требует большего количества кода для указания всех требующихся для его работы параметров:

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));

Здесь устанавливаем название сервиса, описание, поставщика сервиса (например название компании) и уникальный идентификатор (в данном приложении содержится в константе в виде строки и задаётся в формате xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, где x – это шестнадцатеричное число). Первые три атрибута позволяют получить базовое представление о найденном сервисе в то время как четвёртый может использоваться устройствами для поиска конкретного сервиса.

QBluetoothServiceInfo::Sequence classId;
classId << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::SerialPort));
serviceInfo.setAttribute(QBluetoothServiceInfo::BluetoothProfileDescriptorList, classId);
classId.prepend(QVariant::fromValue(QBluetoothUuid(SERVICE_UUID)));
serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId);

Конструкция подобного рода использует последовательность (QBluetoothServiceInfo::Sequence) для установки прочих атрибутов. В данном случае мы устанавливаем уникальный идентификатор сервиса. Таким образом сервер даёт знать о том, какие сервисы он предоставляет.

QBluetoothServiceInfo::Sequence publicBrowse;
publicBrowse << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::PublicBrowseGroup));
serviceInfo.setAttribute(QBluetoothServiceInfo::BrowseGroupList, publicBrowse);

Данными строчками устанавливаем группу общедоступного поиска, что позволит устройствам свободно находить данный сервис. В противном случае сервис найден не будет.

QBluetoothServiceInfo::Sequence protocol;
QBluetoothServiceInfo::Sequence protocolDescriptorList;
protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Rfcomm))
  << QVariant::fromValue(quint8(bluetoothServer->serverPort()));
protocolDescriptorList.append(QVariant::fromValue(protocol));
serviceInfo.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList);

Здесь для доступа к сервису устанавливаем протокол RFCOMM, аналогично используемому сервером.

serviceInfo.registerService(bluetoothAddress);

Наконец, выполняем регистрацию созданного сервиса на адресе, полученном ранее и используемом сервером. С этого момента сервис будет виден при поиске по Bluetooth другими устройствами.

Работа с входящими соединениями


Теперь, когда сервис зарегистрирован и приложение готово принимать входящие соединения, необходимо их обрабатывать. Как упоминалось ранее, сервер приложения должен принимать строку от клиента, разворачивать её и отправлять обратно.

При подключении клиента к серверу мы создаём сокет, представленный в виде экземпляра QBluetoothSocket, который можно получить вызвав метод nextPendingConnection() на экземпляре класса QBluetoothServer. У сокета есть целый набор сигналов, позволяющих отследить его состояние, наиболее полезными из которых являются:

  • connected() – вызывается при создании соединения по сокету.
  • disconnected() – вызывается при разрыве соединения.
  • error(QBluetoothSocket::SocketError error) – вызывается при возникновении ошибки, в качестве аргумента передаётся её тип.
  • readyRead() – вызывается, когда в сокете доступны новые данные для чтения.

Используем их для обработки входящих соединений. Ранее мы прикрепили сигнал newConnection() к слоту clientConnected(), рассмотрим его реализацию.

void MessengerServer::clientConnected() {
  //...
  socket = bluetoothServer->nextPendingConnection();
  connect(socket, &QBluetoothSocket::readyRead, this, &MessengerServer::readSocket);
  connect(socket, &QBluetoothSocket::disconnected, this,  &MessengerServer::clientDisconnected);
}

Объект QBluetoothSocket является наследником QIODevice, как следствие ему доступны методы для чтения линии, символа, выбранного количества символов и т.п. Методы для чтения (как и методы для записи) используют QByteArray, что позволяет передавать не только строки, но и любые другие данные в виде набора байтов. Таким образом возможна передача любых типов данных вне зависимости от содержимого.

В нашем примере для обработки входящих сообщений мы соединили сигнал readyRead() с методом readSocket(), код которого выглядит следующим образом:

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());
}

Для чтения данных в виде массива байтов мы используем метод readLine(), после чего преобразуем считанную линию в строку, разворачиваем её и отправляем обратно с помощью метода write(), преобразовав обратно в массив байтов. Таким образом реализованный нами сервер способен получать строку от любого другого устройства по Bluetooth и возвращать её обратно в развёрнутом виде.

Поиск сервиса


Теперь, когда сервер реализован, запущен и ждёт входящих соединений, необходимо к нему подключиться. Каким образом возможно найти устройство, которое предоставляет необходимый сервис? Во-первых, необходимо произвести поиск сервисов, доступных на видимых Bluetooth устройствах и только затем к нему подключаться.

Заголовочный файл клиента имеет следующее содержание:

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();
};

Рассмотрим компоненты, которые необходимы для реализации поиска сервиса и отправки сообщений.

Для поиска сервисов библиотека Qt предоставляет класс QBluetoothServiceDiscoveryAgent. Он позволяет автоматически проверить все устройства на наличие определённого сервиса, который мы ищем по UUID. В дальнейшем, при нахождении сервиса объект данного класса инициирует соответствующий сигнал, с помощью которого мы можем обработать результат поиска. Следует заметить, что использование данного класса требует, чтобы приложение было запущено с повышенными привилегиями. Класс содержит следующие интересующие нас методы:

  • setUuidFilter(const QBluetoothUuid &uuid) – устанавливает UUID, сервис с которым требуется найти. Также есть аналогичный метод для установки нескольких UUID.
  • setRemoteAddress(const QBluetoothAddress &address) – устанавливает адрес устройства, на котором необходимо найти сервис. Может использоваться, если точно известен адрес устройства, которое необходимо найти.
  • start() – запускает поиск сервисов.
  • stop() – останавливает поиск сервисов.
  • discoveredServices() – возвращает список найденных сервисов.
  • clear() – очищает список найденных сервисов.

Для обработки результата полезны сигналы:

  • serviceDiscovered(const QBluetoothServiceInfo &info) – вызывается при обнаружении сервиса, информация о нём передаётся с аргументом.
  • finished() – вызывается при завершении поиска.
  • error(QBluetoothServiceDiscoveryAgent::Error error) – вызывается при возникновении ошибок.

Для поиска нашего конкретного сервиса необходимо установить методом setUuidFilter() фильтр по UUID, который мы указывали при регистрации сервиса и методом start() начать поиск. После этого при обнаружении нашего сервиса будет инициирован сигнал serviceDiscovered(). Экземпляр QBluetoothServiceInfo содержит информацию о найденном сервисе (имя, UUID, информация об устройстве, на котором он зарегистрирован и т.п.). Экземпляр данного класса мы будем использовать для подключения к сервису, о чём будет упомянуто вдальнейшем.

Конкретно в нашем примере будем рассматривать другой класс, не требующий повышенных привилегий – QBluetoothDeviceDiscoveryAgent. С его помощью возможен поиск устройств, а не сервисов, и он не требует повышенных привилегий. Для каждого найденного устройства будем просматривать сервисы, зарегистрированные на устройстве, и если в списке есть наш сервис, то считаем сервис найденным и в дальнейшем будем подключаться к нему.

QBluetoothDeviceDiscoveryAgent состоит из небольшого числа методов для поиска устройств. Наиболее полезными являются следующие:

  • start() – начинает поиск устройств.
  • stop() – останавливает поиск устройств.
  • discoveredDevices() – возвращает список всех найденных устройств.
  • error() – возвращает тип последней возникшей при поиске ошибки. Также есть сигнал, который будет инициирован сразу после возникновения ошибки с типом ошибки в качестве аргумента.
  • errorText() – возвращает текст последней возникшей ошибки.

Также в случае нахождения устройства будет немедленно инициирован сигнал deviceDiscovered(const QBluetoothDeviceInfo &info), который может служить для обработки результата.

Информация о найденных устройствах представлена в виде объекта QDeviceInfo. Из данного объекта можно извлечь данные с помощью специальных методов. Наиболее интересными являются следующие:

  • address() – mac-адрес найденного устройства. Используется при поиске любых устройств кроме macOS и iOS.
  • deviceUuid() – уникальный идентификатор найденного устройства. Используется только при поиске устройств на macOS и iOS.
  • name() – имя найденного устройства.
  • serviceUuids() – список уникальных идентификаторов зарегистрированных сервисов.

Теперь, когда мы знаем как искать сервисы на Bluetooth устройствах попробуем найти наш собственный сервис. В конструкторе инициализируем объект для поиска устройств:

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);
  //...
}

Во-первых, создаём экземпляр QBluetoothDeviceDiscoveryAgent, которому в качестве аргумента передаём адрес текущего Bluetooth устройства. Затем присоединяем два сигнала объекта к нашему текущему: deviceDiscovered() для обработки нового найденного устройства и finished() для обработки завершения поиска.

Метод для начала поиска содержит следующие строчки:

void MessengerClient::startDiscovery(const QString &messageToSend) {
  //...
  this->message = messageToSend;
  discoveryAgent->start();
  //...
}

Здесь мы сохраняем сообщение, которое необходимо передать и начинаем поиск устройств.

Для обработки найденных устройств используется слот deviceDiscovered(), к которому ранее мы уже подключили сигнал:

void MessengerClient::deviceDiscovered(const QBluetoothDeviceInfo &deviceInfo) {
  //...
  if (deviceInfo.serviceUuids().contains(QBluetoothUuid(SERVICE_UUID))) {
    emit clientStatusChanged(QStringLiteral("Device found"));
    discoveryAgent->stop();
    requestPairing(deviceInfo.address());
  }
}

Как упоминалось ранее мы смотрим на список уникальных идентификаторов сервисов для поиска в нём нашего зарегистрированного. При первом найденном устройстве, которое предоставляет искомый сервис, мы завершаем поиск и вызываем метод для установления сопряжения между устройствами.

Сопряжение устройств


Сопряжение устройств является важным аспектом во взаимодействии устройств с помощью Bluetooth. Это подразумевает собой, что два устройства устанавливают между собой доверительные отношения и им доступен более широкий спектр возможностей взаимодействия (например, дистанционное управление). Конкретно в нашем примере сопряжение не требуется, но мы установим его, чтобы разобрать как это делается в общем случае. Установка сопряжения требует повышенных привилегий.

Для сопряжения устройств используется класс QBluetoothLocalDevice. Мы уже использовали его ранее в коде серверной части для получения адреса текущего устройства. Он же используется и для сопряжения устройств. Нас интересуют методы:

  • pairingStatus(const QBluetoothAddress &address) – позволяет получить статус сопряжения между текущим устройством и устройством по адресу. Возвращает одно из значений:
  • requestPairing(const QBluetoothAddress &address, Pairing pairing) – запрашивает изменения статуса сопряжения с устройством (вторым аргументом передаётся Paired для установки сопряжения или Unpaired для разрыва).

и сигналы:

  • pairingFinished(const QBluetoothAddress &address, QBluetoothLocalDevice::Pairing pairing) – возвращается при успешном изменении статуса сопряжения.
  • error(QBluetoothLocalDevice::Error error) – возвращается при ошибке изменения статуса сопряжения (в том числе отмена предложения о сопряжении на одном из устройств).

Адрес удалённого устройства мы можем получить с помощью вызова метода address() на экземпляре QBluetoothDeviceInfo, в дальнейшем будем использовать его при установке сопряжения и подключения к сервису. Теперь попробуем установить сопряжение между двумя устройствами. Для начала добавим подключение к сигналам в конструктор класса клиента:

connect(&localDevice, &QBluetoothLocalDevice::pairingFinished,
  this, &MessengerClient::pairingFinished);
connect(&localDevice, &QBluetoothLocalDevice::error, this, &MessengerClient::pairingError);

Экземпляр QBluetoothLocalDevice в данном случае является полем класса. Слот pairingFinished() содержит строчку, запускающую клиент startClient(address), а pairingError() – отладочный вывод.

Для установки сопряжения мы реализовали метод requestPairing() со следующим содержанием:

void MessengerClient::requestPairing(const QBluetoothAddress &address) {
  //...
  if (localDevice.pairingStatus(address) == QBluetoothLocalDevice::Paired) {
    startClient(address);
  } else {
    localDevice.requestPairing(address, QBluetoothLocalDevice::Paired);
  }
}

Если устройства уже сопряжены, то просто инициируем подключение к серверу, в противном случае запрашиваем сопряжение. В результате при успешном установлении сопряжения также инициируется подключение к серверу, а при ошибке уведомляем пользователя о проблеме.

Подключение к серверу


Экземпляр класса QBluetoothDeviceInfo, соответствующий найденному устройству, содержит метод для получения адреса, которого достаточно для подключения к сервису. Для этого используется QBluetoothSocket, достаточно создать экземпляр данного класса с помощью конструктора, передав ему протокол RFCOMM и вызвать метод connectToService(), которому в качестве аргументов передаётся адрес из экземпляра QBluetoothDeviceInfo и порт, по которому необходимо установить соединение. Чтобы установить соединение с сервисом, необходимо указывать порт 1.

Теперь рассмотрим процесс установки соединения, передачи и приёма данных с помощью сокета. В клиенте используется тот же самый QBluetoothSocket что и на сервере, что позволяет нам использовать рассмотренные ранее сигналы для реализации обработчиков и методы для записи данных в сокет. Метод startClient() устанавливает соединение с устройством, предоставляющим сервис, с помощью сокета:

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);
}

Создаём экземпляр сокета с протоколом RFCOMM и подключаем его сигналы к слотам нашего класса. Затем вызовом метода connectToService() подключаемся к другому устройству. Следует заметить, что если бы мы использовали класс QBluetoothServiceInfo, который позволяет получить информацию о найденных сервисах в виде экземпляров QBluetoothServiceInfo, то достаточно было бы вызвать метод connectToService() с одним аргументом, принимающим информацию о сервисе.

Метод socketConnected() вызывается при установке подключения по сокету, внутри него мы отправляем данные на сервер:

void MessengerClient::socketConnected() {
  //...
  socket->write(message.toUtf8());
}


Здесь используется тот же самый класс сокета, что и на сервере, так что мы можем передавать любые данные в виде массива байтов.

Как мы помним, код сервера позволяет получить строку, развернуть её и вернуть нам, для обработки входящего сообщения мы соединили слот readSocket() с сигналом readyRead(). Выглядит этот слот следующим образом:

void MessengerClient::readSocket() {
  //...
  QString receivedMessage = QString::fromUtf8(socket->readLine().trimmed());
  emit messageReceived(receivedMessage);
}

Результат


В итоге мы покрыли большую часть функционала, требующегося для реализации сервера и клиента для передачи данных любого рода с между ними. Также рассмотрели процедуру поиска устройств. Материала, упомянутого в статье, достаточно для реализации передачи любых данных между двумя устройствами. Код приложения-примера доступен на GitHub.

Технические вопросы можно также обсудить на канале русскоязычного сообщества Sailfish OS в Telegram или группе ВКонтакте.

Автор: Сергей Аверкиев

Комментарии (0)