From 5116ee8f2d0ab5550566b18e0a36da9a6856b54e Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 2 Apr 2019 21:01:21 +0200 Subject: [PATCH 1/6] Get the gas mix index directly from the event data There is no need to lookup the gas mix index, because the number is stored directly in the event data, right next to the oxygen and helium values. This actually removes an ambiguity in cases where the user has configured two or more identical gas mixes. In that case, the lookup would always find the first gas mix. --- src/suunto_d9_parser.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/suunto_d9_parser.c b/src/suunto_d9_parser.c index 10a7590..362f0d7 100644 --- a/src/suunto_d9_parser.c +++ b/src/suunto_d9_parser.c @@ -729,7 +729,7 @@ suunto_d9_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t ca ERROR (abstract->context, "Buffer overflow detected!"); return DC_STATUS_DATAFORMAT; } - unknown = data[offset + 0]; + type = data[offset + 0]; he = data[offset + 1]; o2 = data[offset + 2]; if (parser->model == DX || parser->model == VYPERNOVO || @@ -738,7 +738,7 @@ suunto_d9_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t ca } else { seconds = data[offset + 3]; } - idx = suunto_d9_parser_find_gasmix(parser, o2, he); + idx = type & 0x0F; if (idx >= parser->ngasmixes) { ERROR (abstract->context, "Invalid gas mix."); return DC_STATUS_DATAFORMAT; From 6867ffb1435a292e34c9d1a77a254677bae107a6 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 2 Apr 2019 21:07:03 +0200 Subject: [PATCH 2/6] Implement the initial gas mix Initialize the initial gas mix correctly from the data in the dive header, instead of always using the first gas mix. --- src/suunto_d9_parser.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/suunto_d9_parser.c b/src/suunto_d9_parser.c index 362f0d7..420d438 100644 --- a/src/suunto_d9_parser.c +++ b/src/suunto_d9_parser.c @@ -223,6 +223,8 @@ suunto_d9_parser_cache (suunto_d9_parser_t *parser) } else { parser->gasmix = data[0x28]; } + } else if (parser->model == DX) { + parser->gasmix = data[0x31] & 0x7F; } } parser->config = config; From 8a70885f89d3ddef67e79c246a8d2d94c5f3efdd Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 2 Apr 2019 21:12:16 +0200 Subject: [PATCH 3/6] Take the CCR diluents into account The Suunto DX supports 3 CCR diluents and 8 OC gas mixes. Since the gas mix index in the data is relative to either the set of CCR diluents or OC gas mixes (depending on the dive mode) and libdivecomputer reports both sets, the index needs to be adjusted. --- src/suunto_d9_parser.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/suunto_d9_parser.c b/src/suunto_d9_parser.c index 420d438..39b42d3 100644 --- a/src/suunto_d9_parser.c +++ b/src/suunto_d9_parser.c @@ -77,6 +77,7 @@ struct suunto_d9_parser_t { unsigned int id; unsigned int mode; unsigned int ngasmixes; + unsigned int nccr; unsigned int oxygen[NGASMIXES]; unsigned int helium[NGASMIXES]; unsigned int gasmix; @@ -109,7 +110,7 @@ static unsigned int suunto_d9_parser_find_gasmix (suunto_d9_parser_t *parser, unsigned int o2, unsigned int he) { // Find the gasmix in the list. - unsigned int i = 0; + unsigned int i = parser->nccr; while (i < parser->ngasmixes) { if (o2 == parser->oxygen[i] && he == parser->helium[i]) break; @@ -136,6 +137,7 @@ suunto_d9_parser_cache (suunto_d9_parser_t *parser) unsigned int gasmode_offset = 0x19; unsigned int gasmix_offset = 0x21; unsigned int gasmix_count = 3; + unsigned int ccr_count = 0; if (parser->model == HELO2) { gasmode_offset = 0x1F; gasmix_offset = 0x54; @@ -169,6 +171,7 @@ suunto_d9_parser_cache (suunto_d9_parser_t *parser) else gasmix_offset = 0xC1; gasmix_count = 11; + ccr_count = 3; } // Offset to the configuration data. @@ -189,12 +192,15 @@ suunto_d9_parser_cache (suunto_d9_parser_t *parser) parser->gasmix = 0; if (parser->mode == GAUGE || parser->mode == FREEDIVE) { parser->ngasmixes = 0; + parser->nccr = 0; } else if (parser->mode == AIR) { parser->oxygen[0] = 21; parser->helium[0] = 0; parser->ngasmixes = 1; + parser->nccr = 0; } else { parser->ngasmixes = 0; + parser->nccr = ccr_count; for (unsigned int i = 0; i < gasmix_count; ++i) { if (parser->model == HELO2 || parser->model == D4i || parser->model == D6i || parser->model == D9tx || @@ -225,6 +231,9 @@ suunto_d9_parser_cache (suunto_d9_parser_t *parser) } } else if (parser->model == DX) { parser->gasmix = data[0x31] & 0x7F; + if ((data[0x31] & 0x80) == 0) { + parser->gasmix += parser->nccr; + } } } parser->config = config; @@ -255,6 +264,7 @@ suunto_d9_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int parser->id = 0; parser->mode = AIR; parser->ngasmixes = 0; + parser->nccr = 0; for (unsigned int i = 0; i < NGASMIXES; ++i) { parser->oxygen[i] = 0; parser->helium[i] = 0; @@ -278,6 +288,7 @@ suunto_d9_parser_set_data (dc_parser_t *abstract, const unsigned char *data, uns parser->id = 0; parser->mode = AIR; parser->ngasmixes = 0; + parser->nccr = 0; for (unsigned int i = 0; i < NGASMIXES; ++i) { parser->oxygen[i] = 0; parser->helium[i] = 0; @@ -741,6 +752,9 @@ suunto_d9_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t ca seconds = data[offset + 3]; } idx = type & 0x0F; + if ((type & 0x80) == 0) { + idx += parser->nccr; + } if (idx >= parser->ngasmixes) { ERROR (abstract->context, "Invalid gas mix."); return DC_STATUS_DATAFORMAT; From 6ee786c31a72dc36c79ce34da9091246afd01f69 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 2 Apr 2019 21:17:45 +0200 Subject: [PATCH 4/6] Report the setpoint data --- src/suunto_d9_parser.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/suunto_d9_parser.c b/src/suunto_d9_parser.c index 39b42d3..11b99e9 100644 --- a/src/suunto_d9_parser.c +++ b/src/suunto_d9_parser.c @@ -555,7 +555,7 @@ suunto_d9_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t ca unsigned int event = data[offset++]; unsigned int seconds, type, unknown, heading; unsigned int current, next; - unsigned int he, o2, idx; + unsigned int he, o2, ppo2, idx; unsigned int length; sample.event.type = SAMPLE_EVENT_NONE; @@ -747,8 +747,10 @@ suunto_d9_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t ca o2 = data[offset + 2]; if (parser->model == DX || parser->model == VYPERNOVO || (parser->model == D6i && parser->id == ID_D6I_V2)) { + ppo2 = data[offset + 3]; seconds = data[offset + 4]; } else { + ppo2 = 0; seconds = data[offset + 3]; } idx = type & 0x0F; @@ -761,6 +763,10 @@ suunto_d9_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t ca } sample.gasmix = idx; if (callback) callback (DC_SAMPLE_GASMIX, sample, userdata); + if (type & 0x80) { + sample.setpoint = ppo2 / 10.0; + if (callback) callback (DC_SAMPLE_SETPOINT, sample, userdata); + } offset += length; break; default: From 88a814a616ef8e5acfd250dcca6b35420860bd8a Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 8 Apr 2019 19:29:48 +0200 Subject: [PATCH 5/6] Check the correct vtable pointer --- src/iostream.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iostream.c b/src/iostream.c index e0ba7fc..e775146 100644 --- a/src/iostream.c +++ b/src/iostream.c @@ -210,7 +210,7 @@ dc_iostream_write (dc_iostream_t *iostream, const void *data, size_t size, size_ dc_status_t status = DC_STATUS_SUCCESS; size_t nbytes = 0; - if (iostream == NULL || iostream->vtable->read == NULL) { + if (iostream == NULL || iostream->vtable->write == NULL) { goto out; } From 019a98f80cc3350e727dc0ade88c7e692e9ff7e6 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 7 Dec 2018 14:42:43 +0100 Subject: [PATCH 6/6] Add support for the Mares Bluelink Pro interface 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. Occasionally, the device responds with an empty packet (with just the ACK and EOF byte) or with a short packet where one or more payload bytes are missing. The empty packet is most likely caused because the device didn't receive the second part of the command (with the parameters) in time. The missing bytes might be caused by a buffer overflow on the Bluelink Pro, when the data from the dive computer arrives faster over the serial link than the device can forward it over the slower bluetooth link to the host system. One way to address this problem is to lower the packet size from 256 bytes to only 128 bytes. The main disadvantage is that this also impacts the download speed considerably. In one particular example, the total download time increased from about 135 to 210 seconds (which is already slow compared to the 62 seconds the same download takes over usb)! An alternative solution is to simply retry the failed command. That way there is only a performance penalty for the few bad packets, and not for every single packet. In the above example, only two packets needed a retransmission. Note that the iconhd communication protocol uses no checksums. Hence it's not possible to detect corrupt data packets. Only short packets can be detected, because they result in a timeout. --- src/descriptor.c | 26 ++++++++--- src/mares_iconhd.c | 113 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 127 insertions(+), 12 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index 03049f0..eb5bce8 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -34,6 +34,7 @@ static int dc_filter_suunto (dc_transport_t transport, const void *userdata); static int dc_filter_shearwater (dc_transport_t transport, const void *userdata); static int dc_filter_hw (dc_transport_t transport, const void *userdata); static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata); +static int dc_filter_mares (dc_transport_t transport, const void *userdata); static dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item); @@ -241,16 +242,16 @@ static const dc_descriptor_t g_descriptors[] = { {"Mares", "Airlab", DC_FAMILY_MARES_DARWIN , 1, DC_TRANSPORT_SERIAL, NULL}, /* Mares Icon HD */ {"Mares", "Matrix", DC_FAMILY_MARES_ICONHD , 0x0F, DC_TRANSPORT_SERIAL, NULL}, - {"Mares", "Smart", DC_FAMILY_MARES_ICONHD , 0x000010, DC_TRANSPORT_SERIAL, NULL}, - {"Mares", "Smart Apnea", DC_FAMILY_MARES_ICONHD , 0x010010, DC_TRANSPORT_SERIAL, NULL}, + {"Mares", "Smart", DC_FAMILY_MARES_ICONHD , 0x000010, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_mares}, + {"Mares", "Smart Apnea", DC_FAMILY_MARES_ICONHD , 0x010010, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_mares}, {"Mares", "Icon HD", DC_FAMILY_MARES_ICONHD , 0x14, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Icon HD Net Ready", DC_FAMILY_MARES_ICONHD , 0x15, DC_TRANSPORT_SERIAL, NULL}, - {"Mares", "Puck Pro", DC_FAMILY_MARES_ICONHD , 0x18, DC_TRANSPORT_SERIAL, NULL}, + {"Mares", "Puck Pro", DC_FAMILY_MARES_ICONHD , 0x18, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_mares}, {"Mares", "Nemo Wide 2", DC_FAMILY_MARES_ICONHD , 0x19, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Puck 2", DC_FAMILY_MARES_ICONHD , 0x1F, DC_TRANSPORT_SERIAL, NULL}, - {"Mares", "Quad Air", DC_FAMILY_MARES_ICONHD , 0x23, DC_TRANSPORT_SERIAL, NULL}, - {"Mares", "Smart Air", DC_FAMILY_MARES_ICONHD , 0x24, DC_TRANSPORT_SERIAL, NULL}, - {"Mares", "Quad", DC_FAMILY_MARES_ICONHD , 0x29, DC_TRANSPORT_SERIAL, NULL}, + {"Mares", "Quad Air", DC_FAMILY_MARES_ICONHD , 0x23, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_mares}, + {"Mares", "Smart Air", DC_FAMILY_MARES_ICONHD , 0x24, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_mares}, + {"Mares", "Quad", DC_FAMILY_MARES_ICONHD , 0x29, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_mares}, /* Heinrichs Weikamp */ {"Heinrichs Weikamp", "OSTC", DC_FAMILY_HW_OSTC, 0, DC_TRANSPORT_SERIAL, NULL}, {"Heinrichs Weikamp", "OSTC Mk2", DC_FAMILY_HW_OSTC, 1, DC_TRANSPORT_SERIAL, NULL}, @@ -502,6 +503,19 @@ static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata) return 1; } +static int dc_filter_mares (dc_transport_t transport, const void *userdata) +{ + static const char *bluetooth[] = { + "Mares bluelink pro", + }; + + if (transport == DC_TRANSPORT_BLE) { + return dc_filter_internal_name ((const char *) userdata, bluetooth, C_ARRAY_SIZE(bluetooth)); + } + + return 1; +} + dc_status_t dc_descriptor_iterator (dc_iterator_t **out) { diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index d5f0ede..5d428a9 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -45,6 +45,8 @@ #define SMARTAIR 0x24 #define QUAD 0x29 +#define MAXRETRIES 4 + #define ACK 0xAA #define EOF 0xEA @@ -72,6 +74,9 @@ typedef struct mares_iconhd_device_t { unsigned char version[140]; unsigned int model; unsigned int packetsize; + unsigned char cache[20]; + unsigned int available; + unsigned int offset; } mares_iconhd_device_t; static dc_status_t mares_iconhd_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); @@ -146,7 +151,79 @@ mares_iconhd_get_model (mares_iconhd_device_t *device) } static dc_status_t -mares_iconhd_transfer (mares_iconhd_device_t *device, +mares_iconhd_read (mares_iconhd_device_t *device, 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 : size - nbytes; + + // 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_read (device->iostream, data + nbytes, length, &length); + if (rc != DC_STATUS_SUCCESS) + return rc; + } + + nbytes += length; + } + + return rc; +} + +static dc_status_t +mares_iconhd_write (mares_iconhd_device_t *device, 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) : size - nbytes; + + // 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, &length); + if (rc != DC_STATUS_SUCCESS) + return rc; + + nbytes += length; + } + + return rc; +} + +static dc_status_t +mares_iconhd_packet (mares_iconhd_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize) { @@ -159,7 +236,7 @@ mares_iconhd_transfer (mares_iconhd_device_t *device, return DC_STATUS_CANCELLED; // Send the command header to the dive computer. - status = dc_iostream_write (device->iostream, command, 2, NULL); + status = mares_iconhd_write (device, command, 2); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the command."); return status; @@ -167,7 +244,7 @@ mares_iconhd_transfer (mares_iconhd_device_t *device, // Receive the header byte. unsigned char header[1] = {0}; - status = dc_iostream_read (device->iostream, header, sizeof (header), NULL); + status = mares_iconhd_read (device, header, sizeof (header)); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to receive the answer."); return status; @@ -181,7 +258,7 @@ mares_iconhd_transfer (mares_iconhd_device_t *device, // Send the command payload to the dive computer. if (csize > 2) { - status = dc_iostream_write (device->iostream, command + 2, csize - 2, NULL); + status = mares_iconhd_write (device, command + 2, csize - 2); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the command."); return status; @@ -189,7 +266,7 @@ mares_iconhd_transfer (mares_iconhd_device_t *device, } // Read the packet. - status = dc_iostream_read (device->iostream, answer, asize, NULL); + status = mares_iconhd_read (device, answer, asize); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to receive the answer."); return status; @@ -197,7 +274,7 @@ mares_iconhd_transfer (mares_iconhd_device_t *device, // Receive the trailer byte. unsigned char trailer[1] = {0}; - status = dc_iostream_read (device->iostream, trailer, sizeof (trailer), NULL); + status = mares_iconhd_read (device, trailer, sizeof (trailer)); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to receive the answer."); return status; @@ -212,6 +289,27 @@ mares_iconhd_transfer (mares_iconhd_device_t *device, return DC_STATUS_SUCCESS; } +static dc_status_t +mares_iconhd_transfer (mares_iconhd_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize) +{ + unsigned int nretries = 0; + dc_status_t rc = DC_STATUS_SUCCESS; + while ((rc = mares_iconhd_packet (device, command, csize, answer, asize)) != DC_STATUS_SUCCESS) { + // Automatically discard a corrupted packet, + // and request a new one. + if (rc != DC_STATUS_PROTOCOL && rc != DC_STATUS_TIMEOUT) + return rc; + + // Abort if the maximum number of retries is reached. + if (nretries++ >= MAXRETRIES) + return rc; + + // Discard any garbage bytes. + dc_iostream_purge (device->iostream, DC_DIRECTION_INPUT); + } + + return DC_STATUS_SUCCESS; +} dc_status_t mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) @@ -236,6 +334,9 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_ memset (device->version, 0, sizeof (device->version)); device->model = 0; device->packetsize = 0; + memset (device->cache, 0, sizeof (device->cache)); + device->available = 0; + device->offset = 0; // Set the serial communication protocol (115200 8E1). status = dc_iostream_configure (device->iostream, 115200, 8, DC_PARITY_EVEN, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE);