在今天的例程中,我们将重点介绍如何使用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();
}
}
}
}
}
}