1 (11.10.2012 22:26:21 отредактировано art.pogorelov)

Тема: Работа с Bluetooth средствами Qt

Аннотация.

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

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

120x70

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

Спасибо за внимание!

Post's attachments

BT.zip 27.18 kb, 88 загрузок с 2012-10-11 

У Вас недостаточно прав для загрузки файлов, прикрепленных к этому сообщению.

2

Re: Работа с Bluetooth средствами Qt

Спасибо за статью! Я думаю очень пригодится, сам хотел по Bluetooth написать... но вот кто-то разобрался по-раньше smile
На MeeGo проверю функционал, надо пульт управления роботом написать.

Поделиться

3

Re: Работа с Bluetooth средствами Qt

Рад помочь!
Удачи!  smile

Сайт art.pogorelov

Поделиться