mirror of
https://github.com/subsurface/subsurface.git
synced 2024-11-28 05:00:20 +00:00
Very early and likely quite broken BLE GATT code
This is some very early and hacky code to be able to access BLE-enabled dive computers that use the GATT protocol to send packets back and forth (which seems to be pretty much all of them: a vendor-specific GATT service with a write characteristic and a notification characteristic for reading). For testing only. But it does successfully let me download dives from my EON Steel and my Scubapro G2. NOTE! There are several very hacky pieces in here, including just "knowing" that the write characteristic is the first one, and the notification characteristic is second. The code should actually check the properties rather than have those kinds of hardcoded assumptions. It also checks "vendor specific" by looking at the UUID string representation, and knowing that the standard ones start with zero. Crazily, there doesn't seem to be any normal way to test for this, although I guess that maybe the uuid.minimumSize() function could be used. There are other nasty corners. Don't complain, send me patches. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
This commit is contained in:
parent
03badea06f
commit
196adb591b
6 changed files with 294 additions and 1 deletions
|
@ -216,10 +216,19 @@ if (BTSUPPORT AND "${Qt5Core_VERSION}" VERSION_LESS 5.4.0)
|
|||
list(REMOVE_ITEM QT_LIBRARIES Qt5::Bluetooth)
|
||||
endif()
|
||||
|
||||
#I can't test MacOS, and Windows Qt doesn't support BLE at all afaik
|
||||
if (BTSUPPORT AND CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set(BLESUPPORT ON)
|
||||
endif()
|
||||
|
||||
if(BTSUPPORT)
|
||||
add_definitions(-DBT_SUPPORT)
|
||||
endif()
|
||||
|
||||
if(BLESUPPORT)
|
||||
add_definitions(-DBLE_SUPPORT)
|
||||
endif()
|
||||
|
||||
#set up the subsurface_link_libraries variable
|
||||
set(SUBSURFACE_LINK_LIBRARIES ${SUBSURFACE_LINK_LIBRARIES} ${LIBDIVECOMPUTER_LIBRARIES} ${LIBGIT2_LIBRARIES} ${LIBUSB_LIBRARIES})
|
||||
qt5_add_resources(SUBSURFACE_RESOURCES subsurface.qrc)
|
||||
|
|
|
@ -24,6 +24,11 @@ if(BTSUPPORT)
|
|||
set(BT_CORE_SRC_FILES qtserialbluetooth.cpp)
|
||||
endif()
|
||||
|
||||
if(BLESUPPORT)
|
||||
add_definitions(-DBLE_SUPPORT)
|
||||
set(BT_CORE_SRC_FILES qt-ble.cpp)
|
||||
endif()
|
||||
|
||||
# compile the core library, in C.
|
||||
set(SUBSURFACE_CORE_LIB_SRCS
|
||||
btdiscovery.cpp
|
||||
|
|
228
core/qt-ble.cpp
Normal file
228
core/qt-ble.cpp
Normal file
|
@ -0,0 +1,228 @@
|
|||
#include <errno.h>
|
||||
|
||||
#include <QtBluetooth/QBluetoothAddress>
|
||||
#include <QLowEnergyController>
|
||||
#include <QEventLoop>
|
||||
#include <QTimer>
|
||||
#include <QDebug>
|
||||
|
||||
#include <libdivecomputer/version.h>
|
||||
|
||||
#include "libdivecomputer.h"
|
||||
#include "core/qt-ble.h"
|
||||
|
||||
#if defined(SSRF_CUSTOM_IO)
|
||||
|
||||
#include <libdivecomputer/custom_io.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
void BLEObject::serviceStateChanged(QLowEnergyService::ServiceState s)
|
||||
{
|
||||
QList<QLowEnergyCharacteristic> list;
|
||||
|
||||
list = service->characteristics();
|
||||
|
||||
Q_FOREACH(QLowEnergyCharacteristic c, list) {
|
||||
fprintf(stderr, " %s\n", c.uuid().toString().toUtf8().data());
|
||||
}
|
||||
}
|
||||
|
||||
void BLEObject::characteristcStateChanged(const QLowEnergyCharacteristic &c, const QByteArray &value)
|
||||
{
|
||||
receivedPackets.append(value);
|
||||
waitForPacket.exit();
|
||||
}
|
||||
|
||||
void BLEObject::writeCompleted(const QLowEnergyDescriptor &d, const QByteArray &value)
|
||||
{
|
||||
fprintf(stderr, "Write completed\n");
|
||||
}
|
||||
|
||||
void BLEObject::addService(const QBluetoothUuid &newService)
|
||||
{
|
||||
const char *uuid = newService.toString().toUtf8().data();
|
||||
|
||||
fprintf(stderr, "Found service %s\n", uuid);
|
||||
if (uuid[1] == '0') {
|
||||
fprintf(stderr, " .. ignoring\n");
|
||||
return;
|
||||
}
|
||||
service = controller->createServiceObject(newService, this);
|
||||
fprintf(stderr, " .. created service object %p\n", service);
|
||||
if (service) {
|
||||
connect(service, &QLowEnergyService::stateChanged, this, &BLEObject::serviceStateChanged);
|
||||
connect(service, &QLowEnergyService::characteristicChanged, this, &BLEObject::characteristcStateChanged);
|
||||
connect(service, &QLowEnergyService::descriptorWritten, this, &BLEObject::writeCompleted);
|
||||
service->discoverDetails();
|
||||
}
|
||||
}
|
||||
|
||||
BLEObject::BLEObject(QLowEnergyController *c)
|
||||
{
|
||||
controller = c;
|
||||
}
|
||||
|
||||
BLEObject::~BLEObject()
|
||||
{
|
||||
fprintf(stderr, "Deleting BLE object\n");
|
||||
}
|
||||
|
||||
dc_status_t BLEObject::write(const void* data, size_t size, size_t *actual)
|
||||
{
|
||||
QList<QLowEnergyCharacteristic> list = service->characteristics();
|
||||
QByteArray bytes((const char *)data, (int) size);
|
||||
|
||||
if (!list.isEmpty()) {
|
||||
const QLowEnergyCharacteristic &c = list.constFirst();
|
||||
QLowEnergyService::WriteMode mode;
|
||||
|
||||
mode = (c.properties() & QLowEnergyCharacteristic::WriteNoResponse) ?
|
||||
QLowEnergyService::WriteWithoutResponse :
|
||||
QLowEnergyService::WriteWithResponse;
|
||||
|
||||
service->writeCharacteristic(c, bytes, mode);
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
return DC_STATUS_IO;
|
||||
}
|
||||
|
||||
dc_status_t BLEObject::read(void* data, size_t size, size_t *actual)
|
||||
{
|
||||
if (receivedPackets.isEmpty()) {
|
||||
QList<QLowEnergyCharacteristic> list = service->characteristics();
|
||||
if (list.isEmpty())
|
||||
return DC_STATUS_IO;
|
||||
|
||||
const QLowEnergyCharacteristic &c = list.constLast();
|
||||
|
||||
QTimer timer;
|
||||
int msec = 5000;
|
||||
timer.setSingleShot(true);
|
||||
|
||||
waitForPacket.connect(&timer, SIGNAL(timeout()), SLOT(quit()));
|
||||
timer.start(msec);
|
||||
waitForPacket.exec();
|
||||
}
|
||||
|
||||
// Still no packet?
|
||||
if (receivedPackets.isEmpty())
|
||||
return DC_STATUS_IO;
|
||||
|
||||
QByteArray packet = receivedPackets.takeFirst();
|
||||
if (size > packet.size())
|
||||
size = packet.size();
|
||||
memcpy(data, packet.data(), size);
|
||||
*actual = size;
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
dc_status_t qt_ble_open(dc_custom_io_t *io, dc_context_t *context, const char *devaddr)
|
||||
{
|
||||
QBluetoothAddress remoteDeviceAddress(devaddr);
|
||||
|
||||
// HACK ALERT! Qt 5.9 needs this for proper Bluez operation
|
||||
qputenv("QT_DEFAULT_CENTRAL_SERVICES", "1");
|
||||
|
||||
QLowEnergyController *controller = new QLowEnergyController(remoteDeviceAddress);
|
||||
|
||||
fprintf(stderr, "qt_ble_open(%s)\n", devaddr);
|
||||
|
||||
// Wait until the connection succeeds or until an error occurs
|
||||
QEventLoop loop;
|
||||
loop.connect(controller, SIGNAL(connected()), SLOT(quit()));
|
||||
loop.connect(controller, SIGNAL(error(QLowEnergyController::Error)), SLOT(quit()));
|
||||
|
||||
// Create a timer. If the connection doesn't succeed after five seconds or no error occurs then stop the opening step
|
||||
QTimer timer;
|
||||
int msec = 5000;
|
||||
timer.setSingleShot(true);
|
||||
loop.connect(&timer, SIGNAL(timeout()), SLOT(quit()));
|
||||
|
||||
// Try to connect to the device
|
||||
controller->connectToDevice();
|
||||
timer.start(msec);
|
||||
loop.exec();
|
||||
|
||||
switch (controller->state()) {
|
||||
case QLowEnergyController::ConnectedState:
|
||||
break;
|
||||
default:
|
||||
report_error("Failed to connect to %s: '%s'", devaddr, controller->errorString().toUtf8().data());
|
||||
controller->disconnectFromDevice();
|
||||
delete controller;
|
||||
return DC_STATUS_IO;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Connected to device %s\n", devaddr);
|
||||
|
||||
/* We need to discover services etc here! */
|
||||
BLEObject *ble = new BLEObject(controller);
|
||||
loop.connect(controller, SIGNAL(discoveryFinished()), SLOT(quit()));
|
||||
ble->connect(controller, SIGNAL(serviceDiscovered(QBluetoothUuid)), SLOT(addService(QBluetoothUuid)));
|
||||
|
||||
fprintf(stderr, " .. discovering services\n");
|
||||
|
||||
controller->discoverServices();
|
||||
timer.start(msec);
|
||||
loop.exec();
|
||||
|
||||
fprintf(stderr, " .. done discovering services\n");
|
||||
|
||||
fprintf(stderr, " .. discovering details\n");
|
||||
|
||||
timer.start(msec);
|
||||
loop.exec();
|
||||
|
||||
fprintf(stderr, " .. done waiting\n");
|
||||
|
||||
fprintf(stderr, " .. enabling notifications\n");
|
||||
|
||||
/* Enable notifications */
|
||||
QList<QLowEnergyCharacteristic> list = ble->service->characteristics();
|
||||
|
||||
if (!list.isEmpty()) {
|
||||
const QLowEnergyCharacteristic &c = list.constLast();
|
||||
QList<QLowEnergyDescriptor> l = c.descriptors();
|
||||
|
||||
fprintf(stderr, "Descriptor list (%p, %d)\n", l, l.length());
|
||||
|
||||
if (!l.isEmpty()) {
|
||||
QLowEnergyDescriptor d = l.first();
|
||||
|
||||
fprintf(stderr, "Descriptor: %s uuid: %s\n", d.name().toUtf8().data(), d.uuid().toString().toUtf8().data());
|
||||
|
||||
ble->service->writeDescriptor(d, QByteArray::fromHex("0100"));
|
||||
}
|
||||
}
|
||||
|
||||
// Fill in info
|
||||
io->userdata = (void *)ble;
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
dc_status_t qt_ble_close(dc_custom_io_t *io)
|
||||
{
|
||||
BLEObject *ble = (BLEObject *) io->userdata;
|
||||
|
||||
io->userdata = NULL;
|
||||
delete ble;
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
dc_status_t qt_ble_read(dc_custom_io_t *io, void* data, size_t size, size_t *actual)
|
||||
{
|
||||
BLEObject *ble = (BLEObject *) io->userdata;
|
||||
return ble->read(data, size, actual);
|
||||
}
|
||||
|
||||
dc_status_t qt_ble_write(dc_custom_io_t *io, const void* data, size_t size, size_t *actual)
|
||||
{
|
||||
BLEObject *ble = (BLEObject *) io->userdata;
|
||||
return ble->write(data, size, actual);
|
||||
}
|
||||
|
||||
} /* extern "C" */
|
||||
#endif
|
38
core/qt-ble.h
Normal file
38
core/qt-ble.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#ifndef QT_BLE_H
|
||||
#define QT_BLE_H
|
||||
|
||||
#include <QLowEnergyController>
|
||||
#include <QEventLoop>
|
||||
|
||||
class BLEObject : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
BLEObject(QLowEnergyController *c);
|
||||
~BLEObject();
|
||||
dc_status_t write(const void* data, size_t size, size_t *actual);
|
||||
dc_status_t read(void* data, size_t size, size_t *actual);
|
||||
QLowEnergyService *service;
|
||||
|
||||
public slots:
|
||||
void addService(const QBluetoothUuid &newService);
|
||||
void serviceStateChanged(QLowEnergyService::ServiceState s);
|
||||
void characteristcStateChanged(const QLowEnergyCharacteristic &c, const QByteArray &value);
|
||||
void writeCompleted(const QLowEnergyDescriptor &d, const QByteArray &value);
|
||||
|
||||
private:
|
||||
QLowEnergyController *controller;
|
||||
QList<QByteArray> receivedPackets;
|
||||
QEventLoop waitForPacket;
|
||||
};
|
||||
|
||||
|
||||
extern "C" {
|
||||
dc_status_t qt_ble_open(dc_custom_io_t *io, dc_context_t *context, const char *name);
|
||||
dc_status_t qt_ble_read(dc_custom_io_t *io, void* data, size_t size, size_t *actual);
|
||||
dc_status_t qt_ble_write(dc_custom_io_t *io, const void* data, size_t size, size_t *actual);
|
||||
dc_status_t qt_ble_close(dc_custom_io_t *io);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -19,6 +19,10 @@
|
|||
|
||||
#include <libdivecomputer/custom_io.h>
|
||||
|
||||
#ifdef BLE_SUPPORT
|
||||
# include "qt-ble.h"
|
||||
#endif
|
||||
|
||||
QList<QBluetoothUuid> registeredUuids;
|
||||
|
||||
static QBluetoothUuid getBtUuid()
|
||||
|
@ -414,7 +418,15 @@ dc_custom_io_t qt_serial_ops = {
|
|||
.serial_set_dtr = NULL,
|
||||
.serial_set_rts = NULL,
|
||||
.serial_set_halfduplex = NULL,
|
||||
.serial_set_break = NULL
|
||||
.serial_set_break = NULL,
|
||||
|
||||
#ifdef BLE_SUPPORT
|
||||
.packet_size = 20,
|
||||
.packet_open = qt_ble_open,
|
||||
.packet_close = qt_ble_close,
|
||||
.packet_read = qt_ble_read,
|
||||
.packet_write = qt_ble_write,
|
||||
#endif
|
||||
};
|
||||
|
||||
dc_custom_io_t* get_qt_serial_ops() {
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
# create a log file of the build
|
||||
exec 1> >(tee build.log) 2>&1
|
||||
|
||||
export CMAKE_PREFIX_PATH=/home/torvalds/src/qt5/qtbase/lib/cmake
|
||||
SRC=$(pwd)
|
||||
PLATFORM=$(uname)
|
||||
|
||||
|
|
Loading…
Reference in a new issue