// SPDX-License-Identifier: GPL-2.0 #include #include #include #include #include #include #include #include #include #include #include "libdivecomputer.h" #include "core/qt-ble.h" #if defined(SSRF_CUSTOM_IO) #include extern "C" { void waitFor(int ms) { Q_ASSERT(QCoreApplication::instance()); Q_ASSERT(QThread::currentThread()); QElapsedTimer timer; timer.start(); do { QCoreApplication::processEvents(QEventLoop::AllEvents, ms); QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); QThread::msleep(10); } while (timer.elapsed() < ms); } void BLEObject::serviceStateChanged(QLowEnergyService::ServiceState s) { Q_UNUSED(s) QList list; auto service = qobject_cast(sender()); if (service) list = service->characteristics(); Q_FOREACH(QLowEnergyCharacteristic c, list) { qDebug() << " " << c.uuid().toString(); } } void BLEObject::characteristcStateChanged(const QLowEnergyCharacteristic &c, const QByteArray &value) { Q_UNUSED(c) receivedPackets.append(value); waitForPacket.exit(); } void BLEObject::writeCompleted(const QLowEnergyDescriptor &d, const QByteArray &value) { Q_UNUSED(d) Q_UNUSED(value) qDebug() << "BLE write completed"; } void BLEObject::addService(const QBluetoothUuid &newService) { qDebug() << "Found service" << newService; bool isStandardUuid = false; newService.toUInt16(&isStandardUuid); if (isStandardUuid) { qDebug () << " .. ignoring standard service"; return; } auto service = controller->createServiceObject(newService, this); qDebug() << " .. created service object" << service; if (service) { services.append(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, dc_user_device_t *d) { controller = c; device = d; } BLEObject::~BLEObject() { qDebug() << "Deleting BLE object"; } /* Yeah, I could do the C++ inline member thing */ static int device_is_shearwater(dc_user_device_t *device) { return !strcmp(device->vendor, "Shearwater"); } dc_status_t BLEObject::write(const void *data, size_t size, size_t *actual) { Q_UNUSED(actual) // that seems like it might cause problems QList list = preferredService()->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; if (device_is_shearwater(device)) bytes.prepend("\1\0", 2); preferredService()->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 list = preferredService()->characteristics(); if (list.isEmpty()) return DC_STATUS_IO; 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 (device_is_shearwater(device)) packet.remove(0,2); if (size > (size_t)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) { Q_UNUSED(context) /* * LE-only devices get the "LE:" prepended by the scanning * code, so that the rfcomm code can see they only do LE. * * We just skip that prefix (and it doesn't always exist, * since the device may support both legacy BT and LE). */ if (!strncmp(devaddr, "LE:", 3)) devaddr += 3; 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); qDebug() << "qt_ble_open(" << devaddr << ")"; if (device_is_shearwater(io->user_device)) controller->setRemoteAddressType(QLowEnergyController::RandomAddress); // Try to connect to the device controller->connectToDevice(); // Create a timer. If the connection doesn't succeed after five seconds or no error occurs then stop the opening step int msec = 5000; while (msec > 0 && controller->state() == QLowEnergyController::ConnectingState) { waitFor(100); msec -= 100; }; switch (controller->state()) { case QLowEnergyController::ConnectedState: qDebug() << "connected to the controller for device" << devaddr; break; default: qDebug() << "failed to connect to the controller " << devaddr << "with error" << controller->errorString(); report_error("Failed to connect to %s: '%s'", devaddr, controller->errorString().toUtf8().data()); controller->disconnectFromDevice(); delete controller; return DC_STATUS_IO; } /* We need to discover services etc here! */ BLEObject *ble = new BLEObject(controller, io->user_device); ble->connect(controller, SIGNAL(serviceDiscovered(QBluetoothUuid)), SLOT(addService(QBluetoothUuid))); qDebug() << " .. discovering services"; controller->discoverServices(); msec = 5000; while (msec > 0 && controller->state() == QLowEnergyController::DiscoveringState) { waitFor(100); msec -= 100; }; qDebug() << " .. done discovering services"; if (ble->preferredService() == nullptr) { qDebug() << "failed to find suitable service on" << devaddr; report_error("Failed to find suitable service on '%s'", devaddr); controller->disconnectFromDevice(); delete controller; return DC_STATUS_IO; } qDebug() << " .. discovering details"; msec = 5000; while (msec > 0 && ble->preferredService()->state() == QLowEnergyService::DiscoveringServices) { waitFor(100); msec -= 100; }; if (ble->preferredService()->state() != QLowEnergyService::ServiceDiscovered) { qDebug() << "failed to find suitable service on" << devaddr; report_error("Failed to find suitable service on '%s'", devaddr); controller->disconnectFromDevice(); delete controller; return DC_STATUS_IO; } qDebug() << " .. enabling notifications"; /* Enable notifications */ QList list = ble->preferredService()->characteristics(); if (!list.isEmpty()) { const QLowEnergyCharacteristic &c = list.constLast(); QList l = c.descriptors(); qDebug() << "Descriptor list with" << l.length() << "elements"; QLowEnergyDescriptor d; foreach(d, l) qDebug() << "Descriptor:" << d.name() << "uuid:" << d.uuid().toString(); if (!l.isEmpty()) { d = l.first(); qDebug() << "now writing \"0x0100\" to the first descriptor"; ble->preferredService()->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