Pull to refresh

Разработка для Sailfish OS: Работа с Bluetooth

Reading time16 min
Views4K
Здравствуйте! Данная статья является продолжением цикла статей, посвященных разработке для мобильной платформы 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 или группе ВКонтакте.

Автор: Сергей Аверкиев
Tags:
Hubs:
Total votes 5: ↑5 and ↓0+5
Comments0

Articles