【正点原子Linux连载】第二十章 USB Bluetooth 摘自【正点原子】I.MX6U嵌入式Qt开发指南V1.0.2

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         [email protected]
    * @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         [email protected]
    * @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         [email protected]
    * @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         [email protected]
    * @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         [email protected]
    * @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         [email protected]
    * @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         [email protected]
    * @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         [email protected]
    * @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蓝牙模块,用手机连接后运行的蓝牙聊天第一页效果图。
【正点原子Linux连载】第二十章 USB Bluetooth 摘自【正点原子】I.MX6U嵌入式Qt开发指南V1.0.2_第1张图片

下面程序效果是Ubuntu虚拟机上连接USB蓝牙模块运行的蓝牙聊天第二页效果图。
【正点原子Linux连载】第二十章 USB Bluetooth 摘自【正点原子】I.MX6U嵌入式Qt开发指南V1.0.2_第2张图片

安卓手机可以用蓝牙调试宝等软件进行配对连接。IOS手机请下载某些蓝牙调试软件测试即可。手机接收到的消息如下。

【正点原子Linux连载】第二十章 USB Bluetooth 摘自【正点原子】I.MX6U嵌入式Qt开发指南V1.0.2_第3张图片

在编者测试的过程中,发现在Ubuntu上运行蓝牙聊天程序不太好用,需要开启扫描后,才能连接得上,而且接收的消息反应比较慢,有可能是虚拟机的原因吧。不过在正点原子I.MX6U开发板上运行没有问题。先按照详细请看【正点原子】I.MX6U用户快速体验V1.x.pdf的第3.29小节蓝牙测试开启蓝牙,启用蓝牙被扫描后,先进行配对,手机用蓝牙调试软件就可以连接上进行聊天了。
注意:本程序需要在确保蓝牙能正常使用的情况下才能运行,默认使用第一个蓝牙,如果Ubuntu上查看有两个蓝牙,请不要插着USB蓝牙启动电脑,先等Ubuntu启动后再插蓝牙模块。连接前应先配对,连接不上的原因可能或者蓝牙质量问题,或者系统里的软件没有开启蓝牙,或者使用的手机蓝牙调试软件不支持SPP(串行端口)蓝牙调试等,请退出重试等。程序仅供学习与参考。

你可能感兴趣的:(LINUX,qt,linux,开发语言)