diff --git a/src/descriptor.c b/src/descriptor.c index dad63e2..339aecb 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -35,6 +35,7 @@ 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_garmin (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); @@ -242,16 +243,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 | DC_TRANSPORT_BLE, 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 | DC_TRANSPORT_BLE, 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 | DC_TRANSPORT_BLE, NULL}, - {"Mares", "Smart Air", DC_FAMILY_MARES_ICONHD , 0x24, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL}, - {"Mares", "Quad", DC_FAMILY_MARES_ICONHD , 0x29, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, 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}, @@ -518,6 +519,19 @@ static int dc_filter_garmin (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 6708ca6..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,7 +74,9 @@ typedef struct mares_iconhd_device_t { unsigned char version[140]; unsigned int model; unsigned int packetsize; - unsigned int splitcommand; + 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); @@ -147,23 +151,92 @@ 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) { dc_status_t status = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; - unsigned int split_csize; assert (csize >= 2); if (device_is_cancelled (abstract)) return DC_STATUS_CANCELLED; - split_csize = device->splitcommand ? 2 : csize; - // Send the command header to the dive computer. - status = dc_iostream_write (device->iostream, command, split_csize, NULL); + status = mares_iconhd_write (device, command, 2); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the command."); return status; @@ -171,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; @@ -183,9 +256,9 @@ mares_iconhd_transfer (mares_iconhd_device_t *device, return DC_STATUS_PROTOCOL; } - // Send any remaining command payload to the dive computer. - if (csize > split_csize) { - status = dc_iostream_write (device->iostream, command + split_csize, csize - split_csize, NULL); + // Send the command payload to the dive computer. + if (csize > 2) { + status = mares_iconhd_write (device, command + 2, csize - 2); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the command."); return status; @@ -193,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; @@ -201,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; @@ -216,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) @@ -240,15 +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; - - /* - * At least the Mares Matrix needs the command to be split into - * base and argument, with a wait for the ACK byte in between. - * - * See commit 59bfb0f3189b ("Add support for the Mares Matrix") - * for details. - */ - device->splitcommand = 1; + 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); @@ -323,26 +411,6 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_ break; } - if (dc_iostream_get_transport(device->iostream) == DC_TRANSPORT_BLE) { - /* - * Don't ask for larger amounts of data with the BLE - * transport - it will fail. I suspect there is a buffer - * overflow in the BlueLink Pro dongle when bluetooth is - * slower than the serial protocol that the dongle talks to - * the dive computer. - */ - if (device->packetsize > 128) - device->packetsize = 128; - /* - * With BLE, don't wait for ACK before sending the arguments - * to a command. - * - * There is some timing issue that makes that take too long - * and causes the command to be aborted. - */ - device->splitcommand = 0; - } - *out = (dc_device_t *) device; return DC_STATUS_SUCCESS; diff --git a/src/suunto_d9_parser.c b/src/suunto_d9_parser.c index 33c87e8..e3561d2 100644 --- a/src/suunto_d9_parser.c +++ b/src/suunto_d9_parser.c @@ -79,6 +79,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; @@ -111,7 +112,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; @@ -138,6 +139,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; @@ -171,6 +173,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. @@ -191,12 +194,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,11 @@ suunto_d9_parser_cache (suunto_d9_parser_t *parser) } else { parser->gasmix = data[0x28]; } + } else if (parser->model == DX) { + parser->gasmix = data[0x31] & 0x7F; + if ((data[0x31] & 0x80) == 0) { + parser->gasmix += parser->nccr; + } } } parser->config = config; @@ -256,6 +267,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; @@ -279,6 +291,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; @@ -560,7 +573,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,22 +760,31 @@ 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 || (parser->model == D6i && parser->id == ID_D6I_V2)) { + ppo2 = data[offset + 3]; seconds = data[offset + 4]; } else { + ppo2 = 0; seconds = data[offset + 3]; } - idx = suunto_d9_parser_find_gasmix(parser, o2, he); + idx = type & 0x0F; + if ((type & 0x80) == 0) { + idx += parser->nccr; + } if (idx >= parser->ngasmixes) { ERROR (abstract->context, "Invalid gas mix."); return DC_STATUS_DATAFORMAT; } 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: