|
本帖最后由 正点原子 于 2021-8-11 12:25 编辑
1)实验平台:正点原子i.MX6ULL Linux阿尔法开发板
2) 章节摘自【正点原子】《I.MX6U嵌入式Qt开发指南》
3)购买链接:https://item.taobao.com/item.htm?&id=603672744434
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/arm-linux/zdyz-i.mx6ull.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子Linux技术交流群:1027879335
第二十章 USB Bluetooth
Qt官方提供了蓝牙的相关类和API函数,也提供了相关的例程给我们参考。编者根据Qt官方的例程编写出适合我们Ubuntu和正点原子I.MX6U开发板的例程。注意Windows上不能使用Qt的蓝牙例程,因为底层需要有BlueZ协议栈,而Windows没有。Windows可能需要去移植。编者就不去探究了。确保我们正点原子I.MX6U开发板与Ubuntu可用即可,所以大家还是老实的用Ubuntu来开发吧!
20.1 资源简介
在正点原子IMX6U开发板上虽然没有带板载蓝牙,但是可以外接免驱USB蓝牙,直接在USB接口插上一个USB蓝牙模块就可以进行本小节的实验了。详细请看【正点原子】I.MX6U用户快速体验V1.x.pdf的第3.29小节蓝牙测试,先了解蓝牙是如何在Linux上如何使用的,切记先看正点原子快速体验文档,了解用哪种蓝牙芯片,和怎么测试蓝牙的。本Qt教程就不再介绍了。
20.2 应用实例
项目简介:Qt蓝牙聊天。将蓝牙设置成一个服务器,或者用做客户端,连接手机即可通信。
例06_bluetooth_chat,Qt蓝牙聊天(难度:难)。项目路径为Qt/3/06_bluetooth_chat。
Qt使用蓝牙,需要在项目文件加上相应的蓝牙模块。添加的代码如下红色加粗部分。06_bluetooth_chat.pro文件代码如下。
- 1 QT += core gui bluetooth
- 2
- 3 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
- 4
- 5 CONFIG += c++11
- 6
- 7 # The following define makes your compiler emit warnings if you use
- 8 # any Qt feature that has been marked deprecated (the exact warnings
- 9 # depend on your compiler). Please consult the documentation of the
- 10 # deprecated API in order to know how to port your code away from it.
- 11 DEFINES += QT_DEPRECATED_WARNINGS
- 12
- 13 # You can also make your code fail to compile if it uses deprecated APIs.
- 14 # In order to do so, uncomment the following line.
- 15 # You can also select to disable deprecated APIs only up to a certain version of Qt.
- 16 #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
- 17
- 18 SOURCES += \
- 19 chatclient.cpp \
- 20 chatserver.cpp \
- 21 main.cpp \
- 22 mainwindow.cpp \
- 23 remoteselector.cpp
- 24
- 25 HEADERS += \
- 26 chatclient.h \
- 27 chatserver.h \
- 28 mainwindow.h \
- 29 remoteselector.h
- 30
- 31 # Default rules for deployment.
- 32 qnx: target.path = /tmp/${TARGET}/bin
- 33 else: unix:!android: target.path = /opt/${TARGET}/bin
- 34 !isEmpty(target.path): INSTALLS += target
复制代码
第18~29行,可以看到我们的项目组成文件。一个客户端,一个服务端,一个主界面和一个远程选择蓝牙的文件。总的看起来有四大部分,下面就介绍这四大部分的文件。
chatclient.h的代码如下。
- /******************************************************************
- Copyright (C) 2015 The Qt Company Ltd.
- Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
- * @projectName 06_bluetooth_chat
- * @brief chatclient.h
- * @author Deng Zhimao
- * @email <a href="mailto:1252699831@qq.com" target="_blank">1252699831@qq.com</a>
- * @net <a href="www.openedv.com" target="_blank">www.openedv.com</a>
- * @date 2021-03-20
- *******************************************************************/
- 1 #ifndef CHATCLIENT_H
- 2 #define CHATCLIENT_H
- 3
- 4 #include <qbluetoothserviceinfo.h>
- 5 #include <QBluetoothSocket>
- 6 #include <QtCore/QObject>
- 7
- 8 QT_FORWARD_DECLARE_CLASS(QBluetoothSocket)
- 9
- 10 class ChatClient : public QObject
- 11 {
- 12 Q_OBJECT
- 13
- 14 public:
- 15 explicit ChatClient(QObject *parent = nullptr);
- 16 ~ChatClient();
- 17
- 18 /* 开启客户端 */
- 19 void startClient(const QBluetoothServiceInfo &remoteService);
- 20
- 21 /* 停止客户端 */
- 22 void stopClient();
- 23
- 24 public slots:
- 25 /* 发送消息 */
- 26 void sendMessage(const QString &message);
- 27
- 28 /* 主动断开连接 */
- 29 void disconnect();
- 30
- 31 signals:
- 32 /* 接收到消息信号 */
- 33 void messageReceived(const QString &sender, const QString &message);
- 34
- 35 /* 连接信号 */
- 36 void connected(const QString &name);
- 37
- 38 /* 断开连接信号 */
- 39 void disconnected();
- 40
- 41 private slots:
- 42 /* 从socket里读取消息 */
- 43 void readSocket();
- 44
- 45 /* 连接 */
- 46 void connected();
- 47
- 48 private:
- 49 /* socket通信 */
- 50 QBluetoothSocket *socket;
- 51 };
- 52
- 53 #endif // CHATCLIENT_H
复制代码
chatclient.h文件主要是客户端的头文件,其中写一些接口,比如开启客户端,关闭客户端,接收信号与关闭信号等等。
chatclient.cpp的代码如下。
- /******************************************************************
- Copyright (C) 2015 The Qt Company Ltd.
- Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
- * @projectName 06_bluetooth_chat
- * @brief chatclient.cpp
- * @author Deng Zhimao
- * @email <a href="mailto:1252699831@qq.com" target="_blank">1252699831@qq.com</a>
- * @net <a href="www.openedv.com" target="_blank">www.openedv.com</a>
- * @date 2021-03-20
- *******************************************************************/
- 1 #include "chatclient.h"
- 2 #include <qbluetoothsocket.h>
- 3
- 4 ChatClient::ChatClient(QObject *parent)
- 5 : QObject(parent), socket(0)
- 6 {
- 7 }
- 8
- 9 ChatClient::~ChatClient()
- 10 {
- 11 stopClient();
- 12 }
- 13
- 14 /* 开启客户端 */
- 15 void ChatClient::startClient(const QBluetoothServiceInfo &remoteService)
- 16 {
- 17 if (socket)
- 18 return;
- 19
- 20 // Connect to service
- 21 socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol);
- 22 qDebug() << "Create socket";
- 23 socket->connectToService(remoteService);
- 24 qDebug() << "ConnectToService done";
- 25
- 26 connect(socket, SIGNAL(readyRead()),
- 27 this, SLOT(readSocket()));
- 28 connect(socket, SIGNAL(connected()),
- 29 this, SLOT(connected()));
- 30 connect(socket, SIGNAL(disconnected()),
- 31 this, SIGNAL(disconnected()));
- 32 }
- 33
- 34 /* 停止客户端 */
- 35 void ChatClient::stopClient()
- 36 {
- 37 delete socket;
- 38 socket = 0;
- 39 }
- 40
- 41 /* 从Socket读取消息 */
- 42 void ChatClient::readSocket()
- 43 {
- 44 if (!socket)
- 45 return;
- 46
- 47 while (socket->canReadLine()) {
- 48 QByteArray line = socket->readLine();
- 49 emit messageReceived(socket->peerName(),
- 50 QString::fromUtf8(line.constData(),
- 51 line.length()));
- 52 }
- 53 }
- 54
- 55 /* 发送的消息 */
- 56 void ChatClient::sendMessage(const QString &message)
- 57 {
- 58 qDebug()<<"Sending data in client: " + message;
- 59
- 60 QByteArray text = message.toUtf8() + '\n';
- 61 socket->write(text);
- 62 }
- 63
- 64 /* 主动连接 */
- 65 void ChatClient::connected()
- 66 {
- 67 emit connected(socket->peerName());
- 68 }
- 69
- 70 /* 主动断开连接*/
- 71 void ChatClient::disconnect() {
- 72 qDebug()<<"Going to disconnect in client";
- 73 if (socket) {
- 74 qDebug()<<"diconnecting...";
- 75 socket->close();
- 76 }
- 77 }
复制代码
chatclient.cpp文件主要是客户端的chatclient.h头文件的实现。代码参考Qt官方btchat例子,代码比较长,也有相应的注释了,大家自由查看。主要我们关注的是下面的代码。
第15~32行,我们需要开启客户端模式,那么我们需要将扫描服务器(手机蓝牙)的结果,实例化一个蓝牙socket,使用socket连接传入来的服务器信息,即可将本地蓝牙当作客户端,实现了客户端创建。
chatserver.h代码如下。
- /******************************************************************
- Copyright (C) 2015 The Qt Company Ltd.
- Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
- * @projectName 06_bluetooth_chat
- * @brief chatserver.h
- * @author Deng Zhimao
- * @email <a href="mailto:1252699831@qq.com" target="_blank">1252699831@qq.com</a>
- * @net <a href="www.openedv.com" target="_blank">www.openedv.com</a>
- * @date 2021-03-20
- *******************************************************************/
- 1 #ifndef CHATSERVER_H
- 2 #define CHATSERVER_H
- 3
- 4 #include <qbluetoothserviceinfo.h>
- 5 #include <qbluetoothaddress.h>
- 6 #include <QtCore/QObject>
- 7 #include <QtCore/QList>
- 8 #include <QBluetoothServer>
- 9 #include <QBluetoothSocket>
- 10
- 11
- 12 class ChatServer : public QObject
- 13 {
- 14 Q_OBJECT
- 15
- 16 public:
- 17 explicit ChatServer(QObject *parent = nullptr);
- 18 ~ChatServer();
- 19
- 20 /* 开启服务端 */
- 21 void startServer(const QBluetoothAddress &localAdapter = QBluetoothAddress());
- 22
- 23 /* 停止服务端 */
- 24 void stopServer();
- 25
- 26 public slots:
- 27 /* 发送消息 */
- 28 void sendMessage(const QString &message);
- 29
- 30 /* 服务端主动断开连接 */
- 31 void disconnect();
- 32
- 33 signals:
- 34 /* 接收到消息信号 */
- 35 void messageReceived(const QString &sender, const QString &message);
- 36
- 37 /* 客户端连接信号 */
- 38 void clientConnected(const QString &name);
- 39
- 40 /* 客户端断开连接信号 */
- 41 void clientDisconnected(const QString &name);
- 42
- 43 private slots:
- 44
- 45 /* 客户端连接 */
- 46 void clientConnected();
- 47
- 48 /* 客户端断开连接 */
- 49 void clientDisconnected();
- 50
- 51 /* 读socket */
- 52 void readSocket();
- 53
- 54 private:
- 55 /* 使用rfcomm协议 */
- 56 QBluetoothServer *rfcommServer;
- 57
- 58 /* 服务器蓝牙信息 */
- 59 QBluetoothServiceInfo serviceInfo;
- 60
- 61 /* 用于保存客户端socket */
- 62 QList<QBluetoothSocket *> clientSockets;
- 63
- 64 /* 用于保存客户端的名字 */
- 65 QList<QString> socketsPeername;
- 66 };
- 67
- 68 #endif // CHATSERVER_H
复制代码
chatserver.h文件主要是服务端的头文件,其中写一些接口,比如开启服务端,关闭服务端,接收信号与关闭信号等等。
chatserver.cpp代码如下。
- /******************************************************************
- Copyright (C) 2015 The Qt Company Ltd.
- Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
- * @projectName 06_bluetooth_chat
- * @brief chatserver.cpp
- * @author Deng Zhimao
- * @email <a href="mailto:1252699831@qq.com" target="_blank">1252699831@qq.com</a>
- * @net <a href="www.openedv.com" target="_blank">www.openedv.com</a>
- * @date 2021-03-20
- *******************************************************************/
- 1 #include "chatserver.h"
- 2
- 3 #include <qbluetoothserver.h>
- 4 #include <qbluetoothsocket.h>
- 5 #include <qbluetoothlocaldevice.h>
- 6
- 7 static const QLatin1String serviceUuid("e8e10f95-1a70-4b27-9ccf-02010264e9c8");
- 8 ChatServer::ChatServer(QObject *parent)
- 9 : QObject(parent), rfcommServer(0)
- 10 {
- 11 }
- 12
- 13 ChatServer::~ChatServer()
- 14 {
- 15 stopServer();
- 16 }
- 17
- 18 /* 开启服务端,设置服务端使用rfcomm协议与serviceInfo的一些属性 */
- 19 void ChatServer::startServer(const QBluetoothAddress& localAdapter)
- 20 {
- 21 if (rfcommServer)
- 22 return;
- 23
- 24 rfcommServer = new QBluetoothServer(QBluetoothServiceInfo::RfcommProtocol, this);
- 25 connect(rfcommServer, SIGNAL(newConnection()), this, SLOT(clientConnected()));
- 26 bool result = rfcommServer->listen(localAdapter);
- 27 if (!result) {
- 28 qWarning()<<"Cannot bind chat server to"<<localAdapter.toString();
- 29 return;
- 30 }
- 31
- 32 //serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceRecordHandle, (uint)0x00010010);
- 33
- 34 QBluetoothServiceInfo::Sequence classId;
- 35
- 36 classId<<QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::SerialPort));
- 37 serviceInfo.setAttribute(QBluetoothServiceInfo::BluetoothProfileDescriptorList,
- 38 classId);
- 39
- 40 classId.prepend(QVariant::fromValue(QBluetoothUuid(serviceUuid)));
- 41
- 42 serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId);
- 43 serviceInfo.setAttribute(QBluetoothServiceInfo::BluetoothProfileDescriptorList,classId);
- 44
- 45 serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceName, tr("Bt Chat Server"));
- 46 serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceDescription,
- 47 tr("Example bluetooth chat server"));
- 48 serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceProvider, tr("qt-project.org"));
- 49
- 50 serviceInfo.setServiceUuid(QBluetoothUuid(serviceUuid));
- 51
- 52 QBluetoothServiceInfo::Sequence publicBrowse;
- 53 publicBrowse<< QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::PublicBrowseGroup));
- 54 serviceInfo.setAttribute(QBluetoothServiceInfo::BrowseGroupList,
- 55 publicBrowse);
- 56
- 57 QBluetoothServiceInfo::Sequence protocolDescriptorList;
- 58 QBluetoothServiceInfo::Sequence protocol;
- 59 protocol<< QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap));
- 60 protocolDescriptorList.append(QVariant::fromValue(protocol));
- 61 protocol.clear();
- 62 protocol<< QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Rfcomm))
- 63 << QVariant::fromValue(quint8(rfcommServer->serverPort()));
- 64 protocolDescriptorList.append(QVariant::fromValue(protocol));
- 65 serviceInfo.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList,
- 66 protocolDescriptorList);
- 67
- 68 serviceInfo.registerService(localAdapter);
- 69 }
- 70
- 71 /* 停止服务端 */
- 72 void ChatServer::stopServer()
- 73 {
- 74 // Unregister service
- 75 serviceInfo.unregisterService();
- 76
- 77 // Close sockets
- 78 qDeleteAll(clientSockets);
- 79
- 80 // Close server
- 81 delete rfcommServer;
- 82 rfcommServer = 0;
- 83 }
- 84
- 85 /* 主动断开连接 */
- 86 void ChatServer::disconnect()
- 87 {
- 88 qDebug()<<"Going to disconnect in server";
- 89
- 90 foreach (QBluetoothSocket *socket, clientSockets) {
- 91 qDebug()<<"sending data in server!";
- 92 socket->close();
- 93 }
- 94 }
- 95
- 96 /* 发送消息 */
- 97 void ChatServer::sendMessage(const QString &message)
- 98 {
- 99 qDebug()<<"Going to send message in server: " << message;
- 100 QByteArray text = message.toUtf8() + '\n';
- 101
- 102 foreach (QBluetoothSocket *socket, clientSockets) {
- 103 qDebug()<<"sending data in server!";
- 104 socket->write(text);
- 105 }
- 106 qDebug()<<"server sending done!";
- 107 }
- 108
- 109 /* 客户端连接 */
- 110 void ChatServer::clientConnected()
- 111 {
- 112 qDebug()<<"clientConnected";
- 113
- 114 QBluetoothSocket *socket = rfcommServer->nextPendingConnection();
- 115 if (!socket)
- 116 return;
- 117
- 118 connect(socket, SIGNAL(readyRead()), this, SLOT(readSocket()));
- 119 connect(socket, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
- 120 clientSockets.append(socket);
- 121 socketsPeername.append(socket->peerName());
- 122 emit clientConnected(socket->peerName());
- 123 }
- 124
- 125 /* 客户端断开连接 */
- 126 void ChatServer::clientDisconnected()
- 127 {
- 128 QBluetoothSocket *socket = qobject_cast<QBluetoothSocket *>(sender());
- 129 if (!socket)
- 130 return;
- 131
- 132 if (clientSockets.count() != 0) {
- 133 QString peerName;
- 134
- 135 if (socket->peerName().isEmpty())
- 136 peerName = socketsPeername.at(clientSockets.indexOf(socket));
- 137 else
- 138 peerName = socket->peerName();
- 139
- 140 emit clientDisconnected(peerName);
- 141
- 142 clientSockets.removeOne(socket);
- 143 socketsPeername.removeOne(peerName);
- 144 }
- 145
- 146 socket->deleteLater();
- 147
- 148 }
- 149
- 150 /* 从Socket里读取数据 */
- 151 void ChatServer::readSocket()
- 152 {
- 153 QBluetoothSocket *socket = qobject_cast<QBluetoothSocket *>(sender());
- 154 if (!socket)
- 155 return;
- 156
- 157 while (socket->bytesAvailable()) {
- 158 QByteArray line = socket->readLine().trimmed();
- 159 qDebug()<<QString::fromUtf8(line.constData(), line.length())<<endl;
- 160 emit messageReceived(socket->peerName(),
- 161 QString::fromUtf8(line.constData(), line.length()));
- 162 qDebug()<<QString::fromUtf8(line.constData(), line.length())<<endl;
- 163 }
- 164 }
复制代码
chatserver.cpp文件主要是服务端的chatserver.h头文件的实现。代码也是参考Qt官方btchat例子,代码比较长,也有相应的注释了,大家自由查看。主要我们关注的是下面的代码。
第19~69行,我们需要开启服务端模式,那么我们需要将本地的蓝牙localAdapter的地址传入,创建一个QBluetoothServer对象rfcommServer。在19至69行代码很长,其中使用了serviceInfo.setAttribute()设置了许多参数,这个流程是官方给出的流程,我们只需要了解下就可以了。大体流程:使用了QBluetoothServiceInfo类允许访问服务端蓝牙服务的属性,其中有设置蓝牙的UUID为文件开头定义的serviceUuid,设置serviceUuid的目的是为了区分其他蓝牙,用于搜索此类型蓝牙,但是作用并不是很大,因为我们的手机并不一定开启了这个uuid标识。最后必须用registerService()启动蓝牙。
第36行,转换串行端口(SerialPort),转换成classId,然后再设置串行端口服务。通信原理就是串行端口连接到RFCOMM server channel。(PS:蓝牙使用的协议多且复杂,本教程并不能清晰解释这种原理,如果有错误,欢迎指出)。
remoteselector.h代码如下。
- /******************************************************************
- Copyright (C) 2015 The Qt Company Ltd.
- Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
- * @projectName 06_bluetooth_chat
- * @brief remoteselector.h
- * @author Deng Zhimao
- * @email <a href="mailto:1252699831@qq.com" target="_blank">1252699831@qq.com</a>
- * @net <a href="www.openedv.com" target="_blank">www.openedv.com</a>
- * @date 2021-03-20
- *******************************************************************/
- 1 #ifndef REMOTESELECTOR_H
- 2 #define REMOTESELECTOR_H
- 3
- 4 #include <qbluetoothuuid.h>
- 5 #include <qbluetoothserviceinfo.h>
- 6 #include <qbluetoothservicediscoveryagent.h>
- 7 #include <QListWidgetItem>
- 8
- 9 /* 声明一个蓝牙适配器类 */
- 10 class RemoteSelector : public QObject
- 11 {
- 12 Q_OBJECT
- 13
- 14 public:
- 15 explicit RemoteSelector(QBluetoothAddress&,
- 16 QObject *parent = nullptr);
- 17 ~RemoteSelector();
- 18
- 19 /* 开启发现蓝牙 */
- 20 void startDiscovery(const QBluetoothUuid &uuid);
- 21
- 22 /* 停止发现蓝牙 */
- 23 void stopDiscovery();
- 24
- 25 /* 蓝牙服务 */
- 26 QBluetoothServiceInfo service() const;
- 27
- 28 signals:
- 29 /* 找到新服务 */
- 30 void newServiceFound(QListWidgetItem*);
- 31
- 32 /* 完成 */
- 33 void finished();
- 34
- 35 private:
- 36 /* 蓝牙服务代理,用于发现蓝牙服务 */
- 37 QBluetoothServiceDiscoveryAgent *m_discoveryAgent;
- 38
- 39 /* 服务信息 */
- 40 QBluetoothServiceInfo m_serviceInfo;
- 41
- 42 private slots:
- 43 /* 服务发现完成 */
- 44 void serviceDiscovered(const QBluetoothServiceInfo &serviceInfo);
- 45
- 46 /* 蓝牙发现完成 */
- 47 void discoveryFinished();
- 48
- 49 public:
- 50 /* 键值类容器 */
- 51 QMap<QString, QBluetoothServiceInfo> m_discoveredServices;
- 52 };
- 53
- 54 #endif // REMOTESELECTOR_H
- 55
复制代码
remoteselector.h翻译成远程选择器,代码也是参考Qt官方btchat例子,这个头文件定义了开启蓝牙发现模式,蓝牙关闭发现模式,还有服务完成等等,代码有注释,请自由查看。
remoteselector.cpp代码如下。
- /******************************************************************
- Copyright (C) 2015 The Qt Company Ltd.
- Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
- * @projectName 06_bluetooth_chat
- * @brief remoteselector.cpp
- * @author Deng Zhimao
- * @email <a href="mailto:1252699831@qq.com" target="_blank">1252699831@qq.com</a>
- * @net <a href="www.openedv.com" target="_blank">www.openedv.com</a>
- * @date 2021-03-20
- *******************************************************************/
- 1 #include "remoteselector.h"
- 2
- 3 /* 初始化本地蓝牙 */
- 4 RemoteSelector::RemoteSelector(QBluetoothAddress &localAdapter, QObject *parent)
- 5 : QObject(parent)
- 6 {
- 7 m_discoveryAgent = new QBluetoothServiceDiscoveryAgent(localAdapter);
- 8
- 9 connect(m_discoveryAgent, SIGNAL(serviceDiscovered(QBluetoothServiceInfo)),
- 10 this, SLOT(serviceDiscovered(QBluetoothServiceInfo)));
- 11 connect(m_discoveryAgent, SIGNAL(finished()), this, SLOT(discoveryFinished()));
- 12 connect(m_discoveryAgent, SIGNAL(canceled()), this, SLOT(discoveryFinished()));
- 13 }
- 14
- 15 RemoteSelector::~RemoteSelector()
- 16 {
- 17 delete m_discoveryAgent;
- 18 }
- 19
- 20 /* 开启发现模式,这里无需设置过滤uuid,否则搜索不到手机
- 21 * uuid会过滤符合条件的uuid服务都会返回相应的蓝牙设备
- 22 */
- 23 void RemoteSelector::startDiscovery(const QBluetoothUuid &uuid)
- 24 {
- 25 Q_UNUSED(uuid);
- 26 qDebug()<<"startDiscovery";
- 27 if (m_discoveryAgent->isActive()) {
- 28 qDebug()<<"stop the searching first";
- 29 m_discoveryAgent->stop();
- 30 }
- 31
- 32 //m_discoveryAgent->setUuidFilter(uuid);
- 33 m_discoveryAgent->start(QBluetoothServiceDiscoveryAgent::FullDiscovery);
- 34 }
- 35
- 36 /* 停止发现 */
- 37 void RemoteSelector::stopDiscovery()
- 38 {
- 39 qDebug()<<"stopDiscovery";
- 40 if (m_discoveryAgent){
- 41 m_discoveryAgent->stop();
- 42 }
- 43 }
- 44
- 45 QBluetoothServiceInfo RemoteSelector::service() const
- 46 {
- 47 return m_serviceInfo;
- 48 }
- 49
- 50 /* 扫描蓝牙服务信息 */
- 51 void RemoteSelector::serviceDiscovered(const QBluetoothServiceInfo &serviceInfo)
- 52 {
- 53 #if 0
- 54 qDebug() << "Discovered service on"
- 55 << serviceInfo.device().name() << serviceInfo.device().address().toString();
- 56 qDebug() << "\tService name:" << serviceInfo.serviceName();
- 57 qDebug() << "\tDescription:"
- 58 << serviceInfo.attribute(QBluetoothServiceInfo::ServiceDescription).toString();
- 59 qDebug() << "\tProvider:"
- 60 << serviceInfo.attribute(QBluetoothServiceInfo::ServiceProvider).toString();
- 61 qDebug() << "\tL2CAP protocol service multiplexer:"
- 62 << serviceInfo.protocolServiceMultiplexer();
- 63 qDebug() << "\tRFCOMM server channel:" << serviceInfo.serverChannel();
- 64 #endif
- 65
- 66 QMapIterator<QString, QBluetoothServiceInfo> i(m_discoveredServices);
- 67 while (i.hasNext()){
- 68 i.next();
- 69 if (serviceInfo.device().address() == i.value().device().address()){
- 70 return;
- 71 }
- 72 }
- 73
- 74 QString remoteName;
- 75 if (serviceInfo.device().name().isEmpty())
- 76 remoteName = serviceInfo.device().address().toString();
- 77 else
- 78 remoteName = serviceInfo.device().name();
- 79
- 80 qDebug()<<"adding to the list....";
- 81 qDebug()<<"remoteName: "<< remoteName;
- 82 QListWidgetItem *item =
- 83 new QListWidgetItem(QString::fromLatin1("%1%2")
- 84 .arg(remoteName, serviceInfo.serviceName()));
- 85 m_discoveredServices.insert(remoteName, serviceInfo);
- 86 emit newServiceFound(item);
- 87 }
- 88
- 89 /* 发现完成 */
- 90 void RemoteSelector::discoveryFinished()
- 91 {
- 92 qDebug()<<"discoveryFinished";
- 93 emit finished();
- 94 }
复制代码
remoteselector .cpp是remoteselector.h的实现代码。主要看以下几点。
第4~13行,初始化本地蓝牙,实例化对象discoveryAgent(代理对象),蓝牙主要通过本地代理对象去扫描其他蓝牙。
第32行,这里官方Qt代码设计是过滤uuid。只有符合对应的uuid的蓝牙,才会返回结果。因为我们要扫描我们的手机,所以这里我们要把他注释掉。手机的uuid没有设置成设定的uuid,如果设置了uuid过滤,手机就扫描不出了。(uuid指的是唯一标识,手机蓝牙有很多uuid,不同的uuid有不同的作用,指示着不同的服务)。
其他代码都是一些逻辑性的代码,比较简单,请自由查看。
mainwindow.h代码如下。
mainwindow.h是整个代码重要的文件,这里使用了客户端类,服务端类和远程服务端类。前面介绍的客户端类,服务端类和远程服务端类都是为mainwindow.h服务的。我们在编程的时候可以不用改动客户端类,服务端类和远程服务端类了,直接像mainwindow.h一样使用它们的接口就可以编程了。
其中编者还在mainwindow.h使用了很多控件,这些控件都是界面组成的重要元素。并不复杂,如果看不懂界面布局,或者理解不了界面布局,请回到本教程的第七章学习基础,本教程不再一一说明这种简单的布局了。
mainwindow.cpp代码如下。
- /******************************************************************
- Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
- * @projectName 06_bluetooth_chat
- * @brief mainwindow.cpp
- * @author Deng Zhimao
- * @email <a href="mailto:1252699831@qq.com" target="_blank">1252699831@qq.com</a>
- * @net <a href="www.openedv.com" target="_blank">www.openedv.com</a>
- * @date 2021-03-19
- *******************************************************************/
- 1 #include "mainwindow.h"
- 2 #include "remoteselector.h"
- 3 #include "chatserver.h"
- 4 #include "chatclient.h"
- 5 #include <qbluetoothuuid.h>
- 6 #include <qbluetoothserver.h>
- 7 #include <qbluetoothservicediscoveryagent.h>
- 8 #include <qbluetoothdeviceinfo.h>
- 9 #include <qbluetoothlocaldevice.h>
- 10 #include <QGuiApplication>
- 11 #include <QScreen>
- 12 #include <QRect>
- 13 #include <QTimer>
- 14 #include <QDebug>
- 15 #include <QTabBar>
- 16 #include <QHeaderView>
- 17 #include <QTableView>
- 18
- 19
- 20 static const QLatin1String
- 21 serviceUuid("e8e10f95-1a70-4b27-9ccf-02010264e9c8");
- 22
- 23 MainWindow::MainWindow(QWidget *parent)
- 24 : QMainWindow(parent)
- 25 {
- 26 /* 本地蓝牙初始化 */
- 27 localAdapterInit();
- 28
- 29 /* 界面布局初始化 */
- 30 layoutInit();
- 31 }
- 32
- 33 MainWindow::~MainWindow()
- 34 {
- 35 qDeleteAll(clients);
- 36 delete server;
- 37 }
- 38
- 39 /* 初始化本地蓝牙,作为服务端 */
- 40 void MainWindow::localAdapterInit()
- 41 {
- 42 /* 查找本地蓝牙的个数 */
- 43 localAdapters = QBluetoothLocalDevice::allDevices();
- 44 qDebug() << "localAdapter: " << localAdapters.count();
- 45
- 46 QBluetoothLocalDevice localDevice;
- 47 localDevice.setHostMode(QBluetoothLocalDevice::HostDiscoverable);
- 48
- 49 QBluetoothAddress adapter = QBluetoothAddress();
- 50 remoteSelector = new RemoteSelector(adapter, this);
- 51 connect(remoteSelector,
- 52 SIGNAL(newServiceFound(QListWidgetItem*)),
- 53 this, SLOT(newServiceFound(QListWidgetItem*)));
- 54
- 55 /* 初始化服务端 */
- 56 server = new ChatServer(this);
- 57
- 58 connect(server, SIGNAL(clientConnected(QString)),
- 59 this, SLOT(connected(QString)));
- 60
- 61 connect(server, SIGNAL(clientDisconnected(QString)),
- 62 this, SLOT(disconnected(QString)));
- 63
- 64 connect(server, SIGNAL(messageReceived(QString, QString)),
- 65 this, SLOT(showMessage(QString, QString)));
- 66
- 67 connect(this, SIGNAL(sendMessage(QString)),
- 68 server, SLOT(sendMessage(QString)));
- 69
- 70 connect(this, SIGNAL(disconnect()),
- 71 server, SLOT(disconnect()));
- 72
- 73 server->startServer();
- 74
- 75 /* 获取本地蓝牙的名称 */
- 76 localName = QBluetoothLocalDevice().name();
- 77 }
- 78
- 79 void MainWindow::layoutInit()
- 80 {
- 81 /* 获取屏幕的分辨率,Qt官方建议使用这
- 82 * 种方法获取屏幕分辨率,防上多屏设备导致对应不上
- 83 * 注意,这是获取整个桌面系统的分辨率
- 84 */
- 85 QList <QScreen *> list_screen = QGuiApplication::screens();
- 86
- 87 /* 如果是ARM平台,直接设置大小为屏幕的大小 */
- 88 #if __arm__
- 89 /* 重设大小 */
- 90 this->resize(list_screen.at(0)->geometry().width(),
- 91 list_screen.at(0)->geometry().height());
- 92 #else
- 93 /* 否则则设置主窗体大小为800x480 */
- 94 this->resize(800, 480);
- 95 #endif
- 96
- 97 /* 主视图 */
- 98 tabWidget = new QTabWidget(this);
- 99
- 100 /* 设置主窗口居中视图为tabWidget */
- 101 setCentralWidget(tabWidget);
- 102
- 103 /* 页面一对象实例化 */
- 104 vBoxLayout[0] = new QVBoxLayout();
- 105 hBoxLayout[0] = new QHBoxLayout();
- 106 pageWidget[0] = new QWidget();
- 107 subWidget[0] = new QWidget();
- 108 listWidget = new QListWidget();
- 109 /* 0为扫描按钮,1为连接按钮 */
- 110 pushButton[0] = new QPushButton();
- 111 pushButton[1] = new QPushButton();
- 112 pushButton[2] = new QPushButton();
- 113 pushButton[3] = new QPushButton();
- 114 pushButton[4] = new QPushButton();
- 115
- 116 /* 页面二对象实例化 */
- 117 hBoxLayout[1] = new QHBoxLayout();
- 118 vBoxLayout[1] = new QVBoxLayout();
- 119 subWidget[1] = new QWidget();
- 120 textBrowser = new QTextBrowser();
- 121 lineEdit = new QLineEdit();
- 122 pushButton[2] = new QPushButton();
- 123 pageWidget[1] = new QWidget();
- 124
- 125
- 126 tabWidget->addTab(pageWidget[1], "蓝牙聊天");
- 127 tabWidget->addTab(pageWidget[0], "蓝牙列表");
- 128
- 129 /* 页面一 */
- 130 vBoxLayout[0]->addWidget(pushButton[0]);
- 131 vBoxLayout[0]->addWidget(pushButton[1]);
- 132 vBoxLayout[0]->addWidget(pushButton[2]);
- 133 vBoxLayout[0]->addWidget(pushButton[3]);
- 134 subWidget[0]->setLayout(vBoxLayout[0]);
- 135 hBoxLayout[0]->addWidget(listWidget);
- 136 hBoxLayout[0]->addWidget(subWidget[0]);
- 137 pageWidget[0]->setLayout(hBoxLayout[0]);
- 138 pushButton[0]->setMinimumSize(120, 40);
- 139 pushButton[1]->setMinimumSize(120, 40);
- 140 pushButton[2]->setMinimumSize(120, 40);
- 141 pushButton[3]->setMinimumSize(120, 40);
- 142 pushButton[0]->setText("开始扫描");
- 143 pushButton[1]->setText("停止扫描");
- 144 pushButton[2]->setText("连接");
- 145 pushButton[3]->setText("断开");
- 146
- 147 /* 页面二 */
- 148 hBoxLayout[1]->addWidget(lineEdit);
- 149 hBoxLayout[1]->addWidget(pushButton[4]);
- 150 subWidget[1]->setLayout(hBoxLayout[1]);
- 151 vBoxLayout[1]->addWidget(textBrowser);
- 152 vBoxLayout[1]->addWidget(subWidget[1]);
- 153 pageWidget[1]->setLayout(vBoxLayout[1]);
- 154 pushButton[4]->setMinimumSize(120, 40);
- 155 pushButton[4]->setText("发送");
- 156 lineEdit->setMinimumHeight(40);
- 157 lineEdit->setText("正点原子论坛网址<a href="www.openedv.com" target="_blank">www.openedv.com</a>");
- 158
- 159 /* 设置表头的大小 */
- 160 QString str = tr("QTabBar::tab {height:40; width:%1};")
- 161 .arg(this->width()/2);
- 162 tabWidget->setStyleSheet(str);
- 163
- 164 /* 开始搜寻蓝牙 */
- 165 connect(pushButton[0], SIGNAL(clicked()),
- 166 this, SLOT(searchForDevices()));
- 167
- 168 /* 停止搜寻蓝牙 */
- 169 connect(pushButton[1], SIGNAL(clicked()),
- 170 this, SLOT(stopSearch()));
- 171
- 172 /* 点击连接按钮,本地蓝牙作为客户端去连接外界的服务端 */
- 173 connect(pushButton[2], SIGNAL(clicked()),
- 174 this, SLOT(connectToDevice()));
- 175
- 176 /* 点击断开连接按钮,断开连接 */
- 177 connect(pushButton[3], SIGNAL(clicked()),
- 178 this, SLOT(toDisconnected()));
- 179
- 180 /* 发送消息 */
- 181 connect(pushButton[4], SIGNAL(clicked()),
- 182 this, SLOT(sendMessage()));
- 183 }
- 184
- 185 /* 作为客户端去连接 */
- 186 void MainWindow::connectToDevice()
- 187 {
- 188 if (listWidget->currentRow() == -1)
- 189 return;
- 190
- 191 QString name = listWidget->currentItem()->text();
- 192 qDebug() << "Connecting to " << name;
- 193
- 194 // Trying to get the service
- 195 QBluetoothServiceInfo service;
- 196 QMapIterator<QString,QBluetoothServiceInfo>
- 197 i(remoteSelector->m_discoveredServices);
- 198 bool found = false;
- 199 while (i.hasNext()){
- 200 i.next();
- 201
- 202 QString key = i.key();
- 203
- 204 /* 判断连接的蓝牙名称是否在发现的设备里 */
- 205 if (key == name) {
- 206 qDebug() << "The device is found";
- 207 service = i.value();
- 208 qDebug() << "value: " << i.value().device().address();
- 209 found = true;
- 210 break;
- 211 }
- 212 }
- 213
- 214 /* 如果找到,则连接设备 */
- 215 if (found) {
- 216 qDebug() << "Going to create client";
- 217 ChatClient *client = new ChatClient(this);
- 218 qDebug() << "Connecting...";
- 219
- 220 connect(client, SIGNAL(messageReceived(QString,QString)),
- 221 this, SLOT(showMessage(QString,QString)));
- 222 connect(client, SIGNAL(disconnected()),
- 223 this, SLOT(clientDisconnected()));;
- 224 connect(client, SIGNAL(connected(QString)),
- 225 this, SLOT(connected(QString)));
- 226 connect(this, SIGNAL(sendMessage(QString)),
- 227 client, SLOT(sendMessage(QString)));
- 228 connect(this, SIGNAL(disconnect()),
- 229 client, SLOT(disconnect()));
- 230
- 231 qDebug() << "Start client";
- 232 client->startClient(service);
- 233
- 234 clients.append(client);
- 235 }
- 236 }
- 237
- 238 /* 本地蓝牙选择,默认使用第一个蓝牙 */
- 239 int MainWindow::adapterFromUserSelection() const
- 240 {
- 241 int result = 0;
- 242 QBluetoothAddress newAdapter = localAdapters.at(0).address();
- 243 return result;
- 244 }
- 245
- 246 /* 开始搜索 */
- 247 void MainWindow::searchForDevices()
- 248 {
- 249 /* 先清空 */
- 250 listWidget->clear();
- 251 qDebug() << "search for devices!";
- 252 if (remoteSelector) {
- 253 delete remoteSelector;
- 254 remoteSelector = NULL;
- 255 }
- 256
- 257 QBluetoothAddress adapter = QBluetoothAddress();
- 258 remoteSelector = new RemoteSelector(adapter, this);
- 259
- 260 connect(remoteSelector,
- 261 SIGNAL(newServiceFound(QListWidgetItem*)),
- 262 this, SLOT(newServiceFound(QListWidgetItem*)));
- 263
- 264 remoteSelector->m_discoveredServices.clear();
- 265 remoteSelector->startDiscovery(QBluetoothUuid(serviceUuid));
- 266 connect(remoteSelector, SIGNAL(finished()),
- 267 this, SIGNAL(discoveryFinished()));
- 268 }
- 269
- 270 /* 停止搜索 */
- 271 void MainWindow::stopSearch()
- 272 {
- 273 qDebug() << "Going to stop discovery...";
- 274 if (remoteSelector) {
- 275 remoteSelector->stopDiscovery();
- 276 }
- 277 }
- 278
- 279 /* 找到蓝牙服务 */
- 280 void MainWindow::newServiceFound(QListWidgetItem *item)
- 281 {
- 282 /* 设置项的大小 */
- 283 item->setSizeHint(QSize(listWidget->width(), 50));
- 284
- 285 /* 添加项 */
- 286 listWidget->addItem(item);
- 287
- 288 /* 设置当前项 */
- 289 listWidget->setCurrentRow(listWidget->count() - 1);
- 290
- 291 qDebug() << "newServiceFound";
- 292
- 293 // get all of the found devices
- 294 QStringList list;
- 295
- 296 QMapIterator<QString, QBluetoothServiceInfo>
- 297 i(remoteSelector->m_discoveredServices);
- 298 while (i.hasNext()){
- 299 i.next();
- 300 qDebug() << "key: " << i.key();
- 301 qDebug() << "value: " << i.value().device().address();
- 302 list << i.key();
- 303 }
- 304
- 305 qDebug() << "list count: " << list.count();
- 306
- 307 emit newServicesFound(list);
- 308 }
- 309
- 310 /* 已经连接 */
- 311 void MainWindow::connected(const QString &name)
- 312 {
- 313 textBrowser->insertPlainText(tr("%1:已连接\n").arg(name));
- 314 tabWidget->setCurrentIndex(0);
- 315 textBrowser->moveCursor(QTextCursor::End);
- 316 }
- 317
- 318 /* 接收消息 */
- 319 void MainWindow::showMessage(const QString &sender,
- 320 const QString &message)
- 321 {
- 322 textBrowser->insertPlainText(QString::fromLatin1("%1: %2\n")
- 323 .arg(sender, message));
- 324 tabWidget->setCurrentIndex(0);
- 325 textBrowser->moveCursor(QTextCursor::End);
- 326 }
- 327
- 328 /* 发送消息 */
- 329 void MainWindow::sendMessage()
- 330 {
- 331 showMessage(localName, lineEdit->text());
- 332 emit sendMessage(lineEdit->text());
- 333 }
- 334
- 335 /* 作为客户端断开连接 */
- 336 void MainWindow::clientDisconnected()
- 337 {
- 338 ChatClient *client = qobject_cast<ChatClient *>(sender());
- 339 if (client) {
- 340 clients.removeOne(client);
- 341 client->deleteLater();
- 342 }
- 343
- 344 tabWidget->setCurrentIndex(0);
- 345 textBrowser->moveCursor(QTextCursor::End);
- 346 }
- 347
- 348 /* 主动断开连接 */
- 349 void MainWindow::toDisconnected()
- 350 {
- 351 emit disconnect();
- 352 textBrowser->moveCursor(QTextCursor::End);
- 353 tabWidget->setCurrentIndex(0);
- 354 }
- 355
- 356 /* 作为服务端时,客户端断开连接 */
- 357 void MainWindow::disconnected(const QString &name)
- 358 {
- 359 textBrowser->insertPlainText(tr("%1:已断开\n").arg(name));
- 360 tabWidget->setCurrentIndex(0);
- 361 textBrowser->moveCursor(QTextCursor::End);
- 362 }
复制代码
mainwindow.cpp则是整个项目的核心文件,包括处理界面点击的事件,客户端连接,服务端连接,扫描蓝牙,断开蓝牙和连接蓝牙等。设计这样的一个逻辑界面并不难,只要我们前面第七章Qt控件打下了基础。上面的代码注释详细,请自由查看。
20.3 程序运行效果
本例程运行后,默认开启蓝牙的服务端模式,可以用手机安装蓝牙调试软件(安卓手机如蓝牙调试宝、蓝牙串口助手)。当我们点击蓝牙列表页面时,点击扫描后请等待扫描的结果,选中需要连接的蓝牙再点击连接。
下面程序效果是Ubuntu虚拟机上连接USB蓝牙模块,用手机连接后运行的蓝牙聊天第一页效果图。
下面程序效果是Ubuntu虚拟机上连接USB蓝牙模块运行的蓝牙聊天第二页效果图。
安卓手机可以用蓝牙调试宝等软件进行配对连接。IOS手机请下载某些蓝牙调试软件测试即可。手机接收到的消息如下。
在编者测试的过程中,发现在Ubuntu上运行蓝牙聊天程序不太好用,需要开启扫描后,才能连接的上,而且接收的消息反应比较慢,有可能是虚拟机的原因吧。不过在正点原子I.MX6U开发板上运行没有问题。先按照详细请看【正点原子】I.MX6U用户快速体验V1.x.pdf的第3.29小节蓝牙测试开启蓝牙,启用蓝牙被扫描后,先进行配对,手机用蓝牙调试软件就可以连接上进行聊天了。
注意:本程序需要确保蓝牙能正常使用的情况下才能运行,默认使用第一个蓝牙,如果Ubuntu上查看有两个蓝牙,请不要插着USB蓝牙启动电脑,先等Ubuntu启动后再插蓝牙模块。连接前应先配对,连接不上的原因可能或者蓝牙质量问题,或者系统里的软件没有开启蓝牙,或者使用的手机蓝牙调试软件不支持SPP(串行端口)蓝牙调试等,请退出重试等。程序仅供学习与参考。
|
阿莫论坛20周年了!感谢大家的支持与爱护!!
该献的血还是要献的。你不献他不献。难道让我去献? --- 出自坛友:lovejp1981
|