草庐IT

Qt开发上位机软件建立经典蓝牙通讯

RobotFreak 2023-07-24 原文

Qt开发上位机软件建立经典蓝牙通讯

之前做了一个具有经典蓝牙通讯功能的Windows上位机软件,在网上学习了相关博客以及参考了官方经典蓝牙例程之后,总结出了使用Qt建立经典蓝牙通讯的步骤,附带相关源码,作为分享

开发环境

我使用的Qt版本是5.15,使用的CMake构建项目。

整体开发使用的IDEQt Creator,采用的方式是基于widgetsui设计界面、C++写逻辑的方式。

编译使用的是Desktop Qt 5.15.2 MINGW 64-bit

CMake配置

经典蓝牙通讯需要用到Qt的蓝牙模块,需要添加Bluetooth模块:

find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Widgets Bluetooth) #寻找Bluetooth模块
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Widgets Bluetooth) #寻找Bluetooth模块

add_executable之后设置target_link_libraries

target_link_libraries(bluetooth_serial_host_computer PRIVATE
    Qt${QT_VERSION_MAJOR}::Core
    Qt${QT_VERSION_MAJOR}::Widgets
    Qt${QT_VERSION_MAJOR}::Bluetooth #添加蓝牙
    )

包含头文件

#include <QtBluetooth/QBluetoothLocalDevice>            //本地设备
#include <QtBluetooth/QBluetoothDeviceDiscoveryAgent>   //设备发现
#include <QtBluetooth/QBluetoothSocket>                 //蓝牙套接字
#include <QtBluetooth/QBluetoothUuid>                   //蓝牙uuid
#include <QtBluetooth/QBluetoothServiceDiscoveryAgent>  //服务发现
#include <QtBluetooth/QBluetoothServiceInfo>            //服务信息

建立经典蓝牙通讯

建立经典蓝牙通讯的基本步骤如下:

首先是在构造函数中的初始化操作:

//创建设备发现对象
m_deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
m_btLocalDevice = new QBluetoothLocalDevice(); //创建本地设备对象
m_btSocket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol); //蓝牙套接字
QBluetoothAddress adapterAddress = m_btLocalDevice->address(); //使用默认蓝牙适配器
m_serviceDiscoveryAgent = new QBluetoothServiceDiscoveryAgent(adapterAddress); //创建服务发现对象
//连接信号与槽(deviceDiscoveryAgent是代码中自己声明的对象,ui中没有,无法自动生成槽函数;需要自己写槽函数,自己连接)
/*-------------------- 初始化设备列表与服务列表 -----------------*/
//设置设备列表
QListWidgetItem* item = new QListWidgetItem();
//创建自定义窗口,放入到listwidget中
BluetoothDeviceCell* btDevCell = new BluetoothDeviceCell();
//设置item的高
item->setSizeHint(QSize(ui->deviceListWidget->width(), btDevCell->height()));
//设置label显示
//第一个加进去的item在最上面,相当于标题
btDevCell->m_btName->setText("名称");
btDevCell->m_btAddr->setText("地址");
btDevCell->m_btRssi->setText("信号强度");

//将item加入到listwidget中
ui->deviceListWidget->addItem(item);
//设置item的窗口为自定义的窗口
ui->deviceListWidget->setItemWidget(item, btDevCell);

//设置服务列表的标题
QListWidgetItem* items = new QListWidgetItem();
//创建自定义窗口,放入到listwidget中
BluetoothDeviceCell* btDevCells = new BluetoothDeviceCell();
//设定item的尺寸
items->setSizeHint(QSize(ui->serviceListWidget->width(), btDevCells->height()));
//设置label的显示
//第一个加进去的item在最上面,相当于标题
btDevCells->m_btName->setText("名称");
btDevCells->m_btAddr->setText("服务Uuid");
btDevCells->m_btRssi->setText("空");
//将item加入到listwidget中
ui->serviceListWidget->addItem(items);
//设置item的窗口为自定义的窗口
ui->serviceListWidget->setItemWidget(items, btDevCells);
/*------------- m_deviceDiscoveryAgent设备搜索对象的信号槽 -------------*/
connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &BluetoothConnect::deviceDiscoveredSlot);//发现设备
connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished, this, &BluetoothConnect::deviceSearchFinishedSlot);//搜索完毕
void (QBluetoothDeviceDiscoveryAgent:: *deviceSearchErrorOccurred)(QBluetoothDeviceDiscoveryAgent::Error) = &QBluetoothDeviceDiscoveryAgent::error;//有重载
connect(m_deviceDiscoveryAgent, deviceSearchErrorOccurred, this, &BluetoothConnect::deviceSearchErrorOccurredSlot);//设备搜索发生错误

