From c4b694fdb1876b72143c465c933517d27eac4ab0 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 12 Sep 2021 19:07:44 +0200 Subject: [PATCH 1/4] Fix the salinity parsing The parser->mode field is only initialized at the end of the function. The result is that the current code always used the default value (zero). Inside the function itself, the local variable should be used instead. --- src/mares_iconhd_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index a9ca390..15fab95 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -266,7 +266,7 @@ mares_iconhd_cache (mares_iconhd_parser_t *parser) unsigned int settings = 0; if (parser->model == SMARTAPNEA) { settings = array_uint16_le (p + 0x1C); - } else if (parser->mode == ICONHD_FREEDIVE) { + } else if (mode == ICONHD_FREEDIVE) { settings = array_uint16_le (p + 0x08); } else { settings = array_uint16_le (p + 0x0C); From 1a4798792e30b0d22d5d826e2691bfdd14bed908 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 12 Sep 2021 19:43:02 +0200 Subject: [PATCH 2/4] Use the divetime stored in the header There is no need to calculate the total time from the pseudo profile, because the dive time is also stored in the header. --- src/mares_iconhd_parser.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index 15fab95..52b56cb 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -697,13 +697,7 @@ mares_iconhd_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi } else if (parser->model == SMARTAPNEA) { *((unsigned int *) value) = array_uint16_le (p + 0x24); } else if (parser->mode == ICONHD_FREEDIVE) { - unsigned int divetime = 0; - unsigned int offset = 4; - for (unsigned int i = 0; i < parser->nsamples; ++i) { - divetime += array_uint16_le (abstract->data + offset + 2); - offset += parser->samplesize; - } - *((unsigned int *) value) = divetime; + *((unsigned int *) value) = array_uint16_le (p + 0x0C); } else { *((unsigned int *) value) = parser->nsamples * parser->interval - parser->surftime; } From e52468e0c3d3349be2fb5a3a46da8bc468076969 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 12 Sep 2021 19:56:36 +0200 Subject: [PATCH 3/4] Use a layout descriptor Replace hardcoded constants with a layout descriptor. This reduces the amount of model specific conditions, and makes it easier to add support for new models. --- src/mares_iconhd_parser.c | 227 ++++++++++++++++++++++++-------------- 1 file changed, 146 insertions(+), 81 deletions(-) diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index 52b56cb..917e44e 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -35,6 +35,8 @@ (((major) & 0xFF) << 8) | \ ((minor) & 0xFF)) +#define UNSUPPORTED 0xFFFFFFFF + #define SMART 0x000010 #define SMARTAPNEA 0x010010 #define ICONHD 0x14 @@ -120,6 +122,19 @@ typedef struct mares_iconhd_parser_t mares_iconhd_parser_t; +typedef struct mares_iconhd_layout_t { + unsigned int settings; + unsigned int datetime; + unsigned int divetime; + unsigned int maxdepth; + unsigned int atmospheric; + unsigned int atmospheric_divisor; + unsigned int temperature_min; + unsigned int temperature_max; + unsigned int gasmixes; + unsigned int tanks; +} mares_iconhd_layout_t; + typedef struct mares_iconhd_gasmix_t { unsigned int oxygen; unsigned int helium; @@ -150,6 +165,91 @@ struct mares_iconhd_parser_t { unsigned int ngasmixes; mares_iconhd_gasmix_t gasmix[NGASMIXES]; mares_iconhd_tank_t tank[NTANKS]; + const mares_iconhd_layout_t *layout; +}; + +static const mares_iconhd_layout_t iconhd = { + 0x0C, /* settings */ + 0x02, /* datetime */ + UNSUPPORTED, /* divetime */ + 0x00, /* maxdepth */ + 0x22, 8, /* atmospheric */ + 0x42, /* temperature_min */ + 0x44, /* temperature_max */ + 0x10, /* gasmixes */ + UNSUPPORTED, /* tanks */ +}; + +static const mares_iconhd_layout_t iconhdnet = { + 0x0C, /* settings */ + 0x02, /* datetime */ + UNSUPPORTED, /* divetime */ + 0x00, /* maxdepth */ + 0x22, 8, /* atmospheric */ + 0x42, /* temperature_min */ + 0x44, /* temperature_max */ + 0x10, /* gasmixes */ + 0x58, /* tanks */ +}; + +static const mares_iconhd_layout_t smartair = { + 0x0C, /* settings */ + 0x02, /* datetime */ + UNSUPPORTED, /* divetime */ + 0x00, /* maxdepth */ + 0x22, 8, /* atmospheric */ + 0x42, /* temperature_min */ + 0x44, /* temperature_max */ + 0x10, /* gasmixes */ + 0x5C, /* tanks */ +}; + +static const mares_iconhd_layout_t smartapnea = { + 0x1C, /* settings */ + 0x40, /* datetime */ + 0x24, /* divetime */ + 0x3A, /* maxdepth */ + 0x38, 1, /* atmospheric */ + 0x3E, /* temperature_min */ + 0x3C, /* temperature_max */ + UNSUPPORTED, /* gasmixes */ + UNSUPPORTED, /* tanks */ +}; + +static const mares_iconhd_layout_t smart_freedive = { + 0x08, /* settings */ + 0x20, /* datetime */ + 0x0C, /* divetime */ + 0x1A, /* maxdepth */ + 0x18, 1, /* atmospheric */ + 0x1C, /* temperature_min */ + 0x1E, /* temperature_max */ + UNSUPPORTED, /* gasmixes */ + UNSUPPORTED, /* tanks */ +}; + +static const mares_iconhd_layout_t genius = { + 0x0C, /* settings */ + 0x08, /* datetime */ + UNSUPPORTED, /* divetime */ + 0x22, /* maxdepth */ + 0x3E, 1, /* atmospheric */ + 0x28, /* temperature_min */ + 0x26, /* temperature_max */ + 0x54, /* gasmixes */ + 0x54, /* tanks */ +}; + +static const mares_iconhd_layout_t horizon = { + 0x0C, /* settings */ + 0x08, /* datetime */ + UNSUPPORTED, /* divetime */ + 0x22 + 8, /* maxdepth */ + 0x3E + 8, 1, /* atmospheric */ + 0x28 + 8, /* temperature_min */ + 0x26 + 8, /* temperature_max */ + 0x54 + 8, /* gasmixes */ + 0x54 + 8, /* tanks */ }; static dc_status_t mares_iconhd_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); @@ -233,23 +333,29 @@ mares_iconhd_cache (mares_iconhd_parser_t *parser) // Get the header and sample size. unsigned int headersize = 0x5C; unsigned int samplesize = 8; + const mares_iconhd_layout_t *layout = &iconhd; if (parser->model == ICONHDNET) { headersize = 0x80; samplesize = 12; + layout = &iconhdnet; } else if (parser->model == QUADAIR || parser->model == SMARTAIR) { headersize = 0x84; samplesize = 12; + layout = &smartair; } else if (parser->model == SMART) { if (mode == ICONHD_FREEDIVE) { headersize = 0x2E; samplesize = 6; + layout = &smart_freedive; } else { headersize = 0x5C; samplesize = 8; + layout = &iconhd; } } else if (parser->model == SMARTAPNEA) { headersize = 0x50; samplesize = 14; + layout = &smartapnea; } if (length < 4 + headersize) { @@ -263,14 +369,7 @@ mares_iconhd_cache (mares_iconhd_parser_t *parser) } // Get the dive settings. - unsigned int settings = 0; - if (parser->model == SMARTAPNEA) { - settings = array_uint16_le (p + 0x1C); - } else if (mode == ICONHD_FREEDIVE) { - settings = array_uint16_le (p + 0x08); - } else { - settings = array_uint16_le (p + 0x0C); - } + unsigned int settings = array_uint16_le (p + layout->settings); // Get the sample interval. unsigned int interval = 0; @@ -288,7 +387,7 @@ mares_iconhd_cache (mares_iconhd_parser_t *parser) // Calculate the total number of bytes for this dive. unsigned int nbytes = 4 + headersize + nsamples * samplesize; - if (parser->model == ICONHDNET || parser->model == QUADAIR || parser->model == SMARTAIR) { + if (layout->tanks != UNSUPPORTED) { nbytes += (nsamples / 4) * 8; } else if (parser->model == SMARTAPNEA) { unsigned int divetime = array_uint32_le (p + 0x24); @@ -302,31 +401,34 @@ mares_iconhd_cache (mares_iconhd_parser_t *parser) // Gas mixes unsigned int ngasmixes = 0; mares_iconhd_gasmix_t gasmix[NGASMIXES_ICONHD] = {0}; - if (mode == ICONHD_GAUGE || mode == ICONHD_FREEDIVE) { - ngasmixes = 0; - } else if (mode == ICONHD_AIR) { - gasmix[0].oxygen = 21; - gasmix[0].helium = 0; - ngasmixes = 1; - } else { - // Count the number of active gas mixes. The active gas - // mixes are always first, so we stop counting as soon - // as the first gas marked as disabled is found. - ngasmixes = 0; - while (ngasmixes < NGASMIXES_ICONHD) { - if (p[0x10 + ngasmixes * 4 + 1] & 0x80) - break; - gasmix[ngasmixes].oxygen = p[0x10 + ngasmixes * 4]; - gasmix[ngasmixes].helium = 0; - ngasmixes++; + if (layout->gasmixes != UNSUPPORTED) { + if (mode == ICONHD_GAUGE || mode == ICONHD_FREEDIVE) { + ngasmixes = 0; + } else if (mode == ICONHD_AIR) { + gasmix[0].oxygen = 21; + gasmix[0].helium = 0; + ngasmixes = 1; + } else { + // Count the number of active gas mixes. The active gas + // mixes are always first, so we stop counting as soon + // as the first gas marked as disabled is found. + ngasmixes = 0; + while (ngasmixes < NGASMIXES_ICONHD) { + unsigned int offset = layout->gasmixes + ngasmixes * 4; + if (p[offset + 1] & 0x80) + break; + gasmix[ngasmixes].oxygen = p[offset]; + gasmix[ngasmixes].helium = 0; + ngasmixes++; + } } } // Tanks unsigned int ntanks = 0; mares_iconhd_tank_t tank[NTANKS_ICONHD] = {0}; - if (parser->model == ICONHDNET || parser->model == QUADAIR || parser->model == SMARTAIR) { - unsigned int tankoffset = (parser->model == ICONHDNET) ? 0x58 : 0x5C; + if (layout->tanks != UNSUPPORTED) { + unsigned int tankoffset = layout->tanks; while (ntanks < NTANKS_ICONHD) { tank[ntanks].volume = array_uint16_le (p + tankoffset + 0x0C + ntanks * 8 + 0); tank[ntanks].workpressure = array_uint16_le (p + tankoffset + 0x0C + ntanks * 8 + 2); @@ -359,6 +461,7 @@ mares_iconhd_cache (mares_iconhd_parser_t *parser) for (unsigned int i = 0; i < ntanks; ++i) { parser->tank[i] = tank[i]; } + parser->layout = layout; parser->cached = 1; return DC_STATUS_SUCCESS; @@ -391,8 +494,10 @@ mares_genius_cache (mares_iconhd_parser_t *parser) // The Horizon header has 8 bytes extra at offset 0x18. unsigned int extra = 0; + const mares_iconhd_layout_t * layout = &genius; if (logformat == 1) { extra = 8; + layout = &horizon; } // The Genius header (v1.x) has 10 bytes more at the end. @@ -412,7 +517,7 @@ mares_genius_cache (mares_iconhd_parser_t *parser) unsigned int nsamples = array_uint16_le (data + 0x20 + extra); // Get the dive settings. - unsigned int settings = array_uint32_le (data + 0x0C); + unsigned int settings = array_uint32_le (data + layout->settings); // Get the dive mode. unsigned int mode = settings & 0xF; @@ -449,7 +554,7 @@ mares_genius_cache (mares_iconhd_parser_t *parser) mares_iconhd_gasmix_t gasmix[NGASMIXES_GENIUS] = {0}; mares_iconhd_tank_t tank[NTANKS_GENIUS] = {0}; for (unsigned int i = 0; i < NGASMIXES_GENIUS; i++) { - unsigned int offset = 0x54 + extra + i * 20; + unsigned int offset = layout->tanks + i * 20; unsigned int gasmixparams = array_uint32_le(data + offset + 0); unsigned int beginpressure = array_uint16_le(data + offset + 4); unsigned int endpressure = array_uint16_le(data + offset + 6); @@ -505,6 +610,7 @@ mares_genius_cache (mares_iconhd_parser_t *parser) for (unsigned int i = 0; i < ntanks; ++i) { parser->tank[i] = tank[i]; } + parser->layout = layout; parser->cached = 1; return DC_STATUS_SUCCESS; @@ -563,6 +669,7 @@ mares_iconhd_parser_create (dc_parser_t **out, dc_context_t *context, unsigned i parser->tank[i].beginpressure = 0; parser->tank[i].endpressure = 0; } + parser->layout = NULL; *out = (dc_parser_t*) parser; @@ -598,6 +705,7 @@ mares_iconhd_parser_set_data (dc_parser_t *abstract, const unsigned char *data, parser->tank[i].beginpressure = 0; parser->tank[i].endpressure = 0; } + parser->layout = NULL; return DC_STATUS_SUCCESS; } @@ -623,15 +731,7 @@ mares_iconhd_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime } // Offset to the date/time field. - if (parser->model == GENIUS || parser->model == HORIZON) { - p += 0x08; - } else if (parser->model == SMARTAPNEA) { - p += 0x40; - } else if (parser->mode == ICONHD_FREEDIVE) { - p += 0x20; - } else { - p += 2; - } + p += parser->layout->datetime; if (datetime) { if (parser->model == GENIUS || parser->model == HORIZON) { @@ -692,25 +792,14 @@ mares_iconhd_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi if (value) { switch (type) { case DC_FIELD_DIVETIME: - if (parser->model == GENIUS || parser->model == HORIZON) { - *((unsigned int *) value) = parser->nsamples * parser->interval - parser->surftime; - } else if (parser->model == SMARTAPNEA) { - *((unsigned int *) value) = array_uint16_le (p + 0x24); - } else if (parser->mode == ICONHD_FREEDIVE) { - *((unsigned int *) value) = array_uint16_le (p + 0x0C); + if (parser->layout->divetime != UNSUPPORTED) { + *((unsigned int *) value) = array_uint16_le (p + parser->layout->divetime); } else { *((unsigned int *) value) = parser->nsamples * parser->interval - parser->surftime; } break; case DC_FIELD_MAXDEPTH: - if (parser->model == GENIUS || parser->model == HORIZON) - *((double *) value) = array_uint16_le (p + 0x22 + extra) / 10.0; - else if (parser->model == SMARTAPNEA) - *((double *) value) = array_uint16_le (p + 0x3A) / 10.0; - else if (parser->mode == ICONHD_FREEDIVE) - *((double *) value) = array_uint16_le (p + 0x1A) / 10.0; - else - *((double *) value) = array_uint16_le (p + 0x00) / 10.0; + *((double *) value) = array_uint16_le (p + parser->layout->maxdepth) / 10.0; break; case DC_FIELD_GASMIX_COUNT: *((unsigned int *) value) = parser->ngasmixes; @@ -745,14 +834,7 @@ mares_iconhd_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi } break; case DC_FIELD_ATMOSPHERIC: - if (parser->model == GENIUS || parser->model == HORIZON) - *((double *) value) = array_uint16_le (p + 0x3E + extra) / 1000.0; - else if (parser->model == SMARTAPNEA) - *((double *) value) = array_uint16_le (p + 0x38) / 1000.0; - else if (parser->mode == ICONHD_FREEDIVE) - *((double *) value) = array_uint16_le (p + 0x18) / 1000.0; - else - *((double *) value) = array_uint16_le (p + 0x22) / 8000.0; + *((double *) value) = array_uint16_le (p + parser->layout->atmospheric) / (1000.0 * parser->layout->atmospheric_divisor); break; case DC_FIELD_SALINITY: if (parser->model == GENIUS || parser->model == HORIZON) { @@ -791,24 +873,10 @@ mares_iconhd_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi } break; case DC_FIELD_TEMPERATURE_MINIMUM: - if (parser->model == GENIUS || parser->model == HORIZON) - *((double *) value) = (signed short) array_uint16_le (p + 0x28 + extra) / 10.0; - else if (parser->model == SMARTAPNEA) - *((double *) value) = (signed short) array_uint16_le (p + 0x3E) / 10.0; - else if (parser->mode == ICONHD_FREEDIVE) - *((double *) value) = (signed short) array_uint16_le (p + 0x1C) / 10.0; - else - *((double *) value) = (signed short) array_uint16_le (p + 0x42) / 10.0; + *((double *) value) = (signed short) array_uint16_le (p + parser->layout->temperature_min) / 10.0; break; case DC_FIELD_TEMPERATURE_MAXIMUM: - if (parser->model == GENIUS || parser->model == HORIZON) - *((double *) value) = (signed short) array_uint16_le (p + 0x26 + extra) / 10.0; - else if (parser->model == SMARTAPNEA) - *((double *) value) = (signed short) array_uint16_le (p + 0x3C) / 10.0; - else if (parser->mode == ICONHD_FREEDIVE) - *((double *) value) = (signed short) array_uint16_le (p + 0x1E) / 10.0; - else - *((double *) value) = (signed short) array_uint16_le (p + 0x44) / 10.0; + *((double *) value) = (signed short) array_uint16_le (p + parser->layout->temperature_max) / 10.0; break; case DC_FIELD_DIVEMODE: if (parser->model == GENIUS || parser->model == HORIZON) { @@ -881,9 +949,6 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t // Previous gas mix - initialize with impossible value unsigned int gasmix_previous = 0xFFFFFFFF; - unsigned int isairintegrated = (parser->model == ICONHDNET || parser->model == QUADAIR || - parser->model == SMARTAIR || parser->model == GENIUS || parser->model == HORIZON); - unsigned int offset = 4; unsigned int marker = 0; if (parser->model == GENIUS || parser->model == HORIZON) { @@ -1099,7 +1164,7 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t nsamples++; // Some extra data. - if (isairintegrated && (nsamples % 4) == 0) { + if (parser->layout->tanks != UNSUPPORTED && (nsamples % 4) == 0) { if ((parser->model == GENIUS || parser->model == HORIZON) && !mares_genius_isvalid (data + offset, AIRS_SIZE, AIRS_TYPE)) { ERROR (abstract->context, "Invalid AIRS record."); From 7e075eb959a0e932d9fa55a00ece182e425123eb Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 31 Aug 2021 18:01:26 +0200 Subject: [PATCH 4/4] Add support for Mares Smart Air freedives The Mares Smart Air supports a freedive mode, which uses a different data format compared to the scuba dives. --- src/mares_iconhd.c | 14 ++++++++++---- src/mares_iconhd_parser.c | 24 +++++++++++++++++++++++- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index 2a70be3..21f88fa 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -790,9 +790,15 @@ mares_iconhd_device_foreach_raw (dc_device_t *abstract, dc_dive_callback_t callb samplesize = 14; fingerprint = 0x40; } else if (model == SMARTAIR) { - headersize = 0x84; - samplesize = 12; - fingerprint = 2; + if (mode == FREEDIVE) { + headersize = 0x30; + samplesize = 6; + fingerprint = 0x22; + } else { + headersize = 0x84; + samplesize = 12; + fingerprint = 2; + } } if (offset < headersize) break; @@ -811,7 +817,7 @@ mares_iconhd_device_foreach_raw (dc_device_t *abstract, dc_dive_callback_t callb // end of the ringbuffer. The current dive is incomplete (partially // overwritten with newer data), and processing should stop. unsigned int nbytes = 4 + headersize + nsamples * samplesize; - if (model == ICONHDNET || model == QUADAIR || model == SMARTAIR) { + if (model == ICONHDNET || model == QUADAIR || (model == SMARTAIR && mode != FREEDIVE)) { nbytes += (nsamples / 4) * 8; } else if (model == SMARTAPNEA) { unsigned int settings = array_uint16_le (buffer + offset - headersize + 0x1C); diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index 917e44e..93cc5f2 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -228,6 +228,18 @@ static const mares_iconhd_layout_t smart_freedive = { UNSUPPORTED, /* tanks */ }; +static const mares_iconhd_layout_t smartair_freedive = { + 0x08, /* settings */ + 0x22, /* datetime */ + 0x0E, /* divetime */ + 0x1C, /* maxdepth */ + 0x1A, 1, /* atmospheric */ + 0x20, /* temperature_min */ + 0x1E, /* temperature_max */ + UNSUPPORTED, /* gasmixes */ + UNSUPPORTED, /* tanks */ +}; + static const mares_iconhd_layout_t genius = { 0x0C, /* settings */ 0x08, /* datetime */ @@ -338,7 +350,7 @@ mares_iconhd_cache (mares_iconhd_parser_t *parser) headersize = 0x80; samplesize = 12; layout = &iconhdnet; - } else if (parser->model == QUADAIR || parser->model == SMARTAIR) { + } else if (parser->model == QUADAIR) { headersize = 0x84; samplesize = 12; layout = &smartair; @@ -356,6 +368,16 @@ mares_iconhd_cache (mares_iconhd_parser_t *parser) headersize = 0x50; samplesize = 14; layout = &smartapnea; + } else if (parser->model == SMARTAIR) { + if (mode == ICONHD_FREEDIVE) { + headersize = 0x30; + samplesize = 6; + layout = &smartair_freedive; + } else { + headersize = 0x84; + samplesize = 12; + layout = &smartair; + } } if (length < 4 + headersize) {