From 8254edb110d95d98e81e4a13cb5361816d68981f Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 7 May 2026 17:57:28 +0200 Subject: [PATCH 01/18] Ignore all invalid end-of-profile pointer values The end-of-profile pointer can be stored in two different locations (possibly as a simple wear leveling mechanism). Each location is expected to contain either a valid value or 0xffffffff to indicate the alternative location should be used. In a bugreport, a Nemo Wide 2 device appears to have some kind of data corruption issue, where 0x7f bytes are present in the invalid value instead of the expected 0xff bytes. As a workaround, ignore all out of range values. --- src/mares_iconhd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index 159264c7..26fd6d75 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -870,7 +870,7 @@ mares_iconhd_device_foreach_raw (dc_device_t *abstract, dc_dive_callback_t callb device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); eop = array_uint32_le (pointer); - if (eop != 0xFFFFFFFF) + if (eop >= layout->rb_profile_begin && eop < layout->rb_profile_end) break; } if (eop < layout->rb_profile_begin || eop >= layout->rb_profile_end) { From cc9e75630f5afd5b37212d8e1ec3be5b9e1b182d Mon Sep 17 00:00:00 2001 From: Simone Carletti Date: Sun, 17 May 2026 09:45:20 +0200 Subject: [PATCH 02/18] Add support for the Mares Sirius L The Sirius L uses the Genius/Sirius variant of the protocol, with a new model number (0x33) and bluetooth device name. The model number was confirmed against a factory-new unit on firmware 01.00.01: the version packet reports the product name "Sirius L" at offset 0x46, and the OBJ_DEVICE_MODEL response returns 0x33. --- src/descriptor.c | 2 ++ src/mares_iconhd.c | 5 +++++ src/mares_iconhd_parser.c | 2 ++ 3 files changed, 9 insertions(+) diff --git a/src/descriptor.c b/src/descriptor.c index fe933ef5..0e3a03c1 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -319,6 +319,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Mares", "Sirius", DC_FAMILY_MARES_ICONHD , 0x2F, DC_TRANSPORT_BLE, dc_filter_mares}, {"Mares", "Quad Ci", DC_FAMILY_MARES_ICONHD , 0x31, DC_TRANSPORT_BLE, dc_filter_mares}, {"Mares", "Quad 2", DC_FAMILY_MARES_ICONHD , 0x32, DC_TRANSPORT_BLE, dc_filter_mares}, + {"Mares", "Sirius L", DC_FAMILY_MARES_ICONHD , 0x33, DC_TRANSPORT_BLE, dc_filter_mares}, {"Mares", "Puck 4", DC_FAMILY_MARES_ICONHD , 0x35, DC_TRANSPORT_BLE, dc_filter_mares}, {"Mares", "Puck Lite", DC_FAMILY_MARES_ICONHD , 0x35, DC_TRANSPORT_BLE, dc_filter_mares}, {"Mares", "Puck Pro EZ", DC_FAMILY_MARES_ICONHD , 0x35, DC_TRANSPORT_BLE, dc_filter_mares}, @@ -776,6 +777,7 @@ dc_filter_mares (const dc_descriptor_t *descriptor, dc_transport_t transport, co "Mares bluelink pro", "Mares Genius", "Sirius", + "Sirius L", "Quad Ci", "Quad2", "Puck4", diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index 26fd6d75..b700c80f 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -52,6 +52,7 @@ #define SIRIUS 0x2F #define QUADCI 0x31 #define QUAD2 0x32 +#define SIRIUS_L 0x33 #define PUCK4 0x35 #define ISSMART(model) ( \ @@ -66,6 +67,7 @@ (model) == SIRIUS || \ (model) == QUADCI || \ (model) == QUAD2 || \ + (model) == SIRIUS_L || \ (model) == PUCK4) #define ISSIRIUS(model) ( \ @@ -73,6 +75,7 @@ (model) == SIRIUS || \ (model) == QUADCI || \ (model) == QUAD2 || \ + (model) == SIRIUS_L || \ (model) == PUCK4) #define MAXRETRIES 4 @@ -199,6 +202,7 @@ mares_iconhd_get_model (mares_iconhd_device_t *device) {"Horizon", HORIZON}, {"Puck Air 2", PUCKAIR2}, {"Sirius", SIRIUS}, + {"Sirius L", SIRIUS_L}, {"Quad Ci", QUADCI}, {"Quad2", QUAD2}, {"Puck4", PUCK4}, @@ -675,6 +679,7 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_ case SIRIUS: case QUADCI: case QUAD2: + case SIRIUS_L: case PUCK4: device->layout = &mares_genius_layout; device->packetsize = 4096; diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index 4066e582..12c36d9d 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -50,6 +50,7 @@ #define SIRIUS 0x2F #define QUADCI 0x31 #define QUAD2 0x32 +#define SIRIUS_L 0x33 #define PUCK4 0x35 #define ISSMART(model) ( \ @@ -64,6 +65,7 @@ (model) == SIRIUS || \ (model) == QUADCI || \ (model) == QUAD2 || \ + (model) == SIRIUS_L || \ (model) == PUCK4) #define NGASMIXES_ICONHD 3 From 26863206f07b4af4cd9110349adf7d59f8dbd0b7 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sat, 16 May 2026 15:12:56 +0200 Subject: [PATCH 03/18] Use the GNSS status to ignore invalid locations The GNSS status field indicates whether the longitude and latitude fields contain valid location data. This is more reliable than checking for the magic values zero and 0xFFFFFFFF, which are technically valid locations (although also very unlikely dive locations). --- src/shearwater_predator_parser.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index 8a8e4538..80de9396 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -107,6 +107,13 @@ #define PETREL 3 #define TERIC 8 +#define GNSS_NOSAT 0 +#define GNSS_NOFIX 1 +#define GNSS_FIX_2D 2 +#define GNSS_FIX_3D 3 +#define GNSS_UNKNOWN 0xFF +#define GNSS_DISABLED 0x10 + #define UNDEFINED 0xFFFFFFFF typedef struct shearwater_predator_parser_t shearwater_predator_parser_t; @@ -758,6 +765,7 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ unsigned int decomodel_idx = parser->pnf ? parser->opening[2] + 18 : 67; unsigned int gf_idx = parser->pnf ? parser->opening[0] + 4 : 4; int latitude = 0, longitude = 0; + unsigned int gnss = 0; dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_tank_t *tank = (dc_tank_t *) value; @@ -873,12 +881,12 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ } break; case DC_FIELD_LOCATION: - if (parser->opening[9] == UNDEFINED || parser->aimode != AI_ON_GPS) + if (parser->opening[9] == UNDEFINED || parser->logversion < 17) return DC_STATUS_UNSUPPORTED; + gnss = data[parser->opening[9] + 16]; latitude = (signed int) array_uint32_be (data + parser->opening[9] + 21); longitude = (signed int) array_uint32_be (data + parser->opening[9] + 25); - if ((latitude == 0 && longitude == 0) || - (latitude == -1 && longitude == -1)) + if (gnss != GNSS_FIX_2D && gnss != GNSS_FIX_3D) return DC_STATUS_UNSUPPORTED; location->latitude = latitude / 100000.0; location->longitude = longitude / 100000.0; From f351434cff89b672954013124ce9781c2c4e845a Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sat, 16 May 2026 15:28:12 +0200 Subject: [PATCH 04/18] Read the shearwater model number directly The Shearwater dive computers support reading the model number directly with the RDBI command 0x8060. This previously unknown feature eliminates the need to first read the hardware type and then map it onto the corresponding model number. Since new hardware types are introduced relative frequently, the mapping was always a bit out of date. --- src/shearwater_common.c | 81 ----------------------------------------- src/shearwater_common.h | 4 +- src/shearwater_petrel.c | 26 +++++-------- 3 files changed, 10 insertions(+), 101 deletions(-) diff --git a/src/shearwater_common.c b/src/shearwater_common.c index 37c75e5b..deca525d 100644 --- a/src/shearwater_common.c +++ b/src/shearwater_common.c @@ -694,84 +694,3 @@ shearwater_common_timesync_utc (shearwater_common_device_t *device, const dc_dat return status; } - -unsigned int -shearwater_common_get_model (shearwater_common_device_t *device, unsigned int hardware) -{ - unsigned int model = 0; - - switch (hardware) { - case 0x0101: - case 0x0202: - model = PREDATOR; - break; - case 0x0404: - case 0x0909: - model = PETREL; - break; - case 0x0505: - case 0x0808: - case 0x0838: - case 0x08A5: - case 0x0B0B: - case 0x7828: - case 0x7B2C: - case 0x8838: - model = PETREL2; - break; - case 0xB407: - case 0xB429: - model = PETREL3; - break; - case 0x0606: - case 0x0A0A: - model = NERD; - break; - case 0x0E0D: - case 0x7E2D: - model = NERD2; - break; - case 0x0707: - model = PERDIX; - break; - case 0x0C0D: - case 0x425B: - case 0x7C2D: - case 0x8D6C: - model = PERDIXAI; - break; - case 0x704C: - case 0x924C: - case 0x9C64: - case 0xC407: - case 0xC429: - case 0xC964: - model = PERDIX2; - break; - case 0x0F0F: - case 0x0F10: - case 0x1F0A: - case 0x1F0F: - case 0x1F10: - case 0x1F1A: - model = TERIC; - break; - case 0x1512: - case 0x1613: - case 0x2623: - case 0x63A5: - model = PEREGRINE; - break; - case 0x1712: - case 0x813A: - model = PEREGRINE_TX; - break; - case 0xC0E0: - model = TERN; - break; - default: - WARNING (device->base.context, "Unknown hardware type 0x%04x.", hardware); - } - - return model; -} diff --git a/src/shearwater_common.h b/src/shearwater_common.h index d5f425cb..e7940f08 100644 --- a/src/shearwater_common.h +++ b/src/shearwater_common.h @@ -34,6 +34,7 @@ extern "C" { #define ID_FIRMWARE 0x8011 #define ID_LOGUPLOAD 0x8021 #define ID_HARDWARE 0x8050 +#define ID_MODEL 0x8060 #define ID_TIME_LOCAL 0x9030 #define ID_TIME_UTC 0x9031 @@ -83,9 +84,6 @@ shearwater_common_timesync_local (shearwater_common_device_t *device, const dc_d dc_status_t shearwater_common_timesync_utc (shearwater_common_device_t *device, const dc_datetime_t *datetime); -unsigned int -shearwater_common_get_model (shearwater_common_device_t *device, unsigned int hardware); - #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/shearwater_petrel.c b/src/shearwater_petrel.c index a4414ade..56f90feb 100644 --- a/src/shearwater_petrel.c +++ b/src/shearwater_petrel.c @@ -189,19 +189,15 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call // Convert to a number. unsigned int firmware = str2num (rsp_firmware, rsp_firmware_length, 1); - // Read the hardware type. - unsigned char rsp_hardware[2] = {0}; - rc = shearwater_common_rdbi (&device->base, ID_HARDWARE, rsp_hardware, sizeof(rsp_hardware), NULL); + // Read the model number. + unsigned char model = 0; + rc = shearwater_common_rdbi (&device->base, ID_MODEL, &model, sizeof(model), NULL); if (rc != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the hardware type."); + ERROR (abstract->context, "Failed to read the model number."); return rc; } - HEXDUMP(abstract->context, DC_LOGLEVEL_DEBUG, "Hardware", rsp_hardware, sizeof(rsp_hardware)); - - // Convert and map to the model number. - unsigned int hardware = array_uint16_be (rsp_hardware); - unsigned int model = shearwater_common_get_model (&device->base, hardware); + HEXDUMP(abstract->context, DC_LOGLEVEL_DEBUG, "Model", &model, sizeof(model)); // Emit a device info event. dc_event_devinfo_t devinfo; @@ -367,18 +363,14 @@ shearwater_petrel_device_timesync (dc_device_t *abstract, const dc_datetime_t *d dc_status_t status = DC_STATUS_SUCCESS; shearwater_common_device_t *device = (shearwater_common_device_t *) abstract; - // Read the hardware type. - unsigned char rsp_hardware[2] = {0}; - status = shearwater_common_rdbi (device, ID_HARDWARE, rsp_hardware, sizeof(rsp_hardware), NULL); + // Read the model number. + unsigned char model = 0; + status = shearwater_common_rdbi (device, ID_MODEL, &model, sizeof(model), NULL); if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the hardware type."); + ERROR (abstract->context, "Failed to read the model number."); return status; } - // Convert and map to the model number. - unsigned int hardware = array_uint16_be (rsp_hardware); - unsigned int model = shearwater_common_get_model (device, hardware); - if (model == TERIC) { return shearwater_common_timesync_utc (device, datetime); } else { From 720edf385679cddb2f8c049453a35be198b6d9aa Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 28 May 2026 18:03:03 +0200 Subject: [PATCH 05/18] Fix the clock synchronization for DC_TIMEZONE_NONE Because the special DC_TIMEZONE_NONE value is out of range for a valid UTC offset, setting the clock fails with DC_STATUS_INVALIDARGS. Assume an UTC offset of zero instead. --- src/divesystem_idive.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/divesystem_idive.c b/src/divesystem_idive.c index ece18e1d..51904b52 100644 --- a/src/divesystem_idive.c +++ b/src/divesystem_idive.c @@ -649,6 +649,10 @@ divesystem_idive_device_timesync (dc_device_t *abstract, const dc_datetime_t *da // Adjust the epoch. timestamp -= EPOCH; + // UTC offset. + int tz_offset = datetime->timezone != DC_TIMEZONE_NONE ? + datetime->timezone : 0; + // Find the timezone index. size_t tz_idx = C_ARRAY_SIZE(tz_array); for (size_t i = 0; i < C_ARRAY_SIZE(tz_array); i += 2) { @@ -659,7 +663,7 @@ divesystem_idive_device_timesync (dc_device_t *abstract, const dc_datetime_t *da timezone += tz_array[i + 1] * 60; } - if (timezone == datetime->timezone) { + if (timezone == tz_offset) { tz_idx = i; break; } From 77759abf8caa237f0dc5014acea89c1ffc13d2c5 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 21 Jun 2026 16:55:08 +0200 Subject: [PATCH 06/18] Add a new location sample to report multiple GPS locations Some of the dive computers with an integrated GPS receiver use a data format that is capable of reporting multiple GPS coordinates throughout the dive. Add a new location sample to support this feature and update all dive computer backends to use it. The existing location field remains available for dive computers which can only support a single location that is not linked to a sample. Currently there are no such computers left anymore. In practice, only the Halycon Symbios appears to report multiple GPS locations. The Shearwater Swift records the entry and exit location and those locations are now both reported, respectively on the first and last sample. --- examples/output_xml.c | 11 +++++++++ include/libdivecomputer/parser.h | 4 +++- src/divesoft_freedom_parser.c | 36 ++++++---------------------- src/divesystem_idive_parser.c | 41 ++++++++++---------------------- src/halcyon_symbios_parser.c | 31 +++++------------------- src/hw_ostc_parser.c | 25 ++++--------------- src/shearwater_predator_parser.c | 40 +++++++++++++++++++------------ 7 files changed, 69 insertions(+), 119 deletions(-) diff --git a/examples/output_xml.c b/examples/output_xml.c index 643bbb79..66dd7723 100644 --- a/examples/output_xml.c +++ b/examples/output_xml.c @@ -179,6 +179,17 @@ sample_cb (dc_sample_type_t type, const dc_sample_value_t *value, void *userdata case DC_SAMPLE_GASMIX: fprintf (sampledata->ostream, " %u\n", value->gasmix); break; + case DC_SAMPLE_LOCATION: + fprintf (sampledata->ostream, + " \n" + " %.6f\n" + " %.6f\n" + " %.3f\n" + " \n", + value->location.latitude, + value->location.longitude, + value->location.altitude); + break; default: break; } diff --git a/include/libdivecomputer/parser.h b/include/libdivecomputer/parser.h index 14dbe348..4bcbdbd2 100644 --- a/include/libdivecomputer/parser.h +++ b/include/libdivecomputer/parser.h @@ -46,7 +46,8 @@ typedef enum dc_sample_type_t { DC_SAMPLE_PPO2, DC_SAMPLE_CNS, DC_SAMPLE_DECO, - DC_SAMPLE_GASMIX + DC_SAMPLE_GASMIX, + DC_SAMPLE_LOCATION, } dc_sample_type_t; typedef enum dc_field_type_t { @@ -282,6 +283,7 @@ typedef union dc_sample_value_t { unsigned int tts; } deco; unsigned int gasmix; /* Gas mix index */ + dc_location_t location; } dc_sample_value_t; typedef struct dc_parser_t dc_parser_t; diff --git a/src/divesoft_freedom_parser.c b/src/divesoft_freedom_parser.c index eb6621a4..1724bc33 100644 --- a/src/divesoft_freedom_parser.c +++ b/src/divesoft_freedom_parser.c @@ -252,9 +252,6 @@ typedef struct divesoft_freedom_parser_t { unsigned int seawater; unsigned int calibration[NSENSORS]; unsigned int calibrated; - unsigned int have_location; - int latitude; - int longitude; } divesoft_freedom_parser_t; static dc_status_t divesoft_freedom_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); @@ -397,10 +394,6 @@ divesoft_freedom_cache (divesoft_freedom_parser_t *parser) unsigned int gasmixid_previous = UNDEFINED; - unsigned int have_location = 0; - int latitude = 0; - int longitude = 0; - // Parse the dive profile. unsigned int offset = headersize; while (offset + RECORD_SIZE <= size) { @@ -550,14 +543,6 @@ divesoft_freedom_cache (divesoft_freedom_parser_t *parser) } tank[idx].endpressure = pressure; } - } else if (id == MEASURE_ID_GPS) { - if (!have_location) { - latitude = (signed int) array_uint32_le (data + offset + 4); - longitude = (signed int) array_uint32_le (data + offset + 8); - have_location = 1; - } else { - WARNING (abstract->context, "Multiple GPS locations present."); - } } } @@ -650,9 +635,6 @@ divesoft_freedom_cache (divesoft_freedom_parser_t *parser) parser->calibration[i] = calibration[i]; } parser->calibrated = calibrated; - parser->have_location = have_location; - parser->latitude = latitude; - parser->longitude = longitude; return DC_STATUS_SUCCESS; } @@ -707,9 +689,6 @@ divesoft_freedom_parser_create (dc_parser_t **out, dc_context_t *context, const parser->calibration[i] = 0; } parser->calibrated = 0; - parser->have_location = 0; - parser->latitude = 0; - parser->longitude = 0; *out = (dc_parser_t *) parser; @@ -766,7 +745,6 @@ divesoft_freedom_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_decomodel_t *decomodel = (dc_decomodel_t *) value; - dc_location_t *location = (dc_location_t *) value; if (value) { switch (type) { @@ -862,13 +840,6 @@ divesoft_freedom_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, decomodel->params.gf.high = parser->gf_hi; } break; - case DC_FIELD_LOCATION: - if (!parser->have_location) - return DC_STATUS_UNSUPPORTED; - location->latitude = parser->latitude / 1000000.0; - location->longitude = parser->longitude / 1000000.0; - location->altitude = 0.0; - break; default: return DC_STATUS_UNSUPPORTED; } @@ -1061,6 +1032,13 @@ divesoft_freedom_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callba sample.ppo2.value = value / 100.0 * parser->calibration[i] / BAR; if (callback) callback(DC_SAMPLE_PPO2, &sample, userdata); } + } else if (id == MEASURE_ID_GPS) { + int latitude = (signed int) array_uint32_le (data + offset + 4); + int longitude = (signed int) array_uint32_le (data + offset + 8); + sample.location.latitude = latitude / 1000000.0; + sample.location.longitude = longitude / 1000000.0; + sample.location.altitude = 0.0; + if (callback) callback (DC_SAMPLE_LOCATION, &sample, userdata); } } else if (type == LREC_STATE) { // Tissue saturation record. diff --git a/src/divesystem_idive_parser.c b/src/divesystem_idive_parser.c index 65b99d64..2586c9bc 100644 --- a/src/divesystem_idive_parser.c +++ b/src/divesystem_idive_parser.c @@ -90,10 +90,6 @@ struct divesystem_idive_parser_t { unsigned int algorithm; unsigned int gf_low; unsigned int gf_high; - unsigned int have_location; - int latitude; - int longitude; - int altitude; }; static dc_status_t divesystem_idive_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); @@ -153,10 +149,6 @@ divesystem_idive_parser_create (dc_parser_t **out, dc_context_t *context, const parser->algorithm = INVALID; parser->gf_low = INVALID; parser->gf_high = INVALID; - parser->have_location = 0; - parser->latitude = 0; - parser->longitude = 0; - parser->altitude = 0; *out = (dc_parser_t*) parser; @@ -283,7 +275,6 @@ divesystem_idive_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, dc_tank_t *tank = (dc_tank_t *) value; dc_salinity_t *water = (dc_salinity_t *) value; dc_decomodel_t *decomodel = (dc_decomodel_t *) value; - dc_location_t *location = (dc_location_t *) value; if (value) { switch (type) { @@ -389,13 +380,6 @@ divesystem_idive_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, } } break; - case DC_FIELD_LOCATION: - if (!parser->have_location) - return DC_STATUS_UNSUPPORTED; - location->latitude = parser->latitude / 10000000.0; - location->longitude = parser->longitude / 10000000.0; - location->altitude = parser->altitude / 1000.0; - break; default: return DC_STATUS_UNSUPPORTED; } @@ -466,14 +450,10 @@ divesystem_idive_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callba REC_SAMPLE; if (type != REC_SAMPLE) { if (type == REC_INFO) { - if (!have_location) { - altitude = (signed int) array_uint32_le (data + offset + 40); - longitude = (signed int) array_uint32_le (data + offset + 44); - latitude = (signed int) array_uint32_le (data + offset + 48); - have_location = 1; - } else { - WARNING (abstract->context, "Multiple GPS locations present."); - } + altitude = (signed int) array_uint32_le (data + offset + 40); + longitude = (signed int) array_uint32_le (data + offset + 44); + latitude = (signed int) array_uint32_le (data + offset + 48); + have_location = 1; } // Skip non-sample records. @@ -660,6 +640,15 @@ divesystem_idive_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callba } } + // GPS location + if (have_location) { + sample.location.latitude = latitude / 10000000.0; + sample.location.longitude = longitude / 10000000.0; + sample.location.altitude = altitude / 1000.0; + if (callback) callback (DC_SAMPLE_LOCATION, &sample, userdata); + have_location = 0; + } + offset += samplesize; } @@ -678,10 +667,6 @@ divesystem_idive_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callba parser->algorithm = algorithm; parser->gf_low = gf_low; parser->gf_high = gf_high; - parser->have_location = have_location; - parser->latitude = latitude; - parser->longitude = longitude; - parser->altitude = altitude; parser->cached = 1; return DC_STATUS_SUCCESS; diff --git a/src/halcyon_symbios_parser.c b/src/halcyon_symbios_parser.c index 5fc0122e..977e660c 100644 --- a/src/halcyon_symbios_parser.c +++ b/src/halcyon_symbios_parser.c @@ -107,8 +107,6 @@ typedef struct halcyon_symbios_parser_t { halcyon_symbios_tank_t tank[NTANKS]; unsigned int gf_lo; unsigned int gf_hi; - unsigned int have_location; - int latitude, longitude; } halcyon_symbios_parser_t; static dc_status_t halcyon_symbios_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); @@ -153,9 +151,6 @@ halcyon_symbios_parser_create (dc_parser_t **out, dc_context_t *context, const u parser->atmospheric = UNDEFINED; parser->gf_lo = UNDEFINED; parser->gf_hi = UNDEFINED; - parser->have_location = 0; - parser->latitude = 0; - parser->longitude = 0; parser->ngasmixes = 0; parser->ntanks = 0; for (unsigned int i = 0; i < NGASMIXES; ++i) { @@ -230,7 +225,6 @@ halcyon_symbios_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, u dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_tank_t *tank = (dc_tank_t *) value; dc_decomodel_t *decomodel = (dc_decomodel_t *) value; - dc_location_t *location = (dc_location_t *) value; if (value) { switch (type) { @@ -293,13 +287,6 @@ halcyon_symbios_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, u decomodel->params.gf.low = parser->gf_lo; decomodel->params.gf.high = parser->gf_hi; break; - case DC_FIELD_LOCATION: - if (!parser->have_location) - return DC_STATUS_UNSUPPORTED; - location->latitude = parser->latitude / 1000000.0; - location->longitude = parser->longitude / 1000000.0; - location->altitude = 0.0; - break; default: return DC_STATUS_UNSUPPORTED; } @@ -348,8 +335,6 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac unsigned int atmospheric = UNDEFINED; unsigned int gf_lo = UNDEFINED; unsigned int gf_hi = UNDEFINED; - unsigned int have_location = 0; - int latitude = 0, longitude = 0; unsigned int ngasmixes = 0; unsigned int ntanks = 0; halcyon_symbios_gasmix_t gasmix[NGASMIXES] = {0}; @@ -539,13 +524,12 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac unsigned int DC_ATTR_UNUSED he = array_uint16_le (data + offset + 36 + i * 2); } } else if (type == ID_GPS) { - if (!have_location) { - longitude = (signed int) array_uint32_le (data + offset + 4); - latitude = (signed int) array_uint32_le (data + offset + 8); - have_location = 1; - } else { - WARNING (abstract->context, "Multiple GPS locations present."); - } + int longitude = (signed int) array_uint32_le (data + offset + 4); + int latitude = (signed int) array_uint32_le (data + offset + 8); + sample.location.latitude = latitude / 1000000.0; + sample.location.longitude = longitude / 1000000.0; + sample.location.altitude = 0.0; + if (callback) callback (DC_SAMPLE_LOCATION, &sample, userdata); } else if (type == ID_PO2_BOARD) { unsigned int DC_ATTR_UNUSED serial = array_uint16_le (data + offset + 6); for (unsigned int i = 0; i < 3; ++i) { @@ -748,9 +732,6 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac parser->atmospheric = atmospheric; parser->gf_lo = gf_lo; parser->gf_hi = gf_hi; - parser->have_location = have_location; - parser->latitude = latitude; - parser->longitude = longitude; parser->ngasmixes = ngasmixes; parser->ntanks = ntanks; for (unsigned int i = 0; i < NGASMIXES; ++i) { diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index 37309f3d..38de7fc2 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -142,9 +142,6 @@ typedef struct hw_ostc_parser_t { unsigned int initial_setpoint; unsigned int initial_cns; hw_ostc_gasmix_t gasmix[NGASMIXES]; - unsigned int have_location; - float latitude; - float longitude; } hw_ostc_parser_t; static dc_status_t hw_ostc_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); @@ -435,9 +432,6 @@ hw_ostc_parser_create_internal (dc_parser_t **out, dc_context_t *context, const parser->gasmix[i].active = 0; parser->gasmix[i].diluent = 0; } - parser->have_location = 0; - parser->latitude = 0.0; - parser->longitude = 0.0; *out = (dc_parser_t *) parser; @@ -541,7 +535,6 @@ 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; - dc_location_t *location = (dc_location_t *) value; unsigned int salinity = data[layout->salinity]; if (version == 0x23 || version == 0x24) @@ -709,13 +702,6 @@ hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned } decomodel->conservatism = 0; break; - case DC_FIELD_LOCATION: - if (!parser->have_location) - return DC_STATUS_UNSUPPORTED; - location->latitude = parser->latitude; - location->longitude = parser->longitude; - location->altitude = 0.0; - break; default: return DC_STATUS_UNSUPPORTED; } @@ -1075,13 +1061,10 @@ hw_ostc_parser_internal_foreach (hw_ostc_parser_t *parser, dc_sample_callback_t if (latitude != OSTC4_GNSS_DUMMY_LATITUDE || longitude != OSTC4_GNSS_DUMMY_LONGITUDE) { - if (!parser->have_location) { - parser->latitude = latitude; - parser->longitude = longitude; - parser->have_location = 1; - } else { - WARNING (abstract->context, "Multiple GNSS locations present."); - } + sample.location.latitude = latitude; + sample.location.longitude = longitude; + sample.location.altitude = 0.0; + if (callback) callback (DC_SAMPLE_LOCATION, &sample, userdata); } offset += 8; diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index 80de9396..f0bd8916 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -152,6 +152,7 @@ struct shearwater_predator_parser_t { unsigned int opening[NRECORDS]; unsigned int closing[NRECORDS]; unsigned int final; + unsigned int nsamples; unsigned int ngasmixes; unsigned int ntanks; shearwater_predator_gasmix_t gasmix[NGASMIXES]; @@ -418,6 +419,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) } } + unsigned int nsamples = 0; unsigned int offset = headersize; unsigned int length = size - footersize; while (offset + parser->samplesize <= length) { @@ -432,6 +434,9 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) if (type == LOG_RECORD_DIVE_SAMPLE || type == LOG_RECORD_AVELO_SAMPLE) { + // Number of samples. + nsamples++; + // Status flags. unsigned int status = 0; unsigned int ccr = 0; @@ -739,6 +744,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) parser->tankidx[i] = UNDEFINED; } } + parser->nsamples = nsamples; parser->aimode = aimode; parser->hpccr = hpccr; parser->divemode = divemode; @@ -764,14 +770,11 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ unsigned int decomodel_idx = parser->pnf ? parser->opening[2] + 18 : 67; unsigned int gf_idx = parser->pnf ? parser->opening[0] + 4 : 4; - int latitude = 0, longitude = 0; - unsigned int gnss = 0; 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; - dc_location_t *location = (dc_location_t *) value; if (value) { switch (type) { @@ -880,18 +883,6 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ return DC_STATUS_DATAFORMAT; } break; - case DC_FIELD_LOCATION: - if (parser->opening[9] == UNDEFINED || parser->logversion < 17) - return DC_STATUS_UNSUPPORTED; - gnss = data[parser->opening[9] + 16]; - latitude = (signed int) array_uint32_be (data + parser->opening[9] + 21); - longitude = (signed int) array_uint32_be (data + parser->opening[9] + 25); - if (gnss != GNSS_FIX_2D && gnss != GNSS_FIX_3D) - return DC_STATUS_UNSUPPORTED; - location->latitude = latitude / 100000.0; - location->longitude = longitude / 100000.0; - location->altitude = 0.0; - break; default: return DC_STATUS_UNSUPPORTED; } @@ -924,6 +915,7 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal interval = array_uint16_be (data + parser->opening[5] + 23); } + unsigned int nsamples = 0; unsigned int pnf = parser->pnf; unsigned int offset = parser->headersize; unsigned int length = size - parser->footersize; @@ -941,6 +933,9 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal if (type == LOG_RECORD_DIVE_SAMPLE || type == LOG_RECORD_AVELO_SAMPLE) { + // Number of samples. + nsamples++; + // Time (seconds). time += interval; sample.time = time; @@ -1090,6 +1085,21 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal if (callback) callback (DC_SAMPLE_RBT, &sample, userdata); } } + + if (nsamples == 1 || nsamples == parser->nsamples) { + unsigned int record = nsamples == 1 ? parser->opening[9] : parser->closing[9]; + if (record != UNDEFINED && parser->logversion >= 17) { + unsigned int gnss = data[record + 16]; + int latitude = (signed int) array_uint32_be (data + record + 21); + int longitude = (signed int) array_uint32_be (data + record + 25); + if (gnss == GNSS_FIX_2D || gnss == GNSS_FIX_3D) { + sample.location.latitude = latitude / 100000.0; + sample.location.longitude = longitude / 100000.0; + sample.location.altitude = 0.0; + if (callback) callback (DC_SAMPLE_LOCATION, &sample, userdata); + } + } + } } else if (type == LOG_RECORD_DIVE_SAMPLE_EXT) { // Tank pressure if (parser->logversion >= 13) { From a512a3a677af238ee1cd18285051eb6c30f4cf91 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 20 Jan 2026 18:57:13 +0100 Subject: [PATCH 07/18] Rename a local variable Using a variable named "status" for the display message is confusing because that name is typically used for the return value of a function. --- src/hw_ostc3.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 6bd85609..6bc4af17 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -1454,9 +1454,9 @@ hw_ostc3_device_fwupdate3 (dc_device_t *abstract, const char *filename) hw_ostc3_device_display (abstract, " Uploading..."); for (unsigned int len = 0; len < SZ_FIRMWARE; len += SZ_FIRMWARE_BLOCK) { - char status[SZ_DISPLAY + 1]; // Status message on the display - dc_platform_snprintf (status, sizeof(status), " Uploading %2d%%", (100 * len) / SZ_FIRMWARE); - hw_ostc3_device_display (abstract, status); + char msg[SZ_DISPLAY + 1]; // Status message on the display + dc_platform_snprintf (msg, sizeof(msg), " Uploading %2d%%", (100 * len) / SZ_FIRMWARE); + hw_ostc3_device_display (abstract, msg); rc = hw_ostc3_firmware_block_write (device, FIRMWARE_AREA + len, firmware->data + len, SZ_FIRMWARE_BLOCK); if (rc != DC_STATUS_SUCCESS) { @@ -1473,9 +1473,9 @@ 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 - dc_platform_snprintf (status, sizeof(status), " Verifying %2d%%", (100 * len) / SZ_FIRMWARE); - hw_ostc3_device_display (abstract, status); + char msg[SZ_DISPLAY + 1]; // Status message on the display + dc_platform_snprintf (msg, sizeof(msg), " Verifying %2d%%", (100 * len) / SZ_FIRMWARE); + hw_ostc3_device_display (abstract, msg); rc = hw_ostc3_firmware_block_read (device, FIRMWARE_AREA + len, block, sizeof (block)); if (rc != DC_STATUS_SUCCESS) { From 3c16913028312098081a59b465cdc3130b5196f5 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 16 Feb 2026 21:48:02 +0100 Subject: [PATCH 08/18] Fix some small typos --- 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 6bc4af17..d5772870 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -325,7 +325,7 @@ hw_ostc3_transfer (hw_ostc3_device_t *device, dc_iostream_sleep (device->iostream, 10); - // Send the reamainder of the input data packet. + // Send the remainder of the input data packet. status = hw_ostc3_write (device, progress, input + 1, isize - 1); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the data packet."); @@ -1498,7 +1498,7 @@ hw_ostc3_device_fwupdate3 (dc_device_t *abstract, const char *filename) rc = hw_ostc3_firmware_upgrade (abstract, firmware->checksum); if (rc != DC_STATUS_SUCCESS) { - ERROR (context, "Failed to start programing"); + ERROR (context, "Failed to start programming"); free (firmware); return rc; } From 7c6195ba1ceec90f939b22d8658d79962a20c0db Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 27 May 2026 19:55:12 +0200 Subject: [PATCH 09/18] Use memcmp to compare the service key The service key is already hardcoded in the command and doesn't need to be repeated a second time. --- src/hw_ostc3.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index d5772870..eebaeb85 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -61,6 +61,7 @@ #define S_BLOCK_WRITE 0x30 #define S_BLOCK_WRITE2 0x31 #define S_ERASE 0x42 +#define S_KEY 0x4B #define S_READY 0x4C #define READY 0x4D #define S_UPGRADE 0x50 @@ -523,9 +524,9 @@ hw_ostc3_device_init_service (hw_ostc3_device_t *device) } // Verify the response to service mode. - if (answer[0] != 0x4B || answer[1] != 0xAB || - answer[2] != 0xCD || answer[3] != 0xEF || - answer[4] != S_READY) { + if (answer[0] != S_KEY || + memcmp (answer + 1, command + 1, sizeof(command) - 1) != 0 || + answer[4] != S_READY) { ERROR (abstract->context, "Failed to verify the answer."); return DC_STATUS_PROTOCOL; } From 35df71c0fa55fc2559b6a70ca791eddbbe3d1a6b Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 11 Jan 2026 20:53:04 +0100 Subject: [PATCH 10/18] Integrate the frog code into the ostc3 backend The communication protocol used by the frog is almost identical to the ostc3 protocol, with a few small differences: - No profile length stored in the dive header. The length must be calculated from the profile begin/end pointers instead. - Smaller display message (15 vs 16 characters) - Smaller custom text (13 vs 60 characters) - Smaller firmware size (116K vs 120K) - Different service key - Different firmware encryption key These differences can easily be handled in the ostc3 backend. The main improvement is less code duplication. For features not supported by the frog, the corresponding commands will fail at runtime with DC_STATUS_UNSUPPORTED, just like with the ostc3. The exceptions are the INIT and HEADER commands, but those two commands are supported by the frog. The following functions are removed from the public api and can be replaced with the corresponding ostc3 variant: - hw_frog_device_version - hw_frog_device_display - hw_frog_device_customtext --- contrib/android/Android.mk | 1 - contrib/msvc/libdivecomputer.vcxproj | 3 - include/libdivecomputer/Makefile.am | 1 - include/libdivecomputer/hw_frog.h | 48 --- include/libdivecomputer/hw_ostc3.h | 3 + src/Makefile.am | 1 - src/device.c | 5 +- src/hw_frog.c | 535 --------------------------- src/hw_frog.h | 41 -- src/hw_ostc3.c | 300 +++++++++------ src/hw_ostc3.h | 2 +- src/libdivecomputer.symbols | 3 - src/parser.c | 1 - 13 files changed, 199 insertions(+), 745 deletions(-) delete mode 100644 include/libdivecomputer/hw_frog.h delete mode 100644 src/hw_frog.c delete mode 100644 src/hw_frog.h diff --git a/contrib/android/Android.mk b/contrib/android/Android.mk index c11d0d1a..fd6e8b4a 100644 --- a/contrib/android/Android.mk +++ b/contrib/android/Android.mk @@ -42,7 +42,6 @@ LOCAL_SRC_FILES := \ src/halcyon_symbios.c \ src/halcyon_symbios_parser.c \ src/hdlc.c \ - src/hw_frog.c \ src/hw_ostc3.c \ src/hw_ostc.c \ src/hw_ostc_parser.c \ diff --git a/contrib/msvc/libdivecomputer.vcxproj b/contrib/msvc/libdivecomputer.vcxproj index db583a37..b1571952 100644 --- a/contrib/msvc/libdivecomputer.vcxproj +++ b/contrib/msvc/libdivecomputer.vcxproj @@ -210,7 +210,6 @@ - @@ -301,7 +300,6 @@ - @@ -342,7 +340,6 @@ - diff --git a/include/libdivecomputer/Makefile.am b/include/libdivecomputer/Makefile.am index 6f679c77..401f839a 100644 --- a/include/libdivecomputer/Makefile.am +++ b/include/libdivecomputer/Makefile.am @@ -29,7 +29,6 @@ libdivecomputer_HEADERS = \ oceanic_veo250.h \ oceanic_vtpro.h \ hw_ostc.h \ - hw_frog.h \ hw_ostc3.h \ atomics_cobalt.h \ divesystem_idive.h diff --git a/include/libdivecomputer/hw_frog.h b/include/libdivecomputer/hw_frog.h deleted file mode 100644 index 0af10cac..00000000 --- a/include/libdivecomputer/hw_frog.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * libdivecomputer - * - * Copyright (C) 2012 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 DC_HW_FROG_H -#define DC_HW_FROG_H - -#include "common.h" -#include "device.h" -#include "datetime.h" - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -#define HW_FROG_DISPLAY_SIZE 15 -#define HW_FROG_CUSTOMTEXT_SIZE 13 - -dc_status_t -hw_frog_device_version (dc_device_t *device, unsigned char data[], unsigned int size); - -dc_status_t -hw_frog_device_display (dc_device_t *device, const char *text); - -dc_status_t -hw_frog_device_customtext (dc_device_t *device, const char *text); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ -#endif /* DC_HW_FROG_H */ diff --git a/include/libdivecomputer/hw_ostc3.h b/include/libdivecomputer/hw_ostc3.h index c69dd93e..f53d5d1f 100644 --- a/include/libdivecomputer/hw_ostc3.h +++ b/include/libdivecomputer/hw_ostc3.h @@ -33,6 +33,9 @@ extern "C" { #define HW_OSTC3_DISPLAY_SIZE 16 #define HW_OSTC3_CUSTOMTEXT_SIZE 60 +#define HW_FROG_DISPLAY_SIZE 15 +#define HW_FROG_CUSTOMTEXT_SIZE 13 + dc_status_t hw_ostc3_device_version (dc_device_t *device, unsigned char data[], unsigned int size); diff --git a/src/Makefile.am b/src/Makefile.am index ff0ee642..583546b1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -51,7 +51,6 @@ libdivecomputer_la_SOURCES = \ mares_iconhd.h mares_iconhd.c mares_iconhd_parser.c \ ihex.h ihex.c \ hw_ostc.h hw_ostc.c hw_ostc_parser.c \ - hw_frog.h hw_frog.c \ hw_ostc3.h hw_ostc3.c \ aes.h aes.c \ cressi_edy.h cressi_edy.c cressi_edy_parser.c \ diff --git a/src/device.c b/src/device.c index ec11b93d..2cd4fc2d 100644 --- a/src/device.c +++ b/src/device.c @@ -43,7 +43,6 @@ #include "mares_iconhd.h" #include "mares_nemo.h" #include "mares_puck.h" -#include "hw_frog.h" #include "hw_ostc.h" #include "hw_ostc3.h" #include "cressi_edy.h" @@ -183,10 +182,10 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr rc = hw_ostc_device_open (&device, context, iostream); break; case DC_FAMILY_HW_FROG: - rc = hw_frog_device_open (&device, context, iostream); + rc = hw_ostc3_device_open (&device, context, iostream, 1); break; case DC_FAMILY_HW_OSTC3: - rc = hw_ostc3_device_open (&device, context, iostream); + rc = hw_ostc3_device_open (&device, context, iostream, 0); break; case DC_FAMILY_CRESSI_EDY: rc = cressi_edy_device_open (&device, context, iostream); diff --git a/src/hw_frog.c b/src/hw_frog.c deleted file mode 100644 index dcd9dec3..00000000 --- a/src/hw_frog.c +++ /dev/null @@ -1,535 +0,0 @@ -/* - * libdivecomputer - * - * Copyright (C) 2012 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 "hw_frog.h" -#include "context-private.h" -#include "device-private.h" -#include "checksum.h" -#include "ringbuffer.h" -#include "array.h" - -#define ISINSTANCE(device) dc_device_isinstance((device), &hw_frog_device_vtable) - -#define SZ_DISPLAY 15 -#define SZ_CUSTOMTEXT 13 -#define SZ_VERSION (SZ_CUSTOMTEXT + 4) - -#define RB_LOGBOOK_SIZE 256 -#define RB_LOGBOOK_COUNT 256 - -#define RB_PROFILE_BEGIN 0x000000 -#define RB_PROFILE_END 0x200000 -#define RB_PROFILE_DISTANCE(a,b) ringbuffer_distance (a, b, DC_RINGBUFFER_EMPTY, RB_PROFILE_BEGIN, RB_PROFILE_END) - -#define READY 0x4D -#define HEADER 0x61 -#define CLOCK 0x62 -#define CUSTOMTEXT 0x63 -#define DIVE 0x66 -#define IDENTITY 0x69 -#define DISPLAY 0x6E -#define INIT 0xBB -#define EXIT 0xFF - -typedef struct hw_frog_device_t { - dc_device_t base; - dc_iostream_t *iostream; - unsigned char fingerprint[5]; -} hw_frog_device_t; - -static dc_status_t hw_frog_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); -static dc_status_t hw_frog_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); -static dc_status_t hw_frog_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime); -static dc_status_t hw_frog_device_close (dc_device_t *abstract); - -static const dc_device_vtable_t hw_frog_device_vtable = { - sizeof(hw_frog_device_t), - DC_FAMILY_HW_FROG, - hw_frog_device_set_fingerprint, /* set_fingerprint */ - NULL, /* read */ - NULL, /* write */ - NULL, /* dump */ - hw_frog_device_foreach, /* foreach */ - hw_frog_device_timesync, /* timesync */ - hw_frog_device_close /* close */ -}; - - -static int -hw_frog_strncpy (unsigned char *data, unsigned int size, const char *text) -{ - // Check the maximum length. - size_t length = (text ? strlen (text) : 0); - if (length > size) { - return -1; - } - - // Copy the text. - if (length) - memcpy (data, text, length); - - // Pad with spaces. - memset (data + length, 0x20, size - length); - - return 0; -} - - -static dc_status_t -hw_frog_transfer (hw_frog_device_t *device, - dc_event_progress_t *progress, - unsigned char cmd, - const unsigned char input[], - unsigned int isize, - unsigned char output[], - unsigned int osize) -{ - dc_status_t status = DC_STATUS_SUCCESS; - dc_device_t *abstract = (dc_device_t *) device; - - if (device_is_cancelled (abstract)) - return DC_STATUS_CANCELLED; - - // Send the command. - unsigned char command[1] = {cmd}; - status = dc_iostream_write (device->iostream, command, sizeof (command), NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to send the command."); - return status; - } - - if (cmd != INIT && cmd != HEADER) { - // Read the echo. - unsigned char answer[1] = {0}; - status = dc_iostream_read (device->iostream, answer, sizeof (answer), NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to receive the echo."); - return status; - } - - // Verify the echo. - if (memcmp (answer, command, sizeof (command)) != 0) { - ERROR (abstract->context, "Unexpected echo."); - return DC_STATUS_PROTOCOL; - } - } - - if (input) { - // Send the input data packet. - status = dc_iostream_write (device->iostream, input, isize, NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to send the data packet."); - return status; - } - } - - if (output) { - unsigned int nbytes = 0; - while (nbytes < osize) { - // Set the minimum packet size. - unsigned int len = 1024; - - // Increase the packet size if more data is immediately available. - size_t available = 0; - status = dc_iostream_get_available (device->iostream, &available); - if (status == DC_STATUS_SUCCESS && available > len) - len = available; - - // Limit the packet size to the total size. - if (nbytes + len > osize) - len = osize - nbytes; - - // Read the packet. - status = dc_iostream_read (device->iostream, output + nbytes, len, NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to receive the answer."); - return status; - } - - // Update and emit a progress event. - if (progress) { - progress->current += len; - device_event_emit ((dc_device_t *) device, DC_EVENT_PROGRESS, progress); - } - - nbytes += len; - } - } - - if (cmd != EXIT) { - // Read the ready byte. - unsigned char answer[1] = {0}; - status = dc_iostream_read (device->iostream, answer, sizeof (answer), NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to receive the ready byte."); - return status; - } - - // Verify the ready byte. - if (answer[0] != READY) { - ERROR (abstract->context, "Unexpected ready byte."); - return DC_STATUS_PROTOCOL; - } - } - - return DC_STATUS_SUCCESS; -} - - -dc_status_t -hw_frog_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) -{ - dc_status_t status = DC_STATUS_SUCCESS; - hw_frog_device_t *device = NULL; - - if (out == NULL) - return DC_STATUS_INVALIDARGS; - - // Allocate memory. - device = (hw_frog_device_t *) dc_device_allocate (context, &hw_frog_device_vtable); - if (device == NULL) { - ERROR (context, "Failed to allocate memory."); - return DC_STATUS_NOMEMORY; - } - - // Set the default values. - device->iostream = iostream; - memset (device->fingerprint, 0, sizeof (device->fingerprint)); - - // Set the serial communication protocol (115200 8N1). - status = dc_iostream_configure (device->iostream, 115200, 8, DC_PARITY_NONE, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE); - if (status != DC_STATUS_SUCCESS) { - ERROR (context, "Failed to set the terminal attributes."); - goto error_free; - } - - // Set the timeout for receiving data (3000ms). - status = dc_iostream_set_timeout (device->iostream, 3000); - if (status != DC_STATUS_SUCCESS) { - ERROR (context, "Failed to set the timeout."); - goto error_free; - } - - // Make sure everything is in a sane state. - dc_iostream_sleep (device->iostream, 300); - dc_iostream_purge (device->iostream, DC_DIRECTION_ALL); - - // Send the init command. - status = hw_frog_transfer (device, NULL, INIT, NULL, 0, NULL, 0); - if (status != DC_STATUS_SUCCESS) { - ERROR (context, "Failed to send the command."); - goto error_free; - } - - *out = (dc_device_t *) device; - - return DC_STATUS_SUCCESS; - -error_free: - dc_device_deallocate ((dc_device_t *) device); - return status; -} - - -static dc_status_t -hw_frog_device_close (dc_device_t *abstract) -{ - dc_status_t status = DC_STATUS_SUCCESS; - hw_frog_device_t *device = (hw_frog_device_t*) abstract; - dc_status_t rc = DC_STATUS_SUCCESS; - - // Send the exit command. - rc = hw_frog_transfer (device, NULL, EXIT, NULL, 0, NULL, 0); - if (rc != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to send the command."); - dc_status_set_error(&status, rc); - } - - return status; -} - - -static dc_status_t -hw_frog_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size) -{ - hw_frog_device_t *device = (hw_frog_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; -} - - -dc_status_t -hw_frog_device_version (dc_device_t *abstract, unsigned char data[], unsigned int size) -{ - hw_frog_device_t *device = (hw_frog_device_t *) abstract; - - if (!ISINSTANCE (abstract)) - return DC_STATUS_INVALIDARGS; - - if (size != SZ_VERSION) - return DC_STATUS_INVALIDARGS; - - // Send the command. - dc_status_t rc = hw_frog_transfer (device, NULL, IDENTITY, NULL, 0, data, size); - if (rc != DC_STATUS_SUCCESS) - return rc; - - return DC_STATUS_SUCCESS; -} - - -static dc_status_t -hw_frog_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) -{ - hw_frog_device_t *device = (hw_frog_device_t *) abstract; - - // Enable progress notifications. - dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; - progress.maximum = (RB_LOGBOOK_SIZE * RB_LOGBOOK_COUNT) + - (RB_PROFILE_END - RB_PROFILE_BEGIN); - device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - - // Download the version data. - unsigned char id[SZ_VERSION] = {0}; - dc_status_t rc = hw_frog_device_version (abstract, id, sizeof (id)); - if (rc != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the version."); - return rc; - } - - HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Version", id, sizeof (id)); - - // Emit a device info event. - dc_event_devinfo_t devinfo; - devinfo.model = 0; - devinfo.firmware = array_uint16_be (id + 2); - devinfo.serial = array_uint16_le (id + 0); - device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); - - // Allocate memory. - unsigned char *header = (unsigned char *) malloc (RB_LOGBOOK_SIZE * RB_LOGBOOK_COUNT); - if (header == NULL) { - ERROR (abstract->context, "Failed to allocate memory."); - return DC_STATUS_NOMEMORY; - } - - // Download the logbook headers. - rc = hw_frog_transfer (device, &progress, HEADER, - NULL, 0, header, RB_LOGBOOK_SIZE * RB_LOGBOOK_COUNT); - if (rc != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the header."); - free (header); - return rc; - } - - // Locate the most recent dive. - // The device maintains an internal counter which is incremented for every - // dive, and the current value at the time of the dive is stored in the - // dive header. Thus the most recent dive will have the highest value. - unsigned int count = 0; - unsigned int latest = 0; - unsigned int maximum = 0; - for (unsigned int i = 0; i < RB_LOGBOOK_COUNT; ++i) { - unsigned int offset = i * RB_LOGBOOK_SIZE; - - // Ignore uninitialized header entries. - if (array_isequal (header + offset, RB_LOGBOOK_SIZE, 0xFF)) - break; - - // Get the internal dive number. - unsigned int current = array_uint16_le (header + offset + 52); - if (current > maximum) { - maximum = current; - latest = i; - } - - count++; - } - - // Calculate the total and maximum size. - unsigned int ndives = 0; - unsigned int size = 0; - unsigned int maxsize = 0; - for (unsigned int i = 0; i < count; ++i) { - unsigned int idx = (latest + RB_LOGBOOK_COUNT - i) % RB_LOGBOOK_COUNT; - unsigned int offset = idx * RB_LOGBOOK_SIZE; - - // Get the ringbuffer pointers. - unsigned int begin = array_uint24_le (header + offset + 2); - unsigned int end = array_uint24_le (header + offset + 5); - if (begin < RB_PROFILE_BEGIN || - begin >= RB_PROFILE_END || - end < RB_PROFILE_BEGIN || - end >= RB_PROFILE_END) - { - ERROR (abstract->context, "Invalid ringbuffer pointer detected (0x%06x 0x%06x).", begin, end); - free (header); - return DC_STATUS_DATAFORMAT; - } - - // Calculate the profile length. - unsigned int length = RB_LOGBOOK_SIZE + RB_PROFILE_DISTANCE (begin, end) - 6; - - // Check the fingerprint data. - if (memcmp (header + offset + 9, device->fingerprint, sizeof (device->fingerprint)) == 0) - break; - - if (length > maxsize) - maxsize = length; - size += length; - ndives++; - } - - // Update and emit a progress event. - progress.maximum = (RB_LOGBOOK_SIZE * RB_LOGBOOK_COUNT) + size; - device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - - // Finish immediately if there are no dives available. - if (ndives == 0) { - free (header); - return DC_STATUS_SUCCESS; - } - - // Allocate enough memory for the largest dive. - unsigned char *profile = (unsigned char *) malloc (maxsize); - if (profile == NULL) { - ERROR (abstract->context, "Failed to allocate memory."); - free (header); - return DC_STATUS_NOMEMORY; - } - - // Download the dives. - for (unsigned int i = 0; i < ndives; ++i) { - unsigned int idx = (latest + RB_LOGBOOK_COUNT - i) % RB_LOGBOOK_COUNT; - unsigned int offset = idx * RB_LOGBOOK_SIZE; - - // Get the ringbuffer pointers. - unsigned int begin = array_uint24_le (header + offset + 2); - unsigned int end = array_uint24_le (header + offset + 5); - - // Calculate the profile length. - unsigned int length = RB_LOGBOOK_SIZE + RB_PROFILE_DISTANCE (begin, end) - 6; - - // Download the dive. - unsigned char number[1] = {idx}; - rc = hw_frog_transfer (device, &progress, DIVE, - number, sizeof (number), profile, length); - if (rc != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the dive."); - free (profile); - free (header); - return rc; - } - - // Verify the header in the logbook and profile are identical. - if (memcmp (profile, header + offset, RB_LOGBOOK_SIZE) != 0) { - ERROR (abstract->context, "Unexpected profile header."); - free (profile); - free (header); - return rc; - - } - - if (callback && !callback (profile, length, profile + 9, sizeof (device->fingerprint), userdata)) - break; - } - - free (profile); - free (header); - - return DC_STATUS_SUCCESS; -} - - -static dc_status_t -hw_frog_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) -{ - hw_frog_device_t *device = (hw_frog_device_t *) abstract; - - // Send the command. - unsigned char packet[6] = { - datetime->hour, datetime->minute, datetime->second, - datetime->month, datetime->day, datetime->year - 2000}; - dc_status_t rc = hw_frog_transfer (device, NULL, CLOCK, packet, sizeof (packet), NULL, 0); - if (rc != DC_STATUS_SUCCESS) - return rc; - - return DC_STATUS_SUCCESS; -} - - -dc_status_t -hw_frog_device_display (dc_device_t *abstract, const char *text) -{ - hw_frog_device_t *device = (hw_frog_device_t *) abstract; - - if (!ISINSTANCE (abstract)) - return DC_STATUS_INVALIDARGS; - - // Pad the data packet with spaces. - unsigned char packet[SZ_DISPLAY] = {0}; - if (hw_frog_strncpy (packet, sizeof (packet), text) != 0) { - ERROR (abstract->context, "Invalid parameter specified."); - return DC_STATUS_INVALIDARGS; - } - - // Send the command. - dc_status_t rc = hw_frog_transfer (device, NULL, DISPLAY, packet, sizeof (packet), NULL, 0); - if (rc != DC_STATUS_SUCCESS) - return rc; - - return DC_STATUS_SUCCESS; -} - - -dc_status_t -hw_frog_device_customtext (dc_device_t *abstract, const char *text) -{ - hw_frog_device_t *device = (hw_frog_device_t *) abstract; - - if (!ISINSTANCE (abstract)) - return DC_STATUS_INVALIDARGS; - - // Pad the data packet with spaces. - unsigned char packet[SZ_CUSTOMTEXT] = {0}; - if (hw_frog_strncpy (packet, sizeof (packet), text) != 0) { - ERROR (abstract->context, "Invalid parameter specified."); - return DC_STATUS_INVALIDARGS; - } - - // Send the command. - dc_status_t rc = hw_frog_transfer (device, NULL, CUSTOMTEXT, packet, sizeof (packet), NULL, 0); - if (rc != DC_STATUS_SUCCESS) - return rc; - - return DC_STATUS_SUCCESS; -} diff --git a/src/hw_frog.h b/src/hw_frog.h deleted file mode 100644 index 74cf5afb..00000000 --- a/src/hw_frog.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * libdivecomputer - * - * Copyright (C) 2012 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 HW_FROG_H -#define HW_FROG_H - -#include -#include -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -dc_status_t -hw_frog_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ -#endif /* HW_FROG_H */ diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index eebaeb85..fd0bd249 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -31,6 +31,7 @@ #include "aes.h" #include "platform.h" #include "packet.h" +#include "ringbuffer.h" #define ISINSTANCE(device) dc_device_isinstance((device), &hw_ostc3_device_vtable) @@ -40,15 +41,23 @@ (((major) & 0xFF) << 8) | \ ((minor) & 0xFF)) -#define SZ_DISPLAY 16 -#define SZ_CUSTOMTEXT 60 -#define SZ_VERSION (SZ_CUSTOMTEXT + 4) +#define SZ_DISPLAY_FROG 15 +#define SZ_DISPLAY_OSTC3 16 +#define SZ_DISPLAY_MAX SZ_DISPLAY_OSTC3 +#define SZ_CUSTOMTEXT_FROG 13 +#define SZ_CUSTOMTEXT_OSTC3 60 +#define SZ_CUSTOMTEXT_MAX SZ_CUSTOMTEXT_OSTC3 +#define SZ_VERSION_FROG (SZ_CUSTOMTEXT_FROG + 4) +#define SZ_VERSION_OSTC3 (SZ_CUSTOMTEXT_OSTC3 + 4) +#define SZ_VERSION_MAX SZ_VERSION_OSTC3 + #define SZ_HARDWARE 1 #define SZ_HARDWARE2 5 #define SZ_MEMORY 0x400000 #define SZ_CONFIG 4 #define SZ_FWINFO 4 -#define SZ_FIRMWARE 0x01E000 // 120KB +#define SZ_FIRMWARE_OSTC3 0x01E000 // 120KB +#define SZ_FIRMWARE_FROG 0x01D000 // 116KB #define SZ_FIRMWARE_BLOCK 0x1000 // 4KB #define SZ_FIRMWARE_BLOCK2 0x0100 // 256B #define FIRMWARE_AREA 0x3E0000 @@ -57,6 +66,10 @@ #define RB_LOGBOOK_SIZE_FULL 256 #define RB_LOGBOOK_COUNT 256 +#define RB_PROFILE_BEGIN 0x000000 +#define RB_PROFILE_END 0x200000 +#define RB_PROFILE_DISTANCE(a,b) ringbuffer_distance (a, b, DC_RINGBUFFER_EMPTY, RB_PROFILE_BEGIN, RB_PROFILE_END) + #define S_BLOCK_READ 0x20 #define S_BLOCK_WRITE 0x30 #define S_BLOCK_WRITE2 0x31 @@ -84,6 +97,7 @@ #define EXIT 0xFF #define INVALID 0xFFFFFFFF +#define UNDEFINED 0xFFFFFFFF #define OSTC4 0x43 #define OSTC5 0x44 @@ -91,19 +105,6 @@ #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_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_POINTERS 2 // 6 bytes -#define HDR_FULL_FIRMWARE 48 // 2 bytes - typedef enum hw_ostc3_state_t { OPEN, DOWNLOAD, @@ -114,6 +115,7 @@ typedef enum hw_ostc3_state_t { typedef struct hw_ostc3_device_t { dc_device_t base; dc_iostream_t *iostream; + unsigned int frog; unsigned int hardware; unsigned int feature; unsigned int model; @@ -129,23 +131,16 @@ typedef struct hw_ostc3_logbook_t { unsigned int fingerprint; unsigned int number; unsigned int version; + unsigned int pointers; + unsigned int firmware; } hw_ostc3_logbook_t; typedef struct hw_ostc3_firmware_t { - unsigned char data[SZ_FIRMWARE]; + unsigned int size; unsigned int checksum; + unsigned char data[]; } hw_ostc3_firmware_t; -// This key is used both for the OSTC3 and its cousin, -// the OSTC Sport. -// The Frog uses a similar protocol, and with another key. -static const unsigned char ostc3_key[16] = { - 0xF1, 0xE9, 0xB0, 0x30, - 0x45, 0x6F, 0xBE, 0x55, - 0xFF, 0xE7, 0xF8, 0x31, - 0x13, 0x6C, 0xF2, 0xFE -}; - static dc_status_t hw_ostc3_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); static dc_status_t hw_ostc3_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size); static dc_status_t hw_ostc3_device_write (dc_device_t *abstract, unsigned int address, const unsigned char data[], unsigned int size); @@ -168,20 +163,53 @@ 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 */ - HDR_COMPACT_LENGTH, /* profile */ - HDR_COMPACT_SUMMARY, /* fingerprint */ - HDR_COMPACT_NUMBER, /* number */ - HDR_COMPACT_VERSION, /* version */ + 0, /* profile */ + 3, /* fingerprint */ + 13, /* number */ + 15, /* version */ + UNDEFINED, /* pointers */ + UNDEFINED, /* firmware */ }; -static const hw_ostc3_logbook_t hw_ostc3_logbook_full = { - RB_LOGBOOK_SIZE_FULL, /* size */ - HDR_FULL_LENGTH, /* profile */ - HDR_FULL_SUMMARY, /* fingerprint */ - HDR_FULL_NUMBER, /* number */ - HDR_FULL_VERSION, /* version */ +static const hw_ostc3_logbook_t hw_ostc3_logbook_full[2] = { + /* OSTC3 */ + { + RB_LOGBOOK_SIZE_FULL, /* size */ + 9, /* profile */ + 12, /* fingerprint */ + 80, /* number */ + 8, /* version */ + 2, /* pointers */ + 48, /* firmware */ + }, + /* Frog */ + { + RB_LOGBOOK_SIZE_FULL, /* size */ + UNDEFINED, /* profile */ + 9, /* fingerprint */ + 52, /* number */ + 8, /* version */ + 2, /* pointers */ + 32, /* firmware */ + } }; +static const unsigned char hw_ostc3_key[2][16] = { + /* OSTC3 */ + { + 0xF1, 0xE9, 0xB0, 0x30, + 0x45, 0x6F, 0xBE, 0x55, + 0xFF, 0xE7, 0xF8, 0x31, + 0x13, 0x6C, 0xF2, 0xFE + }, + /* Frog */ + { + 0x6F, 0x55, 0xBE, 0x45, + 0x6C, 0xFE, 0xF2, 0x13, + 0xE7, 0x31, 0xF8, 0xFF, + 0xE9, 0x30, 0xB0, 0xF1 + } +}; static int hw_ostc3_strncpy (unsigned char *data, unsigned int size, const char *text) @@ -277,6 +305,7 @@ hw_ostc3_transfer (hw_ostc3_device_t *device, { dc_device_t *abstract = (dc_device_t *) device; dc_status_t status = DC_STATUS_SUCCESS; + const hw_ostc3_logbook_t *layout = &hw_ostc3_logbook_full[device->frog]; unsigned int length = osize; if (cmd == DIVE && length < RB_LOGBOOK_SIZE_FULL) @@ -296,22 +325,24 @@ hw_ostc3_transfer (hw_ostc3_device_t *device, return status; } - // Read the echo. - unsigned char echo[1] = {0}; - status = dc_iostream_read (device->iostream, echo, sizeof (echo), NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to receive the echo."); - return status; - } + if (!device->frog || (cmd != INIT && cmd != HEADER)) { + // Read the echo. + unsigned char echo[1] = {0}; + status = dc_iostream_read (device->iostream, echo, sizeof (echo), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the echo."); + return status; + } - // Verify the echo. - if (memcmp (echo, command, sizeof (command)) != 0) { - if (echo[0] == ready) { - ERROR (abstract->context, "Unsupported command."); - return DC_STATUS_UNSUPPORTED; - } else { - ERROR (abstract->context, "Unexpected echo."); - return DC_STATUS_PROTOCOL; + // Verify the echo. + if (memcmp (echo, command, sizeof (command)) != 0) { + if (echo[0] == ready) { + ERROR (abstract->context, "Unsupported command."); + return DC_STATUS_UNSUPPORTED; + } else { + ERROR (abstract->context, "Unexpected echo."); + return DC_STATUS_PROTOCOL; + } } } @@ -356,8 +387,9 @@ hw_ostc3_transfer (hw_ostc3_device_t *device, // 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 && + if (!device->frog && + array_isequal (output + layout->pointers, 6, 0x00) && + array_uint24_le (output + layout->profile) == 8 && length > RB_LOGBOOK_SIZE_FULL + 5) { length = RB_LOGBOOK_SIZE_FULL + 5; } @@ -412,7 +444,7 @@ hw_ostc3_transfer (hw_ostc3_device_t *device, dc_status_t -hw_ostc3_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) +hw_ostc3_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream, unsigned int frog) { dc_status_t status = DC_STATUS_SUCCESS; hw_ostc3_device_t *device = NULL; @@ -429,6 +461,7 @@ hw_ostc3_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *i } // Set the default values. + device->frog = frog; device->hardware = INVALID; device->feature = 0; device->model = 0; @@ -506,18 +539,22 @@ hw_ostc3_device_init_service (hw_ostc3_device_t *device) dc_status_t status = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; - const unsigned char command[] = {S_INIT, 0xAB, 0xCD, 0xEF}; + const unsigned char command[2][4] = { + {S_INIT, 0xAB, 0xCD, 0xEF}, // OSTC3 + {S_INIT, 0xAA, 0xAB, 0xAC}, // Frog + }; + const unsigned int echolen = device->frog ? 0 : sizeof(command[device->frog]) - 1; unsigned char answer[5] = {0}; // Send the command and service key. - status = dc_iostream_write (device->iostream, command, sizeof (command), NULL); + status = dc_iostream_write (device->iostream, command[device->frog], sizeof (command[device->frog]), NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the command."); return status; } // Read the response. - status = dc_iostream_read (device->iostream, answer, sizeof (answer), NULL); + status = dc_iostream_read (device->iostream, answer, 1 + echolen + 1, NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to receive the answer."); return status; @@ -525,8 +562,8 @@ hw_ostc3_device_init_service (hw_ostc3_device_t *device) // Verify the response to service mode. if (answer[0] != S_KEY || - memcmp (answer + 1, command + 1, sizeof(command) - 1) != 0 || - answer[4] != S_READY) { + memcmp (answer + 1, command[device->frog] + 1, echolen) != 0 || + answer[1 + echolen] != S_READY) { ERROR (abstract->context, "Failed to verify the answer."); return DC_STATUS_PROTOCOL; } @@ -596,14 +633,16 @@ hw_ostc3_device_init (hw_ostc3_device_t *device, hw_ostc3_state_t state) HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Hardware", hardware + hardware_offset, hardware_size); // Read the version information. - unsigned char version[SZ_VERSION] = {0}; - rc = hw_ostc3_transfer (device, NULL, IDENTITY, NULL, 0, version, sizeof(version), NULL, NODELAY); + unsigned char version[SZ_VERSION_MAX] = {0}; + const unsigned int versionsize = device->frog ? + SZ_VERSION_FROG : SZ_VERSION_OSTC3; + rc = hw_ostc3_transfer (device, NULL, IDENTITY, NULL, 0, version, versionsize, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the version information."); return rc; } - HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Version", version, sizeof(version)); + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Version", version, versionsize); // Cache the descriptor. device->hardware = array_uint16_be(hardware + 0); @@ -701,7 +740,10 @@ hw_ostc3_device_version (dc_device_t *abstract, unsigned char data[], unsigned i if (!ISINSTANCE (abstract)) return DC_STATUS_INVALIDARGS; - if (size != SZ_VERSION) + const unsigned int versionsize = device->frog ? + SZ_VERSION_FROG : SZ_VERSION_OSTC3; + + if (size != versionsize) return DC_STATUS_INVALIDARGS; dc_status_t rc = hw_ostc3_device_init (device, DOWNLOAD); @@ -787,12 +829,15 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi return rc; } + // Get the correct header layout. + const hw_ostc3_logbook_t *layout = &hw_ostc3_logbook_full[device->frog]; + // Get the correct logbook layout. const hw_ostc3_logbook_t *logbook = NULL; if (compact) { logbook = &hw_ostc3_logbook_compact; } else { - logbook = &hw_ostc3_logbook_full; + logbook = layout; } // Locate the most recent dive. @@ -831,13 +876,32 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi continue; } + // Calculate the profile length. - 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 + HDR_FULL_FIRMWARE); - if (firmware < OSTC3FW(0,93)) - length -= 3; + unsigned int length = 0; + if (device->frog) { + // Get the ringbuffer pointers. + unsigned int begin = array_uint24_le (header + offset + logbook->pointers); + unsigned int end = array_uint24_le (header + offset + logbook->pointers + 3); + if (begin < RB_PROFILE_BEGIN || + begin >= RB_PROFILE_END || + end < RB_PROFILE_BEGIN || + end >= RB_PROFILE_END) + { + ERROR (abstract->context, "Invalid ringbuffer pointer detected (0x%06x 0x%06x).", begin, end); + free (header); + return DC_STATUS_DATAFORMAT; + } + + length = RB_LOGBOOK_SIZE_FULL + RB_PROFILE_DISTANCE (begin, end) - 6; + } else { + 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 + logbook->firmware); + if (firmware < OSTC3FW(0,93)) + length -= 3; + } } if (length < RB_LOGBOOK_SIZE_FULL) { ERROR (abstract->context, "Invalid profile length (%u bytes).", length); @@ -880,12 +944,21 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi unsigned int offset = idx * logbook->size; // Calculate the profile length. - 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 + HDR_FULL_FIRMWARE); - if (firmware < OSTC3FW(0,93)) - length -= 3; + unsigned int length = 0; + if (device->frog) { + // Get the ringbuffer pointers. + unsigned int begin = array_uint24_le (header + offset + logbook->pointers); + unsigned int end = array_uint24_le (header + offset + logbook->pointers + 3); + + length = RB_LOGBOOK_SIZE_FULL + RB_PROFILE_DISTANCE (begin, end) - 6; + } else { + 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 + logbook->firmware); + if (firmware < OSTC3FW(0,93)) + length -= 3; + } } // Download the dive. @@ -900,11 +973,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 (memcmp (profile + HDR_FULL_VERSION, header + offset + logbook->version, 1) != 0 || + if (memcmp (profile + layout->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) { + memcmp (profile + layout->fingerprint, header + offset + logbook->fingerprint, 10) != 0 || + memcmp (profile + layout->number, header + offset + logbook->number, 2) != 0 : + memcmp (profile + layout->fingerprint, header + offset + layout->fingerprint, RB_LOGBOOK_SIZE_FULL - layout->fingerprint) != 0) { ERROR (abstract->context, "Unexpected profile header."); free (profile); free (header); @@ -922,8 +995,8 @@ 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 || - array_uint24_le (profile + RB_LOGBOOK_SIZE_FULL) + delta != array_uint24_le (profile + HDR_FULL_LENGTH)) { + } else if (!device->frog && (length < RB_LOGBOOK_SIZE_FULL + 5 || + array_uint24_le (profile + RB_LOGBOOK_SIZE_FULL) + delta != array_uint24_le (profile + layout->profile))) { // If there is more data available, then there should be a // valid profile header containing a length matching the // length in the dive header. @@ -931,7 +1004,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 + HDR_FULL_SUMMARY, sizeof (device->fingerprint), userdata)) + if (callback && !callback (profile, length, profile + layout->fingerprint, sizeof (device->fingerprint), userdata)) break; } @@ -971,9 +1044,12 @@ hw_ostc3_device_display (dc_device_t *abstract, const char *text) if (!ISINSTANCE (abstract)) return DC_STATUS_INVALIDARGS; + const unsigned int packetsize = device->frog ? + SZ_DISPLAY_FROG : SZ_DISPLAY_OSTC3; + // Pad the data packet with spaces. - unsigned char packet[SZ_DISPLAY] = {0}; - if (hw_ostc3_strncpy (packet, sizeof (packet), text) != 0) { + unsigned char packet[SZ_DISPLAY_MAX] = {0}; + if (hw_ostc3_strncpy (packet, packetsize, text) != 0) { ERROR (abstract->context, "Invalid parameter specified."); return DC_STATUS_INVALIDARGS; } @@ -983,7 +1059,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, NULL, NODELAY); + rc = hw_ostc3_transfer (device, NULL, DISPLAY, packet, packetsize, NULL, 0, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; @@ -999,9 +1075,12 @@ hw_ostc3_device_customtext (dc_device_t *abstract, const char *text) if (!ISINSTANCE (abstract)) return DC_STATUS_INVALIDARGS; + const unsigned int packetsize = device->frog ? + SZ_CUSTOMTEXT_FROG : SZ_CUSTOMTEXT_OSTC3; + // Pad the data packet with spaces. - unsigned char packet[SZ_CUSTOMTEXT] = {0}; - if (hw_ostc3_strncpy (packet, sizeof (packet), text) != 0) { + unsigned char packet[SZ_CUSTOMTEXT_MAX] = {0}; + if (hw_ostc3_strncpy (packet, packetsize, text) != 0) { ERROR (abstract->context, "Invalid parameter specified."); return DC_STATUS_INVALIDARGS; } @@ -1011,7 +1090,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, NULL, NODELAY); + rc = hw_ostc3_transfer (device, NULL, CUSTOMTEXT, packet, packetsize, NULL, 0, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; @@ -1167,7 +1246,7 @@ hw_ostc3_firmware_readline (FILE *fp, dc_context_t *context, unsigned int addr, static dc_status_t -hw_ostc3_firmware_readfile3 (hw_ostc3_firmware_t *firmware, dc_context_t *context, const char *filename) +hw_ostc3_firmware_readfile3 (hw_ostc3_firmware_t *firmware, dc_context_t *context, const char *filename, const unsigned char key[]) { dc_status_t rc = DC_STATUS_SUCCESS; FILE *fp = NULL; @@ -1183,7 +1262,7 @@ hw_ostc3_firmware_readfile3 (hw_ostc3_firmware_t *firmware, dc_context_t *contex } // Initialize the buffers. - memset (firmware->data, 0xFF, sizeof (firmware->data)); + memset (firmware->data, 0xFF, firmware->size); firmware->checksum = 0; fp = fopen (filename, "rb"); @@ -1201,9 +1280,9 @@ hw_ostc3_firmware_readfile3 (hw_ostc3_firmware_t *firmware, dc_context_t *contex bytes += 16; // Load the iv for AES-FCB-mode - AES128_ECB_encrypt (iv, ostc3_key, tmpbuf); + AES128_ECB_encrypt (iv, key, tmpbuf); - for (addr = 0; addr < SZ_FIRMWARE; addr += 16, bytes += 16) { + for (addr = 0; addr < firmware->size; addr += 16, bytes += 16) { rc = hw_ostc3_firmware_readline (fp, context, bytes, encrypted, sizeof(encrypted)); if (rc != DC_STATUS_SUCCESS) { ERROR (context, "Failed to parse file data."); @@ -1216,7 +1295,7 @@ hw_ostc3_firmware_readfile3 (hw_ostc3_firmware_t *firmware, dc_context_t *contex firmware->data[addr + i] = encrypted[i] ^ tmpbuf[i]; // Run the next round of encryption - AES128_ECB_encrypt (encrypted, ostc3_key, tmpbuf); + AES128_ECB_encrypt (encrypted, key, tmpbuf); } // This file format contains a tail with the checksum in @@ -1230,7 +1309,7 @@ hw_ostc3_firmware_readfile3 (hw_ostc3_firmware_t *firmware, dc_context_t *contex fclose (fp); unsigned int csum1 = array_uint32_le (checksum); - unsigned int csum2 = hw_ostc3_firmware_checksum (firmware->data, sizeof(firmware->data)); + unsigned int csum2 = hw_ostc3_firmware_checksum (firmware->data, firmware->size); if (csum1 != csum2) { ERROR (context, "Failed to verify file checksum."); return DC_STATUS_DATAFORMAT; @@ -1415,21 +1494,28 @@ hw_ostc3_device_fwupdate3 (dc_device_t *abstract, const char *filename) hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; dc_context_t *context = (abstract ? abstract->context : NULL); + // Firmware size. + const unsigned int firmwaresize = device->frog ? + SZ_FIRMWARE_FROG : SZ_FIRMWARE_OSTC3; + // Enable progress notifications. // load, erase, upload FZ, verify FZ, reprogram dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; - progress.maximum = 3 + SZ_FIRMWARE * 2 / SZ_FIRMWARE_BLOCK; + progress.maximum = 3 + firmwaresize * 2 / SZ_FIRMWARE_BLOCK; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); // Allocate memory for the firmware data. - hw_ostc3_firmware_t *firmware = (hw_ostc3_firmware_t *) malloc (sizeof (hw_ostc3_firmware_t)); + hw_ostc3_firmware_t *firmware = (hw_ostc3_firmware_t *) malloc (sizeof (hw_ostc3_firmware_t) + firmwaresize); if (firmware == NULL) { ERROR (context, "Failed to allocate memory."); return DC_STATUS_NOMEMORY; } + // Initialize the size. + firmware->size = firmwaresize; + // Read the hex file. - rc = hw_ostc3_firmware_readfile3 (firmware, context, filename); + rc = hw_ostc3_firmware_readfile3 (firmware, context, filename, hw_ostc3_key[device->frog]); if (rc != DC_STATUS_SUCCESS) { free (firmware); return rc; @@ -1441,7 +1527,7 @@ hw_ostc3_device_fwupdate3 (dc_device_t *abstract, const char *filename) hw_ostc3_device_display (abstract, " Erasing FW..."); - rc = hw_ostc3_firmware_erase (device, FIRMWARE_AREA, SZ_FIRMWARE); + rc = hw_ostc3_firmware_erase (device, FIRMWARE_AREA, firmware->size); if (rc != DC_STATUS_SUCCESS) { ERROR (context, "Failed to erase old firmware"); free (firmware); @@ -1454,9 +1540,9 @@ hw_ostc3_device_fwupdate3 (dc_device_t *abstract, const char *filename) hw_ostc3_device_display (abstract, " Uploading..."); - for (unsigned int len = 0; len < SZ_FIRMWARE; len += SZ_FIRMWARE_BLOCK) { - char msg[SZ_DISPLAY + 1]; // Status message on the display - dc_platform_snprintf (msg, sizeof(msg), " Uploading %2d%%", (100 * len) / SZ_FIRMWARE); + for (unsigned int len = 0; len < firmware->size; len += SZ_FIRMWARE_BLOCK) { + char msg[SZ_DISPLAY_MAX + 1]; // Status message on the display + dc_platform_snprintf (msg, sizeof(msg), " Uploading %2d%%", (100 * len) / firmware->size); hw_ostc3_device_display (abstract, msg); rc = hw_ostc3_firmware_block_write (device, FIRMWARE_AREA + len, firmware->data + len, SZ_FIRMWARE_BLOCK); @@ -1472,10 +1558,10 @@ hw_ostc3_device_fwupdate3 (dc_device_t *abstract, const char *filename) hw_ostc3_device_display (abstract, " Verifying..."); - for (unsigned int len = 0; len < SZ_FIRMWARE; len += SZ_FIRMWARE_BLOCK) { + for (unsigned int len = 0; len < firmware->size; len += SZ_FIRMWARE_BLOCK) { unsigned char block[SZ_FIRMWARE_BLOCK]; - char msg[SZ_DISPLAY + 1]; // Status message on the display - dc_platform_snprintf (msg, sizeof(msg), " Verifying %2d%%", (100 * len) / SZ_FIRMWARE); + char msg[SZ_DISPLAY_MAX + 1]; // Status message on the display + dc_platform_snprintf (msg, sizeof(msg), " Verifying %2d%%", (100 * len) / firmware->size); hw_ostc3_device_display (abstract, msg); rc = hw_ostc3_firmware_block_read (device, FIRMWARE_AREA + len, block, sizeof (block)); diff --git a/src/hw_ostc3.h b/src/hw_ostc3.h index 0fddc965..e042c44b 100644 --- a/src/hw_ostc3.h +++ b/src/hw_ostc3.h @@ -33,7 +33,7 @@ extern "C" { #endif /* __cplusplus */ dc_status_t -hw_ostc3_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); +hw_ostc3_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream, unsigned int frog); dc_status_t hw_ostc3_parser_create (dc_parser_t **out, dc_context_t *context, const unsigned char data[], size_t size, unsigned int model); diff --git a/src/libdivecomputer.symbols b/src/libdivecomputer.symbols index e04c01b8..46ad677c 100644 --- a/src/libdivecomputer.symbols +++ b/src/libdivecomputer.symbols @@ -139,9 +139,6 @@ hw_ostc_device_eeprom_write hw_ostc_device_reset hw_ostc_device_screenshot hw_ostc_device_fwupdate -hw_frog_device_version -hw_frog_device_display -hw_frog_device_customtext hw_ostc3_device_version hw_ostc3_device_hardware hw_ostc3_device_display diff --git a/src/parser.c b/src/parser.c index 6beb5a89..9a114bd8 100644 --- a/src/parser.c +++ b/src/parser.c @@ -42,7 +42,6 @@ #include "mares_iconhd.h" #include "mares_nemo.h" #include "mares_puck.h" -#include "hw_frog.h" #include "hw_ostc.h" #include "hw_ostc3.h" #include "cressi_edy.h" From 24a68b79db2cb1e38b615d04a93a3d13ae7006b3 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 6 Jul 2026 17:29:54 +0200 Subject: [PATCH 11/18] Use a symbolic constants for the commands --- src/shearwater_common.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/shearwater_common.c b/src/shearwater_common.c index deca525d..0e32908a 100644 --- a/src/shearwater_common.c +++ b/src/shearwater_common.c @@ -42,6 +42,15 @@ #define WDBI_REQUEST 0x2E #define WDBI_RESPONSE 0x6E +#define UPLOAD_INIT_REQUEST 0x35 +#define UPLOAD_INIT_RESPONSE 0x75 + +#define UPLOAD_DATA_REQUEST 0x36 +#define UPLOAD_DATA_RESPONSE 0x76 + +#define UPLOAD_EXIT_REQUEST 0x37 +#define UPLOAD_EXIT_RESPONSE 0x77 + #define NAK 0x7F dc_status_t @@ -396,7 +405,7 @@ shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buf unsigned int n = 0; unsigned char req_init[] = { - 0x35, + UPLOAD_INIT_REQUEST, (compression ? 0x10 : 0x00), 0x34, (address >> 24) & 0xFF, @@ -406,8 +415,8 @@ shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buf (size >> 16) & 0xFF, (size >> 8) & 0xFF, (size ) & 0xFF}; - unsigned char req_block[] = {0x36, 0x00}; - unsigned char req_quit[] = {0x37}; + unsigned char req_block[] = {UPLOAD_DATA_REQUEST, 0x00}; + unsigned char req_quit[] = {UPLOAD_EXIT_REQUEST}; unsigned char response[SZ_PACKET]; // Erase the current contents of the buffer. @@ -430,7 +439,7 @@ shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buf } // Verify the init response. - if (n != 3 || response[0] != 0x75 || response[1] != 0x10 || response[2] > SZ_PACKET) { + if (n != 3 || response[0] != UPLOAD_INIT_RESPONSE || response[1] != 0x10 || response[2] > SZ_PACKET) { ERROR (abstract->context, "Unexpected response packet."); return DC_STATUS_PROTOCOL; } @@ -454,7 +463,7 @@ shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buf } // Verify the block header. - if (n < 2 || response[0] != 0x76 || response[1] != block) { + if (n < 2 || response[0] != UPLOAD_DATA_RESPONSE || response[1] != block) { ERROR (abstract->context, "Unexpected response packet."); return DC_STATUS_PROTOCOL; } @@ -503,7 +512,7 @@ shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buf } // Verify the quit response. - if (n != 2 || response[0] != 0x77 || response[1] != 0x00) { + if (n != 2 || response[0] != UPLOAD_EXIT_RESPONSE || response[1] != 0x00) { ERROR (abstract->context, "Unexpected response packet."); return DC_STATUS_PROTOCOL; } From 661cb6e50215b3bb31f9a71ed115ef9bfad01015 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 6 Jul 2026 17:37:16 +0200 Subject: [PATCH 12/18] Handle variable sized packets The response to the upload init command contains the maximum length of the data packets which will be received in the next stage of the communication. This length field has a variable length and the number of bytes is indicated in the upper nibble of the second byte. Update the code to handle response packets with a multibyte length. --- src/shearwater_common.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/shearwater_common.c b/src/shearwater_common.c index 0e32908a..f9892bd6 100644 --- a/src/shearwater_common.c +++ b/src/shearwater_common.c @@ -433,13 +433,27 @@ shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buf } // Transfer the init request. - rc = shearwater_common_transfer (device, req_init, sizeof (req_init), response, 3, &n); + rc = shearwater_common_transfer (device, req_init, sizeof (req_init), response, sizeof(response), &n); if (rc != DC_STATUS_SUCCESS) { return rc; } // Verify the init response. - if (n != 3 || response[0] != UPLOAD_INIT_RESPONSE || response[1] != 0x10 || response[2] > SZ_PACKET) { + if (n < 2 || response[0] != UPLOAD_INIT_RESPONSE) { + ERROR (abstract->context, "Unexpected response packet."); + return DC_STATUS_PROTOCOL; + } + + // Verify the init response length. + unsigned int len = (response[1] & 0xF0) >> 4; + if (n < len + 2 || len > 4) { + ERROR (abstract->context, "Unexpected response packet."); + return DC_STATUS_PROTOCOL; + } + + // Verify the maximum packet length. + unsigned int maxlength = array_uint_be (response + 2, len); + if (maxlength > SZ_PACKET) { ERROR (abstract->context, "Unexpected response packet."); return DC_STATUS_PROTOCOL; } From b92409206145a1eee90046b345a810683d4b96ce Mon Sep 17 00:00:00 2001 From: Simone Carletti Date: Sun, 5 Jul 2026 18:49:12 +0200 Subject: [PATCH 13/18] Add Shearwater Perdix 3 support The Shearwater Perdix 3 uses a new variant of the communication protocol, with the following changes: - New BLE GATT service and characteristics: Service: 1aa44039-1667-4b29-87cc-dfecaaf31d97 Rx characteristic: e8460acd-e525-477d-bc50-c743e08d23f4 (write-without-response) Tx characteristic: cd5683d6-eb69-4012-9e5b-9083e419cef2 (notify) - The 2 byte BLE packet header containing the total number of frames and the current frame number is no longer present. - The header of each frame changed to support larger frames: - The length field changed from an 8 bit value to 16 bit value, increasing the total length of the header to 5 bytes. - The existing 0x00 byte moved to a different location. It's now before the length field instead of after the length field. - The block request command has an extra 0x00 byte. The new protocol variant is selected based on the model number of the Perdix 3. Co-authored-by: Steve Houle Co-authored-by: Jef Driesen --- src/descriptor.c | 2 + src/device.c | 2 +- src/shearwater_common.c | 100 +++++++++++++++++++++++++++----------- src/shearwater_common.h | 4 +- src/shearwater_petrel.c | 4 +- src/shearwater_petrel.h | 2 +- src/shearwater_predator.c | 2 +- 7 files changed, 81 insertions(+), 35 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index 0e3a03c1..ea8a653a 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -380,6 +380,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Shearwater", "Tern", DC_FAMILY_SHEARWATER_PETREL, 12, DC_TRANSPORT_BLE, dc_filter_shearwater}, {"Shearwater", "Tern TX", DC_FAMILY_SHEARWATER_PETREL, 12, DC_TRANSPORT_BLE, dc_filter_shearwater}, {"Shearwater", "Peregrine TX", DC_FAMILY_SHEARWATER_PETREL, 13, DC_TRANSPORT_BLE, dc_filter_shearwater}, + {"Shearwater", "Perdix 3", DC_FAMILY_SHEARWATER_PETREL, 14, DC_TRANSPORT_BLE, dc_filter_shearwater}, /* Dive Rite NiTek Q */ {"Dive Rite", "NiTek Q", DC_FAMILY_DIVERITE_NITEKQ, 0, DC_TRANSPORT_SERIAL, NULL}, /* Citizen Hyper Aqualand */ @@ -743,6 +744,7 @@ dc_filter_shearwater (const dc_descriptor_t *descriptor, dc_transport_t transpor "NERD 2", "Perdix", "Perdix 2", + "Perdix 3", "Teric", "Peregrine", "Peregrine TX", diff --git a/src/device.c b/src/device.c index 2cd4fc2d..46ef48ef 100644 --- a/src/device.c +++ b/src/device.c @@ -206,7 +206,7 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr rc = shearwater_predator_device_open (&device, context, iostream); break; case DC_FAMILY_SHEARWATER_PETREL: - rc = shearwater_petrel_device_open (&device, context, iostream); + rc = shearwater_petrel_device_open (&device, context, iostream, dc_descriptor_get_model (descriptor)); break; case DC_FAMILY_DIVERITE_NITEKQ: rc = diverite_nitekq_device_open (&device, context, iostream); diff --git a/src/shearwater_common.c b/src/shearwater_common.c index f9892bd6..112de5ef 100644 --- a/src/shearwater_common.c +++ b/src/shearwater_common.c @@ -28,7 +28,19 @@ #include "platform.h" #include "array.h" -#define SZ_PACKET 254 +// Protocol variants. +#define V1 1 +#define V2 2 + +// Maximum packet payload size. +// The V1 protocol uses an 8 bit length field, while the V2 protocol +// uses a 16 bit length field. Theoretically the V2 maximum size is +// 2^16-1 bytes, but the largest observed value is much smaller (0x0202 +// for the manifest and 0x0902 for the dive). Allow for an extra margin +// for future expansions. +#define SZ_PACKET_V1 254 +#define SZ_PACKET_V2 4096 +#define SZ_PACKET_MAX SZ_PACKET_V2 // SLIP special character codes #define END 0xC0 @@ -54,7 +66,7 @@ #define NAK 0x7F dc_status_t -shearwater_common_setup (shearwater_common_device_t *device, dc_context_t *context, dc_iostream_t *iostream) +shearwater_common_setup (shearwater_common_device_t *device, dc_context_t *context, dc_iostream_t *iostream, unsigned int model) { dc_status_t status = DC_STATUS_SUCCESS; @@ -78,6 +90,8 @@ shearwater_common_setup (shearwater_common_device_t *device, dc_context_t *conte dc_iostream_sleep (device->iostream, 300); dc_iostream_purge (device->iostream, DC_DIRECTION_ALL); + device->protocol = (model == PERDIX3) ? V2 : V1; + return DC_STATUS_SUCCESS; } @@ -145,10 +159,11 @@ shearwater_common_slip_write (shearwater_common_device_t *device, const unsigned { dc_status_t status = DC_STATUS_SUCCESS; dc_transport_t transport = dc_iostream_get_transport(device->iostream); + const unsigned int header = transport == DC_TRANSPORT_BLE && device->protocol != V2; unsigned char buffer[32]; unsigned int nbytes = 0; - if (transport == DC_TRANSPORT_BLE) { + if (header) { // Calculate the total number of bytes. unsigned int count = 1; for (unsigned int i = 0; i < size; ++i) { @@ -183,7 +198,7 @@ shearwater_common_slip_write (shearwater_common_device_t *device, const unsigned return status; } - if (transport == DC_TRANSPORT_BLE) { + if (header) { buffer[1]++; nbytes = 2; } else { @@ -210,7 +225,7 @@ shearwater_common_slip_write (shearwater_common_device_t *device, const unsigned return status; } - if (transport == DC_TRANSPORT_BLE) { + if (header) { buffer[1]++; nbytes = 2; } else { @@ -238,6 +253,7 @@ shearwater_common_slip_read (shearwater_common_device_t *device, unsigned char d { dc_status_t status = DC_STATUS_SUCCESS; dc_transport_t transport = dc_iostream_get_transport(device->iostream); + const unsigned int header = transport == DC_TRANSPORT_BLE && device->protocol != V2; unsigned char buffer[256]; unsigned int escaped = 0; unsigned int nbytes = 0; @@ -258,7 +274,7 @@ shearwater_common_slip_read (shearwater_common_device_t *device, unsigned char d } size_t offset = 0; - if (transport == DC_TRANSPORT_BLE) { + if (header) { if (transferred < 2) { ERROR (device->base.context, "Invalid packet length (" DC_PRINTF_SIZE ").", transferred); return DC_STATUS_PROTOCOL; @@ -339,10 +355,12 @@ shearwater_common_transfer (shearwater_common_device_t *device, const unsigned c { dc_status_t status = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; - unsigned char packet[SZ_PACKET + 4]; + const unsigned int maxpacket = (device->protocol == V2) ? SZ_PACKET_V2 : SZ_PACKET_V1; + const unsigned int headerlen = (device->protocol == V2) ? 5 : 4; + unsigned char packet[SZ_PACKET_MAX + 5]; unsigned int n = 0; - if (isize > SZ_PACKET || osize > SZ_PACKET) + if (isize > maxpacket || osize > maxpacket) return DC_STATUS_INVALIDARGS; if (device_is_cancelled (abstract)) @@ -351,12 +369,19 @@ shearwater_common_transfer (shearwater_common_device_t *device, const unsigned c // Setup the request packet. packet[0] = 0xFF; packet[1] = 0x01; - packet[2] = isize + 1; - packet[3] = 0x00; - memcpy (packet + 4, input, isize); + if (device->protocol == V2) { + // FF 01 00 [len_hi] [len_lo] + packet[2] = 0x00; + array_uint16_be_set (packet + 3, isize); + } else { + // FF 01 [len+1] 00 + packet[2] = isize + 1; + packet[3] = 0x00; + } + memcpy (packet + headerlen, input, isize); // Send the request packet. - status = shearwater_common_slip_write (device, packet, isize + 4); + status = shearwater_common_slip_write (device, packet, isize + headerlen); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the request packet."); return status; @@ -377,21 +402,33 @@ shearwater_common_transfer (shearwater_common_device_t *device, const unsigned c } // Validate the packet header. - if (n < 4 || packet[0] != 0x01 || packet[1] != 0xFF || packet[3] != 0x00) { + unsigned int idx = (device->protocol == V2) ? 2 : 3; + if (n < headerlen || packet[0] != 0x01 || packet[1] != 0xFF || packet[idx] != 0x00) { ERROR (abstract->context, "Invalid packet header."); return DC_STATUS_PROTOCOL; } // Validate the packet length. - unsigned int length = packet[2]; - if (length < 1 || length - 1 + 4 != n || length - 1 > osize) { + unsigned int length = 0; + if (device->protocol == V2) { + length = array_uint16_be (packet + 3); + } else { + length = packet[2]; + if (length < 1) { + ERROR (abstract->context, "Invalid packet header."); + return DC_STATUS_PROTOCOL; + } + + length -= 1; + } + if (length + headerlen != n || length > osize) { ERROR (abstract->context, "Invalid packet header."); return DC_STATUS_PROTOCOL; } - memcpy (output, packet + 4, length - 1); + memcpy (output, packet + headerlen, length); if (actual) - *actual = length - 1; + *actual = length; return DC_STATUS_SUCCESS; } @@ -402,6 +439,8 @@ shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buf { dc_device_t *abstract = (dc_device_t *) device; dc_status_t rc = DC_STATUS_SUCCESS; + const unsigned int maxpacket = (device->protocol == V2) ? SZ_PACKET_V2 : SZ_PACKET_V1; + unsigned char response[SZ_PACKET_MAX]; unsigned int n = 0; unsigned char req_init[] = { @@ -415,9 +454,10 @@ shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buf (size >> 16) & 0xFF, (size >> 8) & 0xFF, (size ) & 0xFF}; - unsigned char req_block[] = {UPLOAD_DATA_REQUEST, 0x00}; + unsigned char req_block[] = {UPLOAD_DATA_REQUEST, 0x00, 0x00}; unsigned char req_quit[] = {UPLOAD_EXIT_REQUEST}; - unsigned char response[SZ_PACKET]; + unsigned int req_block_len = (device->protocol == V2) ? + sizeof(req_block) : sizeof(req_block) - 1; // Erase the current contents of the buffer. if (!dc_buffer_clear (buffer)) { @@ -433,7 +473,7 @@ shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buf } // Transfer the init request. - rc = shearwater_common_transfer (device, req_init, sizeof (req_init), response, sizeof(response), &n); + rc = shearwater_common_transfer (device, req_init, sizeof (req_init), response, maxpacket, &n); if (rc != DC_STATUS_SUCCESS) { return rc; } @@ -453,7 +493,7 @@ shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buf // Verify the maximum packet length. unsigned int maxlength = array_uint_be (response + 2, len); - if (maxlength > SZ_PACKET) { + if (maxlength > maxpacket) { ERROR (abstract->context, "Unexpected response packet."); return DC_STATUS_PROTOCOL; } @@ -471,7 +511,7 @@ shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buf while (nbytes < size && !done) { // Transfer the block request. req_block[1] = block; - rc = shearwater_common_transfer (device, req_block, sizeof (req_block), response, sizeof (response), &n); + rc = shearwater_common_transfer (device, req_block, req_block_len, response, maxpacket, &n); if (rc != DC_STATUS_SUCCESS) { return rc; } @@ -520,7 +560,7 @@ shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buf } // Transfer the quit request. - rc = shearwater_common_transfer (device, req_quit, sizeof (req_quit), response, 2, &n); + rc = shearwater_common_transfer (device, req_quit, sizeof (req_quit), response, maxpacket, &n); if (rc != DC_STATUS_SUCCESS) { return rc; } @@ -547,6 +587,8 @@ shearwater_common_rdbi (shearwater_common_device_t *device, unsigned int id, uns { dc_status_t status = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; + const unsigned int maxpacket = (device->protocol == V2) ? SZ_PACKET_V2 : SZ_PACKET_V1; + unsigned char response[SZ_PACKET_MAX]; // Transfer the request. unsigned int n = 0; @@ -554,8 +596,7 @@ shearwater_common_rdbi (shearwater_common_device_t *device, unsigned int id, uns RDBI_REQUEST, (id >> 8) & 0xFF, (id ) & 0xFF}; - unsigned char response[SZ_PACKET]; - status = shearwater_common_transfer (device, request, sizeof (request), response, sizeof (response), &n); + status = shearwater_common_transfer (device, request, sizeof (request), response, maxpacket, &n); if (status != DC_STATUS_SUCCESS) { return status; } @@ -600,22 +641,23 @@ shearwater_common_wdbi (shearwater_common_device_t *device, unsigned int id, con { dc_status_t status = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; + const unsigned int maxpacket = (device->protocol == V2) ? SZ_PACKET_V2 : SZ_PACKET_V1; + unsigned char response[SZ_PACKET_MAX]; - if (size + 3 > SZ_PACKET) { + if (size + 3 > maxpacket) { return DC_STATUS_INVALIDARGS; } // Transfer the request. unsigned int n = 0; - unsigned char request[SZ_PACKET] = { + unsigned char request[SZ_PACKET_MAX] = { WDBI_REQUEST, (id >> 8) & 0xFF, (id ) & 0xFF}; if (size) { memcpy (request + 3, data, size); } - unsigned char response[SZ_PACKET]; - status = shearwater_common_transfer (device, request, size + 3, response, sizeof (response), &n); + status = shearwater_common_transfer (device, request, size + 3, response, maxpacket, &n); if (status != DC_STATUS_SUCCESS) { return status; } diff --git a/src/shearwater_common.h b/src/shearwater_common.h index e7940f08..28beb5bd 100644 --- a/src/shearwater_common.h +++ b/src/shearwater_common.h @@ -54,6 +54,7 @@ extern "C" { #define PERDIX2 11 #define TERN 12 #define PEREGRINE_TX 13 +#define PERDIX3 14 #define NSTEPS 10000 #define STEP(i,n) ((NSTEPS * (i) + (n) / 2) / (n)) @@ -61,10 +62,11 @@ extern "C" { typedef struct shearwater_common_device_t { dc_device_t base; dc_iostream_t *iostream; + unsigned int protocol; } shearwater_common_device_t; dc_status_t -shearwater_common_setup (shearwater_common_device_t *device, dc_context_t *context, dc_iostream_t *iostream); +shearwater_common_setup (shearwater_common_device_t *device, dc_context_t *context, dc_iostream_t *iostream, unsigned int model); dc_status_t shearwater_common_transfer (shearwater_common_device_t *device, const unsigned char input[], unsigned int isize, unsigned char output[], unsigned int osize, unsigned int *actual); diff --git a/src/shearwater_petrel.c b/src/shearwater_petrel.c index 56f90feb..c1cc880f 100644 --- a/src/shearwater_petrel.c +++ b/src/shearwater_petrel.c @@ -78,7 +78,7 @@ str2num (unsigned char data[], unsigned int size, unsigned int offset) dc_status_t -shearwater_petrel_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) +shearwater_petrel_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream, unsigned int model) { dc_status_t status = DC_STATUS_SUCCESS; shearwater_petrel_device_t *device = NULL; @@ -97,7 +97,7 @@ shearwater_petrel_device_open (dc_device_t **out, dc_context_t *context, dc_iost memset (device->fingerprint, 0, sizeof (device->fingerprint)); // Setup the device. - status = shearwater_common_setup (&device->base, context, iostream); + status = shearwater_common_setup (&device->base, context, iostream, model); if (status != DC_STATUS_SUCCESS) { goto error_free; } diff --git a/src/shearwater_petrel.h b/src/shearwater_petrel.h index 166007e1..aed4cce4 100644 --- a/src/shearwater_petrel.h +++ b/src/shearwater_petrel.h @@ -32,7 +32,7 @@ extern "C" { #endif /* __cplusplus */ dc_status_t -shearwater_petrel_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); +shearwater_petrel_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream, unsigned int model); dc_status_t shearwater_petrel_parser_create (dc_parser_t **parser, dc_context_t *context, const unsigned char data[], size_t size, unsigned int model); diff --git a/src/shearwater_predator.c b/src/shearwater_predator.c index fffd511f..ca2df7c2 100644 --- a/src/shearwater_predator.c +++ b/src/shearwater_predator.c @@ -81,7 +81,7 @@ shearwater_predator_device_open (dc_device_t **out, dc_context_t *context, dc_io memset (device->fingerprint, 0, sizeof (device->fingerprint)); // Setup the device. - status = shearwater_common_setup (&device->base, context, iostream); + status = shearwater_common_setup (&device->base, context, iostream, PREDATOR); if (status != DC_STATUS_SUCCESS) { goto error_free; } From 06120b0a3ada355e93ba87b3dfdbffa2bc71653b Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sat, 11 Jul 2026 12:42:11 +0200 Subject: [PATCH 14/18] Update the minimum and maximum BLE packet size For maximum compatibility, use the minimum ATT MTU size (23 bytes) for sending and the maximum ATT MTU size (517 bytes) for receiving. The corresponding payload size is always 3 bytes smaller (20 and 514 bytes). --- src/shearwater_common.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/shearwater_common.c b/src/shearwater_common.c index 112de5ef..ef3e71fc 100644 --- a/src/shearwater_common.c +++ b/src/shearwater_common.c @@ -28,6 +28,9 @@ #include "platform.h" #include "array.h" +#define BLE_MTU_MIN 20 +#define BLE_MTU_MAX 514 + // Protocol variants. #define V1 1 #define V2 2 @@ -160,7 +163,7 @@ shearwater_common_slip_write (shearwater_common_device_t *device, const unsigned dc_status_t status = DC_STATUS_SUCCESS; dc_transport_t transport = dc_iostream_get_transport(device->iostream); const unsigned int header = transport == DC_TRANSPORT_BLE && device->protocol != V2; - unsigned char buffer[32]; + unsigned char buffer[BLE_MTU_MIN]; unsigned int nbytes = 0; if (header) { @@ -254,7 +257,7 @@ shearwater_common_slip_read (shearwater_common_device_t *device, unsigned char d dc_status_t status = DC_STATUS_SUCCESS; dc_transport_t transport = dc_iostream_get_transport(device->iostream); const unsigned int header = transport == DC_TRANSPORT_BLE && device->protocol != V2; - unsigned char buffer[256]; + unsigned char buffer[BLE_MTU_MAX]; unsigned int escaped = 0; unsigned int nbytes = 0; From 171067ef9772a1f726a082afc24ddda7822863cf Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 29 Jul 2026 18:19:52 +0200 Subject: [PATCH 15/18] Fix the Seac Tablet memory size The Seac Tablet has twice the amount of flash memory compared to the older models. --- src/seac_screen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seac_screen.c b/src/seac_screen.c index 9632f097..be33a794 100644 --- a/src/seac_screen.c +++ b/src/seac_screen.c @@ -135,7 +135,7 @@ static const seac_screen_layout_t layout_screen = { static const seac_screen_layout_t layout_tablet = { 0x0A0000, /* rb_profile_begin */ - 0x200000, /* rb_profile_end */ + 0x400000, /* rb_profile_end */ }; static dc_status_t From 8cd68faf6ad8a3aa16d069bc0ced084c9db23bbc Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 17 Jul 2026 17:13:07 +0200 Subject: [PATCH 16/18] Remove deprecated AC_PROG_CC_C99 autoconf macro Since autoconf 2.70, the AC_PROG_CC_C99 macro is deprecated and can be replaced with the AC_PROG_CC macro, which will automatically check for C99 or later. For backwards compatibility, the use of the macro is preserved for older autoconf versions. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 8d253852..b5a0bff9 100644 --- a/configure.ac +++ b/configure.ac @@ -62,7 +62,7 @@ AM_CONDITIONAL([ENABLE_DOC], [test "x$enable_doc" = "xyes"]) # Checks for programs. AC_PROG_CC -AC_PROG_CC_C99 +m4_version_prereq([2.70], [], [AC_PROG_CC_C99]) AC_CHECK_PROGS([DOXYGEN], [doxygen]) AC_CHECK_PROGS([MANDOC], [mandoc]) From 1a6e16508c0bc57a848a0d2299d000ebfa0c6bba Mon Sep 17 00:00:00 2001 From: Simone Carletti Date: Wed, 12 Aug 2026 11:07:39 +0200 Subject: [PATCH 17/18] Fix the closing tags of the location elements The latitude and altitude elements are written with a second opening tag instead of a closing tag, in both the dive location and the sample location output. The resulting document is not well-formed XML. --- examples/output_xml.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/output_xml.c b/examples/output_xml.c index 66dd7723..2354f260 100644 --- a/examples/output_xml.c +++ b/examples/output_xml.c @@ -182,9 +182,9 @@ sample_cb (dc_sample_type_t type, const dc_sample_value_t *value, void *userdata case DC_SAMPLE_LOCATION: fprintf (sampledata->ostream, " \n" - " %.6f\n" + " %.6f\n" " %.6f\n" - " %.3f\n" + " %.3f\n" " \n", value->location.latitude, value->location.longitude, @@ -497,9 +497,9 @@ dctool_xml_output_write (dctool_output_t *abstract, dc_parser_t *parser, const u if (status != DC_STATUS_UNSUPPORTED) { fprintf (output->ostream, "\n" - " %.6f\n" + " %.6f\n" " %.6f\n" - " %.2f\n" + " %.2f\n" "\n", location.latitude, location.longitude, From ac7e59925818786f5824f9b912628d3a662d611b Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Fri, 21 Aug 2026 14:47:18 +1200 Subject: [PATCH 18/18] Fix build errors from libdivecomputer master merge Three issues introduced by the merge of libdivecomputer/master: 1. parser.h: DC_SAMPLE_LOCATION was added to parsers (divesoft_freedom, divesystem_idive, halcyon_symbios, shearwater_predator) but the corresponding enum value was omitted from dc_sample_type_t. Add DC_SAMPLE_LOCATION after DC_SAMPLE_TTS and add the matching #define guard for compile-time feature testing. 2. shearwater_predator_parser.c (DC_FIELD_LOCATION case): latitude and longitude were used but never declared. The upstream version declared them as signed int inside a block; reproduce that with an explicit block scope so the variables are properly declared. 3. shearwater_petrel.c: shearwater_common_get_model() returns an unsigned int, but HEXDUMP expects const unsigned char *. Add an explicit cast to silence the incompatible-pointer-type error. Signed-off-by: Michael Keller --- include/libdivecomputer/parser.h | 3 +++ src/hw_ostc3.c | 37 ++++++++++++++++++++------------ src/shearwater_common.c | 15 ++++++++++++- src/shearwater_common.h | 4 +--- src/shearwater_petrel.c | 10 ++------- src/shearwater_predator_parser.c | 21 +++++++++--------- 6 files changed, 53 insertions(+), 37 deletions(-) diff --git a/include/libdivecomputer/parser.h b/include/libdivecomputer/parser.h index 7466a934..d3608e1d 100644 --- a/include/libdivecomputer/parser.h +++ b/include/libdivecomputer/parser.h @@ -48,10 +48,13 @@ typedef enum dc_sample_type_t { DC_SAMPLE_DECO, DC_SAMPLE_GASMIX, DC_SAMPLE_TTS, // time to surface in seconds + DC_SAMPLE_LOCATION, } dc_sample_type_t; // Make it easy to test support compile-time with "#ifdef DC_SAMPLE_TTS" #define DC_SAMPLE_TTS DC_SAMPLE_TTS +// Make it easy to test support compile-time with "#ifdef DC_SAMPLE_LOCATION" +#define DC_SAMPLE_LOCATION DC_SAMPLE_LOCATION typedef enum dc_field_type_t { DC_FIELD_DIVETIME, diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 66ae93b1..0e48b799 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -866,18 +866,27 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi // Download the compact logbook headers. If the firmware doesn't support // compact headers yet, fallback to downloading the full logbook headers. // 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, NULL, NODELAY); - if (rc == DC_STATUS_UNSUPPORTED) { - compact = 0; + // The Frog always uses the full header format. + unsigned int compact = !device->frog; + if (compact) { + rc = hw_ostc3_transfer (device, &progress, COMPACT, + NULL, 0, header, RB_LOGBOOK_SIZE_COMPACT * RB_LOGBOOK_COUNT, NULL, NODELAY); + if (rc == DC_STATUS_UNSUPPORTED) { + compact = 0; + } else if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the header."); + free (header); + return rc; + } + } + if (!compact) { rc = hw_ostc3_transfer (device, &progress, HEADER, 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."); - free (header); - return rc; + if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the header."); + free (header); + return rc; + } } // Get the correct header layout. @@ -1025,10 +1034,10 @@ 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 (memcmp (profile + layout->version, header + offset + logbook->version, 1) != 0 || - compact ? - memcmp (profile + layout->fingerprint, header + offset + logbook->fingerprint, 10) != 0 || - memcmp (profile + layout->number, header + offset + logbook->number, 2) != 0 : - memcmp (profile + layout->fingerprint, header + offset + layout->fingerprint, RB_LOGBOOK_SIZE_FULL - layout->fingerprint) != 0) { + (compact ? + (memcmp (profile + layout->fingerprint, header + offset + logbook->fingerprint, 10) != 0 || + memcmp (profile + layout->number, header + offset + logbook->number, 2) != 0) : + memcmp (profile + layout->fingerprint, header + offset + layout->fingerprint, RB_LOGBOOK_SIZE_FULL - layout->fingerprint) != 0)) { ERROR (abstract->context, "Unexpected profile header."); free (profile); free (header); diff --git a/src/shearwater_common.c b/src/shearwater_common.c index 2d75fb6e..0777ea75 100644 --- a/src/shearwater_common.c +++ b/src/shearwater_common.c @@ -846,8 +846,21 @@ dc_status_t shearwater_common_get_model(shearwater_common_device_t *device, unsi *model = TERN; break; default: - WARNING (device->base.context, "Unknown hardware type 0x%04x.", hardware); + // Unknown hardware type: fall back to reading the model number directly from the device. + WARNING (device->base.context, "Unknown hardware type 0x%04x, falling back to ID_MODEL.", hardware); + { + unsigned char rsp_model = 0; + dc_status_t rc = shearwater_common_rdbi (device, ID_MODEL, &rsp_model, sizeof(rsp_model), NULL); + if (rc != DC_STATUS_SUCCESS) { + ERROR (device->base.context, "Failed to read the model number."); + return rc; + } + *model = rsp_model; + } + break; } return status; } + + diff --git a/src/shearwater_common.h b/src/shearwater_common.h index 1d7b00c7..9808875b 100644 --- a/src/shearwater_common.h +++ b/src/shearwater_common.h @@ -89,13 +89,11 @@ shearwater_common_timesync_local (shearwater_common_device_t *device, const dc_d dc_status_t shearwater_common_timesync_utc (shearwater_common_device_t *device, const dc_datetime_t *datetime); -dc_status_t shearwater_common_get_model(shearwater_common_device_t *device, unsigned int *model); - dc_status_t shearwater_common_can_wdbi (shearwater_common_device_t *device, dc_buffer_t *buffer, unsigned int id); dc_status_t shearwater_common_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime); -dc_status_t shearwater_common_read_model(shearwater_common_device_t *device, unsigned int *model); +dc_status_t shearwater_common_get_model(shearwater_common_device_t *device, unsigned int *model); #ifdef __cplusplus } diff --git a/src/shearwater_petrel.c b/src/shearwater_petrel.c index 7c3086e2..3a9af132 100644 --- a/src/shearwater_petrel.c +++ b/src/shearwater_petrel.c @@ -192,12 +192,8 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call unsigned int model = 0; rc = shearwater_common_get_model (&device->base, &model); - if (rc != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the model number."); + if (rc != DC_STATUS_SUCCESS) return rc; - } - - HEXDUMP(abstract->context, DC_LOGLEVEL_DEBUG, "Model", &model, sizeof(model)); // Emit a device info event. dc_event_devinfo_t devinfo; @@ -365,10 +361,8 @@ shearwater_petrel_device_timesync (dc_device_t *abstract, const dc_datetime_t *d unsigned int model = 0; status = shearwater_common_get_model (device, &model); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the model number."); + if (status != DC_STATUS_SUCCESS) return status; - } if (model == TERIC) { return shearwater_common_timesync_utc (device, datetime); diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index bb82e36e..10b2f13a 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -126,9 +126,6 @@ #define UNDEFINED 0xFFFFFFFF -#define GNSS_FIX_2D 2 -#define GNSS_FIX_3D 3 - typedef struct shearwater_predator_parser_t shearwater_predator_parser_t; typedef struct shearwater_predator_gasmix_t { @@ -1120,15 +1117,17 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ if (parser->opening[9] == UNDEFINED || parser->aimode != AI_ON_GPS) return DC_STATUS_UNSUPPORTED; - unsigned int gnss_status = data[parser->opening[9] + 16]; - if (!(gnss_status == GNSS_FIX_2D || gnss_status == GNSS_FIX_3D)) - return DC_STATUS_UNSUPPORTED; + { + unsigned int gnss_status = data[parser->opening[9] + 16]; + if (!(gnss_status == GNSS_FIX_2D || gnss_status == GNSS_FIX_3D)) + return DC_STATUS_UNSUPPORTED; - latitude = (signed int) array_uint32_be (data + parser->opening[9] + 21); - longitude = (signed int) array_uint32_be (data + parser->opening[9] + 25); - location->latitude = latitude / 100000.0; - location->longitude = longitude / 100000.0; - location->altitude = 0.0; + signed int latitude = (signed int) array_uint32_be (data + parser->opening[9] + 21); + signed int longitude = (signed int) array_uint32_be (data + parser->opening[9] + 25); + location->latitude = latitude / 100000.0; + location->longitude = longitude / 100000.0; + location->altitude = 0.0; + } break; case DC_FIELD_STRING: return dc_field_get_string(&parser->cache, flags, string);