From a7d0033baef6092e8bae03b5eb5c913a4392210b Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sat, 16 Dec 2017 14:04:50 +0100 Subject: [PATCH 1/5] Add a Bluetooth Low Energy (BLE) transport type Libdivecomputer doesn't have built-in support for BLE communication yet, so this is mainly for future use and custom I/O implementations. --- examples/common.c | 1 + include/libdivecomputer/common.h | 1 + 2 files changed, 2 insertions(+) diff --git a/examples/common.c b/examples/common.c index f6ecceb..d23982b 100644 --- a/examples/common.c +++ b/examples/common.c @@ -98,6 +98,7 @@ static const transport_table_t g_transports[] = { {"usbhid", DC_TRANSPORT_USBHID}, {"irda", DC_TRANSPORT_IRDA}, {"bluetooth", DC_TRANSPORT_BLUETOOTH}, + {"ble", DC_TRANSPORT_BLE}, }; const char * diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h index 0cbaa08..db6d8eb 100644 --- a/include/libdivecomputer/common.h +++ b/include/libdivecomputer/common.h @@ -48,6 +48,7 @@ typedef enum dc_transport_t { DC_TRANSPORT_USBHID = (1 << 2), DC_TRANSPORT_IRDA = (1 << 3), DC_TRANSPORT_BLUETOOTH = (1 << 4), + DC_TRANSPORT_BLE = (1 << 5) } dc_transport_t; typedef enum dc_family_t { From afff8b450f80084f0a4eeee548430b1f3e67d204 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 19 Feb 2018 22:58:53 +0100 Subject: [PATCH 2/5] Add BLE support for the Suunto Eon Steel devices The main difference with the USB HID communication is that the BLE data stream is encoded using HDLC framing with a 32 bit CRC checksum. Due to this encoding, the data packets can no longer be processed one by one (as is done for the USB HID packets). The entire HDLC encoded stream needs to be received before it can be processed. This requires some additional buffering. --- src/checksum.c | 45 +++++++++ src/checksum.h | 3 + src/descriptor.c | 4 +- src/suunto_eonsteel.c | 230 +++++++++++++++++++++++++++++++++++++++--- 4 files changed, 265 insertions(+), 17 deletions(-) diff --git a/src/checksum.c b/src/checksum.c index 8caf563..a8b332e 100644 --- a/src/checksum.c +++ b/src/checksum.c @@ -112,3 +112,48 @@ checksum_crc_ccitt_uint16 (const unsigned char data[], unsigned int size) return crc; } + +unsigned int +checksum_crc32 (const unsigned char data[], unsigned int size) +{ + static const unsigned int crc_table[] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, + }; + + unsigned int crc = 0xffffffff; + for (unsigned int i = 0; i < size; ++i) + crc = crc_table[(crc ^ data[i]) & 0xff] ^ (crc >> 8); + + return crc ^ 0xffffffff; +} diff --git a/src/checksum.h b/src/checksum.h index 4e54d45..a6ead09 100644 --- a/src/checksum.h +++ b/src/checksum.h @@ -41,6 +41,9 @@ checksum_xor_uint8 (const unsigned char data[], unsigned int size, unsigned char unsigned short checksum_crc_ccitt_uint16 (const unsigned char data[], unsigned int size); +unsigned int +checksum_crc32 (const unsigned char data[], unsigned int size); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/descriptor.c b/src/descriptor.c index 948a9f3..4ba943e 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -98,8 +98,8 @@ static const dc_descriptor_t g_descriptors[] = { {"Suunto", "Zoop Novo", DC_FAMILY_SUUNTO_D9, 0x1E, DC_TRANSPORT_SERIAL, NULL}, {"Suunto", "D4f", DC_FAMILY_SUUNTO_D9, 0x20, DC_TRANSPORT_SERIAL, NULL}, /* Suunto EON Steel */ - {"Suunto", "EON Steel", DC_FAMILY_SUUNTO_EONSTEEL, 0, DC_TRANSPORT_USBHID, dc_filter_suunto}, - {"Suunto", "EON Core", DC_FAMILY_SUUNTO_EONSTEEL, 1, DC_TRANSPORT_USBHID, dc_filter_suunto}, + {"Suunto", "EON Steel", DC_FAMILY_SUUNTO_EONSTEEL, 0, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_suunto}, + {"Suunto", "EON Core", DC_FAMILY_SUUNTO_EONSTEEL, 1, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_suunto}, /* Uwatec Aladin */ {"Uwatec", "Aladin Air Twin", DC_FAMILY_UWATEC_ALADIN, 0x1C, DC_TRANSPORT_SERIAL, NULL}, {"Uwatec", "Aladin Sport Plus", DC_FAMILY_UWATEC_ALADIN, 0x3E, DC_TRANSPORT_SERIAL, NULL}, diff --git a/src/suunto_eonsteel.c b/src/suunto_eonsteel.c index 6c430b4..336877b 100644 --- a/src/suunto_eonsteel.c +++ b/src/suunto_eonsteel.c @@ -28,6 +28,7 @@ #include "device-private.h" #include "array.h" #include "platform.h" +#include "checksum.h" #define EONSTEEL 0 #define EONCORE 1 @@ -76,6 +77,13 @@ struct directory_entry { #define PACKET_SIZE 64 #define HEADER_SIZE 12 +#define MAXDATA_SIZE 2048 +#define CRC_SIZE 4 + +// HDLC special characters +#define END 0x7E +#define ESC 0x7D +#define ESC_BIT 0x20 static dc_status_t suunto_eonsteel_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); static dc_status_t suunto_eonsteel_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); @@ -133,6 +141,142 @@ static void put_le32(unsigned int val, unsigned char *p) p[3] = val >> 24; } +static dc_status_t +suunto_eonsteel_hdlc_write (suunto_eonsteel_device_t *device, const unsigned char data[], size_t size, size_t *actual) +{ + dc_status_t status = DC_STATUS_SUCCESS; + unsigned char buffer[20]; + size_t nbytes = 0; + + // Start of the packet. + buffer[nbytes++] = END; + + for (size_t i = 0; i < size; ++i) { + unsigned char c = data[i]; + + if (c == END || c == ESC) { + // Append the escape character. + buffer[nbytes++] = ESC; + + // Flush the buffer if necessary. + if (nbytes >= sizeof(buffer)) { + status = dc_iostream_write(device->iostream, buffer, nbytes, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR(device->base.context, "Failed to send the packet."); + return status; + } + + nbytes = 0; + } + + // Escape the character. + c ^= ESC_BIT; + } + + // Append the character. + buffer[nbytes++] = c; + + // Flush the buffer if necessary. + if (nbytes >= sizeof(buffer)) { + status = dc_iostream_write(device->iostream, buffer, nbytes, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR(device->base.context, "Failed to send the packet."); + return status; + } + + nbytes = 0; + } + } + + // End of the packet. + buffer[nbytes++] = END; + + // Flush the buffer. + status = dc_iostream_write(device->iostream, buffer, nbytes, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR(device->base.context, "Failed to send the packet."); + return status; + } + + if (actual) + *actual = size; + + return status; + +} + +static dc_status_t +suunto_eonsteel_hdlc_read (suunto_eonsteel_device_t *device, unsigned char data[], size_t size, size_t *actual) +{ + dc_status_t status = DC_STATUS_SUCCESS; + unsigned char buffer[20]; + unsigned int initialized = 0; + unsigned int escaped = 0; + size_t nbytes = 0; + + while (1) { + // Read a single data packet. + size_t transferred = 0; + status = dc_iostream_read(device->iostream, buffer, sizeof(buffer), &transferred); + if (status != DC_STATUS_SUCCESS) { + ERROR(device->base.context, "Failed to receive the packet."); + return status; + } + + for (size_t i = 0; i < transferred; ++i) { + unsigned char c = buffer[i]; + + if (c == END) { + if (escaped) { + ERROR (device->base.context, "HDLC frame escaped the special character %02x.", c); + return DC_STATUS_PROTOCOL; + } + + if (initialized) { + goto done; + } + + initialized = 1; + continue; + } + + if (!initialized) { + continue; + } + + if (c == ESC) { + if (escaped) { + ERROR (device->base.context, "HDLC frame escaped the special character %02x.", c); + return DC_STATUS_PROTOCOL; + } + escaped = 1; + continue; + } + + if (escaped) { + c ^= ESC_BIT; + escaped = 0; + } + + if (nbytes < size) + data[nbytes] = c; + nbytes++; + + } + } + +done: + if (nbytes > size) { + ERROR(device->base.context, "Insufficient buffer space available."); + return DC_STATUS_PROTOCOL; + } + + if (actual) + *actual = nbytes; + + return status; +} + /* * Get a single 64-byte packet from the dive computer. This handles packet * logging and any obvious packet-level errors, and returns the payload of @@ -144,7 +288,7 @@ static void put_le32(unsigned int val, unsigned char *p) * The maximum payload is 62 bytes. */ static dc_status_t -suunto_eonsteel_receive(suunto_eonsteel_device_t *device, unsigned char data[], unsigned int size, unsigned int *actual) +suunto_eonsteel_receive_usb(suunto_eonsteel_device_t *device, unsigned char data[], unsigned int size, unsigned int *actual) { dc_status_t rc = DC_STATUS_SUCCESS; unsigned char buf[PACKET_SIZE]; @@ -187,6 +331,48 @@ suunto_eonsteel_receive(suunto_eonsteel_device_t *device, unsigned char data[], return DC_STATUS_SUCCESS; } +static dc_status_t +suunto_eonsteel_receive_ble(suunto_eonsteel_device_t *device, unsigned char data[], unsigned int size, unsigned int *actual) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + unsigned char buffer[HEADER_SIZE + MAXDATA_SIZE + CRC_SIZE]; + size_t transferred = 0; + + rc = suunto_eonsteel_hdlc_read(device, buffer, sizeof(buffer), &transferred); + if (rc != DC_STATUS_SUCCESS) { + ERROR(device->base.context, "Failed to receive the packet."); + return rc; + } + + if (transferred < CRC_SIZE) { + ERROR(device->base.context, "Invalid packet length (" DC_PRINTF_SIZE ").", transferred); + return DC_STATUS_PROTOCOL; + } + + unsigned int nbytes = transferred - CRC_SIZE; + + unsigned int crc = array_uint32_le(buffer + nbytes); + unsigned int ccrc = checksum_crc32(buffer, nbytes); + if (crc != ccrc) { + ERROR(device->base.context, "Invalid checksum (expected %08x, received %08x).", ccrc, crc); + return DC_STATUS_PROTOCOL; + } + + if (nbytes > size) { + ERROR(device->base.context, "Insufficient buffer space available."); + return DC_STATUS_PROTOCOL; + } + + memcpy(data, buffer, nbytes); + + HEXDUMP (device->base.context, DC_LOGLEVEL_DEBUG, "rcv", buffer, nbytes); + + if (actual) + *actual = nbytes; + + return DC_STATUS_SUCCESS; +} + static dc_status_t suunto_eonsteel_send(suunto_eonsteel_device_t *device, unsigned short cmd, @@ -194,11 +380,10 @@ suunto_eonsteel_send(suunto_eonsteel_device_t *device, unsigned int size) { dc_status_t rc = DC_STATUS_SUCCESS; - unsigned char buf[PACKET_SIZE]; - size_t transferred = 0; + unsigned char buf[PACKET_SIZE + CRC_SIZE]; // Two-byte packet header, followed by 12 bytes of extended header - if (size + 2 + HEADER_SIZE > sizeof(buf)) { + if (size + 2 + HEADER_SIZE + CRC_SIZE > sizeof(buf)) { ERROR(device->base.context, "Insufficient buffer space available."); return DC_STATUS_PROTOCOL; } @@ -225,7 +410,15 @@ suunto_eonsteel_send(suunto_eonsteel_device_t *device, memcpy(buf + 14, data, size); } - rc = dc_iostream_write(device->iostream, buf, sizeof(buf), &transferred); + // 4 byte LE checksum + unsigned int crc = checksum_crc32(buf + 2, size + HEADER_SIZE); + put_le32(crc, buf + 14 + size); + + if (dc_iostream_get_transport(device->iostream) == DC_TRANSPORT_BLE) { + rc = suunto_eonsteel_hdlc_write(device, buf + 2, size + HEADER_SIZE + CRC_SIZE, NULL); + } else { + rc = dc_iostream_write(device->iostream, buf, sizeof(buf) - CRC_SIZE, NULL); + } if (rc != DC_STATUS_SUCCESS) { ERROR(device->base.context, "Failed to send the command."); return rc; @@ -257,7 +450,7 @@ suunto_eonsteel_transfer(suunto_eonsteel_device_t *device, unsigned int *actual) { dc_status_t rc = DC_STATUS_SUCCESS; - unsigned char header[PACKET_SIZE]; + unsigned char header[HEADER_SIZE + MAXDATA_SIZE]; unsigned int len = 0; // Send the command. @@ -265,8 +458,13 @@ suunto_eonsteel_transfer(suunto_eonsteel_device_t *device, if (rc != DC_STATUS_SUCCESS) return rc; - // Receive the header and the first part of the data. - rc = suunto_eonsteel_receive(device, header, sizeof(header), &len); + if (dc_iostream_get_transport(device->iostream) == DC_TRANSPORT_BLE) { + // Receive the entire data packet. + rc = suunto_eonsteel_receive_ble(device, header, sizeof(header), &len); + } else { + // Receive the header and the first part of the data. + rc = suunto_eonsteel_receive_usb(device, header, sizeof(header), &len); + } if (rc != DC_STATUS_SUCCESS) return rc; @@ -319,15 +517,17 @@ suunto_eonsteel_transfer(suunto_eonsteel_device_t *device, memcpy(answer, header + HEADER_SIZE, nbytes); // Receive the remainder of the data. - while (nbytes < length) { - rc = suunto_eonsteel_receive(device, answer + nbytes, length - nbytes, &len); - if (rc != DC_STATUS_SUCCESS) - return rc; + if (dc_iostream_get_transport(device->iostream) != DC_TRANSPORT_BLE) { + while (nbytes < length) { + rc = suunto_eonsteel_receive_usb(device, answer + nbytes, length - nbytes, &len); + if (rc != DC_STATUS_SUCCESS) + return rc; - nbytes += len; + nbytes += len; - if (len < PACKET_SIZE - 2) - break; + if (len < PACKET_SIZE - 2) + break; + } } // Verify the total payload length. From 3dcf93e26e349d01f518871f3a394cf7e30da7be Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 12 Feb 2018 12:07:33 +0100 Subject: [PATCH 3/5] Add BLE support for the Scubapro G2 devices The main difference with the USB HID communication is that the BLE data packets have a variable size and are no longer padded to the full 32 (Tx) or 64 (Rx) bytes. --- src/descriptor.c | 4 ++-- src/uwatec_g2.c | 23 ++++++++++++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index 4ba943e..b756cf3 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -135,9 +135,9 @@ static const dc_descriptor_t g_descriptors[] = { {"Scubapro", "Chromis", DC_FAMILY_UWATEC_MERIDIAN, 0x24, DC_TRANSPORT_SERIAL, NULL}, {"Scubapro", "Mantis 2", DC_FAMILY_UWATEC_MERIDIAN, 0x26, DC_TRANSPORT_SERIAL, NULL}, /* Scubapro G2 */ - {"Scubapro", "Aladin Sport Matrix", DC_FAMILY_UWATEC_G2, 0x17, DC_TRANSPORT_NONE, dc_filter_uwatec}, + {"Scubapro", "Aladin Sport Matrix", DC_FAMILY_UWATEC_G2, 0x17, DC_TRANSPORT_BLE, dc_filter_uwatec}, {"Scubapro", "Aladin Square", DC_FAMILY_UWATEC_G2, 0x22, DC_TRANSPORT_USBHID, dc_filter_uwatec}, - {"Scubapro", "G2", DC_FAMILY_UWATEC_G2, 0x32, DC_TRANSPORT_USBHID, dc_filter_uwatec}, + {"Scubapro", "G2", DC_FAMILY_UWATEC_G2, 0x32, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_uwatec}, /* Reefnet */ {"Reefnet", "Sensus", DC_FAMILY_REEFNET_SENSUS, 1, DC_TRANSPORT_SERIAL, NULL}, {"Reefnet", "Sensus Pro", DC_FAMILY_REEFNET_SENSUSPRO, 2, DC_TRANSPORT_SERIAL, NULL}, diff --git a/src/uwatec_g2.c b/src/uwatec_g2.c index ea2d324..798cc9a 100644 --- a/src/uwatec_g2.c +++ b/src/uwatec_g2.c @@ -74,21 +74,25 @@ receive_data (uwatec_g2_device_t *device, dc_event_progress_t *progress, unsigne rc = dc_iostream_read (device->iostream, buf, sizeof(buf), &transferred); if (rc != DC_STATUS_SUCCESS) { - ERROR (device->base.context, "read interrupt transfer failed"); + ERROR (device->base.context, "Failed to receive the packet."); return rc; } - if (transferred != sizeof(buf)) { - ERROR (device->base.context, "incomplete read interrupt transfer (got " DC_PRINTF_SIZE ", expected " DC_PRINTF_SIZE ")", transferred, sizeof(buf)); + + if (transferred < 1) { + ERROR (device->base.context, "Invalid packet length (" DC_PRINTF_SIZE ").", transferred); return DC_STATUS_PROTOCOL; } + len = buf[0]; - if (len >= sizeof(buf)) { - ERROR (device->base.context, "read interrupt transfer returns impossible packet size (%d)", len); + if (len + 1 > transferred) { + ERROR (device->base.context, "Invalid payload length (%u).", len); return DC_STATUS_PROTOCOL; } + HEXDUMP (device->base.context, DC_LOGLEVEL_DEBUG, "rcv", buf + 1, len); + if (len > size) { - ERROR (device->base.context, "receive result buffer too small"); + ERROR (device->base.context, "Insufficient buffer space available."); return DC_STATUS_PROTOCOL; } @@ -124,7 +128,12 @@ uwatec_g2_transfer (uwatec_g2_device_t *device, const unsigned char command[], u buf[1] = csize; memcpy(buf + 2, command, csize); memset(buf + 2 + csize, 0, sizeof(buf) - (csize + 2)); - status = dc_iostream_write (device->iostream, buf, sizeof(buf), &transferred); + + if (dc_iostream_get_transport(device->iostream) == DC_TRANSPORT_BLE) { + status = dc_iostream_write(device->iostream, buf + 1, csize + 1, &transferred); + } else { + status = dc_iostream_write(device->iostream, buf, sizeof(buf), &transferred); + } if (status != DC_STATUS_SUCCESS) { ERROR (device->base.context, "Failed to send the command."); return status; From 0978f8c0fa70710bad3fb865ffe3a20961c408dc Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 11 Mar 2018 13:50:24 +0100 Subject: [PATCH 4/5] Add BLE support for the HW OSTC3 devices The main difference with the serial communication is that the BLE communication uses data packets (with a maximum size of 20 bytes) instead of a continuous data stream. --- src/descriptor.c | 18 +++--- src/hw_ostc3.c | 163 ++++++++++++++++++++++++++++++----------------- 2 files changed, 115 insertions(+), 66 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index b756cf3..6a31c29 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -252,18 +252,18 @@ static const dc_descriptor_t g_descriptors[] = { {"Heinrichs Weikamp", "OSTC 2N", DC_FAMILY_HW_OSTC, 2, DC_TRANSPORT_SERIAL, NULL}, {"Heinrichs Weikamp", "OSTC 2C", DC_FAMILY_HW_OSTC, 3, DC_TRANSPORT_SERIAL, NULL}, {"Heinrichs Weikamp", "Frog", DC_FAMILY_HW_FROG, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_hw}, - {"Heinrichs Weikamp", "OSTC 2", DC_FAMILY_HW_OSTC3, 0x11, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_hw}, - {"Heinrichs Weikamp", "OSTC 2", DC_FAMILY_HW_OSTC3, 0x13, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_hw}, - {"Heinrichs Weikamp", "OSTC 2", DC_FAMILY_HW_OSTC3, 0x1B, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_hw}, + {"Heinrichs Weikamp", "OSTC 2", DC_FAMILY_HW_OSTC3, 0x11, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, + {"Heinrichs Weikamp", "OSTC 2", DC_FAMILY_HW_OSTC3, 0x13, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, + {"Heinrichs Weikamp", "OSTC 2", DC_FAMILY_HW_OSTC3, 0x1B, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, {"Heinrichs Weikamp", "OSTC 3", DC_FAMILY_HW_OSTC3, 0x0A, DC_TRANSPORT_SERIAL, dc_filter_hw}, - {"Heinrichs Weikamp", "OSTC Plus", DC_FAMILY_HW_OSTC3, 0x13, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_hw}, - {"Heinrichs Weikamp", "OSTC Plus", DC_FAMILY_HW_OSTC3, 0x1A, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_hw}, - {"Heinrichs Weikamp", "OSTC 4", DC_FAMILY_HW_OSTC3, 0x3B, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_hw}, + {"Heinrichs Weikamp", "OSTC Plus", DC_FAMILY_HW_OSTC3, 0x13, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, + {"Heinrichs Weikamp", "OSTC Plus", DC_FAMILY_HW_OSTC3, 0x1A, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, + {"Heinrichs Weikamp", "OSTC 4", DC_FAMILY_HW_OSTC3, 0x3B, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, {"Heinrichs Weikamp", "OSTC cR", DC_FAMILY_HW_OSTC3, 0x05, DC_TRANSPORT_SERIAL, dc_filter_hw}, {"Heinrichs Weikamp", "OSTC cR", DC_FAMILY_HW_OSTC3, 0x07, DC_TRANSPORT_SERIAL, dc_filter_hw}, - {"Heinrichs Weikamp", "OSTC Sport", DC_FAMILY_HW_OSTC3, 0x12, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_hw}, - {"Heinrichs Weikamp", "OSTC Sport", DC_FAMILY_HW_OSTC3, 0x13, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_hw}, - {"Heinrichs Weikamp", "OSTC 2 TR", DC_FAMILY_HW_OSTC3, 0x33, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_hw}, + {"Heinrichs Weikamp", "OSTC Sport", DC_FAMILY_HW_OSTC3, 0x12, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, + {"Heinrichs Weikamp", "OSTC Sport", DC_FAMILY_HW_OSTC3, 0x13, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, + {"Heinrichs Weikamp", "OSTC 2 TR", DC_FAMILY_HW_OSTC3, 0x33, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, /* Cressi Edy */ {"Tusa", "IQ-700", DC_FAMILY_CRESSI_EDY, 0x05, DC_TRANSPORT_SERIAL, NULL}, {"Cressi", "Edy", DC_FAMILY_CRESSI_EDY, 0x08, DC_TRANSPORT_SERIAL, NULL}, diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index dec0a44..d018e07 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -96,6 +96,9 @@ typedef struct hw_ostc3_device_t { unsigned int model; unsigned char fingerprint[5]; hw_ostc3_state_t state; + unsigned char cache[20]; + unsigned int available; + unsigned int offset; } hw_ostc3_device_t; typedef struct hw_ostc3_logbook_t { @@ -174,6 +177,89 @@ hw_ostc3_strncpy (unsigned char *data, unsigned int size, const char *text) return 0; } +static dc_status_t +hw_ostc3_read (hw_ostc3_device_t *device, dc_event_progress_t *progress, unsigned char data[], size_t size) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + dc_transport_t transport = dc_iostream_get_transport(device->iostream); + + size_t nbytes = 0; + while (nbytes < size) { + if (transport == DC_TRANSPORT_BLE) { + if (device->available == 0) { + // Read a packet into the cache. + size_t len = 0; + rc = dc_iostream_read (device->iostream, device->cache, sizeof(device->cache), &len); + if (rc != DC_STATUS_SUCCESS) + return rc; + + device->available = len; + device->offset = 0; + } + } + + // Set the minimum packet size. + size_t length = (transport == DC_TRANSPORT_BLE) ? device->available : 1024; + + // Limit the packet size to the total size. + if (nbytes + length > size) + length = size - nbytes; + + if (transport == DC_TRANSPORT_BLE) { + // Copy the data from the cached packet. + memcpy (data + nbytes, device->cache + device->offset, length); + device->available -= length; + device->offset += length; + } else { + // Read the packet. + rc = dc_iostream_write (device->iostream, data + nbytes, length, NULL); + if (rc != DC_STATUS_SUCCESS) + return rc; + } + + // Update and emit a progress event. + if (progress) { + progress->current += length; + device_event_emit ((dc_device_t *) device, DC_EVENT_PROGRESS, progress); + } + + nbytes += length; + } + + return rc; +} + +static dc_status_t +hw_ostc3_write (hw_ostc3_device_t *device, dc_event_progress_t *progress, const unsigned char data[], size_t size) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + dc_transport_t transport = dc_iostream_get_transport(device->iostream); + + size_t nbytes = 0; + while (nbytes < size) { + // Set the maximum packet size. + size_t length = (transport == DC_TRANSPORT_BLE) ? sizeof(device->cache) : 64; + + // Limit the packet size to the total size. + if (nbytes + length > size) + length = size - nbytes; + + // Write the packet. + rc = dc_iostream_write (device->iostream, data + nbytes, length, NULL); + if (rc != DC_STATUS_SUCCESS) + return rc; + + // Update and emit a progress event. + if (progress) { + progress->current += length; + device_event_emit ((dc_device_t *) device, DC_EVENT_PROGRESS, progress); + } + + nbytes += length; + } + + return rc; +} static dc_status_t hw_ostc3_transfer (hw_ostc3_device_t *device, @@ -196,7 +282,7 @@ hw_ostc3_transfer (hw_ostc3_device_t *device, // Send the command. unsigned char command[1] = {cmd}; - status = dc_iostream_write (device->iostream, command, sizeof (command), NULL); + status = hw_ostc3_write (device, NULL, command, sizeof (command)); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the command."); return status; @@ -204,7 +290,7 @@ hw_ostc3_transfer (hw_ostc3_device_t *device, // Read the echo. unsigned char echo[1] = {0}; - status = dc_iostream_read (device->iostream, echo, sizeof (echo), NULL); + status = hw_ostc3_read (device, NULL, echo, sizeof (echo)); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to receive the echo."); return status; @@ -223,68 +309,28 @@ hw_ostc3_transfer (hw_ostc3_device_t *device, if (input) { // Send the input data packet. - unsigned int nbytes = 0; - while (nbytes < isize) { - // Set the minimum packet size. - unsigned int len = 64; - - // Limit the packet size to the total size. - if (nbytes + len > isize) - len = isize - nbytes; - - // Write the packet. - status = dc_iostream_write (device->iostream, input + nbytes, len, NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to send the data packet."); - return status; - } - - // Update and emit a progress event. - if (progress) { - progress->current += len; - device_event_emit ((dc_device_t *) device, DC_EVENT_PROGRESS, progress); - } - - nbytes += len; + status = hw_ostc3_write (device, progress, input, isize); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the data packet."); + return status; } } if (output) { - unsigned int nbytes = 0; - while (nbytes < osize) { - // Set the minimum packet size. - unsigned int len = 1024; - - // Increase the packet size if more data is immediately available. - size_t available = 0; - status = dc_iostream_get_available (device->iostream, &available); - if (status == DC_STATUS_SUCCESS && available > len) - len = available; - - // Limit the packet size to the total size. - if (nbytes + len > osize) - len = osize - nbytes; - - // Read the packet. - status = dc_iostream_read (device->iostream, output + nbytes, len, NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to receive the answer."); - return status; - } - - // Update and emit a progress event. - if (progress) { - progress->current += len; - device_event_emit ((dc_device_t *) device, DC_EVENT_PROGRESS, progress); - } - - nbytes += len; + // Read the ouput data packet. + status = hw_ostc3_read (device, progress, output, osize); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the answer."); + return status; } } if (delay) { unsigned int count = delay / 100; for (unsigned int i = 0; i < count; ++i) { + if (device->available) + break; + size_t available = 0; status = dc_iostream_get_available (device->iostream, &available); if (status == DC_STATUS_SUCCESS && available > 0) @@ -297,7 +343,7 @@ hw_ostc3_transfer (hw_ostc3_device_t *device, if (cmd != EXIT) { // Read the ready byte. unsigned char answer[1] = {0}; - status = dc_iostream_read (device->iostream, answer, sizeof (answer), NULL); + status = hw_ostc3_read (device, NULL, answer, sizeof (answer)); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to receive the ready byte."); return status; @@ -336,6 +382,9 @@ hw_ostc3_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *i device->feature = 0; device->model = 0; memset (device->fingerprint, 0, sizeof (device->fingerprint)); + memset (device->cache, 0, sizeof (device->cache)); + device->available = 0; + device->offset = 0; // Set the serial communication protocol (115200 8N1). status = dc_iostream_configure (device->iostream, 115200, 8, DC_PARITY_NONE, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE); @@ -424,7 +473,7 @@ hw_ostc3_device_init_service (hw_ostc3_device_t *device) unsigned char output[5]; // We cant use hw_ostc3_transfer here, due to the different echos - status = dc_iostream_write (device->iostream, command, sizeof (command), NULL); + status = hw_ostc3_write (device, NULL, command, sizeof (command)); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to send the command."); return status; @@ -434,7 +483,7 @@ hw_ostc3_device_init_service (hw_ostc3_device_t *device) dc_iostream_sleep (device->iostream, 100); // Read the response - status = dc_iostream_read (device->iostream, output, sizeof (output), NULL); + status = hw_ostc3_read (device, NULL, output, sizeof (output)); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to receive the echo."); return status; From 8f7abc5a2d3ca5099c6e261a42ece1b145cac1e3 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 13 Mar 2018 23:01:16 +0100 Subject: [PATCH 5/5] Add BLE support for the Shearwater devices The main difference with the serial communication is that the BLE communication transmits each SLIP encoded data packet as one or more BLE data packets. The BLE packets have an extra two byte header with the total number of packets and the current packet number. --- src/descriptor.c | 8 +-- src/shearwater_common.c | 140 ++++++++++++++++++++++++++-------------- 2 files changed, 95 insertions(+), 53 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index 6a31c29..0d0ed66 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -284,11 +284,11 @@ static const dc_descriptor_t g_descriptors[] = { {"Shearwater", "Predator", DC_FAMILY_SHEARWATER_PREDATOR, 2, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_shearwater}, /* Shearwater Petrel */ {"Shearwater", "Petrel", DC_FAMILY_SHEARWATER_PETREL, 3, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_shearwater}, - {"Shearwater", "Petrel 2", DC_FAMILY_SHEARWATER_PETREL, 3, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_shearwater}, + {"Shearwater", "Petrel 2", DC_FAMILY_SHEARWATER_PETREL, 3, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_shearwater}, {"Shearwater", "Nerd", DC_FAMILY_SHEARWATER_PETREL, 4, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_shearwater}, - {"Shearwater", "Perdix", DC_FAMILY_SHEARWATER_PETREL, 5, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_shearwater}, - {"Shearwater", "Perdix AI", DC_FAMILY_SHEARWATER_PETREL, 6, DC_TRANSPORT_NONE, dc_filter_shearwater}, - {"Shearwater", "Nerd 2", DC_FAMILY_SHEARWATER_PETREL, 7, DC_TRANSPORT_NONE, dc_filter_shearwater}, + {"Shearwater", "Perdix", DC_FAMILY_SHEARWATER_PETREL, 5, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_shearwater}, + {"Shearwater", "Perdix AI", DC_FAMILY_SHEARWATER_PETREL, 6, DC_TRANSPORT_BLE, dc_filter_shearwater}, + {"Shearwater", "Nerd 2", DC_FAMILY_SHEARWATER_PETREL, 7, DC_TRANSPORT_BLE, dc_filter_shearwater}, /* Dive Rite NiTek Q */ {"Dive Rite", "NiTek Q", DC_FAMILY_DIVERITE_NITEKQ, 0, DC_TRANSPORT_SERIAL, NULL}, /* Citizen Hyper Aqualand */ diff --git a/src/shearwater_common.c b/src/shearwater_common.c index 130ff62..a7f67f6 100644 --- a/src/shearwater_common.c +++ b/src/shearwater_common.c @@ -25,6 +25,7 @@ #include "shearwater_common.h" #include "context-private.h" +#include "platform.h" #include "array.h" #define SZ_PACKET 254 @@ -122,19 +123,33 @@ shearwater_common_decompress_xor (unsigned char *data, unsigned int size) return 0; } - static dc_status_t shearwater_common_slip_write (shearwater_common_device_t *device, const unsigned char data[], unsigned int size) { dc_status_t status = DC_STATUS_SUCCESS; + dc_transport_t transport = dc_iostream_get_transport(device->iostream); unsigned char buffer[32]; unsigned int nbytes = 0; -#if 0 - // Send an initial END character to flush out any data that may have - // accumulated in the receiver due to line noise. - buffer[nbytes++] = END; -#endif + if (transport == DC_TRANSPORT_BLE) { + // Calculate the total number of bytes. + unsigned int count = 1; + for (unsigned int i = 0; i < size; ++i) { + unsigned char c = data[i]; + if (c == END || c == ESC) { + count += 2; + } else { + count++; + } + } + + // Calculate the total number of frames. + unsigned int nframes = (count + sizeof(buffer) - 1) / sizeof(buffer); + + buffer[0] = nframes; + buffer[1] = 0; + nbytes = 2; + } for (unsigned int i = 0; i < size; ++i) { unsigned char c = data[i]; @@ -151,7 +166,12 @@ shearwater_common_slip_write (shearwater_common_device_t *device, const unsigned return status; } - nbytes = 0; + if (transport == DC_TRANSPORT_BLE) { + buffer[1]++; + nbytes = 2; + } else { + nbytes = 0; + } } // Escape the character. @@ -173,7 +193,12 @@ shearwater_common_slip_write (shearwater_common_device_t *device, const unsigned return status; } - nbytes = 0; + if (transport == DC_TRANSPORT_BLE) { + buffer[1]++; + nbytes = 2; + } else { + nbytes = 0; + } } } @@ -195,70 +220,87 @@ static dc_status_t shearwater_common_slip_read (shearwater_common_device_t *device, unsigned char data[], unsigned int size, unsigned int *actual) { dc_status_t status = DC_STATUS_SUCCESS; + dc_transport_t transport = dc_iostream_get_transport(device->iostream); + unsigned char buffer[256]; unsigned int escaped = 0; unsigned int nbytes = 0; + // Get the packet size. + size_t packetsize = (transport == DC_TRANSPORT_BLE) ? sizeof(buffer) : 1; + // Read bytes until a complete packet has been received. If the // buffer runs out of space, bytes are dropped. The caller can // detect this condition because the return value will be larger // than the supplied buffer size. while (1) { - unsigned char c = 0; - - // Get a single character to process. - status = dc_iostream_read (device->iostream, &c, 1, NULL); + size_t transferred = 0; + status = dc_iostream_read (device->iostream, buffer, packetsize, &transferred); if (status != DC_STATUS_SUCCESS) { ERROR (device->base.context, "Failed to receive the packet."); return status; } - if (c == END || c == ESC) { - if (escaped) { - // If the END or ESC characters are escaped, then we - // have a protocol violation, and an error is reported. - ERROR (device->base.context, "SLIP frame escaped the special character %02x.", c); + size_t offset = 0; + if (transport == DC_TRANSPORT_BLE) { + if (transferred < 2) { + ERROR (device->base.context, "Invalid packet length (" DC_PRINTF_SIZE ").", transferred); return DC_STATUS_PROTOCOL; } - if (c == END) { - // If it's an END character then we're done. - // As a minor optimization, empty packets are ignored. This - // is to avoid bothering the upper layers with all the empty - // packets generated by the duplicate END characters which - // are sent to try to detect line noise. - if (nbytes) { - goto done; + offset = 2; + } + + for (size_t i = offset; i < transferred; ++i) { + unsigned char c = buffer[i]; + + if (c == END || c == ESC) { + if (escaped) { + // If the END or ESC characters are escaped, then we + // have a protocol violation, and an error is reported. + ERROR (device->base.context, "SLIP frame escaped the special character %02x.", c); + return DC_STATUS_PROTOCOL; } - } else { - // If it's an ESC character, get another character and then - // figure out what to store in the packet based on that. - escaped = 1; + + if (c == END) { + // If it's an END character then we're done. + // As a minor optimization, empty packets are ignored. This + // is to avoid bothering the upper layers with all the empty + // packets generated by the duplicate END characters which + // are sent to try to detect line noise. + if (nbytes) { + goto done; + } + } else { + // If it's an ESC character, get another character and then + // figure out what to store in the packet based on that. + escaped = 1; + } + + continue; } - continue; - } + if (escaped) { + // If it's not one of the two escaped characters, then we + // have a protocol violation. The best bet seems to be to + // leave the byte alone and just stuff it into the packet. + switch (c) { + case ESC_END: + c = END; + break; + case ESC_ESC: + c = ESC; + break; + default: + break; + } - if (escaped) { - // If it's not one of the two escaped characters, then we - // have a protocol violation. The best bet seems to be to - // leave the byte alone and just stuff it into the packet. - switch (c) { - case ESC_END: - c = END; - break; - case ESC_ESC: - c = ESC; - break; - default: - break; + escaped = 0; } - escaped = 0; + if (nbytes < size) + data[nbytes] = c; + nbytes++; } - - if (nbytes < size) - data[nbytes] = c; - nbytes++; } done: