1)实验平台:正点原子阿尔法Linux开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html
4)对正点原子Linux感兴趣的同学可以加群讨论:935446741
第十九章 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 1252699831@qq.com* @net www.openedv.com* @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 1252699831@qq.com* @net www.openedv.com* @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 1252699831@qq.com* @net www.openedv.com* @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 1252699831@qq.com* @net www.openedv.com* @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 1252699831@qq.com* @net www.openedv.com* @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 1252699831@qq.com* @net www.openedv.com* @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代码如下。
/******************************************************************Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.* @projectName 06_bluetooth_chat* @brief mainwindow.h* @author Deng Zhimao* @email 1252699831@qq.com* @net www.openedv.com* @date 2021-03-19*******************************************************************/
1 #ifndef MAINWINDOW_H
2 #define MAINWINDOW_H
3
4 #include <QMainWindow>
5 #include <qbluetoothserviceinfo.h>
6 #include <qbluetoothsocket.h>
7 #include <qbluetoothhostinfo.h>
8 #include <QDebug>
9 #include <QTabWidget>
10 #include <QHBoxLayout>
11 #include <QVBoxLayout>
12 #include <QPushButton>
13 #include <QListWidget>
14 #include <QTextBrowser>
15 #include <QLineEdit>
16
17 class ChatServer;
18 class ChatClient;
19 class RemoteSelector;
20
21 class MainWindow : public QMainWindow
22 {
23 Q_OBJECT
24
25 public:
26 MainWindow(QWidget *parent = nullptr);
27 ~MainWindow();
28
29 public:
30 /* 暴露的接口,主动连接设备*/
31 Q_INVOKABLE void connectToDevice();
32
33 signals:
34 /* 发送消息信号 */
35 void sendMessage(const QString &message);
36
37 /* 连接断开信号 */
38 void disconnect();
39
40 /* 发现完成信号 */
41 void discoveryFinished();
42
43 /* 找到新服务信号 */
44 void newServicesFound(const QStringList &list);
45
46 public slots:
47 /* 停止搜索 */
48 void searchForDevices();
49
50 /* 开始搜索 */
51 void stopSearch();
52
53 /* 找到新服务 */
54 void newServiceFound(QListWidgetItem*);
55
56 /* 已连接 */
57 void connected(const QString &name);
58
59 /* 显示消息 */
60 void showMessage(const QString &sender, const QString &message);
61
62 /* 发送消息 */
63 void sendMessage();
64
65 /* 作为客户端断开连接 */
66 void clientDisconnected();
67
68 /* 主动断开连接 */
69 void toDisconnected();
70
71 /* 作为服务端时,客户端断开连接 */
72 void disconnected(const QString &name);
73
74 private:
75 /* 选择本地蓝牙 */
76 int adapterFromUserSelection() const;
77
78 /* 本地蓝牙的Index */
79 int currentAdapterIndex;
80
81 /* 蓝牙本地适配器初始化 */
82 void localAdapterInit();
83
84 /* 布局初始化 */
85 void layoutInit();
86
87 /* 服务端*/
88 ChatServer *server;
89
90 /* 多个客户端 */
91 QList<ChatClient *> clients;
92
93 /* 远程选择器,使用本地蓝牙去搜索蓝牙,可过滤蓝牙等 */
94 RemoteSelector *remoteSelector;
95
96 /* 本地蓝牙 */
97 QList<QBluetoothHostInfo> localAdapters;
98
99 /* 本地蓝牙名称 */
100 QString localName;
101
102 /* tabWidget视图,用于切换页面 */
103 QTabWidget *tabWidget;
104
105 /* 3个按钮,扫描按钮,连接按钮,发送按钮 */
106 QPushButton *pushButton[5];
107
108 /* 2个垂直布局,一个用于页面一,另一个用于页面二 */
109 QVBoxLayout *vBoxLayout[2];
110
111 /* 2个水平布局,一个用于页面一,另一个用于页面二 */
112 QHBoxLayout *hBoxLayout[2];
113
114 /* 页面一和页面二容器 */
115 QWidget *pageWidget[2];
116
117 /* 用于布局, pageWidget包含subWidget */
118 QWidget *subWidget[2];
119
120 /* 蓝牙列表 */
121 QListWidget *listWidget;
122
123 /* 显示对话的内容 */
124 QTextBrowser *textBrowser;
125
126 /* 发送消息输入框 */
127 QLineEdit *lineEdit;
128
129 };
130 #endif // 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 1252699831@qq.com* @net www.openedv.com* @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("正点原子论坛网址www.openedv.com");
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(串行端口)蓝牙调试等,请退出重试等。程序仅供学习与参考。