From 95f309a1c96b94c39d201c06ce901a69d974a3df Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 2 Aug 2022 06:57:53 +0200 Subject: [PATCH 01/58] Add support for the Cressi Donatello The Cressi Donatello appears to be compatible with the Goa, with just a different model number. --- src/descriptor.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/descriptor.c b/src/descriptor.c index 9e1e7eb..ce95fd9 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -332,6 +332,7 @@ static const dc_descriptor_t g_descriptors[] = { /* Cressi Goa */ {"Cressi", "Cartesio", DC_FAMILY_CRESSI_GOA, 1, DC_TRANSPORT_SERIAL, NULL}, {"Cressi", "Goa", DC_FAMILY_CRESSI_GOA, 2, DC_TRANSPORT_SERIAL, NULL}, + {"Cressi", "Donatello", DC_FAMILY_CRESSI_GOA, 4, DC_TRANSPORT_SERIAL, NULL}, {"Cressi", "Michelangelo", DC_FAMILY_CRESSI_GOA, 5, DC_TRANSPORT_SERIAL, NULL}, {"Cressi", "Neon", DC_FAMILY_CRESSI_GOA, 9, DC_TRANSPORT_SERIAL, NULL}, /* Zeagle N2iTiON3 */ From 7fb943ae7fd8448ceb0c8aa3761061dbc3c44580 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 30 Jun 2022 22:03:28 +0200 Subject: [PATCH 02/58] Add support for parsing the decompression model Report the decompression algorithm (Buhlmann, VPM, RGBM or DCIEM), and if available also the parameters. For now only the conservatism setting is supported, and for the Buhlmann algorithm also the Gradient Factors (GF). --- examples/output_xml.c | 24 ++++++++++ include/libdivecomputer/parser.h | 40 +++++++++++++++- src/divesystem_idive_parser.c | 56 +++++++++++++++++++++++ src/hw_ostc_parser.c | 78 ++++++++++++++++++++++++++++++++ src/liquivision_lynx_parser.c | 20 ++++++++ src/seac_screen_parser.c | 26 +++++++++++ src/shearwater_predator_parser.c | 30 ++++++++++++ src/suunto_d9_parser.c | 15 ++++++ src/suunto_eonsteel_parser.c | 18 ++++++++ src/suunto_vyper_parser.c | 5 ++ 10 files changed, 311 insertions(+), 1 deletion(-) diff --git a/examples/output_xml.c b/examples/output_xml.c index 854646b..7d76cb1 100644 --- a/examples/output_xml.c +++ b/examples/output_xml.c @@ -386,6 +386,30 @@ dctool_xml_output_write (dctool_output_t *abstract, dc_parser_t *parser, const u names[divemode]); } + // Parse the deco model. + message ("Parsing the deco model.\n"); + dc_decomodel_t decomodel = {DC_DECOMODEL_NONE}; + status = dc_parser_get_field (parser, DC_FIELD_DECOMODEL, 0, &decomodel); + if (status != DC_STATUS_SUCCESS && status != DC_STATUS_UNSUPPORTED) { + ERROR ("Error parsing the deco model."); + goto cleanup; + } + + if (status != DC_STATUS_UNSUPPORTED) { + const char *names[] = {"none", "buhlmann", "vpm", "rgbm", "dciem"}; + fprintf (output->ostream, "%s\n", + names[decomodel.type]); + if (decomodel.type == DC_DECOMODEL_BUHLMANN && + (decomodel.params.gf.low != 0 || decomodel.params.gf.high != 0)) { + fprintf (output->ostream, "%u/%u\n", + decomodel.params.gf.low, decomodel.params.gf.high); + } + if (decomodel.conservatism) { + fprintf (output->ostream, "%d\n", + decomodel.conservatism); + } + } + // Parse the salinity. message ("Parsing the salinity.\n"); dc_salinity_t salinity = {DC_WATER_FRESH, 0.0}; diff --git a/include/libdivecomputer/parser.h b/include/libdivecomputer/parser.h index 1b53fd3..dc81317 100644 --- a/include/libdivecomputer/parser.h +++ b/include/libdivecomputer/parser.h @@ -62,7 +62,8 @@ typedef enum dc_field_type_t { DC_FIELD_TEMPERATURE_MAXIMUM, DC_FIELD_TANK_COUNT, DC_FIELD_TANK, - DC_FIELD_DIVEMODE + DC_FIELD_DIVEMODE, + DC_FIELD_DECOMODEL, } dc_field_type_t; typedef enum parser_sample_event_t { @@ -186,6 +187,43 @@ typedef struct dc_tank_t { double endpressure; /* End pressure (bar) */ } dc_tank_t; +typedef enum dc_decomodel_type_t { + DC_DECOMODEL_NONE, + DC_DECOMODEL_BUHLMANN, + DC_DECOMODEL_VPM, + DC_DECOMODEL_RGBM, + DC_DECOMODEL_DCIEM, +} dc_decomodel_type_t; + +/* + * Decompression model + * + * The type field contains the decompression algorithm. + * + * The (optional) conservatism field contains the personal adjustment + * setting of the algorithm. The exact interpretation depends on the + * dive computer, but the default value (zero) will typically correspond + * to the neutral setting, while a positive value is more conservative + * and a negative value more aggressive. + * + * The (optional) params field contains the parameters of the algorithm: + * + * DC_DECOMODEL_BUHLMANN: The Gradient Factor (GF) parameters low and + * high. For a pure Buhlmann algorithm (e.g. without GF enabled), both + * values are 100. If GF are enabled, but the actual parameter values + * are not available from the dive computer, both values are zero. + */ +typedef struct dc_decomodel_t { + dc_decomodel_type_t type; + int conservatism; + union { + struct { + unsigned int high; + unsigned int low; + } gf; + } params; +} dc_decomodel_t; + typedef union dc_sample_value_t { unsigned int time; double depth; diff --git a/src/divesystem_idive_parser.c b/src/divesystem_idive_parser.c index 5ace8f4..f0d7700 100644 --- a/src/divesystem_idive_parser.c +++ b/src/divesystem_idive_parser.c @@ -48,6 +48,9 @@ #define FREEDIVE 4 #define INVALID 0xFFFFFFFF +#define BUHLMANN 0 +#define VPM 1 + typedef struct divesystem_idive_parser_t divesystem_idive_parser_t; typedef struct divesystem_idive_gasmix_t { @@ -74,6 +77,9 @@ struct divesystem_idive_parser_t { unsigned int ntanks; divesystem_idive_gasmix_t gasmix[NGASMIXES]; divesystem_idive_tank_t tank[NTANKS]; + unsigned int algorithm; + unsigned int gf_low; + unsigned int gf_high; }; static dc_status_t divesystem_idive_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); @@ -129,6 +135,10 @@ divesystem_idive_parser_create (dc_parser_t **out, dc_context_t *context, unsign parser->tank[i].beginpressure = 0; parser->tank[i].endpressure = 0; } + parser->algorithm = INVALID; + parser->gf_low = INVALID; + parser->gf_high = INVALID; + *out = (dc_parser_t*) parser; @@ -157,6 +167,9 @@ divesystem_idive_parser_set_data (dc_parser_t *abstract, const unsigned char *da parser->tank[i].beginpressure = 0; parser->tank[i].endpressure = 0; } + parser->algorithm = INVALID; + parser->gf_low = INVALID; + parser->gf_high = INVALID; return DC_STATUS_SUCCESS; } @@ -280,6 +293,7 @@ divesystem_idive_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_tank_t *tank = (dc_tank_t *) value; dc_salinity_t *water = (dc_salinity_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; if (value) { switch (type) { @@ -343,6 +357,25 @@ divesystem_idive_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, return DC_STATUS_DATAFORMAT; } break; + case DC_FIELD_DECOMODEL: + if (parser->algorithm == INVALID) + return DC_STATUS_UNSUPPORTED; + switch (parser->algorithm) { + case BUHLMANN: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->conservatism = 0; + decomodel->params.gf.low = parser->gf_low; + decomodel->params.gf.high = parser->gf_high; + break; + case VPM: + decomodel->type = DC_DECOMODEL_VPM; + decomodel->conservatism = 0; + break; + default: + ERROR (abstract->context, "Unknown deco algorithm %02x.", parser->algorithm); + return DC_STATUS_DATAFORMAT; + } + break; default: return DC_STATUS_UNSUPPORTED; } @@ -371,6 +404,10 @@ divesystem_idive_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callba unsigned int divemode = INVALID; unsigned int tank_previous = INVALID; unsigned int tank_idx = INVALID; + unsigned int algorithm = INVALID; + unsigned int algorithm_previous = INVALID; + unsigned int gf_low = INVALID; + unsigned int gf_high = INVALID; unsigned int firmware = 0; unsigned int apos4 = 0; @@ -433,6 +470,22 @@ divesystem_idive_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callba divemode = mode; } + // Deco model + unsigned int s_algorithm = data[offset + 14]; + unsigned int s_gf_high = data[offset + 15]; + unsigned int s_gf_low = data[offset + 16]; + if (s_algorithm != algorithm_previous) { + if (algorithm_previous != INVALID) { + WARNING (abstract->context, "Deco algorithm changed from %02x to %02x.", algorithm_previous, s_algorithm); + } + algorithm_previous = s_algorithm; + } + if (algorithm == INVALID) { + algorithm = s_algorithm; + gf_low = s_gf_low; + gf_high = s_gf_high; + } + // Setpoint if (mode == SCR || mode == CCR) { unsigned int setpoint = array_uint16_le (data + offset + 19); @@ -560,6 +613,9 @@ divesystem_idive_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callba parser->maxdepth = maxdepth; parser->divetime = time; parser->divemode = divemode; + parser->algorithm = algorithm; + parser->gf_low = gf_low; + parser->gf_high = gf_high; parser->cached = 1; return DC_STATUS_SUCCESS; diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index f1092c0..06470d7 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -69,6 +69,10 @@ #define OSTC3_APNEA 3 #define OSTC3_PSCR 4 +#define OSTC3_ZHL16 0 +#define OSTC3_ZHL16_GF 1 +#define OSTC4_VPM 2 + #define OSTC4 0x3B #define OSTC3FW(major,minor) ( \ @@ -97,6 +101,8 @@ typedef struct hw_ostc_layout_t { unsigned int salinity; unsigned int avgdepth; unsigned int duration; + unsigned int gf; + unsigned int decomodel; unsigned int divemode; } hw_ostc_layout_t; @@ -147,6 +153,8 @@ static const hw_ostc_layout_t hw_ostc_layout_ostc = { 43, /* salinity */ 45, /* avgdepth */ 47, /* duration */ + 49, /* gf */ + UNDEFINED, /* decomodel */ 51, /* divemode */ }; @@ -160,6 +168,8 @@ static const hw_ostc_layout_t hw_ostc_layout_frog = { 43, /* salinity */ 45, /* avgdepth */ 47, /* duration */ + 49, /* gf */ + UNDEFINED, /* decomodel */ 51, /* divemode */ }; @@ -173,6 +183,8 @@ static const hw_ostc_layout_t hw_ostc_layout_ostc3 = { 70, /* salinity */ 73, /* avgdepth */ 75, /* duration */ + 77, /* gf */ + 79, /* decomodel */ 82, /* divemode */ }; @@ -472,6 +484,8 @@ hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_salinity_t *water = (dc_salinity_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; + unsigned int salinity = data[layout->salinity]; if (version == 0x23 || version == 0x24) salinity += 100; @@ -572,6 +586,70 @@ hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned return DC_STATUS_UNSUPPORTED; } break; + case DC_FIELD_DECOMODEL: + if (version == 0x21) { + switch (data[layout->divemode]) { + case OSTC_APNEA: + case OSTC_GAUGE: + decomodel->type = DC_DECOMODEL_NONE; + break; + case OSTC_ZHL16_OC: + case OSTC_ZHL16_CC: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->params.gf.low = 100; + decomodel->params.gf.high = 100; + break; + case OSTC_ZHL16_OC_GF: + case OSTC_ZHL16_CC_GF: + case OSTC_PSCR_GF: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->params.gf.low = data[layout->gf + 0]; + decomodel->params.gf.high = data[layout->gf + 1]; + break; + default: + return DC_STATUS_DATAFORMAT; + } + } else if (version == 0x22) { + switch (data[layout->divemode]) { + case FROG_ZHL16: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->params.gf.low = 100; + decomodel->params.gf.high = 100; + break; + case FROG_ZHL16_GF: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->params.gf.low = data[layout->gf + 0]; + decomodel->params.gf.high = data[layout->gf + 1]; + break; + case FROG_APNEA: + decomodel->type = DC_DECOMODEL_NONE; + break; + default: + return DC_STATUS_DATAFORMAT; + } + } else if (version == 0x23 || version == 0x24) { + switch (data[layout->decomodel]) { + case OSTC3_ZHL16: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->params.gf.low = 100; + decomodel->params.gf.high = 100; + break; + case OSTC3_ZHL16_GF: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->params.gf.low = data[layout->gf + 0]; + decomodel->params.gf.high = data[layout->gf + 1]; + break; + case OSTC4_VPM: + decomodel->type = DC_DECOMODEL_VPM; + break; + default: + return DC_STATUS_DATAFORMAT; + } + } else { + return DC_STATUS_UNSUPPORTED; + } + decomodel->conservatism = 0; + break; default: return DC_STATUS_UNSUPPORTED; } diff --git a/src/liquivision_lynx_parser.c b/src/liquivision_lynx_parser.c index c4272d8..2ff52cb 100644 --- a/src/liquivision_lynx_parser.c +++ b/src/liquivision_lynx_parser.c @@ -66,6 +66,9 @@ #define TEC 2 #define REC 3 +#define ZHL16GF 0 +#define RGBM 1 + #define NORMAL 0 #define BOOKMARK 1 #define ALARM_DEPTH 2 @@ -231,6 +234,7 @@ liquivision_lynx_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_tank_t *tank = (dc_tank_t *) value; dc_salinity_t *water = (dc_salinity_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; if (value) { switch (type) { @@ -288,6 +292,22 @@ liquivision_lynx_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, } } break; + case DC_FIELD_DECOMODEL: + switch (abstract->data[93]) { + case ZHL16GF: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->conservatism = 0; + decomodel->params.gf.low = 0; + decomodel->params.gf.high = 0; + break; + case RGBM: + decomodel->type = DC_DECOMODEL_RGBM; + decomodel->conservatism = 0; + break; + default: + return DC_STATUS_DATAFORMAT; + } + break; case DC_FIELD_GASMIX_COUNT: *((unsigned int *) value) = parser->ngasmixes; break; diff --git a/src/seac_screen_parser.c b/src/seac_screen_parser.c index 28988e4..46e3303 100644 --- a/src/seac_screen_parser.c +++ b/src/seac_screen_parser.c @@ -44,6 +44,8 @@ struct seac_screen_parser_t { unsigned int cached; unsigned int ngasmixes; unsigned int oxygen[NGASMIXES]; + unsigned int gf_low; + unsigned int gf_high; }; static dc_status_t seac_screen_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); @@ -82,6 +84,8 @@ seac_screen_parser_create (dc_parser_t **out, dc_context_t *context) for (unsigned int i = 0; i < NGASMIXES; ++i) { parser->oxygen[i] = 0; } + parser->gf_low = 0; + parser->gf_high = 0; *out = (dc_parser_t *) parser; @@ -99,6 +103,8 @@ seac_screen_parser_set_data (dc_parser_t *abstract, const unsigned char *data, u for (unsigned int i = 0; i < NGASMIXES; ++i) { parser->oxygen[i] = 0; } + parser->gf_low = 0; + parser->gf_high = 0; return DC_STATUS_SUCCESS; } @@ -207,6 +213,7 @@ seac_screen_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsig } dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; if (value) { switch (type) { @@ -249,6 +256,12 @@ seac_screen_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsig return DC_STATUS_DATAFORMAT; } break; + case DC_FIELD_DECOMODEL: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->conservatism = 0; + decomodel->params.gf.low = parser->gf_low; + decomodel->params.gf.high = parser->gf_high; + break; default: return DC_STATUS_UNSUPPORTED; } @@ -279,6 +292,9 @@ seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t unsigned int oxygen[NGASMIXES] = {0}; unsigned int o2_previous = INVALID; + unsigned int gf_low = 0; + unsigned int gf_high = 0; + unsigned int time = 0; unsigned int offset = SZ_HEADER; while (offset + SZ_SAMPLE <= size) { @@ -298,6 +314,8 @@ seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t unsigned int decotime = array_uint16_le (data + offset + 0x10); unsigned int ndl_tts = array_uint16_le (data + offset + 0x12); unsigned int cns = array_uint16_le (data + offset + 0x16); + unsigned int gf_hi = data[offset + 0x3B]; + unsigned int gf_lo = data[offset + 0x3C]; if (id != dive_id) { ERROR (abstract->context, "Unexpected sample id (%u %u).", dive_id, id); @@ -362,6 +380,12 @@ seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t sample.cns = cns / 100.0; if (callback) callback (DC_SAMPLE_CNS, sample, userdata); + // Deco model + if (gf_low == 0 && gf_high == 0) { + gf_low = gf_lo; + gf_high = gf_hi; + } + offset += SZ_SAMPLE; } @@ -370,6 +394,8 @@ seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t parser->oxygen[i] = oxygen[i]; } parser->ngasmixes = ngasmixes; + parser->gf_low = gf_low; + parser->gf_high = gf_high; parser->cached = 1; return DC_STATUS_SUCCESS; diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index 28c4915..5113f75 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -78,6 +78,11 @@ #define M_OC_REC 6 #define M_FREEDIVE 7 +#define GF 0 +#define VPMB 1 +#define VPMB_GFS 2 +#define DCIEM 3 + #define METRIC 0 #define IMPERIAL 1 @@ -669,9 +674,13 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ if (rc != DC_STATUS_SUCCESS) return rc; + unsigned int decomodel_idx = parser->pnf ? parser->opening[2] + 18 : 67; + unsigned int gf_idx = parser->pnf ? parser->opening[0] + 4 : 4; + dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_tank_t *tank = (dc_tank_t *) value; dc_salinity_t *water = (dc_salinity_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; if (value) { switch (type) { @@ -742,6 +751,27 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ return DC_STATUS_DATAFORMAT; } break; + case DC_FIELD_DECOMODEL: + switch (data[decomodel_idx]) { + case GF: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->conservatism = 0; + decomodel->params.gf.low = data[gf_idx + 0]; + decomodel->params.gf.high = data[gf_idx + 1]; + break; + case VPMB: + case VPMB_GFS: + decomodel->type = DC_DECOMODEL_VPM; + decomodel->conservatism = data[decomodel_idx + 1]; + break; + case DCIEM: + decomodel->type = DC_DECOMODEL_DCIEM; + decomodel->conservatism = 0; + break; + default: + return DC_STATUS_DATAFORMAT; + } + break; default: return DC_STATUS_UNSUPPORTED; } diff --git a/src/suunto_d9_parser.c b/src/suunto_d9_parser.c index 7fc05fd..32e28ea 100644 --- a/src/suunto_d9_parser.c +++ b/src/suunto_d9_parser.c @@ -361,6 +361,7 @@ suunto_d9_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigne return rc; dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; if (value) { switch (type) { @@ -408,6 +409,20 @@ suunto_d9_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigne return DC_STATUS_DATAFORMAT; } break; + case DC_FIELD_DECOMODEL: + decomodel->type = DC_DECOMODEL_RGBM; + if (parser->model == D4i ||parser->model == D6i || + parser->model == D9tx || parser->model == ZOOPNOVO_A || + parser->model == ZOOPNOVO_B || parser->model == VYPERNOVO || + parser->model == D4F) + decomodel->conservatism = data[0x21] - 2; + else if (parser->model == HELO2) + decomodel->conservatism = data[0x23] - 2; + else if (parser->model == DX) + decomodel->conservatism = data[0x25] - 2; + else + decomodel->conservatism = data[0x1E]; + break; default: return DC_STATUS_UNSUPPORTED; } diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index de00001..cc9276a 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -90,6 +90,7 @@ typedef struct suunto_eonsteel_parser_t { dc_tankvolume_t tankinfo[MAXGASES]; double tanksize[MAXGASES]; double tankworkingpressure[MAXGASES]; + dc_decomodel_t decomodel; } cache; } suunto_eonsteel_parser_t; @@ -1095,6 +1096,9 @@ suunto_eonsteel_parser_get_field(dc_parser_t *parser, dc_field_type_t type, unsi tank->type = DC_TANKVOLUME_IMPERIAL; } break; + case DC_FIELD_DECOMODEL: + field_value(value, eon->cache.decomodel); + break; default: return DC_STATUS_UNSUPPORTED; } @@ -1341,6 +1345,20 @@ static int traverse_diving_fields(suunto_eonsteel_parser_t *eon, const struct ty return 0; } + if (!strcmp(name, "Algorithm")) { + if (!strcmp((const char *)data, "Suunto Fused RGBM")) { + eon->cache.decomodel.type = DC_DECOMODEL_RGBM; + eon->cache.initialized |= 1 << DC_FIELD_DECOMODEL; + } + return 0; + } + + if (!strcmp(name, "Conservatism")) { + eon->cache.decomodel.conservatism = *(const signed char *)data; + eon->cache.initialized |= 1 << DC_FIELD_DECOMODEL; + return 0; + } + if (!strcmp(name, "LowSetPoint")) { unsigned int pressure = array_uint32_le(data); // in SI units - Pascal eon->cache.lowsetpoint = pressure / 100000.0; // bar diff --git a/src/suunto_vyper_parser.c b/src/suunto_vyper_parser.c index 369ad5c..d7af05c 100644 --- a/src/suunto_vyper_parser.c +++ b/src/suunto_vyper_parser.c @@ -238,6 +238,7 @@ suunto_vyper_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi dc_gasmix_t *gas = (dc_gasmix_t *) value; dc_tank_t *tank = (dc_tank_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; // Cache the data. dc_status_t rc = suunto_vyper_parser_cache (parser); @@ -297,6 +298,10 @@ suunto_vyper_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi *((dc_divemode_t *) value) = DC_DIVEMODE_OC; } break; + case DC_FIELD_DECOMODEL: + decomodel->type = DC_DECOMODEL_RGBM; + decomodel->conservatism = (data[4] & 0x0F) / 3; + break; default: return DC_STATUS_UNSUPPORTED; } From 18f06ea585638ca71c78985c22eadbb53bb94a7b Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 4 Jul 2022 21:09:15 +0200 Subject: [PATCH 03/58] Add a macro for the default density and atmospheric pressure Replace the hardcoded default values with a macro defined in a central location. This makes it much easier to adjust the values if necessary. --- src/atomics_cobalt_parser.c | 2 +- src/parser-private.h | 4 ++++ src/reefnet_sensus_parser.c | 4 ++-- src/reefnet_sensuspro_parser.c | 4 ++-- src/reefnet_sensusultra_parser.c | 4 ++-- src/shearwater_predator_parser.c | 8 ++++---- 6 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/atomics_cobalt_parser.c b/src/atomics_cobalt_parser.c index ace7b7a..545b500 100644 --- a/src/atomics_cobalt_parser.c +++ b/src/atomics_cobalt_parser.c @@ -75,7 +75,7 @@ atomics_cobalt_parser_create (dc_parser_t **out, dc_context_t *context) } // Set the default values. - parser->hydrostatic = 1025.0 * GRAVITY; + parser->hydrostatic = DEF_DENSITY_SALT * GRAVITY; *out = (dc_parser_t*) parser; diff --git a/src/parser-private.h b/src/parser-private.h index eed9d51..497b1b1 100644 --- a/src/parser-private.h +++ b/src/parser-private.h @@ -25,6 +25,10 @@ #include #include +#define DEF_DENSITY_FRESH 1000.0 +#define DEF_DENSITY_SALT 1025.0 +#define DEF_ATMOSPHERIC ATM + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ diff --git a/src/reefnet_sensus_parser.c b/src/reefnet_sensus_parser.c index 35fb952..5b3cf7b 100644 --- a/src/reefnet_sensus_parser.c +++ b/src/reefnet_sensus_parser.c @@ -80,8 +80,8 @@ reefnet_sensus_parser_create (dc_parser_t **out, dc_context_t *context, unsigned } // Set the default values. - parser->atmospheric = ATM; - parser->hydrostatic = 1025.0 * GRAVITY; + parser->atmospheric = DEF_ATMOSPHERIC; + parser->hydrostatic = DEF_DENSITY_SALT * GRAVITY; parser->devtime = devtime; parser->systime = systime; parser->cached = 0; diff --git a/src/reefnet_sensuspro_parser.c b/src/reefnet_sensuspro_parser.c index c303ee9..267c99e 100644 --- a/src/reefnet_sensuspro_parser.c +++ b/src/reefnet_sensuspro_parser.c @@ -79,8 +79,8 @@ reefnet_sensuspro_parser_create (dc_parser_t **out, dc_context_t *context, unsig } // Set the default values. - parser->atmospheric = ATM; - parser->hydrostatic = 1025.0 * GRAVITY; + parser->atmospheric = DEF_ATMOSPHERIC; + parser->hydrostatic = DEF_DENSITY_SALT * GRAVITY; parser->devtime = devtime; parser->systime = systime; parser->cached = 0; diff --git a/src/reefnet_sensusultra_parser.c b/src/reefnet_sensusultra_parser.c index bad5450..da86180 100644 --- a/src/reefnet_sensusultra_parser.c +++ b/src/reefnet_sensusultra_parser.c @@ -79,8 +79,8 @@ reefnet_sensusultra_parser_create (dc_parser_t **out, dc_context_t *context, uns } // Set the default values. - parser->atmospheric = ATM; - parser->hydrostatic = 1025.0 * GRAVITY; + parser->atmospheric = DEF_ATMOSPHERIC; + parser->hydrostatic = DEF_DENSITY_SALT * GRAVITY; parser->devtime = devtime; parser->systime = systime; parser->cached = 0; diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index 5113f75..e1334d7 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -245,8 +245,8 @@ shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsig } parser->divemode = M_OC_TEC; parser->units = METRIC; - parser->density = 1025; - parser->atmospheric = ATM / (BAR / 1000); + parser->density = DEF_DENSITY_SALT; + parser->atmospheric = DEF_ATMOSPHERIC / (BAR / 1000); *out = (dc_parser_t *) parser; @@ -307,8 +307,8 @@ shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char } parser->divemode = M_OC_TEC; parser->units = METRIC; - parser->density = 1025; - parser->atmospheric = ATM / (BAR / 1000); + parser->density = DEF_DENSITY_SALT; + parser->atmospheric = DEF_ATMOSPHERIC / (BAR / 1000); return DC_STATUS_SUCCESS; } From 6ab140461a3a85fba3803283070427f3be413c79 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 23 Apr 2021 20:10:23 +0200 Subject: [PATCH 04/58] Add a public api to configure the depth calibration Some dive computers store the depth as an absolute pressure value (in bar). To convert to a depth value (in meters), the atmospheric pressure and water density are required. For dive computers that do not have those values available, libdivecomputer uses a default value. With the new public api functions, applications can adjust those default values. Some dive computers already provided a backend specific calibration function. Those functions are now deprecated. They are kept around to maintain backwards compatibility for now, but they will be removed in the next version. --- include/libdivecomputer/parser.h | 6 ++++++ src/atomics_cobalt_parser.c | 14 ++++++++++++++ src/citizen_aqualand_parser.c | 2 ++ src/cochran_commander_parser.c | 2 ++ src/cressi_edy_parser.c | 2 ++ src/cressi_goa_parser.c | 2 ++ src/cressi_leonardo_parser.c | 2 ++ src/deepsix_excursion_parser.c | 2 ++ src/diverite_nitekq_parser.c | 2 ++ src/divesystem_idive_parser.c | 2 ++ src/hw_ostc_parser.c | 2 ++ src/libdivecomputer.symbols | 2 ++ src/liquivision_lynx_parser.c | 2 ++ src/mares_darwin_parser.c | 2 ++ src/mares_iconhd_parser.c | 2 ++ src/mares_nemo_parser.c | 2 ++ src/mclean_extreme_parser.c | 2 ++ src/oceanic_atom2_parser.c | 2 ++ src/oceanic_veo250_parser.c | 2 ++ src/oceanic_vtpro_parser.c | 2 ++ src/parser-private.h | 4 ++++ src/parser.c | 26 ++++++++++++++++++++++++++ src/reefnet_sensus_parser.c | 26 ++++++++++++++++++++++++++ src/reefnet_sensuspro_parser.c | 26 ++++++++++++++++++++++++++ src/reefnet_sensusultra_parser.c | 26 ++++++++++++++++++++++++++ src/seac_screen_parser.c | 2 ++ src/shearwater_predator_parser.c | 4 ++++ src/sporasub_sp2_parser.c | 2 ++ src/suunto_d9_parser.c | 2 ++ src/suunto_eon_parser.c | 2 ++ src/suunto_eonsteel_parser.c | 2 ++ src/suunto_solution_parser.c | 2 ++ src/suunto_vyper_parser.c | 2 ++ src/tecdiving_divecomputereu_parser.c | 2 ++ src/uwatec_memomouse_parser.c | 2 ++ src/uwatec_smart_parser.c | 2 ++ 36 files changed, 188 insertions(+) diff --git a/include/libdivecomputer/parser.h b/include/libdivecomputer/parser.h index dc81317..2713d9b 100644 --- a/include/libdivecomputer/parser.h +++ b/include/libdivecomputer/parser.h @@ -270,6 +270,12 @@ dc_parser_new2 (dc_parser_t **parser, dc_context_t *context, dc_descriptor_t *de dc_family_t dc_parser_get_type (dc_parser_t *parser); +dc_status_t +dc_parser_set_atmospheric (dc_parser_t *parser, double atmospheric); + +dc_status_t +dc_parser_set_density (dc_parser_t *parser, double density); + dc_status_t dc_parser_set_data (dc_parser_t *parser, const unsigned char *data, unsigned int size); diff --git a/src/atomics_cobalt_parser.c b/src/atomics_cobalt_parser.c index 545b500..0b24bee 100644 --- a/src/atomics_cobalt_parser.c +++ b/src/atomics_cobalt_parser.c @@ -44,6 +44,7 @@ struct atomics_cobalt_parser_t { }; static dc_status_t atomics_cobalt_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t atomics_cobalt_parser_set_density (dc_parser_t *abstract, double density); static dc_status_t atomics_cobalt_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); static dc_status_t atomics_cobalt_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); static dc_status_t atomics_cobalt_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); @@ -52,6 +53,8 @@ static const dc_parser_vtable_t atomics_cobalt_parser_vtable = { sizeof(atomics_cobalt_parser_t), DC_FAMILY_ATOMICS_COBALT, atomics_cobalt_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + atomics_cobalt_parser_set_density, /* set_density */ atomics_cobalt_parser_get_datetime, /* datetime */ atomics_cobalt_parser_get_field, /* fields */ atomics_cobalt_parser_samples_foreach, /* samples_foreach */ @@ -104,6 +107,17 @@ atomics_cobalt_parser_set_calibration (dc_parser_t *abstract, double atmospheric } +static dc_status_t +atomics_cobalt_parser_set_density (dc_parser_t *abstract, double density) +{ + atomics_cobalt_parser_t *parser = (atomics_cobalt_parser_t *) abstract; + + parser->hydrostatic = density * GRAVITY; + + return DC_STATUS_SUCCESS; +} + + static dc_status_t atomics_cobalt_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) { diff --git a/src/citizen_aqualand_parser.c b/src/citizen_aqualand_parser.c index 319dcf7..806578c 100644 --- a/src/citizen_aqualand_parser.c +++ b/src/citizen_aqualand_parser.c @@ -45,6 +45,8 @@ static const dc_parser_vtable_t citizen_aqualand_parser_vtable = { sizeof(citizen_aqualand_parser_t), DC_FAMILY_CITIZEN_AQUALAND, citizen_aqualand_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ citizen_aqualand_parser_get_datetime, /* datetime */ citizen_aqualand_parser_get_field, /* fields */ citizen_aqualand_parser_samples_foreach, /* samples_foreach */ diff --git a/src/cochran_commander_parser.c b/src/cochran_commander_parser.c index 853b01b..f1906ae 100644 --- a/src/cochran_commander_parser.c +++ b/src/cochran_commander_parser.c @@ -108,6 +108,8 @@ static const dc_parser_vtable_t cochran_commander_parser_vtable = { sizeof(cochran_commander_parser_t), DC_FAMILY_COCHRAN_COMMANDER, cochran_commander_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ cochran_commander_parser_get_datetime, /* datetime */ cochran_commander_parser_get_field, /* fields */ cochran_commander_parser_samples_foreach, /* samples_foreach */ diff --git a/src/cressi_edy_parser.c b/src/cressi_edy_parser.c index eaf55c3..9d41635 100644 --- a/src/cressi_edy_parser.c +++ b/src/cressi_edy_parser.c @@ -47,6 +47,8 @@ static const dc_parser_vtable_t cressi_edy_parser_vtable = { sizeof(cressi_edy_parser_t), DC_FAMILY_CRESSI_EDY, cressi_edy_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ cressi_edy_parser_get_datetime, /* datetime */ cressi_edy_parser_get_field, /* fields */ cressi_edy_parser_samples_foreach, /* samples_foreach */ diff --git a/src/cressi_goa_parser.c b/src/cressi_goa_parser.c index 53263b1..8e25d5f 100644 --- a/src/cressi_goa_parser.c +++ b/src/cressi_goa_parser.c @@ -64,6 +64,8 @@ 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 */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ cressi_goa_parser_get_datetime, /* datetime */ cressi_goa_parser_get_field, /* fields */ cressi_goa_parser_samples_foreach, /* samples_foreach */ diff --git a/src/cressi_leonardo_parser.c b/src/cressi_leonardo_parser.c index d4f37f5..1899876 100644 --- a/src/cressi_leonardo_parser.c +++ b/src/cressi_leonardo_parser.c @@ -48,6 +48,8 @@ static const dc_parser_vtable_t cressi_leonardo_parser_vtable = { sizeof(cressi_leonardo_parser_t), DC_FAMILY_CRESSI_LEONARDO, cressi_leonardo_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ cressi_leonardo_parser_get_datetime, /* datetime */ cressi_leonardo_parser_get_field, /* fields */ cressi_leonardo_parser_samples_foreach, /* samples_foreach */ diff --git a/src/deepsix_excursion_parser.c b/src/deepsix_excursion_parser.c index a5d312d..ad8aca0 100644 --- a/src/deepsix_excursion_parser.c +++ b/src/deepsix_excursion_parser.c @@ -54,6 +54,8 @@ static const dc_parser_vtable_t deepsix_parser_vtable = { sizeof(deepsix_excursion_parser_t), DC_FAMILY_DEEPSIX_EXCURSION, deepsix_excursion_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ deepsix_excursion_parser_get_datetime, /* datetime */ deepsix_excursion_parser_get_field, /* fields */ deepsix_excursion_parser_samples_foreach, /* samples_foreach */ diff --git a/src/diverite_nitekq_parser.c b/src/diverite_nitekq_parser.c index a09b0e5..a4a13cf 100644 --- a/src/diverite_nitekq_parser.c +++ b/src/diverite_nitekq_parser.c @@ -58,6 +58,8 @@ static const dc_parser_vtable_t diverite_nitekq_parser_vtable = { sizeof(diverite_nitekq_parser_t), DC_FAMILY_DIVERITE_NITEKQ, diverite_nitekq_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ diverite_nitekq_parser_get_datetime, /* datetime */ diverite_nitekq_parser_get_field, /* fields */ diverite_nitekq_parser_samples_foreach, /* samples_foreach */ diff --git a/src/divesystem_idive_parser.c b/src/divesystem_idive_parser.c index f0d7700..c19bfd4 100644 --- a/src/divesystem_idive_parser.c +++ b/src/divesystem_idive_parser.c @@ -91,6 +91,8 @@ static const dc_parser_vtable_t divesystem_idive_parser_vtable = { sizeof(divesystem_idive_parser_t), DC_FAMILY_DIVESYSTEM_IDIVE, divesystem_idive_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ divesystem_idive_parser_get_datetime, /* datetime */ divesystem_idive_parser_get_field, /* fields */ divesystem_idive_parser_samples_foreach, /* samples_foreach */ diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index 06470d7..343cc6d 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -137,6 +137,8 @@ static const dc_parser_vtable_t hw_ostc_parser_vtable = { sizeof(hw_ostc_parser_t), DC_FAMILY_HW_OSTC, hw_ostc_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ hw_ostc_parser_get_datetime, /* datetime */ hw_ostc_parser_get_field, /* fields */ hw_ostc_parser_samples_foreach, /* samples_foreach */ diff --git a/src/libdivecomputer.symbols b/src/libdivecomputer.symbols index 800e217..a8cb63c 100644 --- a/src/libdivecomputer.symbols +++ b/src/libdivecomputer.symbols @@ -87,6 +87,8 @@ dc_custom_open dc_parser_new dc_parser_new2 +dc_parser_set_atmospheric +dc_parser_set_density dc_parser_get_type dc_parser_set_data dc_parser_get_datetime diff --git a/src/liquivision_lynx_parser.c b/src/liquivision_lynx_parser.c index 2ff52cb..8009ff8 100644 --- a/src/liquivision_lynx_parser.c +++ b/src/liquivision_lynx_parser.c @@ -126,6 +126,8 @@ static const dc_parser_vtable_t liquivision_lynx_parser_vtable = { sizeof(liquivision_lynx_parser_t), DC_FAMILY_LIQUIVISION_LYNX, liquivision_lynx_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ liquivision_lynx_parser_get_datetime, /* datetime */ liquivision_lynx_parser_get_field, /* fields */ liquivision_lynx_parser_samples_foreach, /* samples_foreach */ diff --git a/src/mares_darwin_parser.c b/src/mares_darwin_parser.c index ba0382f..3bca55c 100644 --- a/src/mares_darwin_parser.c +++ b/src/mares_darwin_parser.c @@ -56,6 +56,8 @@ static const dc_parser_vtable_t mares_darwin_parser_vtable = { sizeof(mares_darwin_parser_t), DC_FAMILY_MARES_DARWIN, mares_darwin_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ mares_darwin_parser_get_datetime, /* datetime */ mares_darwin_parser_get_field, /* fields */ mares_darwin_parser_samples_foreach, /* samples_foreach */ diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index 93cc5f2..5398cfc 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -273,6 +273,8 @@ static const dc_parser_vtable_t mares_iconhd_parser_vtable = { sizeof(mares_iconhd_parser_t), DC_FAMILY_MARES_ICONHD, mares_iconhd_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ mares_iconhd_parser_get_datetime, /* datetime */ mares_iconhd_parser_get_field, /* fields */ mares_iconhd_parser_samples_foreach, /* samples_foreach */ diff --git a/src/mares_nemo_parser.c b/src/mares_nemo_parser.c index c0ab03b..85957a0 100644 --- a/src/mares_nemo_parser.c +++ b/src/mares_nemo_parser.c @@ -68,6 +68,8 @@ static const dc_parser_vtable_t mares_nemo_parser_vtable = { sizeof(mares_nemo_parser_t), DC_FAMILY_MARES_NEMO, mares_nemo_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ mares_nemo_parser_get_datetime, /* datetime */ mares_nemo_parser_get_field, /* fields */ mares_nemo_parser_samples_foreach, /* samples_foreach */ diff --git a/src/mclean_extreme_parser.c b/src/mclean_extreme_parser.c index 5fef8aa..5f99fda 100644 --- a/src/mclean_extreme_parser.c +++ b/src/mclean_extreme_parser.c @@ -67,6 +67,8 @@ static const dc_parser_vtable_t mclean_extreme_parser_vtable = { sizeof(mclean_extreme_parser_t), DC_FAMILY_MCLEAN_EXTREME, mclean_extreme_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ mclean_extreme_parser_get_datetime, /* datetime */ mclean_extreme_parser_get_field, /* fields */ mclean_extreme_parser_samples_foreach, /* samples_foreach */ diff --git a/src/oceanic_atom2_parser.c b/src/oceanic_atom2_parser.c index bd89f9c..90a9cfc 100644 --- a/src/oceanic_atom2_parser.c +++ b/src/oceanic_atom2_parser.c @@ -140,6 +140,8 @@ static const dc_parser_vtable_t oceanic_atom2_parser_vtable = { sizeof(oceanic_atom2_parser_t), DC_FAMILY_OCEANIC_ATOM2, oceanic_atom2_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ oceanic_atom2_parser_get_datetime, /* datetime */ oceanic_atom2_parser_get_field, /* fields */ oceanic_atom2_parser_samples_foreach, /* samples_foreach */ diff --git a/src/oceanic_veo250_parser.c b/src/oceanic_veo250_parser.c index fc7c6ed..1eff473 100644 --- a/src/oceanic_veo250_parser.c +++ b/src/oceanic_veo250_parser.c @@ -57,6 +57,8 @@ static const dc_parser_vtable_t oceanic_veo250_parser_vtable = { sizeof(oceanic_veo250_parser_t), DC_FAMILY_OCEANIC_VEO250, oceanic_veo250_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ oceanic_veo250_parser_get_datetime, /* datetime */ oceanic_veo250_parser_get_field, /* fields */ oceanic_veo250_parser_samples_foreach, /* samples_foreach */ diff --git a/src/oceanic_vtpro_parser.c b/src/oceanic_vtpro_parser.c index fff7389..6c8d200 100644 --- a/src/oceanic_vtpro_parser.c +++ b/src/oceanic_vtpro_parser.c @@ -53,6 +53,8 @@ static const dc_parser_vtable_t oceanic_vtpro_parser_vtable = { sizeof(oceanic_vtpro_parser_t), DC_FAMILY_OCEANIC_VTPRO, oceanic_vtpro_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ oceanic_vtpro_parser_get_datetime, /* datetime */ oceanic_vtpro_parser_get_field, /* fields */ oceanic_vtpro_parser_samples_foreach, /* samples_foreach */ diff --git a/src/parser-private.h b/src/parser-private.h index 497b1b1..73ef161 100644 --- a/src/parser-private.h +++ b/src/parser-private.h @@ -52,6 +52,10 @@ struct dc_parser_vtable_t { dc_status_t (*set_data) (dc_parser_t *parser, const unsigned char *data, unsigned int size); + dc_status_t (*set_atmospheric) (dc_parser_t *parser, double atmospheric); + + dc_status_t (*set_density) (dc_parser_t *parser, double density); + dc_status_t (*datetime) (dc_parser_t *parser, dc_datetime_t *datetime); dc_status_t (*field) (dc_parser_t *parser, dc_field_type_t type, unsigned int flags, void *value); diff --git a/src/parser.c b/src/parser.c index 0750eb1..1f74ea8 100644 --- a/src/parser.c +++ b/src/parser.c @@ -270,6 +270,32 @@ dc_parser_get_type (dc_parser_t *parser) } +dc_status_t +dc_parser_set_atmospheric (dc_parser_t *parser, double atmospheric) +{ + if (parser == NULL) + return DC_STATUS_UNSUPPORTED; + + if (parser->vtable->set_atmospheric == NULL) + return DC_STATUS_UNSUPPORTED; + + return parser->vtable->set_atmospheric (parser, atmospheric); +} + + +dc_status_t +dc_parser_set_density (dc_parser_t *parser, double density) +{ + if (parser == NULL) + return DC_STATUS_UNSUPPORTED; + + if (parser->vtable->set_density == NULL) + return DC_STATUS_UNSUPPORTED; + + return parser->vtable->set_density (parser, density); +} + + dc_status_t dc_parser_set_data (dc_parser_t *parser, const unsigned char *data, unsigned int size) { diff --git a/src/reefnet_sensus_parser.c b/src/reefnet_sensus_parser.c index 5b3cf7b..4af1660 100644 --- a/src/reefnet_sensus_parser.c +++ b/src/reefnet_sensus_parser.c @@ -49,6 +49,8 @@ struct reefnet_sensus_parser_t { }; static dc_status_t reefnet_sensus_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t reefnet_sensus_parser_set_atmospheric (dc_parser_t *abstract, double atmospheric); +static dc_status_t reefnet_sensus_parser_set_density (dc_parser_t *abstract, double density); static dc_status_t reefnet_sensus_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); static dc_status_t reefnet_sensus_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); static dc_status_t reefnet_sensus_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); @@ -57,6 +59,8 @@ static const dc_parser_vtable_t reefnet_sensus_parser_vtable = { sizeof(reefnet_sensus_parser_t), DC_FAMILY_REEFNET_SENSUS, reefnet_sensus_parser_set_data, /* set_data */ + reefnet_sensus_parser_set_atmospheric, /* set_atmospheric */ + reefnet_sensus_parser_set_density, /* set_density */ reefnet_sensus_parser_get_datetime, /* datetime */ reefnet_sensus_parser_get_field, /* fields */ reefnet_sensus_parser_samples_foreach, /* samples_foreach */ @@ -123,6 +127,28 @@ reefnet_sensus_parser_set_calibration (dc_parser_t *abstract, double atmospheric } +static dc_status_t +reefnet_sensus_parser_set_atmospheric (dc_parser_t *abstract, double atmospheric) +{ + reefnet_sensus_parser_t *parser = (reefnet_sensus_parser_t *) abstract; + + parser->atmospheric = atmospheric; + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +reefnet_sensus_parser_set_density (dc_parser_t *abstract, double density) +{ + reefnet_sensus_parser_t *parser = (reefnet_sensus_parser_t *) abstract; + + parser->hydrostatic = density * GRAVITY; + + return DC_STATUS_SUCCESS; +} + + static dc_status_t reefnet_sensus_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) { diff --git a/src/reefnet_sensuspro_parser.c b/src/reefnet_sensuspro_parser.c index 267c99e..f8f23eb 100644 --- a/src/reefnet_sensuspro_parser.c +++ b/src/reefnet_sensuspro_parser.c @@ -48,6 +48,8 @@ struct reefnet_sensuspro_parser_t { }; static dc_status_t reefnet_sensuspro_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t reefnet_sensuspro_parser_set_atmospheric (dc_parser_t *abstract, double atmospheric); +static dc_status_t reefnet_sensuspro_parser_set_density (dc_parser_t *abstract, double density); static dc_status_t reefnet_sensuspro_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); static dc_status_t reefnet_sensuspro_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); static dc_status_t reefnet_sensuspro_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); @@ -56,6 +58,8 @@ static const dc_parser_vtable_t reefnet_sensuspro_parser_vtable = { sizeof(reefnet_sensuspro_parser_t), DC_FAMILY_REEFNET_SENSUSPRO, reefnet_sensuspro_parser_set_data, /* set_data */ + reefnet_sensuspro_parser_set_atmospheric, /* set_atmospheric */ + reefnet_sensuspro_parser_set_density, /* set_density */ reefnet_sensuspro_parser_get_datetime, /* datetime */ reefnet_sensuspro_parser_get_field, /* fields */ reefnet_sensuspro_parser_samples_foreach, /* samples_foreach */ @@ -122,6 +126,28 @@ reefnet_sensuspro_parser_set_calibration (dc_parser_t *abstract, double atmosphe } +static dc_status_t +reefnet_sensuspro_parser_set_atmospheric (dc_parser_t *abstract, double atmospheric) +{ + reefnet_sensuspro_parser_t *parser = (reefnet_sensuspro_parser_t *) abstract; + + parser->atmospheric = atmospheric; + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +reefnet_sensuspro_parser_set_density (dc_parser_t *abstract, double density) +{ + reefnet_sensuspro_parser_t *parser = (reefnet_sensuspro_parser_t *) abstract; + + parser->hydrostatic = density * GRAVITY; + + return DC_STATUS_SUCCESS; +} + + static dc_status_t reefnet_sensuspro_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) { diff --git a/src/reefnet_sensusultra_parser.c b/src/reefnet_sensusultra_parser.c index da86180..f19364e 100644 --- a/src/reefnet_sensusultra_parser.c +++ b/src/reefnet_sensusultra_parser.c @@ -48,6 +48,8 @@ struct reefnet_sensusultra_parser_t { }; static dc_status_t reefnet_sensusultra_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t reefnet_sensusultra_parser_set_atmospheric (dc_parser_t *abstract, double atmospheric); +static dc_status_t reefnet_sensusultra_parser_set_density (dc_parser_t *abstract, double density); static dc_status_t reefnet_sensusultra_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); static dc_status_t reefnet_sensusultra_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); static dc_status_t reefnet_sensusultra_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); @@ -56,6 +58,8 @@ static const dc_parser_vtable_t reefnet_sensusultra_parser_vtable = { sizeof(reefnet_sensusultra_parser_t), DC_FAMILY_REEFNET_SENSUSULTRA, reefnet_sensusultra_parser_set_data, /* set_data */ + reefnet_sensusultra_parser_set_atmospheric, /* set_atmospheric */ + reefnet_sensusultra_parser_set_density, /* set_density */ reefnet_sensusultra_parser_get_datetime, /* datetime */ reefnet_sensusultra_parser_get_field, /* fields */ reefnet_sensusultra_parser_samples_foreach, /* samples_foreach */ @@ -122,6 +126,28 @@ reefnet_sensusultra_parser_set_calibration (dc_parser_t *abstract, double atmosp } +static dc_status_t +reefnet_sensusultra_parser_set_atmospheric (dc_parser_t *abstract, double atmospheric) +{ + reefnet_sensusultra_parser_t *parser = (reefnet_sensusultra_parser_t *) abstract; + + parser->atmospheric = atmospheric; + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +reefnet_sensusultra_parser_set_density (dc_parser_t *abstract, double density) +{ + reefnet_sensusultra_parser_t *parser = (reefnet_sensusultra_parser_t *) abstract; + + parser->hydrostatic = density * GRAVITY; + + return DC_STATUS_SUCCESS; +} + + static dc_status_t reefnet_sensusultra_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) { diff --git a/src/seac_screen_parser.c b/src/seac_screen_parser.c index 46e3303..2a8647d 100644 --- a/src/seac_screen_parser.c +++ b/src/seac_screen_parser.c @@ -57,6 +57,8 @@ static const dc_parser_vtable_t seac_screen_parser_vtable = { sizeof(seac_screen_parser_t), DC_FAMILY_SEAC_SCREEN, seac_screen_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ seac_screen_parser_get_datetime, /* datetime */ seac_screen_parser_get_field, /* fields */ seac_screen_parser_samples_foreach, /* samples_foreach */ diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index e1334d7..b658e6e 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -152,6 +152,8 @@ static const dc_parser_vtable_t shearwater_predator_parser_vtable = { sizeof(shearwater_predator_parser_t), DC_FAMILY_SHEARWATER_PREDATOR, shearwater_predator_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ shearwater_predator_parser_get_datetime, /* datetime */ shearwater_predator_parser_get_field, /* fields */ shearwater_predator_parser_samples_foreach, /* samples_foreach */ @@ -162,6 +164,8 @@ static const dc_parser_vtable_t shearwater_petrel_parser_vtable = { sizeof(shearwater_predator_parser_t), DC_FAMILY_SHEARWATER_PETREL, shearwater_predator_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ shearwater_predator_parser_get_datetime, /* datetime */ shearwater_predator_parser_get_field, /* fields */ shearwater_predator_parser_samples_foreach, /* samples_foreach */ diff --git a/src/sporasub_sp2_parser.c b/src/sporasub_sp2_parser.c index 42cde0c..3869a39 100644 --- a/src/sporasub_sp2_parser.c +++ b/src/sporasub_sp2_parser.c @@ -46,6 +46,8 @@ static const dc_parser_vtable_t sporasub_sp2_parser_vtable = { sizeof(sporasub_sp2_parser_t), DC_FAMILY_SPORASUB_SP2, sporasub_sp2_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ sporasub_sp2_parser_get_datetime, /* datetime */ sporasub_sp2_parser_get_field, /* fields */ sporasub_sp2_parser_samples_foreach, /* samples_foreach */ diff --git a/src/suunto_d9_parser.c b/src/suunto_d9_parser.c index 32e28ea..164b01e 100644 --- a/src/suunto_d9_parser.c +++ b/src/suunto_d9_parser.c @@ -101,6 +101,8 @@ static const dc_parser_vtable_t suunto_d9_parser_vtable = { sizeof(suunto_d9_parser_t), DC_FAMILY_SUUNTO_D9, suunto_d9_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ suunto_d9_parser_get_datetime, /* datetime */ suunto_d9_parser_get_field, /* fields */ suunto_d9_parser_samples_foreach, /* samples_foreach */ diff --git a/src/suunto_eon_parser.c b/src/suunto_eon_parser.c index 2106c69..28d94c2 100644 --- a/src/suunto_eon_parser.c +++ b/src/suunto_eon_parser.c @@ -52,6 +52,8 @@ static const dc_parser_vtable_t suunto_eon_parser_vtable = { sizeof(suunto_eon_parser_t), DC_FAMILY_SUUNTO_EON, suunto_eon_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ suunto_eon_parser_get_datetime, /* datetime */ suunto_eon_parser_get_field, /* fields */ suunto_eon_parser_samples_foreach, /* samples_foreach */ diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index cc9276a..fa9d880 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -1525,6 +1525,8 @@ static const dc_parser_vtable_t suunto_eonsteel_parser_vtable = { sizeof(suunto_eonsteel_parser_t), DC_FAMILY_SUUNTO_EONSTEEL, suunto_eonsteel_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ suunto_eonsteel_parser_get_datetime, /* datetime */ suunto_eonsteel_parser_get_field, /* fields */ suunto_eonsteel_parser_samples_foreach, /* samples_foreach */ diff --git a/src/suunto_solution_parser.c b/src/suunto_solution_parser.c index 34aab7d..440a067 100644 --- a/src/suunto_solution_parser.c +++ b/src/suunto_solution_parser.c @@ -47,6 +47,8 @@ static const dc_parser_vtable_t suunto_solution_parser_vtable = { sizeof(suunto_solution_parser_t), DC_FAMILY_SUUNTO_SOLUTION, suunto_solution_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ NULL, /* datetime */ suunto_solution_parser_get_field, /* fields */ suunto_solution_parser_samples_foreach, /* samples_foreach */ diff --git a/src/suunto_vyper_parser.c b/src/suunto_vyper_parser.c index d7af05c..e14ffc8 100644 --- a/src/suunto_vyper_parser.c +++ b/src/suunto_vyper_parser.c @@ -53,6 +53,8 @@ static const dc_parser_vtable_t suunto_vyper_parser_vtable = { sizeof(suunto_vyper_parser_t), DC_FAMILY_SUUNTO_VYPER, suunto_vyper_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ suunto_vyper_parser_get_datetime, /* datetime */ suunto_vyper_parser_get_field, /* fields */ suunto_vyper_parser_samples_foreach, /* samples_foreach */ diff --git a/src/tecdiving_divecomputereu_parser.c b/src/tecdiving_divecomputereu_parser.c index a3006ea..7b8d76b 100644 --- a/src/tecdiving_divecomputereu_parser.c +++ b/src/tecdiving_divecomputereu_parser.c @@ -45,6 +45,8 @@ static const dc_parser_vtable_t tecdiving_divecomputereu_parser_vtable = { sizeof(tecdiving_divecomputereu_parser_t), DC_FAMILY_TECDIVING_DIVECOMPUTEREU, tecdiving_divecomputereu_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ tecdiving_divecomputereu_parser_get_datetime, /* datetime */ tecdiving_divecomputereu_parser_get_field, /* fields */ tecdiving_divecomputereu_parser_samples_foreach, /* samples_foreach */ diff --git a/src/uwatec_memomouse_parser.c b/src/uwatec_memomouse_parser.c index 386e984..ae2267c 100644 --- a/src/uwatec_memomouse_parser.c +++ b/src/uwatec_memomouse_parser.c @@ -47,6 +47,8 @@ static const dc_parser_vtable_t uwatec_memomouse_parser_vtable = { sizeof(uwatec_memomouse_parser_t), DC_FAMILY_UWATEC_MEMOMOUSE, uwatec_memomouse_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ uwatec_memomouse_parser_get_datetime, /* datetime */ uwatec_memomouse_parser_get_field, /* fields */ uwatec_memomouse_parser_samples_foreach, /* samples_foreach */ diff --git a/src/uwatec_smart_parser.c b/src/uwatec_smart_parser.c index 74e6317..ec1a25e 100644 --- a/src/uwatec_smart_parser.c +++ b/src/uwatec_smart_parser.c @@ -167,6 +167,8 @@ static const dc_parser_vtable_t uwatec_smart_parser_vtable = { sizeof(uwatec_smart_parser_t), DC_FAMILY_UWATEC_SMART, uwatec_smart_parser_set_data, /* set_data */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ uwatec_smart_parser_get_datetime, /* datetime */ uwatec_smart_parser_get_field, /* fields */ uwatec_smart_parser_samples_foreach, /* samples_foreach */ From 12c77a228e84f1ceed520b6afb53b4b64ea9def6 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 23 Apr 2021 21:10:41 +0200 Subject: [PATCH 05/58] Add a public api to configure the clock synchronization For dive computers where the reference time (epoch) of the device is unknown, libdivecomputer uses the current time of the device (devtime) and the host system (systime) to synchronize both clocks. Currently, both timestamps are passed directly to the constructor of the parser. With the new public function, the application can adjust the timestamps afterwards. --- include/libdivecomputer/parser.h | 3 +++ src/atomics_cobalt_parser.c | 1 + src/citizen_aqualand_parser.c | 1 + src/cochran_commander_parser.c | 1 + src/cressi_edy_parser.c | 1 + src/cressi_goa_parser.c | 1 + src/cressi_leonardo_parser.c | 1 + src/deepsix_excursion_parser.c | 1 + src/diverite_nitekq_parser.c | 1 + src/divesystem_idive_parser.c | 1 + src/hw_ostc_parser.c | 1 + src/libdivecomputer.symbols | 1 + src/liquivision_lynx_parser.c | 1 + src/mares_darwin_parser.c | 1 + src/mares_iconhd_parser.c | 1 + src/mares_nemo_parser.c | 1 + src/mclean_extreme_parser.c | 1 + src/oceanic_atom2_parser.c | 1 + src/oceanic_veo250_parser.c | 1 + src/oceanic_vtpro_parser.c | 1 + src/parser-private.h | 2 ++ src/parser.c | 13 +++++++++++++ src/reefnet_sensus_parser.c | 13 +++++++++++++ src/reefnet_sensuspro_parser.c | 14 ++++++++++++++ src/reefnet_sensusultra_parser.c | 14 ++++++++++++++ src/seac_screen_parser.c | 1 + src/shearwater_predator_parser.c | 2 ++ src/sporasub_sp2_parser.c | 1 + src/suunto_d9_parser.c | 1 + src/suunto_eon_parser.c | 1 + src/suunto_eonsteel_parser.c | 1 + src/suunto_solution_parser.c | 1 + src/suunto_vyper_parser.c | 1 + src/tecdiving_divecomputereu_parser.c | 1 + src/uwatec_memomouse_parser.c | 14 ++++++++++++++ src/uwatec_smart_parser.c | 1 + 36 files changed, 103 insertions(+) diff --git a/include/libdivecomputer/parser.h b/include/libdivecomputer/parser.h index 2713d9b..6c49d0b 100644 --- a/include/libdivecomputer/parser.h +++ b/include/libdivecomputer/parser.h @@ -270,6 +270,9 @@ dc_parser_new2 (dc_parser_t **parser, dc_context_t *context, dc_descriptor_t *de dc_family_t dc_parser_get_type (dc_parser_t *parser); +dc_status_t +dc_parser_set_clock (dc_parser_t *parser, unsigned int devtime, dc_ticks_t systime); + dc_status_t dc_parser_set_atmospheric (dc_parser_t *parser, double atmospheric); diff --git a/src/atomics_cobalt_parser.c b/src/atomics_cobalt_parser.c index 0b24bee..905b730 100644 --- a/src/atomics_cobalt_parser.c +++ b/src/atomics_cobalt_parser.c @@ -53,6 +53,7 @@ static const dc_parser_vtable_t atomics_cobalt_parser_vtable = { sizeof(atomics_cobalt_parser_t), DC_FAMILY_ATOMICS_COBALT, atomics_cobalt_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ atomics_cobalt_parser_set_density, /* set_density */ atomics_cobalt_parser_get_datetime, /* datetime */ diff --git a/src/citizen_aqualand_parser.c b/src/citizen_aqualand_parser.c index 806578c..5b95555 100644 --- a/src/citizen_aqualand_parser.c +++ b/src/citizen_aqualand_parser.c @@ -45,6 +45,7 @@ static const dc_parser_vtable_t citizen_aqualand_parser_vtable = { sizeof(citizen_aqualand_parser_t), DC_FAMILY_CITIZEN_AQUALAND, citizen_aqualand_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ citizen_aqualand_parser_get_datetime, /* datetime */ diff --git a/src/cochran_commander_parser.c b/src/cochran_commander_parser.c index f1906ae..a4515ea 100644 --- a/src/cochran_commander_parser.c +++ b/src/cochran_commander_parser.c @@ -108,6 +108,7 @@ static const dc_parser_vtable_t cochran_commander_parser_vtable = { sizeof(cochran_commander_parser_t), DC_FAMILY_COCHRAN_COMMANDER, cochran_commander_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ cochran_commander_parser_get_datetime, /* datetime */ diff --git a/src/cressi_edy_parser.c b/src/cressi_edy_parser.c index 9d41635..3817fc7 100644 --- a/src/cressi_edy_parser.c +++ b/src/cressi_edy_parser.c @@ -47,6 +47,7 @@ static const dc_parser_vtable_t cressi_edy_parser_vtable = { sizeof(cressi_edy_parser_t), DC_FAMILY_CRESSI_EDY, cressi_edy_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ cressi_edy_parser_get_datetime, /* datetime */ diff --git a/src/cressi_goa_parser.c b/src/cressi_goa_parser.c index 8e25d5f..deb44b3 100644 --- a/src/cressi_goa_parser.c +++ b/src/cressi_goa_parser.c @@ -64,6 +64,7 @@ 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 */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ cressi_goa_parser_get_datetime, /* datetime */ diff --git a/src/cressi_leonardo_parser.c b/src/cressi_leonardo_parser.c index 1899876..eefebdf 100644 --- a/src/cressi_leonardo_parser.c +++ b/src/cressi_leonardo_parser.c @@ -48,6 +48,7 @@ static const dc_parser_vtable_t cressi_leonardo_parser_vtable = { sizeof(cressi_leonardo_parser_t), DC_FAMILY_CRESSI_LEONARDO, cressi_leonardo_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ cressi_leonardo_parser_get_datetime, /* datetime */ diff --git a/src/deepsix_excursion_parser.c b/src/deepsix_excursion_parser.c index ad8aca0..8f14708 100644 --- a/src/deepsix_excursion_parser.c +++ b/src/deepsix_excursion_parser.c @@ -54,6 +54,7 @@ static const dc_parser_vtable_t deepsix_parser_vtable = { sizeof(deepsix_excursion_parser_t), DC_FAMILY_DEEPSIX_EXCURSION, deepsix_excursion_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ deepsix_excursion_parser_get_datetime, /* datetime */ diff --git a/src/diverite_nitekq_parser.c b/src/diverite_nitekq_parser.c index a4a13cf..5623769 100644 --- a/src/diverite_nitekq_parser.c +++ b/src/diverite_nitekq_parser.c @@ -58,6 +58,7 @@ static const dc_parser_vtable_t diverite_nitekq_parser_vtable = { sizeof(diverite_nitekq_parser_t), DC_FAMILY_DIVERITE_NITEKQ, diverite_nitekq_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ diverite_nitekq_parser_get_datetime, /* datetime */ diff --git a/src/divesystem_idive_parser.c b/src/divesystem_idive_parser.c index c19bfd4..1d19797 100644 --- a/src/divesystem_idive_parser.c +++ b/src/divesystem_idive_parser.c @@ -91,6 +91,7 @@ static const dc_parser_vtable_t divesystem_idive_parser_vtable = { sizeof(divesystem_idive_parser_t), DC_FAMILY_DIVESYSTEM_IDIVE, divesystem_idive_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ divesystem_idive_parser_get_datetime, /* datetime */ diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index 343cc6d..90994f8 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -137,6 +137,7 @@ static const dc_parser_vtable_t hw_ostc_parser_vtable = { sizeof(hw_ostc_parser_t), DC_FAMILY_HW_OSTC, hw_ostc_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ hw_ostc_parser_get_datetime, /* datetime */ diff --git a/src/libdivecomputer.symbols b/src/libdivecomputer.symbols index a8cb63c..a9d2d39 100644 --- a/src/libdivecomputer.symbols +++ b/src/libdivecomputer.symbols @@ -87,6 +87,7 @@ dc_custom_open dc_parser_new dc_parser_new2 +dc_parser_set_clock dc_parser_set_atmospheric dc_parser_set_density dc_parser_get_type diff --git a/src/liquivision_lynx_parser.c b/src/liquivision_lynx_parser.c index 8009ff8..764d20e 100644 --- a/src/liquivision_lynx_parser.c +++ b/src/liquivision_lynx_parser.c @@ -126,6 +126,7 @@ static const dc_parser_vtable_t liquivision_lynx_parser_vtable = { sizeof(liquivision_lynx_parser_t), DC_FAMILY_LIQUIVISION_LYNX, liquivision_lynx_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ liquivision_lynx_parser_get_datetime, /* datetime */ diff --git a/src/mares_darwin_parser.c b/src/mares_darwin_parser.c index 3bca55c..88e831c 100644 --- a/src/mares_darwin_parser.c +++ b/src/mares_darwin_parser.c @@ -56,6 +56,7 @@ static const dc_parser_vtable_t mares_darwin_parser_vtable = { sizeof(mares_darwin_parser_t), DC_FAMILY_MARES_DARWIN, mares_darwin_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ mares_darwin_parser_get_datetime, /* datetime */ diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index 5398cfc..3b17482 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -273,6 +273,7 @@ static const dc_parser_vtable_t mares_iconhd_parser_vtable = { sizeof(mares_iconhd_parser_t), DC_FAMILY_MARES_ICONHD, mares_iconhd_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ mares_iconhd_parser_get_datetime, /* datetime */ diff --git a/src/mares_nemo_parser.c b/src/mares_nemo_parser.c index 85957a0..2f26605 100644 --- a/src/mares_nemo_parser.c +++ b/src/mares_nemo_parser.c @@ -68,6 +68,7 @@ static const dc_parser_vtable_t mares_nemo_parser_vtable = { sizeof(mares_nemo_parser_t), DC_FAMILY_MARES_NEMO, mares_nemo_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ mares_nemo_parser_get_datetime, /* datetime */ diff --git a/src/mclean_extreme_parser.c b/src/mclean_extreme_parser.c index 5f99fda..694bf60 100644 --- a/src/mclean_extreme_parser.c +++ b/src/mclean_extreme_parser.c @@ -67,6 +67,7 @@ static const dc_parser_vtable_t mclean_extreme_parser_vtable = { sizeof(mclean_extreme_parser_t), DC_FAMILY_MCLEAN_EXTREME, mclean_extreme_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ mclean_extreme_parser_get_datetime, /* datetime */ diff --git a/src/oceanic_atom2_parser.c b/src/oceanic_atom2_parser.c index 90a9cfc..66bec88 100644 --- a/src/oceanic_atom2_parser.c +++ b/src/oceanic_atom2_parser.c @@ -140,6 +140,7 @@ static const dc_parser_vtable_t oceanic_atom2_parser_vtable = { sizeof(oceanic_atom2_parser_t), DC_FAMILY_OCEANIC_ATOM2, oceanic_atom2_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ oceanic_atom2_parser_get_datetime, /* datetime */ diff --git a/src/oceanic_veo250_parser.c b/src/oceanic_veo250_parser.c index 1eff473..4e59ede 100644 --- a/src/oceanic_veo250_parser.c +++ b/src/oceanic_veo250_parser.c @@ -57,6 +57,7 @@ static const dc_parser_vtable_t oceanic_veo250_parser_vtable = { sizeof(oceanic_veo250_parser_t), DC_FAMILY_OCEANIC_VEO250, oceanic_veo250_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ oceanic_veo250_parser_get_datetime, /* datetime */ diff --git a/src/oceanic_vtpro_parser.c b/src/oceanic_vtpro_parser.c index 6c8d200..1c60e62 100644 --- a/src/oceanic_vtpro_parser.c +++ b/src/oceanic_vtpro_parser.c @@ -53,6 +53,7 @@ static const dc_parser_vtable_t oceanic_vtpro_parser_vtable = { sizeof(oceanic_vtpro_parser_t), DC_FAMILY_OCEANIC_VTPRO, oceanic_vtpro_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ oceanic_vtpro_parser_get_datetime, /* datetime */ diff --git a/src/parser-private.h b/src/parser-private.h index 73ef161..af1a76e 100644 --- a/src/parser-private.h +++ b/src/parser-private.h @@ -52,6 +52,8 @@ struct dc_parser_vtable_t { dc_status_t (*set_data) (dc_parser_t *parser, const unsigned char *data, unsigned int size); + dc_status_t (*set_clock) (dc_parser_t *parser, unsigned int devtime, dc_ticks_t systime); + dc_status_t (*set_atmospheric) (dc_parser_t *parser, double atmospheric); dc_status_t (*set_density) (dc_parser_t *parser, double density); diff --git a/src/parser.c b/src/parser.c index 1f74ea8..1ae480a 100644 --- a/src/parser.c +++ b/src/parser.c @@ -270,6 +270,19 @@ dc_parser_get_type (dc_parser_t *parser) } +dc_status_t +dc_parser_set_clock (dc_parser_t *parser, unsigned int devtime, dc_ticks_t systime) +{ + if (parser == NULL) + return DC_STATUS_UNSUPPORTED; + + if (parser->vtable->set_clock == NULL) + return DC_STATUS_UNSUPPORTED; + + return parser->vtable->set_clock (parser, devtime, systime); +} + + dc_status_t dc_parser_set_atmospheric (dc_parser_t *parser, double atmospheric) { diff --git a/src/reefnet_sensus_parser.c b/src/reefnet_sensus_parser.c index 4af1660..a93914b 100644 --- a/src/reefnet_sensus_parser.c +++ b/src/reefnet_sensus_parser.c @@ -49,6 +49,7 @@ struct reefnet_sensus_parser_t { }; static dc_status_t reefnet_sensus_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t reefnet_sensus_parser_set_clock (dc_parser_t *abstract, unsigned int devtime, dc_ticks_t systime); static dc_status_t reefnet_sensus_parser_set_atmospheric (dc_parser_t *abstract, double atmospheric); static dc_status_t reefnet_sensus_parser_set_density (dc_parser_t *abstract, double density); static dc_status_t reefnet_sensus_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); @@ -59,6 +60,7 @@ static const dc_parser_vtable_t reefnet_sensus_parser_vtable = { sizeof(reefnet_sensus_parser_t), DC_FAMILY_REEFNET_SENSUS, reefnet_sensus_parser_set_data, /* set_data */ + reefnet_sensus_parser_set_clock, /* set_clock */ reefnet_sensus_parser_set_atmospheric, /* set_atmospheric */ reefnet_sensus_parser_set_density, /* set_density */ reefnet_sensus_parser_get_datetime, /* datetime */ @@ -128,6 +130,17 @@ reefnet_sensus_parser_set_calibration (dc_parser_t *abstract, double atmospheric static dc_status_t +reefnet_sensus_parser_set_clock (dc_parser_t *abstract, unsigned int devtime, dc_ticks_t systime) +{ + reefnet_sensus_parser_t *parser = (reefnet_sensus_parser_t *) abstract; + + parser->devtime = devtime; + parser->systime = systime; + + return DC_STATUS_SUCCESS; +} + + reefnet_sensus_parser_set_atmospheric (dc_parser_t *abstract, double atmospheric) { reefnet_sensus_parser_t *parser = (reefnet_sensus_parser_t *) abstract; diff --git a/src/reefnet_sensuspro_parser.c b/src/reefnet_sensuspro_parser.c index f8f23eb..ad371d7 100644 --- a/src/reefnet_sensuspro_parser.c +++ b/src/reefnet_sensuspro_parser.c @@ -48,6 +48,7 @@ struct reefnet_sensuspro_parser_t { }; static dc_status_t reefnet_sensuspro_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t reefnet_sensuspro_parser_set_clock (dc_parser_t *abstract, unsigned int devtime, dc_ticks_t systime); static dc_status_t reefnet_sensuspro_parser_set_atmospheric (dc_parser_t *abstract, double atmospheric); static dc_status_t reefnet_sensuspro_parser_set_density (dc_parser_t *abstract, double density); static dc_status_t reefnet_sensuspro_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); @@ -58,6 +59,7 @@ static const dc_parser_vtable_t reefnet_sensuspro_parser_vtable = { sizeof(reefnet_sensuspro_parser_t), DC_FAMILY_REEFNET_SENSUSPRO, reefnet_sensuspro_parser_set_data, /* set_data */ + reefnet_sensuspro_parser_set_clock, /* set_clock */ reefnet_sensuspro_parser_set_atmospheric, /* set_atmospheric */ reefnet_sensuspro_parser_set_density, /* set_density */ reefnet_sensuspro_parser_get_datetime, /* datetime */ @@ -126,6 +128,18 @@ reefnet_sensuspro_parser_set_calibration (dc_parser_t *abstract, double atmosphe } +static dc_status_t +reefnet_sensuspro_parser_set_clock (dc_parser_t *abstract, unsigned int devtime, dc_ticks_t systime) +{ + reefnet_sensuspro_parser_t *parser = (reefnet_sensuspro_parser_t *) abstract; + + parser->devtime = devtime; + parser->systime = systime; + + return DC_STATUS_SUCCESS; +} + + static dc_status_t reefnet_sensuspro_parser_set_atmospheric (dc_parser_t *abstract, double atmospheric) { diff --git a/src/reefnet_sensusultra_parser.c b/src/reefnet_sensusultra_parser.c index f19364e..bc52f1f 100644 --- a/src/reefnet_sensusultra_parser.c +++ b/src/reefnet_sensusultra_parser.c @@ -48,6 +48,7 @@ struct reefnet_sensusultra_parser_t { }; static dc_status_t reefnet_sensusultra_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t reefnet_sensusultra_parser_set_clock (dc_parser_t *abstract, unsigned int devtime, dc_ticks_t systime); static dc_status_t reefnet_sensusultra_parser_set_atmospheric (dc_parser_t *abstract, double atmospheric); static dc_status_t reefnet_sensusultra_parser_set_density (dc_parser_t *abstract, double density); static dc_status_t reefnet_sensusultra_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); @@ -58,6 +59,7 @@ static const dc_parser_vtable_t reefnet_sensusultra_parser_vtable = { sizeof(reefnet_sensusultra_parser_t), DC_FAMILY_REEFNET_SENSUSULTRA, reefnet_sensusultra_parser_set_data, /* set_data */ + reefnet_sensusultra_parser_set_clock, /* set_clock */ reefnet_sensusultra_parser_set_atmospheric, /* set_atmospheric */ reefnet_sensusultra_parser_set_density, /* set_density */ reefnet_sensusultra_parser_get_datetime, /* datetime */ @@ -126,6 +128,18 @@ reefnet_sensusultra_parser_set_calibration (dc_parser_t *abstract, double atmosp } +static dc_status_t +reefnet_sensusultra_parser_set_clock (dc_parser_t *abstract, unsigned int devtime, dc_ticks_t systime) +{ + reefnet_sensusultra_parser_t *parser = (reefnet_sensusultra_parser_t *) abstract; + + parser->devtime = devtime; + parser->systime = systime; + + return DC_STATUS_SUCCESS; +} + + static dc_status_t reefnet_sensusultra_parser_set_atmospheric (dc_parser_t *abstract, double atmospheric) { diff --git a/src/seac_screen_parser.c b/src/seac_screen_parser.c index 2a8647d..8c588d2 100644 --- a/src/seac_screen_parser.c +++ b/src/seac_screen_parser.c @@ -57,6 +57,7 @@ static const dc_parser_vtable_t seac_screen_parser_vtable = { sizeof(seac_screen_parser_t), DC_FAMILY_SEAC_SCREEN, seac_screen_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ seac_screen_parser_get_datetime, /* datetime */ diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index b658e6e..3787d0a 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -152,6 +152,7 @@ static const dc_parser_vtable_t shearwater_predator_parser_vtable = { sizeof(shearwater_predator_parser_t), DC_FAMILY_SHEARWATER_PREDATOR, shearwater_predator_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ shearwater_predator_parser_get_datetime, /* datetime */ @@ -164,6 +165,7 @@ static const dc_parser_vtable_t shearwater_petrel_parser_vtable = { sizeof(shearwater_predator_parser_t), DC_FAMILY_SHEARWATER_PETREL, shearwater_predator_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ shearwater_predator_parser_get_datetime, /* datetime */ diff --git a/src/sporasub_sp2_parser.c b/src/sporasub_sp2_parser.c index 3869a39..5ac44b8 100644 --- a/src/sporasub_sp2_parser.c +++ b/src/sporasub_sp2_parser.c @@ -46,6 +46,7 @@ static const dc_parser_vtable_t sporasub_sp2_parser_vtable = { sizeof(sporasub_sp2_parser_t), DC_FAMILY_SPORASUB_SP2, sporasub_sp2_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ sporasub_sp2_parser_get_datetime, /* datetime */ diff --git a/src/suunto_d9_parser.c b/src/suunto_d9_parser.c index 164b01e..0abb079 100644 --- a/src/suunto_d9_parser.c +++ b/src/suunto_d9_parser.c @@ -101,6 +101,7 @@ static const dc_parser_vtable_t suunto_d9_parser_vtable = { sizeof(suunto_d9_parser_t), DC_FAMILY_SUUNTO_D9, suunto_d9_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ suunto_d9_parser_get_datetime, /* datetime */ diff --git a/src/suunto_eon_parser.c b/src/suunto_eon_parser.c index 28d94c2..8e80aca 100644 --- a/src/suunto_eon_parser.c +++ b/src/suunto_eon_parser.c @@ -52,6 +52,7 @@ static const dc_parser_vtable_t suunto_eon_parser_vtable = { sizeof(suunto_eon_parser_t), DC_FAMILY_SUUNTO_EON, suunto_eon_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ suunto_eon_parser_get_datetime, /* datetime */ diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index fa9d880..db24cf3 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -1525,6 +1525,7 @@ static const dc_parser_vtable_t suunto_eonsteel_parser_vtable = { sizeof(suunto_eonsteel_parser_t), DC_FAMILY_SUUNTO_EONSTEEL, suunto_eonsteel_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ suunto_eonsteel_parser_get_datetime, /* datetime */ diff --git a/src/suunto_solution_parser.c b/src/suunto_solution_parser.c index 440a067..59648d5 100644 --- a/src/suunto_solution_parser.c +++ b/src/suunto_solution_parser.c @@ -47,6 +47,7 @@ static const dc_parser_vtable_t suunto_solution_parser_vtable = { sizeof(suunto_solution_parser_t), DC_FAMILY_SUUNTO_SOLUTION, suunto_solution_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ NULL, /* datetime */ diff --git a/src/suunto_vyper_parser.c b/src/suunto_vyper_parser.c index e14ffc8..4970b86 100644 --- a/src/suunto_vyper_parser.c +++ b/src/suunto_vyper_parser.c @@ -53,6 +53,7 @@ static const dc_parser_vtable_t suunto_vyper_parser_vtable = { sizeof(suunto_vyper_parser_t), DC_FAMILY_SUUNTO_VYPER, suunto_vyper_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ suunto_vyper_parser_get_datetime, /* datetime */ diff --git a/src/tecdiving_divecomputereu_parser.c b/src/tecdiving_divecomputereu_parser.c index 7b8d76b..d060996 100644 --- a/src/tecdiving_divecomputereu_parser.c +++ b/src/tecdiving_divecomputereu_parser.c @@ -45,6 +45,7 @@ static const dc_parser_vtable_t tecdiving_divecomputereu_parser_vtable = { sizeof(tecdiving_divecomputereu_parser_t), DC_FAMILY_TECDIVING_DIVECOMPUTEREU, tecdiving_divecomputereu_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ tecdiving_divecomputereu_parser_get_datetime, /* datetime */ diff --git a/src/uwatec_memomouse_parser.c b/src/uwatec_memomouse_parser.c index ae2267c..3c818fa 100644 --- a/src/uwatec_memomouse_parser.c +++ b/src/uwatec_memomouse_parser.c @@ -39,6 +39,7 @@ struct uwatec_memomouse_parser_t { }; static dc_status_t uwatec_memomouse_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t uwatec_memomouse_parser_set_clock (dc_parser_t *abstract, unsigned int devtime, dc_ticks_t systime); static dc_status_t uwatec_memomouse_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); static dc_status_t uwatec_memomouse_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); static dc_status_t uwatec_memomouse_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); @@ -47,6 +48,7 @@ static const dc_parser_vtable_t uwatec_memomouse_parser_vtable = { sizeof(uwatec_memomouse_parser_t), DC_FAMILY_UWATEC_MEMOMOUSE, uwatec_memomouse_parser_set_data, /* set_data */ + uwatec_memomouse_parser_set_clock, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ uwatec_memomouse_parser_get_datetime, /* datetime */ @@ -88,6 +90,18 @@ uwatec_memomouse_parser_set_data (dc_parser_t *abstract, const unsigned char *da } +static dc_status_t +uwatec_memomouse_parser_set_clock (dc_parser_t *abstract, unsigned int devtime, dc_ticks_t systime) +{ + uwatec_memomouse_parser_t *parser = (uwatec_memomouse_parser_t *) abstract; + + parser->devtime = devtime; + parser->systime = systime; + + return DC_STATUS_SUCCESS; +} + + static dc_status_t uwatec_memomouse_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) { diff --git a/src/uwatec_smart_parser.c b/src/uwatec_smart_parser.c index ec1a25e..3c4e348 100644 --- a/src/uwatec_smart_parser.c +++ b/src/uwatec_smart_parser.c @@ -167,6 +167,7 @@ static const dc_parser_vtable_t uwatec_smart_parser_vtable = { sizeof(uwatec_smart_parser_t), DC_FAMILY_UWATEC_SMART, uwatec_smart_parser_set_data, /* set_data */ + NULL, /* set_clock */ NULL, /* set_atmospheric */ NULL, /* set_density */ uwatec_smart_parser_get_datetime, /* datetime */ From ce578cafb9c4e855f88d6d5f4983b5211228e321 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 11 Aug 2022 18:00:12 +0200 Subject: [PATCH 06/58] Report the dive mode for Air and Nitrox dives Currently the dive mode is only reported for rebreather dives. --- src/suunto_eonsteel_parser.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index db24cf3..c5d7a63 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -1338,7 +1338,10 @@ static int traverse_diving_fields(suunto_eonsteel_parser_t *eon, const struct ty } if (!strcmp(name, "DiveMode")) { - if (!strncmp((const char *)data, "CCR", 3)) { + if (!strncmp((const char *)data, "Air", 3) || !strncmp((const char *)data, "Nitrox", 6)) { + eon->cache.divemode = DC_DIVEMODE_OC; + eon->cache.initialized |= 1 << DC_FIELD_DIVEMODE; + } else if (!strncmp((const char *)data, "CCR", 3)) { eon->cache.divemode = DC_DIVEMODE_CCR; eon->cache.initialized |= 1 << DC_FIELD_DIVEMODE; } From 3e5282bf74d0ec9d1a8acc54590d38c60ed2f367 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 10 Oct 2022 19:01:10 +0200 Subject: [PATCH 07/58] Add support for the Scorpena Alpha The Scorpena Alpha uses the same communication protocol and data format as the Deep Six Excursion. Reported-by: Sven Knoch --- src/descriptor.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/descriptor.c b/src/descriptor.c index ce95fd9..fce47f3 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -439,6 +439,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Crest", "CR-4", DC_FAMILY_DEEPSIX_EXCURSION, 0, DC_TRANSPORT_BLE, dc_filter_deepsix}, {"Genesis", "Centauri", DC_FAMILY_DEEPSIX_EXCURSION, 0, DC_TRANSPORT_BLE, dc_filter_deepsix}, {"Tusa", "TC1", DC_FAMILY_DEEPSIX_EXCURSION, 0, DC_TRANSPORT_BLE, dc_filter_deepsix}, + {"Scorpena", "Alpha", DC_FAMILY_DEEPSIX_EXCURSION, 0, DC_TRANSPORT_BLE, dc_filter_deepsix}, /* Seac Screen */ {"Seac", "Screen", DC_FAMILY_SEAC_SCREEN, 0, DC_TRANSPORT_SERIAL, NULL}, {"Seac", "Action", DC_FAMILY_SEAC_SCREEN, 0, DC_TRANSPORT_SERIAL, NULL}, @@ -751,6 +752,7 @@ static int dc_filter_deepsix (dc_transport_t transport, const void *userdata, vo "Crest-CR4", "CENTAURI", "TC1", + "ALPHA", }; if (transport == DC_TRANSPORT_BLE) { From 3eedf4d24d029ecda909174f999207a44d481202 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 11 Oct 2022 22:09:33 +0200 Subject: [PATCH 08/58] Use the value stored in the dive header The maximum depth value is stored in the dive header. There is no need to parse the profile data to obtain it. This also avoids returning a zero depth when the profile data is no longer available. A few other fields (e.g. average depth, atmospheric pressure and temperature) are also present in the dive header. --- src/cressi_goa_parser.c | 143 +++++++++++++++++++++++++++------------- 1 file changed, 96 insertions(+), 47 deletions(-) diff --git a/src/cressi_goa_parser.c b/src/cressi_goa_parser.c index deb44b3..f5d4a4c 100644 --- a/src/cressi_goa_parser.c +++ b/src/cressi_goa_parser.c @@ -29,9 +29,6 @@ #define ISINSTANCE(parser) dc_device_isinstance((parser), &cressi_goa_parser_vtable) #define SZ_HEADER 23 -#define SZ_HEADER_SCUBA 0x61 -#define SZ_HEADER_FREEDIVE 0x2B -#define SZ_HEADER_GAUGE 0x2D #define DEPTH 0 #define DEPTH2 1 @@ -45,16 +42,26 @@ #define NGASMIXES 2 +#define UNDEFINED 0xFFFFFFFF + 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; }; +typedef struct cressi_goa_layout_t { + unsigned int headersize; + unsigned int datetime; + unsigned int divetime; + unsigned int gasmix; + unsigned int atmospheric; + unsigned int maxdepth; + unsigned int avgdepth; + unsigned int temperature; +} cressi_goa_layout_t; + 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); @@ -73,11 +80,51 @@ static const dc_parser_vtable_t cressi_goa_parser_vtable = { NULL /* destroy */ }; -static const unsigned int headersizes[] = { - SZ_HEADER_SCUBA, - SZ_HEADER_SCUBA, - SZ_HEADER_FREEDIVE, - SZ_HEADER_GAUGE, +static const cressi_goa_layout_t layouts[] = { + /* SCUBA */ + { + 0x61, /* headersize */ + 0x11, /* datetime */ + 0x19, /* divetime */ + 0x1F, /* gasmix */ + 0x23, /* atmospheric */ + 0x4E, /* maxdepth */ + 0x50, /* avgdepth */ + 0x52, /* temperature */ + }, + /* NITROX */ + { + 0x61, /* headersize */ + 0x11, /* datetime */ + 0x19, /* divetime */ + 0x1F, /* gasmix */ + 0x23, /* atmospheric */ + 0x4E, /* maxdepth */ + 0x50, /* avgdepth */ + 0x52, /* temperature */ + }, + /* FREEDIVE */ + { + 0x2B, /* headersize */ + 0x11, /* datetime */ + 0x19, /* divetime */ + UNDEFINED, /* gasmix */ + UNDEFINED, /* atmospheric */ + 0x1C, /* maxdepth */ + UNDEFINED, /* avgdepth */ + 0x1E, /* temperature */ + }, + /* GAUGE */ + { + 0x2D, /* headersize */ + 0x11, /* datetime */ + 0x19, /* divetime */ + UNDEFINED, /* gasmix */ + 0x1B, /* atmospheric */ + 0x1D, /* maxdepth */ + 0x1F, /* avgdepth */ + 0x21, /* temperature */ + }, }; dc_status_t @@ -96,8 +143,6 @@ cressi_goa_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int } parser->model = model; - parser->cached = 0; - parser->maxdepth = 0.0; *out = (dc_parser_t*) parser; @@ -107,12 +152,6 @@ cressi_goa_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int 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; } @@ -126,15 +165,16 @@ cressi_goa_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) return DC_STATUS_DATAFORMAT; unsigned int divemode = data[2]; - if (divemode >= C_ARRAY_SIZE(headersizes)) { + if (divemode >= C_ARRAY_SIZE(layouts)) { return DC_STATUS_DATAFORMAT; } - unsigned int headersize = headersizes[divemode]; - if (size < headersize) + const cressi_goa_layout_t *layout = &layouts[divemode]; + + if (size < layout->headersize) return DC_STATUS_DATAFORMAT; - const unsigned char *p = abstract->data + 0x11; + const unsigned char *p = abstract->data + layout->datetime; if (datetime) { datetime->year = array_uint16_le(p); @@ -152,7 +192,6 @@ 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) { - cressi_goa_parser_t *parser = (cressi_goa_parser_t *) abstract; const unsigned char *data = abstract->data; unsigned int size = abstract->size; @@ -160,29 +199,19 @@ cressi_goa_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsign return DC_STATUS_DATAFORMAT; unsigned int divemode = data[2]; - if (divemode >= C_ARRAY_SIZE(headersizes)) { + if (divemode >= C_ARRAY_SIZE(layouts)) { return DC_STATUS_DATAFORMAT; } - unsigned int headersize = headersizes[divemode]; - if (size < headersize) + const cressi_goa_layout_t *layout = &layouts[divemode]; + + if (size < layout->headersize) return DC_STATUS_DATAFORMAT; - 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; - } - unsigned int ngasmixes = 0; - if (divemode == SCUBA || divemode == NITROX) { + if (layout->gasmix != UNDEFINED) { for (unsigned int i = 0; i < NGASMIXES; ++i) { - if (data[0x20 + 2 * i] == 0) + if (data[layout->gasmix + 2 * i + 1] == 0) break; ngasmixes++; } @@ -193,17 +222,36 @@ cressi_goa_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsign if (value) { switch (type) { case DC_FIELD_DIVETIME: - *((unsigned int *) value) = array_uint16_le (data + 0x19); + if (layout->divetime == UNDEFINED) + return DC_STATUS_UNSUPPORTED; + *((unsigned int *) value) = array_uint16_le (data + layout->divetime); break; case DC_FIELD_MAXDEPTH: - *((double *) value) = parser->maxdepth; + if (layout->maxdepth == UNDEFINED) + return DC_STATUS_UNSUPPORTED; + *((double *) value) = array_uint16_le (data + layout->maxdepth) / 10.0; + break; + case DC_FIELD_AVGDEPTH: + if (layout->avgdepth == UNDEFINED) + return DC_STATUS_UNSUPPORTED; + *((double *) value) = array_uint16_le (data + layout->avgdepth) / 10.0; + break; + case DC_FIELD_TEMPERATURE_MINIMUM: + if (layout->temperature == UNDEFINED) + return DC_STATUS_UNSUPPORTED; + *((double *) value) = array_uint16_le (data + layout->temperature) / 10.0; + break; + case DC_FIELD_ATMOSPHERIC: + if (layout->atmospheric == UNDEFINED) + return DC_STATUS_UNSUPPORTED; + *((double *) value) = array_uint16_le (data + layout->atmospheric) / 1000.0; break; case DC_FIELD_GASMIX_COUNT: *((unsigned int *) value) = ngasmixes; break; case DC_FIELD_GASMIX: gasmix->helium = 0.0; - gasmix->oxygen = data[0x20 + 2 * flags] / 100.0; + gasmix->oxygen = data[layout->gasmix + 2 * flags + 1] / 100.0; gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; break; case DC_FIELD_DIVEMODE: @@ -240,12 +288,13 @@ cressi_goa_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t c return DC_STATUS_DATAFORMAT; unsigned int divemode = data[2]; - if (divemode >= C_ARRAY_SIZE(headersizes)) { + if (divemode >= C_ARRAY_SIZE(layouts)) { return DC_STATUS_DATAFORMAT; } - unsigned int headersize = headersizes[divemode]; - if (size < headersize) + const cressi_goa_layout_t *layout = &layouts[divemode]; + + if (size < layout->headersize) return DC_STATUS_DATAFORMAT; unsigned int interval = divemode == FREEDIVE ? 2 : 5; @@ -257,7 +306,7 @@ cressi_goa_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t c unsigned int have_temperature = 0; unsigned int complete = 0; - unsigned int offset = headersize; + unsigned int offset = layout->headersize; while (offset + 2 <= size) { dc_sample_value_t sample = {0}; From c5813d624ad3dbb083820b122dfa4eff0b1b1529 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 13 Oct 2022 23:12:35 +0200 Subject: [PATCH 09/58] Add support for the Scubapro G2 TEK The Scubapro G2 TEK is compatible with the G2, but with a new model number. Reported-by: Greg McLaughlin --- src/descriptor.c | 4 +++- src/uwatec_smart_parser.c | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index fce47f3..f735368 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -171,6 +171,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Scubapro", "Aladin A1", DC_FAMILY_UWATEC_SMART, 0x25, DC_TRANSPORT_BLE, dc_filter_uwatec}, {"Scubapro", "Mantis 2", DC_FAMILY_UWATEC_SMART, 0x26, DC_TRANSPORT_SERIAL, NULL}, {"Scubapro", "Aladin A2", DC_FAMILY_UWATEC_SMART, 0x28, DC_TRANSPORT_BLE, dc_filter_uwatec}, + {"Scubapro", "G2 TEK", DC_FAMILY_UWATEC_SMART, 0x31, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_uwatec}, {"Scubapro", "G2", DC_FAMILY_UWATEC_SMART, 0x32, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_uwatec}, {"Scubapro", "G2 Console", DC_FAMILY_UWATEC_SMART, 0x32, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_uwatec}, {"Scubapro", "G2 HUD", DC_FAMILY_UWATEC_SMART, 0x42, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_uwatec}, @@ -557,7 +558,7 @@ static int dc_filter_uwatec (dc_transport_t transport, const void *userdata, voi "UWATEC Galileo Sol", }; static const dc_usb_desc_t usbhid[] = { - {0x2e6c, 0x3201}, // G2 + {0x2e6c, 0x3201}, // G2, G2 TEK {0x2e6c, 0x3211}, // G2 Console {0x2e6c, 0x4201}, // G2 HUD {0xc251, 0x2006}, // Aladin Square @@ -568,6 +569,7 @@ static int dc_filter_uwatec (dc_transport_t transport, const void *userdata, voi "HUD", "A1", "A2", + "G2 TEK", }; if (transport == DC_TRANSPORT_IRDA) { diff --git a/src/uwatec_smart_parser.c b/src/uwatec_smart_parser.c index 3c4e348..39a927c 100644 --- a/src/uwatec_smart_parser.c +++ b/src/uwatec_smart_parser.c @@ -49,6 +49,7 @@ #define ALADINA1 0x25 #define MANTIS2 0x26 #define ALADINA2 0x28 +#define G2TEK 0x31 #define G2 0x32 #define G2HUD 0x42 @@ -532,7 +533,8 @@ uwatec_smart_parser_cache (uwatec_smart_parser_t *parser) parser->model == CHROMIS || parser->model == MANTIS2 || parser->model == G2 || parser->model == ALADINSPORTMATRIX || parser->model == ALADINSQUARE || parser->model == G2HUD || - parser->model == ALADINA1 || parser->model == ALADINA2 ) { + parser->model == ALADINA1 || parser->model == ALADINA2 || + parser->model == G2TEK) { unsigned int offset = header->tankpressure + 2 * i; endpressure = array_uint16_le(data + offset); beginpressure = array_uint16_le(data + offset + 2 * header->ngases); @@ -622,6 +624,7 @@ uwatec_smart_parser_create (dc_parser_t **out, dc_context_t *context, unsigned i break; case G2: case G2HUD: + case G2TEK: case ALADINSPORTMATRIX: case ALADINA1: case ALADINA2: @@ -968,7 +971,8 @@ uwatec_smart_parse (uwatec_smart_parser_t *parser, dc_sample_callback_t callback parser->model == CHROMIS || parser->model == MANTIS2 || parser->model == G2 || parser->model == ALADINSPORTMATRIX || parser->model == ALADINSQUARE || parser->model == G2HUD || - parser->model == ALADINA1 || parser->model == ALADINA2 ) { + parser->model == ALADINA1 || parser->model == ALADINA2 || + parser->model == G2TEK) { // Uwatec Galileo id = uwatec_galileo_identify (data[offset]); } else { From 3d388a0a9617036eccda8d5df9c077207431ba1c Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 16 Oct 2022 21:29:49 +0200 Subject: [PATCH 10/58] Don't pass a NULL pointer to memcpy The memcpy and related functions expects a valid pointer, even if the size is zero. Most libc implementations will handle a NULL pointer just fine, but that's not guaranteed. Simply skip the call when there is nothing to copy. --- src/deepsix_excursion.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/deepsix_excursion.c b/src/deepsix_excursion.c index 378e20f..53135de 100644 --- a/src/deepsix_excursion.c +++ b/src/deepsix_excursion.c @@ -163,7 +163,9 @@ deepsix_excursion_recv (deepsix_excursion_device_t *device, unsigned char grp, u return DC_STATUS_PROTOCOL; } - memcpy(data, packet + 4, len); + if (len) { + memcpy(data, packet + 4, len); + } if (actual) *actual = len; From 59dd6a2a564882b078e848c2e53d1fe547a6b69a Mon Sep 17 00:00:00 2001 From: Greg McLaughlin Date: Mon, 31 Oct 2022 22:40:06 +0100 Subject: [PATCH 11/58] Increase the memory size for the Aqualung i770R The Aqualung i770R appears to have 6M instead of 4M (high) memory. Confirmed by trying to read past the 6M limit, which fails with a NAK response. This amount also matches with the capacity stated in the manual (6553 hours of profile data at a 60 second sample rate). --- src/oceanic_atom2.c | 4 ++-- src/oceanic_common.c | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index 88efbc3..f8699ea 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -365,7 +365,7 @@ static const oceanic_common_layout_t oceanic_proplusx_layout = { }; static const oceanic_common_layout_t aqualung_i770r_layout = { - 0x440000, /* memsize */ + 0x640000, /* memsize */ 0x40000, /* highmem */ 0x0000, /* cf_devinfo */ 0x0040, /* cf_pointers */ @@ -373,7 +373,7 @@ static const oceanic_common_layout_t aqualung_i770r_layout = { 0x10000, /* rb_logbook_end */ 16, /* rb_logbook_entry_size */ 0x40000, /* rb_profile_begin */ - 0x440000, /* rb_profile_end */ + 0x640000, /* rb_profile_end */ 0, /* pt_mode_global */ 1, /* pt_mode_logbook */ 0, /* pt_mode_serial */ diff --git a/src/oceanic_common.c b/src/oceanic_common.c index c33655b..f876d28 100644 --- a/src/oceanic_common.c +++ b/src/oceanic_common.c @@ -56,8 +56,9 @@ get_profile_first (const unsigned char data[], const oceanic_common_layout_t *la } unsigned int npages = (layout->memsize - layout->highmem) / pagesize; - - if (npages > 0x2000) { + if (npages > 0x4000) { + value &= 0x7FFF; + } else if (npages > 0x2000) { value &= 0x3FFF; } else if (npages > 0x1000) { value &= 0x1FFF; @@ -86,7 +87,9 @@ get_profile_last (const unsigned char data[], const oceanic_common_layout_t *lay unsigned int npages = (layout->memsize - layout->highmem) / pagesize; - if (npages > 0x2000) { + if (npages > 0x4000) { + value &= 0x7FFF; + } else if (npages > 0x2000) { value &= 0x3FFF; } else if (npages > 0x1000) { value &= 0x1FFF; From 34bc6b1613fee3d72a2940dfdd5acad6def434fb Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 3 Nov 2022 21:31:24 +0100 Subject: [PATCH 12/58] Use the macro for encoding firmware versions This makes it a bit easer to quickly locate the workarounds for specific firmware versions. --- src/hw_ostc3.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index e8dc223..467a64d 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -770,7 +770,7 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi if (!compact) { // Workaround for a bug in older firmware versions. unsigned int firmware = array_uint16_be (header + offset + 0x30); - if (firmware < 93) + if (firmware < OSTC3FW(0,93)) length -= 3; } if (length < RB_LOGBOOK_SIZE_FULL) { @@ -818,7 +818,7 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi if (!compact) { // Workaround for a bug in older firmware versions. unsigned int firmware = array_uint16_be (header + offset + 0x30); - if (firmware < 93) + if (firmware < OSTC3FW(0,93)) length -= 3; } From c578e0a158a107c43b5af235c007d583cfbe11f6 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 3 Nov 2022 21:37:09 +0100 Subject: [PATCH 13/58] Use symbolic constants for the header offsets --- src/hw_ostc3.c | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 467a64d..2b4c262 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -89,6 +89,16 @@ #define NODELAY 0 #define TIMEOUT 400 +#define HDR_COMPACT_LENGTH 0 // 3 bytes +#define HDR_COMPACT_SUMMARY 3 // 10 bytes +#define HDR_COMPACT_NUMBER 13 // 2 bytes + +#define HDR_FULL_LENGTH 9 // 3 bytes +#define HDR_FULL_SUMMARY 12 // 10 bytes +#define HDR_FULL_NUMBER 80 // 2 bytes + +#define HDR_FULL_FIRMWARE 48 // 2 bytes + typedef enum hw_ostc3_state_t { OPEN, DOWNLOAD, @@ -155,16 +165,16 @@ static const dc_device_vtable_t hw_ostc3_device_vtable = { static const hw_ostc3_logbook_t hw_ostc3_logbook_compact = { RB_LOGBOOK_SIZE_COMPACT, /* size */ - 0, /* profile */ - 3, /* fingerprint */ - 13, /* number */ + HDR_COMPACT_LENGTH, /* profile */ + HDR_COMPACT_SUMMARY, /* fingerprint */ + HDR_COMPACT_NUMBER, /* number */ }; static const hw_ostc3_logbook_t hw_ostc3_logbook_full = { RB_LOGBOOK_SIZE_FULL, /* size */ - 9, /* profile */ - 12, /* fingerprint */ - 80, /* number */ + HDR_FULL_LENGTH, /* profile */ + HDR_FULL_SUMMARY, /* fingerprint */ + HDR_FULL_NUMBER, /* number */ }; @@ -769,7 +779,7 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi unsigned int length = RB_LOGBOOK_SIZE_FULL + array_uint24_le (header + offset + logbook->profile) - 3; if (!compact) { // Workaround for a bug in older firmware versions. - unsigned int firmware = array_uint16_be (header + offset + 0x30); + unsigned int firmware = array_uint16_be (header + offset + HDR_FULL_FIRMWARE); if (firmware < OSTC3FW(0,93)) length -= 3; } @@ -817,7 +827,7 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi unsigned int length = RB_LOGBOOK_SIZE_FULL + array_uint24_le (header + offset + logbook->profile) - 3; if (!compact) { // Workaround for a bug in older firmware versions. - unsigned int firmware = array_uint16_be (header + offset + 0x30); + unsigned int firmware = array_uint16_be (header + offset + HDR_FULL_FIRMWARE); if (firmware < OSTC3FW(0,93)) length -= 3; } @@ -853,7 +863,7 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi // A profile containing only the 2 byte end-of-profile // marker is considered a valid empty profile. } else if (length < RB_LOGBOOK_SIZE_FULL + 5 + 2 || - array_uint24_le (profile + RB_LOGBOOK_SIZE_FULL) + delta != array_uint24_le (profile + 9)) { + array_uint24_le (profile + RB_LOGBOOK_SIZE_FULL) + delta != array_uint24_le (profile + HDR_FULL_LENGTH)) { // If there is more data available, then there should be a // valid profile header containing a length matching the // length in the dive header. @@ -861,7 +871,7 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi length = RB_LOGBOOK_SIZE_FULL; } - if (callback && !callback (profile, length, profile + 12, sizeof (device->fingerprint), userdata)) + if (callback && !callback (profile, length, profile + HDR_FULL_SUMMARY, sizeof (device->fingerprint), userdata)) break; } From a99d990117c8806cb566b6a602317cd5976900ae Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 27 Oct 2022 20:14:29 +0200 Subject: [PATCH 14/58] Verify the fields of the compact header When downloading the compact headers (which is the default for recent hwOS firmware), it's not possible to compare the entire dive header, but we can at least the check the fields that are available. Also return an error if the verification fails. --- src/hw_ostc3.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 2b4c262..cfb25d7 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -92,10 +92,12 @@ #define HDR_COMPACT_LENGTH 0 // 3 bytes #define HDR_COMPACT_SUMMARY 3 // 10 bytes #define HDR_COMPACT_NUMBER 13 // 2 bytes +#define HDR_COMPACT_VERSION 15 // 1 byte #define HDR_FULL_LENGTH 9 // 3 bytes #define HDR_FULL_SUMMARY 12 // 10 bytes #define HDR_FULL_NUMBER 80 // 2 bytes +#define HDR_FULL_VERSION 8 // 1 byte #define HDR_FULL_FIRMWARE 48 // 2 bytes @@ -844,11 +846,16 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi } // Verify the header in the logbook and profile are identical. - if (!compact && memcmp (profile, header + offset, logbook->size) != 0) { + if (compact ? + memcmp (profile + HDR_FULL_LENGTH, header + offset + HDR_COMPACT_LENGTH, 3) != 0 || + memcmp (profile + HDR_FULL_SUMMARY, header + offset + HDR_COMPACT_SUMMARY, 10) != 0 || + memcmp (profile + HDR_FULL_NUMBER, header + offset + HDR_COMPACT_NUMBER, 2) != 0 || + memcmp (profile + HDR_FULL_VERSION, header + offset + HDR_COMPACT_VERSION, 1) != 0 : + memcmp (profile, header + offset, RB_LOGBOOK_SIZE_FULL) != 0) { ERROR (abstract->context, "Unexpected profile header."); free (profile); free (header); - return rc; + return DC_STATUS_DATAFORMAT; } // Detect invalid profile data. From 89ae8b94cfb10e44d04bad37a0db2cbbae8bf8c0 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 27 Oct 2022 22:02:21 +0200 Subject: [PATCH 15/58] Fix the detection of empty dive profiles Not only the two byte end-of-profile marker 0xFDFD is a valid empty dive profile, but also a profile with the length field present and set to 8 bytes. In that case the actual length will be just 5 bytes. --- src/hw_ostc3.c | 2 +- src/hw_ostc_parser.c | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index cfb25d7..7b87587 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -869,7 +869,7 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi } else if (length == RB_LOGBOOK_SIZE_FULL + 2) { // A profile containing only the 2 byte end-of-profile // marker is considered a valid empty profile. - } else if (length < RB_LOGBOOK_SIZE_FULL + 5 + 2 || + } else if (length < RB_LOGBOOK_SIZE_FULL + 5 || array_uint24_le (profile + RB_LOGBOOK_SIZE_FULL) + delta != array_uint24_le (profile + HDR_FULL_LENGTH)) { // If there is more data available, then there should be a // valid profile header containing a length matching the diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index 90994f8..d745ef8 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -20,6 +20,7 @@ */ #include +#include #include "libdivecomputer/units.h" @@ -679,8 +680,10 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call const hw_ostc_layout_t *layout = parser->layout; // Exit if no profile data available. - if (size == header || (size == header + 2 && - data[header] == 0xFD && data[header + 1] == 0xFD)) { + const unsigned char empty[] = {0x08, 0x00, 0x00, 0xFD, 0xFD}; + if (size == header || + (size == header + 2 && memcmp(data + header, empty + 3, 2) == 0) || + (size == header + 5 && memcmp(data + header, empty, 5) == 0)) { parser->cached = PROFILE; return DC_STATUS_SUCCESS; } From 9508401971d3644289d8efedf4d01c107f6f5bfc Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 28 Oct 2022 22:12:04 +0200 Subject: [PATCH 16/58] Fix the download of dives without a profile At the moment, trying to download an old dive for which the profile data has already been overwritten with newer data fails. This used to work fine, but around hwOS firmware v3.10, the behaviour described in commit 76187c550a806fe422920eb8795fa687244513f1 changed. When downloading the compact/full headers, the firmware always sends the headers without inspecting their content. Next, libdivecomputer uses the length field in these headers to determine how many bytes to expect when downloading the dive. However, when downloading the entire dive, the hwOS firmware now checks whether the profile data of the dive is still available. If that's no longer the case, the firmware sends a modified dive header (with the begin/end pointer fields reset to zero, and the length field reduced to 8 bytes), along with an empty dive profile. Since libdivecomputer expects to receive the full profile as indicated in the original header, the download fails with a timeout. To workaround this problem, download the dive data in two steps. First, download the 256 byte header and check whether it has been modified. If that's the case, reduce the length to that of the 5 byte empty profile. The header check is also updated to exclude the modified fields. For the progress events, just pretend the full profile has been downloaded. --- src/hw_ostc3.c | 110 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 77 insertions(+), 33 deletions(-) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 7b87587..07b7d8f 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -99,6 +99,7 @@ #define HDR_FULL_NUMBER 80 // 2 bytes #define HDR_FULL_VERSION 8 // 1 byte +#define HDR_FULL_POINTERS 2 // 6 bytes #define HDR_FULL_FIRMWARE 48 // 2 bytes typedef enum hw_ostc3_state_t { @@ -128,6 +129,7 @@ typedef struct hw_ostc3_logbook_t { unsigned int profile; unsigned int fingerprint; unsigned int number; + unsigned int version; } hw_ostc3_logbook_t; typedef struct hw_ostc3_firmware_t { @@ -170,6 +172,7 @@ static const hw_ostc3_logbook_t hw_ostc3_logbook_compact = { HDR_COMPACT_LENGTH, /* profile */ HDR_COMPACT_SUMMARY, /* fingerprint */ HDR_COMPACT_NUMBER, /* number */ + HDR_COMPACT_VERSION, /* version */ }; static const hw_ostc3_logbook_t hw_ostc3_logbook_full = { @@ -177,6 +180,7 @@ static const hw_ostc3_logbook_t hw_ostc3_logbook_full = { HDR_FULL_LENGTH, /* profile */ HDR_FULL_SUMMARY, /* fingerprint */ HDR_FULL_NUMBER, /* number */ + HDR_FULL_VERSION, /* version */ }; @@ -291,10 +295,15 @@ hw_ostc3_transfer (hw_ostc3_device_t *device, unsigned int isize, unsigned char output[], unsigned int osize, + unsigned int *actual, unsigned int delay) { dc_device_t *abstract = (dc_device_t *) device; dc_status_t status = DC_STATUS_SUCCESS; + unsigned int length = osize; + + if (cmd == DIVE && length < RB_LOGBOOK_SIZE_FULL) + return DC_STATUS_INVALIDARGS; if (device_is_cancelled (abstract)) return DC_STATUS_CANCELLED; @@ -357,11 +366,44 @@ hw_ostc3_transfer (hw_ostc3_device_t *device, } if (output) { - // Read the output data packet. - status = hw_ostc3_read (device, progress, output, osize); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to receive the answer."); - return status; + if (cmd == DIVE) { + // Read the dive header. + status = hw_ostc3_read (device, progress, output, RB_LOGBOOK_SIZE_FULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the dive header."); + return status; + } + + // When the hwOS firmware detects the dive profile is no longer + // valid, it sends a modified dive header (with the begin/end + // pointer fields reset to zero, and the length field reduced to 8 + // bytes), along with an empty dive profile. Detect this condition + // and adjust the expected length. + if (array_isequal (output + HDR_FULL_POINTERS, 6, 0x00) && + array_uint24_le (output + HDR_FULL_LENGTH) == 8 && + length > RB_LOGBOOK_SIZE_FULL + 5) { + length = RB_LOGBOOK_SIZE_FULL + 5; + } + + // Read the dive profile. + status = hw_ostc3_read (device, progress, output + RB_LOGBOOK_SIZE_FULL, length - RB_LOGBOOK_SIZE_FULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the dive profile."); + return status; + } + + // Update and emit a progress event. + if (progress && osize > length) { + progress->current += osize - length; + device_event_emit ((dc_device_t *) device, DC_EVENT_PROGRESS, progress); + } + } else { + // Read the output data packet. + status = hw_ostc3_read (device, progress, output, length); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the answer."); + return status; + } } } @@ -385,6 +427,9 @@ hw_ostc3_transfer (hw_ostc3_device_t *device, } } + if (actual) + *actual = length; + return DC_STATUS_SUCCESS; } @@ -457,9 +502,9 @@ hw_ostc3_device_id (hw_ostc3_device_t *device, unsigned char data[], unsigned in // Send the command. unsigned char hardware[SZ_HARDWARE2] = {0}; - status = hw_ostc3_transfer (device, NULL, HARDWARE2, NULL, 0, hardware, SZ_HARDWARE2, NODELAY); + status = hw_ostc3_transfer (device, NULL, HARDWARE2, NULL, 0, hardware, SZ_HARDWARE2, NULL, NODELAY); if (status == DC_STATUS_UNSUPPORTED) { - status = hw_ostc3_transfer (device, NULL, HARDWARE, NULL, 0, hardware + 1, SZ_HARDWARE, NODELAY); + status = hw_ostc3_transfer (device, NULL, HARDWARE, NULL, 0, hardware + 1, SZ_HARDWARE, NULL, NODELAY); } if (status != DC_STATUS_SUCCESS) return status; @@ -481,7 +526,7 @@ hw_ostc3_device_init_download (hw_ostc3_device_t *device) dc_context_t *context = (abstract ? abstract->context : NULL); // Send the init command. - dc_status_t status = hw_ostc3_transfer (device, NULL, INIT, NULL, 0, NULL, 0, NODELAY); + dc_status_t status = hw_ostc3_transfer (device, NULL, INIT, NULL, 0, NULL, 0, NULL, NODELAY); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to send the command."); return status; @@ -574,7 +619,7 @@ hw_ostc3_device_init (hw_ostc3_device_t *device, hw_ostc3_state_t state) // Read the version information. unsigned char version[SZ_VERSION] = {0}; - rc = hw_ostc3_transfer (device, NULL, IDENTITY, NULL, 0, version, sizeof(version), NODELAY); + rc = hw_ostc3_transfer (device, NULL, IDENTITY, NULL, 0, version, sizeof(version), NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the version information."); return rc; @@ -604,7 +649,7 @@ hw_ostc3_device_close (dc_device_t *abstract) // Send the exit command if (device->state == DOWNLOAD || device->state == SERVICE) { - rc = hw_ostc3_transfer (device, NULL, EXIT, NULL, 0, NULL, 0, NODELAY); + rc = hw_ostc3_transfer (device, NULL, EXIT, NULL, 0, NULL, 0, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the command."); dc_status_set_error(&status, rc); @@ -648,7 +693,7 @@ hw_ostc3_device_version (dc_device_t *abstract, unsigned char data[], unsigned i return rc; // Send the command. - rc = hw_ostc3_transfer (device, NULL, IDENTITY, NULL, 0, data, size, NODELAY); + rc = hw_ostc3_transfer (device, NULL, IDENTITY, NULL, 0, data, size, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; @@ -721,11 +766,11 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi // This is slower, but also works for older firmware versions. unsigned int compact = 1; rc = hw_ostc3_transfer (device, &progress, COMPACT, - NULL, 0, header, RB_LOGBOOK_SIZE_COMPACT * RB_LOGBOOK_COUNT, NODELAY); + NULL, 0, header, RB_LOGBOOK_SIZE_COMPACT * RB_LOGBOOK_COUNT, NULL, NODELAY); if (rc == DC_STATUS_UNSUPPORTED) { compact = 0; rc = hw_ostc3_transfer (device, &progress, HEADER, - NULL, 0, header, RB_LOGBOOK_SIZE_FULL * RB_LOGBOOK_COUNT, NODELAY); + NULL, 0, header, RB_LOGBOOK_SIZE_FULL * RB_LOGBOOK_COUNT, NULL, NODELAY); } if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the header."); @@ -837,7 +882,7 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi // Download the dive. unsigned char number[1] = {idx}; rc = hw_ostc3_transfer (device, &progress, DIVE, - number, sizeof (number), profile, length, NODELAY); + number, sizeof (number), profile, length, &length, NODELAY); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the dive."); free (profile); @@ -846,12 +891,11 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi } // Verify the header in the logbook and profile are identical. - if (compact ? - memcmp (profile + HDR_FULL_LENGTH, header + offset + HDR_COMPACT_LENGTH, 3) != 0 || - memcmp (profile + HDR_FULL_SUMMARY, header + offset + HDR_COMPACT_SUMMARY, 10) != 0 || - memcmp (profile + HDR_FULL_NUMBER, header + offset + HDR_COMPACT_NUMBER, 2) != 0 || - memcmp (profile + HDR_FULL_VERSION, header + offset + HDR_COMPACT_VERSION, 1) != 0 : - memcmp (profile, header + offset, RB_LOGBOOK_SIZE_FULL) != 0) { + if (memcmp (profile + HDR_FULL_VERSION, header + offset + logbook->version, 1) != 0 || + compact ? + memcmp (profile + HDR_FULL_SUMMARY, header + offset + HDR_COMPACT_SUMMARY, 10) != 0 || + memcmp (profile + HDR_FULL_NUMBER, header + offset + HDR_COMPACT_NUMBER, 2) != 0 : + memcmp (profile + HDR_FULL_SUMMARY, header + offset + HDR_FULL_SUMMARY, RB_LOGBOOK_SIZE_FULL - HDR_FULL_SUMMARY) != 0) { ERROR (abstract->context, "Unexpected profile header."); free (profile); free (header); @@ -907,7 +951,7 @@ hw_ostc3_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) unsigned char packet[6] = { datetime->hour, datetime->minute, datetime->second, datetime->month, datetime->day, datetime->year - 2000}; - rc = hw_ostc3_transfer (device, NULL, CLOCK, packet, sizeof (packet), NULL, 0, NODELAY); + rc = hw_ostc3_transfer (device, NULL, CLOCK, packet, sizeof (packet), NULL, 0, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; @@ -935,7 +979,7 @@ hw_ostc3_device_display (dc_device_t *abstract, const char *text) return rc; // Send the command. - rc = hw_ostc3_transfer (device, NULL, DISPLAY, packet, sizeof (packet), NULL, 0, NODELAY); + rc = hw_ostc3_transfer (device, NULL, DISPLAY, packet, sizeof (packet), NULL, 0, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; @@ -963,7 +1007,7 @@ hw_ostc3_device_customtext (dc_device_t *abstract, const char *text) return rc; // Send the command. - rc = hw_ostc3_transfer (device, NULL, CUSTOMTEXT, packet, sizeof (packet), NULL, 0, NODELAY); + rc = hw_ostc3_transfer (device, NULL, CUSTOMTEXT, packet, sizeof (packet), NULL, 0, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; @@ -989,7 +1033,7 @@ hw_ostc3_device_config_read (dc_device_t *abstract, unsigned int config, unsigne // Send the command. unsigned char command[1] = {config}; - rc = hw_ostc3_transfer (device, NULL, READ, command, sizeof (command), data, size, NODELAY); + rc = hw_ostc3_transfer (device, NULL, READ, command, sizeof (command), data, size, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; @@ -1016,7 +1060,7 @@ hw_ostc3_device_config_write (dc_device_t *abstract, unsigned int config, const // Send the command. unsigned char command[SZ_CONFIG + 1] = {config}; memcpy(command + 1, data, size); - rc = hw_ostc3_transfer (device, NULL, WRITE, command, size + 1, NULL, 0, NODELAY); + rc = hw_ostc3_transfer (device, NULL, WRITE, command, size + 1, NULL, 0, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; @@ -1036,7 +1080,7 @@ hw_ostc3_device_config_reset (dc_device_t *abstract) return rc; // Send the command. - rc = hw_ostc3_transfer (device, NULL, RESET, NULL, 0, NULL, 0, NODELAY); + rc = hw_ostc3_transfer (device, NULL, RESET, NULL, 0, NULL, 0, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; @@ -1262,7 +1306,7 @@ hw_ostc3_firmware_erase (hw_ostc3_device_t *device, unsigned int addr, unsigned array_uint24_be_set (buffer, addr); buffer[3] = blocks; - return hw_ostc3_transfer (device, NULL, S_ERASE, buffer, sizeof (buffer), NULL, 0, delay); + return hw_ostc3_transfer (device, NULL, S_ERASE, buffer, sizeof (buffer), NULL, 0, NULL, delay); } static dc_status_t @@ -1272,7 +1316,7 @@ hw_ostc3_firmware_block_read (hw_ostc3_device_t *device, unsigned int addr, unsi array_uint24_be_set (buffer, addr); array_uint24_be_set (buffer + 3, block_size); - return hw_ostc3_transfer (device, NULL, S_BLOCK_READ, buffer, sizeof (buffer), block, block_size, NODELAY); + return hw_ostc3_transfer (device, NULL, S_BLOCK_READ, buffer, sizeof (buffer), block, block_size, NULL, NODELAY); } static dc_status_t @@ -1287,7 +1331,7 @@ hw_ostc3_firmware_block_write1 (hw_ostc3_device_t *device, unsigned int addr, co array_uint24_be_set (buffer, addr); memcpy (buffer + 3, block, block_size); - return hw_ostc3_transfer (device, NULL, S_BLOCK_WRITE, buffer, 3 + block_size, NULL, 0, TIMEOUT); + return hw_ostc3_transfer (device, NULL, S_BLOCK_WRITE, buffer, 3 + block_size, NULL, 0, NULL, TIMEOUT); } static dc_status_t @@ -1306,7 +1350,7 @@ hw_ostc3_firmware_block_write2 (hw_ostc3_device_t *device, unsigned int address, array_uint24_be_set (buffer, address); memcpy (buffer + 3, data + nbytes, SZ_FIRMWARE_BLOCK2); - status = hw_ostc3_transfer (device, NULL, S_BLOCK_WRITE2, buffer, sizeof(buffer), NULL, 0, NODELAY); + status = hw_ostc3_transfer (device, NULL, S_BLOCK_WRITE2, buffer, sizeof(buffer), NULL, 0, NULL, NODELAY); if (status != DC_STATUS_SUCCESS) { return status; } @@ -1347,7 +1391,7 @@ hw_ostc3_firmware_upgrade (dc_device_t *abstract, unsigned int checksum) buffer[4] = (buffer[4]<<1 | buffer[4]>>7); } - rc = hw_ostc3_transfer (device, NULL, S_UPGRADE, buffer, sizeof (buffer), NULL, 0, NODELAY); + rc = hw_ostc3_transfer (device, NULL, S_UPGRADE, buffer, sizeof (buffer), NULL, 0, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) { ERROR (context, "Failed to send flash firmware command"); return rc; @@ -1529,7 +1573,7 @@ hw_ostc3_device_fwupdate4 (dc_device_t *abstract, const char *filename) // Read the firmware version info. unsigned char fwinfo[SZ_FWINFO] = {0}; status = hw_ostc3_transfer (device, NULL, S_FWINFO, - data + offset + 4, 1, fwinfo, sizeof(fwinfo), NODELAY); + data + offset + 4, 1, fwinfo, sizeof(fwinfo), NULL, NODELAY); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the firmware info."); goto error; @@ -1542,7 +1586,7 @@ hw_ostc3_device_fwupdate4 (dc_device_t *abstract, const char *filename) !array_isequal(fwinfo, sizeof(fwinfo), 0xFF)) { status = hw_ostc3_transfer (device, &progress, S_UPLOAD, - data + offset, length, NULL, 0, usecs / 1000); + data + offset, length, NULL, 0, NULL, usecs / 1000); if (status != DC_STATUS_SUCCESS) { goto error; } From 8a6abab1dab04abf8ad7bd67b83d904347bab5d9 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 1 Nov 2022 19:55:30 +0100 Subject: [PATCH 17/58] Update the Github actions The Github actions need an update due to upcoming deprecations: * The 'set-output' command is deprecated [1]. Update to write to the GITHUB_OUTPUT environment file instead. * All Github actions using Node.js 12 are deprecated [2]. Update the following actions to a newer version using Node.js 16: - actions/checkout - actions/upload-artifact - microsoft/setup-msbuild * The Github create-release and upload-release-asset actions are no longer maintained. Replace with an alternative solution using the Github CLI. [1] https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ [2] https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/ --- .github/workflows/build.yml | 18 +++++++++--------- .github/workflows/release.yml | 32 +++++++++----------------------- 2 files changed, 18 insertions(+), 32 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 161c2f3..a2bee25 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: CC: ${{ matrix.compiler }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install dependencies run: sudo apt-get install libbluetooth-dev libusb-1.0-0-dev - run: autoreconf --install --force @@ -30,7 +30,7 @@ jobs: run: | make install DESTDIR=$PWD/artifacts tar -czf ${{ github.job }}-${{ matrix.compiler }}.tar.gz -C artifacts usr - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: ${{ github.job }}-${{ matrix.compiler }} path: ${{ github.job }}-${{ matrix.compiler }}.tar.gz @@ -50,7 +50,7 @@ jobs: CC: ${{ matrix.compiler }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install dependencies run: brew install autoconf automake libtool hidapi libusb - run: autoreconf --install --force @@ -61,7 +61,7 @@ jobs: run: | make install DESTDIR=$PWD/artifacts tar -czf ${{ github.job }}-${{ matrix.compiler }}.tar.gz -C artifacts usr - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: ${{ github.job }}-${{ matrix.compiler }} path: ${{ github.job }}-${{ matrix.compiler }}.tar.gz @@ -78,7 +78,7 @@ jobs: arch: [i686, x86_64] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install dependencies run: sudo apt-get install gcc-mingw-w64 binutils-mingw-w64 mingw-w64-tools - name: Install libusb @@ -118,7 +118,7 @@ jobs: run: | make install DESTDIR=$PWD/artifacts tar -czf ${{ github.job }}-${{ matrix.arch }}.tar.gz -C artifacts usr - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: ${{ github.job }}-${{ matrix.arch }} path: ${{ github.job }}-${{ matrix.arch }}.tar.gz @@ -138,7 +138,7 @@ jobs: CONFIGURATION: Release steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: msys2/setup-msys2@v2 with: install: autoconf automake libtool pkg-config make gcc @@ -146,9 +146,9 @@ jobs: autoreconf --install --force ./configure --prefix=/usr shell: msys2 {0} - - uses: microsoft/setup-msbuild@v1.0.2 + - uses: microsoft/setup-msbuild@v1 - run: msbuild -m -p:Platform=${{ matrix.platform }} -p:Configuration=${{ env.CONFIGURATION }} msvc/libdivecomputer.vcxproj - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: ${{ github.job }}-${{ matrix.platform }} path: msvc/${{ matrix.platform }}/${{ env.CONFIGURATION }}/bin diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 37f578e..fcf8f23 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,19 +9,13 @@ jobs: name: Release runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Version number id: version run: | VERSION="${GITHUB_REF/refs\/tags\/v/}" - if [ "${VERSION}" = "${VERSION%%-*}" ]; then - PRERELEASE=false - else - PRERELEASE=true - fi - echo ::set-output name=version::${VERSION} - echo ::set-output name=prerelease::${PRERELEASE} + echo "version=${VERSION}" >> $GITHUB_OUTPUT - name: Build distribution tarball id: build @@ -41,21 +35,13 @@ jobs: exit 1 fi - - uses: actions/create-release@v1 + - name: Create Github release id: release + run: | + VERSION="${{ steps.version.outputs.version }}" + if [ "${VERSION}" != "${VERSION%%-*}" ]; then + PRERELEASE="-p" + fi + gh release create ${PRERELEASE} "${{ github.ref }}" "libdivecomputer-${VERSION}.tar.gz" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: ${{ github.ref }} - prerelease: ${{ steps.version.outputs.prerelease }} - - - uses: actions/upload-release-asset@v1 - id: upload - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.release.outputs.upload_url }} - asset_path: libdivecomputer-${{ steps.version.outputs.version }}.tar.gz - asset_name: libdivecomputer-${{ steps.version.outputs.version }}.tar.gz - asset_content_type: application/gzip From bf93040ab1f7b0170e8bc2fc0f21851eb4e8795e Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 6 Nov 2022 20:08:11 +0100 Subject: [PATCH 18/58] Receive only a single USB packet at a time The hidapi based implementation returns as soon as the first packet is received, while the libusb based implementation tries to read the requested number of bytes. That fails with a timeout if the requested number of bytes is larger than the size of a single packet and no further packets are received. Avoid this problem by limiting the size to the maximum packet size. --- src/usbhid.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/usbhid.c b/src/usbhid.c index b3805ca..acd20ef 100644 --- a/src/usbhid.c +++ b/src/usbhid.c @@ -86,6 +86,7 @@ struct dc_usbhid_device_t { int interface; unsigned char endpoint_in; unsigned char endpoint_out; + unsigned short packetsize; #elif defined(USE_HIDAPI) char *path; #endif @@ -125,6 +126,7 @@ typedef struct dc_usbhid_t { int interface; unsigned char endpoint_in; unsigned char endpoint_out; + unsigned short packetsize; unsigned int timeout; #elif defined(USE_HIDAPI) hid_device *handle; @@ -507,6 +509,7 @@ dc_usbhid_iterator_next (dc_iterator_t *abstract, void *out) device->interface = interface->bInterfaceNumber; device->endpoint_in = ep_in->bEndpointAddress; device->endpoint_out = ep_out->bEndpointAddress; + device->packetsize = ep_in->wMaxPacketSize; *(dc_usbhid_device_t **) out = device; @@ -614,6 +617,7 @@ dc_usbhid_open (dc_iostream_t **out, dc_context_t *context, dc_usbhid_device_t * usbhid->interface = device->interface; usbhid->endpoint_in = device->endpoint_in; usbhid->endpoint_out = device->endpoint_out; + usbhid->packetsize = device->packetsize; usbhid->timeout = 0; #elif defined(USE_HIDAPI) @@ -704,6 +708,10 @@ dc_usbhid_read (dc_iostream_t *abstract, void *data, size_t size, size_t *actual int nbytes = 0; #if defined(USE_LIBUSB) + if (size > usbhid->packetsize) { + size = usbhid->packetsize; + } + int rc = libusb_interrupt_transfer (usbhid->handle, usbhid->endpoint_in, data, size, &nbytes, usbhid->timeout); if (rc != LIBUSB_SUCCESS || nbytes < 0) { ERROR (abstract->context, "Usb read interrupt transfer failed (%s).", From c2102f62d62c13380ab20eb289fdb593f7867551 Mon Sep 17 00:00:00 2001 From: Greg McLaughlin Date: Tue, 8 Nov 2022 23:52:24 +0100 Subject: [PATCH 19/58] Add support for parsing bookmark events The bookmark value is a bitfield indicating the type of bookmark: 1 - Pressed the bookmark button during a dive 2 - Reset the stopwatch 4 - Unknown 8 - Unknown --- src/mares_iconhd_parser.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index 3b17482..184bf92 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -1074,6 +1074,7 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t unsigned int depth = 0, temperature = 0; unsigned int gasmix = 0, alarms = 0; unsigned int decostop = 0, decodepth = 0, decotime = 0, tts = 0; + unsigned int bookmark = 0; if (parser->model == GENIUS || parser->model == HORIZON) { if (parser->logformat == 1) { if (!mares_genius_isvalid (data + offset, SDPT_SIZE, SDPT_TYPE)) { @@ -1087,6 +1088,7 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t alarms = array_uint32_le (data + offset + marker + 0x14); misc = array_uint32_le (data + offset + marker + 0x18); deco = array_uint32_le (data + offset + marker + 0x1C); + bookmark = (misc >> 2) & 0x0F; gasmix = (misc >> 6) & 0x0F; decostop = (misc >> 10) & 0x01; if (decostop) { @@ -1108,6 +1110,7 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t decotime = array_uint16_le (data + offset + marker + 0x0A); alarms = array_uint32_le (data + offset + marker + 0x0C); misc = array_uint32_le (data + offset + marker + 0x14); + bookmark = (misc >> 2) & 0x0F; gasmix = (misc >> 6) & 0x0F; decostop = (misc >> 18) & 0x01; decodepth = (misc >> 19) & 0x7F; @@ -1144,6 +1147,15 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t } } + // Bookmark + if (bookmark) { + sample.event.type = SAMPLE_EVENT_BOOKMARK; + sample.event.time = 0; + sample.event.flags = 0; + sample.event.value = bookmark; + if (callback) callback (DC_SAMPLE_EVENT, sample, userdata); + } + if (parser->model == GENIUS || parser->model == HORIZON) { // Deco stop / NDL. if (decostop) { From 2f3a0579698abe3dd45c86b65b963e07e9f7f057 Mon Sep 17 00:00:00 2001 From: Charlotte Koch Date: Sun, 13 Nov 2022 22:38:33 -0800 Subject: [PATCH 20/58] Look for select(2) in a more reliable place --- src/serial_posix.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/serial_posix.c b/src/serial_posix.c index 8872982..b23f69d 100644 --- a/src/serial_posix.c +++ b/src/serial_posix.c @@ -30,6 +30,7 @@ #include // fcntl #include // tcgetattr, tcsetattr, cfsetispeed, cfsetospeed, tcflush, tcsendbreak #include // ioctl +#include // select #ifdef HAVE_LINUX_SERIAL_H #include #endif From 68741307432d7b0575e6756389eccd4ef15572aa Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 18 Nov 2022 20:40:44 +0100 Subject: [PATCH 21/58] Add the return type to the function definition In commit 12c77a228e84f1ceed520b6afb53b4b64ea9def6, the return type of the function was accidentally omitted. --- src/reefnet_sensus_parser.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/reefnet_sensus_parser.c b/src/reefnet_sensus_parser.c index a93914b..00c3b47 100644 --- a/src/reefnet_sensus_parser.c +++ b/src/reefnet_sensus_parser.c @@ -141,6 +141,7 @@ reefnet_sensus_parser_set_clock (dc_parser_t *abstract, unsigned int devtime, dc } +static dc_status_t reefnet_sensus_parser_set_atmospheric (dc_parser_t *abstract, double atmospheric) { reefnet_sensus_parser_t *parser = (reefnet_sensus_parser_t *) abstract; From 5218d3921ab2a9238f8e4de5dd7958071d0c1909 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 22 Nov 2022 19:39:17 +0100 Subject: [PATCH 22/58] Read the software and hardware version --- src/uwatec_smart.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/uwatec_smart.c b/src/uwatec_smart.c index d5d7ab0..38774bb 100644 --- a/src/uwatec_smart.c +++ b/src/uwatec_smart.c @@ -36,6 +36,8 @@ #define PACKETSIZE_USBHID_TX 32 #define CMD_MODEL 0x10 +#define CMD_HARDWARE 0x11 +#define CMD_SOFTWARE 0x13 #define CMD_SERIAL 0x14 #define CMD_DEVTIME 0x1A #define CMD_HANDSHAKE1 0x1B @@ -557,6 +559,18 @@ uwatec_smart_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) if (rc != DC_STATUS_SUCCESS) return rc; + // Read the hardware version. + unsigned char hardware[1] = {0}; + rc = uwatec_smart_transfer (device, CMD_HARDWARE, NULL, 0, hardware, sizeof (hardware)); + if (rc != DC_STATUS_SUCCESS) + return rc; + + // Read the software version. + unsigned char software[1] = {0}; + rc = uwatec_smart_transfer (device, CMD_SOFTWARE, NULL, 0, software, sizeof (software)); + if (rc != DC_STATUS_SUCCESS) + return rc; + // Read the serial number. unsigned char serial[4] = {0}; rc = uwatec_smart_transfer (device, CMD_SERIAL, NULL, 0, serial, sizeof (serial)); @@ -574,7 +588,7 @@ uwatec_smart_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) device->devtime = array_uint32_le (devtime); // Update and emit a progress event. - progress.current += 9; + progress.current += 11; device_event_emit (&device->base, DC_EVENT_PROGRESS, &progress); // Emit a clock event. @@ -586,7 +600,7 @@ uwatec_smart_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) // Emit a device info event. dc_event_devinfo_t devinfo; devinfo.model = model[0]; - devinfo.firmware = 0; + devinfo.firmware = bcd2dec (software[0]); devinfo.serial = array_uint32_le (serial); device_event_emit (&device->base, DC_EVENT_DEVINFO, &devinfo); @@ -610,7 +624,7 @@ uwatec_smart_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) unsigned int length = array_uint32_le (answer); // Update and emit a progress event. - progress.maximum = 4 + 9 + (length ? length + 4 : 0); + progress.maximum = 4 + 11 + (length ? length + 4 : 0); progress.current += 4; device_event_emit (&device->base, DC_EVENT_PROGRESS, &progress); From 913a65fde6cd8c06aad6c2249f7860a6a6382d38 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 6 Nov 2022 20:54:34 +0100 Subject: [PATCH 23/58] Allow to specify the hidapi library variant On Linux, the hidapi library is usually available in two variants: hidapi-libusb and hidapi-hidraw. By default, the autotools build system won't be able to detect those variants (due to the difference in the name) and will automatically fallback to the libusb implementation instead. If for some reason the hidapi library should be used, the preferred hidapi variant can now be specified during configuation with a parameter: ./configure --with-hidapi=hidapi-libusb|hidapi-hidraw The default value for the parameter remains 'hidapi'. --- configure.ac | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/configure.ac b/configure.ac index ca39faf..5dc1c63 100644 --- a/configure.ac +++ b/configure.ac @@ -107,12 +107,12 @@ AS_IF([test "x$with_libusb" != "xno"], [ AC_ARG_WITH([hidapi], [AS_HELP_STRING([--without-hidapi], [Build without the hidapi library])], - [], [with_hidapi=auto]) + [], [with_hidapi=hidapi]) AS_IF([test "x$with_hidapi" != "xno"], [ - PKG_CHECK_MODULES([HIDAPI], [hidapi], [have_hidapi=yes], [have_hidapi=no]) + PKG_CHECK_MODULES([HIDAPI], [$with_hidapi], [have_hidapi=yes], [have_hidapi=no]) AS_IF([test "x$have_hidapi" = "xyes"], [ AC_DEFINE([HAVE_HIDAPI], [1], [hidapi library]) - DEPENDENCIES="$DEPENDENCIES hidapi" + DEPENDENCIES="$DEPENDENCIES $with_hidapi" ]) ]) From 2c5ebef5941da3bdd8f4bb1de7972c3c3fd4acf9 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 6 Nov 2022 21:07:48 +0100 Subject: [PATCH 24/58] Add udev rules for hidraw devices The hidapi-hidraw variant of the hidapi library needs access to the specific /dev/hidraw* device nodes. The existing udev rules for the USB devices don't apply to the hidraw device nodes. --- contrib/udev/libdivecomputer.rules | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/contrib/udev/libdivecomputer.rules b/contrib/udev/libdivecomputer.rules index 02ef7da..026afc5 100644 --- a/contrib/udev/libdivecomputer.rules +++ b/contrib/udev/libdivecomputer.rules @@ -1,26 +1,35 @@ # Atomic Aquatics Cobalt SUBSYSTEM=="usb", ATTR{idVendor}=="0471", ATTR{idProduct}=="0888", GROUP="plugdev" +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0471", ATTRS{idProduct}=="0888", GROUP="plugdev" # Suunto EON Steel SUBSYSTEM=="usb", ATTR{idVendor}=="1493", ATTR{idProduct}=="0030", GROUP="plugdev" +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1493", ATTRS{idProduct}=="0030", GROUP="plugdev" # Suunto EON Core SUBSYSTEM=="usb", ATTR{idVendor}=="1493", ATTR{idProduct}=="0033", GROUP="plugdev" +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1493", ATTRS{idProduct}=="0033", GROUP="plugdev" # Suunto D5 SUBSYSTEM=="usb", ATTR{idVendor}=="1493", ATTR{idProduct}=="0035", GROUP="plugdev" +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1493", ATTRS{idProduct}=="0035", GROUP="plugdev" # Suunto EON Steel Black SUBSYSTEM=="usb", ATTR{idVendor}=="1493", ATTR{idProduct}=="0036", GROUP="plugdev" +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1493", ATTRS{idProduct}=="0036", GROUP="plugdev" # Scubapro G2 SUBSYSTEM=="usb", ATTR{idVendor}=="2e6c", ATTR{idProduct}=="3201", GROUP="plugdev" +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2e6c", ATTRS{idProduct}=="3201", GROUP="plugdev" # Scubapro G2 Console SUBSYSTEM=="usb", ATTR{idVendor}=="2e6c", ATTR{idProduct}=="3211", GROUP="plugdev" +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2e6c", ATTRS{idProduct}=="3211", GROUP="plugdev" # Scubapro G2 HUD SUBSYSTEM=="usb", ATTR{idVendor}=="2e6c", ATTR{idProduct}=="4201", GROUP="plugdev" +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2e6c", ATTRS{idProduct}=="4201", GROUP="plugdev" # Scubapro Aladin Square SUBSYSTEM=="usb", ATTR{idVendor}=="c251", ATTR{idProduct}=="2006", GROUP="plugdev" +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="c251", ATTRS{idProduct}=="2006", GROUP="plugdev" From 2577afed5595a659d4be53907b7978cd2268e432 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 24 Nov 2022 21:14:07 +0100 Subject: [PATCH 25/58] Update libusb and hidapi in the CI builds The current hidapi version (v0.10.1) fails to build with newer autoconf versions (v2.70) due to a duplicated AC_CONFIG_MACRO_DIR macro in the configure.ac file. This is fixed in newer versions. --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a2bee25..4b2a3b1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -83,7 +83,7 @@ jobs: run: sudo apt-get install gcc-mingw-w64 binutils-mingw-w64 mingw-w64-tools - name: Install libusb env: - LIBUSB_VERSION: 1.0.24 + LIBUSB_VERSION: 1.0.26 run: | wget -c https://github.com/libusb/libusb/archive/refs/tags/v${LIBUSB_VERSION}.tar.gz tar xzf v${LIBUSB_VERSION}.tar.gz @@ -95,7 +95,7 @@ jobs: popd - name: Install hidapi env: - HIDAPI_VERSION: 0.10.1 + HIDAPI_VERSION: 0.12.0 run: | wget -c https://github.com/libusb/hidapi/archive/refs/tags/hidapi-${HIDAPI_VERSION}.tar.gz tar xzf hidapi-${HIDAPI_VERSION}.tar.gz From db2540485ec0062db3d18833ec9eab901979ba63 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 24 Nov 2022 21:18:53 +0100 Subject: [PATCH 26/58] Link hidapi statically against libgcc When compiling a 32bit dll with the mingw-w64 compiler, some 64bit integer arithmetic operations are implemented using functions from libgcc (e.g. __udivdi3 and __umoddi3 from libgcc_s_dw2-1.dll). This unexpected dependency is inconvenient for applications. The run-time dependency can be avoid by linking statically. --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4b2a3b1..b23c7f2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -101,7 +101,7 @@ jobs: tar xzf hidapi-${HIDAPI_VERSION}.tar.gz pushd hidapi-hidapi-${HIDAPI_VERSION} autoreconf --install --force - ./configure --host=${{ matrix.arch }}-w64-mingw32 --prefix=/usr + ./configure --host=${{ matrix.arch }}-w64-mingw32 --prefix=/usr LDFLAGS='-static-libgcc' make make install DESTDIR=$PWD/../artifacts popd From 755f23fdfab9437325a1ba541549e46d9995b1f7 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 1 Nov 2022 21:36:31 +0100 Subject: [PATCH 27/58] Ignore the first byte of the BLE packets The first byte of the BLE packets does no longer contain the size of the payload. Since BLE supports variable sized packets, we can simply ignore this byte and obtain the payload size from the BLE packet size. --- src/uwatec_smart.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/uwatec_smart.c b/src/uwatec_smart.c index 38774bb..d5df3db 100644 --- a/src/uwatec_smart.c +++ b/src/uwatec_smart.c @@ -310,6 +310,7 @@ uwatec_smart_usbhid_receive (uwatec_smart_device_t *device, dc_event_progress_t { dc_status_t rc = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; + dc_transport_t transport = dc_iostream_get_transport(device->iostream); unsigned char buf[PACKETSIZE_USBHID_RX]; size_t nbytes = 0; @@ -364,9 +365,11 @@ uwatec_smart_usbhid_receive (uwatec_smart_device_t *device, dc_event_progress_t * * It may be just an oddly implemented sequence number. Whatever. */ - unsigned int len = buf[0]; - if (len + 1 > transferred) - len = transferred-1; + unsigned int len = transferred - 1; + if (transport == DC_TRANSPORT_USBHID) { + if (len > buf[0]) + len = buf[0]; + } HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "rcv", buf + 1, len); From ed0b21beae7d778788f05c46aac000fac537696a Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 2 Nov 2022 19:46:42 +0100 Subject: [PATCH 28/58] Increase the BLE packet size In the latest G2 firmware v2.0, the size of the BLE packets increased to 101 bytes (with a one byte header and 100 bytes of actual payload). This caused the download to fail, because the internal buffer was suddenly too small for those larger packets. These larger packets are most likely due to an update in the BLE stack of the dive computer. Originally, the maximum BLE packet size was just 20 bytes (excluding the 4 byte L2CAP header and 3 bytes GATT header), but BLE 4.2 increased the maximum packet size to 244 bytes (or 251 bytes with the headers). The USB HID code path keeps using the same fixed size packets as before. --- src/uwatec_smart.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/uwatec_smart.c b/src/uwatec_smart.c index d5df3db..e7be4fc 100644 --- a/src/uwatec_smart.c +++ b/src/uwatec_smart.c @@ -275,9 +275,13 @@ uwatec_smart_usbhid_send (uwatec_smart_device_t *device, unsigned char cmd, cons { dc_status_t rc = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; - unsigned char buf[PACKETSIZE_USBHID_TX + 1]; + dc_transport_t transport = dc_iostream_get_transport(device->iostream); + unsigned char buf[DATASIZE + 3]; - if (size + 3 > sizeof(buf)) { + size_t packetsize = transport == DC_TRANSPORT_USBHID ? + PACKETSIZE_USBHID_TX + 1 : sizeof(buf); + + if (size > DATASIZE || size + 3 > packetsize) { ERROR (abstract->context, "Command too large (" DC_PRINTF_SIZE ").", size); return DC_STATUS_INVALIDARGS; } @@ -295,7 +299,7 @@ uwatec_smart_usbhid_send (uwatec_smart_device_t *device, unsigned char cmd, cons if (dc_iostream_get_transport(device->iostream) == DC_TRANSPORT_BLE) { rc = dc_iostream_write(device->iostream, buf + 1, size + 2, NULL); } else { - rc = dc_iostream_write(device->iostream, buf, sizeof(buf), NULL); + rc = dc_iostream_write(device->iostream, buf, packetsize, NULL); } if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the command."); @@ -311,12 +315,15 @@ uwatec_smart_usbhid_receive (uwatec_smart_device_t *device, dc_event_progress_t dc_status_t rc = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; dc_transport_t transport = dc_iostream_get_transport(device->iostream); - unsigned char buf[PACKETSIZE_USBHID_RX]; + unsigned char buf[DATASIZE + 1]; + + size_t packetsize = transport == DC_TRANSPORT_USBHID ? + PACKETSIZE_USBHID_RX : sizeof(buf); size_t nbytes = 0; while (nbytes < size) { size_t transferred = 0; - rc = dc_iostream_read (device->iostream, buf, sizeof(buf), &transferred); + rc = dc_iostream_read (device->iostream, buf, packetsize, &transferred); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to receive the packet."); return rc; From 59a0844ee6b4b48d46ede32f4de556642cd5603e Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sat, 3 Dec 2022 13:17:04 +0100 Subject: [PATCH 29/58] Fix the progress events when no dives are present When no dives are present, the maximum value for the progress events is set to zero, which triggers an assert. Fixed by letting the progress events reach 100% instead. --- src/deepsix_excursion.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/deepsix_excursion.c b/src/deepsix_excursion.c index 53135de..2ee02eb 100644 --- a/src/deepsix_excursion.c +++ b/src/deepsix_excursion.c @@ -313,7 +313,8 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call unsigned int ndives = array_uint16_le (rsp_index); // Update and emit a progress event. - progress.maximum = ndives * NSTEPS; + progress.current = 1 * NSTEPS; + progress.maximum = (ndives + 1) * NSTEPS; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); dc_buffer_t *buffer = dc_buffer_new(0); @@ -341,7 +342,7 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call unsigned int length = array_uint32_le (rsp_header + 8); // Update and emit a progress event. - progress.current = i * NSTEPS + STEP(sizeof(rsp_header), sizeof(rsp_header) + length); + progress.current = (i + 1) * NSTEPS + STEP(sizeof(rsp_header), sizeof(rsp_header) + length); device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); dc_buffer_clear(buffer); @@ -377,7 +378,7 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call } // Update and emit a progress event. - progress.current = i * NSTEPS + STEP(sizeof(rsp_header) + offset + n, sizeof(rsp_header) + length); + progress.current = (i + 1) * NSTEPS + STEP(sizeof(rsp_header) + offset + n, sizeof(rsp_header) + length); device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); if (!dc_buffer_append(buffer, rsp_profile, n)) { From 79c9c5b7f97251c7879e939a539be26abb3c626c Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 24 Nov 2022 17:16:27 +0100 Subject: [PATCH 30/58] Add support for the Oceanic Geo Air The Oceanic Geo Air appears to be compatible with the OC1. --- src/descriptor.c | 2 ++ src/oceanic_atom2.c | 1 + src/oceanic_atom2_parser.c | 26 ++++++++++++++++++-------- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index f735368..b4d7930 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -272,6 +272,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Sherwood", "Beacon", DC_FAMILY_OCEANIC_ATOM2, 0x4742, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, {"Aqualung", "i470TC", DC_FAMILY_OCEANIC_ATOM2, 0x4743, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, {"Aqualung", "i200C", DC_FAMILY_OCEANIC_ATOM2, 0x4749, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, + {"Oceanic", "Geo Air", DC_FAMILY_OCEANIC_ATOM2, 0x474B, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, /* Mares Nemo */ {"Mares", "Nemo", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Nemo Steel", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL}, @@ -706,6 +707,7 @@ static int dc_filter_oceanic (dc_transport_t transport, const void *userdata, vo 0x4742, // Sherwood Beacon 0x4743, // Aqualung i470TC 0x4749, // Aqualung i200C + 0x474B, // Oceanic Geo Air }; if (transport == DC_TRANSPORT_BLE) { diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index f8699ea..4de6dec 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -479,6 +479,7 @@ static const oceanic_common_version_t versions[] = { {"WISDOM04 \0\0 1024", 0, &oceanic_oc1_layout}, {"AQUA470C \0\0 1024", 0, &oceanic_oc1_layout}, {"AQUA200C \0\0 1024", 0, &oceanic_oc1_layout}, + {"GEOAIR \0\0 1024", 0, &oceanic_oc1_layout}, {"OCEANOCI \0\0 1024", 0, &oceanic_oci_layout}, diff --git a/src/oceanic_atom2_parser.c b/src/oceanic_atom2_parser.c index 66bec88..56d9a00 100644 --- a/src/oceanic_atom2_parser.c +++ b/src/oceanic_atom2_parser.c @@ -102,6 +102,7 @@ #define BEACON 0x4742 #define I470TC 0x4743 #define I200CV2 0x4749 +#define GEOAIR 0x474B #define NORMAL 0 #define GAUGE 1 @@ -178,7 +179,8 @@ oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, unsigned model == I300 || model == I550 || model == I200 || model == I200C || model == I300C || model == GEO40 || - model == VEO40 || model == I470TC) { + model == VEO40 || model == I470TC || + model == GEOAIR) { parser->headersize -= PAGESIZE; } else if (model == VT4 || model == VT41) { parser->headersize += PAGESIZE; @@ -284,6 +286,7 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim case WISDOM4: case I470TC: case I200CV2: + case GEOAIR: datetime->year = ((p[5] & 0xE0) >> 5) + ((p[7] & 0xE0) >> 2) + 2000; datetime->month = (p[3] & 0x0F); datetime->day = ((p[0] & 0x80) >> 3) + ((p[3] & 0xF0) >> 4); @@ -719,7 +722,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == VTX || parser->model == I450T || parser->model == I750TC || parser->model == PROPLUSX || parser->model == I770R || parser->model == I470TC || - parser->model == SAGE || parser->model == BEACON) { + parser->model == SAGE || parser->model == BEACON || + parser->model == GEOAIR) { samplesize = PAGESIZE; } @@ -896,7 +900,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I200 || parser->model == I100 || parser->model == I300C || parser->model == I200C || parser->model == GEO40 || parser->model == VEO40 || - parser->model == I470TC || parser->model == I200CV2) { + parser->model == I470TC || parser->model == I200CV2 || + parser->model == GEOAIR) { temperature = data[offset + 3]; } else if (parser->model == OCS || parser->model == TX1) { temperature = data[offset + 1]; @@ -940,7 +945,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ if (have_pressure) { if (parser->model == OC1A || parser->model == OC1B || parser->model == OC1C || parser->model == OCI || - parser->model == I450T || parser->model == I470TC) + parser->model == I450T || parser->model == I470TC || + parser->model == GEOAIR) pressure = (data[offset + 10] + (data[offset + 11] << 8)) & 0x0FFF; else if (parser->model == VT4 || parser->model == VT41|| parser->model == ATOM3 || parser->model == ATOM31 || @@ -975,7 +981,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I200 || parser->model == I100 || parser->model == I300C || parser->model == I200C || parser->model == GEO40 || parser->model == VEO40 || - parser->model == I470TC || parser->model == I200CV2) + parser->model == I470TC || parser->model == I200CV2 || + parser->model == GEOAIR) depth = (data[offset + 4] + (data[offset + 5] << 8)) & 0x0FFF; else if (parser->model == ATOM1) depth = data[offset + 3] * 16; @@ -1031,7 +1038,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I100 || parser->model == I300C || parser->model == I450T || parser->model == I200C || parser->model == GEO40 || parser->model == VEO40 || - parser->model == I470TC || parser->model == I200CV2) { + parser->model == I470TC || parser->model == I200CV2 || + parser->model == GEOAIR) { decostop = (data[offset + 7] & 0xF0) >> 4; decotime = array_uint16_le(data + offset + 6) & 0x0FFF; have_deco = 1; @@ -1056,7 +1064,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ } else if (parser->model == I450T || parser->model == OC1A || parser->model == OC1B || parser->model == OC1C || parser->model == OCI || parser->model == PROPLUSX || - parser->model == I770R || parser->model == I470TC) { + parser->model == I770R || parser->model == I470TC || + parser->model == GEOAIR) { rbt = array_uint16_le(data + offset + 8) & 0x01FF; have_rbt = 1; } else if (parser->model == VISION || parser->model == XPAIR || @@ -1073,7 +1082,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ // Bookmarks unsigned int have_bookmark = 0; if (parser->model == OC1A || parser->model == OC1B || - parser->model == OC1C || parser->model == OCI) { + parser->model == OC1C || parser->model == OCI || + parser->model == GEOAIR) { have_bookmark = data[offset + 12] & 0x80; } if (have_bookmark) { From 094a225363304dcbfcf920e069c8a8c4f6aaba47 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 2 Dec 2022 19:46:31 +0100 Subject: [PATCH 31/58] Detect the posix unistd.h header file The getopt function is defined in the unistd.h header file. This header file is only available on posix compatible systems. For example, on Windows it's not available when building without mingw. --- configure.ac | 2 +- examples/dctool.c | 4 +++- examples/dctool_download.c | 4 +++- examples/dctool_dump.c | 4 +++- examples/dctool_fwupdate.c | 4 +++- examples/dctool_help.c | 4 +++- examples/dctool_list.c | 4 +++- examples/dctool_parse.c | 4 +++- examples/dctool_read.c | 4 +++- examples/dctool_scan.c | 4 +++- examples/dctool_timesync.c | 4 +++- examples/dctool_version.c | 4 +++- examples/dctool_write.c | 4 +++- 13 files changed, 37 insertions(+), 13 deletions(-) diff --git a/configure.ac b/configure.ac index 5dc1c63..32496f6 100644 --- a/configure.ac +++ b/configure.ac @@ -157,7 +157,7 @@ AC_CHECK_HEADERS([sys/socket.h linux/types.h linux/irda.h], , , [ # Checks for header files. AC_CHECK_HEADERS([linux/serial.h]) AC_CHECK_HEADERS([IOKit/serial/ioss.h]) -AC_CHECK_HEADERS([getopt.h]) +AC_CHECK_HEADERS([unistd.h getopt.h]) AC_CHECK_HEADERS([sys/param.h]) AC_CHECK_HEADERS([pthread.h]) AC_CHECK_HEADERS([mach/mach_time.h]) diff --git a/examples/dctool.c b/examples/dctool.c index 3104bc4..9d3f74b 100644 --- a/examples/dctool.c +++ b/examples/dctool.c @@ -24,10 +24,12 @@ #endif #include -#include #include #include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_download.c b/examples/dctool_download.c index 2a39f5f..04aedcf 100644 --- a/examples/dctool_download.c +++ b/examples/dctool_download.c @@ -24,9 +24,11 @@ #endif #include -#include #include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_dump.c b/examples/dctool_dump.c index 6aa2e4a..966edbc 100644 --- a/examples/dctool_dump.c +++ b/examples/dctool_dump.c @@ -24,9 +24,11 @@ #endif #include -#include #include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_fwupdate.c b/examples/dctool_fwupdate.c index 596ccb1..4395c39 100644 --- a/examples/dctool_fwupdate.c +++ b/examples/dctool_fwupdate.c @@ -24,8 +24,10 @@ #endif #include -#include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_help.c b/examples/dctool_help.c index 2848c24..33ea0a5 100644 --- a/examples/dctool_help.c +++ b/examples/dctool_help.c @@ -24,8 +24,10 @@ #endif #include -#include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_list.c b/examples/dctool_list.c index 89e5386..bc70d13 100644 --- a/examples/dctool_list.c +++ b/examples/dctool_list.c @@ -24,8 +24,10 @@ #endif #include -#include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_parse.c b/examples/dctool_parse.c index 6b7da08..b898b5c 100644 --- a/examples/dctool_parse.c +++ b/examples/dctool_parse.c @@ -24,9 +24,11 @@ #endif #include -#include #include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_read.c b/examples/dctool_read.c index 2a9bf18..31ffc8d 100644 --- a/examples/dctool_read.c +++ b/examples/dctool_read.c @@ -24,8 +24,10 @@ #endif #include -#include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_scan.c b/examples/dctool_scan.c index 868a9a7..378a8f8 100644 --- a/examples/dctool_scan.c +++ b/examples/dctool_scan.c @@ -24,8 +24,10 @@ #endif #include -#include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_timesync.c b/examples/dctool_timesync.c index 35647b1..eeff0bb 100644 --- a/examples/dctool_timesync.c +++ b/examples/dctool_timesync.c @@ -24,8 +24,10 @@ #endif #include -#include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_version.c b/examples/dctool_version.c index deb0702..dbb7a40 100644 --- a/examples/dctool_version.c +++ b/examples/dctool_version.c @@ -24,8 +24,10 @@ #endif #include -#include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_write.c b/examples/dctool_write.c index fc76c78..04db198 100644 --- a/examples/dctool_write.c +++ b/examples/dctool_write.c @@ -24,8 +24,10 @@ #endif #include -#include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif From f4fae1b9f6745dcbc17d22f14bdc84c57548416f Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 2 Dec 2022 20:07:36 +0100 Subject: [PATCH 32/58] Add some workarounds for the msvc compiler --- examples/utils.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/utils.h b/examples/utils.h index da8a65b..0fb2712 100644 --- a/examples/utils.h +++ b/examples/utils.h @@ -26,6 +26,12 @@ extern "C" { #endif /* __cplusplus */ +#ifdef _MSC_VER +#define snprintf _snprintf +#define strcasecmp _stricmp +#define strncasecmp _strnicmp +#endif + #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) #define FUNCTION __func__ #else From 9019805f52ff56b0f45b4aa95bfbb020633134f3 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 18 Dec 2022 11:16:01 +0100 Subject: [PATCH 33/58] Validate the parameter before calling the vtable function This removes the need to validate the date/time pointer in every single backend. --- src/deepsix_excursion.c | 2 +- src/device.c | 3 +++ src/divesystem_idive.c | 5 ----- src/hw_frog.c | 5 ----- src/hw_ostc.c | 5 ----- src/hw_ostc3.c | 5 ----- src/mclean_extreme.c | 5 ----- src/sporasub_sp2.c | 2 +- 8 files changed, 5 insertions(+), 27 deletions(-) diff --git a/src/deepsix_excursion.c b/src/deepsix_excursion.c index 2ee02eb..c6eaaa8 100644 --- a/src/deepsix_excursion.c +++ b/src/deepsix_excursion.c @@ -408,7 +408,7 @@ deepsix_excursion_device_timesync (dc_device_t *abstract, const dc_datetime_t *d dc_status_t status = DC_STATUS_SUCCESS; deepsix_excursion_device_t *device = (deepsix_excursion_device_t *) abstract; - if (datetime == NULL || datetime->year < 2000) { + if (datetime->year < 2000) { ERROR (abstract->context, "Invalid date/time value specified."); return DC_STATUS_INVALIDARGS; } diff --git a/src/device.c b/src/device.c index 3080e6a..66944b8 100644 --- a/src/device.c +++ b/src/device.c @@ -404,6 +404,9 @@ dc_device_timesync (dc_device_t *device, const dc_datetime_t *datetime) if (device->vtable->timesync == NULL) return DC_STATUS_UNSUPPORTED; + if (datetime == NULL) + return DC_STATUS_INVALIDARGS; + return device->vtable->timesync (device, datetime); } diff --git a/src/divesystem_idive.c b/src/divesystem_idive.c index 61ec469..0410cd2 100644 --- a/src/divesystem_idive.c +++ b/src/divesystem_idive.c @@ -608,11 +608,6 @@ divesystem_idive_device_timesync (dc_device_t *abstract, const dc_datetime_t *da return DC_STATUS_UNSUPPORTED; } - if (datetime == NULL) { - ERROR (abstract->context, "Invalid parameter specified."); - return DC_STATUS_INVALIDARGS; - } - // Get the UTC timestamp. dc_ticks_t timestamp = dc_datetime_mktime(datetime); if (timestamp == -1) { diff --git a/src/hw_frog.c b/src/hw_frog.c index 74fc170..f98523b 100644 --- a/src/hw_frog.c +++ b/src/hw_frog.c @@ -473,11 +473,6 @@ hw_frog_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) { hw_frog_device_t *device = (hw_frog_device_t *) abstract; - if (datetime == NULL) { - ERROR (abstract->context, "Invalid parameter specified."); - return DC_STATUS_INVALIDARGS; - } - // Send the command. unsigned char packet[6] = { datetime->hour, datetime->minute, datetime->second, diff --git a/src/hw_ostc.c b/src/hw_ostc.c index 7017321..6fdc6eb 100644 --- a/src/hw_ostc.c +++ b/src/hw_ostc.c @@ -348,11 +348,6 @@ hw_ostc_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) dc_status_t status = DC_STATUS_SUCCESS; hw_ostc_device_t *device = (hw_ostc_device_t *) abstract; - if (datetime == NULL) { - ERROR (abstract->context, "Invalid parameter specified."); - return DC_STATUS_INVALIDARGS; - } - // Send the command. dc_status_t rc = hw_ostc_send (device, 'b', 1); if (rc != DC_STATUS_SUCCESS) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 07b7d8f..550bf7c 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -938,11 +938,6 @@ hw_ostc3_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) { hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; - if (datetime == NULL) { - ERROR (abstract->context, "Invalid parameter specified."); - return DC_STATUS_INVALIDARGS; - } - dc_status_t rc = hw_ostc3_device_init (device, DOWNLOAD); if (rc != DC_STATUS_SUCCESS) return rc; diff --git a/src/mclean_extreme.c b/src/mclean_extreme.c index b69546f..98038ff 100644 --- a/src/mclean_extreme.c +++ b/src/mclean_extreme.c @@ -471,11 +471,6 @@ mclean_extreme_device_timesync(dc_device_t *abstract, const dc_datetime_t *datet { mclean_extreme_device_t *device = (mclean_extreme_device_t *)abstract; - if (datetime == NULL) { - ERROR(abstract->context, "Invalid parameter specified."); - return DC_STATUS_INVALIDARGS; - } - // Get the UTC timestamp. dc_ticks_t ticks = dc_datetime_mktime(datetime); if (ticks == -1 || ticks < EPOCH || ticks - EPOCH > 0xFFFFFFFF) { diff --git a/src/sporasub_sp2.c b/src/sporasub_sp2.c index 8112305..8be2e8a 100644 --- a/src/sporasub_sp2.c +++ b/src/sporasub_sp2.c @@ -461,7 +461,7 @@ sporasub_sp2_device_timesync (dc_device_t *abstract, const dc_datetime_t *dateti dc_status_t status = DC_STATUS_SUCCESS; sporasub_sp2_device_t *device = (sporasub_sp2_device_t *) abstract; - if (datetime == NULL || datetime->year < 2000) { + if (datetime->year < 2000) { ERROR (abstract->context, "Invalid parameter specified."); return DC_STATUS_INVALIDARGS; } From 547b1cfd156c9679cae3f245d51ed5ef80c4113a Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 8 Dec 2022 19:04:44 +0100 Subject: [PATCH 34/58] Parse the timezone setting Since firmware version 5B and later, a timezone offset is available. --- src/deepsix_excursion_parser.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/deepsix_excursion_parser.c b/src/deepsix_excursion_parser.c index 8f14708..8e68702 100644 --- a/src/deepsix_excursion_parser.c +++ b/src/deepsix_excursion_parser.c @@ -41,6 +41,10 @@ #define DENSITY 1024.0 +#define FWVERSION(major,minor) ( \ + ((((major) + '0') & 0xFF) << 8) | \ + ((minor) & 0xFF)) + typedef struct deepsix_excursion_parser_t { dc_parser_t base; } deepsix_excursion_parser_t; @@ -98,6 +102,8 @@ deepsix_excursion_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dat if (size < HEADERSIZE) return DC_STATUS_DATAFORMAT; + unsigned int firmware = array_uint16_be (data + 48 + 4); + if (datetime) { datetime->year = data[12] + 2000; datetime->month = data[13]; @@ -105,7 +111,12 @@ deepsix_excursion_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dat datetime->hour = data[15]; datetime->minute = data[16]; datetime->second = data[17]; - datetime->timezone = DC_TIMEZONE_NONE; + + if (firmware >= FWVERSION(5, 'B')) { + datetime->timezone = (data[18] - 12) * 3600; + } else { + datetime->timezone = DC_TIMEZONE_NONE; + } } return DC_STATUS_SUCCESS; From b1f4ad94ebf07f34bfc1694cfa13009289775c12 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 20 Dec 2022 23:41:21 +0100 Subject: [PATCH 35/58] Fix the decoding of the CNS value The CNS value is reported as a fraction instead of a percentage. --- src/deepsix_excursion_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deepsix_excursion_parser.c b/src/deepsix_excursion_parser.c index 8e68702..687f86f 100644 --- a/src/deepsix_excursion_parser.c +++ b/src/deepsix_excursion_parser.c @@ -254,7 +254,7 @@ deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb unsigned int ceiling_time = array_uint16_le(data + offset + 6); } else if (type == CNS) { unsigned int cns = array_uint16_le(data + offset + 4); - sample.cns = cns; + sample.cns = cns / 100.0; if (callback) callback(DC_SAMPLE_CNS, sample, userdata); } From 989c9921543db10562f352f078d6cad4ab5b3333 Mon Sep 17 00:00:00 2001 From: Nikolay Zhekov <143594+nikz-codes@users.noreply.github.com> Date: Sat, 7 Jan 2023 17:45:06 +0000 Subject: [PATCH 36/58] Add Shearwater Perdix AI hardware ID --- src/shearwater_petrel.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shearwater_petrel.c b/src/shearwater_petrel.c index 08fa140..c047cca 100644 --- a/src/shearwater_petrel.c +++ b/src/shearwater_petrel.c @@ -246,6 +246,7 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call break; case 0x0C0D: case 0x7C2D: + case 0x8D6C: model = PERDIXAI; break; case 0xC407: From bf268d79b49cd4293c4a730990d7df211bae6303 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 13 Jan 2023 06:40:54 +0100 Subject: [PATCH 37/58] Fix parsing dives using dual Buhlmann and VPM algorithm Some iX3M models support a dual mode Buhlmann and VPM decompression algorithm. Currently libdivecomputer is only capable of reporting one of those two algorithms, but that's still better than returning an error. --- src/divesystem_idive_parser.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/divesystem_idive_parser.c b/src/divesystem_idive_parser.c index 1d19797..fb7efa6 100644 --- a/src/divesystem_idive_parser.c +++ b/src/divesystem_idive_parser.c @@ -50,6 +50,7 @@ #define BUHLMANN 0 #define VPM 1 +#define DUAL 2 typedef struct divesystem_idive_parser_t divesystem_idive_parser_t; @@ -365,6 +366,7 @@ divesystem_idive_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, return DC_STATUS_UNSUPPORTED; switch (parser->algorithm) { case BUHLMANN: + case DUAL: decomodel->type = DC_DECOMODEL_BUHLMANN; decomodel->conservatism = 0; decomodel->params.gf.low = parser->gf_low; From 90bb40e5ead28cfdc50056fa6c32a0622a9d2f7d Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 18 Jan 2023 19:12:30 +0100 Subject: [PATCH 38/58] Fix the iX3M 2 decompression algorithms The iX3M 2021 and iX3M 2 models use different values for the decompression algorithm. --- src/divesystem_idive_parser.c | 56 +++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/src/divesystem_idive_parser.c b/src/divesystem_idive_parser.c index fb7efa6..9be5f54 100644 --- a/src/divesystem_idive_parser.c +++ b/src/divesystem_idive_parser.c @@ -29,6 +29,7 @@ #define ISINSTANCE(parser) dc_device_isinstance((parser), &divesystem_idive_parser_vtable) #define ISIX3M(model) ((model) >= 0x21) +#define ISIX3M2(model) ((model) >= 0x60 && (model) < 0x1000) #define SZ_HEADER_IDIVE 0x32 #define SZ_SAMPLE_IDIVE 0x2A @@ -52,6 +53,11 @@ #define VPM 1 #define DUAL 2 +#define IX3M2_BUHLMANN 0 +#define IX3M2_ZHL16B 1 +#define IX3M2_ZHL16C 2 +#define IX3M2_VPM 3 + typedef struct divesystem_idive_parser_t divesystem_idive_parser_t; typedef struct divesystem_idive_gasmix_t { @@ -364,21 +370,41 @@ divesystem_idive_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, case DC_FIELD_DECOMODEL: if (parser->algorithm == INVALID) return DC_STATUS_UNSUPPORTED; - switch (parser->algorithm) { - case BUHLMANN: - case DUAL: - decomodel->type = DC_DECOMODEL_BUHLMANN; - decomodel->conservatism = 0; - decomodel->params.gf.low = parser->gf_low; - decomodel->params.gf.high = parser->gf_high; - break; - case VPM: - decomodel->type = DC_DECOMODEL_VPM; - decomodel->conservatism = 0; - break; - default: - ERROR (abstract->context, "Unknown deco algorithm %02x.", parser->algorithm); - return DC_STATUS_DATAFORMAT; + if (ISIX3M2(parser->model)) { + switch (parser->algorithm) { + case IX3M2_BUHLMANN: + case IX3M2_ZHL16B: + case IX3M2_ZHL16C: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->conservatism = 0; + decomodel->params.gf.low = parser->gf_low; + decomodel->params.gf.high = parser->gf_high; + break; + case IX3M2_VPM: + decomodel->type = DC_DECOMODEL_VPM; + decomodel->conservatism = 0; + break; + default: + ERROR (abstract->context, "Unknown deco algorithm %02x.", parser->algorithm); + return DC_STATUS_DATAFORMAT; + } + } else { + switch (parser->algorithm) { + case BUHLMANN: + case DUAL: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->conservatism = 0; + decomodel->params.gf.low = parser->gf_low; + decomodel->params.gf.high = parser->gf_high; + break; + case VPM: + decomodel->type = DC_DECOMODEL_VPM; + decomodel->conservatism = 0; + break; + default: + ERROR (abstract->context, "Unknown deco algorithm %02x.", parser->algorithm); + return DC_STATUS_DATAFORMAT; + } } break; default: From cf81ac79b3a280b2b6f35e349d2c3b0a6e9fd164 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 18 Jan 2023 19:45:12 +0100 Subject: [PATCH 39/58] Add support for the 300bar pressure sensor The new 300bar pressure transmitter records the pressure in units of 2bar, because otherwise the value doesn't fit into an 8-bit integer. --- src/divesystem_idive_parser.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/divesystem_idive_parser.c b/src/divesystem_idive_parser.c index 9be5f54..3270c55 100644 --- a/src/divesystem_idive_parser.c +++ b/src/divesystem_idive_parser.c @@ -585,6 +585,11 @@ divesystem_idive_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callba unsigned int flags = data[offset + 47] & 0xF0; unsigned int pressure = data[offset + 49]; + if (flags & 0x20) { + // 300 bar transmitter. + pressure *= 2; + } + if (flags & 0x80) { // No active transmitter available } else if (flags & 0x40) { From 45b9ee8376cee4d3156c3330fad64335f057e456 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 30 Dec 2022 15:20:10 +0100 Subject: [PATCH 40/58] Remove a duplicated include statement --- src/parser.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/parser.c b/src/parser.c index 1ae480a..c766477 100644 --- a/src/parser.c +++ b/src/parser.c @@ -35,7 +35,6 @@ #include "uwatec_memomouse.h" #include "uwatec_smart.h" #include "oceanic_atom2.h" -#include "oceanic_atom2.h" #include "oceanic_veo250.h" #include "oceanic_vtpro.h" #include "mares_darwin.h" From f59cbf0fe5364a88d69416d27d7a224fc93b7a31 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 30 Dec 2022 15:21:15 +0100 Subject: [PATCH 41/58] Move all model numbers to the common header --- src/oceanic_atom2.c | 8 --- src/oceanic_atom2_parser.c | 73 ---------------------------- src/oceanic_common.h | 97 +++++++++++++++++++++++++++++++++++++ src/oceanic_veo250_parser.c | 6 --- src/oceanic_vtpro.c | 2 - src/oceanic_vtpro_parser.c | 2 - 6 files changed, 97 insertions(+), 91 deletions(-) diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index 4de6dec..9f5cc17 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -35,14 +35,6 @@ #define ISINSTANCE(device) dc_device_isinstance((device), &oceanic_atom2_device_vtable.base) -#define PROPLUSX 0x4552 -#define VTX 0x4557 -#define I750TC 0x455A -#define SAGE 0x4647 -#define I770R 0x4651 -#define GEO40 0x4653 -#define BEACON 0x4742 - #define MAXPACKET 256 #define MAXRETRIES 2 #define MAXDELAY 16 diff --git a/src/oceanic_atom2_parser.c b/src/oceanic_atom2_parser.c index 56d9a00..f6b1f71 100644 --- a/src/oceanic_atom2_parser.c +++ b/src/oceanic_atom2_parser.c @@ -31,79 +31,6 @@ #define ISINSTANCE(parser) dc_parser_isinstance((parser), &oceanic_atom2_parser_vtable) -#define ATOM1 0x4250 -#define EPICA 0x4257 -#define VT3 0x4258 -#define T3A 0x4259 -#define ATOM2 0x4342 -#define GEO 0x4344 -#define MANTA 0x4345 -#define DATAMASK 0x4347 -#define COMPUMASK 0x4348 -#define OC1A 0x434E -#define F10A 0x434D -#define WISDOM2 0x4350 -#define INSIGHT2 0x4353 -#define ELEMENT2 0x4357 -#define VEO20 0x4359 -#define VEO30 0x435A -#define ZEN 0x4441 -#define ZENAIR 0x4442 -#define ATMOSAI2 0x4443 -#define PROPLUS21 0x4444 -#define GEO20 0x4446 -#define VT4 0x4447 -#define OC1B 0x4449 -#define VOYAGER2G 0x444B -#define ATOM3 0x444C -#define DG03 0x444D -#define OCS 0x4450 -#define OC1C 0x4451 -#define VT41 0x4452 -#define EPICB 0x4453 -#define T3B 0x4455 -#define ATOM31 0x4456 -#define A300AI 0x4457 -#define WISDOM3 0x4458 -#define A300 0x445A -#define TX1 0x4542 -#define MUNDIAL2 0x4543 -#define AMPHOS 0x4545 -#define AMPHOSAIR 0x4546 -#define PROPLUS3 0x4548 -#define F11A 0x4549 -#define OCI 0x454B -#define A300CS 0x454C -#define TALIS 0x454E -#define MUNDIAL3 0x4550 -#define PROPLUSX 0x4552 -#define F10B 0x4553 -#define F11B 0x4554 -#define XPAIR 0x4555 -#define VISION 0x4556 -#define VTX 0x4557 -#define I300 0x4559 -#define I750TC 0x455A -#define I450T 0x4641 -#define I550 0x4642 -#define I200 0x4646 -#define SAGE 0x4647 -#define I300C 0x4648 -#define I200C 0x4649 -#define I100 0x464E -#define I770R 0x4651 -#define I550C 0x4652 -#define GEO40 0x4653 -#define VEO40 0x4654 -#define WISDOM4 0x4655 -#define PROPLUS4 0x4656 -#define AMPHOS2 0x4657 -#define AMPHOSAIR2 0x4658 -#define BEACON 0x4742 -#define I470TC 0x4743 -#define I200CV2 0x4749 -#define GEOAIR 0x474B - #define NORMAL 0 #define GAUGE 1 #define FREEDIVE 2 diff --git a/src/oceanic_common.h b/src/oceanic_common.h index 915081e..16fd976 100644 --- a/src/oceanic_common.h +++ b/src/oceanic_common.h @@ -28,6 +28,103 @@ extern "C" { #endif /* __cplusplus */ +// vtpro +#define AERIS500AI 0x4151 +#define VERSAPRO 0x4155 +#define ATMOS2 0x4158 +#define PROPLUS2 0x4159 +#define ATMOSAI 0x4244 +#define VTPRO 0x4245 +#define WISDOM 0x4246 +#define ELITE 0x424F + +// veo250 +#define REACTPRO 0x4247 +#define VEO200 0x424B +#define VEO250 0x424C +#define XP5 0x4251 +#define VEO180 0x4252 +#define XR2 0x4255 +#define INSIGHT 0x425A +#define DG02 0x4352 + +// atom2 +#define ATOM1 0x4250 +#define EPICA 0x4257 +#define VT3 0x4258 +#define T3A 0x4259 +#define ATOM2 0x4342 +#define GEO 0x4344 +#define MANTA 0x4345 +#define XR1NX 0x4346 +#define DATAMASK 0x4347 +#define COMPUMASK 0x4348 +#define F10A 0x434D +#define OC1A 0x434E +#define WISDOM2 0x4350 +#define INSIGHT2 0x4353 +#define REACTPROWHITE 0x4354 +#define ELEMENT2 0x4357 +#define VEO10 0x4358 +#define VEO20 0x4359 +#define VEO30 0x435A +#define ZEN 0x4441 +#define ZENAIR 0x4442 +#define ATMOSAI2 0x4443 +#define PROPLUS21 0x4444 +#define GEO20 0x4446 +#define VT4 0x4447 +#define OC1B 0x4449 +#define VOYAGER2G 0x444B +#define ATOM3 0x444C +#define DG03 0x444D +#define OCS 0x4450 +#define OC1C 0x4451 +#define VT41 0x4452 +#define EPICB 0x4453 +#define T3B 0x4455 +#define ATOM31 0x4456 +#define A300AI 0x4457 +#define WISDOM3 0x4458 +#define A300 0x445A +#define TX1 0x4542 +#define MUNDIAL2 0x4543 +#define AMPHOS 0x4545 +#define AMPHOSAIR 0x4546 +#define PROPLUS3 0x4548 +#define F11A 0x4549 +#define OCI 0x454B +#define A300CS 0x454C +#define TALIS 0x454E +#define MUNDIAL3 0x4550 +#define PROPLUSX 0x4552 +#define F10B 0x4553 +#define F11B 0x4554 +#define XPAIR 0x4555 +#define VISION 0x4556 +#define VTX 0x4557 +#define I300 0x4559 +#define I750TC 0x455A +#define I450T 0x4641 +#define I550 0x4642 +#define I200 0x4646 +#define SAGE 0x4647 +#define I300C 0x4648 +#define I200C 0x4649 +#define I100 0x464E +#define I770R 0x4651 +#define I550C 0x4652 +#define GEO40 0x4653 +#define VEO40 0x4654 +#define WISDOM4 0x4655 +#define PROPLUS4 0x4656 +#define AMPHOS2 0x4657 +#define AMPHOSAIR2 0x4658 +#define BEACON 0x4742 +#define I470TC 0x4743 +#define I200CV2 0x4749 +#define GEOAIR 0x474B + #define PAGESIZE 0x10 #define FPMAXSIZE 0x20 diff --git a/src/oceanic_veo250_parser.c b/src/oceanic_veo250_parser.c index 4e59ede..90d3383 100644 --- a/src/oceanic_veo250_parser.c +++ b/src/oceanic_veo250_parser.c @@ -31,12 +31,6 @@ #define ISINSTANCE(parser) dc_parser_isinstance((parser), &oceanic_veo250_parser_vtable) -#define REACTPRO 0x4247 -#define VEO200 0x424B -#define VEO250 0x424C -#define INSIGHT 0x425A -#define REACTPROWHITE 0x4354 - typedef struct oceanic_veo250_parser_t oceanic_veo250_parser_t; struct oceanic_veo250_parser_t { diff --git a/src/oceanic_vtpro.c b/src/oceanic_vtpro.c index 74b94ec..4cb85f1 100644 --- a/src/oceanic_vtpro.c +++ b/src/oceanic_vtpro.c @@ -40,8 +40,6 @@ #define NAK 0xA5 #define END 0x51 -#define AERIS500AI 0x4151 - typedef enum oceanic_vtpro_protocol_t { MOD, INTR, diff --git a/src/oceanic_vtpro_parser.c b/src/oceanic_vtpro_parser.c index 1c60e62..9650c49 100644 --- a/src/oceanic_vtpro_parser.c +++ b/src/oceanic_vtpro_parser.c @@ -31,8 +31,6 @@ #define ISINSTANCE(parser) dc_parser_isinstance((parser), &oceanic_vtpro_parser_vtable) -#define AERIS500AI 0x4151 - typedef struct oceanic_vtpro_parser_t oceanic_vtpro_parser_t; struct oceanic_vtpro_parser_t { From d0857c49ec80c8e7074e6a44cfdf1b14f96c52b2 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 30 Dec 2022 16:09:21 +0100 Subject: [PATCH 42/58] Add the model number to the version table With the model number in the version table, the version string can be mapped to the corresponding model number. This allows to implement some model specific behaviour already before being able to read the model number. In most cases, there is a simple one to one relationship between the version string and the model number, but there are also a few exceptions: * For the Sherwood Wisdom 2 and 3, and the Beuchat Mundial 2 and 3, each variant has a different model number, but the first part of the version string is identical. The difference is in the firmware version part. Handling this correctly requires two entries in the table. * For the Oceanic OC1 there are 3 different model numbers, and only 2 different version strings. That means there is no correct mapping possible. --- src/oceanic_atom2.c | 158 ++++++++++++++++++++++--------------------- src/oceanic_common.c | 5 +- src/oceanic_common.h | 4 +- src/oceanic_veo250.c | 24 ++++--- src/oceanic_vtpro.c | 23 ++++--- 5 files changed, 116 insertions(+), 98 deletions(-) diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index 9f5cc17..de52e96 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -402,101 +402,103 @@ static const oceanic_common_layout_t aqualung_i450t_layout = { }; static const oceanic_common_version_t versions[] = { - {"OCEVEO10 \0\0 8K", 0, &oceanic_veo1_layout}, - {"AERIS XR1 NX R\0\0", 0, &oceanic_veo1_layout}, + {"OCEVEO10 \0\0 8K", 0, VEO10, &oceanic_veo1_layout}, + {"AERIS XR1 NX R\0\0", 0, XR1NX, &oceanic_veo1_layout}, - {"ATOM rev\0\0 256K", 0, &oceanic_atom1_layout}, + {"ATOM rev\0\0 256K", 0, ATOM1, &oceanic_atom1_layout}, - {"MANTA R\0\0 512K", 0x3242, &oceanic_atom2a_layout}, - {"MANTA R\0\0 512K", 0, &oceanic_atom2c_layout}, - {"2M ATOM r\0\0 512K", 0x3349, &oceanic_atom2a_layout}, - {"2M ATOM r\0\0 512K", 0, &oceanic_atom2c_layout}, + {"MANTA R\0\0 512K", 0x3242, MANTA, &oceanic_atom2a_layout}, + {"MANTA R\0\0 512K", 0, MANTA, &oceanic_atom2c_layout}, + {"2M ATOM r\0\0 512K", 0x3349, ATOM2, &oceanic_atom2a_layout}, + {"2M ATOM r\0\0 512K", 0, ATOM2, &oceanic_atom2c_layout}, - {"INSIGHT2 \0\0 512K", 0, &oceanic_atom2a_layout}, - {"OCEVEO30 \0\0 512K", 0, &oceanic_atom2a_layout}, - {"ATMOSAI R\0\0 512K", 0, &oceanic_atom2a_layout}, - {"PROPLUS2 \0\0 512K", 0, &oceanic_atom2a_layout}, - {"OCEGEO20 \0\0 512K", 0, &oceanic_atom2a_layout}, - {"OCE GEO R\0\0 512K", 0, &oceanic_atom2a_layout}, - {"AQUAI200 \0\0 512K", 0, &oceanic_atom2a_layout}, - {"AQUA200C \0\0 512K", 0, &oceanic_atom2a_layout}, + {"INSIGHT2 \0\0 512K", 0, INSIGHT2, &oceanic_atom2a_layout}, + {"OCEVEO30 \0\0 512K", 0, VEO30, &oceanic_atom2a_layout}, + {"ATMOSAI R\0\0 512K", 0, ATMOSAI2, &oceanic_atom2a_layout}, + {"PROPLUS2 \0\0 512K", 0, PROPLUS21, &oceanic_atom2a_layout}, + {"OCEGEO20 \0\0 512K", 0, GEO20, &oceanic_atom2a_layout}, + {"OCE GEO R\0\0 512K", 0, GEO, &oceanic_atom2a_layout}, + {"AQUAI200 \0\0 512K", 0, I200, &oceanic_atom2a_layout}, + {"AQUA200C \0\0 512K", 0, I200C, &oceanic_atom2a_layout}, - {"ELEMENT2 \0\0 512K", 0, &oceanic_atom2b_layout}, - {"OCEVEO20 \0\0 512K", 0, &oceanic_atom2b_layout}, - {"TUSAZEN \0\0 512K", 0, &oceanic_atom2b_layout}, - {"AQUAI300 \0\0 512K", 0, &oceanic_atom2b_layout}, - {"HOLLDG03 \0\0 512K", 0, &oceanic_atom2b_layout}, - {"AQUAI100 \0\0 512K", 0, &oceanic_atom2b_layout}, - {"AQUA300C \0\0 512K", 0, &oceanic_atom2b_layout}, - {"OCEGEO40 \0\0 512K", 0, &oceanic_atom2b_layout}, - {"VEOSMART \0\0 512K", 0, &oceanic_atom2b_layout}, + {"ELEMENT2 \0\0 512K", 0, ELEMENT2, &oceanic_atom2b_layout}, + {"OCEVEO20 \0\0 512K", 0, VEO20, &oceanic_atom2b_layout}, + {"TUSAZEN \0\0 512K", 0, ZEN, &oceanic_atom2b_layout}, + {"AQUAI300 \0\0 512K", 0, I300, &oceanic_atom2b_layout}, + {"HOLLDG03 \0\0 512K", 0, DG03, &oceanic_atom2b_layout}, + {"AQUAI100 \0\0 512K", 0, I100, &oceanic_atom2b_layout}, + {"AQUA300C \0\0 512K", 0, I300C, &oceanic_atom2b_layout}, + {"OCEGEO40 \0\0 512K", 0, GEO40, &oceanic_atom2b_layout}, + {"VEOSMART \0\0 512K", 0, VEO40, &oceanic_atom2b_layout}, - {"2M EPIC r\0\0 512K", 0, &oceanic_atom2c_layout}, - {"EPIC1 R\0\0 512K", 0, &oceanic_atom2c_layout}, - {"AERIA300 \0\0 512K", 0, &oceanic_atom2c_layout}, + {"2M EPIC r\0\0 512K", 0, EPICA, &oceanic_atom2c_layout}, + {"EPIC1 R\0\0 512K", 0, EPICB, &oceanic_atom2c_layout}, + {"AERIA300 \0\0 512K", 0, A300, &oceanic_atom2c_layout}, - {"OCE VT3 R\0\0 512K", 0, &oceanic_default_layout}, - {"ELITET3 R\0\0 512K", 0, &oceanic_default_layout}, - {"ELITET31 \0\0 512K", 0, &oceanic_default_layout}, - {"DATAMASK \0\0 512K", 0, &oceanic_default_layout}, - {"COMPMASK \0\0 512K", 0, &oceanic_default_layout}, + {"OCE VT3 R\0\0 512K", 0, VT3, &oceanic_default_layout}, + {"ELITET3 R\0\0 512K", 0, T3A, &oceanic_default_layout}, + {"ELITET31 \0\0 512K", 0, T3B, &oceanic_default_layout}, + {"DATAMASK \0\0 512K", 0, DATAMASK, &oceanic_default_layout}, + {"COMPMASK \0\0 512K", 0, COMPUMASK, &oceanic_default_layout}, - {"WISDOM R\0\0 512K", 0, &sherwood_wisdom_layout}, + {"WISDOM R\0\0 512K", 0x3342, WISDOM3, &sherwood_wisdom_layout}, + {"WISDOM R\0\0 512K", 0, WISDOM2, &sherwood_wisdom_layout}, - {"PROPLUS3 \0\0 512K", 0, &oceanic_proplus3_layout}, - {"PROPLUS4 \0\0 512K", 0, &oceanic_proplus3_layout}, + {"PROPLUS3 \0\0 512K", 0, PROPLUS3, &oceanic_proplus3_layout}, + {"PROPLUS4 \0\0 512K", 0, PROPLUS4, &oceanic_proplus3_layout}, - {"TUZENAIR \0\0 512K", 0, &tusa_zenair_layout}, - {"AMPHOSSW \0\0 512K", 0, &tusa_zenair_layout}, - {"AMPHOAIR \0\0 512K", 0, &tusa_zenair_layout}, - {"VOYAGE2G \0\0 512K", 0, &tusa_zenair_layout}, - {"TUSTALIS \0\0 512K", 0, &tusa_zenair_layout}, - {"AMPHOS20 \0\0 512K", 0, &tusa_zenair_layout}, - {"AMPAIR20 \0\0 512K", 0, &tusa_zenair_layout}, + {"TUZENAIR \0\0 512K", 0, ZENAIR, &tusa_zenair_layout}, + {"AMPHOSSW \0\0 512K", 0, AMPHOS, &tusa_zenair_layout}, + {"AMPHOAIR \0\0 512K", 0, AMPHOSAIR, &tusa_zenair_layout}, + {"VOYAGE2G \0\0 512K", 0, VOYAGER2G, &tusa_zenair_layout}, + {"TUSTALIS \0\0 512K", 0, TALIS, &tusa_zenair_layout}, + {"AMPHOS20 \0\0 512K", 0, AMPHOS2, &tusa_zenair_layout}, + {"AMPAIR20 \0\0 512K", 0, AMPHOSAIR2, &tusa_zenair_layout}, - {"REACPRO2 \0\0 512K", 0, &oceanic_reactpro_layout}, + {"REACPRO2 \0\0 512K", 0, REACTPROWHITE, &oceanic_reactpro_layout}, - {"FREEWAER \0\0 512K", 0, &aeris_f10_layout}, - {"OCEANF10 \0\0 512K", 0, &aeris_f10_layout}, - {"MUNDIAL R\0\0 512K", 0, &aeris_f10_layout}, + {"FREEWAER \0\0 512K", 0, F10A, &aeris_f10_layout}, + {"OCEANF10 \0\0 512K", 0, F10B, &aeris_f10_layout}, + {"MUNDIAL R\0\0 512K", 0x3300, MUNDIAL3, &aeris_f10_layout}, + {"MUNDIAL R\0\0 512K", 0, MUNDIAL2, &aeris_f10_layout}, - {"AERISF11 \0\0 1024", 0, &aeris_f11_layout}, - {"OCEANF11 \0\0 1024", 0, &aeris_f11_layout}, + {"AERISF11 \0\0 1024", 0, F11A, &aeris_f11_layout}, + {"OCEANF11 \0\0 1024", 0, F11B, &aeris_f11_layout}, - {"OCWATCH R\0\0 1024", 0, &oceanic_oc1_layout}, - {"OC1WATCH \0\0 1024", 0, &oceanic_oc1_layout}, - {"OCSWATCH \0\0 1024", 0, &oceanic_oc1_layout}, - {"AQUAI550 \0\0 1024", 0, &oceanic_oc1_layout}, - {"AQUA550C \0\0 1024", 0, &oceanic_oc1_layout}, - {"WISDOM04 \0\0 1024", 0, &oceanic_oc1_layout}, - {"AQUA470C \0\0 1024", 0, &oceanic_oc1_layout}, - {"AQUA200C \0\0 1024", 0, &oceanic_oc1_layout}, - {"GEOAIR \0\0 1024", 0, &oceanic_oc1_layout}, + {"OCWATCH R\0\0 1024", 0, OC1A, &oceanic_oc1_layout}, + {"OC1WATCH \0\0 1024", 0, OC1B, &oceanic_oc1_layout}, + {"OCSWATCH \0\0 1024", 0, OCS, &oceanic_oc1_layout}, + {"AQUAI550 \0\0 1024", 0, I550, &oceanic_oc1_layout}, + {"AQUA550C \0\0 1024", 0, I550C, &oceanic_oc1_layout}, + {"WISDOM04 \0\0 1024", 0, WISDOM4, &oceanic_oc1_layout}, + {"AQUA470C \0\0 1024", 0, I470TC, &oceanic_oc1_layout}, + {"AQUA200C \0\0 1024", 0, I200CV2, &oceanic_oc1_layout}, + {"GEOAIR \0\0 1024", 0, GEOAIR, &oceanic_oc1_layout}, - {"OCEANOCI \0\0 1024", 0, &oceanic_oci_layout}, + {"OCEANOCI \0\0 1024", 0, OCI, &oceanic_oci_layout}, - {"OCEATOM3 \0\0 1024", 0, &oceanic_atom3_layout}, - {"ATOM31 \0\0 1024", 0, &oceanic_atom3_layout}, + {"OCEATOM3 \0\0 1024", 0, ATOM3, &oceanic_atom3_layout}, + {"ATOM31 \0\0 1024", 0, ATOM31, &oceanic_atom3_layout}, - {"OCEANVT4 \0\0 1024", 0, &oceanic_vt4_layout}, - {"OCEAVT41 \0\0 1024", 0, &oceanic_vt4_layout}, - {"AERISAIR \0\0 1024", 0, &oceanic_vt4_layout}, - {"SWVISION \0\0 1024", 0, &oceanic_vt4_layout}, - {"XPSUBAIR \0\0 1024", 0, &oceanic_vt4_layout}, + {"OCEANVT4 \0\0 1024", 0, VT4, &oceanic_vt4_layout}, + {"OCEAVT41 \0\0 1024", 0, VT41, &oceanic_vt4_layout}, + {"AERISAIR \0\0 1024", 0, A300AI, &oceanic_vt4_layout}, + {"SWVISION \0\0 1024", 0, VISION, &oceanic_vt4_layout}, + {"XPSUBAIR \0\0 1024", 0, XPAIR, &oceanic_vt4_layout}, - {"HOLLDG04 \0\0 2048", 0, &hollis_tx1_layout}, + {"HOLLDG04 \0\0 2048", 0, TX1, &hollis_tx1_layout}, - {"AER300CS \0\0 2048", 0, &aeris_a300cs_layout}, - {"OCEANVTX \0\0 2048", 0, &aeris_a300cs_layout}, - {"AQUAI750 \0\0 2048", 0, &aeris_a300cs_layout}, - {"SWDRAGON \0\0 2048", 0, &aeris_a300cs_layout}, - {"SWBEACON \0\0 2048", 0, &aeris_a300cs_layout}, + {"AER300CS \0\0 2048", 0, A300CS, &aeris_a300cs_layout}, + {"OCEANVTX \0\0 2048", 0, VTX, &aeris_a300cs_layout}, + {"AQUAI750 \0\0 2048", 0, I750TC, &aeris_a300cs_layout}, + {"SWDRAGON \0\0 2048", 0, SAGE, &aeris_a300cs_layout}, + {"SWBEACON \0\0 2048", 0, BEACON, &aeris_a300cs_layout}, - {"AQUAI450 \0\0 2048", 0, &aqualung_i450t_layout}, + {"AQUAI450 \0\0 2048", 0, I450T, &aqualung_i450t_layout}, - {"OCEANOCX \0\0 \0\0\0\0", 0, &oceanic_proplusx_layout}, + {"OCEANOCX \0\0 \0\0\0\0", 0, PROPLUSX, &oceanic_proplusx_layout}, - {"AQUA770R \0\0 \0\0\0\0", 0, &aqualung_i770r_layout}, + {"AQUA770R \0\0 \0\0\0\0", 0, I770R, &aqualung_i770r_layout}, }; /* @@ -925,8 +927,8 @@ oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream } // Detect the memory layout. - device->base.layout = OCEANIC_COMMON_MATCH(device->base.version, versions, &device->base.firmware); - if (device->base.layout == NULL) { + const oceanic_common_version_t *version = OCEANIC_COMMON_MATCH(device->base.version, versions, &device->base.firmware); + if (version == NULL) { WARNING (context, "Unsupported device detected!"); if (memcmp(device->base.version + 12, "256K", 4) == 0) { device->base.layout = &oceanic_atom1_layout; @@ -939,6 +941,10 @@ oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream } else { device->base.layout = &oceanic_default_layout; } + device->base.model = 0; + } else { + device->base.layout = version->layout; + device->base.model = version->model; } // Set the big page support. diff --git a/src/oceanic_common.c b/src/oceanic_common.c index f876d28..62f5f74 100644 --- a/src/oceanic_common.c +++ b/src/oceanic_common.c @@ -132,7 +132,7 @@ oceanic_common_match_pattern (const unsigned char *string, const unsigned char * return 1; } -const oceanic_common_layout_t * +const oceanic_common_version_t * oceanic_common_match (const unsigned char *version, const oceanic_common_version_t patterns[], size_t n, unsigned int *firmware) { for (size_t i = 0; i < n; ++i) { @@ -143,7 +143,7 @@ oceanic_common_match (const unsigned char *version, const oceanic_common_version if (firmware) { *firmware = fw; } - return patterns[i].layout; + return patterns + i; } } @@ -160,6 +160,7 @@ oceanic_common_device_init (oceanic_common_device_t *device) device->firmware = 0; memset (device->version, 0, sizeof (device->version)); memset (device->fingerprint, 0, sizeof (device->fingerprint)); + device->model = 0; device->layout = NULL; device->multipage = 1; } diff --git a/src/oceanic_common.h b/src/oceanic_common.h index 16fd976..045dca4 100644 --- a/src/oceanic_common.h +++ b/src/oceanic_common.h @@ -161,6 +161,7 @@ typedef struct oceanic_common_device_t { unsigned int firmware; unsigned char version[PAGESIZE]; unsigned char fingerprint[FPMAXSIZE]; + unsigned int model; const oceanic_common_layout_t *layout; unsigned int multipage; } oceanic_common_device_t; @@ -174,10 +175,11 @@ typedef struct oceanic_common_device_vtable_t { typedef struct oceanic_common_version_t { unsigned char pattern[PAGESIZE + 1]; unsigned int firmware; + unsigned int model; const oceanic_common_layout_t *layout; } oceanic_common_version_t; -const oceanic_common_layout_t * +const oceanic_common_version_t * oceanic_common_match (const unsigned char *version, const oceanic_common_version_t patterns[], size_t n, unsigned int *firmware); void diff --git a/src/oceanic_veo250.c b/src/oceanic_veo250.c index db323db..58a9ceb 100644 --- a/src/oceanic_veo250.c +++ b/src/oceanic_veo250.c @@ -78,14 +78,14 @@ static const oceanic_common_layout_t oceanic_veo250_layout = { }; static const oceanic_common_version_t versions[] = { - {"GENREACT \0\0 256K", 0, &oceanic_veo250_layout}, - {"VEO 200 R\0\0 256K", 0, &oceanic_veo250_layout}, - {"VEO 250 R\0\0 256K", 0, &oceanic_veo250_layout}, - {"SEEMANN R\0\0 256K", 0, &oceanic_veo250_layout}, - {"VEO 180 R\0\0 256K", 0, &oceanic_veo250_layout}, - {"AERISXR2 \0\0 256K", 0, &oceanic_veo250_layout}, - {"INSIGHT R\0\0 256K", 0, &oceanic_veo250_layout}, - {"HO DGO2 R\0\0 256K", 0, &oceanic_veo250_layout}, + {"GENREACT \0\0 256K", 0, REACTPRO, &oceanic_veo250_layout}, + {"VEO 200 R\0\0 256K", 0, VEO200, &oceanic_veo250_layout}, + {"VEO 250 R\0\0 256K", 0, VEO250, &oceanic_veo250_layout}, + {"SEEMANN R\0\0 256K", 0, XP5, &oceanic_veo250_layout}, + {"VEO 180 R\0\0 256K", 0, VEO180, &oceanic_veo250_layout}, + {"AERISXR2 \0\0 256K", 0, XR2, &oceanic_veo250_layout}, + {"INSIGHT R\0\0 256K", 0, INSIGHT, &oceanic_veo250_layout}, + {"HO DGO2 R\0\0 256K", 0, DG02, &oceanic_veo250_layout}, }; static dc_status_t @@ -316,10 +316,14 @@ oceanic_veo250_device_open (dc_device_t **out, dc_context_t *context, dc_iostrea } // Detect the memory layout. - device->base.layout = OCEANIC_COMMON_MATCH(device->base.version, versions, &device->base.firmware); - if (device->base.layout == NULL) { + const oceanic_common_version_t *version = OCEANIC_COMMON_MATCH(device->base.version, versions, &device->base.firmware); + if (version == NULL) { WARNING (context, "Unsupported device detected!"); device->base.layout = &oceanic_veo250_layout; + device->base.model = 0; + } else { + device->base.layout = version->layout; + device->base.model = version->model; } *out = (dc_device_t*) device; diff --git a/src/oceanic_vtpro.c b/src/oceanic_vtpro.c index 4cb85f1..5e11115 100644 --- a/src/oceanic_vtpro.c +++ b/src/oceanic_vtpro.c @@ -118,14 +118,14 @@ static const oceanic_common_layout_t aeris_500ai_layout = { }; static const oceanic_common_version_t versions[] = { - {"VERSAPRO \0\0 256K", 0, &oceanic_vtpro_layout}, - {"ATMOSTWO \0\0 256K", 0, &oceanic_vtpro_layout}, - {"PROPLUS2 \0\0 256K", 0, &oceanic_vtpro_layout}, - {"ATMOSAIR \0\0 256K", 0, &oceanic_vtpro_layout}, - {"VTPRO r\0\0 256K", 0, &oceanic_vtpro_layout}, - {"ELITE r\0\0 256K", 0, &oceanic_vtpro_layout}, + {"VERSAPRO \0\0 256K", 0, VERSAPRO, &oceanic_vtpro_layout}, + {"ATMOSTWO \0\0 256K", 0, ATMOS2, &oceanic_vtpro_layout}, + {"PROPLUS2 \0\0 256K", 0, PROPLUS2, &oceanic_vtpro_layout}, + {"ATMOSAIR \0\0 256K", 0, ATMOSAI, &oceanic_vtpro_layout}, + {"VTPRO r\0\0 256K", 0, VTPRO, &oceanic_vtpro_layout}, + {"ELITE r\0\0 256K", 0, ELITE, &oceanic_vtpro_layout}, - {"WISDOM r\0\0 256K", 0, &oceanic_wisdom_layout}, + {"WISDOM r\0\0 256K", 0, WISDOM, &oceanic_wisdom_layout}, }; static dc_status_t @@ -490,11 +490,16 @@ oceanic_vtpro_device_open (dc_device_t **out, dc_context_t *context, dc_iostream // Detect the memory layout. if (model == AERIS500AI) { device->base.layout = &aeris_500ai_layout; + device->base.model = AERIS500AI; } else { - device->base.layout = OCEANIC_COMMON_MATCH(device->base.version, versions, &device->base.firmware); - if (device->base.layout == NULL) { + const oceanic_common_version_t * version = OCEANIC_COMMON_MATCH(device->base.version, versions, &device->base.firmware); + if (version == NULL) { WARNING (context, "Unsupported device detected!"); device->base.layout = &oceanic_vtpro_layout; + device->base.model = 0; + } else { + device->base.layout = version->layout; + device->base.model = version->model; } } From 3414f72f605efd64061626e60c8540a92135858a Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 30 Dec 2022 22:26:39 +0100 Subject: [PATCH 43/58] Remove the model number from the vtpro struct The model number is now also available in the common struct. There is no need to store it twice. The auto-detected model number from the version table is also more reliable than the one passed by the caller. --- src/oceanic_vtpro.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/oceanic_vtpro.c b/src/oceanic_vtpro.c index 5e11115..ef3d6d1 100644 --- a/src/oceanic_vtpro.c +++ b/src/oceanic_vtpro.c @@ -48,7 +48,6 @@ typedef enum oceanic_vtpro_protocol_t { typedef struct oceanic_vtpro_device_t { oceanic_common_device_t base; dc_iostream_t *iostream; - unsigned int model; oceanic_vtpro_protocol_t protocol; } oceanic_vtpro_device_t; @@ -379,7 +378,7 @@ oceanic_vtpro_device_logbook (dc_device_t *abstract, dc_event_progress_t *progre { oceanic_vtpro_device_t *device = (oceanic_vtpro_device_t *) abstract; - if (device->model == AERIS500AI) { + if (device->base.model == AERIS500AI) { return oceanic_aeris500ai_device_logbook (abstract, progress, logbook); } else { return oceanic_common_device_logbook (abstract, progress, logbook); @@ -410,7 +409,6 @@ oceanic_vtpro_device_open (dc_device_t **out, dc_context_t *context, dc_iostream // Set the default values. device->iostream = iostream; - device->model = model; if (model == AERIS500AI) { device->protocol = INTR; } else { From ceae89e149c5685a39fa9ca56700e8a856698512 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 29 Dec 2022 16:34:53 +0100 Subject: [PATCH 44/58] Enable big page support The Oceanic Pro Plus 4 appears to support the big page B4 and B8 read commands, but with some strange twists: * When sending the B8 read command, a 256 byte packet is received. The checksums of the packet are valid, but the upper half of the payload data is always filled with zero bytes. That means we can't use this command. * The B4 read command appears to use a 2 byte checksum instead of the normal 1 byte checksum. That means we can use this command with a small model specific tweak. --- src/oceanic_atom2.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index de52e96..ee60215 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -948,7 +948,8 @@ oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream } // Set the big page support. - if (device->base.layout == &aeris_f11_layout) { + if (device->base.layout == &aeris_f11_layout || + device->base.layout == &oceanic_proplus3_layout) { device->bigpage = 8; } else if (device->base.layout == &oceanic_proplusx_layout || device->base.layout == &aqualung_i770r_layout || @@ -1042,7 +1043,7 @@ oceanic_atom2_device_read (dc_device_t *abstract, unsigned int address, unsigned break; case 8: read_cmd = CMD_READ8; - crc_size = 1; + crc_size = device->base.model == PROPLUS4 ? 2 : 1; break; case 16: read_cmd = CMD_READ16; From e0e3bc89946c5f4633f4bfef79b534723cd446e2 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 29 Dec 2022 17:31:01 +0100 Subject: [PATCH 45/58] Repeat the handshake every few packets The Oceanic Pro Plus 4 appears to "disconnect" somehow after about 30 seconds. The BLE connection remains up, but the dive computer simply stops responding to commands. The download fails with a timeout error, and the end-user can only download a few dives at most. The Android DiverLog+ application appears to keep the connection alive by re-sending the version and handshake commands once in a while. Copy this behaviour by repeating those two commands every 50 read requests. During testing, that's approximately every 25 seconds. Note that both commands are required, sending only one of them does not fix the problem. --- src/oceanic_atom2.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index ee60215..cc1e3c3 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -54,9 +54,13 @@ #define ACK 0x5A #define NAK 0xA5 +#define REPEAT 50 + typedef struct oceanic_atom2_device_t { oceanic_common_device_t base; dc_iostream_t *iostream; + unsigned int handshake_repeat; + unsigned int handshake_counter; unsigned int sequence; unsigned int delay; unsigned int extra; @@ -957,6 +961,11 @@ oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream device->bigpage = 16; } + // Repeat the handshaking every few packets. + device->handshake_repeat = dc_iostream_get_transport (device->iostream) == DC_TRANSPORT_BLE && + device->base.model == PROPLUS4; + device->handshake_counter = 0; + *out = (dc_device_t*) device; return DC_STATUS_SUCCESS; @@ -1074,6 +1083,12 @@ oceanic_atom2_device_read (dc_device_t *abstract, unsigned int address, unsigned unsigned int page = (address - highmem) / pagesize; if (page != device->cached_page || highmem != device->cached_highmem) { + if (device->handshake_repeat && ++device->handshake_counter % REPEAT == 0) { + unsigned char version[PAGESIZE] = {0}; + oceanic_atom2_device_version (abstract, version, sizeof (version)); + oceanic_atom2_ble_handshake (device); + } + // Read the package. unsigned int number = highmem ? page : page * device->bigpage; // This is always PAGESIZE, even in big page mode. unsigned char command[] = {read_cmd, From 187f8d625b97d130fbb357a84ad78bb0abd802da Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 3 Jan 2023 21:08:21 +0100 Subject: [PATCH 46/58] Move the snprintf functions to the platform module Moving the implementation of the snprintf wrapper functions to the platform module allows to re-use the same implementation throughout the entire codebase. --- src/bluetooth.c | 3 +-- src/context-private.h | 10 +++----- src/context.c | 55 +++---------------------------------------- src/hw_ostc3.c | 4 ++-- src/irda.c | 3 +-- src/platform.c | 47 ++++++++++++++++++++++++++++++++++++ src/platform.h | 16 ++++++++++++- src/serial_posix.c | 3 +-- src/suunto_eonsteel.c | 2 +- 9 files changed, 74 insertions(+), 69 deletions(-) diff --git a/src/bluetooth.c b/src/bluetooth.c index 1889bf7..aa5f05a 100644 --- a/src/bluetooth.c +++ b/src/bluetooth.c @@ -24,7 +24,6 @@ #endif #include // malloc, free -#include #include "socket.h" @@ -231,7 +230,7 @@ dc_bluetooth_addr2str(dc_bluetooth_address_t address, char *str, size_t size) if (str == NULL || size < DC_BLUETOOTH_SIZE) return NULL; - int n = snprintf(str, size, "%02X:%02X:%02X:%02X:%02X:%02X", + int n = dc_platform_snprintf(str, size, "%02X:%02X:%02X:%02X:%02X:%02X", (unsigned char)((address >> 40) & 0xFF), (unsigned char)((address >> 32) & 0xFF), (unsigned char)((address >> 24) & 0xFF), diff --git a/src/context-private.h b/src/context-private.h index 7f50a68..fed80e5 100644 --- a/src/context-private.h +++ b/src/context-private.h @@ -28,6 +28,8 @@ #include +#include "platform.h" + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ @@ -40,12 +42,6 @@ extern "C" { #define FUNCTION __FUNCTION__ #endif -#if defined(__GNUC__) -#define ATTR_FORMAT_PRINTF(a,b) __attribute__((format(printf, a, b))) -#else -#define ATTR_FORMAT_PRINTF(a,b) -#endif - #ifdef ENABLE_LOGGING #define HEXDUMP(context, loglevel, prefix, data, size) dc_context_hexdump (context, loglevel, __FILE__, __LINE__, FUNCTION, prefix, data, size) #define SYSERROR(context, errcode) dc_context_syserror (context, DC_LOGLEVEL_ERROR, __FILE__, __LINE__, FUNCTION, errcode) @@ -63,7 +59,7 @@ extern "C" { #endif dc_status_t -dc_context_log (dc_context_t *context, dc_loglevel_t loglevel, const char *file, unsigned int line, const char *function, const char *format, ...) ATTR_FORMAT_PRINTF(6, 7); +dc_context_log (dc_context_t *context, dc_loglevel_t loglevel, const char *file, unsigned int line, const char *function, const char *format, ...) DC_ATTR_FORMAT_PRINTF(6, 7); dc_status_t dc_context_syserror (dc_context_t *context, dc_loglevel_t loglevel, const char *file, unsigned int line, const char *function, int errcode); diff --git a/src/context.c b/src/context.c index 21c1610..8e32f78 100644 --- a/src/context.c +++ b/src/context.c @@ -25,7 +25,6 @@ #include #include -#include #include #include @@ -36,6 +35,7 @@ #endif #include "context-private.h" +#include "platform.h" #include "timer.h" struct dc_context_t { @@ -49,55 +49,6 @@ struct dc_context_t { }; #ifdef ENABLE_LOGGING -/* - * A wrapper for the vsnprintf function, which will always null terminate the - * string and returns a negative value if the destination buffer is too small. - */ -static int -l_vsnprintf (char *str, size_t size, const char *format, va_list ap) -{ - int n; - - if (size == 0) - return -1; - -#ifdef _MSC_VER - /* - * The non-standard vsnprintf implementation provided by MSVC doesn't null - * terminate the string and returns a negative value if the destination - * buffer is too small. - */ - n = _vsnprintf (str, size - 1, format, ap); - if (n == size - 1 || n < 0) - str[size - 1] = 0; -#else - /* - * The C99 vsnprintf function will always null terminate the string. If the - * destination buffer is too small, the return value is the number of - * characters that would have been written if the buffer had been large - * enough. - */ - n = vsnprintf (str, size, format, ap); - if (n >= 0 && (size_t) n >= size) - n = -1; -#endif - - return n; -} - -static int -l_snprintf (char *str, size_t size, const char *format, ...) -{ - va_list ap; - int n; - - va_start (ap, format); - n = l_vsnprintf (str, size, format, ap); - va_end (ap); - - return n; -} - static int l_hexdump (char *str, size_t size, const unsigned char data[], size_t n) { @@ -244,7 +195,7 @@ dc_context_log (dc_context_t *context, dc_loglevel_t loglevel, const char *file, return DC_STATUS_SUCCESS; va_start (ap, format); - l_vsnprintf (context->msg, sizeof (context->msg), format, ap); + dc_platform_vsnprintf (context->msg, sizeof (context->msg), format, ap); va_end (ap); context->logfunc (context, loglevel, file, line, function, context->msg, context->userdata); @@ -310,7 +261,7 @@ dc_context_hexdump (dc_context_t *context, dc_loglevel_t loglevel, const char *f if (context->logfunc == NULL) return DC_STATUS_SUCCESS; - n = l_snprintf (context->msg, sizeof (context->msg), "%s: size=%u, data=", prefix, size); + n = dc_platform_snprintf (context->msg, sizeof (context->msg), "%s: size=%u, data=", prefix, size); if (n >= 0) { n = l_hexdump (context->msg + n, sizeof (context->msg) - n, data, size); diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 550bf7c..629e482 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -1447,7 +1447,7 @@ hw_ostc3_device_fwupdate3 (dc_device_t *abstract, const char *filename) for (unsigned int len = 0; len < SZ_FIRMWARE; len += SZ_FIRMWARE_BLOCK) { char status[SZ_DISPLAY + 1]; // Status message on the display - snprintf (status, sizeof(status), " Uploading %2d%%", (100 * len) / SZ_FIRMWARE); + dc_platform_snprintf (status, sizeof(status), " Uploading %2d%%", (100 * len) / SZ_FIRMWARE); hw_ostc3_device_display (abstract, status); rc = hw_ostc3_firmware_block_write (device, FIRMWARE_AREA + len, firmware->data + len, SZ_FIRMWARE_BLOCK); @@ -1466,7 +1466,7 @@ hw_ostc3_device_fwupdate3 (dc_device_t *abstract, const char *filename) for (unsigned int len = 0; len < SZ_FIRMWARE; len += SZ_FIRMWARE_BLOCK) { unsigned char block[SZ_FIRMWARE_BLOCK]; char status[SZ_DISPLAY + 1]; // Status message on the display - snprintf (status, sizeof(status), " Verifying %2d%%", (100 * len) / SZ_FIRMWARE); + dc_platform_snprintf (status, sizeof(status), " Verifying %2d%%", (100 * len) / SZ_FIRMWARE); hw_ostc3_device_display (abstract, status); rc = hw_ostc3_firmware_block_read (device, FIRMWARE_AREA + len, block, sizeof (block)); diff --git a/src/irda.c b/src/irda.c index 8c5d612..a5bb8e4 100644 --- a/src/irda.c +++ b/src/irda.c @@ -24,7 +24,6 @@ #endif #include // malloc, free -#include // snprintf #include #include "socket.h" @@ -314,7 +313,7 @@ dc_irda_open (dc_iostream_t **out, dc_context_t *context, unsigned int address, peer.irdaDeviceID[1] = (address >> 8) & 0xFF; peer.irdaDeviceID[2] = (address >> 16) & 0xFF; peer.irdaDeviceID[3] = (address >> 24) & 0xFF; - snprintf (peer.irdaServiceName, sizeof(peer.irdaServiceName), "LSAP-SEL%u", lsap); + dc_platform_snprintf (peer.irdaServiceName, sizeof(peer.irdaServiceName), "LSAP-SEL%u", lsap); #else struct sockaddr_irda peer; peer.sir_family = AF_IRDA; diff --git a/src/platform.c b/src/platform.c index 16a3c08..4164549 100644 --- a/src/platform.c +++ b/src/platform.c @@ -28,6 +28,8 @@ #include #endif +#include + #include "platform.h" int @@ -49,3 +51,48 @@ dc_platform_sleep (unsigned int milliseconds) return 0; } + +int +dc_platform_vsnprintf (char *str, size_t size, const char *format, va_list ap) +{ + int n = 0; + + if (size == 0) + return -1; + +#ifdef _MSC_VER + /* + * The non-standard vsnprintf implementation provided by MSVC doesn't null + * terminate the string and returns a negative value if the destination + * buffer is too small. + */ + n = _vsnprintf (str, size - 1, format, ap); + if (n == size - 1 || n < 0) + str[size - 1] = 0; +#else + /* + * The C99 vsnprintf function will always null terminate the string. If the + * destination buffer is too small, the return value is the number of + * characters that would have been written if the buffer had been large + * enough. + */ + n = vsnprintf (str, size, format, ap); + if (n >= 0 && (size_t) n >= size) + n = -1; +#endif + + return n; +} + +int +dc_platform_snprintf (char *str, size_t size, const char *format, ...) +{ + va_list ap; + int n = 0; + + va_start (ap, format); + n = dc_platform_vsnprintf (str, size, format, ap); + va_end (ap); + + return n; +} diff --git a/src/platform.h b/src/platform.h index 438aa7d..a652503 100644 --- a/src/platform.h +++ b/src/platform.h @@ -22,10 +22,18 @@ #ifndef DC_PLATFORM_H #define DC_PLATFORM_H +#include + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ +#if defined(__GNUC__) +#define DC_ATTR_FORMAT_PRINTF(a,b) __attribute__((format(printf, a, b))) +#else +#define DC_ATTR_FORMAT_PRINTF(a,b) +#endif + #ifdef _WIN32 #define DC_PRINTF_SIZE "%Iu" #else @@ -33,7 +41,6 @@ extern "C" { #endif #ifdef _MSC_VER -#define snprintf _snprintf #define strcasecmp _stricmp #define strncasecmp _strnicmp #if _MSC_VER < 1800 @@ -47,6 +54,13 @@ extern "C" { int dc_platform_sleep(unsigned int milliseconds); +/* + * A wrapper for the vsnprintf function, which will always null terminate the + * string and returns a negative value if the destination buffer is too small. + */ +int dc_platform_snprintf (char *str, size_t size, const char *format, ...) DC_ATTR_FORMAT_PRINTF(3, 4); +int dc_platform_vsnprintf (char *str, size_t size, const char *format, va_list ap) DC_ATTR_FORMAT_PRINTF(3, 0); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/serial_posix.c b/src/serial_posix.c index b23f69d..a189e15 100644 --- a/src/serial_posix.c +++ b/src/serial_posix.c @@ -37,7 +37,6 @@ #ifdef HAVE_IOKIT_SERIAL_IOSS_H #include #endif -#include #include #include #include @@ -226,7 +225,7 @@ dc_serial_iterator_next (dc_iterator_t *abstract, void *out) continue; char filename[sizeof(device->name)]; - int n = snprintf (filename, sizeof (filename), "%s/%s", DIRNAME, ep->d_name); + int n = dc_platform_snprintf (filename, sizeof (filename), "%s/%s", DIRNAME, ep->d_name); if (n < 0 || (size_t) n >= sizeof (filename)) { return DC_STATUS_NOMEMORY; } diff --git a/src/suunto_eonsteel.c b/src/suunto_eonsteel.c index 2d8122c..6ffc7bc 100644 --- a/src/suunto_eonsteel.c +++ b/src/suunto_eonsteel.c @@ -897,7 +897,7 @@ suunto_eonsteel_device_foreach(dc_device_t *abstract, dc_dive_callback_t callbac break; } - len = snprintf(pathname, sizeof(pathname), "%s/%s", dive_directory, de->name); + len = dc_platform_snprintf(pathname, sizeof(pathname), "%s/%s", dive_directory, de->name); if (len < 0 || (unsigned int) len >= sizeof(pathname)) { dc_status_set_error(&status, DC_STATUS_PROTOCOL); break; From 3ce34a0b6d93a160253f9d6d22c43ae792ca8089 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 5 Jan 2023 20:20:57 +0100 Subject: [PATCH 47/58] Add missing functions for accessing big/little endian values There are functions for reading 8, 16, 24 and 32-bit big and little endian values, but the corresponding functions for writing such values are not always available. The 64-bit variants are also missing. --- src/array.c | 121 +++++++++++++++++++++++++++++++++++++++++++--------- src/array.h | 36 +++++++++++++--- 2 files changed, 130 insertions(+), 27 deletions(-) diff --git a/src/array.c b/src/array.c index 6d9550e..ad7b4c3 100644 --- a/src/array.c +++ b/src/array.c @@ -208,6 +208,32 @@ array_uint_le (const unsigned char data[], unsigned int n) return value; } +unsigned long long +array_uint64_be (const unsigned char data[]) +{ + return ((unsigned long long) data[0] << 56) | + ((unsigned long long) data[1] << 48) | + ((unsigned long long) data[2] << 40) | + ((unsigned long long) data[3] << 32) | + ((unsigned long long) data[4] << 24) | + ((unsigned long long) data[5] << 16) | + ((unsigned long long) data[6] << 8) | + ((unsigned long long) data[7] << 0); +} + +unsigned long long +array_uint64_le (const unsigned char data[]) +{ + return ((unsigned long long) data[0] << 0) | + ((unsigned long long) data[1] << 8) | + ((unsigned long long) data[2] << 16) | + ((unsigned long long) data[3] << 24) | + ((unsigned long long) data[4] << 32) | + ((unsigned long long) data[5] << 40) | + ((unsigned long long) data[6] << 48) | + ((unsigned long long) data[7] << 56); +} + unsigned int array_uint32_be (const unsigned char data[]) { @@ -237,17 +263,6 @@ array_uint32_word_be (const unsigned char data[]) ((unsigned int) data[3] << 16); } - -void -array_uint32_le_set (unsigned char data[], const unsigned int input) -{ - data[0] = input & 0xFF; - data[1] = (input >> 8) & 0xFF; - data[2] = (input >> 16) & 0xFF; - data[3] = (input >> 24) & 0xFF; -} - - unsigned int array_uint24_be (const unsigned char data[]) { @@ -256,16 +271,6 @@ array_uint24_be (const unsigned char data[]) ((unsigned int) data[2] << 0); } - -void -array_uint24_be_set (unsigned char data[], const unsigned int input) -{ - data[0] = (input >> 16) & 0xFF; - data[1] = (input >> 8) & 0xFF; - data[2] = input & 0xFF; -} - - unsigned int array_uint24_le (const unsigned char data[]) { @@ -289,6 +294,80 @@ array_uint16_le (const unsigned char data[]) ((unsigned int) data[1] << 8); } +void +array_uint64_be_set (unsigned char data[], const unsigned long long input) +{ + data[0] = (input >> 56) & 0xFF; + data[1] = (input >> 48) & 0xFF; + data[2] = (input >> 40) & 0xFF; + data[3] = (input >> 32) & 0xFF; + data[4] = (input >> 24) & 0xFF; + data[5] = (input >> 16) & 0xFF; + data[6] = (input >> 8) & 0xFF; + data[7] = (input ) & 0xFF; +} + +void +array_uint64_le_set (unsigned char data[], const unsigned long long input) +{ + data[0] = (input ) & 0xFF; + data[1] = (input >> 8) & 0xFF; + data[2] = (input >> 16) & 0xFF; + data[3] = (input >> 24) & 0xFF; + data[4] = (input >> 32) & 0xFF; + data[5] = (input >> 40) & 0xFF; + data[6] = (input >> 48) & 0xFF; + data[7] = (input >> 56) & 0xFF; +} + +void +array_uint32_be_set (unsigned char data[], const unsigned int input) +{ + data[0] = (input >> 24) & 0xFF; + data[1] = (input >> 16) & 0xFF; + data[2] = (input >> 8) & 0xFF; + data[3] = (input ) & 0xFF; +} + +void +array_uint32_le_set (unsigned char data[], const unsigned int input) +{ + data[0] = (input ) & 0xFF; + data[1] = (input >> 8) & 0xFF; + data[2] = (input >> 16) & 0xFF; + data[3] = (input >> 24) & 0xFF; +} + +void +array_uint24_be_set (unsigned char data[], const unsigned int input) +{ + data[0] = (input >> 16) & 0xFF; + data[1] = (input >> 8) & 0xFF; + data[2] = (input ) & 0xFF; +} + +void +array_uint24_le_set (unsigned char data[], const unsigned int input) +{ + data[0] = (input ) & 0xFF; + data[1] = (input >> 8) & 0xFF; + data[2] = (input >> 16) & 0xFF; +} + +void +array_uint16_be_set (unsigned char data[], const unsigned short input) +{ + data[0] = (input >> 8) & 0xFF; + data[1] = (input ) & 0xFF; +} + +void +array_uint16_le_set (unsigned char data[], const unsigned short input) +{ + data[0] = (input ) & 0xFF; + data[1] = (input >> 8) & 0xFF; +} + unsigned char bcd2dec (unsigned char value) { diff --git a/src/array.h b/src/array.h index f0f54cd..5cdd1a1 100644 --- a/src/array.h +++ b/src/array.h @@ -66,6 +66,12 @@ array_uint_be (const unsigned char data[], unsigned int n); unsigned int array_uint_le (const unsigned char data[], unsigned int n); +unsigned long long +array_uint64_be (const unsigned char data[]); + +unsigned long long +array_uint64_le (const unsigned char data[]); + unsigned int array_uint32_be (const unsigned char data[]); @@ -75,15 +81,9 @@ array_uint32_le (const unsigned char data[]); unsigned int array_uint32_word_be (const unsigned char data[]); -void -array_uint32_le_set (unsigned char data[], const unsigned int input); - unsigned int array_uint24_be (const unsigned char data[]); -void -array_uint24_be_set (unsigned char data[], const unsigned int input); - unsigned int array_uint24_le (const unsigned char data[]); @@ -93,6 +93,30 @@ array_uint16_be (const unsigned char data[]); unsigned short array_uint16_le (const unsigned char data[]); +void +array_uint64_be_set (unsigned char data[], const unsigned long long input); + +void +array_uint64_le_set (unsigned char data[], const unsigned long long input); + +void +array_uint32_be_set (unsigned char data[], const unsigned int input); + +void +array_uint32_le_set (unsigned char data[], const unsigned int input); + +void +array_uint24_be_set (unsigned char data[], const unsigned int input); + +void +array_uint24_le_set (unsigned char data[], const unsigned int input); + +void +array_uint16_be_set (unsigned char data[], const unsigned short input); + +void +array_uint16_le_set (unsigned char data[], const unsigned short input); + unsigned char bcd2dec (unsigned char value); From 86e9cc34439860e91224f9b0fe760753932eb2cb Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 31 Aug 2022 16:23:03 +0200 Subject: [PATCH 48/58] Add support for the Deepblu Cosmiq+ The Deepblu Cosmiq+ uses a plaintext and line based communication protocol over BLE, where the binary payload data is encoded as hexadecimal characters. Based-on-code-by: Linus Torvalds --- examples/common.c | 1 + include/libdivecomputer/common.h | 2 + msvc/libdivecomputer.vcxproj | 3 + src/Makefile.am | 1 + src/array.c | 11 + src/array.h | 3 + src/deepblu_cosmiq.c | 548 +++++++++++++++++++++++++++++++ src/deepblu_cosmiq.h | 44 +++ src/deepblu_cosmiq_parser.c | 218 ++++++++++++ src/descriptor.c | 15 + src/device.c | 4 + src/parser.c | 4 + 12 files changed, 854 insertions(+) create mode 100644 src/deepblu_cosmiq.c create mode 100644 src/deepblu_cosmiq.h create mode 100644 src/deepblu_cosmiq_parser.c diff --git a/examples/common.c b/examples/common.c index 4b3a63f..fd8ed4c 100644 --- a/examples/common.c +++ b/examples/common.c @@ -96,6 +96,7 @@ static const backend_table_t g_backends[] = { {"sp2", DC_FAMILY_SPORASUB_SP2, 0}, {"excursion", DC_FAMILY_DEEPSIX_EXCURSION, 0}, {"screen", DC_FAMILY_SEAC_SCREEN, 0}, + {"cosmiq", DC_FAMILY_DEEPBLU_COSMIQ, 0}, }; static const transport_table_t g_transports[] = { diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h index 3553cf8..c2b1fcd 100644 --- a/include/libdivecomputer/common.h +++ b/include/libdivecomputer/common.h @@ -114,6 +114,8 @@ typedef enum dc_family_t { DC_FAMILY_DEEPSIX_EXCURSION = (19 << 16), /* Seac Screen */ DC_FAMILY_SEAC_SCREEN = (20 << 16), + /* Deepblu Cosmiq */ + DC_FAMILY_DEEPBLU_COSMIQ = (21 << 16), } dc_family_t; #ifdef __cplusplus diff --git a/msvc/libdivecomputer.vcxproj b/msvc/libdivecomputer.vcxproj index 84bac6b..536dc76 100644 --- a/msvc/libdivecomputer.vcxproj +++ b/msvc/libdivecomputer.vcxproj @@ -182,6 +182,8 @@ + + @@ -308,6 +310,7 @@ + diff --git a/src/Makefile.am b/src/Makefile.am index 58916b6..aabbe19 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -77,6 +77,7 @@ libdivecomputer_la_SOURCES = \ sporasub_sp2.h sporasub_sp2.c sporasub_sp2_parser.c \ deepsix_excursion.h deepsix_excursion.c deepsix_excursion_parser.c \ seac_screen.h seac_screen.c seac_screen_parser.c \ + deepblu_cosmiq.h deepblu_cosmiq.c deepblu_cosmiq_parser.c \ socket.h socket.c \ irda.c \ usb.c \ diff --git a/src/array.c b/src/array.c index ad7b4c3..1216d41 100644 --- a/src/array.c +++ b/src/array.c @@ -373,3 +373,14 @@ bcd2dec (unsigned char value) { return ((value >> 4) & 0x0f) * 10 + (value & 0x0f); } + +unsigned char +dec2bcd (unsigned char value) +{ + if (value >= 100) + return 0; + + unsigned char hi = value / 10; + unsigned char lo = value % 10; + return (hi << 4) | lo; +} diff --git a/src/array.h b/src/array.h index 5cdd1a1..ac4c3b1 100644 --- a/src/array.h +++ b/src/array.h @@ -120,6 +120,9 @@ array_uint16_le_set (unsigned char data[], const unsigned short input); unsigned char bcd2dec (unsigned char value); +unsigned char +dec2bcd (unsigned char value); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/deepblu_cosmiq.c b/src/deepblu_cosmiq.c new file mode 100644 index 0000000..5454842 --- /dev/null +++ b/src/deepblu_cosmiq.c @@ -0,0 +1,548 @@ +/* + * libdivecomputer + * + * Copyright (C) 2019 Linus Torvalds + * Copyright (C) 2022 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 + +#include "deepblu_cosmiq.h" +#include "context-private.h" +#include "device-private.h" +#include "platform.h" +#include "checksum.h" +#include "array.h" + +// Maximum data in a packet. It's actually much +// less than this, since BLE packets are small and +// with the 7 bytes of headers and final newline +// and the HEX encoding, the actual maximum is +// just something like 6 bytes. +// +// But in theory the data could be done over +// multiple packets. That doesn't seem to be +// the case in anything I've seen so far. +// +// Pick something small and easy to use for +// stack buffers. +#define MAX_DATA 20 + +#define SZ_HEADER 36 + +#define FP_OFFSET 6 +#define FP_SIZE 6 + +#define CMD_SET_DATETIME 0x20 + +#define CMD_DIVE_COUNT 0x40 +#define CMD_DIVE_HEADER 0x41 +#define CMD_DIVE_HEADER_DATA 0x42 +#define CMD_DIVE_PROFILE 0x43 +#define CMD_DIVE_PROFILE_DATA 0x44 + +#define CMD_SYSTEM_FW 0x58 +#define CMD_SYSTEM_MAC 0x5A + +#define NSTEPS 1000 +#define STEP(i,n) (NSTEPS * (i) / (n)) + +typedef struct deepblu_cosmiq_device_t { + dc_device_t base; + dc_iostream_t *iostream; + unsigned char fingerprint[FP_SIZE]; +} deepblu_cosmiq_device_t; + +static dc_status_t deepblu_cosmiq_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); +static dc_status_t deepblu_cosmiq_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +static dc_status_t deepblu_cosmiq_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime); + +static const dc_device_vtable_t deepblu_cosmiq_device_vtable = { + sizeof(deepblu_cosmiq_device_t), + DC_FAMILY_DEEPBLU_COSMIQ, + deepblu_cosmiq_device_set_fingerprint, /* set_fingerprint */ + NULL, /* read */ + NULL, /* write */ + NULL, /* dump */ + deepblu_cosmiq_device_foreach, /* foreach */ + deepblu_cosmiq_device_timesync, /* timesync */ + NULL, /* close */ +}; + +// +// Send a cmd packet. +// +// The format of the cmd on the "wire" is: +// - byte '#' +// - HEX char of cmd +// - HEX char two's complement modular sum of packet data (including cmd/size) +// - HEX char size of data as encoded in HEX +// - n * HEX char data +// - byte '\n' +// so you end up having 8 bytes of header/trailer overhead, and two bytes +// for every byte of data sent due to the HEX encoding. +// +static dc_status_t +deepblu_cosmiq_send (deepblu_cosmiq_device_t *device, const unsigned char cmd, const unsigned char data[], size_t size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + + if (size > MAX_DATA) + return DC_STATUS_INVALIDARGS; + + if (device_is_cancelled (abstract)) + return DC_STATUS_CANCELLED; + + // Build the packet. + unsigned char csum = ~checksum_add_uint8 (data, size, cmd + 2 * size) + 1; + unsigned char raw[3 + MAX_DATA] = { + cmd, + csum, + 2 * size + }; + if (size) { + memcpy (raw + 3, data, size); + } + unsigned char packet[1 + 2 * (3 + MAX_DATA) + 1] = {0}; + packet[0] = '#'; + array_convert_bin2hex (raw, 3 + size, packet + 1, 2 * (3 + size)); + packet[1 + 2 * (3 + size)] = '\n'; + + HEXDUMP (device->base.context, DC_LOGLEVEL_DEBUG, "cmd", raw, 3 + size); + + // Send the command. + status = dc_iostream_write (device->iostream, packet, 2 + 2 * (3 + size), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (device->base.context, "Failed to send the command."); + return status; + } + + return status; +} + +// +// Receive one 'line' of data +// +// The deepblu BLE protocol is ASCII line based and packetized. +// Normally one packet is one line, but it looks like the Nordic +// Semi BLE chip will sometimes send packets early (some internal +// serial buffer timeout?) with incompete data. +// +// So read packets until you get newline. +static dc_status_t +deepblu_cosmiq_recv_line (deepblu_cosmiq_device_t *device, unsigned char data[], size_t size, size_t *actual) +{ + dc_status_t status = DC_STATUS_SUCCESS; + size_t nbytes = 0; + + while (1) { + unsigned char buffer[20] = {0}; + size_t transferred = 0; + + status = dc_iostream_read (device->iostream, buffer, sizeof(buffer), &transferred); + if (status != DC_STATUS_SUCCESS) { + ERROR (device->base.context, "Failed to receive the reply packet."); + return status; + } + + if (transferred < 1) { + ERROR (device->base.context, "Empty reply packet received."); + return DC_STATUS_PROTOCOL; + } + + // Append the payload data to the output buffer. If the output + // buffer is too small, the error is not reported immediately + // but delayed until all packets have been received. + if (nbytes < size) { + size_t n = transferred; + if (nbytes + n > size) { + n = size - nbytes; + } + memcpy(data + nbytes, buffer, n); + } + nbytes += transferred; + + // Last packet? + if (buffer[transferred - 1] == '\n') + break; + } + + // Verify the expected number of bytes. + if (nbytes > size) { + ERROR (device->base.context, "Unexpected number of bytes received (" DC_PRINTF_SIZE " " DC_PRINTF_SIZE ").", nbytes, size); + return DC_STATUS_PROTOCOL; + } + + if (actual) + *actual = nbytes; + + return DC_STATUS_SUCCESS; +} + +// +// Receive a reply packet +// +// The reply packet has the same format as the cmd packet we +// send, except the first byte is '$' instead of '#'. +static dc_status_t +deepblu_cosmiq_recv (deepblu_cosmiq_device_t *device, const unsigned char cmd, unsigned char data[], size_t size, size_t *actual) +{ + dc_status_t status = DC_STATUS_SUCCESS; + unsigned char packet[1 + 2 * (3 + MAX_DATA) + 1] = {0}; + + size_t transferred = 0; + status = deepblu_cosmiq_recv_line (device, packet, sizeof(packet), &transferred); + if (status != DC_STATUS_SUCCESS) + return status; + + if (transferred < 8 || (transferred % 2) != 0) { + ERROR (device->base.context, "Unexpected packet length (" DC_PRINTF_SIZE ").", transferred); + return DC_STATUS_PROTOCOL; + } + + if (packet[0] != '$' || packet[transferred - 1] != '\n') { + ERROR (device->base.context, "Unexpected packet start/end byte."); + return DC_STATUS_PROTOCOL; + } + + size_t length = transferred - 2; + + unsigned char raw[3 + MAX_DATA] = {0}; + if (array_convert_hex2bin (packet + 1, length, raw, length / 2) != 0) { + ERROR (device->base.context, "Unexpected packet data."); + return DC_STATUS_PROTOCOL; + } + + length /= 2; + + HEXDUMP (device->base.context, DC_LOGLEVEL_DEBUG, "rcv", raw, length); + + unsigned char rsp = raw[0]; + if (rsp != cmd) { + ERROR (device->base.context, "Unexpected packet command byte (%02x)", rsp); + return DC_STATUS_PROTOCOL; + } + + unsigned int n = raw[2]; + if ((n % 2) != 0 || n != transferred - 8) { + ERROR (device->base.context, "Unexpected packet length (%u)", n); + return DC_STATUS_PROTOCOL; + } + + unsigned char csum = checksum_add_uint8 (raw, length, 0); + if (csum != 0) { + ERROR (device->base.context, "Unexpected packet checksum (%02x).", csum); + return DC_STATUS_PROTOCOL; + } + + length -= 3; + + if (length > size) { + ERROR (device->base.context, "Unexpected number of bytes received (" DC_PRINTF_SIZE " " DC_PRINTF_SIZE ").", length, size); + return DC_STATUS_PROTOCOL; + } + + memcpy (data, raw + 3, length); + + if (actual) + *actual = length; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepblu_cosmiq_transfer (deepblu_cosmiq_device_t *device, const unsigned char cmd, + const unsigned char input[], size_t isize, + unsigned char output[], size_t osize) +{ + dc_status_t status = DC_STATUS_SUCCESS; + + status = deepblu_cosmiq_send (device, cmd, input, isize); + if (status != DC_STATUS_SUCCESS) + return status; + + size_t transferred = 0; + status = deepblu_cosmiq_recv (device, cmd, output, osize, &transferred); + if (status != DC_STATUS_SUCCESS) + return status; + + if (transferred != osize) { + ERROR (device->base.context, "Unexpected number of bytes received (" DC_PRINTF_SIZE " " DC_PRINTF_SIZE ").", osize, transferred); + return DC_STATUS_PROTOCOL; + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepblu_cosmiq_recv_bulk (deepblu_cosmiq_device_t *device, dc_event_progress_t *progress, const unsigned char cmd, unsigned char data[], size_t size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + + const unsigned int initial = progress ? progress->current : 0; + + size_t nbytes = 0; + while (nbytes < size) { + size_t transferred = 0; + status = deepblu_cosmiq_recv (device, cmd, data + nbytes, size - nbytes, &transferred); + if (status != DC_STATUS_SUCCESS) + return status; + + nbytes += transferred; + + // Update and emit a progress event. + if (progress) { + progress->current = initial + STEP(nbytes, size); + device_event_emit (abstract, DC_EVENT_PROGRESS, progress); + } + } + + return DC_STATUS_SUCCESS; +} + +dc_status_t +deepblu_cosmiq_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) +{ + dc_status_t status = DC_STATUS_SUCCESS; + deepblu_cosmiq_device_t *device = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + device = (deepblu_cosmiq_device_t *) dc_device_allocate (context, &deepblu_cosmiq_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 timeout for receiving data (1000ms). + status = dc_iostream_set_timeout (device->iostream, 1000); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the timeout."); + goto error_free; + } + + 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 +deepblu_cosmiq_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size) +{ + deepblu_cosmiq_device_t *device = (deepblu_cosmiq_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 +deepblu_cosmiq_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) +{ + dc_status_t status = DC_STATUS_SUCCESS; + deepblu_cosmiq_device_t *device = (deepblu_cosmiq_device_t *) abstract; + const unsigned char zero = 0; + + // Enable progress notifications. + dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Read the firmware version. + unsigned char fw[1] = {0}; + status = deepblu_cosmiq_transfer (device, CMD_SYSTEM_FW, &zero, 1, fw, sizeof(fw)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the firmware version."); + goto error_exit; + } + + // Read the MAC address. + unsigned char mac[6] = {0}; + status = deepblu_cosmiq_transfer (device, CMD_SYSTEM_MAC, &zero, 1, mac, sizeof(mac)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the MAC address."); + goto error_exit; + } + + // Emit a device info event. + dc_event_devinfo_t devinfo; + devinfo.model = 0; + devinfo.firmware = fw[0] & 0x3F; + devinfo.serial = array_uint32_le (mac); + device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); + + unsigned char ndives = 0; + status = deepblu_cosmiq_transfer (device, CMD_DIVE_COUNT, &zero, 1, &ndives, 1); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the number of dives."); + goto error_exit; + } + + // Update and emit a progress event. + progress.current = (ndives == 0 ? 1 : 0) * NSTEPS; + progress.maximum = (ndives + 1) * NSTEPS; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + if (ndives == 0) { + status = DC_STATUS_SUCCESS; + goto error_exit; + } + + unsigned char *headers = malloc (ndives * SZ_HEADER); + if (headers == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_exit; + } + + unsigned int count = 0; + for (unsigned int i = 0; i < ndives; ++i) { + unsigned char number = i + 1; + + unsigned char len = 0; + status = deepblu_cosmiq_transfer (device, CMD_DIVE_HEADER, &number, 1, &len, 1); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the dive header."); + goto error_free_headers; + } + + if (len != SZ_HEADER) { + status = DC_STATUS_PROTOCOL; + goto error_free_headers; + } + + status = deepblu_cosmiq_recv_bulk (device, NULL, CMD_DIVE_HEADER_DATA, headers + i * SZ_HEADER, SZ_HEADER); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the dive header."); + goto error_free_headers; + } + + // Update and emit a progress event. + progress.current = STEP(i + 1, ndives); + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + if (memcmp (headers + i * SZ_HEADER + FP_OFFSET, device->fingerprint, sizeof (device->fingerprint)) == 0) + break; + + count++; + } + + // Update and emit a progress event. + progress.current = 1 * NSTEPS; + progress.maximum = (count + 1) * NSTEPS; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Allocate memory for the dive. + dc_buffer_t *dive = dc_buffer_new (4096); + if (dive == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_free_headers; + } + + for (unsigned int i = 0; i < count; ++i) { + unsigned char number = i + 1; + + unsigned char rsp_len[2] = {0}; + status = deepblu_cosmiq_transfer (device, CMD_DIVE_PROFILE, &number, 1, rsp_len, sizeof(rsp_len)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the dive profile."); + goto error_free_dive; + } + + unsigned int len = array_uint16_be (rsp_len); + + // Erase the buffer. + dc_buffer_clear (dive); + + // Allocate memory for the dive. + if (!dc_buffer_resize (dive, len + SZ_HEADER)) { + ERROR (abstract->context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_free_dive; + } + + // Cache the pointer. + unsigned char *data = dc_buffer_get_data (dive); + + memcpy (data, headers + i * SZ_HEADER, SZ_HEADER); + + status = deepblu_cosmiq_recv_bulk (device, &progress, CMD_DIVE_PROFILE_DATA, data + SZ_HEADER, len); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the dive profile."); + goto error_free_dive; + } + + if (callback && !callback (data, len + SZ_HEADER, data + FP_OFFSET, FP_SIZE, userdata)) + break; + } + +error_free_dive: + dc_buffer_free (dive); +error_free_headers: + free (headers); +error_exit: + return status; +} + +static dc_status_t +deepblu_cosmiq_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) +{ + dc_status_t status = DC_STATUS_SUCCESS; + deepblu_cosmiq_device_t *device = (deepblu_cosmiq_device_t *) abstract; + + if (datetime->year < 2000) { + ERROR (abstract->context, "Invalid date/time value specified."); + return DC_STATUS_INVALIDARGS; + } + + const unsigned char cmd[6] = { + dec2bcd(datetime->year - 2000), + dec2bcd(datetime->month), + dec2bcd(datetime->day), + dec2bcd(datetime->hour), + dec2bcd(datetime->minute), + dec2bcd(datetime->second)}; + unsigned char rsp[1] = {0}; + status = deepblu_cosmiq_transfer (device, CMD_SET_DATETIME, + cmd, sizeof(cmd), rsp, sizeof(rsp)); + if (status != DC_STATUS_SUCCESS) + return status; + + return DC_STATUS_SUCCESS; +} diff --git a/src/deepblu_cosmiq.h b/src/deepblu_cosmiq.h new file mode 100644 index 0000000..3587812 --- /dev/null +++ b/src/deepblu_cosmiq.h @@ -0,0 +1,44 @@ +/* + * libdivecomputer + * + * Copyright (C) 2018 Linus Torvalds + * Copyright (C) 2022 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 DEEPBLU_COSMIQ_H +#define DEEPBLU_COSMIQ_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +dc_status_t +deepblu_cosmiq_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); + +dc_status_t +deepblu_cosmiq_parser_create (dc_parser_t **parser, dc_context_t *context); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* DEEPBLU_COSMIQ_H */ diff --git a/src/deepblu_cosmiq_parser.c b/src/deepblu_cosmiq_parser.c new file mode 100644 index 0000000..508bc5d --- /dev/null +++ b/src/deepblu_cosmiq_parser.c @@ -0,0 +1,218 @@ +/* + * libdivecomputer + * + * Copyright (C) 2019 Linus Torvalds + * Copyright (C) 2022 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 + +#include "deepblu_cosmiq.h" +#include "context-private.h" +#include "parser-private.h" +#include "array.h" + +#define SCUBA 2 +#define GAUGE 3 +#define FREEDIVE 4 + +#define SZ_HEADER 36 +#define SZ_SAMPLE 4 + +typedef struct deepblu_cosmiq_parser_t { + dc_parser_t base; + double hydrostatic; +} deepblu_cosmiq_parser_t; + +static dc_status_t deepblu_cosmiq_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t deepblu_cosmiq_parser_set_density (dc_parser_t *abstract, double density); +static dc_status_t deepblu_cosmiq_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); +static dc_status_t deepblu_cosmiq_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); +static dc_status_t deepblu_cosmiq_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); + +static const dc_parser_vtable_t deepblu_cosmiq_parser_vtable = { + sizeof(deepblu_cosmiq_parser_t), + DC_FAMILY_DEEPBLU_COSMIQ, + deepblu_cosmiq_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + deepblu_cosmiq_parser_set_density, /* set_density */ + deepblu_cosmiq_parser_get_datetime, /* datetime */ + deepblu_cosmiq_parser_get_field, /* fields */ + deepblu_cosmiq_parser_samples_foreach, /* samples_foreach */ + NULL /* destroy */ +}; + +dc_status_t +deepblu_cosmiq_parser_create (dc_parser_t **out, dc_context_t *context) +{ + deepblu_cosmiq_parser_t *parser = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + parser = (deepblu_cosmiq_parser_t *) dc_parser_allocate (context, &deepblu_cosmiq_parser_vtable); + if (parser == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Set the default values. + parser->hydrostatic = DEF_DENSITY_SALT * GRAVITY; + + *out = (dc_parser_t *) parser; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepblu_cosmiq_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) +{ + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepblu_cosmiq_parser_set_density (dc_parser_t *abstract, double density) +{ + deepblu_cosmiq_parser_t *parser = (deepblu_cosmiq_parser_t *) abstract; + + parser->hydrostatic = density * GRAVITY; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepblu_cosmiq_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) +{ + const unsigned char *data = abstract->data; + unsigned int size = abstract->size; + + if (size < SZ_HEADER) + return DC_STATUS_DATAFORMAT; + + if (datetime) { + datetime->year = array_uint16_le(data + 6); + datetime->day = data[8]; + datetime->month = data[9]; + datetime->minute = data[10]; + datetime->hour = data[11]; + datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepblu_cosmiq_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) +{ + deepblu_cosmiq_parser_t *parser = (deepblu_cosmiq_parser_t *) abstract; + const unsigned char *data = abstract->data; + unsigned int size = abstract->size; + + dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + + if (size < SZ_HEADER) + return DC_STATUS_DATAFORMAT; + + unsigned int mode = data[2]; + unsigned int atmospheric = array_uint16_le (data + 4) & 0x1FFF; + + if (value) { + switch (type) { + case DC_FIELD_DIVETIME: + if (mode == SCUBA || mode == GAUGE) + *((unsigned int *) value) = array_uint16_le(data + 12) * 60; + else + *((unsigned int *) value) = array_uint16_le(data + 12); + break; + case DC_FIELD_MAXDEPTH: + *((double *) value) = (signed int) (array_uint16_le (data + 22) - atmospheric) * (BAR / 1000.0) / parser->hydrostatic; + break; + case DC_FIELD_GASMIX_COUNT: + *((unsigned int *) value) = mode == SCUBA; + break; + case DC_FIELD_GASMIX: + gasmix->oxygen = data[3] / 100.0; + gasmix->helium = 0.0; + gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; + break; + case DC_FIELD_DIVEMODE: + switch (mode) { + case SCUBA: + *((dc_divemode_t *) value) = DC_DIVEMODE_OC; + break; + case GAUGE: + *((dc_divemode_t *) value) = DC_DIVEMODE_GAUGE; + break; + case FREEDIVE: + *((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE; + break; + default: + ERROR (abstract->context, "Unknown activity type '%02x'", mode); + return DC_STATUS_DATAFORMAT; + } + break; + case DC_FIELD_ATMOSPHERIC: + *((double *) value) = atmospheric / 1000.0; + break; + default: + return DC_STATUS_UNSUPPORTED; + } + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepblu_cosmiq_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +{ + deepblu_cosmiq_parser_t *parser = (deepblu_cosmiq_parser_t *) abstract; + const unsigned char *data = abstract->data; + unsigned int size = abstract->size; + + if (size < SZ_HEADER) + return DC_STATUS_DATAFORMAT; + + unsigned int interval = data[26]; + unsigned int atmospheric = array_uint16_le (data + 4) & 0x1FFF; + + unsigned int time = 0; + unsigned int offset = SZ_HEADER; + while (offset + SZ_SAMPLE <= size) { + dc_sample_value_t sample = {0}; + unsigned int temperature = array_uint16_le(data + offset + 0); + unsigned int depth = array_uint16_le(data + offset + 2); + offset += SZ_SAMPLE; + + time += interval; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + + sample.depth = (signed int) (depth - atmospheric) * (BAR / 1000.0) / parser->hydrostatic; + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + + sample.temperature = temperature / 10.0; + if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); + } + + return DC_STATUS_SUCCESS; +} diff --git a/src/descriptor.c b/src/descriptor.c index b4d7930..9b3be61 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -63,6 +63,7 @@ static int dc_filter_oceanic (dc_transport_t transport, const void *userdata, vo static int dc_filter_mclean (dc_transport_t transport, const void *userdata, void *params); static int dc_filter_atomic (dc_transport_t transport, const void *userdata, void *params); static int dc_filter_deepsix (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_deepblu (dc_transport_t transport, const void *userdata, void *params); static dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item); @@ -445,6 +446,8 @@ static const dc_descriptor_t g_descriptors[] = { /* Seac Screen */ {"Seac", "Screen", DC_FAMILY_SEAC_SCREEN, 0, DC_TRANSPORT_SERIAL, NULL}, {"Seac", "Action", DC_FAMILY_SEAC_SCREEN, 0, DC_TRANSPORT_SERIAL, NULL}, + /* Deepblu Cosmiq */ + {"Deepblu", "Cosmiq+", DC_FAMILY_DEEPBLU_COSMIQ, 0, DC_TRANSPORT_BLE, dc_filter_deepblu}, }; static int @@ -766,6 +769,18 @@ static int dc_filter_deepsix (dc_transport_t transport, const void *userdata, vo return 1; } +static int dc_filter_deepblu (dc_transport_t transport, const void *userdata, void *params) +{ + static const char * const bluetooth[] = { + "COSMIQ", + }; + + if (transport == DC_TRANSPORT_BLE) { + return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_name); + } + + return 1; +} dc_status_t dc_descriptor_iterator (dc_iterator_t **out) diff --git a/src/device.c b/src/device.c index 66944b8..b6bbfc2 100644 --- a/src/device.c +++ b/src/device.c @@ -62,6 +62,7 @@ #include "sporasub_sp2.h" #include "deepsix_excursion.h" #include "seac_screen.h" +#include "deepblu_cosmiq.h" #include "device-private.h" #include "context-private.h" @@ -231,6 +232,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr case DC_FAMILY_SEAC_SCREEN: rc = seac_screen_device_open (&device, context, iostream); break; + case DC_FAMILY_DEEPBLU_COSMIQ: + rc = deepblu_cosmiq_device_open (&device, context, iostream); + break; default: return DC_STATUS_INVALIDARGS; } diff --git a/src/parser.c b/src/parser.c index c766477..2016d05 100644 --- a/src/parser.c +++ b/src/parser.c @@ -61,6 +61,7 @@ #include "sporasub_sp2.h" #include "deepsix_excursion.h" #include "seac_screen.h" +#include "deepblu_cosmiq.h" #include "context-private.h" #include "parser-private.h" @@ -191,6 +192,9 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa case DC_FAMILY_SEAC_SCREEN: rc = seac_screen_parser_create (&parser, context); break; + case DC_FAMILY_DEEPBLU_COSMIQ: + rc = deepblu_cosmiq_parser_create (&parser, context); + break; default: return DC_STATUS_INVALIDARGS; } From 9eef8c50c04161bea8beb7b6cdd7cc413fd9a69a Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 13 Dec 2022 20:15:49 +0100 Subject: [PATCH 49/58] Add support for the Oceans S1 The Oceans S1 uses a plaintext and line based communication protocol over BLE. The larger payloads, which also contain plaintext data, are transferred using the XMODEM-CRC protocol. Based-on-code-by: Linus Torvalds --- examples/common.c | 1 + include/libdivecomputer/common.h | 2 + msvc/libdivecomputer.vcxproj | 5 + src/Makefile.am | 2 + src/descriptor.c | 16 + src/device.c | 4 + src/oceans_s1.c | 717 +++++++++++++++++++++++++++++++ src/oceans_s1.h | 44 ++ src/oceans_s1_common.c | 68 +++ src/oceans_s1_common.h | 35 ++ src/oceans_s1_parser.c | 317 ++++++++++++++ src/parser.c | 4 + src/platform.h | 2 + 13 files changed, 1217 insertions(+) create mode 100644 src/oceans_s1.c create mode 100644 src/oceans_s1.h create mode 100644 src/oceans_s1_common.c create mode 100644 src/oceans_s1_common.h create mode 100644 src/oceans_s1_parser.c diff --git a/examples/common.c b/examples/common.c index fd8ed4c..6c9a957 100644 --- a/examples/common.c +++ b/examples/common.c @@ -97,6 +97,7 @@ static const backend_table_t g_backends[] = { {"excursion", DC_FAMILY_DEEPSIX_EXCURSION, 0}, {"screen", DC_FAMILY_SEAC_SCREEN, 0}, {"cosmiq", DC_FAMILY_DEEPBLU_COSMIQ, 0}, + {"s1", DC_FAMILY_OCEANS_S1, 0}, }; static const transport_table_t g_transports[] = { diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h index c2b1fcd..c05bee1 100644 --- a/include/libdivecomputer/common.h +++ b/include/libdivecomputer/common.h @@ -116,6 +116,8 @@ typedef enum dc_family_t { DC_FAMILY_SEAC_SCREEN = (20 << 16), /* Deepblu Cosmiq */ DC_FAMILY_DEEPBLU_COSMIQ = (21 << 16), + /* Oceans S1 */ + DC_FAMILY_OCEANS_S1 = (22 << 16), } dc_family_t; #ifdef __cplusplus diff --git a/msvc/libdivecomputer.vcxproj b/msvc/libdivecomputer.vcxproj index 536dc76..b3b370f 100644 --- a/msvc/libdivecomputer.vcxproj +++ b/msvc/libdivecomputer.vcxproj @@ -219,6 +219,9 @@ + + + @@ -333,6 +336,8 @@ + + diff --git a/src/Makefile.am b/src/Makefile.am index aabbe19..72961b8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -78,6 +78,8 @@ libdivecomputer_la_SOURCES = \ deepsix_excursion.h deepsix_excursion.c deepsix_excursion_parser.c \ seac_screen.h seac_screen.c seac_screen_parser.c \ deepblu_cosmiq.h deepblu_cosmiq.c deepblu_cosmiq_parser.c \ + oceans_s1_common.h oceans_s1_common.c \ + oceans_s1.h oceans_s1.c oceans_s1_parser.c \ socket.h socket.c \ irda.c \ usb.c \ diff --git a/src/descriptor.c b/src/descriptor.c index 9b3be61..e41b016 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -64,6 +64,7 @@ static int dc_filter_mclean (dc_transport_t transport, const void *userdata, voi static int dc_filter_atomic (dc_transport_t transport, const void *userdata, void *params); static int dc_filter_deepsix (dc_transport_t transport, const void *userdata, void *params); static int dc_filter_deepblu (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_oceans (dc_transport_t transport, const void *userdata, void *params); static dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item); @@ -448,6 +449,8 @@ static const dc_descriptor_t g_descriptors[] = { {"Seac", "Action", DC_FAMILY_SEAC_SCREEN, 0, DC_TRANSPORT_SERIAL, NULL}, /* Deepblu Cosmiq */ {"Deepblu", "Cosmiq+", DC_FAMILY_DEEPBLU_COSMIQ, 0, DC_TRANSPORT_BLE, dc_filter_deepblu}, + /* Oceans S1 */ + {"Oceans", "S1", DC_FAMILY_OCEANS_S1, 0, DC_TRANSPORT_BLE, dc_filter_oceans}, }; static int @@ -782,6 +785,19 @@ static int dc_filter_deepblu (dc_transport_t transport, const void *userdata, vo return 1; } +static int dc_filter_oceans (dc_transport_t transport, const void *userdata, void *params) +{ + static const char * const bluetooth[] = { + "S1", + }; + + if (transport == DC_TRANSPORT_BLE) { + return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_prefix); + } + + return 1; +} + dc_status_t dc_descriptor_iterator (dc_iterator_t **out) { diff --git a/src/device.c b/src/device.c index b6bbfc2..e930ff1 100644 --- a/src/device.c +++ b/src/device.c @@ -63,6 +63,7 @@ #include "deepsix_excursion.h" #include "seac_screen.h" #include "deepblu_cosmiq.h" +#include "oceans_s1.h" #include "device-private.h" #include "context-private.h" @@ -235,6 +236,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr case DC_FAMILY_DEEPBLU_COSMIQ: rc = deepblu_cosmiq_device_open (&device, context, iostream); break; + case DC_FAMILY_OCEANS_S1: + rc = oceans_s1_device_open (&device, context, iostream); + break; default: return DC_STATUS_INVALIDARGS; } diff --git a/src/oceans_s1.c b/src/oceans_s1.c new file mode 100644 index 0000000..e26979e --- /dev/null +++ b/src/oceans_s1.c @@ -0,0 +1,717 @@ +/* + * libdivecomputer + * + * Copyright (C) 2020 Linus Torvalds + * Copyright (C) 2022 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 // memcmp, memcpy +#include // malloc, free +#include +#include + +#include "oceans_s1.h" +#include "oceans_s1_common.h" +#include "context-private.h" +#include "device-private.h" +#include "platform.h" +#include "checksum.h" +#include "array.h" + +#define SOH 0x01 +#define EOT 0x04 +#define ACK 0x06 +#define NAK 0x15 +#define CAN 0x18 +#define CRC 0x43 + +#define SZ_PACKET 256 +#define SZ_XMODEM 512 + +#define SZ_FINGERPRINT 8 + +typedef struct oceans_s1_dive_t { + struct oceans_s1_dive_t *next; + dc_ticks_t timestamp; + unsigned int number; +} oceans_s1_dive_t; + +typedef struct oceans_s1_device_t { + dc_device_t base; + dc_iostream_t *iostream; + dc_ticks_t timestamp; +} oceans_s1_device_t; + +static dc_status_t oceans_s1_device_set_fingerprint(dc_device_t *abstract, const unsigned char data[], unsigned int size); +static dc_status_t oceans_s1_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +static dc_status_t oceans_s1_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime); + +static const dc_device_vtable_t oceans_s1_device_vtable = { + sizeof(oceans_s1_device_t), + DC_FAMILY_OCEANS_S1, + oceans_s1_device_set_fingerprint, /* set_fingerprint */ + NULL, /* read */ + NULL, /* write */ + NULL, /* dump */ + oceans_s1_device_foreach, /* foreach */ + oceans_s1_device_timesync, /* timesync */ + NULL, /* close */ +}; + +/* + * Oceans S1 initial sequence (all ASCII text with newlines): + * + * Cmd Reply + * + * utc\n utc>ok 1592912375\r\n + * battery\n battery>ok 59%\r\n + * version\n version>ok 1.1 42a7e564\r\n + * utc 1592912364\n utc>ok\r\n + * units 1\n units>ok\r\n + * dllist\n dllist>xmr\r\n + * + * At this point, the dive computer switches to the XMODEM protocol and + * the sequence is no longer single packets with a full line with newline + * termination. + * + * The actual payload remains ASCII text (note the single space indentation): + * + * divelog v1,10s/sample + * dive 1,0,21,1591372057 + * continue 612,10 + * enddive 3131,496 + * dive 2,0,21,1591372925 + * enddive 1535,277 + * dive 3,0,32,1591463368 + * enddive 1711,4515 + * dive 4,0,32,1591961688 + * continue 300,45 + * continue 391,238 + * continue 420,126 + * continue 236,17 + * enddive 1087,2636 + * endlog + * + * Because the XMODEM protocol uses fixed size packets (512 bytes), the last + * packet is padded with newline characters. + * + * Then it goes back to line-mode: + * + * dlget 4 5\n dlget>xmr\r\n + * + * and the data is again transferred using the XMODEM protocol. The payload is + * also ASCII text (note the space indentation again): + * + * divelog v1,10s/sample + * dive 4,0,32,1591961688 + * 365,13,1 + * 382,13,51456 + * 367,13,16640 + * 381,13,49408 + * 375,13,24576 + * 355,13,16384 + * 346,13,16384 + * 326,14,16384 + * 355,14,16384 + * 394,14,24576 + * 397,14,16384 + * 434,14,49152 + * 479,14,49152 + * 488,14,16384 + * 556,14,57344 + * 616,14,49152 + * 655,14,49152 + * 738,14,49152 + * 800,14,57344 + * 800,14,49408 + * 834,14,16640 + * 871,14,24832 + * 860,14,16640 + * 860,14,16640 + * 815,14,24832 + * 738,14,16640 + * 707,14,16640 + * 653,14,24832 + * 647,13,16640 + * 670,13,16640 + * 653,13,24832 + * ... + * continue 236,17 + * 227,13,57600 + * 238,14,16640 + * 267,14,24832 + * 283,14,16384 + * 272,14,16384 + * 303,14,24576 + * 320,14,16384 + * 318,14,16384 + * 318,14,16384 + * 335,14,24576 + * 332,14,16384 + * 386,14,16384 + * 417,14,24576 + * 244,14,16640 + * 71,14,16640 + * enddive 1087,2636 + * endlog + * + * Where the samples seem to be + * - depth in cm + * - temperature in °C + * - events + * + * Repeat with 'dlget 3 4', 'dlget 2 3', 'dlget 1 2'. + * + * Done. + */ + +/* + * Add a dive to the dive list, sorted with newest dive first + * + * I'm not sure if the dive list is always presented sorted by the + * Oceans S1, but it arrives in the reverse order of what we want + * (we want newest first, it lists them oldest first). So we need + * to switch the order, and we might as well make sure it's sorted + * while doing that. + */ +static void +oceans_s1_list_add (oceans_s1_dive_t **head, oceans_s1_dive_t *dive) +{ + if (head == NULL) + return; + + oceans_s1_dive_t *current = *head, *previous = NULL; + while (current) { + if (dive->number >= current->number) + break; + previous = current; + current = current->next; + } + + if (previous) { + dive->next = previous->next; + previous->next = dive; + } else { + dive->next = *head; + *head = dive; + } +} + +static void +oceans_s1_list_free (oceans_s1_dive_t *head) +{ + oceans_s1_dive_t *current = head; + while (current) { + oceans_s1_dive_t *next = current->next; + free (current); + current = next; + } +} + +/* + * The main data is transferred using the XMODEM-CRC protocol. + * + * This variant of the XMODEM protocol uses a sequence of 517 byte packets, + * where each packet has a three byte header, 512 bytes of payload data and a + * two byte CRC checksum. The header is a 'SOH' byte, followed by the block + * number (starting at 1), and the inverse block number (255-block). + * + * We're supposed to start the sequence with a 'CRC' byte, and reply to each + * packet with a 'ACK' byte. When there is no more data, the device will + * send us a 'EOT' packet, which we'll ack with a final 'ACK' byte. + * + * So we get a sequence of: + * + * 01 01 fe <512 bytes> xx xx + * 01 02 fd <512 bytes> xx xx + * 01 03 fc <512 bytes> xx xx + * 01 04 fb <512 bytes> xx xx + * 01 05 fa <512 bytes> xx xx + * 01 06 f9 <512 bytes> xx xx + * 01 07 f8 <512 bytes> xx xx + * 04 + * + * And we should reply with an 'ACK' byte for each of those entries. + * + * NOTE! The above is not in single BLE packets, although the + * sequence blocks always start at a packet boundary. + * + * NOTE! The Oceans Android app uses GATT "Write Commands" (0x53), and not + * GATT "Write Requests" (0x12) for sending the XMODEM single byte commands, + * but this difference does not seem to matter. + */ +static dc_status_t +oceans_s1_xmodem_packet (oceans_s1_device_t *device, unsigned char seq, unsigned char data[], size_t size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + unsigned char packet[3 + SZ_XMODEM + 2] = {0}; + size_t nbytes = 0; + + if (size < SZ_XMODEM) + return DC_STATUS_INVALIDARGS; + + status = dc_iostream_read (device->iostream, packet, sizeof(packet), &nbytes); + if (status != DC_STATUS_SUCCESS) { + ERROR (device->base.context, "Failed to receive the packet."); + return status; + } + + if (nbytes < 1) { + ERROR (device->base.context, "Unexpected packet length (" DC_PRINTF_SIZE ").", nbytes); + return DC_STATUS_PROTOCOL; + } + + if (packet[0] == EOT) { + return DC_STATUS_DONE; + } + + if (nbytes < 3) { + ERROR (device->base.context, "Unexpected packet length (" DC_PRINTF_SIZE ").", nbytes); + return DC_STATUS_PROTOCOL; + } + + if (packet[0] != SOH || packet[1] != seq || packet[1] + packet[2] != 0xFF) { + ERROR (device->base.context, "Unexpected packet header."); + return DC_STATUS_PROTOCOL; + } + + while (nbytes < sizeof(packet)) { + size_t received = 0; + status = dc_iostream_read (device->iostream, packet + nbytes, sizeof(packet) - nbytes, &received); + if (status != DC_STATUS_SUCCESS) { + ERROR (device->base.context, "Failed to receive the packet."); + return status; + } + + nbytes += received; + } + + unsigned short crc = array_uint16_be (packet + nbytes - 2); + unsigned short ccrc = checksum_crc16_ccitt (packet + 3, nbytes - 5, 0x0000); + if (crc != ccrc) { + ERROR (device->base.context, "Unexpected answer checksum (%04x %04x).", crc, ccrc); + return DC_STATUS_PROTOCOL; + } + + memcpy (data, packet + 3, SZ_XMODEM); + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +oceans_s1_xmodem_recv (oceans_s1_device_t *device, dc_buffer_t *buffer) +{ + dc_status_t status = DC_STATUS_SUCCESS; + const unsigned char crc = CRC; + const unsigned char ack = ACK; + + dc_buffer_clear (buffer); + + // Request XMODEM-CRC mode. + status = dc_iostream_write (device->iostream, &crc, 1, NULL); + if (status != DC_STATUS_SUCCESS) + return status; + + unsigned char seq = 1; + while (1) { + // Receive the XMODEM data packet. + unsigned char packet[SZ_XMODEM] = {0}; + status = oceans_s1_xmodem_packet (device, seq, packet, sizeof(packet)); + if (status != DC_STATUS_SUCCESS) { + if (status == DC_STATUS_DONE) + break; + return status; + } + + dc_buffer_append (buffer, packet, sizeof(packet)); + + // Ack the data packet. + status = dc_iostream_write (device->iostream, &ack, 1, NULL); + if (status != DC_STATUS_SUCCESS) + return status; + + seq++; + } + + // Ack the EOT packet. + status = dc_iostream_write (device->iostream, &ack, 1, NULL); + if (status != DC_STATUS_SUCCESS) { + return status; + } + + // Find trailing newline(s). + size_t size = dc_buffer_get_size (buffer); + unsigned char *data = dc_buffer_get_data (buffer); + while (size > 1 && (data[size - 2] == '\r' || data[size - 2] == '\n')) + size--; + + // Remove trailing newline(s). + dc_buffer_slice (buffer, 0, size); + + return DC_STATUS_SUCCESS; +} + +static dc_status_t DC_ATTR_FORMAT_PRINTF(6, 7) +oceans_s1_transfer (oceans_s1_device_t *device, dc_buffer_t *buffer, char data[], size_t size, const char *cmd, const char *params, ...) +{ + dc_status_t status = DC_STATUS_SUCCESS; + char buf[SZ_PACKET + 1] = {0}; + size_t buflen = 0; + + if (device_is_cancelled (&device->base)) + return DC_STATUS_CANCELLED; + + size_t cmdlen = strlen (cmd); + if (buflen + cmdlen > sizeof(buf) - 1) { + ERROR (device->base.context, "Not enough space for the command string."); + return DC_STATUS_NOMEMORY; + } + + // Copy the command string. + memcpy (buf, cmd, cmdlen); + buflen += cmdlen; + + // Null terminate the buffer. + buf[buflen] = 0; + + if (params) { + if (buflen + 1 > sizeof(buf) - 1) { + ERROR (device->base.context, "Not enough space for the separator."); + return DC_STATUS_NOMEMORY; + } + + // Append a space. + buf[buflen++] = ' '; + + // Null terminate the buffer. + buf[buflen] = 0; + + // Append the arguments. + va_list ap; + va_start (ap, params); + int n = dc_platform_vsnprintf (buf + buflen, sizeof(buf) - buflen, params, ap); + va_end (ap); + if (n < 0) { + ERROR (device->base.context, "Not enough space for the arguments."); + return DC_STATUS_NOMEMORY; + } + + buflen += n; + } + + DEBUG(device->base.context, "cmd: %s", buf); + + if (buflen + 1 > sizeof(buf) - 1) { + ERROR (device->base.context, "Not enough space for the newline."); + return DC_STATUS_NOMEMORY; + } + + // Append a newline. + buf[buflen++] = '\n'; + + // Null terminate the buffer. + buf[buflen] = 0; + + // Send the command. + status = dc_iostream_write (device->iostream, buf, buflen, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (device->base.context, "Failed to send the command."); + return status; + } + + // Receive the response. + size_t nbytes = 0; + status = dc_iostream_read (device->iostream, buf, sizeof(buf) - 1, &nbytes); + if (status != DC_STATUS_SUCCESS) { + ERROR (device->base.context, "Failed to receive the response."); + return status; + } + + // Remove trailing newline(s). + while (nbytes && (buf[nbytes - 1] == '\r' || buf[nbytes - 1] == '\n')) + nbytes--; + + // Null terminate the buffer. + buf[nbytes] = 0; + + DEBUG (device->base.context, "rcv: %s", buf); + + // Verify the response. + if (strncmp (buf, cmd, cmdlen) != 0) { + ERROR (device->base.context, "Received unexpected packet data ('%s').", buf); + return DC_STATUS_PROTOCOL; + } + + // Check the type of response. + // If the response indicates "ok", the payload data is send inline in + // the remainder of the response packet. If the response indicates "xmr", + // the payload data is send separately using the XMODEM protocol. + if (strncmp (buf + cmdlen, ">ok", 3) == 0) { + // Ignore leading whitespace. + const char *line = buf + cmdlen + 3; + while (*line == ' ') + line++; + + // Copy the payload data. + size_t len = nbytes - (line - buf); + if (size) { + if (len + 1 > size) { + ERROR (device->base.context, "Unexpected packet length (" DC_PRINTF_SIZE ").", len); + return DC_STATUS_PROTOCOL; + } + memcpy (data, line, len + 1); + } else { + if (len != 0) { + ERROR (device->base.context, "Unexpected packet length (" DC_PRINTF_SIZE ").", len); + return DC_STATUS_PROTOCOL; + } + } + } else if (strncmp (buf + cmdlen, ">xmr", 4) == 0) { + if (nbytes > cmdlen + 4) { + WARNING (device->base.context, "Packet contains extra data ('%s').", buf + cmdlen + 4); + } + return oceans_s1_xmodem_recv (device, buffer); + } else { + ERROR (device->base.context, "Received unexpected packet data ('%s').", buf); + return DC_STATUS_PROTOCOL; + } + + return status; +} + +dc_status_t +oceans_s1_device_open(dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) +{ + dc_status_t status = DC_STATUS_SUCCESS; + oceans_s1_device_t *device = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + device = (oceans_s1_device_t *) dc_device_allocate (context, &oceans_s1_device_vtable); + if (device == NULL) { + ERROR(context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Set the default values. + device->iostream = iostream; + device->timestamp = 0; + + // Set the timeout for receiving data (4000 ms). + status = dc_iostream_set_timeout (device->iostream, 4000); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the timeout."); + goto error_free; + } + + 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 +oceans_s1_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size) +{ + oceans_s1_device_t *device = (oceans_s1_device_t *) abstract; + + if (size && size != SZ_FINGERPRINT) + return DC_STATUS_INVALIDARGS; + + if (size) + device->timestamp = array_uint64_be (data); + else + device->timestamp = 0; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +oceans_s1_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) +{ + dc_status_t status = DC_STATUS_SUCCESS; + oceans_s1_device_t *device = (oceans_s1_device_t *) abstract; + + dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + char version[SZ_PACKET] = {0}; + status = oceans_s1_transfer (device, NULL, version, sizeof(version), "version", NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the version."); + return status; + } + + unsigned int major = 0, minor = 0, unknown = 0; + if (sscanf (version, "%u.%u %x", &major, &minor, &unknown) != 3) { + ERROR (abstract->context, "Failed to parse the version response."); + return DC_STATUS_PROTOCOL; + } + + // Emit a device info event. + dc_event_devinfo_t devinfo; + devinfo.model = 0; + devinfo.firmware = major << 16 | minor; + devinfo.serial = 0; + device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); + + dc_buffer_t *buffer = dc_buffer_new (4096); + if (buffer == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_exit; + } + + status = oceans_s1_transfer (device, buffer, NULL, 0, "dllist", NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to download the dive list."); + goto error_free_buffer; + } + + const unsigned char *data = dc_buffer_get_data (buffer); + size_t size = dc_buffer_get_size (buffer); + + oceans_s1_dive_t *logbook = NULL, *dive = NULL; + unsigned int ndives = 0; + + char *ptr = NULL; + size_t len = 0; + int n = 0; + while ((n = oceans_s1_getline (&ptr, &len, &data, &size)) != -1) { + // Ignore empty lines. + if (n == 0) + continue; + + // Ignore leading whitespace. + const char *line = ptr; + while (*line == ' ') + line++; + + if (strncmp (line, "divelog", 7) == 0 || + strncmp (line, "endlog", 6) == 0 || + strncmp (line, "continue", 8) == 0) { + // Nothing to do. + } else if (strncmp (line, "dive", 4) == 0) { + if (dive != NULL) { + ERROR (abstract->context, "Skipping dive without 'enddive' line."); + free (dive); + dive = NULL; + } + + unsigned int number = 0, divemode = 0, o2 = 0; + dc_ticks_t timestamp = 0; + if (sscanf (line, "dive %u,%u,%u," DC_FORMAT_INT64, &number, &divemode, &o2, ×tamp) != 4) { + ERROR (abstract->context, "Failed to parse the line '%s'.", line); + status = DC_STATUS_DATAFORMAT; + goto error_free_list; + } + + dive = (oceans_s1_dive_t *) malloc (sizeof (oceans_s1_dive_t)); + if (dive == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_free_list; + } + + dive->next = NULL; + dive->timestamp = timestamp; + dive->number = number; + } else if (strncmp (line, "enddive", 7) == 0) { + if (dive) { + if (dive->timestamp > device->timestamp) { + oceans_s1_list_add (&logbook, dive); + ndives++; + } else { + free (dive); + } + dive = NULL; + } else { + WARNING (abstract->context, "Unexpected line '%s'.", line); + } + } else { + WARNING (abstract->context, "Unexpected line '%s'.", line); + } + } + + if (dive != NULL) { + WARNING (abstract->context, "Skipping dive without 'enddive' line."); + free (dive); + dive = NULL; + } + + progress.current = 1; + progress.maximum = 1 + ndives; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + for (dive = logbook; dive; dive = dive->next) { + status = oceans_s1_transfer (device, buffer, NULL, 0, "dlget", "%u %u", dive->number, dive->number + 1); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to download the dive."); + goto error_free_list; + } + + progress.current++; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + unsigned char fingerprint[SZ_FINGERPRINT] = {0}; + array_uint64_be_set (fingerprint, dive->timestamp); + + if (callback && !callback (dc_buffer_get_data (buffer), dc_buffer_get_size (buffer), fingerprint, sizeof(fingerprint), userdata)) + break; + } + +error_free_list: + oceans_s1_list_free (logbook); + free (ptr); +error_free_buffer: + dc_buffer_free (buffer); +error_exit: + return status; +} + +static dc_status_t +oceans_s1_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) +{ + dc_status_t status = DC_STATUS_SUCCESS; + oceans_s1_device_t *device = (oceans_s1_device_t *) abstract; + + // Ignore the timezone offset. + dc_datetime_t dt = *datetime; + dt.timezone = DC_TIMEZONE_NONE; + + dc_ticks_t timestamp = dc_datetime_mktime (&dt); + if (timestamp < 0) { + ERROR (abstract->context, "Invalid date/time value specified."); + return DC_STATUS_INVALIDARGS; + } + + status = oceans_s1_transfer (device, NULL, NULL, 0, "utc", DC_FORMAT_INT64, timestamp); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to set the date/time."); + return status; + } + + return status; +} diff --git a/src/oceans_s1.h b/src/oceans_s1.h new file mode 100644 index 0000000..825d309 --- /dev/null +++ b/src/oceans_s1.h @@ -0,0 +1,44 @@ +/* + * libdivecomputer + * + * Copyright (C) 2020 Linus Torvalds + * Copyright (C) 2022 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 OCEANS_S1_H +#define OCEANS_S1_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +dc_status_t +oceans_s1_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); + +dc_status_t +oceans_s1_parser_create (dc_parser_t **parser, dc_context_t *context); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* OCEANS_S1_H */ diff --git a/src/oceans_s1_common.c b/src/oceans_s1_common.c new file mode 100644 index 0000000..0522891 --- /dev/null +++ b/src/oceans_s1_common.c @@ -0,0 +1,68 @@ +/* + * libdivecomputer + * + * Copyright (C) 2022 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 // memcmp, memcpy +#include // malloc, free +#include + +#include "oceans_s1_common.h" + +int +oceans_s1_getline (char **line, size_t *linelen, const unsigned char **data, size_t *size) +{ + if (line == NULL || linelen == NULL || data == NULL || size == NULL) + return -1; + + if (*size == 0) + return -1; + + // Find the end of the line. + unsigned int strip = 0; + const unsigned char *p = *data, *end = p + *size; + while (p != end) { + unsigned char c = *p++; + if (c == '\r' || c == '\n') { + strip = 1; + break; + } + } + + // Get the length of the line. + size_t len = p - *data; + + // Resize the buffer (if necessary). + if (*line == NULL || len + 1 > *linelen) { + char *buf = (char *) malloc (len + 1); + if (buf == NULL) + return -1; + free (*line); + *line = buf; + *linelen = len + 1; + } + + // Copy the data. + memcpy (*line, *data, len - strip); + (*line)[len - strip] = 0; + *data += len; + *size -= len; + + return len - strip; +} diff --git a/src/oceans_s1_common.h b/src/oceans_s1_common.h new file mode 100644 index 0000000..37a530c --- /dev/null +++ b/src/oceans_s1_common.h @@ -0,0 +1,35 @@ +/* + * libdivecomputer + * + * Copyright (C) 2022 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 OCEANS_S1_COMMON_H +#define OCEANS_S1_COMMON_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +int +oceans_s1_getline (char **line, size_t *linelen, const unsigned char **data, size_t *size); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* OCEANS_S1_COMMON_H */ diff --git a/src/oceans_s1_parser.c b/src/oceans_s1_parser.c new file mode 100644 index 0000000..8b4cfbd --- /dev/null +++ b/src/oceans_s1_parser.c @@ -0,0 +1,317 @@ +/* + * libdivecomputer + * + * Copyright (C) 2020 Linus Torvalds + * Copyright (C) 2022 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 +#include + +#include "oceans_s1.h" +#include "oceans_s1_common.h" +#include "context-private.h" +#include "parser-private.h" +#include "platform.h" +#include "array.h" + +#define SCUBA 0 +#define APNEA 1 + +#define EVENT_DIVE_STARTED 0x0001 +#define EVENT_DIVE_ENDED 0x0002 +#define EVENT_DIVE_RESUMED 0x0004 +#define EVENT_PING_SENT 0x0008 +#define EVENT_PING_RECEIVED 0x0010 +#define EVENT_DECO_STOP 0x0020 +#define EVENT_SAFETY_STOP 0x0040 +#define EVENT_BATTERY_LOW 0x0080 +#define EVENT_BACKLIGHT_ON 0x0100 + +typedef struct oceans_s1_parser_t oceans_s1_parser_t; + +struct oceans_s1_parser_t { + dc_parser_t base; + // Cached fields. + dc_ticks_t timestamp; + unsigned int cached; + unsigned int number; + unsigned int divemode; + unsigned int oxygen; + unsigned int maxdepth; + unsigned int divetime; +}; + +static dc_status_t oceans_s1_parser_set_data(dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t oceans_s1_parser_get_datetime(dc_parser_t *abstract, dc_datetime_t *datetime); +static dc_status_t oceans_s1_parser_get_field(dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); +static dc_status_t oceans_s1_parser_samples_foreach(dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); + +static const dc_parser_vtable_t oceans_s1_parser_vtable = { + sizeof(oceans_s1_parser_t), + DC_FAMILY_OCEANS_S1, + oceans_s1_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ + oceans_s1_parser_get_datetime, /* datetime */ + oceans_s1_parser_get_field, /* fields */ + oceans_s1_parser_samples_foreach, /* samples_foreach */ + NULL /* destroy */ +}; + +dc_status_t +oceans_s1_parser_create (dc_parser_t **out, dc_context_t *context) +{ + oceans_s1_parser_t *parser = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + parser = (oceans_s1_parser_t *) dc_parser_allocate (context, &oceans_s1_parser_vtable); + if (parser == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Set the default values. + parser->cached = 0; + parser->timestamp = 0; + parser->number = 0; + parser->divemode = 0; + parser->oxygen = 0; + parser->maxdepth = 0; + parser->divetime = 0; + + *out = (dc_parser_t *) parser; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +oceans_s1_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) +{ + oceans_s1_parser_t *parser = (oceans_s1_parser_t *) abstract; + + // Reset the cache. + parser->cached = 0; + parser->timestamp = 0; + parser->number = 0; + parser->divemode = 0; + parser->oxygen = 0; + parser->maxdepth = 0; + parser->divetime = 0; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +oceans_s1_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) +{ + oceans_s1_parser_t *parser = (oceans_s1_parser_t *) abstract; + + if (!parser->cached) { + dc_status_t status = oceans_s1_parser_samples_foreach (abstract, NULL, NULL); + if (status != DC_STATUS_SUCCESS) + return status; + } + + if (!dc_datetime_gmtime (datetime, parser->timestamp)) + return DC_STATUS_DATAFORMAT; + + datetime->timezone = DC_TIMEZONE_NONE; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +oceans_s1_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) +{ + oceans_s1_parser_t *parser = (oceans_s1_parser_t *) abstract; + + if (!parser->cached) { + dc_status_t status = oceans_s1_parser_samples_foreach (abstract, NULL, NULL); + if (status != DC_STATUS_SUCCESS) + return status; + } + + dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + + if (value) { + switch (type) { + case DC_FIELD_DIVETIME: + *((unsigned int *) value) = parser->divetime; + break; + case DC_FIELD_MAXDEPTH: + *((double *) value) = parser->maxdepth / 100.0; + break; + case DC_FIELD_GASMIX_COUNT: + *((unsigned int *) value) = parser->divemode == SCUBA; + break; + case DC_FIELD_GASMIX: + gasmix->helium = 0.0; + gasmix->oxygen = parser->oxygen / 100.0; + gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; + break; + case DC_FIELD_DIVEMODE: + switch (parser->divemode) { + case SCUBA: + *((dc_divemode_t *) value) = DC_DIVEMODE_OC; + break; + case APNEA: + *((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE; + break; + default: + return DC_STATUS_DATAFORMAT; + } + break; + default: + return DC_STATUS_UNSUPPORTED; + } + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +oceans_s1_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +{ + dc_status_t status = DC_STATUS_SUCCESS; + oceans_s1_parser_t *parser = (oceans_s1_parser_t *) abstract; + const unsigned char *data = abstract->data; + size_t size = abstract->size; + + dc_ticks_t timestamp = 0; + unsigned int number = 0, divemode = 0, oxygen = 0; + unsigned int maxdepth = 0, divetime = 0; + unsigned int interval = 10; + unsigned int time = 0; + + char *ptr = NULL; + size_t len = 0; + int n = 0; + while ((n = oceans_s1_getline (&ptr, &len, &data, &size)) != -1) { + dc_sample_value_t sample = {0}; + + // Ignore empty lines. + if (n == 0) + continue; + + // Ignore leading whitespace. + const char *line = ptr; + while (*line == ' ') + line++; + + if (strncmp (line, "divelog", 7) == 0) { + if (sscanf (line, "divelog v1,%us/sample", &interval) != 1) { + ERROR (parser->base.context, "Failed to parse the line '%s'.", line); + status = DC_STATUS_DATAFORMAT; + goto error_free; + } + if (interval == 0) { + ERROR (parser->base.context, "Invalid sample interval (%u).", interval); + status = DC_STATUS_DATAFORMAT; + goto error_free; + } + } else if (strncmp (line, "dive", 4) == 0) { + if (sscanf (line, "dive %u,%u,%u," DC_FORMAT_INT64, &number, &divemode, &oxygen, ×tamp) != 4) { + ERROR (parser->base.context, "Failed to parse the line '%s'.", line); + status = DC_STATUS_DATAFORMAT; + goto error_free; + } + } else if (strncmp (line, "continue", 8) == 0) { + unsigned int depth = 0, seconds = 0; + if (sscanf (line, "continue %u,%u", &depth, &seconds) != 2) { + ERROR (parser->base.context, "Failed to parse the line '%s'.", line); + status = DC_STATUS_DATAFORMAT; + goto error_free; + } + + // Create surface samples for the surface time, + // and then a depth sample at the stated depth. + unsigned int nsamples = seconds / interval; + for (unsigned int i = 0; i < nsamples; ++i) { + time += interval; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + + sample.depth = 0; + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + } + + time += interval; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + + sample.depth = depth / 100.0; + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + } else if (strncmp(line, "enddive", 7) == 0) { + if (sscanf(line, "enddive %u,%u", &maxdepth, &divetime) != 2) { + ERROR (parser->base.context, "Failed to parse the line '%s'.", line); + status = DC_STATUS_DATAFORMAT; + goto error_free; + } + } else if (strncmp (line, "endlog", 6) == 0) { + // Nothing to do. + } else { + unsigned int depth = 0, events = 0; + int temperature = 0; + if (sscanf (line, "%u,%d,%u", &depth, &temperature, &events) != 3) { + ERROR (parser->base.context, "Failed to parse the line '%s'.", line); + status = DC_STATUS_DATAFORMAT; + goto error_free; + } + + time += interval; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + + sample.depth = depth / 100.0; + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + + sample.temperature = temperature; + if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); + + if (events & EVENT_DECO_STOP) { + sample.deco.type = DC_DECO_DECOSTOP; + } else if (events & EVENT_SAFETY_STOP) { + sample.deco.type = DC_DECO_SAFETYSTOP; + } else { + sample.deco.type = DC_DECO_NDL; + } + sample.deco.depth = 0.0; + sample.deco.time = 0; + if (callback) callback (DC_SAMPLE_DECO, sample, userdata); + } + } + + // Cache the data for later use. + parser->timestamp = timestamp; + parser->number = number; + parser->divemode = divemode; + parser->oxygen = oxygen; + parser->maxdepth = maxdepth; + parser->divetime = divetime; + parser->cached = 1; + +error_free: + free (ptr); + return status; +} diff --git a/src/parser.c b/src/parser.c index 2016d05..961f59e 100644 --- a/src/parser.c +++ b/src/parser.c @@ -62,6 +62,7 @@ #include "deepsix_excursion.h" #include "seac_screen.h" #include "deepblu_cosmiq.h" +#include "oceans_s1.h" #include "context-private.h" #include "parser-private.h" @@ -195,6 +196,9 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa case DC_FAMILY_DEEPBLU_COSMIQ: rc = deepblu_cosmiq_parser_create (&parser, context); break; + case DC_FAMILY_OCEANS_S1: + rc = oceans_s1_parser_create (&parser, context); + break; default: return DC_STATUS_INVALIDARGS; } diff --git a/src/platform.h b/src/platform.h index a652503..e4a5cd5 100644 --- a/src/platform.h +++ b/src/platform.h @@ -36,8 +36,10 @@ extern "C" { #ifdef _WIN32 #define DC_PRINTF_SIZE "%Iu" +#define DC_FORMAT_INT64 "%I64d" #else #define DC_PRINTF_SIZE "%zu" +#define DC_FORMAT_INT64 "%lld" #endif #ifdef _MSC_VER From 4e83b1642c5c6f9fe6b5ae95d7aefe3671b2d846 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 26 Jan 2023 19:32:30 +0100 Subject: [PATCH 50/58] Use the correct field for the setpoint sample This only happened to work correctly because both the setpoint and ppO2 field have the same data type and are located at the same offset in the union. --- src/suunto_eonsteel_parser.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index c5d7a63..907eca7 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -829,11 +829,11 @@ static void sample_setpoint_type(const struct type_desc *desc, struct sample_dat } if (!strcasecmp(type, "Low")) - sample.ppo2 = info->eon->cache.lowsetpoint; + sample.setpoint = info->eon->cache.lowsetpoint; else if (!strcasecmp(type, "High")) - sample.ppo2 = info->eon->cache.highsetpoint; + sample.setpoint = info->eon->cache.highsetpoint; else if (!strcasecmp(type, "Custom")) - sample.ppo2 = info->eon->cache.customsetpoint; + sample.setpoint = info->eon->cache.customsetpoint; else { DEBUG(info->eon->base.context, "sample_setpoint_type(%u) unknown type '%s'", value, type); free(type); From 78373d827be59ade15446c74ad660580fb04de73 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 30 Jan 2023 20:06:43 +0100 Subject: [PATCH 51/58] Add support for the HP CCR tank pressure For the HP CCR mode, the O2 and diluent tank pressure was stored in the T1 and T2 tank pressure fields. Starting from log version 14 they moved to dedicated fields in the EXT sample, next to the T3 and T4 tank pressure. Thus the maximum number of tanks increased from 4 to 6. --- src/shearwater_predator_parser.c | 88 ++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 20 deletions(-) diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index 3787d0a..d0cccb3 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -78,6 +78,10 @@ #define M_OC_REC 6 #define M_FREEDIVE 7 +#define AI_OFF 0 +#define AI_HPCCR 4 +#define AI_ON 5 + #define GF 0 #define VPMB 1 #define VPMB_GFS 2 @@ -87,7 +91,7 @@ #define IMPERIAL 1 #define NGASMIXES 10 -#define NTANKS 4 +#define NTANKS 6 #define NRECORDS 8 #define PREDATOR 2 @@ -133,6 +137,7 @@ struct shearwater_predator_parser_t { shearwater_predator_gasmix_t gasmix[NGASMIXES]; shearwater_predator_tank_t tank[NTANKS]; unsigned int tankidx[NTANKS]; + unsigned int aimode; unsigned int calibrated; double calibration[3]; unsigned int divemode; @@ -245,6 +250,7 @@ shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsig memset (parser->tank[i].name, 0, sizeof (parser->tank[i].name)); parser->tankidx[i] = i; } + parser->aimode = AI_OFF; parser->calibrated = 0; for (unsigned int i = 0; i < 3; ++i) { parser->calibration[i] = 0.0; @@ -307,6 +313,7 @@ shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char memset (parser->tank[i].name, 0, sizeof (parser->tank[i].name)); parser->tankidx[i] = i; } + parser->aimode = AI_OFF; parser->calibrated = 0; for (unsigned int i = 0; i < 3; ++i) { parser->calibration[i] = 0.0; @@ -418,6 +425,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) shearwater_predator_gasmix_t gasmix[NGASMIXES] = {0}; shearwater_predator_tank_t tank[NTANKS] = {0}; unsigned int o2_previous = 0, he_previous = 0; + unsigned int aimode = AI_OFF; unsigned int offset = headersize; unsigned int length = size - footersize; @@ -478,14 +486,15 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) // 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 + idx[i]); + unsigned int id = (aimode == AI_HPCCR ? 4 : 0) + i; if (pressure < 0xFFF0) { pressure &= 0x0FFF; - if (!tank[i].active) { - tank[i].active = 1; - tank[i].beginpressure = pressure; - tank[i].endpressure = pressure; + if (!tank[id].active) { + tank[id].active = 1; + tank[id].beginpressure = pressure; + tank[id].endpressure = pressure; } - tank[i].endpressure = pressure; + tank[id].endpressure = pressure; } } } @@ -494,14 +503,33 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) if (logversion >= 13) { for (unsigned int i = 0; i < 2; ++i) { unsigned int pressure = array_uint16_be (data + offset + pnf + i * 2); + unsigned int id = 2 + i; if (pressure < 0xFFF0) { pressure &= 0x0FFF; - if (!tank[i + 2].active) { - tank[i + 2].active = 1; - tank[i + 2].beginpressure = pressure; - tank[i + 2].endpressure = pressure; + if (!tank[id].active) { + tank[id].active = 1; + tank[id].beginpressure = pressure; + tank[id].endpressure = pressure; } - tank[i + 2].endpressure = pressure; + tank[id].endpressure = pressure; + } + } + } + // Tank pressure (HP CCR) + if (logversion >= 14) { + for (unsigned int i = 0; i < 2; ++i) { + unsigned int pressure = array_uint16_be (data + offset + pnf + 4 + i * 2); + unsigned int id = 4 + i; + if (pressure) { + if (!tank[id].active) { + tank[id].active = 1; + tank[id].enabled = 1; + tank[id].beginpressure = pressure; + tank[id].endpressure = pressure; + tank[id].name[0] = i == 0 ? 'D': 'O'; + tank[id].name[1] = 0; + } + tank[id].endpressure = pressure; } } } @@ -518,18 +546,23 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) // Air integration mode if (logversion >= 7) { - unsigned int airmode = data[offset + 28]; + aimode = data[offset + 28]; if (logversion < 13) { - if (airmode == 1 || airmode == 2) { - tank[airmode - 1].enabled = 1; - } else if (airmode == 3) { + if (aimode == 1 || aimode == 2) { + tank[aimode - 1].enabled = 1; + } else if (aimode == 3) { tank[0].enabled = 1; tank[1].enabled = 1; } } - if (airmode == 4) { - tank[0].enabled = 1; - tank[1].enabled = 1; + if (logversion < 14) { + if (aimode == AI_HPCCR) { + for (unsigned int i = 0; i < 2; ++i) { + tank[4 + i].enabled = 1; + tank[4 + i].name[0] = i == 0 ? 'D': 'O'; + tank[4 + i].name[1] = 0; + } + } } } } else if (type == LOG_RECORD_OPENING_5) { @@ -659,6 +692,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) parser->tankidx[i] = UNDEFINED; } } + parser->aimode = aimode; parser->divemode = divemode; parser->units = data[parser->opening[0] + 8]; parser->atmospheric = array_uint16_be (data + parser->opening[1] + (parser->pnf ? 16 : 47)); @@ -946,9 +980,10 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal // 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 + idx[i]); + unsigned int id = (parser->aimode == AI_HPCCR ? 4 : 0) + i; if (pressure < 0xFFF0) { pressure &= 0x0FFF; - sample.pressure.tank = parser->tankidx[i]; + sample.pressure.tank = parser->tankidx[id]; sample.pressure.value = pressure * 2 * PSI / BAR; if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata); } @@ -971,9 +1006,22 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal if (parser->logversion >= 13) { for (unsigned int i = 0; i < 2; ++i) { unsigned int pressure = array_uint16_be (data + offset + pnf + i * 2); + unsigned int id = 2 + i; if (pressure < 0xFFF0) { pressure &= 0x0FFF; - sample.pressure.tank = parser->tankidx[i + 2]; + sample.pressure.tank = parser->tankidx[id]; + sample.pressure.value = pressure * 2 * PSI / BAR; + if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata); + } + } + } + // Tank pressure (HP CCR) + if (parser->logversion >= 14) { + for (unsigned int i = 0; i < 2; ++i) { + unsigned int pressure = array_uint16_be (data + offset + pnf + 4 + i * 2); + unsigned int id = 4 + i; + if (pressure) { + sample.pressure.tank = parser->tankidx[id]; sample.pressure.value = pressure * 2 * PSI / BAR; if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata); } From 201be561d465b1578feced0dc5dafcb2f509879e Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 8 Sep 2022 08:58:28 +0200 Subject: [PATCH 52/58] Add support for the new Excursion v6+ firmware The new Excursion v6 firmware supports some new commands for accessing the dive index, and also uses a completely new data format. To preserve backwards compatibility in the download logic to some extent, some critical fields such as the profile length, remain stored at identical offsets. The new data format now contains a version field to allow for future modifications. This version field is located at byte offset 3, which corresponds to the highest byte of the 32 bit dive number in the old format. Thus, unless someone manages to reach 16M dives, this field will always be zero for the old format. Co-authored-by: Ryan Gardner --- src/deepsix_excursion.c | 109 +++++-- src/deepsix_excursion_parser.c | 579 +++++++++++++++++++++++++++++++-- 2 files changed, 633 insertions(+), 55 deletions(-) diff --git a/src/deepsix_excursion.c b/src/deepsix_excursion.c index c6eaaa8..de0bb64 100644 --- a/src/deepsix_excursion.c +++ b/src/deepsix_excursion.c @@ -32,7 +32,8 @@ #define MAXPACKET 255 -#define HEADERSIZE 156 +#define HEADERSIZE_MIN 128 +#define HEADERSIZE_V0 156 #define NSTEPS 1000 #define STEP(i,n) (NSTEPS * (i) / (n)) @@ -55,9 +56,14 @@ #define CMD_SETTINGS_STORE 0x27 #define CMD_SETTINGS_LOAD 0x28 -#define GRP_DIVE 0xC0 -#define CMD_DIVE_HEADER 0x02 -#define CMD_DIVE_PROFILE 0x03 +#define GRP_DIVE 0xC0 +#define CMD_DIVE_HEADER 0x02 +#define CMD_DIVE_PROFILE 0x03 +#define CMD_DIVE_COUNT 0x05 +#define CMD_DIVE_INDEX_LAST 0x06 +#define CMD_DIVE_INDEX_FIRST 0x07 +#define CMD_DIVE_INDEX_PREVIOUS 0x08 +#define CMD_DIVE_INDEX_NEXT 0x09 typedef struct deepsix_excursion_device_t { dc_device_t base; @@ -216,8 +222,8 @@ deepsix_excursion_device_open (dc_device_t **out, dc_context_t *context, dc_iost goto error_free; } - // Set the timeout for receiving data (1000ms). - status = dc_iostream_set_timeout (device->iostream, 1000); + // Set the timeout for receiving data (3000ms). + status = dc_iostream_set_timeout (device->iostream, 3000); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to set the timeout."); goto error_free; @@ -300,17 +306,41 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call devinfo.serial = array_convert_str2num (rsp_serial + 3, sizeof(rsp_serial) - 3); device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); - // Read the index of the last dive. - const unsigned char cmd_index[2] = {0}; - unsigned char rsp_index[2] = {0}; - status = deepsix_excursion_transfer (device, GRP_INFO, CMD_INFO_LASTDIVE, DIR_READ, cmd_index, sizeof(cmd_index), rsp_index, sizeof(rsp_index), NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the last dive index."); - return status; - } + // Firmware version 6+ uses the new commands. + unsigned int fw6 = memcmp(rsp_software, "D01", 3) == 0 && rsp_software[4] >= '6'; - // Calculate the number of dives. - unsigned int ndives = array_uint16_le (rsp_index); + unsigned int ndives = 0, last = 0; + if (fw6) { + // Read the number of dives. + unsigned char rsp_ndives[2] = {0}; + status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_COUNT, DIR_READ, NULL, 0, rsp_ndives, sizeof(rsp_ndives), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the number of dives"); + return status; + } + + // Read the index of the last dive. + unsigned char rsp_last[4] = {0}; + status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_INDEX_LAST, DIR_READ, NULL, 0, rsp_last, sizeof(rsp_last), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the last dive index."); + return status; + } + + ndives = array_uint16_le (rsp_ndives); + last = array_uint16_le (rsp_last); + } else { + // Read the index of the last dive. + const unsigned char cmd_last[2] = {0}; + unsigned char rsp_last[2] = {0}; + status = deepsix_excursion_transfer (device, GRP_INFO, CMD_INFO_LASTDIVE, DIR_READ, cmd_last, sizeof(cmd_last), rsp_last, sizeof(rsp_last), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the last dive index."); + return status; + } + + ndives = last = array_uint16_le (rsp_last); + } // Update and emit a progress event. progress.current = 1 * NSTEPS; @@ -323,32 +353,58 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call return DC_STATUS_NOMEMORY; } + unsigned int number = last; for (unsigned int i = 0; i < ndives; ++i) { - unsigned int number = ndives - i; + if (fw6) { + if (i > 0) { + const unsigned char cmd_previous[] = { + (number ) & 0xFF, + (number >> 8) & 0xFF, + (number >> 16) & 0xFF, + (number >> 24) & 0xFF}; + unsigned char rsp_previous[4] = {0}; + status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_INDEX_PREVIOUS, DIR_READ, + cmd_previous, sizeof(cmd_previous), rsp_previous, sizeof(rsp_previous), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the previous dive index"); + return status; + } + number = array_uint32_le (rsp_previous); + } + } else { + number = ndives - i; + } + unsigned int headersize = 0; const unsigned char cmd_header[] = { (number ) & 0xFF, (number >> 8) & 0xFF}; - unsigned char rsp_header[HEADERSIZE] = {0}; - status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_HEADER, DIR_READ, cmd_header, sizeof(cmd_header), rsp_header, sizeof(rsp_header), NULL); + unsigned char rsp_header[MAXPACKET] = {0}; + status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_HEADER, DIR_READ, + cmd_header, sizeof(cmd_header), rsp_header, sizeof(rsp_header), &headersize); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the dive header."); goto error_free; } + if (headersize < HEADERSIZE_MIN || headersize != (fw6 ? rsp_header[2] : HEADERSIZE_V0)) { + ERROR (abstract->context, "Unexpected size of the dive header (%u).", headersize); + goto error_free; + } + if (memcmp(rsp_header + FP_OFFSET, device->fingerprint, sizeof(device->fingerprint)) == 0) break; unsigned int length = array_uint32_le (rsp_header + 8); // Update and emit a progress event. - progress.current = (i + 1) * NSTEPS + STEP(sizeof(rsp_header), sizeof(rsp_header) + length); - device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + progress.current = (i + 1) * NSTEPS + STEP(headersize, headersize + length); + device_event_emit(abstract, DC_EVENT_PROGRESS, &progress); dc_buffer_clear(buffer); - dc_buffer_reserve(buffer, sizeof(rsp_header) + length); + dc_buffer_reserve(buffer, headersize + length); - if (!dc_buffer_append(buffer, rsp_header, sizeof(rsp_header))) { + if (!dc_buffer_append(buffer, rsp_header, headersize)) { ERROR (abstract->context, "Insufficient buffer space available."); status = DC_STATUS_NOMEMORY; goto error_free; @@ -365,7 +421,8 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call (offset >> 16) & 0xFF, (offset >> 24) & 0xFF}; unsigned char rsp_profile[MAXPACKET] = {0}; - status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_PROFILE, DIR_READ, cmd_profile, sizeof(cmd_profile), rsp_profile, sizeof(rsp_profile), &len); + status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_PROFILE, DIR_READ, + cmd_profile, sizeof(cmd_profile), rsp_profile, sizeof(rsp_profile), &len); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the dive profile."); goto error_free; @@ -378,8 +435,8 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call } // Update and emit a progress event. - progress.current = (i + 1) * NSTEPS + STEP(sizeof(rsp_header) + offset + n, sizeof(rsp_header) + length); - device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + progress.current = (i + 1) * NSTEPS + STEP(headersize + offset + n, headersize + length); + device_event_emit(abstract, DC_EVENT_PROGRESS, &progress); if (!dc_buffer_append(buffer, rsp_profile, n)) { ERROR (abstract->context, "Insufficient buffer space available."); diff --git a/src/deepsix_excursion_parser.c b/src/deepsix_excursion_parser.c index 687f86f..2fc8c2c 100644 --- a/src/deepsix_excursion_parser.c +++ b/src/deepsix_excursion_parser.c @@ -31,7 +31,11 @@ #include "parser-private.h" #include "array.h" -#define HEADERSIZE 156 +#define HEADERSIZE_MIN 128 + +#define MAX_SAMPLES 7 +#define MAX_EVENTS 7 +#define MAX_GASMIXES 20 #define ALARM 0x0001 #define TEMPERATURE 0x0002 @@ -39,20 +43,81 @@ #define CEILING 0x0004 #define CNS 0x0005 -#define DENSITY 1024.0 +#define SAMPLE_TEMPERATURE 0 +#define SAMPLE_DECO_NDL 1 +#define SAMPLE_CNS 2 + +#define EVENT_CHANGE_GAS 7 +#define EVENT_ALARMS 8 +#define EVENT_CHANGE_SETPOINT 9 +#define EVENT_SAMPLES_MISSED 10 +#define EVENT_RESERVED 15 + +#define ALARM_ASCENTRATE 0 +#define ALARM_CEILING 1 +#define ALARM_PO2 2 +#define ALARM_MAXDEPTH 3 +#define ALARM_DIVETIME 4 +#define ALARM_CNS 5 + +#define DECOSTOP 0x02 +#define SAFETYSTOP 0x04 + +#define DENSITY 1024 + +#define UNDEFINED 0xFFFFFFFF #define FWVERSION(major,minor) ( \ ((((major) + '0') & 0xFF) << 8) | \ ((minor) & 0xFF)) +typedef struct deepsix_excursion_layout_t { + unsigned int headersize; + unsigned int version; + unsigned int divemode; + unsigned int samplerate; + unsigned int salinity; + unsigned int datetime; + unsigned int divetime; + unsigned int maxdepth; + unsigned int temperature_min; + unsigned int avgdepth; + unsigned int firmware; + unsigned int temperature_surf; + unsigned int atmospheric; + unsigned int gf; +} deepsix_excursion_layout_t; + +typedef struct deepsix_excursion_gasmix_t { + unsigned int id; + unsigned int oxygen; + unsigned int helium; +} deepsix_excursion_gasmix_t; + +typedef struct deepsix_excursion_sample_info_t { + unsigned int type; + unsigned int divisor; + unsigned int size; +} deepsix_excursion_sample_info_t; + +typedef struct deepsix_excursion_event_info_t { + unsigned int type; + unsigned int size; +} deepsix_excursion_event_info_t; + typedef struct deepsix_excursion_parser_t { dc_parser_t base; + unsigned int cached; + unsigned int ngasmixes; + deepsix_excursion_gasmix_t gasmix[MAX_GASMIXES]; } deepsix_excursion_parser_t; static dc_status_t deepsix_excursion_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); static dc_status_t deepsix_excursion_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); static dc_status_t deepsix_excursion_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); static dc_status_t deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); +static dc_status_t deepsix_excursion_parser_samples_foreach_v0 (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); +static dc_status_t deepsix_excursion_parser_samples_foreach_v1 (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); static const dc_parser_vtable_t deepsix_parser_vtable = { sizeof(deepsix_excursion_parser_t), @@ -67,6 +132,58 @@ static const dc_parser_vtable_t deepsix_parser_vtable = { NULL /* destroy */ }; +static const deepsix_excursion_layout_t deepsix_excursion_layout_v0 = { + 156,/* headersize */ + UNDEFINED, /* version */ + 4, /* divemode */ + 24, /* samplerate */ + UNDEFINED, /* salinity */ + 12, /* datetime */ + 20, /* divetime */ + 28, /* maxdepth */ + 32, /* temperature_min */ + UNDEFINED, /* avgdepth */ + 48, /* firmware */ + UNDEFINED, /* temperature_surf */ + 56, /* atmospheric */ + UNDEFINED, /* gf */ +}; + +static const deepsix_excursion_layout_t deepsix_excursion_layout_v1 = { + 129,/* headersize */ + 3, /* version */ + 4, /* divemode */ + 5, /* samplerate */ + 7, /* salinity */ + 12, /* datetime */ + 19, /* divetime */ + 29, /* maxdepth */ + 31, /* temperature_min */ + 33, /* avgdepth */ + 35, /* firmware */ + 43, /* temperature_surf */ + 45, /* atmospheric */ + 127, /* gf */ +}; + +static double +pressure_to_depth(unsigned int depth, unsigned int atmospheric, unsigned int density) +{ + return ((signed int)(depth - atmospheric)) * (BAR / 1000.0) / (density * GRAVITY); +} + +static unsigned int +deepsix_excursion_find_gasmix(deepsix_excursion_parser_t *parser, unsigned int o2, unsigned int he, unsigned int id) +{ + unsigned int i = 0; + while (i < parser->ngasmixes) { + if (o2 == parser->gasmix[i].oxygen && he == parser->gasmix[i].helium && id == parser->gasmix[i].id) + break; + i++; + } + return i; +} + dc_status_t deepsix_excursion_parser_create (dc_parser_t **out, dc_context_t *context) { @@ -82,6 +199,15 @@ deepsix_excursion_parser_create (dc_parser_t **out, dc_context_t *context) return DC_STATUS_NOMEMORY; } + // Set the default values. + parser->cached = 0; + parser->ngasmixes = 0; + for (unsigned int i = 0; i < MAX_GASMIXES; ++i) { + parser->gasmix[i].id = 0; + parser->gasmix[i].oxygen = 0; + parser->gasmix[i].helium = 0; + } + *out = (dc_parser_t *) parser; return DC_STATUS_SUCCESS; @@ -90,6 +216,17 @@ deepsix_excursion_parser_create (dc_parser_t **out, dc_context_t *context) static dc_status_t deepsix_excursion_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) { + deepsix_excursion_parser_t *parser = (deepsix_excursion_parser_t *) abstract; + + // Reset the cache. + parser->cached = 0; + parser->ngasmixes = 0; + for (unsigned int i = 0; i < MAX_GASMIXES; ++i) { + parser->gasmix[i].id = 0; + parser->gasmix[i].oxygen = 0; + parser->gasmix[i].helium = 0; + } + return DC_STATUS_SUCCESS; } @@ -99,23 +236,36 @@ deepsix_excursion_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dat const unsigned char *data = abstract->data; unsigned int size = abstract->size; - if (size < HEADERSIZE) + if (size < HEADERSIZE_MIN) return DC_STATUS_DATAFORMAT; - unsigned int firmware = array_uint16_be (data + 48 + 4); + unsigned int version = data[3]; + const deepsix_excursion_layout_t *layout = version == 0 ? + &deepsix_excursion_layout_v0 : &deepsix_excursion_layout_v1; + + if (size < layout->headersize) + return DC_STATUS_DATAFORMAT; + + unsigned int firmware = array_uint16_be (data + layout->firmware + 4); + + const unsigned char *p = data + layout->datetime; if (datetime) { - datetime->year = data[12] + 2000; - datetime->month = data[13]; - datetime->day = data[14]; - datetime->hour = data[15]; - datetime->minute = data[16]; - datetime->second = data[17]; + datetime->year = p[0] + 2000; + datetime->month = p[1]; + datetime->day = p[2]; + datetime->hour = p[3]; + datetime->minute = p[4]; + datetime->second = p[5]; - if (firmware >= FWVERSION(5, 'B')) { - datetime->timezone = (data[18] - 12) * 3600; + if (version == 0) { + if (firmware >= FWVERSION(5, 'B')) { + datetime->timezone = (p[6] - 12) * 3600; + } else { + datetime->timezone = DC_TIMEZONE_NONE; + } } else { - datetime->timezone = DC_TIMEZONE_NONE; + datetime->timezone = ((signed char) p[6]) * 900; } } @@ -125,37 +275,74 @@ deepsix_excursion_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dat static dc_status_t deepsix_excursion_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) { + deepsix_excursion_parser_t *parser = (deepsix_excursion_parser_t *) abstract; const unsigned char *data = abstract->data; unsigned int size = abstract->size; - if (size < HEADERSIZE) + dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + dc_salinity_t *water = (dc_salinity_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; + + if (size < HEADERSIZE_MIN) return DC_STATUS_DATAFORMAT; - unsigned int atmospheric = array_uint32_le(data + 56); + unsigned int version = data[3]; + const deepsix_excursion_layout_t *layout = version == 0 ? + &deepsix_excursion_layout_v0 : &deepsix_excursion_layout_v1; - dc_salinity_t *water = (dc_salinity_t *) value; + if (size < layout->headersize) + return DC_STATUS_DATAFORMAT; + + if (version != 0 && !parser->cached) { + dc_status_t rc = deepsix_excursion_parser_samples_foreach_v1(abstract, NULL, NULL); + if (rc != DC_STATUS_SUCCESS) + return rc; + } + + unsigned int atmospheric = array_uint16_le(data + layout->atmospheric); + unsigned int density = DENSITY; + if (layout->salinity != UNDEFINED) { + density = 1000 + data[layout->salinity] * 10; + } if (value) { switch (type) { case DC_FIELD_DIVETIME: - *((unsigned int *) value) = array_uint32_le(data + 20); + *((unsigned int *) value) = array_uint32_le(data + layout->divetime); break; case DC_FIELD_MAXDEPTH: - *((double *) value) = (signed int)(array_uint32_le(data + 28) - atmospheric) * (BAR / 1000.0) / (DENSITY * GRAVITY); + *((double *) value) = pressure_to_depth(array_uint16_le(data + layout->maxdepth), atmospheric, density); break; + case DC_FIELD_AVGDEPTH: + if (layout->avgdepth == UNDEFINED) + return DC_STATUS_UNSUPPORTED; + *((double *) value) = pressure_to_depth(array_uint16_le(data + layout->avgdepth), atmospheric, density); + break; + case DC_FIELD_GASMIX_COUNT: + *((unsigned int *) value) = parser->ngasmixes; + break; + case DC_FIELD_GASMIX: + gasmix->oxygen = parser->gasmix[flags].oxygen / 100.0; + gasmix->helium = parser->gasmix[flags].helium / 100.0; + gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; break; case DC_FIELD_TEMPERATURE_MINIMUM: - *((double *) value) = (signed int) array_uint32_le(data + 32) / 10.0; + *((double *) value) = (signed int) array_uint16_le(data + layout->temperature_min) / 10.0; + break; + case DC_FIELD_TEMPERATURE_SURFACE: + if (layout->temperature_surf == UNDEFINED) + return DC_STATUS_UNSUPPORTED; + *((double *) value) = (signed int) array_uint16_le(data + layout->temperature_surf) / 10.0; break; case DC_FIELD_ATMOSPHERIC: *((double *) value) = atmospheric / 1000.0; break; case DC_FIELD_SALINITY: - water->type = DC_WATER_SALT; - water->density = DENSITY; + water->type = (density == 1000) ? DC_WATER_FRESH : DC_WATER_SALT; + water->density = density; break; case DC_FIELD_DIVEMODE: - switch (array_uint32_le(data + 4)) { + switch (data[layout->divemode]) { case 0: *((dc_divemode_t *) value) = DC_DIVEMODE_OC; break; @@ -169,6 +356,17 @@ deepsix_excursion_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, return DC_STATUS_DATAFORMAT; } break; + case DC_FIELD_DECOMODEL: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->conservatism = 0; + if (layout->gf != UNDEFINED) { + decomodel->params.gf.low = data[layout->gf + 0]; + decomodel->params.gf.high = data[layout->gf + 1]; + } else { + decomodel->params.gf.low = 0; + decomodel->params.gf.high = 0; + } + break; default: return DC_STATUS_UNSUPPORTED; } @@ -178,23 +376,24 @@ deepsix_excursion_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, } static dc_status_t -deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +deepsix_excursion_parser_samples_foreach_v0 (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) { const unsigned char *data = abstract->data; unsigned int size = abstract->size; + const deepsix_excursion_layout_t *layout = &deepsix_excursion_layout_v0; - if (size < HEADERSIZE) + if (size < layout->headersize) return DC_STATUS_DATAFORMAT; - int firmware4c = memcmp(data + 48, "D01-4C", 6) == 0; + int firmware4c = memcmp(data + layout->firmware, "D01-4C", 6) == 0; unsigned int maxtype = firmware4c ? TEMPERATURE : CNS; - unsigned int interval = array_uint32_le(data + 24); - unsigned int atmospheric = array_uint32_le(data + 56); + unsigned int interval = array_uint32_le(data + layout->samplerate); + unsigned int atmospheric = array_uint32_le(data + layout->atmospheric); unsigned int time = 0; - unsigned int offset = HEADERSIZE; + unsigned int offset = layout->headersize; while (offset + 1 < size) { dc_sample_value_t sample = {0}; @@ -227,7 +426,7 @@ deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb sample.time = time; if (callback) callback(DC_SAMPLE_TIME, sample, userdata); - sample.depth = (signed int)(depth - atmospheric) * (BAR / 1000.0) / (DENSITY * GRAVITY); + sample.depth = pressure_to_depth(depth, atmospheric, DENSITY); if (callback) callback(DC_SAMPLE_DEPTH, sample, userdata); } @@ -263,3 +462,325 @@ deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb return DC_STATUS_SUCCESS; } + +static dc_status_t +deepsix_excursion_parser_samples_foreach_v1 (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +{ + deepsix_excursion_parser_t *parser = (deepsix_excursion_parser_t *) abstract; + const unsigned char *data = abstract->data; + unsigned int size = abstract->size; + const deepsix_excursion_layout_t *layout = &deepsix_excursion_layout_v1; + + if (size < layout->headersize) + return DC_STATUS_DATAFORMAT; + + unsigned int headersize = data[2]; + if (headersize < layout->headersize) + return DC_STATUS_DATAFORMAT; + + unsigned int samplerate = data[layout->samplerate]; + unsigned int atmospheric = array_uint16_le(data + layout->atmospheric); + unsigned int density = 1000 + data[layout->salinity] * 10; + + unsigned int offset = headersize; + if (offset + 1 > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + unsigned int nconfig = data[offset]; + if (nconfig > MAX_SAMPLES) { + ERROR(abstract->context, "Too many sample descriptors (%u).", nconfig); + return DC_STATUS_DATAFORMAT; + } + + offset += 1; + + if (offset + 3 * nconfig > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + deepsix_excursion_sample_info_t sample_info[MAX_SAMPLES] = {{0}}; + for (unsigned int i = 0; i < nconfig; i++) { + sample_info[i].type = data[offset + 3 * i + 0]; + sample_info[i].size = data[offset + 3 * i + 1]; + sample_info[i].divisor = data[offset + 3 * i + 2]; + + if (sample_info[i].divisor) { + switch (sample_info[i].type) { + case SAMPLE_CNS: + case SAMPLE_TEMPERATURE: + if (sample_info[i].size != 2) { + ERROR(abstract->context, "Unexpected sample size (%u).", sample_info[i].size); + return DC_STATUS_DATAFORMAT; + } + break; + case SAMPLE_DECO_NDL: + if (sample_info[i].size != 7) { + ERROR(abstract->context, "Unexpected sample size (%u).", sample_info[i].size); + return DC_STATUS_DATAFORMAT; + } + break; + default: + WARNING (abstract->context, "Unknown sample descriptor (%u %u %u).", + sample_info[i].type, sample_info[i].size, sample_info[i].divisor); + break; + } + } + } + + offset += 3 * nconfig; + + if (offset + 1 > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + unsigned int nevents = data[offset]; + if (nevents > MAX_EVENTS) { + ERROR(abstract->context, "Too many event descriptors (%u).", nevents); + return DC_STATUS_DATAFORMAT; + } + + offset += 1; + + if (offset + 2 * nevents > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + deepsix_excursion_event_info_t event_info[MAX_EVENTS] = {{0}}; + for (unsigned int i = 0; i < nevents; i++) { + event_info[i].type = data[offset + 2 * i]; + event_info[i].size = data[offset + 2 * i + 1]; + + switch (event_info[i].type) { + case EVENT_ALARMS: + if (event_info[i].size != 1) { + ERROR(abstract->context, "Unexpected event size (%u).", event_info[i].size); + return DC_STATUS_DATAFORMAT; + } + break; + case EVENT_CHANGE_GAS: + if (event_info[i].size != 3) { + ERROR(abstract->context, "Unexpected event size (%u).", event_info[i].size); + return DC_STATUS_DATAFORMAT; + } + break; + case EVENT_SAMPLES_MISSED: + if (event_info[i].size != 6) { + ERROR(abstract->context, "Unexpected event size (%u).", event_info[i].size); + return DC_STATUS_DATAFORMAT; + } + break; + default: + WARNING (abstract->context, "Unknown event descriptor (%u %u).", + event_info[i].type, event_info[i].size); + break; + } + } + + offset += 2 * nevents; + + unsigned int time = 0; + unsigned int nsamples = 0; + while (offset + 3 <= size) { + dc_sample_value_t sample = {0}; + nsamples++; + + // Time (seconds). + time += samplerate; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + + unsigned int depth = array_uint16_le (data + offset); + sample.depth = pressure_to_depth(depth, atmospheric, density); + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + offset += 2; + + // event info + unsigned int length = data[offset]; + offset += 1; + + if (offset + length > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + if (length) { + if (length < 2) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + unsigned int events = array_uint16_le (data + offset); + unsigned int event_offset = 2; + + for (unsigned int i = 0; i < nevents; i++) { + if ((events & (1 << event_info[i].type)) == 0) + continue; + + if (event_offset + event_info[i].size > length) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + unsigned int alarms = 0; + unsigned int id = 0, o2 = 0, he = 0; + unsigned int mix_idx = 0; + unsigned int count = 0, timestamp = 0; + switch (event_info[i].type) { + case EVENT_ALARMS: + alarms = data[offset + event_offset]; + for (unsigned int v = alarms, j = 0; v; v >>= 1, ++j) { + if ((v & 1) == 0) + continue; + + sample.event.type = SAMPLE_EVENT_NONE; + sample.event.time = 0; + sample.event.flags = 0; + sample.event.value = 0; + switch (j) { + case ALARM_ASCENTRATE: + sample.event.type = SAMPLE_EVENT_ASCENT; + break; + case ALARM_CEILING: + sample.event.type = SAMPLE_EVENT_CEILING; + break; + case ALARM_PO2: + sample.event.type = SAMPLE_EVENT_PO2; + break; + case ALARM_MAXDEPTH: + sample.event.type = SAMPLE_EVENT_MAXDEPTH; + break; + case ALARM_DIVETIME: + sample.event.type = SAMPLE_EVENT_DIVETIME; + break; + case ALARM_CNS: + break; + default: + WARNING (abstract->context, "Unknown event (%u).", j); + break; + } + if (sample.event.type != SAMPLE_EVENT_NONE) { + if (callback) callback (DC_SAMPLE_EVENT, sample, userdata); + } + } + break; + case EVENT_CHANGE_GAS: + id = data[offset + event_offset]; + o2 = data[offset + event_offset + 1]; + he = data[offset + event_offset + 2]; + + mix_idx = deepsix_excursion_find_gasmix(parser, o2, he, id); + if (mix_idx >= parser->ngasmixes) { + if (mix_idx >= MAX_GASMIXES) { + ERROR (abstract->context, "Maximum number of gas mixes reached."); + return DC_STATUS_NOMEMORY; + } + parser->gasmix[mix_idx].oxygen = o2; + parser->gasmix[mix_idx].helium = he; + parser->gasmix[mix_idx].id = id; + parser->ngasmixes = mix_idx + 1; + } + + sample.gasmix = mix_idx; + if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata); + break; + case EVENT_SAMPLES_MISSED: + count = array_uint16_le(data + offset + event_offset); + timestamp = array_uint32_le(data + offset + event_offset + 2); + if (timestamp < time) { + ERROR (abstract->context, "Timestamp moved backwards (%u %u).", timestamp, time); + return DC_STATUS_DATAFORMAT; + } + nsamples += count; + time = timestamp; + break; + default: + WARNING (abstract->context, "Unknown event (%u %u).", + event_info[i].type, event_info[i].size); + break; + } + event_offset += event_info[i].size; + } + + // Skip remaining sample bytes (if any). + if (event_offset < length) { + WARNING (abstract->context, "Remaining %u bytes skipped.", length - event_offset); + } + offset += length; + } + + for (unsigned int i = 0; i < nconfig; ++i) { + if (sample_info[i].divisor && (nsamples % sample_info[i].divisor) == 0) { + if (offset + sample_info[i].size > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + unsigned int value = 0; + unsigned int deco_flags = 0, deco_ndl_tts = 0; + unsigned int deco_depth = 0, deco_time = 0; + switch (sample_info[i].type) { + case SAMPLE_TEMPERATURE: + value = array_uint16_le(data + offset); + sample.temperature = value / 10.0; + if (callback) callback(DC_SAMPLE_TEMPERATURE, sample, userdata); + break; + case SAMPLE_CNS: + value = array_uint16_le(data + offset); + sample.cns = value / 10000.0; + if (callback) callback (DC_SAMPLE_CNS, sample, userdata); + break; + case SAMPLE_DECO_NDL: + deco_flags = data[offset]; + deco_ndl_tts = array_uint16_le(data + offset + 1); + deco_depth = array_uint16_le(data + offset + 3); + deco_time = array_uint16_le(data + offset + 5); + if (deco_flags & DECOSTOP) { + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.depth = pressure_to_depth(deco_depth, atmospheric, density); + sample.deco.time = deco_time; + } else if (deco_flags & SAFETYSTOP) { + sample.deco.type = DC_DECO_SAFETYSTOP; + sample.deco.depth = pressure_to_depth(deco_depth, atmospheric, density); + sample.deco.time = deco_time; + } else { + sample.deco.type = DC_DECO_NDL; + sample.deco.depth = 0; + sample.deco.time = deco_ndl_tts; + } + if (callback) callback (DC_SAMPLE_DECO, sample, userdata); + break; + default: + break; + } + offset += sample_info[i].size; + } + } + } + + parser->cached = 1; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepsix_excursion_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; + + if (size < HEADERSIZE_MIN) + return DC_STATUS_DATAFORMAT; + + unsigned int version = data[3]; + + if (version == 0) { + return deepsix_excursion_parser_samples_foreach_v0(abstract, callback, userdata); + } else { + return deepsix_excursion_parser_samples_foreach_v1(abstract, callback, userdata); + } +} From 9787bb7ac9e446475be1833db7c8d68391331762 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 2 Feb 2023 18:52:35 +0100 Subject: [PATCH 53/58] Always include all gas mixes defined in the header Especially among technical divers, it's not uncommon to carry spare tanks that will only be used in emergency situations (for example a rebreather with one or more bailout tanks). Since those gas mixes are not used throughout the dive, they were also not reported to the application. Fixed by reporting all configured gas mixes. Applications can still obtain the previous result after manually inspecting the gas switch events in the samples and filtering out the unused gas mixes. This partially reverts commit c8b166dadbf961e17a9bd1cc28db3d92832ddf72. --- src/shearwater_predator_parser.c | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index d0cccb3..1ac89aa 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -90,7 +90,8 @@ #define METRIC 0 #define IMPERIAL 1 -#define NGASMIXES 10 +#define NGASMIXES 20 +#define NFIXED 10 #define NTANKS 6 #define NRECORDS 8 @@ -421,11 +422,17 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) unsigned int divemode = M_OC_TEC; // Get the gas mixes. - unsigned int ngasmixes = 0; + unsigned int ngasmixes = NFIXED; shearwater_predator_gasmix_t gasmix[NGASMIXES] = {0}; shearwater_predator_tank_t tank[NTANKS] = {0}; unsigned int o2_previous = 0, he_previous = 0; unsigned int aimode = AI_OFF; + if (!pnf) { + for (unsigned int i = 0; i < NFIXED; ++i) { + gasmix[i].oxygen = data[20 + i]; + gasmix[i].helium = data[30 + i]; + } + } unsigned int offset = headersize; unsigned int length = size - footersize; @@ -540,7 +547,18 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) // Opening record parser->opening[type - LOG_RECORD_OPENING_0] = offset; - if (type == LOG_RECORD_OPENING_4) { + if (type == LOG_RECORD_OPENING_0) { + for (unsigned int i = 0; i < NFIXED; ++i) { + gasmix[i].oxygen = data[offset + 20 + i]; + } + for (unsigned int i = 0; i < 2; ++i) { + gasmix[i].helium = data[offset + 30 + i]; + } + } else if (type == LOG_RECORD_OPENING_1) { + for (unsigned int i = 2; i < NFIXED; ++i) { + gasmix[i].helium = data[offset + 1 + i - 2]; + } + } else if (type == LOG_RECORD_OPENING_4) { // Log version logversion = data[offset + 16]; @@ -678,9 +696,12 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) parser->logversion = logversion; parser->headersize = headersize; parser->footersize = footersize; - parser->ngasmixes = ngasmixes; + parser->ngasmixes = 0; for (unsigned int i = 0; i < ngasmixes; ++i) { - parser->gasmix[i] = gasmix[i]; + if (gasmix[i].oxygen == 0 && gasmix[i].helium == 0) + continue; + parser->gasmix[parser->ngasmixes] = gasmix[i]; + parser->ngasmixes++; } parser->ntanks = 0; for (unsigned int i = 0; i < NTANKS; ++i) { From 5fd93175331d780e3d2a2ca232bd483f8e374593 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 15 Feb 2023 19:34:40 +0100 Subject: [PATCH 54/58] Ignore all gas mixes for freedives For freedives it makes no sense to report any gas mixes. The freedives also use a different sample format, which doesn't generate any gas change events. --- src/shearwater_predator_parser.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index 1ac89aa..e087558 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -697,11 +697,13 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) parser->headersize = headersize; parser->footersize = footersize; parser->ngasmixes = 0; - for (unsigned int i = 0; i < ngasmixes; ++i) { - if (gasmix[i].oxygen == 0 && gasmix[i].helium == 0) - continue; - parser->gasmix[parser->ngasmixes] = gasmix[i]; - parser->ngasmixes++; + if (divemode != M_FREEDIVE) { + for (unsigned int i = 0; i < ngasmixes; ++i) { + if (gasmix[i].oxygen == 0 && gasmix[i].helium == 0) + continue; + parser->gasmix[parser->ngasmixes] = gasmix[i]; + parser->ngasmixes++; + } } parser->ntanks = 0; for (unsigned int i = 0; i < NTANKS; ++i) { From 98c7887e9c1cab2dc57b6ace8add5d5916fdff08 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 16 Feb 2023 19:56:40 +0100 Subject: [PATCH 55/58] Handle dives without a valid gas mix more explicit Dives without a valid gas mix in the sample data (e.g. both the O2 and He set to zero) are currently ignored by accident. Because the o2_previous and he_previous variables were initialized to zero, those invalid gas mixes were not processed. Add an explicit check for such gas mixes to make this more obvious. --- src/shearwater_predator_parser.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index e087558..9432603 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -425,7 +425,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) unsigned int ngasmixes = NFIXED; shearwater_predator_gasmix_t gasmix[NGASMIXES] = {0}; shearwater_predator_tank_t tank[NTANKS] = {0}; - unsigned int o2_previous = 0, he_previous = 0; + unsigned int o2_previous = UNDEFINED, he_previous = UNDEFINED; unsigned int aimode = AI_OFF; if (!pnf) { for (unsigned int i = 0; i < NFIXED; ++i) { @@ -456,7 +456,8 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) // Gaschange. unsigned int o2 = data[offset + 7 + pnf]; unsigned int he = data[offset + 8 + pnf]; - if (o2 != o2_previous || he != he_previous) { + if ((o2 != o2_previous || he != he_previous) && + (o2 != 0 || he != 0)) { // Find the gasmix in the list. unsigned int idx = 0; while (idx < ngasmixes) { @@ -858,7 +859,7 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal return rc; // Previous gas mix. - unsigned int o2_previous = 0, he_previous = 0; + unsigned int o2_previous = UNDEFINED, he_previous = UNDEFINED; // Sample interval. unsigned int time = 0; @@ -960,7 +961,8 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal // Gaschange. unsigned int o2 = data[offset + pnf + 7]; unsigned int he = data[offset + pnf + 8]; - if (o2 != o2_previous || he != he_previous) { + if ((o2 != o2_previous || he != he_previous) && + (o2 != 0 || he != 0)) { unsigned int idx = shearwater_predator_find_gasmix (parser, o2, he); if (idx >= parser->ngasmixes) { ERROR (abstract->context, "Invalid gas mix."); From 328812e95bfe7c6c9d2a8d36c75144f05c7dc9dc Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 15 Feb 2023 21:07:02 +0100 Subject: [PATCH 56/58] Limit the index to the fixed gas mixes The index in the gas change event should refer to the one of the fixed gas mixes. All gas mixes with a higher index are either manual or bailout gas mixes, and are reported with different events containing an O2 and He percentages instead. --- src/hw_ostc_parser.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index d745ef8..7d75514 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -902,8 +902,8 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call return DC_STATUS_DATAFORMAT; } unsigned int idx = data[offset]; - if (idx < 1 || idx > parser->ngasmixes) { - ERROR(abstract->context, "Invalid gas mix."); + if (idx < 1 || idx > parser->nfixed) { + ERROR(abstract->context, "Invalid gas mix (%u).", idx); return DC_STATUS_DATAFORMAT; } idx--; /* Convert to a zero based index. */ From ee78d6f65beee655caff14b2a86d17e4ad1810d3 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 15 Feb 2023 21:53:02 +0100 Subject: [PATCH 57/58] Parse some extra gas mix information Keep track of the gas mix type, and whether the gas mix is enabled or not. Right now this extra information isn't really used for anything yet, but it's available for future use. --- src/hw_ostc_parser.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index 7d75514..0210937 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -110,6 +110,8 @@ typedef struct hw_ostc_layout_t { typedef struct hw_ostc_gasmix_t { unsigned int oxygen; unsigned int helium; + unsigned int type; + unsigned int enabled; } hw_ostc_gasmix_t; typedef struct hw_ostc_parser_t { @@ -277,12 +279,16 @@ hw_ostc_parser_cache (hw_ostc_parser_t *parser) for (unsigned int i = 0; i < ngasmixes; ++i) { gasmix[i].oxygen = data[25 + 2 * i]; gasmix[i].helium = 0; + gasmix[i].type = 0; + gasmix[i].enabled = 1; } } else if (version == 0x23 || version == 0x24) { ngasmixes = 5; for (unsigned int i = 0; i < ngasmixes; ++i) { gasmix[i].oxygen = data[28 + 4 * i + 0]; gasmix[i].helium = data[28 + 4 * i + 1]; + gasmix[i].type = data[28 + 4 * i + 3]; + gasmix[i].enabled = gasmix[i].type != 0; // Find the first gas marked as the initial gas. if (initial == UNDEFINED && data[28 + 4 * i + 3] == 1) { initial = i + 1; /* One based index! */ @@ -302,6 +308,12 @@ hw_ostc_parser_cache (hw_ostc_parser_t *parser) for (unsigned int i = 0; i < ngasmixes; ++i) { gasmix[i].oxygen = data[19 + 2 * i + 0]; gasmix[i].helium = data[19 + 2 * i + 1]; + gasmix[i].type = 0; + if (version == 0x21) { + gasmix[i].enabled = data[53] & (1 << i); + } else { + gasmix[i].enabled = 1; + } } } if (initial != UNDEFINED) { @@ -361,6 +373,8 @@ hw_ostc_parser_create_internal (dc_parser_t **out, dc_context_t *context, unsign for (unsigned int i = 0; i < NGASMIXES; ++i) { parser->gasmix[i].oxygen = 0; parser->gasmix[i].helium = 0; + parser->gasmix[i].type = 0; + parser->gasmix[i].enabled = 0; } *out = (dc_parser_t *) parser; @@ -399,6 +413,8 @@ hw_ostc_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsig for (unsigned int i = 0; i < NGASMIXES; ++i) { parser->gasmix[i].oxygen = 0; parser->gasmix[i].helium = 0; + parser->gasmix[i].type = 0; + parser->gasmix[i].enabled = 0; } return DC_STATUS_SUCCESS; @@ -886,6 +902,8 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call } parser->gasmix[idx].oxygen = o2; parser->gasmix[idx].helium = he; + parser->gasmix[idx].type = 0; + parser->gasmix[idx].enabled = 1; parser->ngasmixes = idx + 1; } @@ -944,6 +962,8 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call } parser->gasmix[idx].oxygen = o2; parser->gasmix[idx].helium = he; + parser->gasmix[idx].type = 0; + parser->gasmix[idx].enabled = 1; parser->ngasmixes = idx + 1; } @@ -1074,6 +1094,8 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call } parser->gasmix[idx].oxygen = o2; parser->gasmix[idx].helium = he; + parser->gasmix[idx].type = 0; + parser->gasmix[idx].enabled = 1; parser->ngasmixes = idx + 1; } From 255a2dbb9aa804d84c36a855fa92d93d72c04578 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 2 Feb 2023 20:12:35 +0100 Subject: [PATCH 58/58] Keep open-circuit and diluent gas mixes separately The OSTC stores either the OC gas mixes or the CCR diluents depending on the dive mode. For CCR dives, there is also bailout to an OC gas possible, and those gas mixes are added dynamically to the manual gas mixes. The Shearwater dive computers store both the configured OC gas mixes and CCR diluents in the header. In both cases, the gas change events should reference the correct type of gas mix. This patch takes care of that. --- src/hw_ostc_parser.c | 47 ++++++++++++++++++++++++++++---- src/shearwater_predator_parser.c | 30 +++++++++++++------- 2 files changed, 61 insertions(+), 16 deletions(-) diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index 0210937..aff1b8c 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -112,6 +112,7 @@ typedef struct hw_ostc_gasmix_t { unsigned int helium; unsigned int type; unsigned int enabled; + unsigned int diluent; } hw_ostc_gasmix_t; typedef struct hw_ostc_parser_t { @@ -195,7 +196,7 @@ static const hw_ostc_layout_t hw_ostc_layout_ostc3 = { }; static unsigned int -hw_ostc_find_gasmix (hw_ostc_parser_t *parser, unsigned int o2, unsigned int he, unsigned int type) +hw_ostc_find_gasmix (hw_ostc_parser_t *parser, unsigned int o2, unsigned int he, unsigned int dil, unsigned int type) { unsigned int offset = 0; unsigned int count = parser->ngasmixes; @@ -207,7 +208,7 @@ hw_ostc_find_gasmix (hw_ostc_parser_t *parser, unsigned int o2, unsigned int he, unsigned int i = offset; while (i < count) { - if (o2 == parser->gasmix[i].oxygen && he == parser->gasmix[i].helium) + if (o2 == parser->gasmix[i].oxygen && he == parser->gasmix[i].helium && dil == parser->gasmix[i].diluent) break; i++; } @@ -215,6 +216,18 @@ hw_ostc_find_gasmix (hw_ostc_parser_t *parser, unsigned int o2, unsigned int he, return i; } +static unsigned int +hw_ostc_is_ccr (unsigned int divemode, unsigned int version) +{ + if (version == 0x21) { + return divemode == OSTC_ZHL16_CC || divemode == OSTC_ZHL16_CC_GF || divemode == OSTC_PSCR_GF; + } else if (version == 0x23 || version == 0x24) { + return divemode == OSTC3_CC || divemode == OSTC3_PSCR; + } else { + return 0; + } +} + static dc_status_t hw_ostc_parser_cache (hw_ostc_parser_t *parser) { @@ -263,6 +276,13 @@ hw_ostc_parser_cache (hw_ostc_parser_t *parser) return DC_STATUS_DATAFORMAT; } + // Get the dive mode. + unsigned int divemode = layout->divemode < header ? + data[layout->divemode] : UNDEFINED; + + // Get the CCR mode. + unsigned int ccr = hw_ostc_is_ccr (divemode, version); + // Get all the gas mixes, the index of the inital mix, // the initial setpoint (used in the fixed setpoint CCR mode), // and the initial CNS from the header @@ -281,6 +301,7 @@ hw_ostc_parser_cache (hw_ostc_parser_t *parser) gasmix[i].helium = 0; gasmix[i].type = 0; gasmix[i].enabled = 1; + gasmix[i].diluent = 0; } } else if (version == 0x23 || version == 0x24) { ngasmixes = 5; @@ -289,13 +310,14 @@ hw_ostc_parser_cache (hw_ostc_parser_t *parser) gasmix[i].helium = data[28 + 4 * i + 1]; gasmix[i].type = data[28 + 4 * i + 3]; gasmix[i].enabled = gasmix[i].type != 0; + gasmix[i].diluent = ccr; // Find the first gas marked as the initial gas. if (initial == UNDEFINED && data[28 + 4 * i + 3] == 1) { initial = i + 1; /* One based index! */ } } // The first fixed setpoint is the initial setpoint in CCR mode. - if (data[layout->divemode] == OSTC3_CC || data[layout->divemode] == OSTC3_PSCR) { + if (ccr) { initial_setpoint = data[60]; } // Initial CNS @@ -314,6 +336,7 @@ hw_ostc_parser_cache (hw_ostc_parser_t *parser) } else { gasmix[i].enabled = 1; } + gasmix[i].diluent = ccr; } } if (initial != UNDEFINED) { @@ -375,6 +398,7 @@ hw_ostc_parser_create_internal (dc_parser_t **out, dc_context_t *context, unsign parser->gasmix[i].helium = 0; parser->gasmix[i].type = 0; parser->gasmix[i].enabled = 0; + parser->gasmix[i].diluent = 0; } *out = (dc_parser_t *) parser; @@ -415,6 +439,7 @@ hw_ostc_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsig parser->gasmix[i].helium = 0; parser->gasmix[i].type = 0; parser->gasmix[i].enabled = 0; + parser->gasmix[i].diluent = 0; } return DC_STATUS_SUCCESS; @@ -787,6 +812,13 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call firmware = array_uint16_be (data + layout->firmware); } + // Get the dive mode. + unsigned int divemode = layout->divemode < header ? + data[layout->divemode] : UNDEFINED; + + // Get the CCR mode. + unsigned int ccr = hw_ostc_is_ccr (divemode, version); + unsigned int time = 0; unsigned int nsamples = 0; unsigned int tank = parser->initial != UNDEFINED ? parser->initial : 0; @@ -894,7 +926,7 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call } unsigned int o2 = data[offset]; unsigned int he = data[offset + 1]; - unsigned int idx = hw_ostc_find_gasmix (parser, o2, he, MANUAL); + unsigned int idx = hw_ostc_find_gasmix (parser, o2, he, ccr, MANUAL); if (idx >= parser->ngasmixes) { if (idx >= NGASMIXES) { ERROR (abstract->context, "Maximum number of gas mixes reached."); @@ -904,6 +936,7 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call parser->gasmix[idx].helium = he; parser->gasmix[idx].type = 0; parser->gasmix[idx].enabled = 1; + parser->gasmix[idx].diluent = ccr; parser->ngasmixes = idx + 1; } @@ -954,7 +987,7 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call unsigned int o2 = data[offset]; unsigned int he = data[offset + 1]; - unsigned int idx = hw_ostc_find_gasmix (parser, o2, he, MANUAL); + unsigned int idx = hw_ostc_find_gasmix (parser, o2, he, 0, MANUAL); if (idx >= parser->ngasmixes) { if (idx >= NGASMIXES) { ERROR (abstract->context, "Maximum number of gas mixes reached."); @@ -964,6 +997,7 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call parser->gasmix[idx].helium = he; parser->gasmix[idx].type = 0; parser->gasmix[idx].enabled = 1; + parser->gasmix[idx].diluent = 0; parser->ngasmixes = idx + 1; } @@ -1086,7 +1120,7 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call unsigned int o2 = data[offset]; unsigned int he = data[offset + 1]; - unsigned int idx = hw_ostc_find_gasmix (parser, o2, he, MANUAL); + unsigned int idx = hw_ostc_find_gasmix (parser, o2, he, 0, MANUAL); if (idx >= parser->ngasmixes) { if (idx >= NGASMIXES) { ERROR (abstract->context, "Maximum number of gas mixes reached."); @@ -1096,6 +1130,7 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call parser->gasmix[idx].helium = he; parser->gasmix[idx].type = 0; parser->gasmix[idx].enabled = 1; + parser->gasmix[idx].diluent = 0; parser->ngasmixes = idx + 1; } diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index 9432603..06a0882 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -106,6 +106,7 @@ typedef struct shearwater_predator_parser_t shearwater_predator_parser_t; typedef struct shearwater_predator_gasmix_t { unsigned int oxygen; unsigned int helium; + unsigned int diluent; } shearwater_predator_gasmix_t; typedef struct shearwater_predator_tank_t { @@ -182,11 +183,11 @@ static const dc_parser_vtable_t shearwater_petrel_parser_vtable = { static unsigned int -shearwater_predator_find_gasmix (shearwater_predator_parser_t *parser, unsigned int o2, unsigned int he) +shearwater_predator_find_gasmix (shearwater_predator_parser_t *parser, unsigned int o2, unsigned int he, unsigned int dil) { unsigned int i = 0; while (i < parser->ngasmixes) { - if (o2 == parser->gasmix[i].oxygen && he == parser->gasmix[i].helium) + if (o2 == parser->gasmix[i].oxygen && he == parser->gasmix[i].helium && dil == parser->gasmix[i].diluent) break; i++; } @@ -238,6 +239,7 @@ shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsig for (unsigned int i = 0; i < NGASMIXES; ++i) { parser->gasmix[i].oxygen = 0; parser->gasmix[i].helium = 0; + parser->gasmix[i].diluent = 0; } parser->ntanks = 0; for (unsigned int i = 0; i < NTANKS; ++i) { @@ -301,6 +303,7 @@ shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char for (unsigned int i = 0; i < NGASMIXES; ++i) { parser->gasmix[i].oxygen = 0; parser->gasmix[i].helium = 0; + parser->gasmix[i].diluent = 0; } parser->ntanks = 0; for (unsigned int i = 0; i < NTANKS; ++i) { @@ -425,12 +428,13 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) unsigned int ngasmixes = NFIXED; shearwater_predator_gasmix_t gasmix[NGASMIXES] = {0}; shearwater_predator_tank_t tank[NTANKS] = {0}; - unsigned int o2_previous = UNDEFINED, he_previous = UNDEFINED; + unsigned int o2_previous = UNDEFINED, he_previous = UNDEFINED, dil_previous = UNDEFINED; unsigned int aimode = AI_OFF; if (!pnf) { for (unsigned int i = 0; i < NFIXED; ++i) { gasmix[i].oxygen = data[20 + i]; gasmix[i].helium = data[30 + i]; + gasmix[i].diluent = i >= 5; } } @@ -449,19 +453,20 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) if (type == LOG_RECORD_DIVE_SAMPLE) { // Status flags. unsigned int status = data[offset + 11 + pnf]; - if ((status & OC) == 0) { + unsigned int ccr = (status & OC) == 0; + if (ccr) { divemode = status & SC ? M_SC : M_CC; } // Gaschange. unsigned int o2 = data[offset + 7 + pnf]; unsigned int he = data[offset + 8 + pnf]; - if ((o2 != o2_previous || he != he_previous) && + if ((o2 != o2_previous || he != he_previous || ccr != dil_previous) && (o2 != 0 || he != 0)) { // Find the gasmix in the list. unsigned int idx = 0; while (idx < ngasmixes) { - if (o2 == gasmix[idx].oxygen && he == gasmix[idx].helium) + if (o2 == gasmix[idx].oxygen && he == gasmix[idx].helium && ccr == gasmix[idx].diluent) break; idx++; } @@ -474,11 +479,13 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) } gasmix[idx].oxygen = o2; gasmix[idx].helium = he; + gasmix[idx].diluent = ccr; ngasmixes = idx + 1; } o2_previous = o2; he_previous = he; + dil_previous = ccr; } // Tank pressure @@ -551,6 +558,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) if (type == LOG_RECORD_OPENING_0) { for (unsigned int i = 0; i < NFIXED; ++i) { gasmix[i].oxygen = data[offset + 20 + i]; + gasmix[i].diluent = i >= 5; } for (unsigned int i = 0; i < 2; ++i) { gasmix[i].helium = data[offset + 30 + i]; @@ -859,7 +867,7 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal return rc; // Previous gas mix. - unsigned int o2_previous = UNDEFINED, he_previous = UNDEFINED; + unsigned int o2_previous = UNDEFINED, he_previous = UNDEFINED, dil_previous = UNDEFINED; // Sample interval. unsigned int time = 0; @@ -919,8 +927,9 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal // Status flags. unsigned int status = data[offset + pnf + 11]; + unsigned int ccr = (status & OC) == 0; - if ((status & OC) == 0) { + if (ccr) { // PPO2 if ((status & PPO2_EXTERNAL) == 0) { #ifdef SENSOR_AVERAGE @@ -961,9 +970,9 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal // Gaschange. unsigned int o2 = data[offset + pnf + 7]; unsigned int he = data[offset + pnf + 8]; - if ((o2 != o2_previous || he != he_previous) && + if ((o2 != o2_previous || he != he_previous || ccr != dil_previous) && (o2 != 0 || he != 0)) { - unsigned int idx = shearwater_predator_find_gasmix (parser, o2, he); + unsigned int idx = shearwater_predator_find_gasmix (parser, o2, he, ccr); if (idx >= parser->ngasmixes) { ERROR (abstract->context, "Invalid gas mix."); return DC_STATUS_DATAFORMAT; @@ -973,6 +982,7 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal if (callback) callback (DC_SAMPLE_GASMIX, sample, userdata); o2_previous = o2; he_previous = he; + dil_previous = ccr; } // Deco stop / NDL.