So attached are three patches that currently allow me to download
dives from my Shearwater Perdix AI, and that I assume works with the
regular Perdix too (which has some newer versions that are BLE-only
and no longer support rfcomm).

HOWEVER. BIG CAVEAT!

Right now that third patch is a disgusting hack that can not be
committed, because it will break *other* LE dive computers (ie Suunto
EON Steel and Scubapro G2). The issue is that the Shearwaters use a
slightly different model for connecting and their BLE packet sending,
and rather than do the sane thing and discover that dynamically, that
third patch just hardcodes the Shearwater way.

The two first patches are good to go, though, and you should consider
the third patch a "hey, I want to test my Shearwater download" special
patch that you do not actually commit, you just apply to your build
tree for testing.

CAVEAT NUMBER TWO!

The Perdix AI has some new fields for actual air integration, and
those new fields will presumably need libdivecomputer support, and I
haven't got any test data for it. So while I have a Perdix AI download
working fine, I'm not saying it's all perfect.

Anyway, with those caveats out of the way, some explanations:

 - the same bluetooth device may support both legacy bluetooth
(rfcomm) _and_ the BLE GATT model. That's true of the older Shearwater
Perdix, for example, and our current custom_io model actually
describes both modes at the same time, so that the libdivecomputer
backend can just pick whichever it wants.

 - in particular, I don't want to change the behavior of the old
rfcomm code - I don't have anything that uses it, and it supposedly
works fine for others, and it's a more standardized protocol than the
BLE GATT hackery of more modern dive computers, so it should remain
the default mode for those dive computers that support it.

 - HOWEVER, when we scan for the dive computers, we can see that some
dive computer _only_ supports BLE. When that happens, the first patch
in this series will now explicitly mark the bluetooth address with a
"LE:" prefix at scan time, which will allow the rfcomm code to realize
that it cannot use rfcomm for the device.

 - the first patch then also makes the actual BLE packet code ignrioe
the possible "LE:" prefix (but not having that prefix is ok too for
the packet code, for the dual BT support case).  The rfcomm case will
obviously fail with the "LE:" prefix, but it would have failed anyway
since rfcomm doesn't work over LE.

 - the second patch then hooks into the rfcomm code, and instead of
just failing, it says "dammit, if I see that LE prefix, I know that I
can't use rfcomm, but I'll instead do my own serial emulation over the
packets I can send".

 - this is what makes the Perdix AI "just work" without any extra
effort (well, as long as we have the hack in place to do the packet
accesses properly for the shearwater case) - the libdivecomputer
backend will just use the same old serial code, never even realizing
that now it goes over BLE.

 - this *also* allows people who currently happily use rfcomm to play
with the LE back-end. So if you have an older Perdix, and it currently
works with rfcomm, what you can do is to just hand-edit the bluetooth
address to have that "LE:" prefix, and that will force the new BLE
GATT path. It might be faster and lower energy. And it might not. It
might not work for you at all. It's a crapshoot.

 - that third patch really is crap. Don't look at it. Don't commit it.
I'm sorry. But you currently need it to make the BLE GATT code work
with shearwater dive computers.

Anything I forgot?

People who have non-Shearwater dive computers that use rfcomm but
might use BLE, you might *try* to apply the two first patches, and it
might just work for you (and use the "LE:" trick to force LE mode).
Maybe. Don't apply the third one, I suspect Shearwater is alone in how
they do that odd "one single characteristic, two descriptors" thing
(Suunto and Scubapro use two separate characteristics - one for
reading ("notify") and one for writing).

But tit's really worth noting once again the fact that BLE GATT really
isn't standardized wrt exactly how to send packets and what they mean,
meaning that there is really no reason to expect that things will
"JustWork". I suspect each BLE dive computer will need some packet
sniffing and some hackery. So far every single one I've worked on has
been very different (in some ways: the ScubaPro G2 and Suunto EON
Steel are similar in how the packets are being sent and received, but
have different encodings). The Perdix AI has a very plain encoding,
but instead uses a different approach to send and receive the packets.

                Linus
From 2ea9b52643462e7b5684bdee716899174fb2618e Mon Sep 17 00:00:00 2001
From: Linus Torvalds <[email protected]>
Date: Mon, 26 Jun 2017 18:17:06 -0700
Subject: [PATCH 1/3] Bluetooth: make LE-only devices add "LE:" as an address
 prefix

