zoukankan      html  css  js  c++  java
  • Qt on Android 蓝牙通信开发

      版权声明:本文为MULTIBEANS ORG研发跟随文章,未经MLT ORG允许不得转载。

      最近做项目,需要开发安卓应用,实现串口的收发,目测CH340G在安卓手机上非常麻烦,而且驱动都是Java版本的, 就没选择,博主在大二的时候学习过Java SE基本的语法,写过一些小程序就放弃了Java的道路。最后选择了蓝牙无线透传模块,实现串口通信。现在Qt跨平台支持安卓,是在是令人欣喜。在网上找资料,用Qt on Android做蓝牙驱动的几乎没有,也没有相关例程,所以准备撰写此文,献给广大嵌入式程序员们

      2018/6/27更新:

           增加Java版本的蓝牙通信,文章地址:https://www.cnblogs.com/sigma0/p/9234478.html

    一、软硬件平台

    1.1 硬件平台

    1. 蓝牙:HC-05,(淘宝上有卖),它的接口就是跟串口一样的,我们用到了TX,RX,GND,VCC四个引脚。跟下位机或者用CH340G TTL转USB模块接到PC机上。蓝牙工作在串口模式可以通过AT指令调节。具体参考蓝牙配套的说明文档,最主要的就是请将蓝牙设定为从机模式,否则安卓手机搜寻链接不上。
    2.安卓手机:我这里测试用了2台安卓手机,一台是小米4移动版,安卓版本6.0.1;一台是MOTO MT887,安卓版本4.1.2。

    1.2 软件平台

    本项目Qt版本是5.7,系统是windows 8.1 x64

    二、软件基本介绍

      因为第一次做蓝牙,就做一个非常简单的雏形,实现蓝牙状态检测、蓝牙的开关、蓝牙的扫描和蓝牙配对链接,并且能像串口助手一样完成数据收发。如图,就是本一开始做的最简单的软件界面,本软件基于QWidget控件制作,当然你可以选择mainwinodw,更可以自己定义类。

    软件界面

     

      我不用介绍每个部位是什么了,都会明白吧?蓝牙打开后通过扫描,会将蓝牙的MAC地址还有名字显示在List中,我们双击List列表中的蓝牙,就会进入actived信号连接的槽函数,执行蓝牙的配对连接。建立连接之后,就类似串口一样可以进行数据通信了。另外,点击send按钮之后会发送一堆字符串。

    三、 蓝牙开发

    3.1 项目文件准备

    需要用到蓝牙就需要在.pro文件中引入库,我没有用Qt quick,用的是纯C++写的代码,你需要在.pro文件中加入这句话:

    QT += bluetooth
    如果没有这句话的话,包含蓝牙目录下的头文件,会提示找不到该文件。
     
    之后就是要包含一些蓝牙用到的头文件:
     
    #include <QtBluetooth/qbluetoothglobal.h>
    #include <QtBluetooth/qbluetoothlocaldevice.h>
    #include <qbluetoothaddress.h>
    #include <qbluetoothdevicediscoveryagent.h>
    #include <qbluetoothlocaldevice.h>
    #include <qbluetoothsocket.h>
      一会儿介绍每个都是做什么的。
     
    请在类中声明定义蓝牙相关句柄:
    QBluetoothDeviceDiscoveryAgent *discoveryAgent;
    QBluetoothLocalDevice *localDevice;
    QBluetoothSocket *socket;
     
      第一个discoveryAgent是用来对周围蓝牙进行搜寻,localDevice顾名思义,就是对本地设备进行操作,比如进行设备的打开,设备的关闭等等。socket就是用来进行蓝牙配对链接和数据传输的。这里要用到这三个。
     

    3.2 蓝牙开关和可见性设定

    在构造函数中,请为localDevice使用new运算符分配内存。
    localDevice = new QBluetoothLocalDevice();

    1) 蓝牙开关

    本设计在运行APP的时候,会检测一下我们本地设备的蓝牙是否打开,如果判断是开启状态,我们可以将打开蓝牙的按钮disable掉,将关闭蓝牙的按钮enable,所以在APP运行的时候需要进行蓝牙状态检测。检测方法如下:
     
    进行一个这样的检测,对本地设备模式进行判断。
    if( localDevice->hostMode() == QBluetoothLocalDevice::HostPoweredOff )  {
            ui->pushButton_openBluetooth->setEnabled(true);
            ui->pushButton_closeDevice->setEnabled(false);
    }else {
            ui->pushButton_openBluetooth->setEnabled(false);
            ui->pushButton_closeDevice->setEnabled(true);
    }

    在构造函数中

    那么,我们如何来对蓝牙进行打开和关闭呢?我在open按钮和close按钮的槽函数中对蓝牙进行开关操作。

    open按钮的槽函数:

    void Widget::on_pushButton_openBluetooth_clicked()
    {
        localDevice->powerOn();
        ui->pushButton_closeDevice->setEnabled(true);
        ui->pushButton_openBluetooth->setEnabled(false);
        ui->pushButton_scan->setEnabled(true);
    }
    localDevice->powerOn();方法调用打开本地的蓝牙设备,然后你可以根据自己的喜好完成对按钮的使能和禁止操作。
     
    close按钮的槽函数:
    void Widget::on_pushButton_closeDevice_clicked()
    {
        localDevice->setHostMode(QBluetoothLocalDevice::HostPoweredOff);
        ui->pushButton_closeDevice->setEnabled(false);
        ui->pushButton_openBluetooth->setEnabled(true);
        ui->pushButton_scan->setEnabled(false);
    }
    close设备和我们的open设备的方法在形式上不一样,我还以为他们两个是对称的,但是事实上不是,只能用这样的方法对蓝牙进行关闭。

    2) 蓝牙可见性

      同样地,在蓝牙使用过程中,安卓手机提供了蓝牙是否可以被其他蓝牙搜索到这样的功能,也就是蓝牙可见,我们也可以用localDevice下的HostMode()方法,对这个状态进行检测。如下:
    if( localDevice->hostMode() == QBluetoothLocalDevice::HostDiscoverable ) {
            ui->checkBox_discoverable->setChecked(true);
    }else {
            ui->checkBox_discoverable->setChecked(false);
    }
    我的设计中,蓝牙可见如界面图用的是checkBox空间完成的,通过setChecked()方法,一开机对是否可见进行。
    在翻转checkBox的时候,会激发进入checkBox的槽函数,我们在checkBox的槽函数中,完成对蓝牙可见性的设定。代码如下:
    localDevice->setHostMode( QBluetoothLocalDevice::HostDiscoverable);
    同理,不可见你也能想到对吧。
     

    3.3 蓝牙设备的查找

      使用蓝牙设备的查找,就要用到 discoveryAgent 这个类的实例化。我们需要在构造函数中对discoveryAgent =new QBluetoothDeviceDiscoveryAgent();分配内存。然后就可以使用这个类的方法来对蓝牙进行查找了。除此之外,还要进行一个信号和槽的链接。
        connect(discoveryAgent,
                SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)),
                this,
                SLOT(addBlueToothDevicesToList(QBluetoothDeviceInfo))
                );
      在我们发现设备的时候,这个deviceDiscovered信号被触发,进入到addBlueToothDevicesToList的函数中。在上面的软件界面,我们的最上面蓝牙列表下的控件是ListIte控件,这里做一个槽函数,将发现的设备打印到这个列表中列出来。
    void Widget::addBlueToothDevicesToList( const QBluetoothDeviceInfo &info )
    {
        QString label = QString("%1 %2").arg(info.address().toString()).arg(info.name());
    
        QList<QListWidgetItem *> items = ui->list->findItems(label, Qt::MatchExactly);
    
        if (items.empty()) {
            QListWidgetItem *item = new QListWidgetItem(label);
            QBluetoothLocalDevice::Pairing pairingStatus = localDevice->pairingStatus(info.address());
            if (pairingStatus == QBluetoothLocalDevice::Paired || pairingStatus == QBluetoothLocalDevice::AuthorizedPaired )
                item->setTextColor(QColor(Qt::green));
            else
                item->setTextColor(QColor(Qt::black));
            ui->list->addItem(item);
        }
    
    }
      这里给出这个函数,每一句话十分的好理解,这里增加点选操作,当点击listItem中的项目的时候,背景颜色会翻转,双击这个项目就会和这个蓝牙设备建立连接,这里有个actived槽函数,在这个槽函数里面就会进行蓝牙的链接。下一章节写这个如何连接。
     

    3.4 蓝牙设备的建立连接

      在说蓝牙设备连接之前,不得不提一个非常重要的概念,就是蓝牙的Uuid,引用一下百度的:
     
      在蓝牙中,每个服务和服务属性都唯一地由"全球唯一标识符" (UUID)来校验。正如它的名字所暗示的,每一个这样的标识符都要在时空上保证唯一。UUID类可表现为短整形(16或32位)和长整形(128位)UUID。他提供了分别利用String和16位或32位数值来创建类的构造函数,提供了一个可以比较两个UUID(如果两个都是128位)的方法,还有一个可以转换一个UUID为一个字符串的方法。UUID实例是不可改变的(immutable),只有被UUID标示的服务可以被发现。
    在Linux下你用一个命令uuidgen -t可以生成一个UUID值;在Windows下则执行命令uuidgen 。UUID看起来就像如下的这个形式:2d266186-01fb-47c2-8d9f-10b8ec891363。当使用生成的UUID去创建一个UUID对象,你可以去掉连字符。
     
    在我们的项目中,用到的模式是串口模式,我们需要建立一个存储Uuid的机制,如下:
    static const QLatin1String serviceUuid("00001101-0000-1000-8000-00805F9B34FB");
    这个字符串里面的内容就是串口模式的Uuid,如果你开发的蓝牙也是要使用串口,你直接Copy过去就可以了,如果你使用其他模式,自己去找这个Uuid码是多少。
     
    在使用蓝牙建立连接,需要建立蓝牙socket服务。请在构造函数中增加对socket的分配内存,要注意的是构造函数中的参数需要给定模式。
     
    socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol);
      
     
      在Qt文档中,给了3中模式,具体如何这里不做引申,读者需要请自己查询文档。但RfcommProtocol,属于模拟RS232模式,我就叫串口模式了。
      在上一节中说了,当双击ItemList控件中的项目时候,会进入到actived槽函数和蓝牙进行链接,那么如何连接呢?在itemList中会打印一个蓝牙的MAC地址信息,我们会将这个Mac地址保存在QBluetoothAddress这个类的实例化中,并将这个address传递给socket,作为链接依据。
    void Widget::itemActivated(QListWidgetItem *item)
    {
        QString text = item->text();
    
        int index = text.indexOf(' ');
    
        if (index == -1)
            return;
    
        QBluetoothAddress address(text.left(index));
        QString name(text.mid(index + 1));
        qDebug() << "You has choice the bluetooth address is " << address;
        qDebug() << "The device is connneting.... ";
        QMessageBox::information(this,tr("Info"),tr("The device is connecting..."));
        socket->connectToService(address, QBluetoothUuid(serviceUuid) ,QIODevice::ReadWrite);
    
    }

      我们通过对字符串的处理,将得到address信息。通过socket->connectToService(....),把地址,Uuid,和蓝牙模式传递进去,当执行完这句话的时候,安卓手机开始和你

       选择的蓝牙设备进行链接。

      同样在socket中也提供了丰富的槽函数,比如成功建立连接信号,成功断开信号,这里在槽函数中可以做一些例子,这里给出例子:

        connect(socket,
                SIGNAL(connected()),
                this,
                SLOT(bluetoothConnectedEvent())
                );
        connect(socket,
                SIGNAL(disconnected()),
                this,
                SLOT(bluetoothDisconnectedEvent())
                );
    void Widget::bluetoothConnectedEvent()
    {
      // 2017/10/8 更新一下,请在这里插入关闭蓝牙查找服务,否则数据会断。
    // 具体语句是什么我忘记了,反正使用discoveryAgent的一个什么close,或者stop的方法
    qDebug()
    << "The android device has been connected successfully!"; QMessageBox::information(this,tr("Info"),tr("successful connection!")); } void Widget::bluetoothDisconnectedEvent() { qDebug() << "The android device has been disconnected successfully!"; QMessageBox::information(this,tr("Info"),tr("successful disconnection!")); }

    最后,还有一个断开连接函数。通过断开连接按钮的槽函数实现。

    void Widget::on_pushButton_disconnect_clicked()
    {
        socket->disconnectFromService();
    
    }

    3.5 发送和接收数据

      蓝牙发送和接收数据,也是通过socket进行。发送数据十分简单:
    void Widget::on_pushButton_send_clicked()
    {
        QByteArray arrayData;
        QString s("Hello Windows!!!
    This message is sended via bluetooth of android device!
    ");
        arrayData = s.toUtf8();
        socket->write(arrayData);
    }
    这里通过socket->write函数,完成发送。发送之后,上位机,我用的串口助手会显示该信息。
    串口助手接受到信息
     
    那么接收数据呢?
    我们在构造函数中,需要建立这样的一个信号和槽的链接:
        connect(socket,
                SIGNAL(readyRead()),
                this,
                SLOT(readBluetoothDataEvent())
                );
    readyRead()信号触发,跳进readBluetoothDataEvent中。
    void Widget::readBluetoothDataEvent()
    {
    
        QByteArray line = socket->readAll();
        QString strData = line.toHex();
        comStr.append(strData);
        qDebug() <<"rec data is: "<< comStr;
        qDebug() <<"The comStr length is: " << comStr.length();
        if(comStr.length() >= 30) {
    
            ui->textBrowser_info->append(comStr + "
    ");
            comStr.clear();
        }
    
    }
    我这里是这样处理的,当然了,你有你自己的处理方法,意思就是那么个意思。
     

    四、结束语

    完成对蓝牙的开发,实现了最基本的功能,这里为了讲述用Qt开发蓝牙在安卓设备上,用了最简单最简单的例子,给你一个思路框架方法,如果追求极高的稳定性,好需要自
    己深入研究,这里不做讨论。欢迎批评指正,我也是一个求学者,大家共同交流,共同进步。最后,贴上源码,仅供大家参考。
     
     
    ------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    提取码: zykk
    ------------------------------------------------------------------------------------------------------------------------------------------------------------------------
     
  • 相关阅读:
    SAP MM 采购附加费计入物料成本之二
    SAP MM 采购附加费计入物料成本?
    SAP MM 作为采购附加费的运费为啥没能在收货的时候计入物料成本?
    SAP MM 外部采购流程里的Advanced Return Management
    SAP MM 外部采购流程里的如同鸡肋一样的Advanced Returns Management功能
    SAP MM Why is the freight not included in the material cost at the time of GR?
    SAP MM: Change of material moving average price after goods receipt and invoice verification posting for PO
    SAP 创建启用了ARM功能的采购订单,报错 Shipping processing is not selected to supplier 100057 in purchase org. 0002
    GIT·代码仓库默认分支更改
    .Net/C#·运行报错缺少XXX文件,但双击无法跳转缺少位置
  • 原文地址:https://www.cnblogs.com/sigma0/p/5769527.html
Copyright © 2011-2022 走看看