MyTetra Share
Делитесь знаниями!
О, смотри-ка какое хорошее место. Дайте два!
Работа с Bluetooth средствами Qt
06.05.2018
13:58
Текстовые метки: qt, bluetooth
Раздел: Компьютер - Программирование - Язык C++ - Библиотека Qt - Bluetooth

Аннотация.

При разработке мобильных(и не только) приложений часто возникает необходимость организовать соединение между устройствами. Несмотря на популярность Qt в сфере мобильных разработок, в сети недостаточно информации даже на английском языке. Кроме двух-трёх демонстрационных примеров из официальной документации, сложно что-то найти.

В статье рассматриваются различные способы работы с bluetooth средствами Qt. Рассказывается о возможных проблемах и способах их решения. Надеюсь, кому-то это будет полезно.

Введение.

В настоящее время я занят в проекте в сфере mobile healthcare. В нашем распоряжении есть портативный кардиомонитор (сенсоры + само устройство). Идея такова: пользователь из группы риска постоянно носит портативный кардиомонитор, а наше приложение на смартфоне в фоновом режиме мониторит состояние его здоровья. Дальнейших сценариев много, например, запись истории, или отправка сообщений доктору, или совершение экстренного вызова в случае сердечного приступа. Сейчас готов первый прототип для Symbian OS, имеющий неполный функционал, после завершения проекта планируется перенос и на другие популярные мобильные платформы. В рамках этой статьи я расскажу, как использовать Qt для организации bluetooth-взаимодействия с кардиомонитором. (Прилагается небольшой демо-проект, переделать его под другие цели довольно просто).

Итак, есть кардиомонитор, во включенном состоянии непрерывно отправляющий пакеты с данными, есть C++/Qt приложение. Интерфейс реализуется на QML.


Способ №1.


Использование QML-элементов BluetoothDiscoveryModel и BluetoothSocket.


Этот способ является самым простым, т. к. есть возможность работы с высокоуровневыми элементами в декларативном стиле QML. Если вашему проекту, в котором интерфейс реализуется QML, требуется
bluetooth-соединение, советую попробовать этот способ. К тому же, есть хороший пример, вот тут с ним можно ознакомиться:


http://doc.qt.digia.com/qtmobility/decl … r-qml.html


Почему бы не остановиться на этом? Я был бы рад, если бы было можно. Но, например, в моём случае, этот способ не сработал. Устройство если и находилось (странным образом, не всегда) и появлялось в списке, то с ним не удавалось установить соединение. Да и не всегда используется QML.


Способ №2.


Так как QML-элементы по непонятной причине работать с нашим кардиомонитором не хотели, было принято решение написать на C++/Qt свой bluetooth-плагин. Здесь я столкнулся с проблемой, которая упоминалась в аннотации - доступно очень мало информации. Но я разобрался, и спешу поделиться опытом.

Сначала проверяем, доступен ли вообще bluetooth на устройстве с помощью QBluetoothLocalDevice. Если да, включаем его, делаем его видимым, и сканируем область на наличие устройств, мы находим различные сервисы, предоставляемые активными bluetooth-устройствами.


/**

*   Check if Bluetooth is available on this device,

*   turn bluetooth on, read local device name,

*   make it visible to others, initialize discovery

*   agent to search for services.

*   Then start a service discovery.

*/


void BluetoothModule::startDiscovery()

{

   localDevice = new QBluetoothLocalDevice(this);


   if (localDevice->isValid()) {


       qDebug() << "Bluetooth is available on this device";

       localDevice->setHostMode(QBluetoothLocalDevice::HostDiscoverable);

       localDevice->powerOn();

       localDevice->setHostMode(QBluetoothLocalDevice::HostDiscoverable);

       qDebug() << "Local device: " << localDevice->name() << " ("

                << localDevice->address().toString().trimmed() << ")";

       // Create a discovery agent and connect to its signals

       discoveryAgent = new QBluetoothServiceDiscoveryAgent(this);

       connect(discoveryAgent, SIGNAL(finished()),

               this, SLOT(serviceDiscoverFinished()));

       discoveryAgent->start();

       qDebug() << "Service discover started";

   }

   else

       qDebug() << "Bluetooth is not available on this device";

}


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


/**

*   Service discover finished. Get a list of services.

*   Read information about the found services and print it.

*/


void BluetoothModule::serviceDiscoverFinished()

{

   qDebug() << "Service discover finished";


   listOfServices = discoveryAgent->discoveredDevices();


   if (!(listOfServices.isEmpty())) {

       qDebug() << "Found new services:";

       for(int i = 0; i < listOfServices.size(); ++i)

           qDebug() << "Device: "

                    << listOfServices.at(i).device().name().trimmed()

                    << " ("

                    << listOfServices.at(i).device().address().toString().trimmed()

                    << ") \n"

                    << "Service: "

                    << listOfServices.at(i).serviceName()

                    << ", "

                    << listOfServices.at(i).serviceDescription()

                    << ", "

                    << listOfServices.at(i).serviceProvider();

   }

   else

       qDebug() << "No services found";

}


В списке оказались все сервисы, предоставляемые другими телефонами и ноутбуками, которые оказались неподалеку, но о кардиомониторе никакой информации найдено не было. То есть QBluetoothServiceDiscoveryAgent не находит ни одного сервиса на нашем устройстве. Что ж, это объясняет, почему QML BluetoothSocket (см. выше, способ №1) не подключался к кардиомонитору. Как следует из документации, он базируется как раз на QBluetoothSocket, которому для соединения необходимо указать QBluetoothService. Удивительно то, что QML QBluetoothDiscoveryModel, как я понял, базируется на QBluetoothServiceDiscoveryAgent, но иногда всё же находил кардиомонитор.

