在Ubuntu手机上的Bluetooth Chat应用实例

185 篇文章 7 订阅
2 篇文章 0 订阅

在今天的例程中,我们将重点介绍如何使用Ubuntu手机SDK所提供的SDK来创建一个可以聊天的应用.通过这个例程,我们来展示如何利用Bluetooth API接口在两个Ubuntu手机或Ubuntu电脑上进行聊天.


    


1)创建一个Bluetooth Chat server


chatserver.cpp

#include "chatserver.h"

#include <qbluetoothserver.h>
#include <qbluetoothsocket.h>
#include <qbluetoothlocaldevice.h>

static const QLatin1String serviceUuid("e8e10f95-1a70-4b27-9ccf-02010264e9c8");

ChatServer::ChatServer(QObject *parent)
:   QObject(parent), rfcommServer(0)
{
}

ChatServer::~ChatServer()
{
    stopServer();
}

void ChatServer::startServer(const QBluetoothAddress& localAdapter)
{
    if (rfcommServer)
        return;

    rfcommServer = new QBluetoothServer(QBluetoothServiceInfo::RfcommProtocol, this);
    connect(rfcommServer, SIGNAL(newConnection()), this, SLOT(clientConnected()));
    bool result = rfcommServer->listen(localAdapter);
    if (!result) {
        qWarning() << "Cannot bind chat server to" << localAdapter.toString();
        return;
    }

    //serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceRecordHandle, (uint)0x00010010);

    QBluetoothServiceInfo::Sequence classId;

    classId << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::SerialPort));
    serviceInfo.setAttribute(QBluetoothServiceInfo::BluetoothProfileDescriptorList,
                             classId);

    classId.prepend(QVariant::fromValue(QBluetoothUuid(serviceUuid)));

    serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId);
    serviceInfo.setAttribute(QBluetoothServiceInfo::BluetoothProfileDescriptorList,classId);

    serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceName, tr("Bt Chat Server"));
    serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceDescription,
                             tr("Example bluetooth chat server"));
    serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceProvider, tr("qt-project.org"));

    serviceInfo.setServiceUuid(QBluetoothUuid(serviceUuid));

    QBluetoothServiceInfo::Sequence publicBrowse;
    publicBrowse << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::PublicBrowseGroup));
    serviceInfo.setAttribute(QBluetoothServiceInfo::BrowseGroupList,
                             publicBrowse);

    QBluetoothServiceInfo::Sequence protocolDescriptorList;
    QBluetoothServiceInfo::Sequence protocol;
    protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap));
    protocolDescriptorList.append(QVariant::fromValue(protocol));
    protocol.clear();
    protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Rfcomm))
             << QVariant::fromValue(quint8(rfcommServer->serverPort()));
    protocolDescriptorList.append(QVariant::fromValue(protocol));
    serviceInfo.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList,
                             protocolDescriptorList);

    serviceInfo.registerService(localAdapter);
}

void ChatServer::stopServer()
{
    // Unregister service
    serviceInfo.unregisterService();

    // Close sockets
    qDeleteAll(clientSockets);

    // Close server
    delete rfcommServer;
    rfcommServer = 0;
}

void ChatServer::disconnect()
{
    qDebug() << "Going to disconnect in server";

    foreach (QBluetoothSocket *socket, clientSockets) {
        qDebug() << "sending data in server!";
        socket->close();
    }
}

void ChatServer::sendMessage(const QString &message)
{
    qDebug() << "Going to send message in server: " << message;
    QByteArray text = message.toUtf8() + '\n';

    foreach (QBluetoothSocket *socket, clientSockets) {
        qDebug() << "sending data in server!";
        socket->write(text);
    }
    qDebug() << "server sending done!";
}

