From 12039a8c214e5ef7c5ef720f75c6bc73eb9b835e Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 30 Jun 2025 22:20:46 +0200 Subject: [PATCH 01/24] Post release version bump to 0.10.0 --- configure.ac | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index cfb2f2e0..8d253852 100644 --- a/configure.ac +++ b/configure.ac @@ -1,8 +1,8 @@ # Versioning. m4_define([dc_version_major],[0]) -m4_define([dc_version_minor],[9]) +m4_define([dc_version_minor],[10]) m4_define([dc_version_micro],[0]) -m4_define([dc_version_suffix],[]) +m4_define([dc_version_suffix],[devel]) m4_define([dc_version],dc_version_major.dc_version_minor.dc_version_micro[]m4_ifset([dc_version_suffix],-[dc_version_suffix])) # Libtool versioning. From 1c7b28fbde7e498ceb481ea0cc3ad9264c97b621 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 2 Jul 2025 19:36:48 +0200 Subject: [PATCH 02/24] Ignore empty samples Some Cosmiq dive computers appear to produce dive profiles where the last part (or even the entire profile) is filled with samples containing only 0xFF bytes. Trying to interprete those bytes as a valid sample results in depth and temperature values that are clearly invalid. As a workaround, ignore such samples. --- src/deepblu_cosmiq_parser.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/deepblu_cosmiq_parser.c b/src/deepblu_cosmiq_parser.c index 95b5ae78..635a446a 100644 --- a/src/deepblu_cosmiq_parser.c +++ b/src/deepblu_cosmiq_parser.c @@ -191,6 +191,12 @@ deepblu_cosmiq_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback unsigned int time = 0; unsigned int offset = SZ_HEADER; while (offset + SZ_SAMPLE <= size) { + // Ignore empty samples. + if (array_isequal (data + offset, SZ_SAMPLE, 0xFF)) { + offset += SZ_SAMPLE; + continue; + } + dc_sample_value_t sample = {0}; unsigned int temperature = array_uint16_le(data + offset + 0); unsigned int depth = array_uint16_le(data + offset + 2); From 04eff7b30e83920747460d1912fc7cea4e13ec67 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 1 Jul 2025 21:33:53 +0200 Subject: [PATCH 03/24] Fix an error in the OSTC 4 firmware macro So far this error didn't cause any problems because the macro is only used with a minor value of zero. --- src/hw_ostc_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index 5e0c7ca3..e0be0cbe 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -78,7 +78,7 @@ #define OSTC4FW(major,minor,micro,beta) ( \ (((major) & 0x1F) << 11) | \ - (((minor) & 0x1F) >> 6) | \ + (((minor) & 0x1F) << 6) | \ (((micro) & 0x1F) << 1) | \ ((beta) & 0x01)) From c3f23a42b51fcd964683e62d76b1500c02bacc1a Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 1 Jul 2025 21:34:55 +0200 Subject: [PATCH 04/24] Log the firmware version --- src/hw_ostc_parser.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index e0be0cbe..77dfda2f 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -797,8 +797,18 @@ hw_ostc_parser_internal_foreach (hw_ostc_parser_t *parser, dc_sample_callback_t unsigned int firmware = 0; if (parser->model == OSTC4) { firmware = array_uint16_le (data + layout->firmware); + DEBUG (abstract->context, "Device: firmware=%u (%u.%u.%u.%u)", + firmware, + (firmware >> 11) & 0x1F, + (firmware >> 6) & 0x1F, + (firmware >> 1) & 0x1F, + (firmware ) & 0x01); } else { firmware = array_uint16_be (data + layout->firmware); + DEBUG (abstract->context, "Device: firmware=%u (%u.%u)", + firmware, + (firmware >> 8) & 0xFF, + (firmware ) & 0xFF); } // Get the dive mode. From adadc1991de87872e29d83e9bd392de3fe4a29cf Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 8 Jul 2025 22:34:01 +0200 Subject: [PATCH 05/24] Add support for new OSTC 4/5 events In recent OSTC 4/5 firmware versions, the data format gained support for several new events: - Compass heading (v1.6.9) - GNSS position (v1.7.0) - Scrubber state (v1.7.2) Since events are located before the extended sample data, the presence of unsupported events is problematic. That's because the size of the event isn't known and thus the corresponding bytes can't be skipped correctly. Instead, those bytes will get interpreted as the next event or as the extended sample data, resulting in some bogus values. This problem is limited to the current sample only, because the total length of the entire sample is available in the data. --- src/array.c | 9 ++++++ src/array.h | 3 ++ src/hw_ostc_parser.c | 75 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/src/array.c b/src/array.c index 6d97557c..b447b1d0 100644 --- a/src/array.c +++ b/src/array.c @@ -302,6 +302,15 @@ array_uint16_le (const unsigned char data[]) ((unsigned int) data[1] << 8); } +float +array_float_le (const unsigned char data[]) +{ + float result = 0; + unsigned int value = array_uint32_le (data); + memcpy (&result, &value, sizeof(value)); + return result; +} + void array_uint64_be_set (unsigned char data[], const unsigned long long input) { diff --git a/src/array.h b/src/array.h index 81268779..d97abfe5 100644 --- a/src/array.h +++ b/src/array.h @@ -97,6 +97,9 @@ array_uint16_be (const unsigned char data[]); unsigned short array_uint16_le (const unsigned char data[]); +float +array_float_le (const unsigned char data[]); + void array_uint64_be_set (unsigned char data[], const unsigned long long input); diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index 77dfda2f..c5e2f8c0 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -72,6 +72,12 @@ #define OSTC4 0x3B +#define OSTC4_COMPASS_SET 0x4000 +#define OSTC4_COMPASS_CLEARED 0x8000 + +#define OSTC4_SCRUBBER_WARNING 0x2000 +#define OSTC4_SCRUBBER_ERROR 0x4000 + #define OSTC3FW(major,minor) ( \ (((major) & 0xFF) << 8) | \ ((minor) & 0xFF)) @@ -129,6 +135,9 @@ 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); @@ -419,6 +428,9 @@ 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; @@ -522,6 +534,7 @@ 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) @@ -689,6 +702,13 @@ 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; } @@ -1016,6 +1036,61 @@ hw_ostc_parser_internal_foreach (hw_ostc_parser_t *parser, dc_sample_callback_t offset += 2; length -= 2; } + + // Compass heading + if (events & 0x0200) { + if (length < 2) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + unsigned int value = array_uint16_le (data + offset); + unsigned int heading = value & 0x1FF; + + if ((value & OSTC4_COMPASS_CLEARED) == 0) { + sample.bearing = heading; + if (callback) callback (DC_SAMPLE_BEARING, &sample, userdata); + } + + offset += 2; + length -= 2; + } + + // GNSS position + if (events & 0x0400) { + if (length < 8) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + float longitude = array_float_le (data + offset); + float latitude = array_float_le (data + offset + 4); + + if (!parser->have_location) { + parser->latitude = latitude; + parser->longitude = longitude; + parser->have_location = 1; + } else { + WARNING (abstract->context, "Multiple GNSS locations present."); + } + + offset += 8; + length -= 8; + } + + // Scrubber state + if (events & 0x0800) { + if (length < 2) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + unsigned int value = array_uint16_le (data + offset); + int DC_ATTR_UNUSED remaining = (int) signextend (value & 0x0FFF, 12); + + offset += 2; + length -= 2; + } } // Extended sample info. From 7279fdbb59f8932402671f750b99c08e26089a09 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 8 Jul 2025 23:47:01 +0200 Subject: [PATCH 06/24] Ignore the dummy GNSS location The GNSS support isn't enabled in all firmware builds yet and a fixed dummy location can be reported for testing: Latitude: 8.99 Longitude: 47.77 Ignore these dummy locations because they are useless for the end-user. --- src/hw_ostc_parser.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index c5e2f8c0..47590b0a 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -78,6 +78,9 @@ #define OSTC4_SCRUBBER_WARNING 0x2000 #define OSTC4_SCRUBBER_ERROR 0x4000 +#define OSTC4_GNSS_DUMMY_LATITUDE 8.99f +#define OSTC4_GNSS_DUMMY_LONGITUDE 47.77f + #define OSTC3FW(major,minor) ( \ (((major) & 0xFF) << 8) | \ ((minor) & 0xFF)) @@ -1066,12 +1069,15 @@ hw_ostc_parser_internal_foreach (hw_ostc_parser_t *parser, dc_sample_callback_t float longitude = array_float_le (data + offset); float latitude = array_float_le (data + offset + 4); - if (!parser->have_location) { - parser->latitude = latitude; - parser->longitude = longitude; - parser->have_location = 1; - } else { - WARNING (abstract->context, "Multiple GNSS locations present."); + 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."); + } } offset += 8; From f94d9a23593b4c5560f0c95a39e7be45a50422e2 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 9 Jul 2025 23:16:43 +0200 Subject: [PATCH 07/24] Cache the log version internally The log version will be needed internally for features that are only available since a certain version. --- src/halcyon_symbios_parser.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/halcyon_symbios_parser.c b/src/halcyon_symbios_parser.c index 54aa635c..8591c158 100644 --- a/src/halcyon_symbios_parser.c +++ b/src/halcyon_symbios_parser.c @@ -55,6 +55,10 @@ (type) == ID_HEADER || \ (type) == ID_FOOTER) +#define LOGVERSION(major,minor) ( \ + (((major) & 0xFF) << 8) | \ + ((minor) & 0xFF)) + #define UNDEFINED 0xFFFFFFFF #define EPOCH 1609459200 /* 2021-01-01 00:00:00 */ @@ -87,6 +91,7 @@ typedef struct halcyon_symbios_parser_t { dc_parser_t base; // Cached fields. unsigned int cached; + unsigned int logversion; unsigned int datetime; unsigned int divetime; unsigned int maxdepth; @@ -135,6 +140,7 @@ halcyon_symbios_parser_create (dc_parser_t **out, dc_context_t *context, const u // Set the default values. parser->cached = 0; + parser->logversion = 0; parser->datetime = UNDEFINED; parser->divetime = 0; parser->maxdepth = 0; @@ -311,6 +317,7 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac 6, /* ID_GF_INFO */ }; + unsigned int logversion = 0; unsigned int time_start = UNDEFINED, time_end = UNDEFINED; unsigned int maxdepth = 0; unsigned int divemode = UNDEFINED; @@ -367,6 +374,7 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac } if (type == ID_LOG_VERSION) { + logversion = array_uint16_be (data + offset + 2); unsigned int version_major = data[offset + 2]; unsigned int version_minor = data[offset + 3]; DEBUG (abstract->context, "Version: %u.%u", @@ -687,6 +695,7 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac } parser->cached = 1; + parser->logversion = logversion; parser->datetime = time_start; if (time_start != UNDEFINED && time_end != UNDEFINED) { parser->divetime = time_end - time_start; From ee67aea21bd1f06c370ad61bc03c916e44b92639 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 9 Jul 2025 23:20:17 +0200 Subject: [PATCH 08/24] Add full timezone support In the latest data format (v1.9), a timezone offset has been added to address the problem with the date/time decoding mentioned earlier in commit cc70d7ac3fe6430ad0d5c92fedf8d17359c222af. --- src/halcyon_symbios_parser.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/halcyon_symbios_parser.c b/src/halcyon_symbios_parser.c index 8591c158..3452ad87 100644 --- a/src/halcyon_symbios_parser.c +++ b/src/halcyon_symbios_parser.c @@ -93,6 +93,7 @@ typedef struct halcyon_symbios_parser_t { unsigned int cached; unsigned int logversion; unsigned int datetime; + int timezone; unsigned int divetime; unsigned int maxdepth; unsigned int divemode; @@ -142,6 +143,7 @@ halcyon_symbios_parser_create (dc_parser_t **out, dc_context_t *context, const u parser->cached = 0; parser->logversion = 0; parser->datetime = UNDEFINED; + parser->timezone = 0; parser->divetime = 0; parser->maxdepth = 0; parser->divemode = UNDEFINED; @@ -188,8 +190,23 @@ halcyon_symbios_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datet dc_ticks_t ticks = (dc_ticks_t) parser->datetime + EPOCH; - if (!dc_datetime_localtime (datetime, ticks)) - return DC_STATUS_DATAFORMAT; + if (parser->logversion >= LOGVERSION(1,9)) { + // For firmware versions with timezone support, the UTC offset of the + // device is used. + int timezone = parser->timezone * 3600; + + ticks += timezone; + + if (!dc_datetime_gmtime (datetime, ticks)) + return DC_STATUS_DATAFORMAT; + + datetime->timezone = timezone; + } else { + // For firmware versions without timezone support, the current timezone + // of the host system is used. + if (!dc_datetime_localtime (datetime, ticks)) + return DC_STATUS_DATAFORMAT; + } return DC_STATUS_SUCCESS; } @@ -319,6 +336,7 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac unsigned int logversion = 0; unsigned int time_start = UNDEFINED, time_end = UNDEFINED; + int timezone = 0; unsigned int maxdepth = 0; unsigned int divemode = UNDEFINED; unsigned int atmospheric = UNDEFINED; @@ -392,6 +410,7 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac unsigned int DC_ATTR_UNUSED detection = data[offset + 11]; unsigned int DC_ATTR_UNUSED noflytime = data[offset + 12]; divemode = data[offset + 13]; + timezone = (signed char) data[offset + 14]; atmospheric = array_uint16_le(data + offset + 16); unsigned int DC_ATTR_UNUSED number = array_uint16_le(data + offset + 18); unsigned int DC_ATTR_UNUSED battery = array_uint16_le(data + offset + 20); @@ -697,6 +716,7 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac parser->cached = 1; parser->logversion = logversion; parser->datetime = time_start; + parser->timezone = timezone; if (time_start != UNDEFINED && time_end != UNDEFINED) { parser->divetime = time_end - time_start; } else { From 4b52efdc4eab689f85ce69d6a52eaebe6d8d07c8 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 24 Jul 2025 20:05:16 +0200 Subject: [PATCH 09/24] Support response packets with a variable size Prepare the code to handle response packets with a size that is not fixed anymore. An extra output parameter is added to report the actual size back to the caller. --- src/halcyon_symbios.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/halcyon_symbios.c b/src/halcyon_symbios.c index 1ecdb992..551e7e4a 100644 --- a/src/halcyon_symbios.c +++ b/src/halcyon_symbios.c @@ -210,7 +210,7 @@ halcyon_symbios_recv (halcyon_symbios_device_t *device, unsigned char cmd, unsig } static dc_status_t -halcyon_symbios_transfer (halcyon_symbios_device_t *device, unsigned char cmd, const unsigned char data[], unsigned int size, unsigned char answer[], unsigned int asize, unsigned int *errorcode) +halcyon_symbios_transfer (halcyon_symbios_device_t *device, unsigned char cmd, const unsigned char data[], unsigned int size, unsigned char answer[], unsigned int asize, unsigned int *actual, unsigned int *errorcode) { dc_status_t status = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; @@ -231,11 +231,16 @@ halcyon_symbios_transfer (halcyon_symbios_device_t *device, unsigned char cmd, c goto error_exit; } - // Verify the length of the packet. - if (length != asize) { - ERROR (abstract->context, "Unexpected packet length (%u).", length); - status = DC_STATUS_PROTOCOL; - goto error_exit; + if (actual == NULL) { + // Verify the length of the packet. + if (length != asize) { + ERROR (abstract->context, "Unexpected packet length (%u).", length); + status = DC_STATUS_PROTOCOL; + goto error_exit; + } + } else { + // Return the actual length. + *actual = length; } error_exit: @@ -257,7 +262,7 @@ halcyon_symbios_download (halcyon_symbios_device_t *device, dc_event_progress_t // Request the data. unsigned char response[4] = {0}; - status = halcyon_symbios_transfer (device, request, data, size, response, sizeof(response), &errcode); + status = halcyon_symbios_transfer (device, request, data, size, response, sizeof(response), NULL, &errcode); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to request the data."); goto error_exit; @@ -439,7 +444,7 @@ halcyon_symbios_device_foreach (dc_device_t *abstract, dc_dive_callback_t callba // Read the device status. unsigned char info[20] = {0}; - status = halcyon_symbios_transfer (device, CMD_GET_STATUS, NULL, 0, info, sizeof(info), NULL); + status = halcyon_symbios_transfer (device, CMD_GET_STATUS, NULL, 0, info, sizeof(info), NULL, NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the device status."); goto error_exit; @@ -568,7 +573,7 @@ halcyon_symbios_device_timesync (dc_device_t *abstract, const dc_datetime_t *dat datetime->minute, datetime->second, }; - status = halcyon_symbios_transfer (device, CMD_SET_TIME, request, sizeof(request), NULL, 0, NULL); + status = halcyon_symbios_transfer (device, CMD_SET_TIME, request, sizeof(request), NULL, 0, NULL, NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to set the time."); goto error_exit; From 9ea564e77d1f985573e303cc179a80dca9842629 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 24 Jul 2025 20:22:31 +0200 Subject: [PATCH 10/24] Increase the size of the device info buffer In bluetooth protocol v1.30, the length of the CMD_GET_STATUS response increased from 20 to 36 bytes. Increase the size of the buffer and update the code to support both lengths. --- src/halcyon_symbios.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/halcyon_symbios.c b/src/halcyon_symbios.c index 551e7e4a..3eabf806 100644 --- a/src/halcyon_symbios.c +++ b/src/halcyon_symbios.c @@ -443,19 +443,27 @@ halcyon_symbios_device_foreach (dc_device_t *abstract, dc_dive_callback_t callba device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); // Read the device status. - unsigned char info[20] = {0}; - status = halcyon_symbios_transfer (device, CMD_GET_STATUS, NULL, 0, info, sizeof(info), NULL, NULL); + unsigned char info[36] = {0}; + unsigned int info_size = 0; + status = halcyon_symbios_transfer (device, CMD_GET_STATUS, NULL, 0, info, sizeof(info), &info_size, NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the device status."); goto error_exit; } - HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Version", info, sizeof(info)); + // Verify the length of the packet. + if (info_size < 20) { + ERROR (abstract->context, "Unexpected packet length (%u).", info_size); + status = DC_STATUS_PROTOCOL; + goto error_exit; + } + + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Version", info, info_size); // Emit a vendor event. dc_event_vendor_t vendor; vendor.data = info; - vendor.size = sizeof(info); + vendor.size = info_size; device_event_emit (abstract, DC_EVENT_VENDOR, &vendor); // Emit a device info event. From afda77e750502d982c2128bcf33d8e011aac844c Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 25 Aug 2025 19:40:04 +0200 Subject: [PATCH 11/24] Fix the Github actions on linux Update the list of packages before installing new packages. This avoids "404 Not Found" errors when packages on the server have been replaced with newer versions, but the local package cache still refers to the old version. --- .github/workflows/build.yml | 8 ++++++-- .github/workflows/release.yml | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1f7e0efc..4d722cb9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,9 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install dependencies - run: sudo apt-get install libbluetooth-dev libusb-1.0-0-dev + run: | + sudo apt-get update + sudo apt-get install libbluetooth-dev libusb-1.0-0-dev - run: autoreconf --install --force - run: ./configure --prefix=/usr - run: make @@ -80,7 +82,9 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install dependencies - run: sudo apt-get install gcc-mingw-w64 binutils-mingw-w64 mingw-w64-tools + run: | + sudo apt-get update + sudo apt-get install gcc-mingw-w64 binutils-mingw-w64 mingw-w64-tools - name: Install libusb env: LIBUSB_VERSION: 1.0.26 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc4773b5..f8f2b342 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,6 +20,7 @@ jobs: - name: Build distribution tarball id: build run: | + sudo apt-get update sudo apt-get install libbluetooth-dev libusb-1.0-0-dev autoreconf --install --force ./configure From 1e0123f8704c672edef15d3c8fa267bda6af2dbc Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 25 Aug 2025 19:47:04 +0200 Subject: [PATCH 12/24] Fix the Github actions on mac After Github updated the macos-latest image to macOS 15 (with Xcode 16), linking fails with the error: ld: Shared cache eligible dylib cannot link to ineligible dylib '/opt/homebrew/opt/libusb/lib/libusb-1.0.0.dylib'. Remove link to ineligible dylib, fix its eligibility, or opt out of the shared cache using the build setting 'LD_SHARED_CACHE_ELIGIBLE=NO' (or linker flag '-not_for_dyld_shared_cache') The earlier macOS 14 image (with Xcode 15.4) already issued a warning: ld: warning: invalid -install_name (/opt/homebrew/opt/libusb/lib/libusb-1.0.0.dylib) in dependent dylib (/opt/homebrew/Cellar/libusb/1.0.29/lib/libusb-1.0.dylib). Dylibs/frameworks which might go in dyld shared cache cannot link with dylibs that won't be in the shared cache This problem appears to be caused by running the configure script with the --prefix=/usr option. Remove this option and use the default /usr/local prefix instead of enabling some obscure linker option. For consistency, the prefix option is removed from all build targets. --- .github/workflows/build.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4d722cb9..7051a6c2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,13 +25,13 @@ jobs: sudo apt-get update sudo apt-get install libbluetooth-dev libusb-1.0-0-dev - run: autoreconf --install --force - - run: ./configure --prefix=/usr + - run: ./configure - run: make - run: make distcheck - name: Package artifacts run: | make install DESTDIR=$PWD/artifacts - tar -czf ${{ github.job }}-${{ matrix.compiler }}.tar.gz -C artifacts usr + tar -czf ${{ github.job }}-${{ matrix.compiler }}.tar.gz -C artifacts usr/local - uses: actions/upload-artifact@v4 with: name: ${{ github.job }}-${{ matrix.compiler }} @@ -56,13 +56,13 @@ jobs: - name: Install dependencies run: brew install autoconf automake libtool hidapi libusb - run: autoreconf --install --force - - run: ./configure --prefix=/usr + - run: ./configure - run: make - run: make distcheck - name: Package artifacts run: | make install DESTDIR=$PWD/artifacts - tar -czf ${{ github.job }}-${{ matrix.compiler }}.tar.gz -C artifacts usr + tar -czf ${{ github.job }}-${{ matrix.compiler }}.tar.gz -C artifacts usr/local - uses: actions/upload-artifact@v4 with: name: ${{ github.job }}-${{ matrix.compiler }} @@ -93,7 +93,7 @@ jobs: tar xzf v${LIBUSB_VERSION}.tar.gz pushd libusb-${LIBUSB_VERSION} autoreconf --install --force - ./configure --host=${{ matrix.arch }}-w64-mingw32 --prefix=/usr + ./configure --host=${{ matrix.arch }}-w64-mingw32 make make install DESTDIR=$PWD/../artifacts popd @@ -105,14 +105,14 @@ jobs: tar xzf hidapi-${HIDAPI_VERSION}.tar.gz pushd hidapi-hidapi-${HIDAPI_VERSION} autoreconf --install --force - ./configure --host=${{ matrix.arch }}-w64-mingw32 --prefix=/usr LDFLAGS='-static-libgcc' + ./configure --host=${{ matrix.arch }}-w64-mingw32 LDFLAGS='-static-libgcc' make make install DESTDIR=$PWD/../artifacts popd - run: autoreconf --install --force - - run: ./configure --host=${{ matrix.arch }}-w64-mingw32 --prefix=/usr + - run: ./configure --host=${{ matrix.arch }}-w64-mingw32 env: - PKG_CONFIG_LIBDIR: ${{ github.workspace }}/artifacts/usr/lib/pkgconfig + PKG_CONFIG_LIBDIR: ${{ github.workspace }}/artifacts/usr/local/lib/pkgconfig PKG_CONFIG_SYSROOT_DIR: ${{ github.workspace }}/artifacts PKG_CONFIG_ALLOW_SYSTEM_CFLAGS: 1 PKG_CONFIG_ALLOW_SYSTEM_LIBS: 1 @@ -121,7 +121,7 @@ jobs: - name: Package artifacts run: | make install DESTDIR=$PWD/artifacts - tar -czf ${{ github.job }}-${{ matrix.arch }}.tar.gz -C artifacts usr + tar -czf ${{ github.job }}-${{ matrix.arch }}.tar.gz -C artifacts usr/local - uses: actions/upload-artifact@v4 with: name: ${{ github.job }}-${{ matrix.arch }} @@ -148,7 +148,7 @@ jobs: install: autoconf automake libtool pkg-config make gcc - run: | autoreconf --install --force - ./configure --prefix=/usr + ./configure make -C src revision.h shell: msys2 {0} - uses: microsoft/setup-msbuild@v2 @@ -168,7 +168,7 @@ jobs: - uses: actions/checkout@v4 - run: | autoreconf --install --force - ./configure --prefix=/usr + ./configure make -C src revision.h - run: $ANDROID_NDK/ndk-build -C contrib/android NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=Android.mk - uses: actions/upload-artifact@v4 From 461138f7709223a490a61a29519b7199c2f2831c Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 24 Aug 2025 22:31:45 +0200 Subject: [PATCH 13/24] Add support for the Mares Puck Pro EZ The Puck Pro EZ re-uses the same model number as the Puck 4. Only the bluetooth device name appears to be different. --- src/descriptor.c | 2 ++ src/mares_iconhd.c | 1 + 2 files changed, 3 insertions(+) diff --git a/src/descriptor.c b/src/descriptor.c index b37ad23c..f28f6b77 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -319,6 +319,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Mares", "Quad Ci", DC_FAMILY_MARES_ICONHD , 0x31, 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}, /* Heinrichs Weikamp */ {"Heinrichs Weikamp", "OSTC", DC_FAMILY_HW_OSTC, 0, DC_TRANSPORT_SERIAL, NULL}, {"Heinrichs Weikamp", "OSTC Mk2", DC_FAMILY_HW_OSTC, 1, DC_TRANSPORT_SERIAL, NULL}, @@ -779,6 +780,7 @@ dc_filter_mares (const dc_descriptor_t *descriptor, dc_transport_t transport, co "Quad Ci", "Puck4", "Puck Lite", + "Puck", }; if (transport == DC_TRANSPORT_BLE) { diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index f9986f1a..58a75133 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -197,6 +197,7 @@ mares_iconhd_get_model (mares_iconhd_device_t *device) {"Quad Ci", QUADCI}, {"Puck4", PUCK4}, {"Puck Lite", PUCK4}, + {"Puck", PUCK4}, }; // Check the product name in the version packet against the list From ea91f5b05d0d2a2d6d27c7fbef78ad2563f28305 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 2 Sep 2025 21:26:18 +0200 Subject: [PATCH 14/24] Add support for the Mares Puck Pro Ultra The Puck Pro Ultra re-uses the same model number as the Puck 4. Only the bluetooth device name appears to be different. --- src/descriptor.c | 2 ++ src/mares_iconhd.c | 1 + 2 files changed, 3 insertions(+) diff --git a/src/descriptor.c b/src/descriptor.c index f28f6b77..7a815727 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -320,6 +320,7 @@ static const dc_descriptor_t g_descriptors[] = { {"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}, + {"Mares", "Puck Pro Ultra", DC_FAMILY_MARES_ICONHD , 0x35, DC_TRANSPORT_BLE, dc_filter_mares}, /* Heinrichs Weikamp */ {"Heinrichs Weikamp", "OSTC", DC_FAMILY_HW_OSTC, 0, DC_TRANSPORT_SERIAL, NULL}, {"Heinrichs Weikamp", "OSTC Mk2", DC_FAMILY_HW_OSTC, 1, DC_TRANSPORT_SERIAL, NULL}, @@ -781,6 +782,7 @@ dc_filter_mares (const dc_descriptor_t *descriptor, dc_transport_t transport, co "Puck4", "Puck Lite", "Puck", + "Puck Pro U", }; if (transport == DC_TRANSPORT_BLE) { diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index 58a75133..5907e3d2 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -198,6 +198,7 @@ mares_iconhd_get_model (mares_iconhd_device_t *device) {"Puck4", PUCK4}, {"Puck Lite", PUCK4}, {"Puck", PUCK4}, + {"Puck Pro U", PUCK4}, }; // Check the product name in the version packet against the list From ef3bef7dfb999e4ff5fb051c865894e75ee27a5c Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 24 Aug 2025 22:05:28 +0200 Subject: [PATCH 15/24] Add support for the Mares Quad 2 The Quad 2 uses the Genius/Sirius variant of the protocol, with a new model number and bluetooth device name. --- 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 7a815727..3f581225 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -317,6 +317,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Mares", "Puck Air 2", DC_FAMILY_MARES_ICONHD , 0x2D, DC_TRANSPORT_BLE, dc_filter_mares}, {"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", "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}, @@ -779,6 +780,7 @@ dc_filter_mares (const dc_descriptor_t *descriptor, dc_transport_t transport, co "Mares Genius", "Sirius", "Quad Ci", + "Quad2", "Puck4", "Puck Lite", "Puck", diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index 5907e3d2..c34859cc 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -51,6 +51,7 @@ #define PUCKAIR2 0x2D #define SIRIUS 0x2F #define QUADCI 0x31 +#define QUAD2 0x32 #define PUCK4 0x35 #define ISSMART(model) ( \ @@ -64,12 +65,14 @@ (model) == PUCKAIR2 || \ (model) == SIRIUS || \ (model) == QUADCI || \ + (model) == QUAD2 || \ (model) == PUCK4) #define ISSIRIUS(model) ( \ (model) == PUCKAIR2 || \ (model) == SIRIUS || \ (model) == QUADCI || \ + (model) == QUAD2 || \ (model) == PUCK4) #define MAXRETRIES 4 @@ -195,6 +198,7 @@ mares_iconhd_get_model (mares_iconhd_device_t *device) {"Puck Air 2", PUCKAIR2}, {"Sirius", SIRIUS}, {"Quad Ci", QUADCI}, + {"Quad2", QUAD2}, {"Puck4", PUCK4}, {"Puck Lite", PUCK4}, {"Puck", PUCK4}, @@ -668,6 +672,7 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_ case PUCKAIR2: case SIRIUS: case QUADCI: + case QUAD2: 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 f26d010b..4066e582 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -49,6 +49,7 @@ #define PUCKAIR2 0x2D #define SIRIUS 0x2F #define QUADCI 0x31 +#define QUAD2 0x32 #define PUCK4 0x35 #define ISSMART(model) ( \ @@ -62,6 +63,7 @@ (model) == PUCKAIR2 || \ (model) == SIRIUS || \ (model) == QUADCI || \ + (model) == QUAD2 || \ (model) == PUCK4) #define NGASMIXES_ICONHD 3 From e928371c9509efbb27b7501f01987a5e779bc3eb Mon Sep 17 00:00:00 2001 From: tim661811 Date: Thu, 21 Aug 2025 13:49:42 +0200 Subject: [PATCH 16/24] Add an option to limit the number of dives to download --- examples/dctool_download.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/examples/dctool_download.c b/examples/dctool_download.c index 29742e0c..10964080 100644 --- a/examples/dctool_download.c +++ b/examples/dctool_download.c @@ -53,6 +53,7 @@ typedef struct dive_data_t { dc_buffer_t **fingerprint; unsigned int number; dctool_output_t *output; + unsigned int limit; } dive_data_t; static int @@ -96,6 +97,11 @@ dive_cb (const unsigned char *data, unsigned int size, const unsigned char *fing cleanup: dc_parser_destroy (parser); + + if (divedata->limit > 0 && divedata->number >= divedata->limit) { + return 0; + } + return 1; } @@ -146,7 +152,7 @@ event_cb (dc_device_t *device, dc_event_type_t event, const void *data, void *us } static dc_status_t -download (dc_context_t *context, dc_descriptor_t *descriptor, dc_transport_t transport, const char *devname, const char *cachedir, dc_buffer_t *fingerprint, dctool_output_t *output) +download (dc_context_t *context, dc_descriptor_t *descriptor, dc_transport_t transport, const char *devname, const char *cachedir, dc_buffer_t *fingerprint, dctool_output_t *output, unsigned int limit) { dc_status_t rc = DC_STATUS_SUCCESS; dc_iostream_t *iostream = NULL; @@ -214,6 +220,7 @@ download (dc_context_t *context, dc_descriptor_t *descriptor, dc_transport_t tra divedata.fingerprint = &ofingerprint; divedata.number = 0; divedata.output = output; + divedata.limit = limit; // Download the dives. message ("Downloading the dives.\n"); @@ -260,10 +267,11 @@ dctool_download_run (int argc, char *argv[], dc_context_t *context, dc_descripto const char *filename = NULL; const char *cachedir = NULL; const char *format = "xml"; + unsigned int limit = 0; // Parse the command-line options. int opt = 0; - const char *optstring = "ht:o:p:c:f:u:"; + const char *optstring = "ht:o:p:c:f:u:l:"; #ifdef HAVE_GETOPT_LONG struct option options[] = { {"help", no_argument, 0, 'h'}, @@ -273,6 +281,7 @@ dctool_download_run (int argc, char *argv[], dc_context_t *context, dc_descripto {"cache", required_argument, 0, 'c'}, {"format", required_argument, 0, 'f'}, {"units", required_argument, 0, 'u'}, + {"limit", required_argument, 0, 'l'}, {0, 0, 0, 0 } }; while ((opt = getopt_long (argc, argv, optstring, options, NULL)) != -1) { @@ -304,6 +313,9 @@ dctool_download_run (int argc, char *argv[], dc_context_t *context, dc_descripto if (strcmp (optarg, "imperial") == 0) units = DCTOOL_UNITS_IMPERIAL; break; + case 'l': + limit = strtoul (optarg, NULL, 0); + break; default: return EXIT_FAILURE; } @@ -345,7 +357,7 @@ dctool_download_run (int argc, char *argv[], dc_context_t *context, dc_descripto } // Download the dives. - status = download (context, descriptor, transport, argv[0], cachedir, fingerprint, output); + status = download (context, descriptor, transport, argv[0], cachedir, fingerprint, output, limit); if (status != DC_STATUS_SUCCESS) { message ("ERROR: %s\n", dctool_errmsg (status)); exitcode = EXIT_FAILURE; @@ -375,6 +387,7 @@ const dctool_command_t dctool_download = { " -c, --cache Cache directory\n" " -f, --format Output format\n" " -u, --units Set units (metric or imperial)\n" + " -l, --limit Maximum number of dives to download\n" #else " -h Show help message\n" " -t Transport type\n" @@ -383,6 +396,7 @@ const dctool_command_t dctool_download = { " -c Cache directory\n" " -f Output format\n" " -u Set units (metric or imperial)\n" + " -l Maximum number of dives to download\n" #endif "\n" "Supported output formats:\n" From 8d19f22ca94cd40229bcfb77cea144f80de419e0 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 4 Sep 2025 19:58:48 +0200 Subject: [PATCH 17/24] Include the stddef.h header for the size_t type The snprintf functions use the size_t datatype and therefore the platform header should include the stddef.h header. --- src/platform.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform.h b/src/platform.h index f98f0d7a..5f7ca962 100644 --- a/src/platform.h +++ b/src/platform.h @@ -22,6 +22,7 @@ #ifndef DC_PLATFORM_H #define DC_PLATFORM_H +#include #include #ifdef __cplusplus From f3178e08e12a48aee4748a4e14c9265a608cbbc3 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 7 Aug 2025 14:43:39 +0200 Subject: [PATCH 18/24] Check the minimum length before use Each dive should at least contain a complete header. Any smaller size would result in a buffer overflow error in the remainder of the code. --- src/seac_screen.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/seac_screen.c b/src/seac_screen.c index e8f91c67..627ea01e 100644 --- a/src/seac_screen.c +++ b/src/seac_screen.c @@ -672,6 +672,13 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, goto error_free_rbstream; } + // Check the minimum header length. + if (length < SZ_HEADER) { + ERROR (abstract->context, "Unexpected dive length (%u).", length); + status = DC_STATUS_DATAFORMAT; + goto error_free_rbstream; + } + // Check the dive header. if (memcmp (profile + offset, logbook[i].header, SZ_HEADER) != 0) { ERROR (abstract->context, "Unexpected dive header."); From 6bfdbdff9959557f7ca6fb396abbc0879bb1af2c Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 17 Jul 2025 00:08:47 +0200 Subject: [PATCH 19/24] Improve the record validation The Seac data format uses fixed size records (of 64 bytes) for both the header and sample data. Each record contains a checksum, but also a record type and record ID. Add a common helper function to validate all this information. --- contrib/android/Android.mk | 1 + contrib/msvc/libdivecomputer.vcxproj | 2 + src/Makefile.am | 1 + src/seac_screen.c | 20 +++++----- src/seac_screen_common.c | 58 ++++++++++++++++++++++++++++ src/seac_screen_common.h | 45 +++++++++++++++++++++ src/seac_screen_parser.c | 48 ++++++++++++++--------- 7 files changed, 148 insertions(+), 27 deletions(-) create mode 100644 src/seac_screen_common.c create mode 100644 src/seac_screen_common.h diff --git a/contrib/android/Android.mk b/contrib/android/Android.mk index 4a497fd2..c11d0d1a 100644 --- a/contrib/android/Android.mk +++ b/contrib/android/Android.mk @@ -85,6 +85,7 @@ LOCAL_SRC_FILES := \ src/reefnet_sensusultra_parser.c \ src/ringbuffer.c \ src/seac_screen.c \ + src/seac_screen_common.c \ src/seac_screen_parser.c \ src/serial_posix.c \ src/shearwater_common.c \ diff --git a/contrib/msvc/libdivecomputer.vcxproj b/contrib/msvc/libdivecomputer.vcxproj index 81d6df3a..db583a37 100644 --- a/contrib/msvc/libdivecomputer.vcxproj +++ b/contrib/msvc/libdivecomputer.vcxproj @@ -253,6 +253,7 @@ + @@ -370,6 +371,7 @@ + diff --git a/src/Makefile.am b/src/Makefile.am index acd6a511..ff0ee642 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -77,6 +77,7 @@ libdivecomputer_la_SOURCES = \ liquivision_lynx.h liquivision_lynx.c liquivision_lynx_parser.c \ sporasub_sp2.h sporasub_sp2.c sporasub_sp2_parser.c \ deepsix_excursion.h deepsix_excursion.c deepsix_excursion_parser.c \ + seac_screen_common.h seac_screen_common.c \ seac_screen.h seac_screen.c seac_screen_parser.c \ deepblu_cosmiq.h deepblu_cosmiq.c deepblu_cosmiq_parser.c \ oceans_s1_common.h oceans_s1_common.c \ diff --git a/src/seac_screen.c b/src/seac_screen.c index 627ea01e..5d0bd0ca 100644 --- a/src/seac_screen.c +++ b/src/seac_screen.c @@ -23,6 +23,7 @@ #include // malloc, free #include "seac_screen.h" +#include "seac_screen_common.h" #include "context-private.h" #include "device-private.h" #include "ringbuffer.h" @@ -66,9 +67,6 @@ #define SZ_ADDRESS 4 #define SZ_READ 2048 -#define SZ_HEADER 128 -#define SZ_SAMPLE 64 - #define FP_OFFSET 0x0A #define FP_SIZE 7 @@ -588,12 +586,16 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, progress.current += SZ_ADDRESS + SZ_HEADER; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - // Check the header checksums. - if (checksum_crc16_ccitt (logbook[i].header, SZ_HEADER / 2, 0xFFFF, 0x0000) != 0 || - checksum_crc16_ccitt (logbook[i].header + SZ_HEADER / 2, SZ_HEADER / 2, 0xFFFF, 0x0000) != 0) { - ERROR (abstract->context, "Unexpected header checksum."); - status = DC_STATUS_DATAFORMAT; - goto error_free_logbook; + // Check the header records. + for (unsigned int j = 0; j < 2; ++j) { + unsigned int type = j == 0 ? HEADER1 : HEADER2; + if (!seac_screen_record_isvalid (abstract->context, + logbook[i].header + j * SZ_HEADER / 2, SZ_HEADER / 2, + type, number)) { + ERROR (abstract->context, "Invalid header record %u.", j); + status = DC_STATUS_DATAFORMAT; + goto error_free_logbook; + } } // Check the fingerprint. diff --git a/src/seac_screen_common.c b/src/seac_screen_common.c new file mode 100644 index 00000000..f5890a26 --- /dev/null +++ b/src/seac_screen_common.c @@ -0,0 +1,58 @@ +/* + * libdivecomputer + * + * Copyright (C) 2025 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 "seac_screen_common.h" +#include "context-private.h" +#include "checksum.h" +#include "array.h" + +int +seac_screen_record_isvalid (dc_context_t *context, const unsigned char data[], unsigned int size, unsigned int type, unsigned int id) +{ + // Check the record size. + if (size != SZ_RECORD) { + ERROR (context, "Unexpected record size (%u).", size); + return 0; + } + + // Check the record checksum. + unsigned short csum = checksum_crc16_ccitt (data, size, 0xFFFF, 0x0000); + if (csum != 0) { + ERROR (context, "Unexpected record checksum (%04x).", csum); + return 0; + } + + // Check the record type. + unsigned int rtype = data[size - 3]; + if (rtype != type) { + ERROR (context, "Unexpected record type (%02x %02x).", rtype, type); + return 0; + } + + // Check the record id. + unsigned int rid = array_uint32_le (data); + if (rid != id) { + ERROR (context, "Unexpected record id (%u %u).", rid, id); + return 0; + } + + return 1; +} diff --git a/src/seac_screen_common.h b/src/seac_screen_common.h new file mode 100644 index 00000000..054e56b3 --- /dev/null +++ b/src/seac_screen_common.h @@ -0,0 +1,45 @@ +/* + * libdivecomputer + * + * Copyright (C) 2025 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 SEAC_SCREEN_COMMON_H +#define SEAC_SCREEN_COMMON_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define HEADER1 0xCF +#define HEADER2 0xC0 +#define SAMPLE 0xAA + +#define SZ_RECORD 64 +#define SZ_HEADER (SZ_RECORD * 2) +#define SZ_SAMPLE SZ_RECORD + +int +seac_screen_record_isvalid (dc_context_t *context, const unsigned char data[], unsigned int size, unsigned int type, unsigned int id); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* SEAC_SCREEN_COMMON_H */ diff --git a/src/seac_screen_parser.c b/src/seac_screen_parser.c index b19c8ea9..42cf0864 100644 --- a/src/seac_screen_parser.c +++ b/src/seac_screen_parser.c @@ -22,16 +22,13 @@ #include #include "seac_screen.h" +#include "seac_screen_common.h" #include "context-private.h" #include "parser-private.h" -#include "checksum.h" #include "array.h" #define ISINSTANCE(parser) dc_device_isinstance((parser), &seac_screen_parser_vtable) -#define SZ_HEADER 128 -#define SZ_SAMPLE 64 - #define NGASMIXES 2 #define INVALID 0xFFFFFFFF @@ -152,6 +149,20 @@ seac_screen_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) if (abstract->size < SZ_HEADER) return DC_STATUS_DATAFORMAT; + // Get the dive ID. + unsigned int dive_id = array_uint32_le (data + 0x00); + + // Check the header records. + for (unsigned int i = 0; i < 2; ++i) { + unsigned int type = i == 0 ? HEADER1 : HEADER2; + if (!seac_screen_record_isvalid (abstract->context, + data + i * SZ_HEADER / 2, SZ_HEADER / 2, + type, dive_id)) { + ERROR (abstract->context, "Invalid header record %u.", i); + return DC_STATUS_DATAFORMAT; + } + } + // The date/time is stored as UTC time with a timezone offset. To convert to // local time, the UTC time is first converted to unix time (seconds since // the epoch), then adjusted for the timezone offset, and finally converted @@ -271,14 +282,20 @@ seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t if (abstract->size < SZ_HEADER) return DC_STATUS_DATAFORMAT; - if (checksum_crc16_ccitt (data, SZ_HEADER / 2, 0xFFFF, 0x0000) != 0 || - checksum_crc16_ccitt (data + SZ_HEADER / 2, SZ_HEADER / 2, 0xFFFF, 0x0000) != 0) { - ERROR (abstract->context, "Unexpected header checksum."); - return DC_STATUS_DATAFORMAT; - } - + // Get the dive ID. unsigned int dive_id = array_uint32_le (data + 0x00); + // Check the header records. + for (unsigned int i = 0; i < 2; ++i) { + unsigned int type = i == 0 ? HEADER1 : HEADER2; + if (!seac_screen_record_isvalid (abstract->context, + data + i * SZ_HEADER / 2, SZ_HEADER / 2, + type, dive_id)) { + ERROR (abstract->context, "Invalid header record %u.", i); + return DC_STATUS_DATAFORMAT; + } + } + unsigned int ngasmixes = 0; unsigned int oxygen[NGASMIXES] = {0}; unsigned int o2_previous = INVALID; @@ -291,12 +308,12 @@ seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t while (offset + SZ_SAMPLE <= size) { dc_sample_value_t sample = {0}; - if (checksum_crc16_ccitt (data + offset, SZ_SAMPLE, 0xFFFF, 0x0000) != 0) { - ERROR (abstract->context, "Unexpected sample checksum."); + // Check the sample record. + if (!seac_screen_record_isvalid (abstract->context, data + offset, SZ_SAMPLE, SAMPLE, dive_id)) { + ERROR (abstract->context, "Invalid sample record."); return DC_STATUS_DATAFORMAT; } - unsigned int id = array_uint32_le (data + offset + 0x00); unsigned int timestamp = array_uint32_le (data + offset + 0x04); unsigned int depth = array_uint16_le (data + offset + 0x08); unsigned int temperature = array_uint16_le (data + offset + 0x0A); @@ -309,11 +326,6 @@ seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t unsigned int gf_hi = data[offset + 0x3B]; unsigned int gf_lo = data[offset + 0x3C]; - if (id != dive_id) { - ERROR (abstract->context, "Unexpected sample id (%u %u).", dive_id, id); - return DC_STATUS_DATAFORMAT; - } - // Time (seconds). if (timestamp < time) { ERROR (abstract->context, "Timestamp moved backwards (%u %u).", timestamp, time); From 9d34ee27b1943158f858ea488f0cc351c47d9421 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 14 Jul 2025 00:07:01 +0200 Subject: [PATCH 20/24] Make the record validation non-fatal during the download When the dive computer battery voltage is too low, the external memory read/write operations are not performed, which can result in corrupt records with an incorrect checksum. The current code considers such checksum errors as a fatal error and aborts the download. This prevents the user from downloading any older dives. However, during the download the contents of the header isn't really needed, except for two cases: - To determine the length of the most recent dive and calculate the end-of-profile pointer. - To detect and discard padding bytes at the end of each dive. In both cases, there is a reasonable fallback possible whenever the header checksum is invalid and the contents of the header can't be trusted. In the parser, checksum errors are still treated as fatal errors. --- src/seac_screen.c | 78 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 21 deletions(-) diff --git a/src/seac_screen.c b/src/seac_screen.c index 5d0bd0ca..fcb46df4 100644 --- a/src/seac_screen.c +++ b/src/seac_screen.c @@ -546,6 +546,7 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, // Read the header of each dive in reverse order (most recent first). unsigned int eop = 0; unsigned int previous = 0; + unsigned int begin = 0; unsigned int count = 0; unsigned int skip = 0; unsigned int rb_profile_size = 0; @@ -586,30 +587,42 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, progress.current += SZ_ADDRESS + SZ_HEADER; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - // Check the header records. - for (unsigned int j = 0; j < 2; ++j) { - unsigned int type = j == 0 ? HEADER1 : HEADER2; - if (!seac_screen_record_isvalid (abstract->context, - logbook[i].header + j * SZ_HEADER / 2, SZ_HEADER / 2, - type, number)) { - ERROR (abstract->context, "Invalid header record %u.", j); - status = DC_STATUS_DATAFORMAT; - goto error_free_logbook; - } - } - // Check the fingerprint. if (memcmp (logbook[i].header + FP_OFFSET, device->fingerprint, sizeof (device->fingerprint)) == 0) { skip = 1; break; } - // Get the number of samples. - unsigned int nsamples = array_uint32_le (logbook[i].header + 0x44); - unsigned int nbytes = SZ_HEADER + nsamples * SZ_SAMPLE; - // Get the end-of-profile pointer. if (eop == 0) { + // Check the header records. + unsigned int isvalid = 1; + for (unsigned int j = 0; j < 2; ++j) { + unsigned int type = j == 0 ? HEADER1 : HEADER2; + if (!seac_screen_record_isvalid (abstract->context, + logbook[i].header + j * SZ_HEADER / 2, SZ_HEADER / 2, + type, number)) { + WARNING (abstract->context, "Invalid header record %u.", j); + isvalid = 0; + } + } + + // For dives with an invalid header, the number of samples in the + // header is not guaranteed to be valid. Discard the entire dive + // instead and take its start address as the end of the profile. + if (!isvalid) { + WARNING (abstract->context, "Unable to locate the end of the profile."); + eop = previous = logbook[i].address; + begin = 1; + skip++; + continue; + } + + // Get the number of samples. + unsigned int nsamples = array_uint32_le (logbook[i].header + 0x44); + unsigned int nbytes = SZ_HEADER + nsamples * SZ_SAMPLE; + + // Calculate the end of the profile. eop = previous = RB_PROFILE_INCR (logbook[i].address, nbytes, layout); } @@ -619,7 +632,7 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, // Check for the end of the ringbuffer. if (length > remaining) { WARNING (abstract->context, "Reached the end of the ringbuffer."); - skip = 1; + skip++; break; } @@ -660,12 +673,15 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, previous = eop; unsigned int offset = rb_profile_size; for (unsigned int i = 0; i < count; ++i) { + unsigned int idx = begin + i; + unsigned int number = last - idx; + // Calculate the length. - unsigned int length = RB_PROFILE_DISTANCE (logbook[i].address, previous, layout); + unsigned int length = RB_PROFILE_DISTANCE (logbook[idx].address, previous, layout); // Move to the start of the current dive. offset -= length; - previous = logbook[i].address; + previous = logbook[idx].address; // Read the dive. status = dc_rbstream_read (rbstream, &progress, profile + offset, length); @@ -688,12 +704,32 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, goto error_free_rbstream; } + // Check the header records. + unsigned int isvalid = 1; + for (unsigned int j = 0; j < 2; ++j) { + unsigned int type = j == 0 ? HEADER1 : HEADER2; + if (!seac_screen_record_isvalid (abstract->context, + profile + offset + j * SZ_HEADER / 2, SZ_HEADER / 2, + type, number)) { + WARNING (abstract->context, "Invalid header record %u.", j); + isvalid = 0; + } + } + // Get the number of samples. // The actual size of the dive, based on the number of samples, can // sometimes be smaller than the maximum length. In that case, the // remainder of the data is padded with 0xFF bytes. - unsigned int nsamples = array_uint32_le (logbook[i].header + 0x44); - unsigned int nbytes = SZ_HEADER + nsamples * SZ_SAMPLE; + unsigned int nsamples = 0; + unsigned int nbytes = 0; + if (isvalid) { + nsamples = array_uint32_le (profile + offset + 0x44); + nbytes = SZ_HEADER + nsamples * SZ_SAMPLE; + } else { + WARNING (abstract->context, "Unable to locate the padding bytes."); + nbytes = length; + } + if (nbytes > length) { ERROR (abstract->context, "Unexpected dive length (%u %u).", nbytes, length); status = DC_STATUS_DATAFORMAT; From c6cd31db42fb398c4ea749d406fad21a5dfe9ff8 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 29 Jul 2025 15:00:41 +0200 Subject: [PATCH 21/24] Optimize the download using the dive number as fingerprint Previously, the date/time information in the dive header was used as the dive fingerprint. In order to decide which dives need to be downloaded, this choice requires downloading the header of each dive during the first pass of the download. When the entire dive is downloaded in the second pass, these dive headers are downloaded a second time. Using the dive number as the fingerprint can eliminate this first download of the dive headers. That's possible because the range of available dive numbers is communicated earlier by the dive computer and thus the fingerprint (dive number of the most recent dive) can be applied immediately. --- src/seac_screen.c | 116 +++++++++++++++++++++++----------------------- 1 file changed, 59 insertions(+), 57 deletions(-) diff --git a/src/seac_screen.c b/src/seac_screen.c index fcb46df4..06ac942a 100644 --- a/src/seac_screen.c +++ b/src/seac_screen.c @@ -67,8 +67,8 @@ #define SZ_ADDRESS 4 #define SZ_READ 2048 -#define FP_OFFSET 0x0A -#define FP_SIZE 7 +#define FP_OFFSET 0 +#define FP_SIZE 4 #define RB_PROFILE_DISTANCE(a,b,l) ringbuffer_distance (a, b, DC_RINGBUFFER_FULL, l->rb_profile_begin, l->rb_profile_end) #define RB_PROFILE_INCR(a,b,l) ringbuffer_increment (a, b, l->rb_profile_begin, l->rb_profile_end) @@ -93,15 +93,10 @@ typedef struct seac_screen_device_t { dc_iostream_t *iostream; const seac_screen_commands_t *cmds; const seac_screen_layout_t *layout; + unsigned int fingerprint; unsigned char info[SZ_HWINFO + SZ_SWINFO]; - unsigned char fingerprint[FP_SIZE]; } seac_screen_device_t; -typedef struct seac_screen_logbook_t { - unsigned int address; - unsigned char header[SZ_HEADER]; -} seac_screen_logbook_t; - static dc_status_t seac_screen_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); static dc_status_t seac_screen_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size); static dc_status_t seac_screen_device_dump (dc_device_t *abstract, dc_buffer_t *buffer); @@ -327,8 +322,8 @@ seac_screen_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t device->iostream = iostream; device->cmds = NULL; device->layout = NULL; + device->fingerprint = 0; memset (device->info, 0, sizeof (device->info)); - 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); @@ -393,13 +388,14 @@ seac_screen_device_set_fingerprint (dc_device_t *abstract, const unsigned char d { seac_screen_device_t *device = (seac_screen_device_t *) abstract; - if (size && size != sizeof (device->fingerprint)) + if (size && size != FP_SIZE) return DC_STATUS_INVALIDARGS; - if (size) - memcpy (device->fingerprint, data, sizeof (device->fingerprint)); - else - memset (device->fingerprint, 0, sizeof (device->fingerprint)); + if (size) { + device->fingerprint = array_uint32_le (data); + } else { + device->fingerprint = 0; + } return DC_STATUS_SUCCESS; } @@ -531,19 +527,32 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, // Calculate the number of dives. unsigned int ndives = last - first + 1; + // Check the fingerprint. + if (device->fingerprint >= last) { + ndives = 0; + } else if (device->fingerprint >= first) { + first = device->fingerprint + 1; + ndives = last - first + 1; + } + // Update and emit a progress event. progress.current += SZ_RANGE; - progress.maximum += SZ_RANGE + ndives * (SZ_ADDRESS + SZ_HEADER); + progress.maximum += SZ_RANGE + ndives * SZ_ADDRESS; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - // Allocate memory for the logbook data. - seac_screen_logbook_t *logbook = (seac_screen_logbook_t *) malloc (ndives * sizeof (seac_screen_logbook_t)); - if (logbook == NULL) { + // Exit if no dives to download. + if (ndives == 0) { + goto error_exit; + } + + // Allocate memory for the dive addresses. + unsigned int *address = (unsigned int *) malloc (ndives * sizeof (unsigned int)); + if (address == NULL) { status = DC_STATUS_NOMEMORY; goto error_exit; } - // Read the header of each dive in reverse order (most recent first). + // Read the address of each dive in reverse order (most recent first). unsigned int eop = 0; unsigned int previous = 0; unsigned int begin = 0; @@ -568,39 +577,39 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, goto error_free_logbook; } - // Get the dive address. - logbook[i].address = array_uint32_be (rsp_address); - if (logbook[i].address < layout->rb_profile_begin || logbook[i].address >= layout->rb_profile_end) { - ERROR (abstract->context, "Invalid ringbuffer pointer (0x%08x).", logbook[i].address); - status = DC_STATUS_DATAFORMAT; - goto error_free_logbook; - } - - // Read the dive header. - status = seac_screen_device_read (abstract, logbook[i].address, logbook[i].header, SZ_HEADER); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the dive header."); - goto error_free_logbook; - } - // Update and emit a progress event. - progress.current += SZ_ADDRESS + SZ_HEADER; + progress.current += SZ_ADDRESS; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - // Check the fingerprint. - if (memcmp (logbook[i].header + FP_OFFSET, device->fingerprint, sizeof (device->fingerprint)) == 0) { - skip = 1; - break; + // Get the dive address. + address[i] = array_uint32_be (rsp_address); + if (address[i] < layout->rb_profile_begin || address[i] >= layout->rb_profile_end) { + ERROR (abstract->context, "Invalid ringbuffer pointer (0x%08x).", address[i]); + status = DC_STATUS_DATAFORMAT; + goto error_free_logbook; } // Get the end-of-profile pointer. if (eop == 0) { + // Read the dive header. + unsigned char header[SZ_HEADER] = {0}; + status = seac_screen_device_read (abstract, address[i], header, sizeof(header)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the dive header."); + goto error_free_logbook; + } + + // Update and emit a progress event. + progress.current += sizeof(header); + progress.maximum += sizeof(header); + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + // Check the header records. unsigned int isvalid = 1; for (unsigned int j = 0; j < 2; ++j) { unsigned int type = j == 0 ? HEADER1 : HEADER2; if (!seac_screen_record_isvalid (abstract->context, - logbook[i].header + j * SZ_HEADER / 2, SZ_HEADER / 2, + header + j * SZ_HEADER / 2, SZ_HEADER / 2, type, number)) { WARNING (abstract->context, "Invalid header record %u.", j); isvalid = 0; @@ -612,22 +621,22 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, // instead and take its start address as the end of the profile. if (!isvalid) { WARNING (abstract->context, "Unable to locate the end of the profile."); - eop = previous = logbook[i].address; + eop = previous = address[i]; begin = 1; skip++; continue; } // Get the number of samples. - unsigned int nsamples = array_uint32_le (logbook[i].header + 0x44); + unsigned int nsamples = array_uint32_le (header + 0x44); unsigned int nbytes = SZ_HEADER + nsamples * SZ_SAMPLE; // Calculate the end of the profile. - eop = previous = RB_PROFILE_INCR (logbook[i].address, nbytes, layout); + eop = previous = RB_PROFILE_INCR (address[i], nbytes, layout); } // Calculate the length. - unsigned int length = RB_PROFILE_DISTANCE (logbook[i].address, previous, layout); + unsigned int length = RB_PROFILE_DISTANCE (address[i], previous, layout); // Check for the end of the ringbuffer. if (length > remaining) { @@ -641,12 +650,12 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, // Move to the start of the current dive. remaining -= length; - previous = logbook[i].address; + previous = address[i]; count++; } // Update and emit a progress event. - progress.maximum -= (ndives - count - skip) * (SZ_ADDRESS + SZ_HEADER) + + progress.maximum -= (ndives - count - skip) * SZ_ADDRESS + ((layout->rb_profile_end - layout->rb_profile_begin) - rb_profile_size); device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); @@ -677,11 +686,11 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, unsigned int number = last - idx; // Calculate the length. - unsigned int length = RB_PROFILE_DISTANCE (logbook[idx].address, previous, layout); + unsigned int length = RB_PROFILE_DISTANCE (address[idx], previous, layout); // Move to the start of the current dive. offset -= length; - previous = logbook[idx].address; + previous = address[idx]; // Read the dive. status = dc_rbstream_read (rbstream, &progress, profile + offset, length); @@ -697,13 +706,6 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, goto error_free_rbstream; } - // Check the dive header. - if (memcmp (profile + offset, logbook[i].header, SZ_HEADER) != 0) { - ERROR (abstract->context, "Unexpected dive header."); - status = DC_STATUS_DATAFORMAT; - goto error_free_rbstream; - } - // Check the header records. unsigned int isvalid = 1; for (unsigned int j = 0; j < 2; ++j) { @@ -736,7 +738,7 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, goto error_free_rbstream; } - if (callback && !callback (profile + offset, nbytes, profile + offset + FP_OFFSET, sizeof(device->fingerprint), userdata)) { + if (callback && !callback (profile + offset, nbytes, profile + offset + FP_OFFSET, FP_SIZE, userdata)) { break; } } @@ -746,7 +748,7 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, error_free_profile: free (profile); error_free_logbook: - free (logbook); + free (address); error_exit: return status; } From 6026d96237e6042b6e9357682707769cd97be839 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 5 Oct 2025 19:51:57 +0200 Subject: [PATCH 22/24] Handle an invalid access code response When the dive computer doesn't accept the access code, it responds with error code 13 and displays a new PIN on the screen. Update the logic to recognize this error code and automatically ask the user for the PIN and request a new access code. --- src/pelagic_i330r.c | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/pelagic_i330r.c b/src/pelagic_i330r.c index 17aad3bf..8cbbed95 100644 --- a/src/pelagic_i330r.c +++ b/src/pelagic_i330r.c @@ -58,11 +58,14 @@ #define RSP_READY 1 #define RSP_DONE 2 +#define RSP_ACCESSCODE_INVALID 13 #define MAXPACKET 255 #define MAXPASSCODE 6 +#define MAXRETRIES 3 + typedef struct pelagic_i330r_device_t { oceanic_common_device_t base; dc_iostream_t *iostream; @@ -276,6 +279,8 @@ pelagic_i330r_transfer (pelagic_i330r_device_t *device, unsigned char cmd, unsig if (errorcode != response) { ERROR (abstract->context, "Unexpected response code (%u)", errorcode); + if (errorcode == RSP_ACCESSCODE_INVALID) + return DC_STATUS_NOACCESS; return DC_STATUS_PROTOCOL; } @@ -388,14 +393,29 @@ pelagic_i330r_init (pelagic_i330r_device_t *device) return status; } - if (array_isequal (device->accesscode, sizeof(device->accesscode), 0)) { - // Request to display the PIN code. + unsigned int nretries = 0; + while (1) { + // Try to request access. status = pelagic_i330r_init_accesscode (device); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to display the PIN code."); + if (status != DC_STATUS_SUCCESS && status != DC_STATUS_NOACCESS) { + ERROR (abstract->context, "Failed to request access."); return status; } + if (array_isequal (device->accesscode, sizeof(device->accesscode), 0)) { + WARNING (abstract->context, "Access denied, no access code."); + } else { + if (status == DC_STATUS_SUCCESS) + break; + WARNING (abstract->context, "Access denied, invalid access code."); + } + + // Abort if the maximum number of retries is reached. + if (nretries++ >= MAXRETRIES) { + ERROR (abstract->context, "Maximum number of retries reached."); + return DC_STATUS_NOACCESS; + } + // Get the bluetooth PIN code. char pincode[6 + 1] = {0}; status = dc_iostream_ioctl (device->iostream, DC_IOCTL_BLE_GET_PINCODE, pincode, sizeof(pincode)); @@ -422,13 +442,6 @@ pelagic_i330r_init (pelagic_i330r_device_t *device) } } - // Request access. - status = pelagic_i330r_init_accesscode (device); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to request access."); - return status; - } - // Send the wakeup command. status = pelagic_i330r_init_handshake (device, 1); if (status != DC_STATUS_SUCCESS) { From 91e279b1e47b5fbdfbeb006ace8efee0db490e4a Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 29 Oct 2025 12:06:42 +0100 Subject: [PATCH 23/24] Add new Shearwater hardware models --- src/shearwater_common.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/shearwater_common.c b/src/shearwater_common.c index b8aeaff9..3681e6d0 100644 --- a/src/shearwater_common.c +++ b/src/shearwater_common.c @@ -736,6 +736,7 @@ shearwater_common_get_model (shearwater_common_device_t *device, unsigned int ha case 0x0C0D: case 0x7C2D: case 0x8D6C: + case 0x425B: model = PERDIXAI; break; case 0x704C: @@ -753,6 +754,7 @@ shearwater_common_get_model (shearwater_common_device_t *device, unsigned int ha model = PEREGRINE; break; case 0x1712: + case 0x813A: model = PEREGRINE_TX; break; case 0xC0E0: From b09c8d9450a1aec0fabfa5d3af56535a73be09bc Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 18 Nov 2025 18:53:05 +0100 Subject: [PATCH 24/24] Add basic support for the avelo mode The Shearwater Avelo mode introduced a new dive mode and sample type. This new Avelo specific sample shares most of its structure with the traditional sample. The changed fields are mainly the ones related to tank T2 and CCR diving (e.g. status bits, O2 sensors, setpoint, etc). --- src/shearwater_predator_parser.c | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index c513f5c2..487d3ed1 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -37,6 +37,7 @@ #define LOG_RECORD_DIVE_SAMPLE 0x01 #define LOG_RECORD_FREEDIVE_SAMPLE 0x02 +#define LOG_RECORD_AVELO_SAMPLE 0x03 #define LOG_RECORD_OPENING_0 0x10 #define LOG_RECORD_OPENING_1 0x11 #define LOG_RECORD_OPENING_2 0x12 @@ -78,6 +79,7 @@ #define M_CC2 5 #define M_OC_REC 6 #define M_FREEDIVE 7 +#define M_AVELO 12 #define AI_OFF 0 #define AI_HPCCR 4 @@ -416,10 +418,15 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) // Get the record type. unsigned int type = pnf ? data[offset] : LOG_RECORD_DIVE_SAMPLE; - if (type == LOG_RECORD_DIVE_SAMPLE) { + if (type == LOG_RECORD_DIVE_SAMPLE || + type == LOG_RECORD_AVELO_SAMPLE) { // Status flags. - unsigned int status = data[offset + 11 + pnf]; - unsigned int ccr = (status & OC) == 0; + unsigned int status = 0; + unsigned int ccr = 0; + if (type != LOG_RECORD_AVELO_SAMPLE) { + status = data[offset + 11 + pnf]; + ccr = (status & OC) == 0; + } if (ccr) { divemode = status & SC ? M_SC : M_CC; } @@ -459,7 +466,8 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) // Tank pressure if (logversion >= 7) { const unsigned int idx[2] = {27, 19}; - for (unsigned int i = 0; i < 2; ++i) { + const unsigned int count = type == LOG_RECORD_AVELO_SAMPLE ? 1 : 2; + for (unsigned int i = 0; i < count; ++i) { // Values above 0xFFF0 are special codes: // 0xFFFF AI is off // 0xFFFE No comms for 90 seconds+ @@ -822,6 +830,7 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ break; case M_OC_TEC: case M_OC_REC: + case M_AVELO: *((dc_divemode_t *) value) = DC_DIVEMODE_OC; break; case M_GAUGE: @@ -903,7 +912,8 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal // Get the record type. unsigned int type = pnf ? data[offset] : LOG_RECORD_DIVE_SAMPLE; - if (type == LOG_RECORD_DIVE_SAMPLE) { + if (type == LOG_RECORD_DIVE_SAMPLE || + type == LOG_RECORD_AVELO_SAMPLE) { // Time (seconds). time += interval; sample.time = time; @@ -933,8 +943,12 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal if (callback) callback (DC_SAMPLE_TEMPERATURE, &sample, userdata); // Status flags. - unsigned int status = data[offset + pnf + 11]; - unsigned int ccr = (status & OC) == 0; + unsigned int status = 0; + unsigned int ccr = 0; + if (type != LOG_RECORD_AVELO_SAMPLE) { + status = data[offset + 11 + pnf]; + ccr = (status & OC) == 0; + } if (ccr) { // PPO2 @@ -1014,7 +1028,8 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal // detect tank pressure if (parser->logversion >= 7) { const unsigned int idx[2] = {27, 19}; - for (unsigned int i = 0; i < 2; ++i) { + const unsigned int count = type == LOG_RECORD_AVELO_SAMPLE ? 1 : 2; + for (unsigned int i = 0; i < count; ++i) { // Tank pressure // Values above 0xFFF0 are special codes: // 0xFFFF AI is off