Вот мы и пришли к тому, ради чего,  по большому счету,  я и писал эту статью. Из-за особенностей используемого девайса, приходится идти более долгим путем. Так как сервис на кардиомониторе находиться отказывался, пришлось искать способ как-то обойти явный его поиск. Немного модифицировав код, прибегнем к следующему решению: выполним поиск не сервисов, а устройств, их предоставляющийх, с помощью класса QBluetoothDeviceDiscoveryAgent.


/**

*   Check if Bluetooth is available on this device,

*   turn bluetooth on, read local device name,

*   make it visible to others, initialize discovery

*   agent to search for devices.

*   Then start a device discovery.

*/


void BluetoothModule::startDiscovery()

{

   localDevice = new QBluetoothLocalDevice(this);


   if (localDevice->isValid()) {


       qDebug() << "Bluetooth is available on this device";

       localDevice->setHostMode(QBluetoothLocalDevice::HostDiscoverable);

       localDevice->powerOn();

       localDevice->setHostMode(QBluetoothLocalDevice::HostDiscoverable);

       qDebug() << "Local device: " << localDevice->name() << " ("

                << localDevice->address().toString().trimmed() << ")";


       // Create a discovery agent and connect to its signals

       discoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);

       connect(discoveryAgent, SIGNAL(finished()),

               this, SLOT(deviceDiscoverFinished()));

       discoveryAgent->start();

       qDebug() << "Device discover started";

   }

   else {

       qDebug() << "Bluetooth is not available on this device";

   }

}


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


/**

*   Device discover finished. Get a list of devices.

*   Read information about the found devices,

*   print to qDebug() a list of devices names and addresses and

*   send it to GUI (QML), where user can select a prefered device.

*/


void BluetoothModule::deviceDiscoverFinished()

{

   qDebug() << "Device discover finished";


   listOfDevices = discoveryAgent->discoveredDevices();


   if (listOfDevices.isEmpty())

       setError("No devices found");


   qDebug() << "Found new devices:";


   for (int i = 0; i < listOfDevices.size(); i++) {

       qDebug() << listOfDevices.at(i).name().trimmed()

                << " ("

                << listOfDevices.at(i).address().toString().trimmed()

                << ")";

       setDevice(listOfDevices.at(i).name().trimmed() + " (" + listOfDevices.at(i).address().toString().trimmed() + ")");

   }

}


И запустим приложение. Устройство найдено, это не может не радовать.

Отправим этот список в QML интерфейс, заполнив ListView, подождем пользовательского выбора (как это реализуется, здесь описывать не буду, кому интересно, см. код проекта), и... самый интересный момент, описание которого (что уж говорить о примерах) я не нашел нигде. Теперь нужно соединить QBluetoothSocket с QBluetoothServiceInfo. Но сервис-то мы не искали. То есть искали, но не нашли. Оказывается, можно, зная физический адрес устройства, соединить сокет с сервисом, указав его тип и местоположение.


/**

*   In GUI (QML) user select a device with index i.

*   Create a new socket, using Rfcomm protocol.

*   Socket connect to service on selected device,

*   with Uuid Serial Port. Connect a socket's signals with sockets.

*/


void BluetoothModule::deviceSelected(int i)

{

   selectedDevice = listOfDevices.at(i);


   qDebug() << "User select a device: " << selectedDevice.name() << " ("

            << selectedDevice.address().toString().trimmed() << ")";


   socket = new QBluetoothSocket(QBluetoothSocket::RfcommSocket, this);


   socket->connectToService(QBluetoothAddress(selectedDevice.address()),

                            QBluetoothUuid(QBluetoothUuid::SerialPort));


   connect(socket, SIGNAL(error(QBluetoothSocket::SocketError)),

           this, SLOT(socketError(QBluetoothSocket::SocketError)));

   connect(socket, SIGNAL(connected()), this, SLOT(socketConnected()));

   connect(socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));

   connect(socket, SIGNAL(readyRead()), this, SLOT(socketRead()));


Остается только отправлять анализатору данные, принимаемые с кардиомонитора:


/// Read data from device via socket.

void BluetoothModule::socketRead()

{

   QByteArray recievedData = socket->readAll();

   emit dataRecieved(recievedData);

}


Работающий bluetooth-модуль готов. Прилагаю проект, который позволяет осуществлять поиск устройств, просматривать список найденных, и выбирать, к какому из них следует подключиться. Код снабжен достаточным количеством комментариев, думаю, не вызовет затруднений разобраться и использовать его в других проектах. Например, соединившись, можно не только читать из сокета, но и отправлять пакеты:


socket->write(data,data[1]);


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


P. S. Все рассмотренные классы работы с Bluetooth - часть Qt Mobility / Connectivity API, для их использования в .pro файле необходимо раскомментировать/дописать следующие строки:


CONFIG += mobility

MOBILITY = connectivity


Напоминаю, что для работы на Symbian смартфонах приложению необходимы дополнительные полномочия.
В .pro файл добавляем строку:


symbian:TARGET.CAPABILITY += NetworkServices LocalServices Location ReadUserData UserEnvironment WriteUserData ReadDeviceData WriteDeviceData


Чтобы запустить приложение на смартфоне, sis пакет необходимо подписать: www.symbiansigned.com.

P. P. S. Про особенности сборки и запуска под MeeGo не могу пока ничего сказать - не было возможности проверить. Надеюсь, появится устройство, буду с удовольствием разрабатывать и под эту платформу  ;)


 
MyTetra Share v.0.52
Яндекс индекс цитирования