void ChatServer::clientConnected()
{
    qDebug() << "clientConnected";

    QBluetoothSocket *socket = rfcommServer->nextPendingConnection();
    if (!socket)
        return;

    connect(socket, SIGNAL(readyRead()), this, SLOT(readSocket()));
    connect(socket, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
    clientSockets.append(socket);
    emit clientConnected(socket->peerName());
}

void ChatServer::clientDisconnected()
{
    QBluetoothSocket *socket = qobject_cast<QBluetoothSocket *>(sender());
    if (!socket)
        return;

    emit clientDisconnected(socket->peerName());

    clientSockets.removeOne(socket);

    socket->deleteLater();
}

void ChatServer::readSocket()
{
    QBluetoothSocket *socket = qobject_cast<QBluetoothSocket *>(sender());
    if (!socket)
        return;

    while (socket->canReadLine()) {
        QByteArray line = socket->readLine().trimmed();
        emit messageReceived(socket->peerName(),
                             QString::fromUtf8(line.constData(), line.length()));
    }
}


在这里我们通过 QBluetoothServer来创建一个Bluetooth基于RFCOMM协议的server.我们发布一个基于这个协议的server.其它想连接这个服务器的client,必须寻找我们在这里所定义的ServiceUuid:

 classId << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::SerialPort));
    serviceInfo.setAttribute(QBluetoothServiceInfo::BluetoothProfileDescriptorList,
                             classId);

    classId.prepend(QVariant::fromValue(QBluetoothUuid(serviceUuid)));

这里所定义的serviceUuid:

static const QLatin1String serviceUuid("e8e10f95-1a70-4b27-9ccf-02010264e9c8");

2)创建一个Bluetooth client


我们可以创建一个Bluetooth的client用来发起一个向Bluetooth server的连接请求.

chatclient.cpp


#include "chatclient.h"

#include <qbluetoothsocket.h>

ChatClient::ChatClient(QObject *parent)
:   QObject(parent), socket(0)
{
}

ChatClient::~ChatClient()
{
    stopClient();
}

void ChatClient::startClient(const QBluetoothServiceInfo &remoteService)
{
    if (socket)
        return;

    // Connect to service
    socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol);
    qDebug() << "Create socket";
    socket->connectToService(remoteService);
    qDebug() << "ConnectToService done";

    connect(socket, SIGNAL(readyRead()), this, SLOT(readSocket()));
    connect(socket, SIGNAL(connected()), this, SLOT(connected()));
    connect(socket, SIGNAL(disconnected()), this, SIGNAL(disconnected()));
}

void ChatClient::stopClient()
{
    delete socket;
    socket = 0;
}

void ChatClient::readSocket()
{
    if (!socket)
        return;

    while (socket->canReadLine()) {
        QByteArray line = socket->readLine();
        emit messageReceived(socket->peerName(),
                             QString::fromUtf8(line.constData(), line.length()));
    }
}

void ChatClient::sendMessage(const QString &message)
{
    qDebug() << "Sending data in client: " + message;

    QByteArray text = message.toUtf8() + '\n';
    socket->write(text);
}

void ChatClient::connected()
{
    emit connected(socket->peerName());
}

void ChatClient::disconnect() {
    qDebug() << "Going to disconnect in client";
    if ( socket ) {
        qDebug() << "diconnecting...";
        socket->close();
    }
}

当我们发生一个请求的时候,我们可以通过如下的方式来连接:

chat.cpp

void Chat::connectToDevice(QString name)
{
    qDebug() << "Connecting to " << name;

    // Trying to get the service
    QBluetoothServiceInfo service;
    QMapIterator<QString, QBluetoothServiceInfo> i(remoteSelector->m_discoveredServices);
    bool found = false;
    while (i.hasNext()){
        i.next();
        QString key = i.key();
        if ( key == name ) {
            qDebug() << "The device is found";
            service = i.value();
            qDebug() << "value: " << i.value().device().address();
            found = true;
            break;
        }
    }

    if ( found ) {
        qDebug() << "Going to create client";
        ChatClient *client = new ChatClient(this);
        qDebug() << "Connecting...";

        connect(client, SIGNAL(messageReceived(QString,QString)),
                this, SIGNAL(showMessage(QString,QString)));
        connect(client, SIGNAL(disconnected()), this, SIGNAL(clientDisconnected()));
        connect(client, SIGNAL(disconnected()), this, SLOT(clientIsDisconnected()));
        connect(client, SIGNAL(connected(QString)), this, SIGNAL(connected(QString)));
        connect(this, SIGNAL(sendMessage(QString)), client, SLOT(sendMessage(QString)));
        connect(this, SIGNAL(disconnect()), client, SLOT(disconnect()));

        qDebug() << "Start client";
        client->startClient(service);

        clients.append(client);
    }
}