/*------------- m_btSocket蓝牙套接字的信号槽 ----------------*/
void (QBluetoothSocket:: *socketErrorOccurred)(QBluetoothSocket::SocketError) = &QBluetoothSocket::error;
connect(m_btSocket, socketErrorOccurred, this, &BluetoothConnect::socketErrorOccurredSlot); //错误处理槽函数
connect(m_btSocket, &QBluetoothSocket::connected, this, &BluetoothConnect::socketConnectedSlot);//连接成功

/*------------- m_serviceDiscoveryAgent服务发现对象的信号槽 ---------------*/
connect(m_serviceDiscoveryAgent, &QBluetoothServiceDiscoveryAgent::serviceDiscovered, this, &BluetoothConnect::serviceDiscoveredSlot); //发现一个服务
connect(m_serviceDiscoveryAgent, &QBluetoothServiceDiscoveryAgent::finished, this, &BluetoothConnect::serviceSearchFinishedSlot); //服务搜索完毕

deviceListWidget和serviceListWidget是我在ui中设置的两个列表组件,用来存放设备和服务信息。

搜索设备

利用QBluetoothDeviceDiscoveryAgentstart方法即可开启设备搜索:

void BluetoothConnect::on_searchButton_clicked() //点击Search按钮后开始搜索设备
{
    //如果已经搜索过1次,那么设备列表可能会大于1,需要先清空设备列表
    //但是标题也会被清除,所以重新添加标题
    if(ui->deviceListWidget->count() > 1)
    {
        ui->deviceListWidget->clear();

        //设置设备列表
        QListWidgetItem* item = new QListWidgetItem();
        //创建自定义窗口,放入到listwidget中
        BluetoothDeviceCell* btDevCell = new BluetoothDeviceCell();
        //设置item的高
        item->setSizeHint(QSize(ui->deviceListWidget->width(), btDevCell->height()));
        //设置label显示
        btDevCell->m_btName->setText("名称");
        btDevCell->m_btAddr->setText("地址");
        btDevCell->m_btRssi->setText("信号强度");

        //将item加入到listwidget中
        ui->deviceListWidget->addItem(item);
        //设置item的窗口为自定义的窗口
        ui->deviceListWidget->setItemWidget(item, btDevCell);
    }
    m_deviceDiscoveryAgent->start(QBluetoothDeviceDiscoveryAgent::ClassicMethod); //开启经典蓝牙设备搜索

    ui->searchButton->setEnabled(false);//搜索过程中,search按钮不可点击
    ui->statusLabel->setText("Searching for devices......");
}

QBluetoothDeviceDiscoveryAgent::ClassicMethod是以经典方式进行搜索,可以发现经典蓝牙和低功耗蓝牙设备。

发现一个设备的槽函数:

void BluetoothConnect::deviceDiscoveredSlot(QBluetoothDeviceInfo btDevInfo) //发现设备后将设备添加到listwidget中
{
        QListWidgetItem* item = new QListWidgetItem(); //声明一个item
        BluetoothDeviceCell* btDevCell = new BluetoothDeviceCell(); //声明一个蓝牙设备单元

        item->setSizeHint(QSize(ui->deviceListWidget->width(), btDevCell->height())); //设定item的尺寸
        //搜索到的设备信息存到设备单元中
        btDevCell->m_btName->setText(btDevInfo.name());
        btDevCell->m_btAddr->setText(btDevInfo.address().toString());
        btDevCell->m_btRssi->setText(QString::number(btDevInfo.rssi()));

        //将设备单元添加到deviceListWidget中
        ui->deviceListWidget->addItem(item);
        ui->deviceListWidget->setItemWidget(item,btDevCell);
        //设备信息list新增一个设备信息
        m_devInfos.append(btDevInfo);

}

设备搜索完成的槽函数:

void BluetoothConnect::deviceSearchFinishedSlot() //设备搜索完成
{
    ui->statusLabel->setText("Double Click a device to searhc for services");
    QMessageBox::about(this, "提示", "设备搜索完成");
//    qDebug() << "搜索已完成";
    ui->searchButton->setEnabled(true); //一次搜索完之后才可以重新点击search键
}

设备搜索出现错误的槽函数:

void BluetoothConnect::deviceSearchErrorOccurredSlot(QBluetoothDeviceDiscoveryAgent::Error error) //搜索蓝牙设备出现错误
{
    qDebug() << error;
    //警告对话框
    QMessageBox::warning(this, "警告!", "搜索蓝牙设备发生错误,请检查蓝牙是否开启");
}

搜索选定设备的服务

激活设备列表的设备,搜索这个选定设备的服务:

