From 0b2959d8c707888c94f22ad42e0e8acf1257f7d5 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 12 Oct 2018 09:42:26 +0200 Subject: [PATCH 1/9] Fix a potential buffer overflow Check whether there is space available for a complete sample, and not just a single byte! --- src/shearwater_predator_parser.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index dd9992a..f6a7b1d 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -264,7 +264,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) unsigned int offset = headersize; unsigned int length = size - footersize; - while (offset < length) { + while (offset + parser->samplesize <= length) { // Ignore empty samples. if (array_isequal (data + offset, parser->samplesize, 0x00)) { offset += parser->samplesize; @@ -440,7 +440,7 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal unsigned int time = 0; unsigned int offset = parser->headersize; unsigned int length = size - parser->footersize; - while (offset < length) { + while (offset + parser->samplesize <= length) { dc_sample_value_t sample = {0}; // Ignore empty samples. From 864b40cb3d2784a1e54dab67fd78503b80863866 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Fri, 7 Sep 2018 15:12:12 -0700 Subject: [PATCH 2/9] Shearwater: skip deleted dives Without this change a deleted dive on device is treated like the end of the dive list. Signed-off-by: Dirk Hohndel --- src/shearwater_petrel.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/shearwater_petrel.c b/src/shearwater_petrel.c index 2e87a3f..f0eadd0 100644 --- a/src/shearwater_petrel.c +++ b/src/shearwater_petrel.c @@ -263,11 +263,17 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call unsigned int size = dc_buffer_get_size (buffer); // Process the records in the manifest. - unsigned int count = 0; + unsigned int count = 0, deleted = 0; unsigned int offset = 0; while (offset < size) { // Check for a valid dive header. unsigned int header = array_uint16_be (data + offset); + if (header == 0x5A23) { + // this is a deleted dive; keep looking + offset += RECORD_SIZE; + deleted++; + continue; + } if (header != 0xA5C4) break; @@ -281,7 +287,7 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call // Update the progress state. current += 1; - maximum -= RECORD_COUNT - count; + maximum -= RECORD_COUNT - count - deleted; // Append the manifest records to the main buffer. if (!dc_buffer_append (manifests, data, count * RECORD_SIZE)) { @@ -292,7 +298,7 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call } // Stop downloading manifest if there are no more records. - if (count != RECORD_COUNT) + if (count + deleted != RECORD_COUNT) break; } @@ -307,6 +313,11 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call unsigned int offset = 0; while (offset < size) { + // skip deleted dives + if (array_uint16_be(data + offset) == 0x5A23) { + offset += RECORD_SIZE; + continue; + } // Get the address of the dive. unsigned int address = array_uint32_be (data + offset + 20); From 472e73118d4a78f49aeee4eda5989c3c17d6f236 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Sat, 8 Sep 2018 17:17:47 -0700 Subject: [PATCH 3/9] Shearwater: add Teric to list of supported dive computers Signed-off-by: Dirk Hohndel --- src/descriptor.c | 2 ++ src/shearwater_common.h | 1 + src/shearwater_petrel.c | 3 +++ 3 files changed, 6 insertions(+) diff --git a/src/descriptor.c b/src/descriptor.c index 794b825..f57080d 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -294,6 +294,7 @@ static const dc_descriptor_t g_descriptors[] = { {"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}, + {"Shearwater", "Teric", DC_FAMILY_SHEARWATER_PETREL, 8, 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 */ @@ -444,6 +445,7 @@ static int dc_filter_shearwater (dc_transport_t transport, const void *userdata) "Petrel", "Nerd", "Perdix", + "Teric", }; if (transport == DC_TRANSPORT_BLUETOOTH) { diff --git a/src/shearwater_common.h b/src/shearwater_common.h index 767372f..8798853 100644 --- a/src/shearwater_common.h +++ b/src/shearwater_common.h @@ -40,6 +40,7 @@ extern "C" { #define PERDIX 5 #define PERDIXAI 6 #define NERD2 7 +#define TERIC 8 #define NSTEPS 10000 #define STEP(i,n) ((NSTEPS * (i) + (n) / 2) / (n)) diff --git a/src/shearwater_petrel.c b/src/shearwater_petrel.c index f0eadd0..e6b6a59 100644 --- a/src/shearwater_petrel.c +++ b/src/shearwater_petrel.c @@ -229,6 +229,9 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call case 0x0C0D: model = PERDIXAI; break; + case 0x0F0F: + model = TERIC; + break; default: WARNING (abstract->context, "Unknown hardware type %04x.", hardware); } From ae503626ae8d43d57a8a6ffed79e3ff0e4860ffb Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Fri, 7 Sep 2018 16:15:21 -0700 Subject: [PATCH 4/9] Shearwater: detect which logbook format is support The Log Upload RDBI (Read Data by Identifier) response tells us which format the dive computer supports. Shearwater recommends to use the 'Petrel Native Format' for all dive computers which support it, even those pre-Teric models which (depending on firmware) might support both PNF and the older 'Predator-Like Format'. They also recommend to ignore the 0x90000000 format which is very similar to PNF but without the final record and to use the older Predator Like Format in that case. The 0xDD000000 format is never an option by the time you got here, but in the old code (prior to the PNF addition) we would have fallen back to 0xC0000000, so let's do the same here. Any other value is actually an unknown format and should be treated as such. Which format we use is determined by the base address used to download the logbook entries. Signed-off-by: Dirk Hohndel --- src/shearwater_common.h | 7 ++++--- src/shearwater_petrel.c | 39 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/shearwater_common.h b/src/shearwater_common.h index 8798853..35a0d01 100644 --- a/src/shearwater_common.h +++ b/src/shearwater_common.h @@ -30,9 +30,10 @@ extern "C" { #endif /* __cplusplus */ -#define ID_SERIAL 0x8010 -#define ID_FIRMWARE 0x8011 -#define ID_HARDWARE 0x8050 +#define ID_SERIAL 0x8010 +#define ID_FIRMWARE 0x8011 +#define ID_LOGUPLOAD 0x8021 +#define ID_HARDWARE 0x8050 #define PREDATOR 2 #define PETREL 3 diff --git a/src/shearwater_petrel.c b/src/shearwater_petrel.c index e6b6a59..a8b1995 100644 --- a/src/shearwater_petrel.c +++ b/src/shearwater_petrel.c @@ -26,6 +26,7 @@ #include "shearwater_common.h" #include "context-private.h" #include "device-private.h" +#include "platform.h" #include "array.h" #define ISINSTANCE(device) dc_device_isinstance((device), &shearwater_petrel_device_vtable) @@ -33,7 +34,6 @@ #define MANIFEST_ADDR 0xE0000000 #define MANIFEST_SIZE 0x600 -#define DIVE_ADDR 0xC0000000 #define DIVE_SIZE 0xFFFFFF #define RECORD_SIZE 0x20 @@ -243,6 +243,41 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call devinfo.serial = array_uint32_be (serial); device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); + // Read the logbook type + rc = shearwater_common_identifier (&device->base, buffer, ID_LOGUPLOAD); + if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the logbook type."); + dc_buffer_free (buffer); + dc_buffer_free (manifests); + return rc; + } + + if (dc_buffer_get_size (buffer) != 9) { + ERROR (abstract->context, "Unexpected packet size (" DC_PRINTF_SIZE " bytes).", dc_buffer_get_size(buffer)); + dc_buffer_free (buffer); + dc_buffer_free (manifests); + return DC_STATUS_DATAFORMAT; + } + + unsigned int base_addr = array_uint32_be (dc_buffer_get_data (buffer) + 1); + switch (base_addr) { + case 0xDD000000: // Predator - we shouldn't get here, we could give up or we can try 0xC0000000 + case 0xC0000000: // Predator-Like Format (what we used to call the Petrel format) + case 0x90000000: // some firmware versions supported an earlier version of PNF without final record + // use the Predator-Like Format instead + base_addr = 0xC0000000; + break; + case 0x80000000: // new Petrel Native Format with final record + // that's the correct address + break; + default: // unknown format + ERROR (abstract->context, "Unknown logbook format %08x", base_addr); + dc_buffer_free (buffer); + dc_buffer_free (manifests); + return DC_STATUS_DATAFORMAT; + } + + // Read the manifest pages while (1) { // Update the progress state. // Assume the worst case scenario of a full manifest, and adjust the @@ -327,7 +362,7 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call // Download the dive. progress.current = NSTEPS * current; progress.maximum = NSTEPS * maximum; - rc = shearwater_common_download (&device->base, buffer, DIVE_ADDR + address, DIVE_SIZE, 1, &progress); + rc = shearwater_common_download (&device->base, buffer, base_addr + address, DIVE_SIZE, 1, &progress); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to download the dive."); dc_buffer_free (buffer); From 47bb08bbfdda885dcd8e1c36f4e728a7d610eac6 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 23 Nov 2018 15:35:34 +0100 Subject: [PATCH 5/9] Shearwater Petrel Native Format parsing This will allow parsing dives from the Shearwater Teric, but depending on the firmware could also be used on older models. Based on ideas and code from Dirk Hohndel --- src/shearwater_predator_parser.c | 529 ++++++++++++++++++++----------- 1 file changed, 338 insertions(+), 191 deletions(-) diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index f6a7b1d..7d3a420 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -33,9 +33,30 @@ dc_parser_isinstance((parser), &shearwater_predator_parser_vtable) || \ dc_parser_isinstance((parser), &shearwater_petrel_parser_vtable)) +#define LOG_RECORD_DIVE_SAMPLE 0x01 +#define LOG_RECORD_FREEDIVE_SAMPLE 0x02 +#define LOG_RECORD_OPENING_0 0x10 +#define LOG_RECORD_OPENING_1 0x11 +#define LOG_RECORD_OPENING_2 0x12 +#define LOG_RECORD_OPENING_3 0x13 +#define LOG_RECORD_OPENING_4 0x14 +#define LOG_RECORD_OPENING_5 0x15 +#define LOG_RECORD_OPENING_6 0x16 +#define LOG_RECORD_OPENING_7 0x17 +#define LOG_RECORD_CLOSING_0 0x20 +#define LOG_RECORD_CLOSING_1 0x21 +#define LOG_RECORD_CLOSING_2 0x22 +#define LOG_RECORD_CLOSING_3 0x23 +#define LOG_RECORD_CLOSING_4 0x24 +#define LOG_RECORD_CLOSING_5 0x25 +#define LOG_RECORD_CLOSING_6 0x26 +#define LOG_RECORD_CLOSING_7 0x27 +#define LOG_RECORD_FINAL 0xFF + #define SZ_BLOCK 0x80 #define SZ_SAMPLE_PREDATOR 0x10 #define SZ_SAMPLE_PETREL 0x20 +#define SZ_SAMPLE_FREEDIVE 0x08 #define GASSWITCH 0x01 #define PPO2_EXTERNAL 0x02 @@ -47,10 +68,13 @@ #define IMPERIAL 1 #define NGASMIXES 10 +#define NRECORDS 7 #define PREDATOR 2 #define PETREL 3 +#define UNDEFINED 0xFFFFFFFF + typedef struct shearwater_predator_parser_t shearwater_predator_parser_t; struct shearwater_predator_parser_t { @@ -60,15 +84,22 @@ struct shearwater_predator_parser_t { unsigned int samplesize; // Cached fields. unsigned int cached; + unsigned int pnf; unsigned int logversion; unsigned int headersize; unsigned int footersize; + unsigned int opening[NRECORDS]; + unsigned int closing[NRECORDS]; + unsigned int final; unsigned int ngasmixes; unsigned int oxygen[NGASMIXES]; unsigned int helium[NGASMIXES]; unsigned int calibrated; double calibration[3]; dc_divemode_t mode; + unsigned int units; + unsigned int atmospheric; + unsigned int density; }; static dc_status_t shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); @@ -76,6 +107,8 @@ static dc_status_t shearwater_predator_parser_get_datetime (dc_parser_t *abstrac static dc_status_t shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); static dc_status_t shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); +static dc_status_t shearwater_predator_parser_cache (shearwater_predator_parser_t *parser); + static const dc_parser_vtable_t shearwater_predator_parser_vtable = { sizeof(shearwater_predator_parser_t), DC_FAMILY_SHEARWATER_PREDATOR, @@ -141,9 +174,15 @@ shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsig parser->petrel = petrel; parser->samplesize = samplesize; parser->cached = 0; + parser->pnf = 0; parser->logversion = 0; parser->headersize = 0; parser->footersize = 0; + for (unsigned int i = 0; i < NRECORDS; ++i) { + parser->opening[i] = UNDEFINED; + parser->closing[i] = UNDEFINED; + } + parser->final = UNDEFINED; parser->ngasmixes = 0; for (unsigned int i = 0; i < NGASMIXES; ++i) { parser->oxygen[i] = 0; @@ -154,6 +193,9 @@ shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsig parser->calibration[i] = 0.0; } parser->mode = DC_DIVEMODE_OC; + parser->units = METRIC; + parser->density = 1025; + parser->atmospheric = ATM / (BAR / 1000); *out = (dc_parser_t *) parser; @@ -182,9 +224,15 @@ shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char // Reset the cache. parser->cached = 0; + parser->pnf = 0; parser->logversion = 0; parser->headersize = 0; parser->footersize = 0; + for (unsigned int i = 0; i < NRECORDS; ++i) { + parser->opening[i] = UNDEFINED; + parser->closing[i] = UNDEFINED; + } + parser->final = UNDEFINED; parser->ngasmixes = 0; for (unsigned int i = 0; i < NGASMIXES; ++i) { parser->oxygen[i] = 0; @@ -195,6 +243,9 @@ shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char parser->calibration[i] = 0.0; } parser->mode = DC_DIVEMODE_OC; + parser->units = METRIC; + parser->density = 1025; + parser->atmospheric = ATM / (BAR / 1000); return DC_STATUS_SUCCESS; } @@ -203,13 +254,15 @@ shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char static dc_status_t shearwater_predator_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) { + shearwater_predator_parser_t *parser = (shearwater_predator_parser_t *) abstract; const unsigned char *data = abstract->data; - unsigned int size = abstract->size; - if (size < 2 * SZ_BLOCK) - return DC_STATUS_DATAFORMAT; + // Cache the parser data. + dc_status_t rc = shearwater_predator_parser_cache (parser); + if (rc != DC_STATUS_SUCCESS) + return rc; - unsigned int ticks = array_uint32_be (data + 12); + unsigned int ticks = array_uint32_be (data + parser->opening[0] + 12); if (!dc_datetime_gmtime (datetime, ticks)) return DC_STATUS_DATAFORMAT; @@ -231,26 +284,48 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) return DC_STATUS_SUCCESS; } - unsigned int headersize = SZ_BLOCK; - unsigned int footersize = SZ_BLOCK; - if (size < headersize + footersize) { + // Verify the minimum length. + if (size < 2) { ERROR (abstract->context, "Invalid data length."); return DC_STATUS_DATAFORMAT; } - // Log versions before 6 weren't reliably stored in the data, but - // 6 is also the oldest version that we assume in our code - unsigned int logversion = 6; - if (data[127] > 6) - logversion = data[127]; - - // Adjust the footersize for the final block. - if (parser->petrel || array_uint16_be (data + size - footersize) == 0xFFFD) { - footersize += SZ_BLOCK; + // The Petrel Native Format (PNF) is very similar to the legacy + // Predator and Predator-like format. The samples are simply offset + // by one (so we can use pnf as the offset). For the header and + // footer data, it's more complicated because of the new 32 byte + // block structure. + unsigned int pnf = parser->petrel ? array_uint16_be (data) != 0xFFFF : 0; + unsigned int headersize = 0; + unsigned int footersize = 0; + if (!pnf) { + // Opening and closing blocks. + headersize = SZ_BLOCK; + footersize = SZ_BLOCK; if (size < headersize + footersize) { ERROR (abstract->context, "Invalid data length."); return DC_STATUS_DATAFORMAT; } + + // Adjust the footersize for the final block. + if (parser->petrel || array_uint16_be (data + size - footersize) == 0xFFFD) { + footersize += SZ_BLOCK; + if (size < headersize + footersize) { + ERROR (abstract->context, "Invalid data length."); + return DC_STATUS_DATAFORMAT; + } + + parser->final = size - SZ_BLOCK; + } + + // The Predator and Predator-like format have just one large 128 + // byte opening and closing block. To minimize the differences + // with the PNF format, all record offsets are assigned the same + // value here. + for (unsigned int i = 0; i < NRECORDS; ++i) { + parser->opening[i] = 0; + parser->closing[i] = size - footersize; + } } // Default dive mode. @@ -271,46 +346,76 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) continue; } - // Status flags. - unsigned int status = data[offset + 11]; - if ((status & OC) == 0) { - mode = DC_DIVEMODE_CCR; - } + // Get the record type. + unsigned int type = pnf ? data[offset] : LOG_RECORD_DIVE_SAMPLE; - // Gaschange. - unsigned int o2 = data[offset + 7]; - unsigned int he = data[offset + 8]; - if (o2 != o2_previous || he != he_previous) { - // Find the gasmix in the list. - unsigned int idx = 0; - while (idx < ngasmixes) { - if (o2 == oxygen[idx] && he == helium[idx]) - break; - idx++; + if (type == LOG_RECORD_DIVE_SAMPLE) { + // Status flags. + unsigned int status = data[offset + 11 + pnf]; + if ((status & OC) == 0) { + mode = DC_DIVEMODE_CCR; } - // Add it to list if not found. - if (idx >= ngasmixes) { - if (idx >= NGASMIXES) { - ERROR (abstract->context, "Maximum number of gas mixes reached."); - return DC_STATUS_NOMEMORY; + // Gaschange. + unsigned int o2 = data[offset + 7 + pnf]; + unsigned int he = data[offset + 8 + pnf]; + if (o2 != o2_previous || he != he_previous) { + // Find the gasmix in the list. + unsigned int idx = 0; + while (idx < ngasmixes) { + if (o2 == oxygen[idx] && he == helium[idx]) + break; + idx++; } - oxygen[idx] = o2; - helium[idx] = he; - ngasmixes = idx + 1; - } - o2_previous = o2; - he_previous = he; + // Add it to list if not found. + if (idx >= ngasmixes) { + if (idx >= NGASMIXES) { + ERROR (abstract->context, "Maximum number of gas mixes reached."); + return DC_STATUS_NOMEMORY; + } + oxygen[idx] = o2; + helium[idx] = he; + ngasmixes = idx + 1; + } + + o2_previous = o2; + he_previous = he; + } + } else if (type == LOG_RECORD_FREEDIVE_SAMPLE) { + // Freedive record + mode = DC_DIVEMODE_FREEDIVE; + } else if (type >= LOG_RECORD_OPENING_0 && type <= LOG_RECORD_OPENING_7) { + // Opening record + parser->opening[type - LOG_RECORD_OPENING_0] = offset; + } else if (type >= LOG_RECORD_CLOSING_0 && type <= LOG_RECORD_CLOSING_7) { + // Closing record + parser->closing[type - LOG_RECORD_CLOSING_0] = offset; + } else if (type == LOG_RECORD_FINAL) { + // Final record + parser->final = offset; } offset += parser->samplesize; } + // Verify the required opening/closing records. + for (unsigned int i = 0; i < NRECORDS - 2; ++i) { + if (parser->opening[i] == UNDEFINED || parser->closing[i] == UNDEFINED) { + ERROR (abstract->context, "Opening or closing record %u not found.", i); + return DC_STATUS_DATAFORMAT; + } + } + + // Log versions before 6 weren't reliably stored in the data, but + // 6 is also the oldest version that we assume in our code + unsigned int logversion = data[parser->opening[4] + (pnf ? 16 : 127)]; + // Cache sensor calibration for later use unsigned int nsensors = 0, ndefaults = 0; + unsigned int base = parser->opening[3] + (pnf ? 6 : 86); for (size_t i = 0; i < 3; ++i) { - unsigned int calibration = array_uint16_be(data + 87 + i * 2); + unsigned int calibration = array_uint16_be(data + base + 1 + i * 2); parser->calibration[i] = calibration / 100000.0; if (parser->model == PREDATOR) { // The Predator expects the mV output of the cells to be @@ -319,7 +424,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) // sensors lines up and matches the average. parser->calibration[i] *= 2.2; } - if (data[86] & (1 << i)) { + if (data[base] & (1 << i)) { if (calibration == 2100) { ndefaults++; } @@ -335,10 +440,11 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) WARNING (abstract->context, "Disabled all O2 sensors due to a default calibration value."); parser->calibrated = 0; } else { - parser->calibrated = data[86]; + parser->calibrated = data[base]; } // Cache the data for later use. + parser->pnf = pnf; parser->logversion = logversion; parser->headersize = headersize; parser->footersize = footersize; @@ -348,6 +454,9 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) parser->helium[i] = helium[i]; } parser->mode = mode; + parser->units = data[parser->opening[0] + 8]; + parser->atmospheric = array_uint16_be (data + parser->opening[1] + (parser->pnf ? 16 : 47)); + parser->density = array_uint16_be (data + parser->opening[3] + (parser->pnf ? 3 : 83)); parser->cached = 1; return DC_STATUS_SUCCESS; @@ -359,33 +468,30 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ shearwater_predator_parser_t *parser = (shearwater_predator_parser_t *) abstract; const unsigned char *data = abstract->data; - unsigned int size = abstract->size; // Cache the parser data. dc_status_t rc = shearwater_predator_parser_cache (parser); if (rc != DC_STATUS_SUCCESS) return rc; - // Get the offset to the footer record. - unsigned int footer = size - parser->footersize; - - // Get the unit system. - unsigned int units = data[8]; - dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_salinity_t *water = (dc_salinity_t *) value; - unsigned int density = 0; if (value) { switch (type) { case DC_FIELD_DIVETIME: - *((unsigned int *) value) = array_uint16_be (data + footer + 6) * 60; + if (parser->pnf) + *((unsigned int *) value) = array_uint24_be (data + parser->closing[0] + 6); + else + *((unsigned int *) value) = array_uint16_be (data + parser->closing[0] + 6) * 60; break; case DC_FIELD_MAXDEPTH: - if (units == IMPERIAL) - *((double *) value) = array_uint16_be (data + footer + 4) * FEET; + if (parser->units == IMPERIAL) + *((double *) value) = array_uint16_be (data + parser->closing[0] + 4) * FEET; else - *((double *) value) = array_uint16_be (data + footer + 4); + *((double *) value) = array_uint16_be (data + parser->closing[0] + 4); + if (parser->pnf) + *((double *)value) /= 10.0; break; case DC_FIELD_GASMIX_COUNT: *((unsigned int *) value) = parser->ngasmixes; @@ -396,15 +502,14 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; break; case DC_FIELD_SALINITY: - density = array_uint16_be (data + 83); - if (density == 1000) + if (parser->density == 1000) water->type = DC_WATER_FRESH; else water->type = DC_WATER_SALT; - water->density = density; + water->density = parser->density; break; case DC_FIELD_ATMOSPHERIC: - *((double *) value) = array_uint16_be (data + 47) / 1000.0; + *((double *) value) = parser->atmospheric / 1000.0; break; case DC_FIELD_DIVEMODE: *((dc_divemode_t *) value) = parser->mode; @@ -431,13 +536,22 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal if (rc != DC_STATUS_SUCCESS) return rc; - // Get the unit system. - unsigned int units = data[8]; - // Previous gas mix. unsigned int o2_previous = 0, he_previous = 0; + // Sample interval. unsigned int time = 0; + unsigned int interval = 10; + if (parser->pnf && parser->logversion >= 9) { + interval = array_uint16_be (data + parser->opening[5] + 23); + if (interval % 1000 != 0) { + ERROR (abstract->context, "Unsupported sample interval (%u ms).", interval); + return DC_STATUS_DATAFORMAT; + } + interval /= 1000; + } + + unsigned int pnf = parser->pnf; unsigned int offset = parser->headersize; unsigned int length = size - parser->footersize; while (offset + parser->samplesize <= length) { @@ -449,142 +563,175 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal continue; } - // Time (seconds). - time += 10; - sample.time = time; - if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + // Get the record type. + unsigned int type = pnf ? data[offset] : LOG_RECORD_DIVE_SAMPLE; - // Depth (1/10 m or ft). - unsigned int depth = array_uint16_be (data + offset); - if (units == IMPERIAL) - sample.depth = depth * FEET / 10.0; - else - sample.depth = depth / 10.0; - if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + if (type == LOG_RECORD_DIVE_SAMPLE) { + // Time (seconds). + time += interval; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); - // Temperature (°C or °F). - int temperature = (signed char) data[offset + 13]; - if (temperature < 0) { - // Fix negative temperatures. - temperature += 102; - if (temperature > 0) { - temperature = 0; - } - } - if (units == IMPERIAL) - sample.temperature = (temperature - 32.0) * (5.0 / 9.0); - else - sample.temperature = temperature; - if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); + // Depth (1/10 m or ft). + unsigned int depth = array_uint16_be (data + pnf + offset); + if (parser->units == IMPERIAL) + sample.depth = depth * FEET / 10.0; + else + sample.depth = depth / 10.0; + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); - // Status flags. - unsigned int status = data[offset + 11]; - - if ((status & OC) == 0) { - // PPO2 - if ((status & PPO2_EXTERNAL) == 0) { -#ifdef SENSOR_AVERAGE - sample.ppo2 = data[offset + 6] / 100.0; - if (callback) callback (DC_SAMPLE_PPO2, sample, userdata); -#else - sample.ppo2 = data[offset + 12] * parser->calibration[0]; - if (callback && (parser->calibrated & 0x01)) callback (DC_SAMPLE_PPO2, sample, userdata); - - sample.ppo2 = data[offset + 14] * parser->calibration[1]; - if (callback && (parser->calibrated & 0x02)) callback (DC_SAMPLE_PPO2, sample, userdata); - - sample.ppo2 = data[offset + 15] * parser->calibration[2]; - if (callback && (parser->calibrated & 0x04)) callback (DC_SAMPLE_PPO2, sample, userdata); -#endif - } - - // Setpoint - if (parser->petrel) { - sample.setpoint = data[offset + 18] / 100.0; - } else { - if (status & SETPOINT_HIGH) { - sample.setpoint = data[18] / 100.0; - } else { - sample.setpoint = data[17] / 100.0; + // Temperature (°C or °F). + int temperature = (signed char) data[offset + pnf + 13]; + if (temperature < 0) { + // Fix negative temperatures. + temperature += 102; + if (temperature > 0) { + temperature = 0; } } - if (callback) callback (DC_SAMPLE_SETPOINT, sample, userdata); - } - - // CNS - if (parser->petrel) { - sample.cns = data[offset + 22] / 100.0; - if (callback) callback (DC_SAMPLE_CNS, sample, userdata); - } - - // Gaschange. - unsigned int o2 = data[offset + 7]; - unsigned int he = data[offset + 8]; - if (o2 != o2_previous || he != he_previous) { - unsigned int idx = shearwater_predator_find_gasmix (parser, o2, he); - 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); - o2_previous = o2; - he_previous = he; - } - - // Deco stop / NDL. - unsigned int decostop = array_uint16_be (data + offset + 2); - if (decostop) { - sample.deco.type = DC_DECO_DECOSTOP; - if (units == IMPERIAL) - sample.deco.depth = decostop * FEET; + if (parser->units == IMPERIAL) + sample.temperature = (temperature - 32.0) * (5.0 / 9.0); else - sample.deco.depth = decostop; - } else { - sample.deco.type = DC_DECO_NDL; - sample.deco.depth = 0.0; - } - sample.deco.time = data[offset + 9] * 60; - if (callback) callback (DC_SAMPLE_DECO, sample, userdata); + sample.temperature = temperature; + if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); - // for logversion 7 and newer (introduced for Perdix AI) - // detect tank pressure - if (parser->logversion >= 7) { - // Tank pressure - // Values above 0xFFF0 are special codes: - // 0xFFFF AI is off - // 0xFFFE No comms for 90 seconds+ - // 0xFFFD No comms for 30 seconds - // 0xFFFC Transmitter not paired - // For regular values, the top 4 bits contain the battery - // level (0=normal, 1=critical, 2=warning), and the lower 12 - // bits the tank pressure in units of 2 psi. - unsigned int pressure = array_uint16_be (data + offset + 27); - if (pressure < 0xFFF0) { - pressure &= 0x0FFF; - sample.pressure.tank = 0; - sample.pressure.value = pressure * 2 * PSI / BAR; - if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata); - } - pressure = array_uint16_be (data + offset + 19); - if (pressure < 0xFFF0) { - pressure &= 0x0FFF; - sample.pressure.tank = 1; - sample.pressure.value = pressure * 2 * PSI / BAR; - if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata); + // Status flags. + unsigned int status = data[offset + pnf + 11]; + + if ((status & OC) == 0) { + // PPO2 + if ((status & PPO2_EXTERNAL) == 0) { +#ifdef SENSOR_AVERAGE + sample.ppo2 = data[offset + pnf + 6] / 100.0; + if (callback) callback (DC_SAMPLE_PPO2, sample, userdata); +#else + sample.ppo2 = data[offset + pnf + 12] * parser->calibration[0]; + if (callback && (parser->calibrated & 0x01)) callback (DC_SAMPLE_PPO2, sample, userdata); + + sample.ppo2 = data[offset + pnf + 14] * parser->calibration[1]; + if (callback && (parser->calibrated & 0x02)) callback (DC_SAMPLE_PPO2, sample, userdata); + + sample.ppo2 = data[offset + pnf + 15] * parser->calibration[2]; + if (callback && (parser->calibrated & 0x04)) callback (DC_SAMPLE_PPO2, sample, userdata); +#endif + } + + // Setpoint + if (parser->petrel) { + sample.setpoint = data[offset + pnf + 18] / 100.0; + } else { + // this will only ever be called for the actual Predator, so no adjustment needed for PNF + if (status & SETPOINT_HIGH) { + sample.setpoint = data[18] / 100.0; + } else { + sample.setpoint = data[17] / 100.0; + } + } + if (callback) callback (DC_SAMPLE_SETPOINT, sample, userdata); } - // Gas time remaining in minutes - // Values above 0xF0 are special codes: - // 0xFF Not paired - // 0xFE No communication - // 0xFD Not available in current mode - // 0xFC Not available because of DECO - // 0xFB Tank size or max pressure haven’t been set up - if (data[offset + 21] < 0xF0) { - sample.rbt = data[offset + 21]; - if (callback) callback (DC_SAMPLE_RBT, sample, userdata); + // CNS + if (parser->petrel) { + sample.cns = data[offset + pnf + 22] / 100.0; + if (callback) callback (DC_SAMPLE_CNS, sample, userdata); + } + + // Gaschange. + unsigned int o2 = data[offset + pnf + 7]; + unsigned int he = data[offset + pnf + 8]; + if (o2 != o2_previous || he != he_previous) { + unsigned int idx = shearwater_predator_find_gasmix (parser, o2, he); + 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); + o2_previous = o2; + he_previous = he; + } + + // Deco stop / NDL. + unsigned int decostop = array_uint16_be (data + offset + pnf + 2); + if (decostop) { + sample.deco.type = DC_DECO_DECOSTOP; + if (parser->units == IMPERIAL) + sample.deco.depth = decostop * FEET; + else + sample.deco.depth = decostop; + } else { + sample.deco.type = DC_DECO_NDL; + sample.deco.depth = 0.0; + } + sample.deco.time = data[offset + pnf + 9] * 60; + if (callback) callback (DC_SAMPLE_DECO, sample, userdata); + + // for logversion 7 and newer (introduced for Perdix AI) + // detect tank pressure + if (parser->logversion >= 7) { + // Tank pressure + // Values above 0xFFF0 are special codes: + // 0xFFFF AI is off + // 0xFFFE No comms for 90 seconds+ + // 0xFFFD No comms for 30 seconds + // 0xFFFC Transmitter not paired + // For regular values, the top 4 bits contain the battery + // level (0=normal, 1=critical, 2=warning), and the lower 12 + // bits the tank pressure in units of 2 psi. + unsigned int pressure = array_uint16_be (data + offset + pnf + 27); + if (pressure < 0xFFF0) { + pressure &= 0x0FFF; + sample.pressure.tank = 0; + sample.pressure.value = pressure * 2 * PSI / BAR; + if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata); + } + pressure = array_uint16_be (data + offset + pnf + 19); + if (pressure < 0xFFF0) { + pressure &= 0x0FFF; + sample.pressure.tank = 1; + sample.pressure.value = pressure * 2 * PSI / BAR; + if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata); + } + + // Gas time remaining in minutes + // Values above 0xF0 are special codes: + // 0xFF Not paired + // 0xFE No communication + // 0xFD Not available in current mode + // 0xFC Not available because of DECO + // 0xFB Tank size or max pressure haven’t been set up + if (data[offset + pnf + 21] < 0xF0) { + sample.rbt = data[offset + pnf + 21]; + if (callback) callback (DC_SAMPLE_RBT, sample, userdata); + } + } + } else if (type == LOG_RECORD_FREEDIVE_SAMPLE) { + // A freedive record is actually 4 samples, each 8-bytes, + // packed into a standard 32-byte sized record. At the end + // of a dive, unused partial records will be 0 padded. + for (unsigned int i = 0; i < 4; ++i) { + unsigned int idx = offset + i * SZ_SAMPLE_FREEDIVE; + + // Ignore empty samples. + if (array_isequal (data + idx, SZ_SAMPLE_FREEDIVE, 0x00)) { + break; + } + + // Time (seconds). + time += interval; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + + // Depth (absolute pressure in millibar) + unsigned int depth = array_uint16_be (data + idx + 1); + sample.depth = (depth - parser->atmospheric) * (BAR / 1000.0) / (parser->density * GRAVITY); + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + + // Temperature (1/10 °C). + int temperature = (signed short) array_uint16_be (data + idx + 3); + sample.temperature = temperature / 10.0; + if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); } } From 4c93e14b0edd0df03219755fadb69cecbe31c899 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 7 Jan 2019 19:13:04 +0100 Subject: [PATCH 6/9] Add support for the Ratio iDive Color series The new iDive Color series uses the same communication protocol and data format as the previous models. --- src/descriptor.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/descriptor.c b/src/descriptor.c index f57080d..dc7ec4c 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -321,6 +321,9 @@ static const dc_descriptor_t g_descriptors[] = { {"Ratio", "iDive Easy", DC_FAMILY_DIVESYSTEM_IDIVE, 0x42, DC_TRANSPORT_SERIAL, NULL}, {"Ratio", "iDive Deep", DC_FAMILY_DIVESYSTEM_IDIVE, 0x44, DC_TRANSPORT_SERIAL, NULL}, {"Ratio", "iDive Tech+", DC_FAMILY_DIVESYSTEM_IDIVE, 0x45, DC_TRANSPORT_SERIAL, NULL}, + {"Ratio", "iDive Color Easy", DC_FAMILY_DIVESYSTEM_IDIVE, 0x52, DC_TRANSPORT_SERIAL, NULL}, + {"Ratio", "iDive Color Deep", DC_FAMILY_DIVESYSTEM_IDIVE, 0x54, DC_TRANSPORT_SERIAL, NULL}, + {"Ratio", "iDive Color Tech", DC_FAMILY_DIVESYSTEM_IDIVE, 0x55, DC_TRANSPORT_SERIAL, NULL}, {"Seac", "Jack", DC_FAMILY_DIVESYSTEM_IDIVE, 0x1000, DC_TRANSPORT_SERIAL, NULL}, {"Seac", "Guru", DC_FAMILY_DIVESYSTEM_IDIVE, 0x1002, DC_TRANSPORT_SERIAL, NULL}, /* Cochran Commander */ From da2582237f7ae35c783690cbb3848022545ab97b Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sat, 3 Nov 2018 21:17:38 +0100 Subject: [PATCH 7/9] Add an extra parameter for the initial CRC value This allows to calculate different variants of the CRC-CCITT algorithm with a single function. --- src/checksum.c | 4 ++-- src/checksum.h | 2 +- src/cressi_leonardo.c | 6 +++--- src/divesystem_idive.c | 4 ++-- src/reefnet_sensuspro.c | 4 ++-- src/reefnet_sensusultra.c | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/checksum.c b/src/checksum.c index a8b332e..816c58d 100644 --- a/src/checksum.c +++ b/src/checksum.c @@ -69,7 +69,7 @@ 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) +checksum_crc16_ccitt (const unsigned char data[], unsigned int size, unsigned short init) { static const unsigned short crc_ccitt_table[] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, @@ -106,7 +106,7 @@ checksum_crc_ccitt_uint16 (const unsigned char data[], unsigned int size) 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 }; - unsigned short crc = 0xffff; + unsigned short crc = init; for (unsigned int i = 0; i < size; ++i) crc = (crc << 8) ^ crc_ccitt_table[(crc >> 8) ^ data[i]]; diff --git a/src/checksum.h b/src/checksum.h index a6ead09..9a53aa6 100644 --- a/src/checksum.h +++ b/src/checksum.h @@ -39,7 +39,7 @@ unsigned char checksum_xor_uint8 (const unsigned char data[], unsigned int size, unsigned char init); unsigned short -checksum_crc_ccitt_uint16 (const unsigned char data[], unsigned int size); +checksum_crc16_ccitt (const unsigned char data[], unsigned int size, unsigned short init); unsigned int checksum_crc32 (const unsigned char data[], unsigned int size); diff --git a/src/cressi_leonardo.c b/src/cressi_leonardo.c index 5359f66..244b887 100644 --- a/src/cressi_leonardo.c +++ b/src/cressi_leonardo.c @@ -84,7 +84,7 @@ cressi_leonardo_make_ascii (const unsigned char raw[], unsigned int rsize, unsig array_convert_bin2hex (raw, rsize, ascii + 1, 2 * rsize); // Checksum - unsigned short crc = checksum_crc_ccitt_uint16 (ascii + 1, 2 * rsize); + unsigned short crc = checksum_crc16_ccitt (ascii + 1, 2 * rsize, 0xffff); unsigned char checksum[] = { (crc >> 8) & 0xFF, // High (crc ) & 0xFF}; // Low @@ -129,7 +129,7 @@ cressi_leonardo_packet (cressi_leonardo_device_t *device, const unsigned char co // Verify the checksum of the packet. unsigned short crc = array_uint16_be (checksum); - unsigned short ccrc = checksum_crc_ccitt_uint16 (answer + 1, asize - 6); + unsigned short ccrc = checksum_crc16_ccitt (answer + 1, asize - 6, 0xffff); if (crc != ccrc) { ERROR (abstract->context, "Unexpected answer checksum."); return DC_STATUS_PROTOCOL; @@ -372,7 +372,7 @@ cressi_leonardo_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) // Verify the checksum. unsigned int csum1 = array_uint16_be (checksum); - unsigned int csum2 = checksum_crc_ccitt_uint16 (data, SZ_MEMORY); + unsigned int csum2 = checksum_crc16_ccitt (data, SZ_MEMORY, 0xffff); if (csum1 != csum2) { ERROR (abstract->context, "Unexpected answer bytes."); return DC_STATUS_PROTOCOL; diff --git a/src/divesystem_idive.c b/src/divesystem_idive.c index 790e71a..f827b52 100644 --- a/src/divesystem_idive.c +++ b/src/divesystem_idive.c @@ -196,7 +196,7 @@ divesystem_idive_send (divesystem_idive_device_t *device, const unsigned char co packet[0] = START; packet[1] = csize; memcpy(packet + 2, command, csize); - crc = checksum_crc_ccitt_uint16 (packet, csize + 2); + crc = checksum_crc16_ccitt (packet, csize + 2, 0xffff); packet[csize + 2] = (crc >> 8) & 0xFF; packet[csize + 3] = (crc ) & 0xFF; @@ -257,7 +257,7 @@ divesystem_idive_receive (divesystem_idive_device_t *device, unsigned char answe // Verify the checksum. unsigned short crc = array_uint16_be (packet + len + 2); - unsigned short ccrc = checksum_crc_ccitt_uint16 (packet, len + 2); + unsigned short ccrc = checksum_crc16_ccitt (packet, len + 2, 0xffff); if (crc != ccrc) { ERROR (abstract->context, "Unexpected packet checksum."); return DC_STATUS_PROTOCOL; diff --git a/src/reefnet_sensuspro.c b/src/reefnet_sensuspro.c index b33fe9a..835fbcb 100644 --- a/src/reefnet_sensuspro.c +++ b/src/reefnet_sensuspro.c @@ -177,7 +177,7 @@ reefnet_sensuspro_handshake (reefnet_sensuspro_device_t *device) // Verify the checksum of the handshake packet. unsigned short crc = array_uint16_le (handshake + SZ_HANDSHAKE); - unsigned short ccrc = checksum_crc_ccitt_uint16 (handshake, SZ_HANDSHAKE); + unsigned short ccrc = checksum_crc16_ccitt (handshake, SZ_HANDSHAKE, 0xffff); if (crc != ccrc) { ERROR (abstract->context, "Unexpected answer checksum."); return DC_STATUS_PROTOCOL; @@ -280,7 +280,7 @@ reefnet_sensuspro_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) } unsigned short crc = array_uint16_le (answer + SZ_MEMORY); - unsigned short ccrc = checksum_crc_ccitt_uint16 (answer, SZ_MEMORY); + unsigned short ccrc = checksum_crc16_ccitt (answer, SZ_MEMORY, 0xffff); if (crc != ccrc) { ERROR (abstract->context, "Unexpected answer checksum."); return DC_STATUS_PROTOCOL; diff --git a/src/reefnet_sensusultra.c b/src/reefnet_sensusultra.c index 22ffe81..6b059d6 100644 --- a/src/reefnet_sensusultra.c +++ b/src/reefnet_sensusultra.c @@ -224,7 +224,7 @@ reefnet_sensusultra_packet (reefnet_sensusultra_device_t *device, unsigned char // Verify the checksum of the packet. unsigned short crc = array_uint16_le (data + size - 2); - unsigned short ccrc = checksum_crc_ccitt_uint16 (data + header, size - header - 2); + unsigned short ccrc = checksum_crc16_ccitt (data + header, size - header - 2, 0xffff); if (crc != ccrc) { ERROR (abstract->context, "Unexpected answer checksum."); return DC_STATUS_PROTOCOL; @@ -477,7 +477,7 @@ reefnet_sensusultra_device_write_user (dc_device_t *abstract, const unsigned cha } // Send the checksum to the device. - unsigned short crc = checksum_crc_ccitt_uint16 (data, SZ_USER); + unsigned short crc = checksum_crc16_ccitt (data, SZ_USER, 0xffff); rc = reefnet_sensusultra_send_ushort (device, crc); if (rc != DC_STATUS_SUCCESS) return rc; From e363e5b1fd459fe8d79854ef63f3f561a29a7401 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sat, 3 Nov 2018 21:23:26 +0100 Subject: [PATCH 8/9] Add support for the Cressi Goa and Cartesio --- examples/common.c | 1 + include/libdivecomputer/common.h | 1 + msvc/libdivecomputer.vcproj | 12 + src/Makefile.am | 1 + src/cressi_goa.c | 498 +++++++++++++++++++++++++++++++ src/cressi_goa.h | 43 +++ src/cressi_goa_parser.c | 209 +++++++++++++ src/descriptor.c | 3 + src/device.c | 4 + src/parser.c | 4 + 10 files changed, 776 insertions(+) create mode 100644 src/cressi_goa.c create mode 100644 src/cressi_goa.h create mode 100644 src/cressi_goa_parser.c diff --git a/examples/common.c b/examples/common.c index b97343f..d8c7262 100644 --- a/examples/common.c +++ b/examples/common.c @@ -80,6 +80,7 @@ static const backend_table_t g_backends[] = { {"ostc3", DC_FAMILY_HW_OSTC3, 0x0A}, {"edy", DC_FAMILY_CRESSI_EDY, 0x08}, {"leonardo", DC_FAMILY_CRESSI_LEONARDO, 1}, + {"goa", DC_FAMILY_CRESSI_GOA, 2}, {"n2ition3", DC_FAMILY_ZEAGLE_N2ITION3, 0}, {"cobalt", DC_FAMILY_ATOMICS_COBALT, 0}, {"predator", DC_FAMILY_SHEARWATER_PREDATOR, 2}, diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h index 1058b01..7f94f14 100644 --- a/include/libdivecomputer/common.h +++ b/include/libdivecomputer/common.h @@ -86,6 +86,7 @@ typedef enum dc_family_t { /* Cressi */ DC_FAMILY_CRESSI_EDY = (7 << 16), DC_FAMILY_CRESSI_LEONARDO, + DC_FAMILY_CRESSI_GOA, /* Zeagle */ DC_FAMILY_ZEAGLE_N2ITION3 = (8 << 16), /* Atomic Aquatics */ diff --git a/msvc/libdivecomputer.vcproj b/msvc/libdivecomputer.vcproj index 1a88ff8..507140e 100644 --- a/msvc/libdivecomputer.vcproj +++ b/msvc/libdivecomputer.vcproj @@ -238,6 +238,14 @@ RelativePath="..\src\cressi_edy_parser.c" > + + + + @@ -584,6 +592,10 @@ RelativePath="..\src\cressi_edy.h" > + + diff --git a/src/Makefile.am b/src/Makefile.am index 6afa24e..4d19b48 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -55,6 +55,7 @@ libdivecomputer_la_SOURCES = \ aes.h aes.c \ cressi_edy.h cressi_edy.c cressi_edy_parser.c \ cressi_leonardo.h cressi_leonardo.c cressi_leonardo_parser.c \ + cressi_goa.h cressi_goa.c cressi_goa_parser.c \ zeagle_n2ition3.h zeagle_n2ition3.c \ atomics_cobalt.h atomics_cobalt.c atomics_cobalt_parser.c \ shearwater_common.h shearwater_common.c \ diff --git a/src/cressi_goa.c b/src/cressi_goa.c new file mode 100644 index 0000000..f6df023 --- /dev/null +++ b/src/cressi_goa.c @@ -0,0 +1,498 @@ +/* + * libdivecomputer + * + * Copyright (C) 2018 Jef Driesen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include // memcpy, memcmp +#include // malloc, free +#include // assert + +#include "cressi_goa.h" +#include "context-private.h" +#include "device-private.h" +#include "checksum.h" +#include "array.h" +#include "platform.h" + +#define ISINSTANCE(device) dc_device_isinstance((device), &cressi_goa_device_vtable) + +#define CMD_VERSION 0x00 +#define CMD_LOGBOOK 0x21 +#define CMD_DIVE 0x22 + +#define HEADER 0xAA +#define TRAILER 0x55 +#define END 0x04 +#define ACK 0x06 + +#define SZ_DATA 512 +#define SZ_PACKET 10 +#define SZ_HEADER 23 + +#define FP_OFFSET 0x11 +#define FP_SIZE 6 + +#define NSTEPS 1000 +#define STEP(i,n) (NSTEPS * (i) / (n)) + +typedef struct cressi_goa_device_t { + dc_device_t base; + dc_iostream_t *iostream; + unsigned char fingerprint[FP_SIZE]; +} cressi_goa_device_t; + +static dc_status_t cressi_goa_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); +static dc_status_t cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); + +static const dc_device_vtable_t cressi_goa_device_vtable = { + sizeof(cressi_goa_device_t), + DC_FAMILY_CRESSI_GOA, + cressi_goa_device_set_fingerprint, /* set_fingerprint */ + NULL, /* read */ + NULL, /* write */ + NULL, /* dump */ + cressi_goa_device_foreach, /* foreach */ + NULL, /* timesync */ + NULL /* close */ +}; + +static dc_status_t +cressi_goa_device_send (cressi_goa_device_t *device, unsigned char cmd, const unsigned char data[], unsigned int size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + + if (size > SZ_PACKET) { + ERROR (abstract->context, "Unexpected payload size (%u).", size); + return DC_STATUS_INVALIDARGS; + } + + // Setup the data packet. + unsigned short crc = 0; + unsigned char packet[SZ_PACKET + 8] = { + HEADER, HEADER, HEADER, + size, + cmd + }; + if (size) { + memcpy (packet + 5, data, size); + } + crc = checksum_crc16_ccitt (packet + 3, size + 2, 0x000); + packet[5 + size + 0] = (crc ) & 0xFF; // Low + packet[5 + size + 1] = (crc >> 8) & 0xFF; // High + packet[5 + size + 2] = TRAILER; + + // Wait a small amount of time before sending the command. Without + // this delay, the transfer will fail most of the time. + dc_iostream_sleep (device->iostream, 100); + + // Send the command to the device. + status = dc_iostream_write (device->iostream, packet, size + 8, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the command."); + return status; + } + + return status; +} + +static dc_status_t +cressi_goa_device_receive (cressi_goa_device_t *device, unsigned char data[], unsigned int size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + + unsigned char packet[SZ_PACKET + 8]; + + // Read the header of the data packet. + status = dc_iostream_read (device->iostream, packet, 4, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the answer."); + return status; + } + + // Verify the header of the packet. + if (packet[0] != HEADER || packet[1] != HEADER || packet[2] != HEADER) { + ERROR (abstract->context, "Unexpected answer header byte."); + return DC_STATUS_PROTOCOL; + } + + // Get the payload length. + unsigned int length = packet[3]; + if (length > SZ_PACKET) { + ERROR (abstract->context, "Unexpected payload size (%u).", length); + return DC_STATUS_PROTOCOL; + } + + // Read the remainder of the data packet. + status = dc_iostream_read (device->iostream, packet + 4, length + 4, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the answer."); + return status; + } + + // Verify the trailer of the packet. + if (packet[length + 7] != TRAILER) { + ERROR (abstract->context, "Unexpected answer trailer byte."); + return DC_STATUS_PROTOCOL; + } + + // Verify the checksum of the packet. + unsigned short crc = array_uint16_le (packet + length + 5); + unsigned short ccrc = checksum_crc16_ccitt (packet + 3, length + 2, 0x0000); + if (crc != ccrc) { + ERROR (abstract->context, "Unexpected answer checksum."); + return DC_STATUS_PROTOCOL; + } + + // Verify the payload length. + if (length != size) { + ERROR (abstract->context, "Unexpected payload size (%u).", length); + return DC_STATUS_PROTOCOL; + } + + if (length) { + memcpy (data, packet + 5, length); + } + + return status; +} + +static dc_status_t +cressi_goa_device_download (cressi_goa_device_t *device, dc_buffer_t *buffer, dc_event_progress_t *progress) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + + const unsigned char ack[] = {ACK}; + const unsigned int initial = progress ? progress->current : 0; + + // Erase the contents of the buffer. + if (!dc_buffer_clear (buffer)) { + ERROR (abstract->context, "Insufficient buffer space available."); + return DC_STATUS_NOMEMORY; + } + + unsigned int skip = 2; + unsigned int size = 2; + unsigned int nbytes = 0; + while (nbytes < size) { + // Read the data packet. + unsigned char packet[3 + SZ_DATA + 2]; + status = dc_iostream_read (device->iostream, packet, sizeof(packet), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the answer."); + return status; + } + + // Verify the checksum of the packet. + unsigned short crc = array_uint16_le (packet + sizeof(packet) - 2); + unsigned short ccrc = checksum_crc16_ccitt (packet + 3, sizeof(packet) - 5, 0x0000); + if (crc != ccrc) { + ERROR (abstract->context, "Unexpected answer checksum."); + return DC_STATUS_PROTOCOL; + } + + // Send the ack byte to the device. + status = dc_iostream_write (device->iostream, ack, sizeof(ack), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the ack byte."); + return status; + } + + // Get the total size from the first data packet. + if (nbytes == 0) { + size += array_uint16_le (packet + 3); + } + + // Calculate the payload size of the packet. + unsigned int length = size - nbytes; + if (length > SZ_DATA) { + length = SZ_DATA; + } + + // Append the payload to the output buffer. + if (!dc_buffer_append (buffer, packet + 3 + skip, length - skip)) { + ERROR (abstract->context, "Insufficient buffer space available."); + return DC_STATUS_NOMEMORY; + } + + nbytes += length; + skip = 0; + + // Update and emit a progress event. + if (progress) { + progress->current = initial + STEP(nbytes, size); + device_event_emit (abstract, DC_EVENT_PROGRESS, progress); + } + } + + // Read the end byte. + unsigned char end = 0; + status = dc_iostream_read (device->iostream, &end, 1, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the end byte."); + return status; + } + + // Verify the end byte. + if (end != END) { + ERROR (abstract->context, "Unexpected end byte (%02x).", end); + return DC_STATUS_PROTOCOL; + } + + // Send the ack byte to the device. + status = dc_iostream_write (device->iostream, ack, sizeof(ack), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the ack byte."); + return status; + } + + return status; +} + +static dc_status_t +cressi_goa_device_transfer (cressi_goa_device_t *device, + unsigned char cmd, + const unsigned char input[], unsigned int isize, + unsigned char output[], unsigned int osize, + dc_buffer_t *buffer, + dc_event_progress_t *progress) +{ + dc_status_t status = DC_STATUS_SUCCESS; + + // Send the command to the dive computer. + status = cressi_goa_device_send (device, cmd, input, isize); + if (status != DC_STATUS_SUCCESS) { + return status; + } + + // Receive the answer from the dive computer. + status = cressi_goa_device_receive (device, output, osize); + if (status != DC_STATUS_SUCCESS) { + return status; + } + + // Download the optional and variable sized payload. + if (buffer) { + status = cressi_goa_device_download (device, buffer, progress); + if (status != DC_STATUS_SUCCESS) { + return status; + } + } + + return status; +} + + +dc_status_t +cressi_goa_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) +{ + dc_status_t status = DC_STATUS_SUCCESS; + cressi_goa_device_t *device = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + device = (cressi_goa_device_t *) dc_device_allocate (context, &cressi_goa_device_vtable); + if (device == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Set the default values. + device->iostream = iostream; + memset (device->fingerprint, 0, sizeof (device->fingerprint)); + + // Set the serial communication protocol (115200 8N1). + status = dc_iostream_configure (device->iostream, 115200, 8, DC_PARITY_NONE, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the terminal attributes."); + goto error_free; + } + + // Set the timeout for receiving data (3000 ms). + status = dc_iostream_set_timeout (device->iostream, 3000); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the timeout."); + goto error_free; + } + + // Clear the RTS line. + status = dc_iostream_set_rts (device->iostream, 0); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to clear the RTS line."); + goto error_free; + } + + // Clear the DTR line. + status = dc_iostream_set_dtr (device->iostream, 0); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to clear the DTR line."); + goto error_free; + } + + dc_iostream_sleep (device->iostream, 100); + dc_iostream_purge (device->iostream, DC_DIRECTION_ALL); + + *out = (dc_device_t *) device; + + return DC_STATUS_SUCCESS; + +error_free: + dc_device_deallocate ((dc_device_t *) device); + return status; +} + +static dc_status_t +cressi_goa_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size) +{ + cressi_goa_device_t *device = (cressi_goa_device_t *) abstract; + + if (size && size != sizeof (device->fingerprint)) + return DC_STATUS_INVALIDARGS; + + if (size) + memcpy (device->fingerprint, data, sizeof (device->fingerprint)); + else + memset (device->fingerprint, 0, sizeof (device->fingerprint)); + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) +{ + dc_status_t status = DC_STATUS_SUCCESS; + cressi_goa_device_t *device = (cressi_goa_device_t *) abstract; + dc_buffer_t *logbook = NULL; + dc_buffer_t *dive = NULL; + + // Enable progress notifications. + dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Read the version information. + unsigned char id[9] = {0}; + status = cressi_goa_device_transfer (device, CMD_VERSION, NULL, 0, id, sizeof(id), NULL, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the version information."); + goto error_exit; + } + + // Emit a vendor event. + dc_event_vendor_t vendor; + vendor.data = id; + vendor.size = sizeof (id); + device_event_emit (abstract, DC_EVENT_VENDOR, &vendor); + + // Emit a device info event. + dc_event_devinfo_t devinfo; + devinfo.model = id[4]; + devinfo.firmware = array_uint16_le (id + 5); + devinfo.serial = array_uint32_le (id + 0); + device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); + + // Allocate memory for the logbook data. + logbook = dc_buffer_new(4096); + if (logbook == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + goto error_exit; + } + + // Read the logbook data. + status = cressi_goa_device_transfer (device, CMD_LOGBOOK, NULL, 0, NULL, 0, logbook, &progress); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the logbook data."); + goto error_free_logbook; + } + + const unsigned char *logbook_data = dc_buffer_get_data(logbook); + size_t logbook_size = dc_buffer_get_size(logbook); + + // Count the number of dives. + unsigned int count = 0; + unsigned int offset = logbook_size; + while (offset > SZ_HEADER) { + // Move to the start of the logbook entry. + offset -= SZ_HEADER; + + // Get the dive number. + unsigned int number= array_uint16_le (logbook_data + offset); + if (number == 0) + break; + + // Compare the fingerprint to identify previously downloaded entries. + if (memcmp (logbook_data + offset + FP_OFFSET, device->fingerprint, sizeof(device->fingerprint)) == 0) { + break; + } + + count++; + } + + // Update and emit a progress event. + progress.maximum = (count + 1) * NSTEPS; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Allocate memory for the dive data. + dive = dc_buffer_new(4096); + if (dive == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + goto error_free_logbook; + } + + // Download the dives. + offset = logbook_size; + for (unsigned int i = 0; i < count; ++i) { + // Move to the start of the logbook entry. + offset -= SZ_HEADER; + + // Read the dive data. + status = cressi_goa_device_transfer (device, CMD_DIVE, logbook_data + offset, 2, NULL, 0, dive, &progress); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the dive data."); + goto error_free_dive; + } + + const unsigned char *dive_data = dc_buffer_get_data (dive); + size_t dive_size = dc_buffer_get_size (dive); + + // Verify the header in the logbook and dive data are identical. + // After the 2 byte dive number, the logbook header has 5 bytes + // extra, which are not present in the dive header. + if (dive_size < SZ_HEADER - 5 || + memcmp (dive_data + 0, logbook_data + offset + 0, 2) != 0 || + memcmp (dive_data + 2, logbook_data + offset + 7, SZ_HEADER - 7) != 0) { + ERROR (abstract->context, "Unexpected dive header."); + status = DC_STATUS_DATAFORMAT; + goto error_free_dive; + } + + if (callback && !callback(dive_data, dive_size, dive_data + FP_OFFSET - 5, sizeof(device->fingerprint), userdata)) + break; + } + +error_free_dive: + dc_buffer_free(dive); +error_free_logbook: + dc_buffer_free(logbook); +error_exit: + return status; +} diff --git a/src/cressi_goa.h b/src/cressi_goa.h new file mode 100644 index 0000000..0a92512 --- /dev/null +++ b/src/cressi_goa.h @@ -0,0 +1,43 @@ +/* + * libdivecomputer + * + * Copyright (C) 2018 Jef Driesen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#ifndef CRESSI_GOA_H +#define CRESSI_GOA_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +dc_status_t +cressi_goa_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); + +dc_status_t +cressi_goa_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int model); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* CRESSI_GOA_H */ diff --git a/src/cressi_goa_parser.c b/src/cressi_goa_parser.c new file mode 100644 index 0000000..cc99d2c --- /dev/null +++ b/src/cressi_goa_parser.c @@ -0,0 +1,209 @@ +/* + * libdivecomputer + * + * Copyright (C) 2018 Jef Driesen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include + +#include "cressi_goa.h" +#include "context-private.h" +#include "parser-private.h" +#include "array.h" + +#define ISINSTANCE(parser) dc_device_isinstance((parser), &cressi_goa_parser_vtable) + +#define SZ_HEADER 0x5C + +#define DEPTH 0 +#define TEMPERATURE 3 + +typedef struct cressi_goa_parser_t cressi_goa_parser_t; + +struct cressi_goa_parser_t { + dc_parser_t base; + unsigned int model; + // Cached fields. + unsigned int cached; + double maxdepth; +}; + +static dc_status_t cressi_goa_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t cressi_goa_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); +static dc_status_t cressi_goa_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); +static dc_status_t cressi_goa_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); + +static const dc_parser_vtable_t cressi_goa_parser_vtable = { + sizeof(cressi_goa_parser_t), + DC_FAMILY_CRESSI_GOA, + cressi_goa_parser_set_data, /* set_data */ + cressi_goa_parser_get_datetime, /* datetime */ + cressi_goa_parser_get_field, /* fields */ + cressi_goa_parser_samples_foreach, /* samples_foreach */ + NULL /* destroy */ +}; + +dc_status_t +cressi_goa_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model) +{ + cressi_goa_parser_t *parser = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + parser = (cressi_goa_parser_t *) dc_parser_allocate (context, &cressi_goa_parser_vtable); + if (parser == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + parser->model = model; + parser->cached = 0; + parser->maxdepth = 0.0; + + *out = (dc_parser_t*) parser; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +cressi_goa_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) +{ + cressi_goa_parser_t *parser = (cressi_goa_parser_t *) abstract; + + // Reset the cache. + parser->cached = 0; + parser->maxdepth = 0.0; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +cressi_goa_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) +{ + if (abstract->size < SZ_HEADER) + return DC_STATUS_DATAFORMAT; + + const unsigned char *p = abstract->data; + + if (datetime) { + datetime->year = array_uint16_le(p + 0x0C); + datetime->month = p[0x0E]; + datetime->day = p[0x0F]; + datetime->hour = p[0x10]; + datetime->minute = p[0x11]; + datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +cressi_goa_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) +{ + cressi_goa_parser_t *parser = (cressi_goa_parser_t *) abstract; + if (abstract->size < SZ_HEADER) + return DC_STATUS_DATAFORMAT; + + const unsigned char *data = abstract->data; + + if (!parser->cached) { + sample_statistics_t statistics = SAMPLE_STATISTICS_INITIALIZER; + dc_status_t rc = cressi_goa_parser_samples_foreach ( + abstract, sample_statistics_cb, &statistics); + if (rc != DC_STATUS_SUCCESS) + return rc; + + parser->cached = 1; + parser->maxdepth = statistics.maxdepth; + } + + dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + + if (value) { + switch (type) { + case DC_FIELD_DIVETIME: + *((unsigned int *) value) = array_uint16_le (data + 0x14); + break; + case DC_FIELD_MAXDEPTH: + *((double *) value) = parser->maxdepth; + break; + case DC_FIELD_GASMIX_COUNT: + *((unsigned int *) value) = 2; + break; + case DC_FIELD_GASMIX: + gasmix->helium = 0.0; + gasmix->oxygen = data[0x1B + 2 * flags] / 100.0; + gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; + break; + default: + return DC_STATUS_UNSUPPORTED; + } + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +cressi_goa_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +{ + const unsigned char *data = abstract->data; + unsigned int size = abstract->size; + + unsigned int time = 0; + unsigned int interval = 5; + unsigned int complete = 1; + + unsigned int offset = SZ_HEADER; + while (offset + 2 <= size) { + dc_sample_value_t sample = {0}; + + // Get the sample type and value. + unsigned int raw = array_uint16_le (data + offset); + unsigned int type = (raw & 0x0003); + unsigned int value = (raw & 0xFFFC) >> 2; + + if (complete) { + // Time (seconds). + time += interval; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + complete = 0; + } + + if (type == DEPTH) { + // Depth (1/10 m). + unsigned int depth = value & 0x0FFF; + sample.depth = depth / 10.0; + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + complete = 1; + } else if (type == TEMPERATURE) { + // Temperature (1/10 °C). + sample.temperature = value / 10.0; + if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); + } else { + WARNING(abstract->context, "Unknown sample type %u.", type); + } + + offset += 2; + } + + return DC_STATUS_SUCCESS; +} diff --git a/src/descriptor.c b/src/descriptor.c index dc7ec4c..1c533db 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -277,6 +277,9 @@ static const dc_descriptor_t g_descriptors[] = { {"Cressi", "Giotto", DC_FAMILY_CRESSI_LEONARDO, 4, DC_TRANSPORT_SERIAL, NULL}, {"Cressi", "Newton", DC_FAMILY_CRESSI_LEONARDO, 5, DC_TRANSPORT_SERIAL, NULL}, {"Cressi", "Drake", DC_FAMILY_CRESSI_LEONARDO, 6, DC_TRANSPORT_SERIAL, NULL}, + /* Cressi Goa */ + {"Cressi", "Cartesio", DC_FAMILY_CRESSI_GOA, 1, DC_TRANSPORT_SERIAL, NULL}, + {"Cressi", "Goa", DC_FAMILY_CRESSI_GOA, 2, DC_TRANSPORT_SERIAL, NULL}, /* Zeagle N2iTiON3 */ {"Zeagle", "N2iTiON3", DC_FAMILY_ZEAGLE_N2ITION3, 0, DC_TRANSPORT_SERIAL, NULL}, {"Apeks", "Quantum X", DC_FAMILY_ZEAGLE_N2ITION3, 0, DC_TRANSPORT_SERIAL, NULL}, diff --git a/src/device.c b/src/device.c index fc59464..9da9cd9 100644 --- a/src/device.c +++ b/src/device.c @@ -47,6 +47,7 @@ #include "hw_ostc3.h" #include "cressi_edy.h" #include "cressi_leonardo.h" +#include "cressi_goa.h" #include "zeagle_n2ition3.h" #include "atomics_cobalt.h" #include "shearwater_petrel.h" @@ -180,6 +181,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr case DC_FAMILY_CRESSI_LEONARDO: rc = cressi_leonardo_device_open (&device, context, iostream); break; + case DC_FAMILY_CRESSI_GOA: + rc = cressi_goa_device_open (&device, context, iostream); + break; case DC_FAMILY_ZEAGLE_N2ITION3: rc = zeagle_n2ition3_device_open (&device, context, iostream); break; diff --git a/src/parser.c b/src/parser.c index 064328d..6a236e6 100644 --- a/src/parser.c +++ b/src/parser.c @@ -47,6 +47,7 @@ #include "hw_ostc3.h" #include "cressi_edy.h" #include "cressi_leonardo.h" +#include "cressi_goa.h" #include "zeagle_n2ition3.h" #include "atomics_cobalt.h" #include "shearwater_petrel.h" @@ -144,6 +145,9 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa case DC_FAMILY_CRESSI_LEONARDO: rc = cressi_leonardo_parser_create (&parser, context, model); break; + case DC_FAMILY_CRESSI_GOA: + rc = cressi_goa_parser_create (&parser, context, model); + break; case DC_FAMILY_ATOMICS_COBALT: rc = atomics_cobalt_parser_create (&parser, context); break; From 2b96b2f52c4276bcb3973ad4e65cfcc8ff9ce8df Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 16 Jan 2019 20:06:17 +0100 Subject: [PATCH 9/9] Use the timezone setting of the dive computer The Ratio dive computers with the latest APOS4 firmware support a timezone setting. Take this timezone into account instead of using the timezone of the host system. --- src/divesystem_idive_parser.c | 89 ++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/src/divesystem_idive_parser.c b/src/divesystem_idive_parser.c index 413d7c4..adfb48a 100644 --- a/src/divesystem_idive_parser.c +++ b/src/divesystem_idive_parser.c @@ -26,6 +26,8 @@ #include "parser-private.h" #include "array.h" +#define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array)) + #define ISINSTANCE(parser) dc_device_isinstance((parser), &divesystem_idive_parser_vtable) #define IX3M_EASY 0x22 @@ -151,13 +153,96 @@ divesystem_idive_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *date { divesystem_idive_parser_t *parser = (divesystem_idive_parser_t *) abstract; + static const signed char tz_array[] = { + -12, 0, /* UTC-12 */ + -11, 0, /* UTC-11 */ + -10, 0, /* UTC-10 */ + -9, 30, /* UTC-9:30 */ + -9, 0, /* UTC-9 */ + -8, 0, /* UTC-8 */ + -7, 0, /* UTC-7 */ + -6, 0, /* UTC-6 */ + -5, 0, /* UTC-5 */ + -4, 30, /* UTC-4:30 */ + -4, 0, /* UTC-4 */ + -3, 30, /* UTC-3:30 */ + -3, 0, /* UTC-3 */ + -2, 0, /* UTC-2 */ + -1, 0, /* UTC-1 */ + 0, 0, /* UTC */ + 1, 0, /* UTC+1 */ + 2, 0, /* UTC+2 */ + 3, 0, /* UTC+3 */ + 3, 30, /* UTC+3:30 */ + 4, 0, /* UTC+4 */ + 4, 30, /* UTC+4:30 */ + 5, 0, /* UTC+5 */ + 5, 30, /* UTC+5:30 */ + 5, 45, /* UTC+5:45 */ + 6, 0, /* UTC+6 */ + 6, 30, /* UTC+6:30 */ + 7, 0, /* UTC+7 */ + 8, 0, /* UTC+8 */ + 8, 45, /* UTC+8:45 */ + 9, 0, /* UTC+9 */ + 9, 30, /* UTC+9:30 */ + 9, 45, /* UTC+9:45 */ + 10, 0, /* UTC+10 */ + 10, 30, /* UTC+10:30 */ + 11, 0, /* UTC+11 */ + 11, 30, /* UTC+11:30 */ + 12, 0, /* UTC+12 */ + 12, 45, /* UTC+12:45 */ + 13, 0, /* UTC+13 */ + 13, 45, /* UTC+13:45 */ + 14, 0 /* UTC+14 */ + }; + if (abstract->size < parser->headersize) return DC_STATUS_DATAFORMAT; dc_ticks_t ticks = array_uint32_le(abstract->data + 7) + EPOCH; - if (!dc_datetime_localtime (datetime, ticks)) - return DC_STATUS_DATAFORMAT; + // Detect the APOS4 firmware. + unsigned int firmware = 0; + unsigned int apos4 = 0; + if (parser->model >= IX3M_EASY) { + firmware = array_uint32_le(abstract->data + 0x2A); + apos4 = (firmware / 10000000) >= 4; + } else { + firmware = array_uint32_le(abstract->data + 0x2E); + apos4 = 0; + } + + if (apos4) { + // For devices with timezone support, the UTC offset of the + // device is used. The UTC offset is stored as an index in the + // timezone table. + unsigned int tz_idx = abstract->data[48]; + if ((tz_idx % 2) != 0 || tz_idx >= C_ARRAY_SIZE(tz_array)) { + ERROR (abstract->context, "Invalid timezone index (%u).", tz_idx); + return DC_STATUS_DATAFORMAT; + } + + int timezone = tz_array[tz_idx] * 3600; + if (timezone < 0) { + timezone -= tz_array[tz_idx + 1] * 60; + } else { + timezone += tz_array[tz_idx + 1] * 60; + } + + ticks += timezone; + + if (!dc_datetime_gmtime (datetime, ticks)) + return DC_STATUS_DATAFORMAT; + + datetime->timezone = timezone; + } else { + // For devices without timezone support, the current timezone of + // the host system is used. + if (!dc_datetime_localtime (datetime, ticks)) + return DC_STATUS_DATAFORMAT; + } return DC_STATUS_SUCCESS; }