3)扫描Bluetooth device


我们可以通过如下的方法来扫描附件的Bluetooth设备:

remoteselector.cpp


#include <qbluetoothdeviceinfo.h>
#include <qbluetoothaddress.h>
#include <qbluetoothlocaldevice.h>

#include "remoteselector.h"

QT_USE_NAMESPACE

RemoteSelector::RemoteSelector(QBluetoothAddress &localAdapter, QObject *parent)
: QObject(parent)
{
    m_discoveryAgent = new QBluetoothServiceDiscoveryAgent(localAdapter);

    connect(m_discoveryAgent, SIGNAL(serviceDiscovered(QBluetoothServiceInfo)),
            this, SLOT(serviceDiscovered(QBluetoothServiceInfo)));
    connect(m_discoveryAgent, SIGNAL(finished()), this, SLOT(discoveryFinished()));
    connect(m_discoveryAgent, SIGNAL(canceled()), this, SLOT(discoveryFinished()));
}

RemoteSelector::~RemoteSelector()
{
    delete m_discoveryAgent;
}

void RemoteSelector::startDiscovery(const QBluetoothUuid &uuid)
{
    qDebug() << "startDiscovery";
    if (m_discoveryAgent->isActive()) {
        qDebug() << "stop the searching first";
        m_discoveryAgent->stop();
    }

    m_discoveryAgent->setUuidFilter(uuid);
    m_discoveryAgent->start(QBluetoothServiceDiscoveryAgent::FullDiscovery);

}

void RemoteSelector::stopDiscovery()
{
    qDebug() << "stopDiscovery";
    if (m_discoveryAgent){
        m_discoveryAgent->stop();
    }
}

QBluetoothServiceInfo RemoteSelector::service() const
{
    return m_service;
}

void RemoteSelector::serviceDiscovered(const QBluetoothServiceInfo &serviceInfo)
{
#if 0
    qDebug() << "Discovered service on"
             << serviceInfo.device().name() << serviceInfo.device().address().toString();
    qDebug() << "\tService name:" << serviceInfo.serviceName();
    qDebug() << "\tDescription:"
             << serviceInfo.attribute(QBluetoothServiceInfo::ServiceDescription).toString();
    qDebug() << "\tProvider:"
             << serviceInfo.attribute(QBluetoothServiceInfo::ServiceProvider).toString();
    qDebug() << "\tL2CAP protocol service multiplexer:"
             << serviceInfo.protocolServiceMultiplexer();
    qDebug() << "\tRFCOMM server channel:" << serviceInfo.serverChannel();
#endif

    QString remoteName;
    if (serviceInfo.device().name().isEmpty())
        remoteName = serviceInfo.device().address().toString();
    else
        remoteName = serviceInfo.device().name();

    qDebug() << "adding to the list....";
    qDebug() << "remoteName: "  << remoteName;
    m_discoveredServices.insert(remoteName, serviceInfo);
    emit newServiceFound();
}

void RemoteSelector::discoveryFinished()
{
    qDebug() << "discoveryFinished";
    emit finished();
}

我们可以通过调用startDiscovery()来扫描附近的设备.在调用时,我们可以设置我们想要的serviceUuid:

chat.cpp