This seems a bit odd, but it actually has three different reasons for it:

 - It's a visual indication of BT LE mode for users

 - the rfcomm code only works with legacy BT support, and if we scan a
   device that only does LE, we want the custom serial code to instead
   automatically fall back on a "emulate serial over LE packets" model.

 - we want rfcomm to remain the default for devices that do both legacy
   BT _and_ LE, but we want people to have the ability to override the
   choice manually.  They can now do so by just editing the address
   field and adding the "LE:" prefix manually, and it automatically gets
   saved for next time.

So while a bit hacky, it's actually a very convenient model that not
only works automatically, but allows the manual override.

Signed-off-by: Linus Torvalds <[email protected]>
---
 core/qt-ble.cpp                             | 10 ++++++++++
 desktop-widgets/btdeviceselectiondialog.cpp |  9 ++++++++-
 2 files changed, 18 insertions(+), 1 deletion(-)

diff --git a/core/qt-ble.cpp b/core/qt-ble.cpp
index 7a73c02f..06adce63 100644
--- a/core/qt-ble.cpp
+++ b/core/qt-ble.cpp
@@ -121,6 +121,16 @@ dc_status_t BLEObject::read(void* data, size_t size, size_t *actual)
 
 dc_status_t qt_ble_open(dc_custom_io_t *io, dc_context_t *context, const char *devaddr)
 {
+	/*
+	 * 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
diff --git a/desktop-widgets/btdeviceselectiondialog.cpp b/desktop-widgets/btdeviceselectiondialog.cpp
index 9a5e9dda..2fa6b767 100644
--- a/desktop-widgets/btdeviceselectiondialog.cpp
+++ b/desktop-widgets/btdeviceselectiondialog.cpp
@@ -420,7 +420,14 @@ void BtDeviceSelectionDialog::deviceDiscoveryError(QBluetoothDeviceDiscoveryAgen
 QString BtDeviceSelectionDialog::getSelectedDeviceAddress()
 {
 	if (selectedRemoteDeviceInfo) {
-		return selectedRemoteDeviceInfo.data()->address().toString();
+		QBluetoothDeviceInfo *deviceInfo = selectedRemoteDeviceInfo.data();
+		QBluetoothDeviceInfo::CoreConfigurations flags;
+		QString prefix = "";
+
+		flags = deviceInfo->coreConfigurations();
+		if (flags == QBluetoothDeviceInfo::LowEnergyCoreConfiguration)
+			prefix = "LE:";
+		return prefix + deviceInfo->address().toString();
 	}
 
 	return QString();
-- 
2.13.1.518.g0d864c4df

From ed39fed962a71efe3cf9c23be6d5a98eda5f1243 Mon Sep 17 00:00:00 2001
From: Linus Torvalds <[email protected]>
Date: Mon, 26 Jun 2017 20:03:09 -0700
Subject: [PATCH 2/3] BT serial: recognize LE-only devices, and fall back to
 emulated serial

This is somewhat hacky, but it allows at least the Shearwater
libdivecomputer backend to continue to treat even the BLE GATT model as
just a serial protocol.

What it does is create a special "emulate serial behavior over the
packetized BLE protocol" helper layer, that qtserialbluetooth falls back
on when rfcomm is not available.

NOTE! This still requires some BLE packet code changes to work with the
odd way that Shearwater sets up their BLE GATT communication.  So note
that no further patches are necessary to *libdivecomputer*, but some
updates are needed for the subsurface qt-ble.cpp code.

I have those updates in my tree, and this code is all tested on my
Perdix AI, but those patches are currently too ugly to commit as-is.
I've cleaned up this "fake serial" code sufficiently, that cleanup comes
next.

Signed-off-by: Linus Torvalds <[email protected]>
---
 core/qtserialbluetooth.cpp | 180 ++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 176 insertions(+), 4 deletions(-)

diff --git a/core/qtserialbluetooth.cpp b/core/qtserialbluetooth.cpp
index ab05a1c7..e6b004f0 100644
--- a/core/qtserialbluetooth.cpp
+++ b/core/qtserialbluetooth.cpp
@@ -37,6 +37,7 @@ void addBtUuid(QBluetoothUuid uuid)
 
 extern "C" {
 typedef struct qt_serial_t {
+	dc_custom_io_t *ops;
 	/*
 	 * RFCOMM socket used for Bluetooth Serial communication.
 	 */
@@ -48,15 +49,169 @@ typedef struct qt_serial_t {
 	long timeout;
 } qt_serial_t;
 
+#ifdef BLE_SUPPORT
+
+static dc_status_t ble_serial_open(void **userdata, const char* devaddr);
+static dc_status_t ble_serial_close(void **userdata);
+static dc_status_t ble_serial_read(void **userdata, void* data, size_t size, size_t *actual);
+static dc_status_t ble_serial_write(void **userdata, const void* data, size_t size, size_t *actual);
+static dc_status_t ble_serial_purge(void **userdata, dc_direction_t queue);
+static dc_status_t ble_serial_get_available(void **userdata, size_t *available);
+
+static dc_custom_io_t ble_serial_ops = {
+        .userdata = NULL,
+
+	.serial_open = ble_serial_open,
+	.serial_close = ble_serial_close,
+	.serial_read = ble_serial_read,
+	.serial_write = ble_serial_write,
+	.serial_purge = ble_serial_purge,
+	.serial_get_available = ble_serial_get_available,
+	.serial_set_timeout = NULL,	// the regular qt_set_timeout is fine
+// These doesn't make sense over bluetooth
+// NULL means NOP
+	.serial_configure = NULL,
+	.serial_set_dtr = NULL,
+	.serial_set_rts = NULL,
+	.serial_set_halfduplex = NULL,
+	.serial_set_break = NULL,
+
+        .packet_size  = 20,
+        .packet_open  = qt_ble_open,
+        .packet_close = qt_ble_close,
+        .packet_read  = qt_ble_read,
+        .packet_write = qt_ble_write,
+};
+
+static struct qt_serial_t serial_over_ble = {
+	.ops = &ble_serial_ops,
+};
+
+
+static dc_status_t ble_serial_open(void **userdata, const char* devaddr)
+{
+	*userdata = &serial_over_ble;
+	return qt_ble_open(&ble_serial_ops, NULL, devaddr);
+}
+
+#define BUFSZ 1024
+static struct {
+	unsigned int out_bytes, in_bytes, in_pos;
+	unsigned char in[BUFSIZ];
+	unsigned char out[BUFSIZ];
+} buffer;
+
+static dc_status_t ble_serial_flush_write(void)
+{
+	int bytes = buffer.out_bytes;
+
+	if (!bytes)
+		return DC_STATUS_SUCCESS;
+	buffer.out_bytes = 0;
+	ble_serial_ops.packet_write(&ble_serial_ops, buffer.out, bytes, NULL);
+}
+
+static dc_status_t ble_serial_flush_read(void)
+{
+	buffer.in_bytes = buffer.in_pos = 0;
+	return DC_STATUS_SUCCESS;
+}
+
+static dc_status_t ble_serial_close(void **userdata)
+{
+	ble_serial_flush_write();
+	*userdata = NULL;
+	return qt_ble_close(&ble_serial_ops);
+}
+
+static dc_status_t ble_serial_read(void **userdata, void* data, size_t size, size_t *actual)
+{
+	int len;
+
+	if (buffer.in_pos >= buffer.in_bytes) {
+		dc_status_t rc;
+		size_t received;
+
+		ble_serial_flush_write();
+		rc = ble_serial_ops.packet_read(&ble_serial_ops, buffer.in, sizeof(buffer.in), &received);
+		if (rc != DC_STATUS_SUCCESS)
+			return rc;
+		if (!received)
+			return DC_STATUS_IO;
+		buffer.in_pos = 0;
+		buffer.in_bytes = received;
+	}
+
+	len = buffer.in_bytes - buffer.in_pos;
+	if (len > size)
+		len = size;
+
+	memcpy(data, buffer.in + buffer.in_pos, len);
+	buffer.in_pos += len;
+	if (actual)
+		*actual = len;
+	return DC_STATUS_SUCCESS;
+}
+
+static dc_status_t ble_serial_write(void **userdata, const void* data, size_t size, size_t *actual)
+{
+	dc_status_t rc = DC_STATUS_SUCCESS;
+	size_t transferred = 0;
+
+	ble_serial_flush_read();
+	while (size) {
+		int len = sizeof(buffer.out) - buffer.out_bytes;
+
+		if (len > size)
+			len = size;
+		memcpy(buffer.out + buffer.out_bytes, data, len);
+		buffer.out_bytes += len;
+
+		if (buffer.out_bytes == sizeof(buffer.out)) {
+			rc = ble_serial_flush_write();
+			if (rc != DC_STATUS_SUCCESS)
+				break;
+		}
+		transferred += len;
+		data = (const void *) (len + (const char *)data);
+		size -= len;
+	}
+	if (actual)
+		*actual = transferred;
+	return DC_STATUS_SUCCESS;
+}
+
+static dc_status_t ble_serial_purge(void **userdata, dc_direction_t queue)
+{
+	/* Do we care? */
+	return DC_STATUS_SUCCESS;
+}
+
+static dc_status_t ble_serial_get_available(void **userdata, size_t *available)
+{
+	*available = buffer.in_bytes - buffer.in_pos;
+	return DC_STATUS_SUCCESS;
+}
+
+#endif
+
+
 
 static dc_status_t qt_serial_open(void **userdata, const char* devaddr)
 {
+#ifdef BLE_SUPPORT
+	if (!strncmp(devaddr, "LE:", 3))
+		return ble_serial_open(userdata, devaddr);
+#endif
+
 	// Allocate memory.
 	qt_serial_t *serial_port = (qt_serial_t *) malloc (sizeof (qt_serial_t));
 	if (serial_port == NULL) {
 		return DC_STATUS_NOMEMORY;
 	}
 
+	serial_port->ops = NULL;
+
 	// Default to blocking reads.
 	serial_port->timeout = -1;
 
@@ -205,6 +360,9 @@ static dc_status_t qt_serial_close(void **userdata)
 	if (device == NULL)
 		return DC_STATUS_SUCCESS;
 
+	if (device && device->ops)
+		return device->ops->serial_close(userdata);
+
 #if defined(Q_OS_WIN)
 	// Cleanup
 	closesocket(device->socket);
@@ -230,6 +388,9 @@ static dc_status_t qt_serial_read(void **userdata, void* data, size_t size, size
 {
 	qt_serial_t *device = (qt_serial_t*) *userdata;
 
+	if (device && device->ops)
+		return device->ops->serial_read(userdata, data, size, actual);
+
 #if defined(Q_OS_WIN)
 	if (device == NULL)
 		return DC_STATUS_INVALIDARGS;
@@ -291,6 +452,9 @@ static dc_status_t qt_serial_write(void **userdata, const void* data, size_t siz
 {
 	qt_serial_t *device = (qt_serial_t*) *userdata;
 
+	if (device && device->ops)
+		return device->ops->serial_write(userdata, data, size, actual);
+
 #if defined(Q_OS_WIN)
 	if (device == NULL)
 		return DC_STATUS_INVALIDARGS;
@@ -336,9 +500,13 @@ static dc_status_t qt_serial_write(void **userdata, const void* data, size_t siz
 	return DC_STATUS_SUCCESS;
 }
 
-static dc_status_t qt_serial_flush(void **userdata, dc_direction_t queue)
+static dc_status_t qt_serial_purge(void **userdata, dc_direction_t queue)
 {
 	qt_serial_t *device = (qt_serial_t*) *userdata;
+
+	if (device && device->ops)
+		return device->ops->serial_purge(userdata, queue);
+
 	(void)queue;
 	if (device == NULL)
 		return DC_STATUS_INVALIDARGS;
@@ -351,9 +519,13 @@ static dc_status_t qt_serial_flush(void **userdata, dc_direction_t queue)
 	return DC_STATUS_SUCCESS;
 }
 
-static dc_status_t qt_serial_get_received(void **userdata, size_t *available)
+static dc_status_t qt_serial_get_available(void **userdata, size_t *available)
 {
 	qt_serial_t *device = (qt_serial_t*) *userdata;
+
+	if (device && device->ops)
+		return device->ops->serial_get_available(userdata, available);
+
 #if defined(Q_OS_WIN)
 	if (device == NULL)
 		return DC_STATUS_INVALIDARGS;
@@ -409,8 +581,8 @@ dc_custom_io_t qt_serial_ops = {
 	.serial_close = qt_serial_close,
 	.serial_read = qt_serial_read,
 	.serial_write = qt_serial_write,
-	.serial_purge = qt_serial_flush,
-	.serial_get_available = qt_serial_get_received,
+	.serial_purge = qt_serial_purge,
+	.serial_get_available = qt_serial_get_available,
 	.serial_set_timeout = qt_serial_set_timeout,
 // These doesn't make sense over bluetooth
 // NULL means NOP
-- 
2.13.1.518.g0d864c4df

From 0afae961325fa310014492fb1ee8e3225c8481b1 Mon Sep 17 00:00:00 2001
From: Linus Torvalds <[email protected]>
Date: Mon, 26 Jun 2017 20:15:19 -0700
Subject: [PATCH 3/3] Local hack, don't use

---
 core/qt-ble.cpp  | 19 ++++++++++++++++---
 scripts/build.sh |  1 +
 2 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/core/qt-ble.cpp b/core/qt-ble.cpp
index 06adce63..f3805af0 100644
--- a/core/qt-ble.cpp
+++ b/core/qt-ble.cpp
@@ -31,7 +31,11 @@ void BLEObject::serviceStateChanged(QLowEnergyService::ServiceState s)
 
 void BLEObject::characteristcStateChanged(const QLowEnergyCharacteristic &c, const QByteArray &value)
 {
-	receivedPackets.append(value);
+	/* HACK FOR SHEARWATER */
+	/* This needs to be somehow made conditional on the model */
+	const QByteArray bytes = value.mid(2);
+	/* Hack end */
+	receivedPackets.append(bytes);
 	waitForPacket.exit();
 }
 
@@ -44,7 +48,7 @@ void BLEObject::addService(const QBluetoothUuid &newService)
 {
 	const char *uuid = newService.toString().toUtf8().data();
 
-	qDebug() << "Found service" << uuid;
+	qDebug() << "Found service" << newService.toString();
 	if (uuid[1] == '0') {
 		qDebug () << " .. ignoring since first digit is '0'";
 		return;
@@ -82,6 +86,10 @@ dc_status_t BLEObject::write(const void* data, size_t size, size_t *actual)
 			QLowEnergyService::WriteWithoutResponse :
 			QLowEnergyService::WriteWithResponse;
 
+		/* HACK FOR SHEARWATER! */
+		/* This needs to be conditional on the characterstic model! */
+		bytes.prepend("\1\0", 2);
+		/* HACK END */
 		service->writeCharacteristic(c, bytes, mode);
 		return DC_STATUS_SUCCESS;
 	}
@@ -140,6 +148,11 @@ dc_status_t qt_ble_open(dc_custom_io_t *io, dc_context_t *context, const char *d
 
 	qDebug() << "qt_ble_open(" << devaddr << ")";
 
+	/* HACK FOR SHEARWATER */
+	/* This needs to be somehow conditional on the particular model */
+	controller->setRemoteAddressType(QLowEnergyController::RandomAddress);
+	/* Hack end */
+
 	// Wait until the connection succeeds or until an error occurs
 	QEventLoop loop;
 	loop.connect(controller, SIGNAL(connected()), SLOT(quit()));
@@ -206,7 +219,7 @@ dc_status_t qt_ble_open(dc_custom_io_t *io, dc_context_t *context, const char *d
 
 		if (!l.isEmpty()) {
 			d = l.first();
-			qDebug() << "now writing \"0x0100\" to the first descriptor";
+			qDebug() << "now writing \"0x0100\" to the first descriptor " << d.name();
 
 			ble->service->writeDescriptor(d, QByteArray::fromHex("0100"));
 		}
diff --git a/scripts/build.sh b/scripts/build.sh
index 88437c76..5f4dc607 100755
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -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)
 
-- 
2.13.1.518.g0d864c4df

_______________________________________________
subsurface mailing list
[email protected]
http://lists.subsurface-divelog.org/cgi-bin/mailman/listinfo/subsurface

Reply via email to