Remove libgato code from to-be-released code
It continues to live in the development branches Change-Id: I13f140f78b26e4f1bf23d8e023834038812190c6 Reviewed-by: Alex Blasche <alexander.blasche@digia.com>
This commit is contained in:
parent
624f8a7a5c
commit
8a02c2ffd8
|
@ -1,609 +0,0 @@
|
||||||
/****************************************************************************
|
|
||||||
**
|
|
||||||
** Copyright (C) 2013 Javier de San Pedro <dev.git@javispedro.com>
|
|
||||||
** Contact: http://www.qt-project.org/legal
|
|
||||||
**
|
|
||||||
** This file is part of the QtBluetooth module of the Qt Toolkit.
|
|
||||||
**
|
|
||||||
** $QT_BEGIN_LICENSE:LGPL21$
|
|
||||||
** Commercial License Usage
|
|
||||||
** Licensees holding valid commercial Qt licenses may use this file in
|
|
||||||
** accordance with the commercial license agreement provided with the
|
|
||||||
** Software or, alternatively, in accordance with the terms contained in
|
|
||||||
** a written agreement between you and Digia. For licensing terms and
|
|
||||||
** conditions see http://qt.digia.com/licensing. For further information
|
|
||||||
** use the contact form at http://qt.digia.com/contact-us.
|
|
||||||
**
|
|
||||||
** GNU Lesser General Public License Usage
|
|
||||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
||||||
** General Public License version 2.1 or version 3 as published by the Free
|
|
||||||
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
|
||||||
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
|
||||||
** following information to ensure the GNU Lesser General Public License
|
|
||||||
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
|
||||||
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
||||||
**
|
|
||||||
** In addition, as a special exception, Digia gives you certain additional
|
|
||||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
|
||||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
||||||
**
|
|
||||||
** $QT_END_LICENSE$
|
|
||||||
**
|
|
||||||
****************************************************************************/
|
|
||||||
|
|
||||||
#include <QtCore/QDataStream>
|
|
||||||
#include <QtCore/QDebug>
|
|
||||||
|
|
||||||
#include "gatoattclient.h"
|
|
||||||
#include "helpers.h"
|
|
||||||
|
|
||||||
#define PROTOCOL_DEBUG 0
|
|
||||||
|
|
||||||
#define ATT_CID 4
|
|
||||||
#define ATT_PSM 31
|
|
||||||
|
|
||||||
#define ATT_DEFAULT_LE_MTU 23
|
|
||||||
#define ATT_MAX_LE_MTU 0x200
|
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
|
||||||
|
|
||||||
enum AttOpcode {
|
|
||||||
AttOpNone = 0,
|
|
||||||
AttOpErrorResponse = 0x1,
|
|
||||||
AttOpExchangeMTURequest = 0x2,
|
|
||||||
AttOpExchangeMTUResponse = 0x3,
|
|
||||||
AttOpFindInformationRequest = 0x4,
|
|
||||||
AttOpFindInformationResponse = 0x5,
|
|
||||||
AttOpFindByTypeValueRequest = 0x6,
|
|
||||||
AttOpFindByTypeValueResponse = 0x7,
|
|
||||||
AttOpReadByTypeRequest = 0x8,
|
|
||||||
AttOpReadByTypeResponse = 0x9,
|
|
||||||
AttOpReadRequest = 0xA,
|
|
||||||
AttOpReadResponse = 0xB,
|
|
||||||
AttOpReadBlobRequest = 0xC,
|
|
||||||
AttOpReadBlobResponse = 0xD,
|
|
||||||
AttOpReadMultipleRequest = 0xE,
|
|
||||||
AttOpReadMultipleResponse = 0xF,
|
|
||||||
AttOpReadByGroupTypeRequest = 0x10,
|
|
||||||
AttOpReadByGroupTypeResponse = 0x11,
|
|
||||||
AttOpWriteRequest = 0x12,
|
|
||||||
AttOpWriteResponse = 0x13,
|
|
||||||
AttOpWriteCommand = 0x52,
|
|
||||||
AttOpPrepareWriteRequest = 0x16,
|
|
||||||
AttOpPrepareWriteResponse = 0x17,
|
|
||||||
AttOpExecuteWriteRequest = 0x18,
|
|
||||||
AttOpExecuteWriteResponse = 0x19,
|
|
||||||
AttOpHandleValueNotification = 0x1B,
|
|
||||||
AttOpHandleValueIndication = 0x1D,
|
|
||||||
AttOpHandleValueConfirmation = 0x1E,
|
|
||||||
AttOpSignedWriteCommand = 0xD2
|
|
||||||
};
|
|
||||||
|
|
||||||
static QByteArray remove_method_signature(const char *sig)
|
|
||||||
{
|
|
||||||
const char* bracketPosition = strchr(sig, '(');
|
|
||||||
if (!bracketPosition || !(sig[0] >= '0' && sig[0] <= '3')) {
|
|
||||||
qWarning("Invalid slot specification");
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
return QByteArray(sig + 1, bracketPosition - 1 - sig);
|
|
||||||
}
|
|
||||||
|
|
||||||
GatoAttClient::GatoAttClient(QObject *parent) :
|
|
||||||
QObject(parent), socket(new GatoSocket(this)), cur_mtu(ATT_DEFAULT_LE_MTU), next_id(1)
|
|
||||||
{
|
|
||||||
connect(socket, SIGNAL(connected()), SLOT(handleSocketConnected()));
|
|
||||||
connect(socket, SIGNAL(disconnected()), SLOT(handleSocketDisconnected()));
|
|
||||||
connect(socket, SIGNAL(readyRead()), SLOT(handleSocketReadyRead()));
|
|
||||||
}
|
|
||||||
|
|
||||||
GatoAttClient::~GatoAttClient()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
GatoSocket::State GatoAttClient::state() const
|
|
||||||
{
|
|
||||||
return socket->state();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GatoAttClient::connectTo(const GatoAddress &addr)
|
|
||||||
{
|
|
||||||
return socket->connectTo(addr, ATT_CID);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoAttClient::close()
|
|
||||||
{
|
|
||||||
socket->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
int GatoAttClient::mtu() const
|
|
||||||
{
|
|
||||||
return cur_mtu;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint GatoAttClient::request(int opcode, const QByteArray &data, QObject *receiver, const char *member)
|
|
||||||
{
|
|
||||||
Request req;
|
|
||||||
req.id = next_id++;
|
|
||||||
req.opcode = opcode;
|
|
||||||
req.pkt = data;
|
|
||||||
req.pkt.prepend(static_cast<char>(opcode));
|
|
||||||
req.receiver = receiver;
|
|
||||||
req.member = remove_method_signature(member);
|
|
||||||
|
|
||||||
pending_requests.enqueue(req);
|
|
||||||
|
|
||||||
if (pending_requests.size() == 1) {
|
|
||||||
// So we can just send this request instead of waiting for others to complete
|
|
||||||
sendARequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
return req.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoAttClient::cancelRequest(uint id)
|
|
||||||
{
|
|
||||||
QQueue<Request>::iterator it = pending_requests.begin();
|
|
||||||
while (it != pending_requests.end()) {
|
|
||||||
if (it->id == id) {
|
|
||||||
it = pending_requests.erase(it);
|
|
||||||
} else {
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint GatoAttClient::requestExchangeMTU(quint16 client_mtu, QObject *receiver, const char *member)
|
|
||||||
{
|
|
||||||
QByteArray data;
|
|
||||||
QDataStream s(&data, QIODevice::WriteOnly);
|
|
||||||
s.setByteOrder(QDataStream::LittleEndian);
|
|
||||||
s << client_mtu;
|
|
||||||
|
|
||||||
return request(AttOpExchangeMTURequest, data, receiver, member);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint GatoAttClient::requestFindInformation(GatoHandle start, GatoHandle end, QObject *receiver, const char *member)
|
|
||||||
{
|
|
||||||
QByteArray data;
|
|
||||||
QDataStream s(&data, QIODevice::WriteOnly);
|
|
||||||
s.setByteOrder(QDataStream::LittleEndian);
|
|
||||||
s << start << end;
|
|
||||||
|
|
||||||
return request(AttOpFindInformationRequest, data, receiver, member);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint GatoAttClient::requestFindByTypeValue(GatoHandle start, GatoHandle end, const GatoUUID &uuid, const QByteArray &value, QObject *receiver, const char *member)
|
|
||||||
{
|
|
||||||
QByteArray data;
|
|
||||||
QDataStream s(&data, QIODevice::WriteOnly);
|
|
||||||
s.setByteOrder(QDataStream::LittleEndian);
|
|
||||||
s << start << end;
|
|
||||||
|
|
||||||
bool uuid16_ok;
|
|
||||||
quint16 uuid16 = uuid.toUInt16(&uuid16_ok);
|
|
||||||
if (uuid16_ok) {
|
|
||||||
s << uuid16;
|
|
||||||
} else {
|
|
||||||
qWarning() << "FindByTypeValue does not support UUIDs other than UUID16";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
s << value;
|
|
||||||
|
|
||||||
return request(AttOpFindByTypeValueRequest, data, receiver, member);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint GatoAttClient::requestReadByType(GatoHandle start, GatoHandle end, const GatoUUID &uuid, QObject *receiver, const char *member)
|
|
||||||
{
|
|
||||||
QByteArray data;
|
|
||||||
QDataStream s(&data, QIODevice::WriteOnly);
|
|
||||||
s.setByteOrder(QDataStream::LittleEndian);
|
|
||||||
s << start << end;
|
|
||||||
write_gatouuid(s, uuid, true, false);
|
|
||||||
|
|
||||||
return request(AttOpReadByTypeRequest, data, receiver, member);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint GatoAttClient::requestRead(GatoHandle handle, QObject *receiver, const char *member)
|
|
||||||
{
|
|
||||||
QByteArray data;
|
|
||||||
QDataStream s(&data, QIODevice::WriteOnly);
|
|
||||||
s.setByteOrder(QDataStream::LittleEndian);
|
|
||||||
s << handle;
|
|
||||||
|
|
||||||
return request(AttOpReadRequest, data, receiver, member);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint GatoAttClient::requestReadByGroupType(GatoHandle start, GatoHandle end, const GatoUUID &uuid, QObject *receiver, const char *member)
|
|
||||||
{
|
|
||||||
QByteArray data;
|
|
||||||
QDataStream s(&data, QIODevice::WriteOnly);
|
|
||||||
s.setByteOrder(QDataStream::LittleEndian);
|
|
||||||
s << start << end;
|
|
||||||
write_gatouuid(s, uuid, true, false);
|
|
||||||
|
|
||||||
return request(AttOpReadByGroupTypeRequest, data, receiver, member);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint GatoAttClient::requestWrite(GatoHandle handle, const QByteArray &value, QObject *receiver, const char *member)
|
|
||||||
{
|
|
||||||
QByteArray data;
|
|
||||||
QDataStream s(&data, QIODevice::WriteOnly);
|
|
||||||
s.setByteOrder(QDataStream::LittleEndian);
|
|
||||||
s << handle;
|
|
||||||
s.writeRawData(value.constData(), value.length());
|
|
||||||
|
|
||||||
return request(AttOpWriteRequest, data, receiver, member);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoAttClient::command(int opcode, const QByteArray &data)
|
|
||||||
{
|
|
||||||
QByteArray packet = data;
|
|
||||||
packet.prepend(static_cast<char>(opcode));
|
|
||||||
|
|
||||||
socket->send(packet);
|
|
||||||
|
|
||||||
#if PROTOCOL_DEBUG
|
|
||||||
qDebug() << "Wrote" << packet.size() << "bytes (command)" << packet.toHex();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoAttClient::commandWrite(GatoHandle handle, const QByteArray &value)
|
|
||||||
{
|
|
||||||
QByteArray data;
|
|
||||||
QDataStream s(&data, QIODevice::WriteOnly);
|
|
||||||
s.setByteOrder(QDataStream::LittleEndian);
|
|
||||||
s << handle;
|
|
||||||
s.writeRawData(value.constData(), value.length());
|
|
||||||
|
|
||||||
command(AttOpWriteCommand, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoAttClient::sendARequest()
|
|
||||||
{
|
|
||||||
if (pending_requests.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Request &req = pending_requests.head();
|
|
||||||
socket->send(req.pkt);
|
|
||||||
|
|
||||||
#if PROTOCOL_DEBUG
|
|
||||||
qDebug() << "Wrote" << req.pkt.size() << "bytes (request)" << req.pkt.toHex();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GatoAttClient::handleEvent(const QByteArray &event)
|
|
||||||
{
|
|
||||||
const char *data = event.constData();
|
|
||||||
quint8 opcode = event[0];
|
|
||||||
GatoHandle handle;
|
|
||||||
|
|
||||||
switch (opcode) {
|
|
||||||
case AttOpHandleValueNotification:
|
|
||||||
handle = read_le<GatoHandle>(&data[1]);
|
|
||||||
emit attributeUpdated(handle, event.mid(3), false);
|
|
||||||
return true;
|
|
||||||
case AttOpHandleValueIndication:
|
|
||||||
handle = read_le<GatoHandle>(&data[1]);
|
|
||||||
|
|
||||||
// Send the confirmation back
|
|
||||||
command(AttOpHandleValueConfirmation, QByteArray());
|
|
||||||
|
|
||||||
emit attributeUpdated(handle, event.mid(3), true);
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GatoAttClient::handleResponse(const Request &req, const QByteArray &response)
|
|
||||||
{
|
|
||||||
// If we know the request, we can provide a decoded answer
|
|
||||||
switch (req.opcode) {
|
|
||||||
case AttOpExchangeMTURequest:
|
|
||||||
if (response[0] == AttOpExchangeMTUResponse) {
|
|
||||||
if (req.receiver) {
|
|
||||||
QMetaObject::invokeMethod(req.receiver, req.member.constData(),
|
|
||||||
Q_ARG(uint, req.id),
|
|
||||||
Q_ARG(quint16, read_le<quint16>(response.constData() + 1)));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else if (response[0] == AttOpErrorResponse && response[1] == AttOpExchangeMTURequest) {
|
|
||||||
if (req.receiver) {
|
|
||||||
QMetaObject::invokeMethod(req.receiver, req.member.constData(),
|
|
||||||
Q_ARG(uint, req.id),
|
|
||||||
Q_ARG(quint16, 0));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case AttOpFindInformationRequest:
|
|
||||||
if (response[0] == AttOpFindInformationResponse) {
|
|
||||||
if (req.receiver) {
|
|
||||||
QMetaObject::invokeMethod(req.receiver, req.member.constData(),
|
|
||||||
Q_ARG(uint, req.id),
|
|
||||||
Q_ARG(QList<GatoAttClient::InformationData>, parseInformationData(response.mid(1))));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else if (response[0] == AttOpErrorResponse && response[1] == AttOpFindInformationRequest) {
|
|
||||||
if (req.receiver) {
|
|
||||||
QMetaObject::invokeMethod(req.receiver, req.member.constData(),
|
|
||||||
Q_ARG(uint, req.id),
|
|
||||||
Q_ARG(QList<GatoAttClient::InformationData>, QList<InformationData>()));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case AttOpFindByTypeValueRequest:
|
|
||||||
if (response[0] == AttOpFindByTypeValueResponse) {
|
|
||||||
if (req.receiver) {
|
|
||||||
QMetaObject::invokeMethod(req.receiver, req.member.constData(),
|
|
||||||
Q_ARG(uint, req.id),
|
|
||||||
Q_ARG(QList<GatoAttClient::HandleInformation>, parseHandleInformation(response.mid(1))));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else if (response[0] == AttOpErrorResponse && response[1] == AttOpFindByTypeValueRequest) {
|
|
||||||
if (req.receiver) {
|
|
||||||
QMetaObject::invokeMethod(req.receiver, req.member.constData(),
|
|
||||||
Q_ARG(uint, req.id),
|
|
||||||
Q_ARG(QList<GatoAttClient::HandleInformation>, QList<HandleInformation>()));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case AttOpReadByTypeRequest:
|
|
||||||
if (response[0] == AttOpReadByTypeResponse) {
|
|
||||||
if (req.receiver) {
|
|
||||||
QMetaObject::invokeMethod(req.receiver, req.member.constData(),
|
|
||||||
Q_ARG(uint, req.id),
|
|
||||||
Q_ARG(QList<GatoAttClient::AttributeData>, parseAttributeData(response.mid(1))));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else if (response[0] == AttOpErrorResponse && response[1] == AttOpReadByTypeRequest) {
|
|
||||||
if (req.receiver) {
|
|
||||||
QMetaObject::invokeMethod(req.receiver, req.member.constData(),
|
|
||||||
Q_ARG(uint, req.id),
|
|
||||||
Q_ARG(QList<GatoAttClient::AttributeData>, QList<AttributeData>()));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case AttOpReadRequest:
|
|
||||||
if (response[0] == AttOpReadResponse) {
|
|
||||||
if (req.receiver) {
|
|
||||||
QMetaObject::invokeMethod(req.receiver, req.member.constData(),
|
|
||||||
Q_ARG(uint, req.id),
|
|
||||||
Q_ARG(QByteArray, response.mid(1)));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else if (response[0] == AttOpErrorResponse && response[1] == AttOpReadRequest) {
|
|
||||||
if (req.receiver) {
|
|
||||||
QMetaObject::invokeMethod(req.receiver, req.member.constData(),
|
|
||||||
Q_ARG(uint, req.id),
|
|
||||||
Q_ARG(QByteArray, QByteArray()));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case AttOpReadByGroupTypeRequest:
|
|
||||||
if (response[0] == AttOpReadByGroupTypeResponse) {
|
|
||||||
if (req.receiver) {
|
|
||||||
QMetaObject::invokeMethod(req.receiver, req.member.constData(),
|
|
||||||
Q_ARG(uint, req.id),
|
|
||||||
Q_ARG(QList<GatoAttClient::AttributeGroupData>, parseAttributeGroupData(response.mid(1))));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else if (response[0] == AttOpErrorResponse && response[1] == AttOpReadByGroupTypeRequest) {
|
|
||||||
if (req.receiver) {
|
|
||||||
QMetaObject::invokeMethod(req.receiver, req.member.constData(),
|
|
||||||
Q_ARG(uint, req.id),
|
|
||||||
Q_ARG(QList<GatoAttClient::AttributeGroupData>, QList<AttributeGroupData>()));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case AttOpWriteRequest:
|
|
||||||
if (response[0] == AttOpWriteResponse) {
|
|
||||||
if (req.receiver) {
|
|
||||||
QMetaObject::invokeMethod(req.receiver, req.member.constData(),
|
|
||||||
Q_ARG(uint, req.id),
|
|
||||||
Q_ARG(bool, true));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else if (response[0] == AttOpErrorResponse && response[1] == AttOpWriteRequest) {
|
|
||||||
if (req.receiver) {
|
|
||||||
QMetaObject::invokeMethod(req.receiver, req.member.constData(),
|
|
||||||
Q_ARG(uint, req.id),
|
|
||||||
Q_ARG(bool, false));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default: // Otherwise just send a QByteArray.
|
|
||||||
if (req.receiver) {
|
|
||||||
QMetaObject::invokeMethod(req.receiver, req.member.constData(),
|
|
||||||
Q_ARG(const QByteArray&, response));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<GatoAttClient::InformationData> GatoAttClient::parseInformationData(const QByteArray &data)
|
|
||||||
{
|
|
||||||
const int format = data[0];
|
|
||||||
QList<InformationData> list;
|
|
||||||
int item_len;
|
|
||||||
|
|
||||||
switch (format) {
|
|
||||||
case 1:
|
|
||||||
item_len = 2 + 2;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
item_len = 2 + 16;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
qWarning() << "Unknown InformationData format!";
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
int items = (data.size() - 1) / item_len;
|
|
||||||
list.reserve(items);
|
|
||||||
|
|
||||||
int pos = 1;
|
|
||||||
const char *s = data.constData();
|
|
||||||
for (int i = 0; i < items; i++) {
|
|
||||||
InformationData d;
|
|
||||||
QByteArray uuid;
|
|
||||||
d.handle = read_le<GatoHandle>(&s[pos]);
|
|
||||||
switch (format) {
|
|
||||||
case 1:
|
|
||||||
uuid = data.mid(pos + 2, 2);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
uuid = data.mid(pos + 2, 16);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
d.uuid = bytearray_to_gatouuid(uuid);
|
|
||||||
|
|
||||||
list.append(d);
|
|
||||||
|
|
||||||
pos += item_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<GatoAttClient::HandleInformation> GatoAttClient::parseHandleInformation(const QByteArray &data)
|
|
||||||
{
|
|
||||||
const int item_len = 2;
|
|
||||||
const int items = data.size() / item_len;
|
|
||||||
QList<HandleInformation> list;
|
|
||||||
list.reserve(items);
|
|
||||||
|
|
||||||
int pos = 0;
|
|
||||||
const char *s = data.constData();
|
|
||||||
for (int i = 0; i < items; i++) {
|
|
||||||
HandleInformation d;
|
|
||||||
d.start = read_le<GatoHandle>(&s[pos]);
|
|
||||||
d.end = read_le<GatoHandle>(&s[pos + 2]);
|
|
||||||
list.append(d);
|
|
||||||
|
|
||||||
pos += item_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<GatoAttClient::AttributeData> GatoAttClient::parseAttributeData(const QByteArray &data)
|
|
||||||
{
|
|
||||||
const int item_len = data[0];
|
|
||||||
const int items = (data.size() - 1) / item_len;
|
|
||||||
QList<AttributeData> list;
|
|
||||||
list.reserve(items);
|
|
||||||
|
|
||||||
int pos = 1;
|
|
||||||
const char *s = data.constData();
|
|
||||||
for (int i = 0; i < items; i++) {
|
|
||||||
AttributeData d;
|
|
||||||
d.handle = read_le<GatoHandle>(&s[pos]);
|
|
||||||
d.value = data.mid(pos + 2, item_len - 2);
|
|
||||||
list.append(d);
|
|
||||||
|
|
||||||
pos += item_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<GatoAttClient::AttributeGroupData> GatoAttClient::parseAttributeGroupData(const QByteArray &data)
|
|
||||||
{
|
|
||||||
const int item_len = data[0];
|
|
||||||
const int items = (data.size() - 1) / item_len;
|
|
||||||
QList<AttributeGroupData> list;
|
|
||||||
list.reserve(items);
|
|
||||||
|
|
||||||
int pos = 1;
|
|
||||||
const char *s = data.constData();
|
|
||||||
for (int i = 0; i < items; i++) {
|
|
||||||
AttributeGroupData d;
|
|
||||||
d.start = read_le<GatoHandle>(&s[pos]);
|
|
||||||
d.end = read_le<GatoHandle>(&s[pos + 2]);
|
|
||||||
d.value = data.mid(pos + 4, item_len - 4);
|
|
||||||
list.append(d);
|
|
||||||
|
|
||||||
pos += item_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoAttClient::handleSocketConnected()
|
|
||||||
{
|
|
||||||
requestExchangeMTU(ATT_MAX_LE_MTU, this, SLOT(handleServerMTU(quint16)));
|
|
||||||
emit connected();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoAttClient::handleSocketDisconnected()
|
|
||||||
{
|
|
||||||
emit disconnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoAttClient::handleSocketReadyRead()
|
|
||||||
{
|
|
||||||
QByteArray pkt = socket->receive();
|
|
||||||
if (!pkt.isEmpty()) {
|
|
||||||
#if PROTOCOL_DEBUG
|
|
||||||
qDebug() << "Received" << pkt.size() << "bytes" << pkt.toHex();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Check if it is an event
|
|
||||||
if (handleEvent(pkt)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, if we have a request waiting, check if this answers it
|
|
||||||
if (!pending_requests.isEmpty()) {
|
|
||||||
if (handleResponse(pending_requests.head(), pkt)) {
|
|
||||||
pending_requests.dequeue();
|
|
||||||
// Proceed to next request
|
|
||||||
if (!pending_requests.isEmpty()) {
|
|
||||||
sendARequest();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
qDebug() << "No idea what this packet ("
|
|
||||||
<< QString("0x%1").arg(uint(pkt.at(0)), 2, 16, QLatin1Char('0'))
|
|
||||||
<< ") is";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoAttClient::handleServerMTU(uint req, quint16 server_mtu)
|
|
||||||
{
|
|
||||||
Q_UNUSED(req);
|
|
||||||
if (server_mtu) {
|
|
||||||
cur_mtu = server_mtu;
|
|
||||||
if (cur_mtu < ATT_DEFAULT_LE_MTU) {
|
|
||||||
cur_mtu = ATT_DEFAULT_LE_MTU;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
|
|
@ -1,135 +0,0 @@
|
||||||
/****************************************************************************
|
|
||||||
**
|
|
||||||
** Copyright (C) 2013 Javier de San Pedro <dev.git@javispedro.com>
|
|
||||||
** Contact: http://www.qt-project.org/legal
|
|
||||||
**
|
|
||||||
** This file is part of the QtBluetooth module of the Qt Toolkit.
|
|
||||||
**
|
|
||||||
** $QT_BEGIN_LICENSE:LGPL21$
|
|
||||||
** Commercial License Usage
|
|
||||||
** Licensees holding valid commercial Qt licenses may use this file in
|
|
||||||
** accordance with the commercial license agreement provided with the
|
|
||||||
** Software or, alternatively, in accordance with the terms contained in
|
|
||||||
** a written agreement between you and Digia. For licensing terms and
|
|
||||||
** conditions see http://qt.digia.com/licensing. For further information
|
|
||||||
** use the contact form at http://qt.digia.com/contact-us.
|
|
||||||
**
|
|
||||||
** GNU Lesser General Public License Usage
|
|
||||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
||||||
** General Public License version 2.1 or version 3 as published by the Free
|
|
||||||
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
|
||||||
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
|
||||||
** following information to ensure the GNU Lesser General Public License
|
|
||||||
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
|
||||||
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
||||||
**
|
|
||||||
** In addition, as a special exception, Digia gives you certain additional
|
|
||||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
|
||||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
||||||
**
|
|
||||||
** $QT_END_LICENSE$
|
|
||||||
**
|
|
||||||
****************************************************************************/
|
|
||||||
|
|
||||||
#ifndef GATOATTCLIENT_H
|
|
||||||
#define GATOATTCLIENT_H
|
|
||||||
|
|
||||||
#include <QtCore/QObject>
|
|
||||||
#include <QtCore/QQueue>
|
|
||||||
#include "gatosocket.h"
|
|
||||||
#include "gatouuid.h"
|
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
|
||||||
|
|
||||||
class GatoAttClient : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit GatoAttClient(QObject *parent = 0);
|
|
||||||
~GatoAttClient();
|
|
||||||
|
|
||||||
GatoSocket::State state() const;
|
|
||||||
|
|
||||||
bool connectTo(const GatoAddress& addr);
|
|
||||||
void close();
|
|
||||||
|
|
||||||
struct InformationData
|
|
||||||
{
|
|
||||||
GatoHandle handle;
|
|
||||||
GatoUUID uuid;
|
|
||||||
};
|
|
||||||
struct HandleInformation
|
|
||||||
{
|
|
||||||
GatoHandle start;
|
|
||||||
GatoHandle end;
|
|
||||||
};
|
|
||||||
struct AttributeData
|
|
||||||
{
|
|
||||||
GatoHandle handle;
|
|
||||||
QByteArray value;
|
|
||||||
};
|
|
||||||
struct AttributeGroupData
|
|
||||||
{
|
|
||||||
GatoHandle start;
|
|
||||||
GatoHandle end;
|
|
||||||
QByteArray value;
|
|
||||||
};
|
|
||||||
|
|
||||||
int mtu() const;
|
|
||||||
|
|
||||||
uint request(int opcode, const QByteArray &data, QObject *receiver, const char *member);
|
|
||||||
uint requestExchangeMTU(quint16 client_mtu, QObject *receiver, const char *member);
|
|
||||||
uint requestFindInformation(GatoHandle start, GatoHandle end, QObject *receiver, const char *member);
|
|
||||||
uint requestFindByTypeValue(GatoHandle start, GatoHandle end, const GatoUUID &uuid, const QByteArray& value, QObject *receiver, const char *member);
|
|
||||||
uint requestReadByType(GatoHandle start, GatoHandle end, const GatoUUID &uuid, QObject *receiver, const char *member);
|
|
||||||
uint requestRead(GatoHandle handle, QObject *receiver, const char *member);
|
|
||||||
uint requestReadByGroupType(GatoHandle start, GatoHandle end, const GatoUUID &uuid, QObject *receiver, const char *member);
|
|
||||||
uint requestWrite(GatoHandle handle, const QByteArray &value, QObject *receiver, const char *member);
|
|
||||||
void cancelRequest(uint id);
|
|
||||||
|
|
||||||
void command(int opcode, const QByteArray &data);
|
|
||||||
void commandWrite(GatoHandle handle, const QByteArray &value);
|
|
||||||
|
|
||||||
Q_SIGNALS:
|
|
||||||
void connected();
|
|
||||||
void disconnected();
|
|
||||||
|
|
||||||
void attributeUpdated(GatoHandle handle, const QByteArray &value, bool confirmed);
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Request
|
|
||||||
{
|
|
||||||
uint id;
|
|
||||||
quint8 opcode;
|
|
||||||
QByteArray pkt;
|
|
||||||
QObject *receiver;
|
|
||||||
QByteArray member;
|
|
||||||
};
|
|
||||||
|
|
||||||
void sendARequest();
|
|
||||||
bool handleEvent(const QByteArray &event);
|
|
||||||
bool handleResponse(const Request& req, const QByteArray &response);
|
|
||||||
|
|
||||||
QList<InformationData> parseInformationData(const QByteArray &data);
|
|
||||||
QList<HandleInformation> parseHandleInformation(const QByteArray &data);
|
|
||||||
QList<AttributeData> parseAttributeData(const QByteArray &data);
|
|
||||||
QList<AttributeGroupData> parseAttributeGroupData(const QByteArray &data);
|
|
||||||
|
|
||||||
private Q_SLOTS:
|
|
||||||
void handleSocketConnected();
|
|
||||||
void handleSocketDisconnected();
|
|
||||||
void handleSocketReadyRead();
|
|
||||||
|
|
||||||
void handleServerMTU(uint req, quint16 server_mtu);
|
|
||||||
|
|
||||||
private:
|
|
||||||
GatoSocket *socket;
|
|
||||||
quint16 cur_mtu;
|
|
||||||
uint next_id;
|
|
||||||
QQueue<Request> pending_requests;
|
|
||||||
};
|
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
|
||||||
|
|
||||||
#endif // GATOATTCLIENT_H
|
|
|
@ -1,866 +0,0 @@
|
||||||
/****************************************************************************
|
|
||||||
**
|
|
||||||
** Copyright (C) 2013 Javier de San Pedro <dev.git@javispedro.com>
|
|
||||||
** Contact: http://www.qt-project.org/legal
|
|
||||||
**
|
|
||||||
** This file is part of the QtBluetooth module of the Qt Toolkit.
|
|
||||||
**
|
|
||||||
** $QT_BEGIN_LICENSE:LGPL21$
|
|
||||||
** Commercial License Usage
|
|
||||||
** Licensees holding valid commercial Qt licenses may use this file in
|
|
||||||
** accordance with the commercial license agreement provided with the
|
|
||||||
** Software or, alternatively, in accordance with the terms contained in
|
|
||||||
** a written agreement between you and Digia. For licensing terms and
|
|
||||||
** conditions see http://qt.digia.com/licensing. For further information
|
|
||||||
** use the contact form at http://qt.digia.com/contact-us.
|
|
||||||
**
|
|
||||||
** GNU Lesser General Public License Usage
|
|
||||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
||||||
** General Public License version 2.1 or version 3 as published by the Free
|
|
||||||
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
|
||||||
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
|
||||||
** following information to ensure the GNU Lesser General Public License
|
|
||||||
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
|
||||||
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
||||||
**
|
|
||||||
** In addition, as a special exception, Digia gives you certain additional
|
|
||||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
|
||||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
||||||
**
|
|
||||||
** $QT_END_LICENSE$
|
|
||||||
**
|
|
||||||
****************************************************************************/
|
|
||||||
|
|
||||||
#include <QtCore/QDebug>
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <bluetooth/bluetooth.h>
|
|
||||||
|
|
||||||
#include "gatoperipheral_p.h"
|
|
||||||
#include "gatoaddress.h"
|
|
||||||
#include "gatouuid.h"
|
|
||||||
#include "helpers.h"
|
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
|
||||||
|
|
||||||
enum EIRDataFields {
|
|
||||||
EIRFlags = 0x01,
|
|
||||||
EIRIncompleteUUID16List = 0x02,
|
|
||||||
EIRCompleteUUID16List = 0x03,
|
|
||||||
EIRIncompleteUUID32List = 0x04,
|
|
||||||
EIRCompleteUUID32List = 0x05,
|
|
||||||
EIRIncompleteUUID128List = 0x06,
|
|
||||||
EIRCompleteUUID128List = 0x07,
|
|
||||||
EIRIncompleteLocalName = 0x08,
|
|
||||||
EIRCompleteLocalName = 0x09,
|
|
||||||
EIRTxPowerLevel = 0x0A,
|
|
||||||
EIRDeviceClass = 0x0D,
|
|
||||||
EIRSecurityManagerTKValue = 0x10,
|
|
||||||
EIRSecurityManagerOutOfBandFlags = 0x11,
|
|
||||||
EIRSolicitedUUID128List = 0x15
|
|
||||||
};
|
|
||||||
|
|
||||||
GatoPeripheral::GatoPeripheral(const GatoAddress &addr, QObject *parent) :
|
|
||||||
QObject(parent), d_ptr(new GatoPeripheralPrivate(this))
|
|
||||||
{
|
|
||||||
Q_D(GatoPeripheral);
|
|
||||||
d->addr = addr;
|
|
||||||
d->att = new GatoAttClient(this);
|
|
||||||
|
|
||||||
connect(d->att, SIGNAL(connected()), d, SLOT(handleAttConnected()));
|
|
||||||
connect(d->att, SIGNAL(disconnected()), d, SLOT(handleAttDisconnected()));
|
|
||||||
connect(d->att, SIGNAL(attributeUpdated(GatoHandle,QByteArray,bool)), d, SLOT(handleAttAttributeUpdated(GatoHandle,QByteArray,bool)));
|
|
||||||
}
|
|
||||||
|
|
||||||
GatoPeripheral::~GatoPeripheral()
|
|
||||||
{
|
|
||||||
if (state() != StateDisconnected) {
|
|
||||||
disconnect();
|
|
||||||
}
|
|
||||||
delete d_ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
GatoPeripheral::State GatoPeripheral::state() const
|
|
||||||
{
|
|
||||||
Q_D(const GatoPeripheral);
|
|
||||||
return static_cast<State>(d->att->state());
|
|
||||||
}
|
|
||||||
|
|
||||||
GatoAddress GatoPeripheral::address() const
|
|
||||||
{
|
|
||||||
Q_D(const GatoPeripheral);
|
|
||||||
return d->addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString GatoPeripheral::name() const
|
|
||||||
{
|
|
||||||
Q_D(const GatoPeripheral);
|
|
||||||
return d->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<GatoService> GatoPeripheral::services() const
|
|
||||||
{
|
|
||||||
Q_D(const GatoPeripheral);
|
|
||||||
return d->services.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheral::parseEIR(quint8 data[], int len)
|
|
||||||
{
|
|
||||||
Q_D(GatoPeripheral);
|
|
||||||
|
|
||||||
int pos = 0;
|
|
||||||
while (pos < len) {
|
|
||||||
int item_len = data[pos];
|
|
||||||
pos++;
|
|
||||||
if (item_len == 0) break;
|
|
||||||
int type = data[pos];
|
|
||||||
assert(pos + item_len <= len);
|
|
||||||
switch (type) {
|
|
||||||
case EIRFlags:
|
|
||||||
d->parseEIRFlags(&data[pos + 1], item_len - 1);
|
|
||||||
break;
|
|
||||||
case EIRIncompleteUUID16List:
|
|
||||||
d->parseEIRUUIDs(16/8, false, &data[pos + 1], item_len - 1);
|
|
||||||
break;
|
|
||||||
case EIRCompleteUUID16List:
|
|
||||||
d->parseEIRUUIDs(16/8, true, &data[pos + 1], item_len - 1);
|
|
||||||
break;
|
|
||||||
case EIRIncompleteUUID32List:
|
|
||||||
d->parseEIRUUIDs(32/8, false, &data[pos + 1], item_len - 1);
|
|
||||||
break;
|
|
||||||
case EIRCompleteUUID32List:
|
|
||||||
d->parseEIRUUIDs(32/8, true, &data[pos + 1], item_len - 1);
|
|
||||||
break;
|
|
||||||
case EIRIncompleteUUID128List:
|
|
||||||
d->parseEIRUUIDs(128/8, false, &data[pos + 1], item_len - 1);
|
|
||||||
break;
|
|
||||||
case EIRCompleteUUID128List:
|
|
||||||
d->parseEIRUUIDs(128/8, true, &data[pos + 1], item_len - 1);
|
|
||||||
break;
|
|
||||||
case EIRIncompleteLocalName:
|
|
||||||
d->parseName(false, &data[pos + 1], item_len - 1);
|
|
||||||
break;
|
|
||||||
case EIRCompleteLocalName:
|
|
||||||
d->parseName(true, &data[pos + 1], item_len - 1);
|
|
||||||
break;
|
|
||||||
case EIRTxPowerLevel:
|
|
||||||
case EIRSolicitedUUID128List:
|
|
||||||
qDebug() << "Unhandled EIR data type" << type;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
qWarning() << "Unknown EIR data type" << type;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
pos += item_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(pos == len);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GatoPeripheral::advertisesService(const GatoUUID &uuid) const
|
|
||||||
{
|
|
||||||
Q_D(const GatoPeripheral);
|
|
||||||
return d->service_uuids.contains(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheral::connectPeripheral()
|
|
||||||
{
|
|
||||||
Q_D(GatoPeripheral);
|
|
||||||
if (d->att->state() != GatoSocket::StateDisconnected) {
|
|
||||||
qDebug() << "Already connecting";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
d->att->connectTo(d->addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheral::disconnectPeripheral()
|
|
||||||
{
|
|
||||||
Q_D(GatoPeripheral);
|
|
||||||
|
|
||||||
d->att->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheral::discoverServices()
|
|
||||||
{
|
|
||||||
Q_D(GatoPeripheral);
|
|
||||||
if (!d->complete_services && state() == StateConnected) {
|
|
||||||
d->clearServices();
|
|
||||||
d->att->requestReadByGroupType(0x0001, 0xFFFF, GatoUUID::GattPrimaryService,
|
|
||||||
d, SLOT(handlePrimary(QList<GatoAttClient::AttributeGroupData>)));
|
|
||||||
} else {
|
|
||||||
qWarning() << "Not connected";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheral::discoverServices(const QList<GatoUUID> &serviceUUIDs)
|
|
||||||
{
|
|
||||||
Q_D(GatoPeripheral);
|
|
||||||
if (serviceUUIDs.isEmpty()) return;
|
|
||||||
if (state() == StateConnected) {
|
|
||||||
foreach (const GatoUUID& uuid, serviceUUIDs) {
|
|
||||||
QByteArray value = gatouuid_to_bytearray(uuid, true, false);
|
|
||||||
uint req = d->att->requestFindByTypeValue(0x0001, 0xFFFF, GatoUUID::GattPrimaryService, value,
|
|
||||||
d, SLOT(handlePrimaryForService(uint,QList<GatoAttClient::HandleInformation>)));
|
|
||||||
d->pending_primary_reqs.insert(req, uuid);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qWarning() << "Not connected";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheral::discoverCharacteristics(const GatoService &service)
|
|
||||||
{
|
|
||||||
Q_D(GatoPeripheral);
|
|
||||||
|
|
||||||
if (!d->services.contains(service.startHandle())) {
|
|
||||||
qWarning() << "Unknown service for this peripheral";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GatoService &our_service = d->services[service.startHandle()];
|
|
||||||
|
|
||||||
if (our_service.startHandle() != service.startHandle() ||
|
|
||||||
our_service.endHandle() != service.endHandle() ||
|
|
||||||
our_service.uuid() != service.uuid()) {
|
|
||||||
qWarning() << "Unknown service for this peripheral";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state() == StateConnected) {
|
|
||||||
GatoHandle start = our_service.startHandle();
|
|
||||||
GatoHandle end = our_service.endHandle();
|
|
||||||
|
|
||||||
d->clearServiceCharacteristics(&our_service);
|
|
||||||
|
|
||||||
uint req = d->att->requestReadByType(start, end, GatoUUID::GattCharacteristic,
|
|
||||||
d, SLOT(handleCharacteristic(QList<GatoAttClient::AttributeData>)));
|
|
||||||
d->pending_characteristic_reqs.insert(req, start);
|
|
||||||
} else {
|
|
||||||
qWarning() << "Not connected";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheral::discoverCharacteristics(const GatoService &service, const QList<GatoUUID> &characteristicUUIDs)
|
|
||||||
{
|
|
||||||
// TODO There seems to be no way to ask for the peripheral to filter by uuid
|
|
||||||
Q_UNUSED(characteristicUUIDs);
|
|
||||||
discoverCharacteristics(service);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheral::discoverDescriptors(const GatoCharacteristic &characteristic)
|
|
||||||
{
|
|
||||||
Q_D(GatoPeripheral);
|
|
||||||
|
|
||||||
GatoHandle char_handle = characteristic.startHandle();
|
|
||||||
GatoHandle service_handle = d->characteristic_to_service.value(char_handle);
|
|
||||||
|
|
||||||
if (!service_handle) {
|
|
||||||
qWarning() << "Unknown characteristic for this peripheral";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GatoService &our_service = d->services[service_handle];
|
|
||||||
Q_ASSERT(our_service.containsCharacteristic(char_handle));
|
|
||||||
GatoCharacteristic our_char = our_service.getCharacteristic(char_handle);
|
|
||||||
Q_ASSERT(our_char.startHandle() == char_handle);
|
|
||||||
|
|
||||||
if (state() == StateConnected) {
|
|
||||||
d->clearCharacteristicDescriptors(&our_char);
|
|
||||||
our_service.addCharacteristic(our_char); // Update service with empty descriptors list
|
|
||||||
uint req = d->att->requestFindInformation(our_char.startHandle() + 1, our_char.endHandle(),
|
|
||||||
d, SLOT(handleDescriptors(uint,QList<GatoAttClient::InformationData>)));
|
|
||||||
d->pending_descriptor_reqs.insert(req, char_handle);
|
|
||||||
} else {
|
|
||||||
qWarning() << "Not connected";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheral::readValue(const GatoCharacteristic &characteristic)
|
|
||||||
{
|
|
||||||
Q_D(GatoPeripheral);
|
|
||||||
|
|
||||||
GatoHandle char_handle = characteristic.startHandle();
|
|
||||||
GatoHandle service_handle = d->characteristic_to_service.value(char_handle);
|
|
||||||
|
|
||||||
if (!service_handle) {
|
|
||||||
qWarning() << "Unknown characteristic for this peripheral";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GatoService &our_service = d->services[service_handle];
|
|
||||||
Q_ASSERT(our_service.containsCharacteristic(char_handle));
|
|
||||||
|
|
||||||
if (state() == StateConnected) {
|
|
||||||
uint req = d->att->requestRead(characteristic.valueHandle(),
|
|
||||||
d, SLOT(handleCharacteristicRead(uint,QByteArray)));
|
|
||||||
d->pending_characteristic_read_reqs.insert(req, char_handle);
|
|
||||||
} else {
|
|
||||||
qWarning() << "Not connected";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheral::readValue(const GatoDescriptor &descriptor)
|
|
||||||
{
|
|
||||||
Q_D(GatoPeripheral);
|
|
||||||
|
|
||||||
GatoHandle desc_handle = descriptor.handle();
|
|
||||||
GatoHandle char_handle = d->descriptor_to_characteristic.value(desc_handle);
|
|
||||||
|
|
||||||
if (!char_handle) {
|
|
||||||
qWarning() << "Unknown descriptor for this peripheral";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GatoHandle service_handle = d->characteristic_to_service.value(char_handle);
|
|
||||||
Q_ASSERT(service_handle);
|
|
||||||
|
|
||||||
GatoService &our_service = d->services[service_handle];
|
|
||||||
Q_ASSERT(our_service.containsCharacteristic(char_handle));
|
|
||||||
|
|
||||||
if (state() == StateConnected) {
|
|
||||||
uint req = d->att->requestRead(descriptor.handle(),
|
|
||||||
d, SLOT(handleDescriptorRead(uint,QByteArray)));
|
|
||||||
d->pending_descriptor_read_reqs.insert(req, char_handle);
|
|
||||||
} else {
|
|
||||||
qWarning() << "Not connected";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheral::writeValue(const GatoCharacteristic &characteristic, const QByteArray &data, WriteType type)
|
|
||||||
{
|
|
||||||
Q_D(GatoPeripheral);
|
|
||||||
|
|
||||||
GatoHandle char_handle = characteristic.startHandle();
|
|
||||||
GatoHandle service_handle = d->characteristic_to_service.value(char_handle);
|
|
||||||
|
|
||||||
if (!service_handle) {
|
|
||||||
qWarning() << "Unknown characteristic for this peripheral";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GatoService &our_service = d->services[service_handle];
|
|
||||||
Q_ASSERT(our_service.containsCharacteristic(char_handle));
|
|
||||||
|
|
||||||
if (state() == StateConnected) {
|
|
||||||
switch (type) {
|
|
||||||
case WriteWithResponse:
|
|
||||||
d->att->requestWrite(characteristic.valueHandle(), data,
|
|
||||||
d, SLOT(handleCharacteristicWrite(uint,bool)));
|
|
||||||
break;
|
|
||||||
case WriteWithoutResponse:
|
|
||||||
d->att->commandWrite(characteristic.valueHandle(), data);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
|
||||||
qWarning() << "Not connected";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheral::writeValue(const GatoDescriptor &descriptor, const QByteArray &data)
|
|
||||||
{
|
|
||||||
Q_D(GatoPeripheral);
|
|
||||||
|
|
||||||
GatoHandle desc_handle = descriptor.handle();
|
|
||||||
GatoHandle char_handle = d->descriptor_to_characteristic.value(desc_handle);
|
|
||||||
|
|
||||||
if (!char_handle) {
|
|
||||||
qWarning() << "Unknown descriptor for this peripheral";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GatoHandle service_handle = d->characteristic_to_service.value(char_handle);
|
|
||||||
Q_ASSERT(service_handle);
|
|
||||||
|
|
||||||
GatoService &our_service = d->services[service_handle];
|
|
||||||
Q_ASSERT(our_service.containsCharacteristic(char_handle));
|
|
||||||
|
|
||||||
if (state() == StateConnected) {
|
|
||||||
d->att->requestWrite(descriptor.handle(), data,
|
|
||||||
d, SLOT(handleDescriptorWrite(uint,bool)));
|
|
||||||
} else {
|
|
||||||
qWarning() << "Not connected";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheral::setNotification(const GatoCharacteristic &characteristic, bool enabled)
|
|
||||||
{
|
|
||||||
Q_D(GatoPeripheral);
|
|
||||||
|
|
||||||
GatoHandle char_handle = characteristic.startHandle();
|
|
||||||
GatoHandle service_handle = d->characteristic_to_service.value(char_handle);
|
|
||||||
|
|
||||||
if (!service_handle) {
|
|
||||||
qWarning() << "Unknown characteristic for this peripheral";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GatoService &our_service = d->services[service_handle];
|
|
||||||
Q_ASSERT(our_service.containsCharacteristic(char_handle));
|
|
||||||
GatoCharacteristic our_char = our_service.getCharacteristic(char_handle);
|
|
||||||
|
|
||||||
if (!(our_char.properties() & GatoCharacteristic::PropertyNotify)) {
|
|
||||||
qWarning() << "Characteristic does not support notifications";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state() != StateConnected) {
|
|
||||||
qWarning() << "Not connected";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const GatoUUID uuid(GatoUUID::GattClientCharacteristicConfiguration);
|
|
||||||
if (our_char.containsDescriptor(uuid)) {
|
|
||||||
GatoDescriptor desc = our_char.getDescriptor(uuid);
|
|
||||||
d->pending_set_notify.remove(char_handle);
|
|
||||||
writeValue(characteristic, d->genClientCharConfiguration(true, false));
|
|
||||||
} else {
|
|
||||||
d->pending_set_notify[char_handle] = enabled;
|
|
||||||
discoverDescriptors(our_char); // May need to find appropiate descriptor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GatoPeripheralPrivate::GatoPeripheralPrivate(GatoPeripheral *parent)
|
|
||||||
: QObject(parent), q_ptr(parent),
|
|
||||||
complete_name(false), complete_services(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
GatoPeripheralPrivate::~GatoPeripheralPrivate()
|
|
||||||
{
|
|
||||||
delete att;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheralPrivate::parseEIRFlags(quint8 data[], int len)
|
|
||||||
{
|
|
||||||
Q_UNUSED(data);
|
|
||||||
Q_UNUSED(len);
|
|
||||||
// Nothing to do for now.
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheralPrivate::parseEIRUUIDs(int size, bool complete, quint8 data[], int len)
|
|
||||||
{
|
|
||||||
Q_UNUSED(complete);
|
|
||||||
|
|
||||||
if (size != 16/8 && size != 32/8 && size != 128/8) {
|
|
||||||
qWarning() << "Unhandled UUID size: " << size;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int pos = 0; pos < len; pos += size) {
|
|
||||||
char *ptr = reinterpret_cast<char*>(&data[pos]);
|
|
||||||
QByteArray ba = QByteArray::fromRawData(ptr, size/8);
|
|
||||||
|
|
||||||
service_uuids.insert(bytearray_to_gatouuid(ba));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheralPrivate::parseName(bool complete, quint8 data[], int len)
|
|
||||||
{
|
|
||||||
Q_Q(GatoPeripheral);
|
|
||||||
if (complete || !complete_name) {
|
|
||||||
name = QString::fromUtf8(reinterpret_cast<char*>(data), len);
|
|
||||||
complete_name = complete;
|
|
||||||
emit q->nameChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GatoCharacteristic GatoPeripheralPrivate::parseCharacteristicValue(const QByteArray &ba)
|
|
||||||
{
|
|
||||||
GatoCharacteristic characteristic;
|
|
||||||
const char *data = ba.constData();
|
|
||||||
|
|
||||||
quint8 properties = data[0];
|
|
||||||
characteristic.setProperties(GatoCharacteristic::Properties(properties));
|
|
||||||
|
|
||||||
GatoHandle handle = read_le<quint16>(&data[1]);
|
|
||||||
characteristic.setValueHandle(handle);
|
|
||||||
|
|
||||||
GatoUUID uuid = bytearray_to_gatouuid(ba.mid(3));
|
|
||||||
characteristic.setUuid(uuid);
|
|
||||||
|
|
||||||
return characteristic;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray GatoPeripheralPrivate::genClientCharConfiguration(bool notification, bool indication)
|
|
||||||
{
|
|
||||||
QByteArray ba;
|
|
||||||
ba.resize(sizeof(quint16));
|
|
||||||
|
|
||||||
quint16 val = 0;
|
|
||||||
if (notification)
|
|
||||||
val |= 0x1;
|
|
||||||
if (indication)
|
|
||||||
val |= 0x2;
|
|
||||||
|
|
||||||
write_le<quint16>(val, ba.data());
|
|
||||||
|
|
||||||
return ba;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheralPrivate::clearServices()
|
|
||||||
{
|
|
||||||
characteristic_to_service.clear();
|
|
||||||
value_to_characteristic.clear();
|
|
||||||
descriptor_to_characteristic.clear();
|
|
||||||
services.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheralPrivate::clearServiceCharacteristics(GatoService *service)
|
|
||||||
{
|
|
||||||
QList<GatoCharacteristic> chars = service->characteristics();
|
|
||||||
QList<GatoCharacteristic>::iterator it;
|
|
||||||
for (it = chars.begin(); it != chars.end(); ++it) {
|
|
||||||
clearCharacteristicDescriptors(&*it);
|
|
||||||
characteristic_to_service.remove(it->startHandle());
|
|
||||||
value_to_characteristic.remove(it->valueHandle());
|
|
||||||
}
|
|
||||||
service->clearCharacteristics();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheralPrivate::clearCharacteristicDescriptors(GatoCharacteristic *characteristic)
|
|
||||||
{
|
|
||||||
QList<GatoDescriptor> descs = characteristic->descriptors();
|
|
||||||
foreach (const GatoDescriptor& d, descs) {
|
|
||||||
descriptor_to_characteristic.remove(d.handle());
|
|
||||||
}
|
|
||||||
characteristic->clearDescriptors();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheralPrivate::finishSetNotifyOperations(const GatoCharacteristic &characteristic)
|
|
||||||
{
|
|
||||||
Q_Q(GatoPeripheral);
|
|
||||||
|
|
||||||
GatoHandle handle = characteristic.startHandle();
|
|
||||||
|
|
||||||
if (pending_set_notify.contains(handle)) {
|
|
||||||
const GatoUUID uuid(GatoUUID::GattClientCharacteristicConfiguration);
|
|
||||||
bool notify = pending_set_notify.value(handle);
|
|
||||||
|
|
||||||
foreach (const GatoDescriptor &descriptor, characteristic.descriptors()) {
|
|
||||||
if (descriptor.uuid() == uuid) {
|
|
||||||
q->writeValue(descriptor, genClientCharConfiguration(notify, false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pending_set_notify.remove(handle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheralPrivate::handleAttConnected()
|
|
||||||
{
|
|
||||||
Q_Q(GatoPeripheral);
|
|
||||||
|
|
||||||
emit q->connected();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheralPrivate::handleAttDisconnected()
|
|
||||||
{
|
|
||||||
Q_Q(GatoPeripheral);
|
|
||||||
|
|
||||||
// Forget about all pending requests
|
|
||||||
pending_primary_reqs.clear();
|
|
||||||
pending_characteristic_reqs.clear();
|
|
||||||
pending_characteristic_read_reqs.clear();
|
|
||||||
pending_descriptor_reqs.clear();
|
|
||||||
pending_descriptor_read_reqs.clear();
|
|
||||||
|
|
||||||
emit q->disconnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheralPrivate::handleAttAttributeUpdated(GatoHandle handle, const QByteArray &value, bool confirmed)
|
|
||||||
{
|
|
||||||
Q_Q(GatoPeripheral);
|
|
||||||
Q_UNUSED(confirmed);
|
|
||||||
|
|
||||||
// Let's see if this is a handle we know about.
|
|
||||||
if (value_to_characteristic.contains(handle)) {
|
|
||||||
// Ok, it's a characteristic value.
|
|
||||||
GatoHandle char_handle = value_to_characteristic.value(handle);
|
|
||||||
GatoHandle service_handle = characteristic_to_service.value(char_handle);
|
|
||||||
if (!service_handle) {
|
|
||||||
qWarning() << "Got a notification for a characteristic I don't know about";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GatoService &service = services[service_handle];
|
|
||||||
GatoCharacteristic characteristic = service.getCharacteristic(char_handle);
|
|
||||||
|
|
||||||
emit q->valueUpdated(characteristic, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheralPrivate::handlePrimary(uint req, const QList<GatoAttClient::AttributeGroupData> &list)
|
|
||||||
{
|
|
||||||
Q_Q(GatoPeripheral);
|
|
||||||
Q_UNUSED(req);
|
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
|
||||||
complete_services = true;
|
|
||||||
emit q->servicesDiscovered();
|
|
||||||
} else {
|
|
||||||
GatoHandle last_handle = 0;
|
|
||||||
|
|
||||||
foreach (const GatoAttClient::AttributeGroupData &data, list) {
|
|
||||||
GatoUUID uuid = bytearray_to_gatouuid(data.value);
|
|
||||||
GatoService service;
|
|
||||||
|
|
||||||
service.setUuid(uuid);
|
|
||||||
service.setStartHandle(data.start);
|
|
||||||
service.setEndHandle(data.end);
|
|
||||||
|
|
||||||
services.insert(data.start, service);
|
|
||||||
service_uuids.insert(uuid);
|
|
||||||
|
|
||||||
last_handle = data.end;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch following attributes
|
|
||||||
att->requestReadByGroupType(last_handle + 1, 0xFFFF, GatoUUID::GattPrimaryService,
|
|
||||||
this, SLOT(handlePrimary(uint,QList<GatoAttClient::AttributeGroupData>)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheralPrivate::handlePrimaryForService(uint req, const QList<GatoAttClient::HandleInformation> &list)
|
|
||||||
{
|
|
||||||
Q_Q(GatoPeripheral);
|
|
||||||
|
|
||||||
GatoUUID uuid = pending_primary_reqs.value(req, GatoUUID());
|
|
||||||
if (uuid.isNull()) {
|
|
||||||
qDebug() << "Got primary for service response for a request I did not make";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pending_primary_reqs.remove(req);
|
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
|
||||||
if (pending_primary_reqs.isEmpty()) {
|
|
||||||
emit q->servicesDiscovered();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
GatoHandle last_handle = 0;
|
|
||||||
|
|
||||||
foreach (const GatoAttClient::HandleInformation &data, list) {
|
|
||||||
GatoService service;
|
|
||||||
|
|
||||||
service.setUuid(uuid);
|
|
||||||
service.setStartHandle(data.start);
|
|
||||||
service.setEndHandle(data.end);
|
|
||||||
|
|
||||||
services.insert(data.start, service);
|
|
||||||
service_uuids.insert(uuid);
|
|
||||||
|
|
||||||
last_handle = data.end;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch following attributes
|
|
||||||
QByteArray value = gatouuid_to_bytearray(uuid, true, false);
|
|
||||||
uint req = att->requestFindByTypeValue(last_handle + 1, 0xFFFF, GatoUUID::GattPrimaryService, value,
|
|
||||||
this, SLOT(handlePrimaryForService(uint,QList<GatoAttClient::HandleInformation>)));
|
|
||||||
pending_primary_reqs.insert(req, uuid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheralPrivate::handleCharacteristic(uint req, const QList<GatoAttClient::AttributeData> &list)
|
|
||||||
{
|
|
||||||
Q_Q(GatoPeripheral);
|
|
||||||
|
|
||||||
GatoHandle service_start = pending_characteristic_reqs.value(req, 0);
|
|
||||||
if (!service_start) {
|
|
||||||
qDebug() << "Got characteristics for a request I did not make";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pending_characteristic_reqs.remove(req);
|
|
||||||
|
|
||||||
Q_ASSERT(services.contains(service_start));
|
|
||||||
GatoService &service = services[service_start];
|
|
||||||
Q_ASSERT(service.startHandle() == service_start);
|
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
|
||||||
emit q->characteristicsDiscovered(service);
|
|
||||||
} else {
|
|
||||||
GatoHandle last_handle = 0;
|
|
||||||
|
|
||||||
// If we are continuing a characteristic list, this means the
|
|
||||||
// last service we discovered in the previous iteration was not
|
|
||||||
// the last one, so we have to reduce its endHandle!
|
|
||||||
QList<GatoCharacteristic> cur_chars = service.characteristics();
|
|
||||||
if (!cur_chars.isEmpty()) {
|
|
||||||
GatoCharacteristic &last = cur_chars.back();
|
|
||||||
last.setEndHandle(list.front().handle - 1);
|
|
||||||
service.addCharacteristic(last);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < list.size(); i++) {
|
|
||||||
const GatoAttClient::AttributeData &data = list.at(i);
|
|
||||||
GatoCharacteristic characteristic = parseCharacteristicValue(data.value);
|
|
||||||
|
|
||||||
characteristic.setStartHandle(data.handle);
|
|
||||||
if (i + 1 < list.size()) {
|
|
||||||
characteristic.setEndHandle(list.at(i + 1).handle - 1);
|
|
||||||
} else {
|
|
||||||
characteristic.setEndHandle(service.endHandle());
|
|
||||||
}
|
|
||||||
|
|
||||||
service.addCharacteristic(characteristic);
|
|
||||||
characteristic_to_service.insert(data.handle, service_start);
|
|
||||||
value_to_characteristic.insert(characteristic.valueHandle(), data.handle);
|
|
||||||
|
|
||||||
last_handle = data.handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (last_handle >= service.endHandle()) {
|
|
||||||
// Already finished, no need to send another request
|
|
||||||
emit q->characteristicsDiscovered(service);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch following attributes
|
|
||||||
uint req = att->requestReadByType(last_handle + 1, service.endHandle(), GatoUUID::GattCharacteristic,
|
|
||||||
this, SLOT(handleCharacteristic(uint,QList<GatoAttClient::AttributeData>)));
|
|
||||||
pending_characteristic_reqs.insert(req, service.startHandle());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheralPrivate::handleDescriptors(uint req, const QList<GatoAttClient::InformationData> &list)
|
|
||||||
{
|
|
||||||
Q_Q(GatoPeripheral);
|
|
||||||
|
|
||||||
GatoHandle char_handle = pending_descriptor_reqs.value(req);
|
|
||||||
if (!char_handle) {
|
|
||||||
qDebug() << "Got descriptor for a request I did not make";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pending_descriptor_reqs.remove(req);
|
|
||||||
GatoHandle service_handle = characteristic_to_service.value(char_handle);
|
|
||||||
if (!service_handle) {
|
|
||||||
qWarning() << "Unknown characteristic during descriptor discovery: " << char_handle;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_ASSERT(services.contains(service_handle));
|
|
||||||
GatoService &service = services[service_handle];
|
|
||||||
Q_ASSERT(service.startHandle() == service_handle);
|
|
||||||
|
|
||||||
Q_ASSERT(service.containsCharacteristic(char_handle));
|
|
||||||
GatoCharacteristic characteristic = service.getCharacteristic(char_handle);
|
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
|
||||||
finishSetNotifyOperations(characteristic);
|
|
||||||
emit q->descriptorsDiscovered(characteristic);
|
|
||||||
} else {
|
|
||||||
GatoHandle last_handle = 0;
|
|
||||||
|
|
||||||
foreach (const GatoAttClient::InformationData &data, list) {
|
|
||||||
// Skip the value attribute itself.
|
|
||||||
if (data.handle == characteristic.valueHandle()) continue;
|
|
||||||
|
|
||||||
GatoDescriptor descriptor;
|
|
||||||
|
|
||||||
descriptor.setHandle(data.handle);
|
|
||||||
descriptor.setUuid(data.uuid);
|
|
||||||
|
|
||||||
characteristic.addDescriptor(descriptor);
|
|
||||||
|
|
||||||
service.addCharacteristic(characteristic);
|
|
||||||
descriptor_to_characteristic.insert(data.handle, char_handle);
|
|
||||||
|
|
||||||
last_handle = data.handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
service.addCharacteristic(characteristic);
|
|
||||||
|
|
||||||
if (last_handle >= characteristic.endHandle()) {
|
|
||||||
// Already finished, no need to send another request
|
|
||||||
finishSetNotifyOperations(characteristic);
|
|
||||||
emit q->descriptorsDiscovered(characteristic);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch following attributes
|
|
||||||
uint req = att->requestFindInformation(last_handle + 1, characteristic.endHandle(),
|
|
||||||
this, SLOT(handleDescriptors(uint,QList<GatoAttClient::InformationData>)));
|
|
||||||
pending_descriptor_reqs.insert(req, char_handle);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheralPrivate::handleCharacteristicRead(uint req, const QByteArray &value)
|
|
||||||
{
|
|
||||||
Q_Q(GatoPeripheral);
|
|
||||||
|
|
||||||
GatoHandle char_handle = pending_characteristic_read_reqs.value(req);
|
|
||||||
if (!char_handle) {
|
|
||||||
qDebug() << "Got characteristics for a request I did not make";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pending_characteristic_read_reqs.remove(req);
|
|
||||||
GatoHandle service_handle = characteristic_to_service.value(char_handle);
|
|
||||||
if (!service_handle) {
|
|
||||||
qWarning() << "Unknown characteristic during read: " << char_handle;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_ASSERT(services.contains(service_handle));
|
|
||||||
GatoService &service = services[service_handle];
|
|
||||||
Q_ASSERT(service.startHandle() == service_handle);
|
|
||||||
|
|
||||||
Q_ASSERT(service.containsCharacteristic(char_handle));
|
|
||||||
GatoCharacteristic characteristic = service.getCharacteristic(char_handle);
|
|
||||||
|
|
||||||
emit q->valueUpdated(characteristic, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheralPrivate::handleDescriptorRead(uint req, const QByteArray &value)
|
|
||||||
{
|
|
||||||
Q_Q(GatoPeripheral);
|
|
||||||
|
|
||||||
GatoHandle desc_handle = pending_descriptor_read_reqs.value(req);
|
|
||||||
if (!desc_handle) {
|
|
||||||
qDebug() << "Got characteristics for a request I did not make";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pending_descriptor_read_reqs.remove(req);
|
|
||||||
GatoHandle char_handle = descriptor_to_characteristic.value(desc_handle);
|
|
||||||
if (!char_handle) {
|
|
||||||
qWarning() << "Unknown characteristic during read: " << char_handle;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
GatoHandle service_handle = characteristic_to_service.value(char_handle);
|
|
||||||
if (!service_handle) {
|
|
||||||
qWarning() << "Unknown characteristic during read: " << char_handle;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_ASSERT(services.contains(service_handle));
|
|
||||||
GatoService &service = services[service_handle];
|
|
||||||
Q_ASSERT(service.startHandle() == service_handle);
|
|
||||||
|
|
||||||
Q_ASSERT(service.containsCharacteristic(char_handle));
|
|
||||||
GatoCharacteristic characteristic = service.getCharacteristic(char_handle);
|
|
||||||
|
|
||||||
Q_ASSERT(characteristic.containsDescriptor(desc_handle));
|
|
||||||
GatoDescriptor descriptor = characteristic.getDescriptor(desc_handle);
|
|
||||||
|
|
||||||
emit q->descriptorValueUpdated(descriptor, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheralPrivate::handleCharacteristicWrite(uint req, bool ok)
|
|
||||||
{
|
|
||||||
Q_UNUSED(req);
|
|
||||||
if (!ok) {
|
|
||||||
qWarning() << "Failed to write some characteristic";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatoPeripheralPrivate::handleDescriptorWrite(uint req, bool ok)
|
|
||||||
{
|
|
||||||
Q_UNUSED(req);
|
|
||||||
if (!ok) {
|
|
||||||
qWarning() << "Failed to write some characteristic";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
|
|
@ -1,111 +0,0 @@
|
||||||
/****************************************************************************
|
|
||||||
**
|
|
||||||
** Copyright (C) 2013 Javier de San Pedro <dev.git@javispedro.com>
|
|
||||||
** Contact: http://www.qt-project.org/legal
|
|
||||||
**
|
|
||||||
** This file is part of the QtBluetooth module of the Qt Toolkit.
|
|
||||||
**
|
|
||||||
** $QT_BEGIN_LICENSE:LGPL21$
|
|
||||||
** Commercial License Usage
|
|
||||||
** Licensees holding valid commercial Qt licenses may use this file in
|
|
||||||
** accordance with the commercial license agreement provided with the
|
|
||||||
** Software or, alternatively, in accordance with the terms contained in
|
|
||||||
** a written agreement between you and Digia. For licensing terms and
|
|
||||||
** conditions see http://qt.digia.com/licensing. For further information
|
|
||||||
** use the contact form at http://qt.digia.com/contact-us.
|
|
||||||
**
|
|
||||||
** GNU Lesser General Public License Usage
|
|
||||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
||||||
** General Public License version 2.1 or version 3 as published by the Free
|
|
||||||
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
|
||||||
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
|
||||||
** following information to ensure the GNU Lesser General Public License
|
|
||||||
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
|
||||||
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
||||||
**
|
|
||||||
** In addition, as a special exception, Digia gives you certain additional
|
|
||||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
|
||||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
||||||
**
|
|
||||||
** $QT_END_LICENSE$
|
|
||||||
**
|
|
||||||
****************************************************************************/
|
|
||||||
|
|
||||||
#ifndef GATOPERIPHERAL_H
|
|
||||||
#define GATOPERIPHERAL_H
|
|
||||||
|
|
||||||
#include <QtCore/QObject>
|
|
||||||
#include "libgato_global.h"
|
|
||||||
#include "gatouuid.h"
|
|
||||||
#include "gatoaddress.h"
|
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
|
||||||
|
|
||||||
class GatoService;
|
|
||||||
class GatoCharacteristic;
|
|
||||||
class GatoDescriptor;
|
|
||||||
class GatoPeripheralPrivate;
|
|
||||||
|
|
||||||
class LIBGATO_EXPORT GatoPeripheral : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
Q_DECLARE_PRIVATE(GatoPeripheral)
|
|
||||||
Q_ENUMS(State)
|
|
||||||
Q_ENUMS(WriteType)
|
|
||||||
Q_PROPERTY(GatoAddress address READ address)
|
|
||||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
|
||||||
|
|
||||||
public:
|
|
||||||
GatoPeripheral(const GatoAddress& addr, QObject *parent = 0);
|
|
||||||
~GatoPeripheral();
|
|
||||||
|
|
||||||
enum State {
|
|
||||||
StateDisconnected,
|
|
||||||
StateConnecting,
|
|
||||||
StateConnected
|
|
||||||
};
|
|
||||||
|
|
||||||
enum WriteType {
|
|
||||||
WriteWithResponse = 0,
|
|
||||||
WriteWithoutResponse
|
|
||||||
};
|
|
||||||
|
|
||||||
State state() const;
|
|
||||||
GatoAddress address() const;
|
|
||||||
QString name() const;
|
|
||||||
QList<GatoService> services() const;
|
|
||||||
|
|
||||||
void parseEIR(quint8 data[], int len);
|
|
||||||
bool advertisesService(const GatoUUID &uuid) const;
|
|
||||||
|
|
||||||
public Q_SLOTS:
|
|
||||||
void connectPeripheral();
|
|
||||||
void disconnectPeripheral();
|
|
||||||
void discoverServices();
|
|
||||||
void discoverServices(const QList<GatoUUID>& serviceUUIDs);
|
|
||||||
void discoverCharacteristics(const GatoService &service);
|
|
||||||
void discoverCharacteristics(const GatoService &service, const QList<GatoUUID>& characteristicUUIDs);
|
|
||||||
void discoverDescriptors(const GatoCharacteristic &characteristic);
|
|
||||||
void readValue(const GatoCharacteristic &characteristic);
|
|
||||||
void readValue(const GatoDescriptor &descriptor);
|
|
||||||
void writeValue(const GatoCharacteristic &characteristic, const QByteArray &data, WriteType type = WriteWithResponse);
|
|
||||||
void writeValue(const GatoDescriptor &descriptor, const QByteArray &data);
|
|
||||||
void setNotification(const GatoCharacteristic &characteristic, bool enabled);
|
|
||||||
|
|
||||||
Q_SIGNALS:
|
|
||||||
void connected();
|
|
||||||
void disconnected();
|
|
||||||
void nameChanged();
|
|
||||||
void servicesDiscovered();
|
|
||||||
void characteristicsDiscovered(const GatoService &service);
|
|
||||||
void descriptorsDiscovered(const GatoCharacteristic &characteristic);
|
|
||||||
void valueUpdated(const GatoCharacteristic &characteristic, const QByteArray &value);
|
|
||||||
void descriptorValueUpdated(const GatoDescriptor &descriptor, const QByteArray &value);
|
|
||||||
|
|
||||||
private:
|
|
||||||
GatoPeripheralPrivate *const d_ptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
|
||||||
|
|
||||||
#endif // GATOPERIPHERAL_H
|
|
|
@ -1,108 +0,0 @@
|
||||||
/****************************************************************************
|
|
||||||
**
|
|
||||||
** Copyright (C) 2013 Javier de San Pedro <dev.git@javispedro.com>
|
|
||||||
** Contact: http://www.qt-project.org/legal
|
|
||||||
**
|
|
||||||
** This file is part of the QtBluetooth module of the Qt Toolkit.
|
|
||||||
**
|
|
||||||
** $QT_BEGIN_LICENSE:LGPL21$
|
|
||||||
** Commercial License Usage
|
|
||||||
** Licensees holding valid commercial Qt licenses may use this file in
|
|
||||||
** accordance with the commercial license agreement provided with the
|
|
||||||
** Software or, alternatively, in accordance with the terms contained in
|
|
||||||
** a written agreement between you and Digia. For licensing terms and
|
|
||||||
** conditions see http://qt.digia.com/licensing. For further information
|
|
||||||
** use the contact form at http://qt.digia.com/contact-us.
|
|
||||||
**
|
|
||||||
** GNU Lesser General Public License Usage
|
|
||||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
||||||
** General Public License version 2.1 or version 3 as published by the Free
|
|
||||||
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
|
||||||
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
|
||||||
** following information to ensure the GNU Lesser General Public License
|
|
||||||
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
|
||||||
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
||||||
**
|
|
||||||
** In addition, as a special exception, Digia gives you certain additional
|
|
||||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
|
||||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
||||||
**
|
|
||||||
** $QT_END_LICENSE$
|
|
||||||
**
|
|
||||||
****************************************************************************/
|
|
||||||
|
|
||||||
#ifndef GATOPERIPHERAL_P_H
|
|
||||||
#define GATOPERIPHERAL_P_H
|
|
||||||
|
|
||||||
#include "gatoperipheral.h"
|
|
||||||
#include "gatoservice.h"
|
|
||||||
#include "gatocharacteristic.h"
|
|
||||||
#include "gatodescriptor.h"
|
|
||||||
#include "gatoattclient.h"
|
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
|
||||||
|
|
||||||
class GatoPeripheralPrivate : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
Q_DECLARE_PUBLIC(GatoPeripheral)
|
|
||||||
|
|
||||||
public:
|
|
||||||
GatoPeripheralPrivate(GatoPeripheral *parent);
|
|
||||||
~GatoPeripheralPrivate();
|
|
||||||
|
|
||||||
GatoPeripheral *q_ptr;
|
|
||||||
GatoAddress addr;
|
|
||||||
QString name;
|
|
||||||
QSet<GatoUUID> service_uuids;
|
|
||||||
QMap<GatoHandle, GatoService> services;
|
|
||||||
|
|
||||||
bool complete_name : 1;
|
|
||||||
bool complete_services : 1;
|
|
||||||
|
|
||||||
/** Maps attribute handles to service handles. */
|
|
||||||
QMap<GatoHandle, GatoHandle> characteristic_to_service;
|
|
||||||
QMap<GatoHandle, GatoHandle> value_to_characteristic;
|
|
||||||
QMap<GatoHandle, GatoHandle> descriptor_to_characteristic;
|
|
||||||
|
|
||||||
GatoAttClient *att;
|
|
||||||
QMap<uint, GatoUUID> pending_primary_reqs;
|
|
||||||
QMap<uint, GatoHandle> pending_characteristic_reqs;
|
|
||||||
QMap<uint, GatoHandle> pending_characteristic_read_reqs;
|
|
||||||
QMap<uint, GatoHandle> pending_descriptor_reqs;
|
|
||||||
QMap<uint, GatoHandle> pending_descriptor_read_reqs;
|
|
||||||
|
|
||||||
QMap<GatoHandle, bool> pending_set_notify;
|
|
||||||
|
|
||||||
void parseEIRFlags(quint8 data[], int len);
|
|
||||||
void parseEIRUUIDs(int size, bool complete, quint8 data[], int len);
|
|
||||||
void parseName(bool complete, quint8 data[], int len);
|
|
||||||
|
|
||||||
static GatoCharacteristic parseCharacteristicValue(const QByteArray &ba);
|
|
||||||
|
|
||||||
static QByteArray genClientCharConfiguration(bool notification, bool indication);
|
|
||||||
|
|
||||||
void clearServices();
|
|
||||||
void clearServiceCharacteristics(GatoService *service);
|
|
||||||
void clearCharacteristicDescriptors(GatoCharacteristic *characteristic);
|
|
||||||
|
|
||||||
void finishSetNotifyOperations(const GatoCharacteristic &characteristic);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void handleAttConnected();
|
|
||||||
void handleAttDisconnected();
|
|
||||||
void handleAttAttributeUpdated(GatoHandle handle, const QByteArray &value, bool confirmed);
|
|
||||||
void handlePrimary(uint req, const QList<GatoAttClient::AttributeGroupData>& list);
|
|
||||||
void handlePrimaryForService(uint req, const QList<GatoAttClient::HandleInformation>& list);
|
|
||||||
void handleCharacteristic(uint req, const QList<GatoAttClient::AttributeData> &list);
|
|
||||||
void handleDescriptors(uint req, const QList<GatoAttClient::InformationData> &list);
|
|
||||||
void handleCharacteristicRead(uint req, const QByteArray &value);
|
|
||||||
void handleDescriptorRead(uint req, const QByteArray &value);
|
|
||||||
void handleCharacteristicWrite(uint req, bool ok);
|
|
||||||
void handleDescriptorWrite(uint req, bool ok);
|
|
||||||
};
|
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
|
||||||
|
|
||||||
#endif // GATOPERIPHERAL_P_H
|
|
|
@ -17,6 +17,3 @@
|
||||||
"qtxmlpatterns" => "",
|
"qtxmlpatterns" => "",
|
||||||
"qtandroidextras" => "",
|
"qtandroidextras" => "",
|
||||||
);
|
);
|
||||||
|
|
||||||
my @gato_headers = ("gatoattclient.h", "gatoperipheral.h");
|
|
||||||
@ignore_for_master_contents = ( @gato_headers );
|
|
||||||
|
|
Loading…
Reference in New Issue