void BluetoothConnect::on_deviceListWidget_itemActivated(QListWidgetItem *item) //点击设备列表中的item
{


    /***
     * 点击设备后,搜索设备的服务。点击服务后,根据服务和设备进行连接。并将socket的准备接收与主窗口BluetoothDebugger的revDataTextEdit的显示连接
    */
    //在开始service搜索之前先重置服务列表
    //如果已经搜索过1次,那么服务列表可能会大于1,需要先清空服务列表
    //但是标题也会被清除,所以重新添加标题
    if(ui->serviceListWidget->count() > 1)
    {
        ui->serviceListWidget->clear();

        //设置服务列表的标题
        QListWidgetItem* items = new QListWidgetItem();
        //创建自定义窗口,放入到listwidget中
        BluetoothDeviceCell* btDevCells = new BluetoothDeviceCell();
        //设定item的尺寸
        items->setSizeHint(QSize(ui->serviceListWidget->width(), btDevCells->height()));
        //设置label的显示
        //第一个加进去的item在最上面,相当于标题
        btDevCells->m_btName->setText("名称");
        btDevCells->m_btAddr->setText("服务Uuid");
        btDevCells->m_btRssi->setText("空");
        //将item加入到listwidget中
        ui->serviceListWidget->addItem(items);
        //设置item的窗口为自定义的窗口
        ui->serviceListWidget->setItemWidget(items, btDevCells);
    }

    QWidget* widget = ui->deviceListWidget->itemWidget(item); //将点击的item信息取出来
    BluetoothDeviceCell* btDevCell = (BluetoothDeviceCell*) widget; //强制类型转换为DeviceCell设备单元
    qDebug() << btDevCell->m_btAddr->text(); //输出设备地址
    m_btAddress = QBluetoothAddress(btDevCell->m_btAddr->text());//记录下来选中的设备的地址.后边点击service进行连接时,使用该地址
    m_serviceDiscoveryAgent->setRemoteAddress(QBluetoothAddress(btDevCell->m_btAddr->text())); //要搜索的服务是当前激活的这个设备的服务
    m_serviceDiscoveryAgent->start(); //开始搜索

    ui->statusLabel->setText("Searching for services......");

    //!!!!不同设备所允许的服务不同,需要寻找设备支持的服务有什么


}

发现一个服务的槽函数,将服务信息添加到listWidget以及自己的list中:

void BluetoothConnect::serviceDiscoveredSlot(const QBluetoothServiceInfo& serviceInfo) //发现服务
{
    //serviceInfo中没有设备地址
    if(serviceInfo.serviceName().isEmpty())
        return;
    //!!!用serviceClassUuids才能得到有效的Uuid
    QList<QBluetoothUuid> btUuids = serviceInfo.serviceClassUuids();
    //每个list中只有1个元素
    qDebug() << btUuids[0];
    QBluetoothUuid btUuid = btUuids[0];

    QListWidgetItem* item = new QListWidgetItem();
    BluetoothDeviceCell* btDevCell = new BluetoothDeviceCell();

    item->setSizeHint(QSize(ui->serviceListWidget->width(), btDevCell->height()));

    btDevCell->m_btName->setText(serviceInfo.serviceName());
    btDevCell->m_btAddr->setText(btUuid.toString());
    btDevCell->m_btRssi->setText("");

    ui->serviceListWidget->addItem(item);
    ui->serviceListWidget->setItemWidget(item, btDevCell);

    m_btUuids.append(btUuid);

}

服务搜索完成的槽函数:

void BluetoothConnect::serviceSearchFinishedSlot() //服务搜索完成
{
    ui->statusLabel->setText("Double Click a service to connect to bluetooth");
    QMessageBox::about(this, "Hint", "Service Search Finished!");
}

连接到选定设备的选定服务,建立通讯

激活服务列表的服务的槽函数:

void BluetoothConnect::on_serviceListWidget_itemActivated(QListWidgetItem *item) //激活服务列表的项目
{
    QWidget* widget = ui->serviceListWidget->itemWidget(item); //将点击的item信息取出来
    BluetoothDeviceCell* btDevCell = (BluetoothDeviceCell*) widget; //强制类型转换为DeviceCell设备单元
    qDebug() << m_btAddress << btDevCell->m_btAddr->text();
    m_btSocket->connectToService(m_btAddress,QBluetoothUuid(btDevCell->m_btAddr->text()),QIODevice::ReadWrite); //根据设备地址与Uuid连接
    ui->statusLabel->setText("Bluetooth connecting......");
}

使用socket套接字连接成功后,经典蓝牙通讯即建立:

void BluetoothConnect::socketConnectedSlot() //socket连接成功
{
    ui->statusLabel->setText("");
    QMessageBox::about(this, "Information", "Socket Connected Successfully!");
    //隐去Bluetooth Connect窗口
    this->close();

}

socket发生错误的槽函数:

