diff options
-rw-r--r-- | gato.h | 13 | ||||
-rw-r--r-- | gatoaddress.cpp | 110 | ||||
-rw-r--r-- | gatoaddress.h | 32 | ||||
-rw-r--r-- | gatoatt.cpp | 578 | ||||
-rw-r--r-- | gatoatt.h | 97 | ||||
-rw-r--r-- | gatocentralmanager.cpp | 222 | ||||
-rw-r--r-- | gatocentralmanager.h | 44 | ||||
-rw-r--r-- | gatocentralmanager_p.h | 32 | ||||
-rw-r--r-- | gatocharacteristic.cpp | 159 | ||||
-rw-r--r-- | gatocharacteristic.h | 69 | ||||
-rw-r--r-- | gatodescriptor.cpp | 72 | ||||
-rw-r--r-- | gatodescriptor.h | 32 | ||||
-rw-r--r-- | gatoperipheral.cpp | 834 | ||||
-rw-r--r-- | gatoperipheral.h | 67 | ||||
-rw-r--r-- | gatoperipheral_p.h | 71 | ||||
-rw-r--r-- | gatoservice.cpp | 129 | ||||
-rw-r--r-- | gatoservice.h | 42 | ||||
-rw-r--r-- | gatosocket.cpp | 193 | ||||
-rw-r--r-- | gatosocket.h | 54 | ||||
-rw-r--r-- | gatouuid.cpp | 184 | ||||
-rw-r--r-- | gatouuid.h | 109 | ||||
-rw-r--r-- | helpers.cpp | 64 | ||||
-rw-r--r-- | helpers.h | 35 | ||||
-rw-r--r-- | libgato.pro | 39 | ||||
-rw-r--r-- | libgato_global.h | 14 |
25 files changed, 3295 insertions, 0 deletions
@@ -0,0 +1,13 @@ +#ifndef GATO_H +#define GATO_H + +#include "libgato_global.h" +#include "gatoaddress.h" +#include "gatouuid.h" +#include "gatocentralmanager.h" +#include "gatoperipheral.h" +#include "gatoservice.h" +#include "gatocharacteristic.h" +#include "gatodescriptor.h" + +#endif // GATO_H diff --git a/gatoaddress.cpp b/gatoaddress.cpp new file mode 100644 index 0000000..e29190b --- /dev/null +++ b/gatoaddress.cpp @@ -0,0 +1,110 @@ +/* + * libgato - A GATT/ATT library for use with Bluez + * + * Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <QtCore/QString> +#include <QtCore/QHash> +#include <cstdio> + +#include <bluetooth/bluetooth.h> + +#include "gatoaddress.h" + +struct GatoAddressPrivate : QSharedData +{ + union { + bdaddr_t bd; + quint64 u64; + } addr; +}; + +GatoAddress::GatoAddress() + : d(new GatoAddressPrivate) +{ +} + + + +GatoAddress::GatoAddress(quint64 addr) + : d(new GatoAddressPrivate) +{ + d->addr.u64 = addr; +} + +GatoAddress::GatoAddress(quint8 addr[]) + : d(new GatoAddressPrivate) +{ + d->addr.u64 = 0; + for (int i = 0; i < 6; i++) { + d->addr.bd.b[i] = addr[i]; + } +} + +GatoAddress::GatoAddress(const QString &addr) + : d(new GatoAddressPrivate) +{ + d->addr.u64 = 0; + str2ba(addr.toLatin1().constData(), &d->addr.bd); +} + +GatoAddress::GatoAddress(const GatoAddress &o) + : d(o.d) +{ +} + +GatoAddress::~GatoAddress() +{ +} + +GatoAddress & GatoAddress::operator =(const GatoAddress& o) +{ + if (this != &o) { + d = o.d; + } + return *this; +} + +quint64 GatoAddress::toUInt64() const +{ + return d->addr.u64; +} + +void GatoAddress::toUInt8Array(quint8 addr[]) const +{ + for (int i = 0; i < 6; i++) { + addr[i] = d->addr.bd.b[i]; + } +} + +QString GatoAddress::toString() const +{ + char addr[18]; + ba2str(&d->addr.bd, addr); + return QString::fromAscii(addr); +} + +bool operator==(const GatoAddress &a, const GatoAddress &b) +{ + return a.toUInt64() == b.toUInt64(); +} + +uint qHash(const GatoAddress &a) +{ + return qHash(a.toUInt64()); +} diff --git a/gatoaddress.h b/gatoaddress.h new file mode 100644 index 0000000..f30ec43 --- /dev/null +++ b/gatoaddress.h @@ -0,0 +1,32 @@ +#ifndef GATOADDRESS_H +#define GATOADDRESS_H + +#include <QtCore/QSharedDataPointer> +#include "libgato_global.h" + +class GatoAddressPrivate; + +class LIBGATO_EXPORT GatoAddress +{ +public: + GatoAddress(); + explicit GatoAddress(quint8 addr[]); + explicit GatoAddress(quint64 addr); + explicit GatoAddress(const QString &addr); + GatoAddress(const GatoAddress& o); + ~GatoAddress(); + + GatoAddress& operator=(const GatoAddress& o); + + void toUInt8Array(quint8 addr[]) const; + quint64 toUInt64() const; + QString toString() const; + +private: + QSharedDataPointer<GatoAddressPrivate> d; +}; + +LIBGATO_EXPORT bool operator==(const GatoAddress &a, const GatoAddress &b); +LIBGATO_EXPORT uint qHash(const GatoAddress &a); + +#endif // GATOADDRESS_H diff --git a/gatoatt.cpp b/gatoatt.cpp new file mode 100644 index 0000000..8015757 --- /dev/null +++ b/gatoatt.cpp @@ -0,0 +1,578 @@ +/* + * libgato - A GATT/ATT library for use with Bluez + * + * Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <QtCore/QDebug> + +#include "gatoatt.h" +#include "helpers.h" + +#define PROTOCOL_DEBUG 0 + +#define ATT_CID 4 +#define ATT_PSM 31 + +#define ATT_DEFAULT_LE_MTU 23 + +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); +} + +GatoAtt::GatoAtt(QObject *parent) : + QObject(parent), socket(new GatoSocket(this)), 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())); +} + +GatoAtt::~GatoAtt() +{ +} + +GatoSocket::State GatoAtt::state() const +{ + return socket->state(); +} + +bool GatoAtt::connectTo(const GatoAddress &addr) +{ + return socket->connectTo(addr, ATT_CID); +} + +void GatoAtt::close() +{ + socket->close(); +} + +uint GatoAtt::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 GatoAtt::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 GatoAtt::requestExchangeMTU(quint8 client_mtu, QObject *receiver, const char *member) +{ + QByteArray data(1, client_mtu); + return request(AttOpExchangeMTURequest, data, receiver, member); +} + +uint GatoAtt::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 GatoAtt::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 GatoAtt::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; + writeUuid16or128(s, uuid); + + return request(AttOpReadByTypeRequest, data, receiver, member); +} + +uint GatoAtt::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 GatoAtt::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; + writeUuid16or128(s, uuid); + + return request(AttOpReadByGroupTypeRequest, data, receiver, member); +} + +uint GatoAtt::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 GatoAtt::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 GatoAtt::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 GatoAtt::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 GatoAtt::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(quint8, response[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(quint8, response[1])); + } + 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<GatoAtt::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<GatoAtt::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<GatoAtt::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<GatoAtt::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<GatoAtt::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<GatoAtt::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<GatoAtt::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<GatoAtt::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; + } +} + +void GatoAtt::writeUuid16or128(QDataStream &s, const GatoUUID &uuid) +{ + s.setByteOrder(QDataStream::LittleEndian); + + bool uuid16_ok; + quint16 uuid16 = uuid.toUInt16(&uuid16_ok); + if (uuid16_ok) { + s << uuid16; + } else { + s << uuid.toUInt128(); + } +} + +QList<GatoAtt::InformationData> GatoAtt::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; + d.handle = read_le<GatoHandle>(&s[pos]); + switch (format) { + case 1: + d.uuid = GatoUUID(read_le<quint16>(&s[pos + 2])); + break; + case 2: + d.uuid = GatoUUID(read_le<gatouint128>(&s[pos + 2])); + break; + } + + list.append(d); + + pos += item_len; + } + + return list; +} + +QList<GatoAtt::HandleInformation> GatoAtt::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<GatoAtt::AttributeData> GatoAtt::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<GatoAtt::AttributeGroupData> GatoAtt::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 GatoAtt::handleSocketConnected() +{ + requestExchangeMTU(ATT_DEFAULT_LE_MTU, this, SLOT(handleServerMTU(quint8))); + emit connected(); +} + +void GatoAtt::handleSocketDisconnected() +{ + emit disconnected(); +} + +void GatoAtt::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 is"; + } +} + +void GatoAtt::handleServerMTU(uint req, quint8 server_mtu) +{ + Q_UNUSED(req); + if (server_mtu != 0) { + mtu = server_mtu; + } +} + + diff --git a/gatoatt.h b/gatoatt.h new file mode 100644 index 0000000..27cea3e --- /dev/null +++ b/gatoatt.h @@ -0,0 +1,97 @@ +#ifndef GATOATTSOCKET_H +#define GATOATTSOCKET_H + +#include <QtCore/QObject> +#include <QtCore/QQueue> +#include "gatosocket.h" +#include "gatouuid.h" + +class GatoAtt : public QObject +{ + Q_OBJECT + +public: + explicit GatoAtt(QObject *parent = 0); + ~GatoAtt(); + + 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; + }; + + uint request(int opcode, const QByteArray &data, QObject *receiver, const char *member); + uint requestExchangeMTU(quint8 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); + +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); + + void writeUuid16or128(QDataStream& s, const GatoUUID& uuid); + + 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 slots: + void handleSocketConnected(); + void handleSocketDisconnected(); + void handleSocketReadyRead(); + + void handleServerMTU(uint req, quint8 server_mtu); + +private: + GatoSocket *socket; + quint8 mtu; + uint next_id; + QQueue<Request> pending_requests; +}; + +#endif // GATOATTSOCKET_H diff --git a/gatocentralmanager.cpp b/gatocentralmanager.cpp new file mode 100644 index 0000000..3073d34 --- /dev/null +++ b/gatocentralmanager.cpp @@ -0,0 +1,222 @@ +/* + * libgato - A GATT/ATT library for use with Bluez + * + * Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <QtCore/QDebug> + +#include <unistd.h> +#include <errno.h> +#include <assert.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> + +#include "gatocentralmanager_p.h" +#include "gatoperipheral.h" + +GatoCentralManager::GatoCentralManager(QObject *parent) : + QObject(parent), d_ptr(new GatoCentralManagerPrivate) +{ + Q_D(GatoCentralManager); + d->q_ptr = this; + + // Hardcode the "default" bluetooth adapter for now. + d->dev_id = hci_get_route(NULL); + d->timeout = 1000; + d->hci = -1; + d->notifier = 0; +} + +GatoCentralManager::~GatoCentralManager() +{ + Q_D(GatoCentralManager); + if (d->scanning()) stopScan(); + delete d_ptr; +} + +void GatoCentralManager::scanForPeripherals(PeripheralScanOptions options) +{ + scanForPeripheralsWithServices(QList<GatoUUID>(), options); +} + +void GatoCentralManager::scanForPeripheralsWithServices(const QList<GatoUUID> &uuids, PeripheralScanOptions options) +{ + Q_D(GatoCentralManager); + + if (d->scanning()) stopScan(); + + if (!d->openDevice()) return; + d->filter_uuids = uuids; + + quint8 filter_dup = options & PeripheralScanOptionAllowDuplicates ? 0 : 1; + quint8 scan_type = options & PeripheralScanOptionActive ? 1 : 0; + int rc; + + hci_le_set_scan_enable(d->hci, 0, 0, d->timeout); + + rc = hci_le_set_scan_parameters(d->hci, scan_type, + htobs(0x0010), htobs(0x0010), + 0 /* Public address */, + 0 /* No filter ? */, + d->timeout); + if (rc < 0) { + qErrnoWarning("LE Set scan parameters failed"); + d->closeDevice(); + return; + } + + rc = hci_le_set_scan_enable(d->hci, 1, filter_dup, d->timeout); + + if (rc < 0) { + qErrnoWarning("LE Set scan enable failed"); + d->closeDevice(); + return; + } + + qDebug() << "LE Scan in progress"; + + d->notifier = new QSocketNotifier(d->hci, QSocketNotifier::Read); + connect(d->notifier, SIGNAL(activated(int)), this, SLOT(_q_readNotify())); + + socklen_t olen = sizeof(d->hci_of); + if (getsockopt(d->hci, SOL_HCI, HCI_FILTER, &d->hci_of, &olen) < 0) { + qErrnoWarning("Could not get existing HCI socket options"); + return; + } + + hci_filter_clear(&d->hci_nf); + hci_filter_set_ptype(HCI_EVENT_PKT, &d->hci_nf); + hci_filter_set_event(EVT_LE_META_EVENT, &d->hci_nf); + + if (setsockopt(d->hci, SOL_HCI, HCI_FILTER, &d->hci_nf, sizeof(d->hci_nf)) < 0) { + qErrnoWarning("Could not set HCI socket options"); + return; + } + + // SocketNotifier will call _q_readNotify() when ready +} + +void GatoCentralManager::stopScan() +{ + Q_D(GatoCentralManager); + if (d->scanning()) { + qDebug() << "Stopping LE scan"; + delete d->notifier; + setsockopt(d->hci, SOL_HCI, HCI_FILTER, &d->hci_of, sizeof(d->hci_of)); + hci_le_set_scan_enable(d->hci, 0, 0, d->timeout); + d->closeDevice(); + } else { + qDebug() << "No scan to stop"; + } + d->notifier = 0; + d->filter_uuids.clear(); + hci_filter_clear(&d->hci_nf); + hci_filter_clear(&d->hci_of); +} + +void GatoCentralManager::_q_readNotify() +{ + Q_D(GatoCentralManager); + unsigned char buf[HCI_MAX_EVENT_SIZE]; + + // Read a full event + int len; + if ((len = read(d->hci, buf, sizeof(buf))) < 0) { + if (errno != EAGAIN && errno != EINTR) { + qErrnoWarning("Could not read HCI events"); + } + return; // Will be notified later, probably. + } + + int pos = HCI_EVENT_HDR_SIZE + 1; + assert(pos < len); + evt_le_meta_event *meta = reinterpret_cast<evt_le_meta_event*>(&buf[pos]); + + + if (meta->subevent == EVT_LE_ADVERTISING_REPORT) { + pos++; // Skip subevent field + int num_reports = buf[pos]; + pos++; // Skip num_reports field + + assert(pos < len); + + while (num_reports > 0) { + le_advertising_info *info = reinterpret_cast<le_advertising_info*>(&buf[pos]); + assert(pos + LE_ADVERTISING_INFO_SIZE < len); + assert(pos + LE_ADVERTISING_INFO_SIZE + info->length < len); + int8_t *rssi = reinterpret_cast<int8_t*>(&buf[pos + LE_ADVERTISING_INFO_SIZE + info->length]); + + d->handleAdvertising(info, *rssi); + + pos += LE_ADVERTISING_INFO_SIZE + info->length + 1; + num_reports--; + } + } +} + +bool GatoCentralManagerPrivate::scanning() +{ + // If the HCI device is open for any reason, it means we're scanning. + return hci != -1; +} + +bool GatoCentralManagerPrivate::openDevice() +{ + hci = hci_open_dev(dev_id); + + if (hci == -1) { + qErrnoWarning("Could not open device"); + return false; + } + + return true; +} + +void GatoCentralManagerPrivate::closeDevice() +{ + hci_close_dev(hci); + hci = -1; +} + +void GatoCentralManagerPrivate::handleAdvertising(le_advertising_info *info, int rssi) +{ + Q_Q(GatoCentralManager); + + qDebug() << "Advertising event type" << info->evt_type + << "address type" << info->bdaddr_type + << "data length" << info->length + << "rssi" << rssi; + + GatoAddress addr(info->bdaddr.b); + GatoPeripheral *peripheral; + QHash<GatoAddress, GatoPeripheral*>::iterator it = peripherals.find(addr); + if (it == peripherals.end()) { + peripheral = new GatoPeripheral(addr, q); + peripherals.insert(addr, peripheral); + } else { + peripheral = *it; + } + + if (info->length > 0) { + peripheral->parseEIR(info->data, info->length); + } + + emit q->discoveredPeripheral(peripheral, rssi); +} diff --git a/gatocentralmanager.h b/gatocentralmanager.h new file mode 100644 index 0000000..00225a0 --- /dev/null +++ b/gatocentralmanager.h @@ -0,0 +1,44 @@ +#ifndef GATOCENTRALMANAGER_H +#define GATOCENTRALMANAGER_H + +#include <QtCore/QObject> +#include "libgato_global.h" +#include "gatouuid.h" + +class GatoPeripheral; +class GatoCentralManagerPrivate; + +class LIBGATO_EXPORT GatoCentralManager : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(GatoCentralManager) + Q_FLAGS(PeripheralScanOptions) + +public: + enum PeripheralScanOption { + PeripheralScanOptionActive = 1 << 0, + PeripheralScanOptionAllowDuplicates = 1 << 1 + }; + Q_DECLARE_FLAGS(PeripheralScanOptions, PeripheralScanOption) + + explicit GatoCentralManager(QObject *parent = 0); + ~GatoCentralManager(); + +public slots: + void scanForPeripherals(PeripheralScanOptions options = 0); + void scanForPeripheralsWithServices(const QList<GatoUUID>& uuids, PeripheralScanOptions options = 0); + void stopScan(); + +signals: + void discoveredPeripheral(GatoPeripheral *peripheral, int rssi); + +private slots: + void _q_readNotify(); + +private: + GatoCentralManagerPrivate *const d_ptr; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(GatoCentralManager::PeripheralScanOptions) + +#endif // GATOCENTRALMANAGER_H diff --git a/gatocentralmanager_p.h b/gatocentralmanager_p.h new file mode 100644 index 0000000..62ef954 --- /dev/null +++ b/gatocentralmanager_p.h @@ -0,0 +1,32 @@ +#ifndef GATOCENTRALMANAGER_P_H +#define GATOCENTRALMANAGER_P_H + +#include <QtCore/QSocketNotifier> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> + +#include "gatocentralmanager.h" +#include "gatoaddress.h" + +class GatoCentralManagerPrivate +{ + Q_DECLARE_PUBLIC(GatoCentralManager) + + GatoCentralManager *q_ptr; + int dev_id; + int timeout; + int hci; + QSocketNotifier *notifier; + QList<GatoUUID> filter_uuids; + hci_filter hci_nf, hci_of; + QHash<GatoAddress, GatoPeripheral*> peripherals; + + bool scanning(); + bool openDevice(); + void closeDevice(); + + void handleAdvertising(le_advertising_info *info, int rssi); +}; + +#endif // GATOCENTRALMANAGER_P_H diff --git a/gatocharacteristic.cpp b/gatocharacteristic.cpp new file mode 100644 index 0000000..a95c4a2 --- /dev/null +++ b/gatocharacteristic.cpp @@ -0,0 +1,159 @@ +/* + * libgato - A GATT/ATT library for use with Bluez + * + * Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <QtCore/QSharedData> + +#include "gatocharacteristic.h" +#include "gatodescriptor.h" + +struct GatoCharacteristicPrivate : public QSharedData +{ + GatoUUID uuid; + GatoHandle start; + GatoHandle end; + GatoCharacteristic::Properties properties; + GatoHandle value_handle; + QMap<GatoHandle, GatoDescriptor> descriptors; + QHash<GatoUUID, GatoHandle> desc_uuids; +}; + +GatoCharacteristic::GatoCharacteristic() + : d(new GatoCharacteristicPrivate) +{ + d->start = 0; + d->end = 0; + d->properties = 0; + d->value_handle = 0; +} + +GatoCharacteristic::GatoCharacteristic(const GatoCharacteristic &o) + : d(o.d) +{ +} + +GatoCharacteristic::~GatoCharacteristic() +{ +} + +bool GatoCharacteristic::isNull() const +{ + return d->start == 0 || d->end == 0 || d->uuid.isNull(); +} + +GatoUUID GatoCharacteristic::uuid() const +{ + return d->uuid; +} + +void GatoCharacteristic::setUuid(const GatoUUID &uuid) +{ + d->uuid = uuid; +} + +GatoCharacteristic::Properties GatoCharacteristic::properties() const +{ + return d->properties; +} + +void GatoCharacteristic::setProperties(GatoCharacteristic::Properties props) +{ + d->properties = props; +} + +GatoHandle GatoCharacteristic::startHandle() const +{ + return d->start; +} + +void GatoCharacteristic::setStartHandle(GatoHandle handle) +{ + d->start = handle; +} + +GatoHandle GatoCharacteristic::endHandle() const +{ + return d->end; +} + +void GatoCharacteristic::setEndHandle(GatoHandle handle) +{ + d->end = handle; +} + +GatoHandle GatoCharacteristic::valueHandle() const +{ + return d->value_handle; +} + +void GatoCharacteristic::setValueHandle(GatoHandle handle) +{ + d->value_handle = handle; +} + +QList<GatoDescriptor> GatoCharacteristic::descriptors() const +{ + return d->descriptors.values(); +} + +bool GatoCharacteristic::containsDescriptor(GatoUUID uuid) const +{ + return d->desc_uuids.contains(uuid); +} + +bool GatoCharacteristic::containsDescriptor(GatoHandle handle) const +{ + return d->descriptors.contains(handle); +} + +GatoDescriptor GatoCharacteristic::getDescriptor(GatoUUID uuid) const +{ + return d->descriptors.value(d->desc_uuids.value(uuid)); +} + +GatoDescriptor GatoCharacteristic::getDescriptor(GatoHandle handle) const +{ + return d->descriptors.value(handle); +} + +void GatoCharacteristic::addDescriptor(const GatoDescriptor &descriptor) +{ + d->descriptors.insert(descriptor.handle(), descriptor); + d->desc_uuids.insert(descriptor.uuid(), descriptor.handle()); +} + +void GatoCharacteristic::removeDescriptor(const GatoDescriptor &descriptor) +{ + d->descriptors.remove(descriptor.handle()); + d->desc_uuids.remove(descriptor.uuid()); +} + +void GatoCharacteristic::clearDescriptors() +{ + d->descriptors.clear(); + d->desc_uuids.clear(); +} + +GatoCharacteristic &GatoCharacteristic::operator=(const GatoCharacteristic &o) +{ + if (this != &o) { + d = o.d; + } + return *this; +} diff --git a/gatocharacteristic.h b/gatocharacteristic.h new file mode 100644 index 0000000..2b2f457 --- /dev/null +++ b/gatocharacteristic.h @@ -0,0 +1,69 @@ +#ifndef GATOCHARACTERISTIC_H +#define GATOCHARACTERISTIC_H + +#include <QtCore/QObject> +#include <QtCore/QSharedDataPointer> + +#include "gatouuid.h" + +class GatoCharacteristicPrivate; +class GatoDescriptor; + +class LIBGATO_EXPORT GatoCharacteristic +{ + Q_GADGET + Q_FLAGS(CharacteristicProperties) + +public: + enum Property { + PropertyBroadcast = 0x1, + PropertyRead = 0x2, + PropertyWriteWithoutResponse = 0x4, + PropertyWrite = 0x5, + PropertyNotify = 0x10, + PropertyIndicate = 0x20, + PropertyAuthenticatedSignedWrites = 0x40, + PropertyExtendedProperties = 0x80 + }; + Q_DECLARE_FLAGS(Properties, Property) + + GatoCharacteristic(); + GatoCharacteristic(const GatoCharacteristic &o); + ~GatoCharacteristic(); + + bool isNull() const; + + GatoUUID uuid() const; + void setUuid(const GatoUUID &uuid); + + Properties properties() const; + void setProperties(Properties props); + + GatoHandle startHandle() const; + void setStartHandle(GatoHandle handle); + + GatoHandle endHandle() const; + void setEndHandle(GatoHandle handle); + + GatoHandle valueHandle() const; + void setValueHandle(GatoHandle handle); + + QList<GatoDescriptor> descriptors() const; + bool containsDescriptor(const GatoDescriptor& descriptor) const; + bool containsDescriptor(GatoUUID uuid) const; + bool containsDescriptor(GatoHandle handle) const; + GatoDescriptor getDescriptor(GatoUUID uuid) const; + GatoDescriptor getDescriptor(GatoHandle handle) const; + void addDescriptor(const GatoDescriptor& descriptor); + void removeDescriptor(const GatoDescriptor& descriptor); + void clearDescriptors(); + + GatoCharacteristic &operator=(const GatoCharacteristic &o); + +private: + QSharedDataPointer<GatoCharacteristicPrivate> d; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(GatoCharacteristic::Properties) + +#endif // GATOCHARACTERISTIC_H diff --git a/gatodescriptor.cpp b/gatodescriptor.cpp new file mode 100644 index 0000000..dda6afd --- /dev/null +++ b/gatodescriptor.cpp @@ -0,0 +1,72 @@ +/* + * libgato - A GATT/ATT library for use with Bluez + * + * Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <QtCore/QSharedData> + +#include "gatodescriptor.h" + +struct GatoDescriptorPrivate : public QSharedData +{ + GatoUUID uuid; + GatoHandle handle; +}; + +GatoDescriptor::GatoDescriptor() + : d(new GatoDescriptorPrivate) +{ + d->handle = 0; +} + +GatoDescriptor::GatoDescriptor(const GatoDescriptor &o) + : d(o.d) +{ +} + +GatoDescriptor::~GatoDescriptor() +{ +} + +GatoUUID GatoDescriptor::uuid() const +{ + return d->uuid; +} + +void GatoDescriptor::setUuid(const GatoUUID &uuid) +{ + d->uuid = uuid; +} + +GatoHandle GatoDescriptor::handle() const +{ + return d->handle; +} + +void GatoDescriptor::setHandle(GatoHandle handle) +{ + d->handle = handle; +} + +GatoDescriptor &GatoDescriptor::operator=(const GatoDescriptor &o) +{ + if (this != &o) { + d = o.d; + } + return *this; +} diff --git a/gatodescriptor.h b/gatodescriptor.h new file mode 100644 index 0000000..7ced38a --- /dev/null +++ b/gatodescriptor.h @@ -0,0 +1,32 @@ +#ifndef GATODESCRIPTOR_H +#define GATODESCRIPTOR_H + +#include <QtCore/QObject> +#include <QtCore/QSharedDataPointer> + +#include "gatouuid.h" + +class GatoDescriptorPrivate; + +class LIBGATO_EXPORT GatoDescriptor +{ + Q_GADGET + +public: + GatoDescriptor(); + GatoDescriptor(const GatoDescriptor &o); + ~GatoDescriptor(); + + GatoUUID uuid() const; + void setUuid(const GatoUUID &uuid); + + GatoHandle handle() const; + void setHandle(GatoHandle handle); + + GatoDescriptor &operator=(const GatoDescriptor &o); + +private: + QSharedDataPointer<GatoDescriptorPrivate> d; +}; + +#endif // GATODESCRIPTOR_H diff --git a/gatoperipheral.cpp b/gatoperipheral.cpp new file mode 100644 index 0000000..faec8cd --- /dev/null +++ b/gatoperipheral.cpp @@ -0,0 +1,834 @@ +/* + * libgato - A GATT/ATT library for use with Bluez + * + * Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <QtCore/QDebug> + +#include <assert.h> +#include <bluetooth/bluetooth.h> + +#include "gatoperipheral_p.h" +#include "gatoaddress.h" +#include "gatouuid.h" +#include "helpers.h" + +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 +}; + +GatoPeripheral::GatoPeripheral(const GatoAddress &addr, QObject *parent) : + QObject(parent), d_ptr(new GatoPeripheralPrivate(this)) +{ + Q_D(GatoPeripheral); + d->addr = addr; + d->att = new GatoAtt(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; + default: + qWarning() << "Unknown EIR data type" << type; + break; + } + + pos += item_len; + } + + assert(pos == len); +} + +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<GatoAtt::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<GatoAtt::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<GatoAtt::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<GatoAtt::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) +{ + 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) { + d->att->requestWrite(characteristic.valueHandle(), data, + d, SLOT(handleCharacteristicRead(uint,QByteArray))); + } 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); + + for (int pos = 0; pos < len; pos += size) { + GatoUUID uuid; + switch (size) { + case 16/8: + uuid = GatoUUID(qFromLittleEndian<quint16>(&data[pos])); + break; + case 32/8: + uuid = GatoUUID(qFromLittleEndian<quint32>(&data[pos])); + break; + case 128/8: + uuid = GatoUUID(qFromLittleEndian<gatouint128>(&data[pos])); + break; + } + + service_uuids.insert(uuid); + } +} + +void GatoPeripheralPrivate::parseName(bool complete, quint8 data[], int len) +{ + Q_Q(GatoPeripheral); + if (complete || !complete_name) { + name = QString::fromAscii(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<GatoAtt::AttributeGroupData> &list) +{ + Q_Q(GatoPeripheral); + Q_UNUSED(req); + + if (list.isEmpty()) { + complete_services = true; + emit q->servicesDiscovered(); + } else { + GatoHandle last_handle = 0; + + foreach (const GatoAtt::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<GatoAtt::AttributeGroupData>))); + } +} + +void GatoPeripheralPrivate::handlePrimaryForService(uint req, const QList<GatoAtt::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 GatoAtt::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<GatoAtt::HandleInformation>))); + pending_primary_reqs.insert(req, uuid); + } +} + +void GatoPeripheralPrivate::handleCharacteristic(uint req, const QList<GatoAtt::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 GatoAtt::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<GatoAtt::AttributeData>))); + pending_characteristic_reqs.insert(req, service.startHandle()); + } +} + +void GatoPeripheralPrivate::handleDescriptors(uint req, const QList<GatoAtt::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 GatoAtt::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<GatoAtt::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"; + } +} diff --git a/gatoperipheral.h b/gatoperipheral.h new file mode 100644 index 0000000..6ad6782 --- /dev/null +++ b/gatoperipheral.h @@ -0,0 +1,67 @@ +#ifndef GATOPERIPHERAL_H +#define GATOPERIPHERAL_H + +#include <QtCore/QObject> +#include "libgato_global.h" +#include "gatouuid.h" +#include "gatoaddress.h" + +class GatoService; +class GatoCharacteristic; +class GatoDescriptor; +class GatoPeripheralPrivate; + +class LIBGATO_EXPORT GatoPeripheral : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(GatoPeripheral) + Q_ENUMS(State) + 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 + }; + + State state() const; + GatoAddress address() const; + QString name() const; + QList<GatoService> services() const; + + void parseEIR(quint8 data[], int len); + +public 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); + void writeValue(const GatoDescriptor &descriptor, const QByteArray &data); + void setNotification(const GatoCharacteristic &characteristic, bool enabled); + +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; +}; + +#endif // GATOPERIPHERAL_H diff --git a/gatoperipheral_p.h b/gatoperipheral_p.h new file mode 100644 index 0000000..895270e --- /dev/null +++ b/gatoperipheral_p.h @@ -0,0 +1,71 @@ +#ifndef GATOPERIPHERAL_P_H +#define GATOPERIPHERAL_P_H + +#include "gatoperipheral.h" +#include "gatoservice.h" +#include "gatocharacteristic.h" +#include "gatodescriptor.h" +#include "gatoatt.h" + +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; + + GatoAtt *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<GatoAtt::AttributeGroupData>& list); + void handlePrimaryForService(uint req, const QList<GatoAtt::HandleInformation>& list); + void handleCharacteristic(uint req, const QList<GatoAtt::AttributeData> &list); + void handleDescriptors(uint req, const QList<GatoAtt::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); +}; + +#endif // GATOPERIPHERAL_P_H diff --git a/gatoservice.cpp b/gatoservice.cpp new file mode 100644 index 0000000..ad9ff1b --- /dev/null +++ b/gatoservice.cpp @@ -0,0 +1,129 @@ +/* + * libgato - A GATT/ATT library for use with Bluez + * + * Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "gatoservice.h" +#include "gatocharacteristic.h" + +struct GatoServicePrivate : QSharedData +{ + GatoServicePrivate(); + GatoUUID uuid; + GatoHandle start; + GatoHandle end; + + QMap<GatoHandle, GatoCharacteristic> characteristics; +}; + +GatoService::GatoService() + : d(new GatoServicePrivate) +{ +} + +GatoService::GatoService(const GatoService &o) + : d(o.d) +{ +} + +GatoService::~GatoService() +{ +} + +GatoUUID GatoService::uuid() const +{ + return d->uuid; +} + +void GatoService::setUuid(const GatoUUID &uuid) +{ + d->uuid = uuid; +} + +GatoHandle GatoService::startHandle() const +{ + return d->start; +} + +void GatoService::setStartHandle(GatoHandle handle) +{ + d->start = handle; +} + +GatoHandle GatoService::endHandle() const +{ + return d->end; +} + +void GatoService::setEndHandle(GatoHandle handle) +{ + d->end = handle; +} + +QList<GatoCharacteristic> GatoService::characteristics() const +{ + return d->characteristics.values(); +} + +bool GatoService::containsCharacteristic(const GatoCharacteristic &characteristic) const +{ + GatoHandle char_handle = characteristic.startHandle(); + if (d->characteristics.contains(char_handle)) { + return d->characteristics[char_handle].uuid() == characteristic.uuid(); + } else { + return false; + } +} + +bool GatoService::containsCharacteristic(GatoHandle handle) const +{ + return d->characteristics.contains(handle); +} + +GatoCharacteristic GatoService::getCharacteristic(GatoHandle handle) const +{ + return d->characteristics.value(handle); +} + +void GatoService::addCharacteristic(const GatoCharacteristic &characteristic) +{ + d->characteristics.insert(characteristic.startHandle(), characteristic); +} + +void GatoService::removeCharacteristic(const GatoCharacteristic &characteristic) +{ + d->characteristics.remove(characteristic.startHandle()); +} + +void GatoService::clearCharacteristics() +{ + d->characteristics.clear(); +} + +GatoService & GatoService::operator =(const GatoService &o) +{ + if (this != &o) { + d = o.d; + } + return *this; +} + +GatoServicePrivate::GatoServicePrivate() + : uuid(), start(0), end(0) +{ +} diff --git a/gatoservice.h b/gatoservice.h new file mode 100644 index 0000000..7af976a --- /dev/null +++ b/gatoservice.h @@ -0,0 +1,42 @@ +#ifndef GATOSERVICE_H +#define GATOSERVICE_H + +#include <QtCore/QSharedDataPointer> +#include "gatouuid.h" + +class GatoServicePrivate; +class GatoCharacteristic; + +class LIBGATO_EXPORT GatoService +{ + Q_GADGET + +public: + GatoService(); + GatoService(const GatoService &o); + ~GatoService(); + + GatoUUID uuid() const; + void setUuid(const GatoUUID &uuid); + + GatoHandle startHandle() const; + void setStartHandle(GatoHandle handle); + + GatoHandle endHandle() const; + void setEndHandle(GatoHandle handle); + + QList<GatoCharacteristic> characteristics() const; + bool containsCharacteristic(const GatoCharacteristic& characteristic) const; + bool containsCharacteristic(GatoHandle handle) const; + GatoCharacteristic getCharacteristic(GatoHandle handle) const; + void addCharacteristic(const GatoCharacteristic& characteristic); + void removeCharacteristic(const GatoCharacteristic& characteristic); + void clearCharacteristics(); + + GatoService & operator=(const GatoService &o); + +private: + QSharedDataPointer<GatoServicePrivate> d; +}; + +#endif // GATOSERVICE_H diff --git a/gatosocket.cpp b/gatosocket.cpp new file mode 100644 index 0000000..e46a811 --- /dev/null +++ b/gatosocket.cpp @@ -0,0 +1,193 @@ +/* + * libgato - A GATT/ATT library for use with Bluez + * + * Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <QtCore/QDebug> + +#include <limits> + +#include <unistd.h> +#include <errno.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/l2cap.h> +#include "gatosocket.h" + +GatoSocket::GatoSocket(QObject *parent) + : QObject(parent), s(StateDisconnected), fd(-1) +{ +} + +GatoSocket::~GatoSocket() +{ + if (s != StateDisconnected) { + close(); + } +} + +GatoSocket::State GatoSocket::state() const +{ + return s; +} + +bool GatoSocket::connectTo(const GatoAddress &addr, unsigned short cid) +{ + if (s != StateDisconnected) { + qWarning() << "Already connecting or connected"; + return false; + } + + fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (fd == -1) { + qErrnoWarning("Could not create L2CAP socket"); + return false; + } + + s = StateConnecting; + + readNotifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); + writeNotifier = new QSocketNotifier(fd, QSocketNotifier::Write, this); + connect(readNotifier, SIGNAL(activated(int)), SLOT(readNotify())); + connect(writeNotifier, SIGNAL(activated(int)), SLOT(writeNotify())); + + struct sockaddr_l2 l2addr; + memset(&l2addr, 0, sizeof(l2addr)); + + l2addr.l2_family = AF_BLUETOOTH; + l2addr.l2_cid = htobs(cid); +#ifdef BDADDR_LE_PUBLIC + l2addr.l2_bdaddr_type = BDADDR_LE_PUBLIC; // TODO +#endif + addr.toUInt8Array(l2addr.l2_bdaddr.b); + + int err = ::connect(fd, reinterpret_cast<sockaddr*>(&l2addr), sizeof(l2addr)); + if (err == -1 && errno != EINPROGRESS) { + qErrnoWarning("Could not connect to L2CAP socket"); + close(); + return false; + } + + return true; +} + +void GatoSocket::close() +{ + if (s != StateDisconnected) { + delete readNotifier; + delete writeNotifier; + readQueue.clear(); + writeQueue.clear(); + fd = -1; + s = StateDisconnected; + emit disconnected(); + } +} + +QByteArray GatoSocket::receive() +{ + if (readQueue.isEmpty()) { + return QByteArray(); + } else { + return readQueue.dequeue(); + } +} + +void GatoSocket::send(const QByteArray &pkt) +{ + if (s == StateConnected && writeQueue.isEmpty()) { + if (transmit(pkt)) { + // Packet transmited succesfully without any queuing + return; + } + } + + writeQueue.enqueue(pkt); + writeNotifier->setEnabled(true); +} + +bool GatoSocket::transmit(const QByteArray &pkt) +{ + int written = ::write(fd, pkt.constData(), pkt.size()); + if (written < 0) { + qErrnoWarning("Could not write to L2 socket"); + close(); + return false; + } else if (written < pkt.size()) { + qWarning("Could not write full packet to L2 socket"); + return true; + } else { + return true; + } +} + +void GatoSocket::readNotify() +{ + QByteArray buf; + buf.resize(1024); // Max packet size + + int read = ::read(fd, buf.data(), buf.size()); + if (read < 0) { + qErrnoWarning("Could not read to L2 socket"); + close(); + return; + } else if (read == 0) { + return; + } + + buf.resize(read); + + readQueue.enqueue(buf); + + if (readQueue.size() == 1) { + emit readyRead(); + } +} + +void GatoSocket::writeNotify() +{ + if (s == StateConnecting) { + int soerror = 0; + socklen_t len = sizeof(soerror); + if (::getsockopt(fd, SOL_SOCKET, SO_ERROR, &soerror, &len) != 0) { + // An error while reading the error? + qErrnoWarning("Could not get L2 socket options"); + close(); + return; + } + if (soerror != 0) { + qWarning() << "Could not connect to L2 socket: " << strerror(soerror); + close(); + return; + } + + s = StateConnected; + emit connected(); + } else if (s == StateConnected) { + if (!writeQueue.isEmpty()) { + if (transmit(writeQueue.head())) { + writeQueue.dequeue(); + } + } + + if (writeQueue.isEmpty()) { + writeNotifier->setEnabled(false); + } + } +} diff --git a/gatosocket.h b/gatosocket.h new file mode 100644 index 0000000..6bb6998 --- /dev/null +++ b/gatosocket.h @@ -0,0 +1,54 @@ +#ifndef GATOSOCKET_H +#define GATOSOCKET_H + +#include <QtCore/QObject> +#include <QtCore/QQueue> +#include <QtCore/QSocketNotifier> + +#include "gatoaddress.h" + +class GatoSocket : public QObject +{ + Q_OBJECT + Q_ENUMS(State) + +public: + explicit GatoSocket(QObject *parent); + ~GatoSocket(); + + enum State { + StateDisconnected, + StateConnecting, + StateConnected + }; + + State state() const; + + bool connectTo(const GatoAddress &addr, unsigned short cid); + void close(); + + QByteArray receive(); + void send(const QByteArray &pkt); + +signals: + void connected(); + void disconnected(); + void readyRead(); + +private: + bool transmit(const QByteArray &pkt); + +private slots: + void readNotify(); + void writeNotify(); + +private: + State s; + int fd; + QSocketNotifier *readNotifier; + QQueue<QByteArray> readQueue; + QSocketNotifier *writeNotifier; + QQueue<QByteArray> writeQueue; +}; + +#endif // GATOSOCKET_H diff --git a/gatouuid.cpp b/gatouuid.cpp new file mode 100644 index 0000000..a1a3866 --- /dev/null +++ b/gatouuid.cpp @@ -0,0 +1,184 @@ +/* + * libgato - A GATT/ATT library for use with Bluez + * + * Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <QtCore/QDebug> +#include <QtCore/QHash> +#include <bluetooth/uuid.h> + +#include "gatouuid.h" + +union convert128 { + uint128_t u; + gatouint128 g; +}; + +struct GatoUUIDPrivate : QSharedData +{ + bt_uuid_t uuid; +}; + +GatoUUID::GatoUUID() + : d(new GatoUUIDPrivate) +{ + memset(&d->uuid, 0, sizeof(d->uuid)); +} + +GatoUUID::GatoUUID(GattUuid uuid) + : d(new GatoUUIDPrivate) +{ + bt_uuid16_create(&d->uuid, uuid); +} + +GatoUUID::GatoUUID(quint16 uuid) + : d(new GatoUUIDPrivate) +{ + bt_uuid16_create(&d->uuid, uuid); +} + +GatoUUID::GatoUUID(quint32 uuid) + : d(new GatoUUIDPrivate) +{ + bt_uuid32_create(&d->uuid, uuid); +} + +GatoUUID::GatoUUID(gatouint128 uuid) + : d(new GatoUUIDPrivate) +{ + convert128 c128; + c128.g = uuid; + bt_uuid128_create(&d->uuid, c128.u); +} + +GatoUUID::GatoUUID(const QString &uuid) + : d(new GatoUUIDPrivate) +{ + bt_string_to_uuid(&d->uuid, uuid.toLatin1().constData()); +} + +GatoUUID::GatoUUID(const GatoUUID &o) + : d(o.d) +{ +} + +GatoUUID::~GatoUUID() +{ +} + +bool GatoUUID::isNull() const +{ + return d->uuid.type == bt_uuid_t::BT_UUID_UNSPEC; +} + +quint16 GatoUUID::toUInt16(bool *ok) const +{ + if (d->uuid.type == bt_uuid_t::BT_UUID16) { + if (ok) *ok = true; + return d->uuid.value.u16; + } else { + if (ok) *ok = false; + return 0; + } +} + +quint32 GatoUUID::toUInt32(bool *ok) const +{ + if (d->uuid.type == bt_uuid_t::BT_UUID32) { + if (ok) *ok = true; + return d->uuid.value.u32; + } else { + if (ok) *ok = false; + return 0; + } +} + +gatouint128 GatoUUID::toUInt128() const +{ + bt_uuid_t u128; + bt_uuid_to_uuid128(&d->uuid, &u128); + return *reinterpret_cast<gatouint128*>(&u128.value.u128); +} + +QString GatoUUID::toString() const +{ + char buf[MAX_LEN_UUID_STR + 1]; + if (bt_uuid_to_string(&d->uuid, buf, MAX_LEN_UUID_STR) == 0) { + return QString::fromAscii(buf); + } else { + return QString(); + } +} + +GatoUUID & GatoUUID::operator =(const GatoUUID &o) +{ + if (this != &o) { + d = o.d; + } + return *this; +} + +bool operator==(const GatoUUID &a, const GatoUUID &b) +{ + return bt_uuid_cmp(&a.d->uuid, &b.d->uuid) == 0; +} + +bool operator!=(const GatoUUID &a, const GatoUUID &b) +{ + return bt_uuid_cmp(&a.d->uuid, &b.d->uuid) != 0; +} + +QDataStream & operator<<(QDataStream &s, const gatouint128 &u) +{ + if (static_cast<QSysInfo::Endian>(s.byteOrder()) == QSysInfo::ByteOrder) { + for (int i = 0; i < 16; i++) { + s << u.data[i]; + } + } else { + for (int i = 15; i >= 0; i--) { + s << u.data[i]; + } + } + return s; +} + +QDataStream & operator>>(QDataStream &s, gatouint128 &u) +{ + if (static_cast<QSysInfo::Endian>(s.byteOrder()) == QSysInfo::ByteOrder) { + for (int i = 0; i < 16; i++) { + s >> u.data[i]; + } + } else { + for (int i = 15; i >= 0; i--) { + s >> u.data[i]; + } + } + return s; +} + +uint qHash(const GatoUUID &a) +{ + gatouint128 u128 = a.toUInt128(); + + uint h = u128.data[0] << 24 | u128.data[1] << 16 | u128.data[2] << 8 | u128.data[3] << 0; + h ^= u128.data[4] << 24 | u128.data[5] << 16 | u128.data[6] << 8 | u128.data[7] << 0; + h ^= u128.data[8] << 24 | u128.data[9] << 16 | u128.data[10] << 8 | u128.data[11] << 0; + h ^= u128.data[12] << 24 | u128.data[13] << 16 | u128.data[14] << 8 | u128.data[15] << 0; + + return h; +} diff --git a/gatouuid.h b/gatouuid.h new file mode 100644 index 0000000..e091512 --- /dev/null +++ b/gatouuid.h @@ -0,0 +1,109 @@ +#ifndef GATOUUID_H +#define GATOUUID_H + +#include <QtCore/QDebug> +#include <QtCore/QtEndian> +#include <QtCore/QSharedDataPointer> +#include <QtCore/QString> +#include "libgato_global.h" + +class GatoUUIDPrivate; + +struct gatouint128 +{ + quint8 data[16]; +}; + +template<> +inline LIBGATO_EXPORT gatouint128 qFromLittleEndian<gatouint128>(const uchar *src) +{ + gatouint128 dest; +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + for (int i = 0; i < 16; i++) { + dest.data[i] = src[i]; + } +#else + for (int i = 0; i < 16; i++) { + dest.data[i] = src[15 - i]; + } +#endif + return dest; +} + +template<> +inline LIBGATO_EXPORT void qToLittleEndian<gatouint128>(gatouint128 src, uchar *dest) +{ +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + for (int i = 0; i < 16; i++) { + dest[i] = src.data[i]; + } +#else + for (int i = 0; i < 16; i++) { + dest[i] = src.data[15 - i]; + } +#endif +} + +class LIBGATO_EXPORT GatoUUID +{ + Q_GADGET + Q_ENUMS(GattUuid) + +public: + enum GattUuid { + GattGenericAccessProfile = 0x1800, + GattGenericAttributeProfile = 0x1801, + GattPrimaryService = 0x2800, + GattSecondaryService = 0x2801, + GattInclude = 0x2802, + GattCharacteristic = 0x2803, + GattCharacteristicExtendedProperties = 0x2900, + GattCharacteristicUserDescription = 0x2901, + GattClientCharacteristicConfiguration = 0x2902, + GattServerCharacteristicConfiguration = 0x2903, + GattCharacteristicFormat = 0x2904, + GattCharacteristicAggregateFormat = 0x2905, + GattDeviceName = 0x2A00, + GattAppearance = 0x2A01, + GattPeripheralPrivacyFlag = 0x2A02, + GattReconnectionAddress = 0x2A03, + GattPeripheralPreferredConnectionParameters = 0x2A04, + GattServiceChanged = 0x2A05 + }; + + GatoUUID(); + GatoUUID(GattUuid uuid); + explicit GatoUUID(quint16 uuid); + explicit GatoUUID(quint32 uuid); + explicit GatoUUID(gatouint128 uuid); + explicit GatoUUID(const QString &uuid); + GatoUUID(const GatoUUID &o); + ~GatoUUID(); + + bool isNull() const; + + quint16 toUInt16(bool *ok = 0) const; + quint32 toUInt32(bool *ok = 0) const; + gatouint128 toUInt128() const; + QString toString() const; + + GatoUUID& operator=(const GatoUUID& o); + friend bool operator==(const GatoUUID &a, const GatoUUID &b); + friend bool operator!=(const GatoUUID &a, const GatoUUID &b); + +private: + QSharedDataPointer<GatoUUIDPrivate> d; +}; + +inline QDebug operator<<(QDebug debug, const GatoUUID &uuid) +{ + debug << uuid.toString(); + return debug; +} + +LIBGATO_EXPORT QDataStream & operator<<(QDataStream &s, const gatouint128 &u); +LIBGATO_EXPORT QDataStream & operator>>(QDataStream &s, gatouint128 &u); + +LIBGATO_EXPORT uint qHash(const GatoUUID &a); + +#endif // GATOUUID_H diff --git a/helpers.cpp b/helpers.cpp new file mode 100644 index 0000000..b62b464 --- /dev/null +++ b/helpers.cpp @@ -0,0 +1,64 @@ +/* + * libgato - A GATT/ATT library for use with Bluez + * + * Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "helpers.h" + +GatoUUID bytearray_to_gatouuid(const QByteArray &ba) +{ + switch (ba.size()) { + case 2: + return GatoUUID(read_le<quint16>(ba.constData())); + case 4: + return GatoUUID(read_le<quint32>(ba.constData())); + case 16: + return GatoUUID(read_le<gatouint128>(ba.constData())); + default: + return GatoUUID(); + } +} + +QByteArray gatouuid_to_bytearray(const GatoUUID &uuid, bool use_uuid16, bool use_uuid32) +{ + QByteArray ba; + + if (use_uuid16) { + quint16 uuid16 = uuid.toUInt16(&use_uuid16); + if (use_uuid16) { + ba.resize(sizeof(quint16)); + write_le(uuid16, ba.data()); + return ba; + } + } + if (use_uuid32) { + quint32 uuid32 = uuid.toUInt32(&use_uuid32); + if (use_uuid32) { + ba.resize(sizeof(quint32)); + write_le(uuid32, ba.data()); + return ba; + } + } + + gatouint128 uuid128 = uuid.toUInt128(); + ba.resize(sizeof(gatouint128)); + Q_ASSERT(ba.size() == 16); + write_le(uuid128, ba.data()); + + return ba; +} diff --git a/helpers.h b/helpers.h new file mode 100644 index 0000000..962026c --- /dev/null +++ b/helpers.h @@ -0,0 +1,35 @@ +#ifndef HELPERS_H +#define HELPERS_H + +#include <QtCore/QByteArray> +#include <QtCore/QtEndian> +#include "gatouuid.h" + +template<typename T> +inline T read_le(const uchar *src) +{ + return qFromLittleEndian<T>(src); +} + +template<typename T> +inline T read_le(const char *src) +{ + return qFromLittleEndian<T>(reinterpret_cast<const uchar*>(src)); +} + +template<typename T> +void write_le(T src, uchar *dst) +{ + qToLittleEndian<T>(src, dst); +} + +template<typename T> +void write_le(T src, char *dst) +{ + qToLittleEndian<T>(src, reinterpret_cast<uchar*>(dst)); +} + +GatoUUID bytearray_to_gatouuid(const QByteArray &ba); +QByteArray gatouuid_to_bytearray(const GatoUUID &uuid, bool use_uuid16, bool use_uuid32); + +#endif // HELPERS_H diff --git a/libgato.pro b/libgato.pro new file mode 100644 index 0000000..7da152e --- /dev/null +++ b/libgato.pro @@ -0,0 +1,39 @@ +TEMPLATE = lib +TARGET = gato + +QT -= gui + +DEFINES += LIBGATO_LIBRARY + +CONFIG += link_pkgconfig +PKGCONFIG += bluez + +SOURCES += \ + gatocentralmanager.cpp \ + gatouuid.cpp \ + gatoperipheral.cpp \ + gatoaddress.cpp \ + gatosocket.cpp \ + gatoatt.cpp \ + helpers.cpp \ + gatoservice.cpp \ + gatocharacteristic.cpp \ + gatodescriptor.cpp + +HEADERS += libgato_global.h \ + gatocentralmanager.h \ + gatouuid.h \ + gatoperipheral.h \ + gatoaddress.h \ + gatosocket.h \ + gatoatt.h \ + helpers.h \ + gatoperipheral_p.h \ + gatocentralmanager_p.h \ + gatoservice.h \ + gatocharacteristic.h \ + gatodescriptor.h \ + gato.h + +target.path = /usr/lib +INSTALLS += target diff --git a/libgato_global.h b/libgato_global.h new file mode 100644 index 0000000..ddc4173 --- /dev/null +++ b/libgato_global.h @@ -0,0 +1,14 @@ +#ifndef LIBGATO_GLOBAL_H +#define LIBGATO_GLOBAL_H + +#include <QtCore/qglobal.h> + +#if defined(LIBGATO_LIBRARY) +# define LIBGATO_EXPORT Q_DECL_EXPORT +#else +# define LIBGATO_EXPORT Q_DECL_IMPORT +#endif + +typedef quint16 GatoHandle; + +#endif // LIBGATO_GLOBAL_H |