void Chat::searchForDevices()
{      
    qDebug() << "search for devices!";
    if ( remoteSelector ) {
        delete remoteSelector;
        remoteSelector = NULL;
    }

    QBluetoothAddress adapter = QBluetoothAddress();
    remoteSelector = new RemoteSelector(adapter, this);

    connect(remoteSelector, SIGNAL(newServiceFound()), this, SLOT(newServiceFound()));

    remoteSelector->m_discoveredServices.clear();
    remoteSelector->startDiscovery(QBluetoothUuid(serviceUuid));
    connect(remoteSelector, SIGNAL(finished()), this, SIGNAL(discoveryFinished()));
}

这样就可以扫描到具有我们所需要的serviceUuid的设备来提供连接.在扫描时,Bluetooth server必须是在运行的状态.


4)应用UI设计


我们的应用UI设计比较简单.就想上面显示的图一样,我们最上面的显示当前正在运行该应用的Bluetooth server的设备.当我们点击该设备时,就开始向该设备发送连接请求.在我们的下面的对话框中,会显示连接的状态及对话:

Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3
import QtBluetooth 5.3

MainView {
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "btchat.xiaoguo"

    width: units.gu(60)
    height: units.gu(85)

    function getBluetoothState(state) {
        switch (state ) {
        case BluetoothSocket.Unconnected:
            return "Unconnected";
        case BluetoothSocket.ServiceLookup:
            return "ServiceLookup";
        case BluetoothSocket.Connecting:
            return "Connecting";
        case BluetoothSocket.Connected:
            return "Connected";
        case BluetoothSocket.Bound:
            return "Bound";
        case BluetoothSocket.Closing:
            return "Closing";
        case BluetoothSocket.Listening:
            return "Listening";
        case BluetoothSocket.ServiceLookup:
            return "ServiceLookup";
        case BluetoothSocket.NoServiceSet:
            return "NoServiceSet";
        default:
            return "Unknow state"
        }
    }

    function appendMessage(msg, alignright) {
        mymodel.append({ "msg": msg, "alignright": alignright} )
        listview.positionViewAtIndex(mymodel.count - 1, ListView.Beginning)
    }

    Connections {
        target: chat
        onConnected: {
            console.log("Connected: " + name)
            appendMessage( "connected to " + name, false )
        }

        onDisconnected: {
            console.log("Disconnected: " + name )
            appendMessage( "disconnected " + name, false )
        }

        onClientDisconnected: {
            console.log("Client Disconnected")
            appendMessage("Client Disconnected", false)
        }

        onShowMessage: {
            console.log("sender: " + sender )
            console.log("message: " + message )
            message = message.replace(/(\r\n|\n|\r)/gm,"");
            var msg = '<font color = "green">' + message + '</font>';
            console.log("msg: " + msg)
            appendMessage(msg, false)
        }

        onNewServicesFound: {
            console.log("new services found!")
            devlist.model = list;
        }

        onDiscoveryFinished: {
            console.log("discovery finished");
            indicator.running = false;
        }
    }

    ListModel {
        id: mymodel
    }

    ListModel {
        id: services
    }

    BluetoothDiscoveryModel {
        id: btModel
        running: false
        discoveryMode: BluetoothDiscoveryModel.FullServiceDiscovery
        onRunningChanged : {
        }

        onErrorChanged: {
        }

        onServiceDiscovered: {
            console.log("service has been found!")
            services.append( {"service": service })
        }
        uuidFilter: "e8e10f95-1a70-4b27-9ccf-02010264e9c8"
    }

    BluetoothSocket {
        id: socket
        connected: true

        onSocketStateChanged: {
            console.log("socketState: " + socketState);
            console.log("Connected to server! ")
            appendMessage( "State: " + getBluetoothState(socketState) )
        }

        onStringDataChanged: {
            console.log("Received data: " )
            var data = "Going to send: " + socket.stringData;
            data = data.substring(0, data.indexOf('\n'))
            console.log(data);
            appendMessage("Received: " + data)
        }
    }

    Component {
        id: highlight
        Rectangle {
            width: devlist.width
            height: devlist.delegate.height
            color: "lightsteelblue"; radius: 5
            Behavior on y {
                SpringAnimation {
                    spring: 3
                    damping: 0.2
                }
            }
        }
    }

    Page {
        id: page
        header: standardHeader

        PageHeader {
            id: standardHeader
            visible: page.header === standardHeader
            title: "Bluetooth chat"
            trailingActionBar.actions: [
                Action {
                    iconName: "edit"
                    text: "Edit"
                    onTriggered: page.header = editHeader
                }
            ]
        }

        PageHeader {
            id: editHeader
            visible: page.header === editHeader
            leadingActionBar.actions: [
                Action {
                    iconName: "back"
                    text: "Back"
                    onTriggered: {
                        page.header = standardHeader
                    }
                }
            ]
            contents: Row {
                id: layout
                anchors {
                    left: parent.left
                    right: parent.right
                    verticalCenter: parent.verticalCenter
                }
                spacing: units.gu(2)

                TextField {
                    id: input
                    width: parent.width*2/3
                    placeholderText: "input words .."
                    text: "I love you!"

                    onAccepted: {
                        console.log("going to send: " + text)
                    }
                }

                Button {
                    text: "Send"
                    width: parent.width - input.width - layout.spacing;
                    onClicked: {
                        console.log("send is clicked")
                        console.log("chat length: " + input.text.length)
                        chat.sendMessage(input.text);
                        appendMessage(input.text, true)
                    }
                }
            }
        }

        Item {
            anchors {
                left: parent.left
                right: parent.right
                bottom: parent.bottom
                top: page.header.bottom
            }

            ActivityIndicator {
                id: indicator
                anchors.centerIn: parent
            }

            Column {
                anchors.fill: parent

                Label {
                    text: "Devices are:"
                    fontSize: "x-large"
                }

                ListView {
                    id: devlist
                    width: parent.width
                    height: parent.height/4
                    model: services
                    highlight: highlight
                    delegate: Label {
                        width: parent.width
                        text: modelData
                        fontSize: "x-large"
                        MouseArea {
                            anchors.fill: parent
                            onClicked: {
                                console.log("it is selected")
                                chat.connectToDevice(modelData)
                            }
                        }
                    }
                }

                Rectangle {
                    width: parent.width
                    height: units.gu(0.4)
                    color: "green"
                }

                ListView {
                    id: listview
                    width: parent.width
                    height: parent.height - devlist.height
                    model: mymodel
                    delegate: Item {
                        width: page.width
                        height: txt.height * 1.2 /*+ div.height*/

                        Rectangle {
                            width: txt.contentWidth
                            height: parent.height
                            color: alignright? "green" : "white"
                            radius: units.gu(0.5)
                            anchors.right: alignright ? parent.right : undefined
                            anchors.verticalCenter: parent.verticalCenter
                        }

                        Label {
                            id: txt
                            width: parent.width*0.7
                            text: msg
                            anchors.right: alignright ? parent.right : undefined
                            horizontalAlignment: alignright ? Text.AlignRight :
                                                              Text.AlignLeft
                            fontSize: "large"
                            anchors.verticalCenter: parent.verticalCenter
                            wrapMode: Text.WordWrap
                        }

//                        Rectangle {
//                            id: div
//                            width: parent.width
//                            height: units.gu(0.2)
//                            color: "blue"
//                        }
                    }
                }
            }


            Row {
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.bottom: parent.bottom
                anchors.bottomMargin: units.gu(1)
                spacing: units.gu(2)

                Button {
                    text: "Search"
                    onClicked: {
                        console.log("btModel.running: " + btModel.running )
                        var list = []
                        devlist.model = list;
                        chat.searchForDevices()
                        indicator.running = true
                    }
                }

                Button {
                    text: "Stop search"
                    onClicked: {
                        console.log("Stop is clicked!")
                        chat.stopSearch();
                        indicator.running = false
                    }
                }

                Button {
                    text: "Disconnect"
                    onClicked: {
                        console.log("Disconnect is clicked!")
                        chat.disconnect();
                    }
                }
            }
        }
    }
}

整个应用的代码在: https://github.com/liu-xiao-guo/btchat


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值