void BluetoothConnect::socketErrorOccurredSlot(QBluetoothSocket::SocketError error) //socket发生错误
{
    switch(error)
    {
        case QBluetoothSocket::NoSocketError:
            break;
        case QBluetoothSocket::UnknownSocketError:
            qDebug() << "Error: Unknown Socket Error!";
            break;
        case QBluetoothSocket::RemoteHostClosedError:
            qDebug() << "Error: Remote Host Closed Error!";
            break;
        case QBluetoothSocket::HostNotFoundError:
            qDebug() << "Error: Host Not Found Error!";
            break;
        case QBluetoothSocket::ServiceNotFoundError:
            qDebug() << "Error: Service Not Found Error!";
            break;
        case QBluetoothSocket::NetworkError:
            qDebug() << "Error: Network Error!";
            break;
        case QBluetoothSocket::UnsupportedProtocolError:
            qDebug() << "Error: Unsupported Protocol Error!";
            break;
        case QBluetoothSocket::OperationError:
            qDebug() << "Error: Operation Error!";
            break;
    }
}

接收数据使用socket,将readyReadsignal与槽函数连接:

void BluetoothDebugger::socketReadyReadSlot() //socket准备好读取
{
    char data[100];
    qint64 len = m_btConnect->m_btSocket->read((char*)data,100);

    QByteArray byteArray((char*)data, len);
    if(m_isClose == false) //窗口显示打开
    {
        ui->revDataTextBrowser->append("接收:");
        ui->revDataTextBrowser->append(byteArray);//添加到窗口
    }

}

发送数据也是使用socket套接字:

void BluetoothDebugger::on_sendButton_clicked() //点击发送按钮
{
    //被发送的数据
    QByteArray dataSent = ui->sendDataLineEdit->text().toUtf8();
    if(m_isClose == false) //窗口显示打开,蓝牙发送使能;窗口显示关闭,蓝牙发送失能
    {
        //将发送的数据也打印到窗口上
        ui->revDataTextBrowser->append("发送:");
        ui->revDataTextBrowser->append(dataSent);
        //通过socket发送数据
        m_btConnect->m_btSocket->write(dataSent);
    }
}

以上即为使用Qt建立经典蓝牙通讯的基本步骤。

有关Qt开发上位机软件建立经典蓝牙通讯的更多相关文章

  1. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  2. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  3. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  4. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  5. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

    我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

  6. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  7. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  8. 7个大一C语言必学的程序 / C语言经典代码大全 - 2

    嗨~大家好,这里是可莉!今天给大家带来的是7个C语言的经典基础代码~那一起往下看下去把【程序一】打印100到200之间的素数#includeintmain(){ inti; for(i=100;i 【程序二】输出乘法口诀表#includeintmain(){inti;for(i=1;i 【程序三】判断1000年---2000年之间的闰年#includeintmain(){intyear;for(year=1000;year 【程序四】给定两个整形变量的值,将两个值的内容进行交换。这里提供两种方法来进行交换,第一种为创建临时变量来进行交换,第二种是不创建临时变量而直接进行交换。1.创建临时变量来

  9. 软件测试基础 - 2

    Ⅰ软件测试基础一、软件测试基础理论1、软件测试的必要性所有的产品或者服务上线都需要测试2、测试的发展过程3、什么是软件测试找bug,发现缺陷4、测试的定义使用人工或自动的手段来运行或者测试某个系统的过程。目的在于检测它是否满足规定的需求。弄清预期结果和实际结果的差别。5、测试的目的以最小的人力、物力和时间找出软件中潜在的错误和缺陷6、测试的原则28原则:20%的主要功能要重点测(eg:支付宝的支付功能,其他功能都是次要的)80%的错误存在于20%的代码中7、测试标准8、测试的基本要求功能测试性能测试安全性测试兼容性测试易用性测试外观界面测试可靠性测试二、质量模型衡量一个优秀软件的维度①功能性功

  10. Qt Designer的简单使用 - 2

    在前面两节的例子中,主界面窗口的尺寸和标签控件显示的矩形区域等,都是用C++代码编写的。窗口和控件的尺寸都是预估的,控件如果多起来,那就不好估计每个控件合适的位置和大小了。用C++代码编写图形界面的问题就是不直观,因此Qt项目开发了专门的可视化图形界面编辑器——QtDesigner(Qt设计师)。通过QtDesigner就可以很方便地创建图形界面文件*.ui,然后将ui文件应用到源代码里面,做到“所见即所得”,大大方便了图形界面的设计。本节就演示一下QtDesigner的简单使用,学习拖拽控件和设置控件属性,并将ui文件应用到Qt程序代码里。使用QtDesigner设计界面在开始菜单中找到「Q

随机推荐