Аннотация.
При разработке мобильных(и не только) приложений часто возникает необходимость организовать соединение между устройствами. Несмотря на популярность 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 не могу пока ничего сказать - не было возможности проверить. Надеюсь, появится устройство, буду с удовольствием разрабатывать и под эту платформу ;)