From dbaf0717e2db4c4c75d3f443d6774eb27b8247ec Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 23 Nov 2025 19:44:19 +0100 Subject: [PATCH 01/33] Add Github sponsorship information --- .github/FUNDING.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..cdc141be --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: jefdriesen +custom: 'https://paypal.me/libdivecomputer' From 6ccb0d0fed6218befb237b67ef468c653fb6763b Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 28 Aug 2025 17:57:34 +0200 Subject: [PATCH 02/33] Update libusb and hidapi in the CI builds Update both dependencies to their latest version. The workaround to link hidapi statically against libgcc is no longer needed because it's the default in the hidapi build system now. --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7051a6c2..86552e10 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -87,7 +87,7 @@ jobs: sudo apt-get install gcc-mingw-w64 binutils-mingw-w64 mingw-w64-tools - name: Install libusb env: - LIBUSB_VERSION: 1.0.26 + LIBUSB_VERSION: 1.0.29 run: | wget -c https://github.com/libusb/libusb/archive/refs/tags/v${LIBUSB_VERSION}.tar.gz tar xzf v${LIBUSB_VERSION}.tar.gz @@ -99,13 +99,13 @@ jobs: popd - name: Install hidapi env: - HIDAPI_VERSION: 0.12.0 + HIDAPI_VERSION: 0.15.0 run: | wget -c https://github.com/libusb/hidapi/archive/refs/tags/hidapi-${HIDAPI_VERSION}.tar.gz tar xzf hidapi-${HIDAPI_VERSION}.tar.gz pushd hidapi-hidapi-${HIDAPI_VERSION} autoreconf --install --force - ./configure --host=${{ matrix.arch }}-w64-mingw32 LDFLAGS='-static-libgcc' + ./configure --host=${{ matrix.arch }}-w64-mingw32 make make install DESTDIR=$PWD/../artifacts popd From c76f25eb1e3ab2dc676f6b54dbcf6450abfb50b4 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 30 Nov 2025 19:43:26 +0100 Subject: [PATCH 03/33] Use the new libusb_init_context function The old libusb_init function is deprecated and may be removed in the future. Detect the API change through the LIBUSB_API_VERSION macro and fallback to the old function for compatibility with older libusb versions. --- src/usb.c | 4 ++++ src/usbhid.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/usb.c b/src/usb.c index d7074cb9..abe22fa4 100644 --- a/src/usb.c +++ b/src/usb.c @@ -190,7 +190,11 @@ dc_usb_session_new (dc_usb_session_t **out, dc_context_t *context) session->refcount = 1; +#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x0100010A) + int rc = libusb_init_context (&session->handle, NULL, 0); +#else int rc = libusb_init (&session->handle); +#endif if (rc != LIBUSB_SUCCESS) { ERROR (context, "Failed to initialize usb support (%s).", libusb_error_name (rc)); diff --git a/src/usbhid.c b/src/usbhid.c index b7818e04..1fd2dfda 100644 --- a/src/usbhid.c +++ b/src/usbhid.c @@ -240,7 +240,11 @@ dc_usbhid_session_new (dc_usbhid_session_t **out, dc_context_t *context) session->refcount = 1; #if defined(USE_LIBUSB) +#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x0100010A) + int rc = libusb_init_context (&session->handle, NULL, 0); +#else int rc = libusb_init (&session->handle); +#endif if (rc != LIBUSB_SUCCESS) { ERROR (context, "Failed to initialize usb support (%s).", libusb_error_name (rc)); From 75ecac9dbdb313256b2bd1df1f9193d9e60a2cae Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 24 Dec 2025 17:19:41 +0100 Subject: [PATCH 04/33] Add new Shearwater hardware models After updating the firmware to version 102, the hardware type of the dive computer appears to change as well. This is an unfortunate change, because the model is no longer detected correctly until the table is fully updated. --- src/shearwater_common.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/shearwater_common.c b/src/shearwater_common.c index 3681e6d0..7b667f32 100644 --- a/src/shearwater_common.c +++ b/src/shearwater_common.c @@ -743,11 +743,13 @@ shearwater_common_get_model (shearwater_common_device_t *device, unsigned int ha case 0xC407: case 0xC964: case 0x9C64: + case 0x924C: model = PERDIX2; break; case 0x0F0F: case 0x1F0A: case 0x1F0F: + case 0x1F1A: model = TERIC; break; case 0x1512: From 4f5abbddc66843c83fabc05959301ec956fd1b77 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 25 Dec 2025 10:54:19 +0100 Subject: [PATCH 05/33] Add support for the Shearwater Swift GPS With the new Swift GPS transmitter, the GPS location of the dive entry and exit points are stored in the opening and closing record number 9. At the moment only the entry location is reported because the api only supports a single location. --- src/shearwater_predator_parser.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index 487d3ed1..7a597099 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -46,6 +46,8 @@ #define LOG_RECORD_OPENING_5 0x15 #define LOG_RECORD_OPENING_6 0x16 #define LOG_RECORD_OPENING_7 0x17 +#define LOG_RECORD_OPENING_8 0x18 +#define LOG_RECORD_OPENING_9 0x19 #define LOG_RECORD_CLOSING_0 0x20 #define LOG_RECORD_CLOSING_1 0x21 #define LOG_RECORD_CLOSING_2 0x22 @@ -54,6 +56,8 @@ #define LOG_RECORD_CLOSING_5 0x25 #define LOG_RECORD_CLOSING_6 0x26 #define LOG_RECORD_CLOSING_7 0x27 +#define LOG_RECORD_CLOSING_8 0x28 +#define LOG_RECORD_CLOSING_9 0x29 #define LOG_RECORD_INFO_EVENT 0x30 #define LOG_RECORD_DIVE_SAMPLE_EXT 0xE1 #define LOG_RECORD_FINAL 0xFF @@ -96,7 +100,7 @@ #define NGASMIXES 20 #define NFIXED 10 #define NTANKS 6 -#define NRECORDS 8 +#define NRECORDS 10 #define PREDATOR 2 #define PETREL 3 @@ -531,7 +535,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) } else if (type == LOG_RECORD_FREEDIVE_SAMPLE) { // Freedive record divemode = M_FREEDIVE; - } else if (type >= LOG_RECORD_OPENING_0 && type <= LOG_RECORD_OPENING_7) { + } else if (type >= LOG_RECORD_OPENING_0 && type <= LOG_RECORD_OPENING_9) { // Opening record parser->opening[type - LOG_RECORD_OPENING_0] = offset; @@ -621,7 +625,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) memcpy (tank[3].name, data + offset + 12, sizeof (tank[3].name)); } } - } else if (type >= LOG_RECORD_CLOSING_0 && type <= LOG_RECORD_CLOSING_7) { + } else if (type >= LOG_RECORD_CLOSING_0 && type <= LOG_RECORD_CLOSING_9) { // Closing record parser->closing[type - LOG_RECORD_CLOSING_0] = offset; } else if (type == LOG_RECORD_FINAL) { @@ -752,11 +756,13 @@ 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; 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) { @@ -865,6 +871,17 @@ 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; + 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) + return DC_STATUS_UNSUPPORTED; + location->latitude = latitude / 100000.0; + location->longitude = longitude / 100000.0; + location->altitude = 0.0; + break; default: return DC_STATUS_UNSUPPORTED; } From b8b41ec1c28927bc5a1c5dedc248b119a7e0e549 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 28 Dec 2025 10:01:22 +0100 Subject: [PATCH 06/33] Ignore invalid GPS locations Apparantly the GPS location fields can also contain the magic value 0xFFFFFFFF (or -1 as signed integer) to indicate the absence of a GPS location. Reported-by: Greg McLaughlin --- src/shearwater_predator_parser.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index 7a597099..91964098 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -876,7 +876,8 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ 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); - if (latitude == 0 && longitude == 0) + if ((latitude == 0 && longitude == 0) || + (latitude == -1 && longitude == -1)) return DC_STATUS_UNSUPPORTED; location->latitude = latitude / 100000.0; location->longitude = longitude / 100000.0; From ebe60afde112ce01494c458ce2e7964003a80389 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 29 Dec 2025 15:54:24 +0100 Subject: [PATCH 07/33] Use the AI mode to detect the presence of GPS data To enable the GPS feature of the Swift transmitter, the AI mode needs to be configured as "On + GPS". Use this new setting to detect the presence of GPS data instead of the log version. --- src/shearwater_predator_parser.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index 91964098..8a8e4538 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -88,6 +88,7 @@ #define AI_OFF 0 #define AI_HPCCR 4 #define AI_ON 5 +#define AI_ON_GPS 6 #define GF 0 #define VPMB 1 @@ -872,7 +873,7 @@ 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->logversion < 17) + if (parser->opening[9] == UNDEFINED || parser->aimode != AI_ON_GPS) 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); From 6d5644b2ca1af963a1bbf8eeb551cf8c441147d3 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 24 Jul 2025 20:31:20 +0200 Subject: [PATCH 08/33] Add some new record types These new record types are not used by libdivecomputer. This change mainly removes some (harmless) "Unknown record" warnings at runtime. --- src/halcyon_symbios_parser.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/halcyon_symbios_parser.c b/src/halcyon_symbios_parser.c index 3452ad87..e5f6a540 100644 --- a/src/halcyon_symbios_parser.c +++ b/src/halcyon_symbios_parser.c @@ -49,6 +49,8 @@ #define ID_GAS_CONFIG 0x11 #define ID_TANK_TRANSMITTER 0x12 #define ID_GF_INFO 0x13 +#define ID_SGC 0x14 +#define ID_GF_DATA 0x15 #define ISCONFIG(type) ( \ (type) == ID_LOG_VERSION || \ @@ -332,6 +334,8 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac 8, /* ID_GAS_CONFIG */ 8, /* ID_TANK_TRANSMITTER */ 6, /* ID_GF_INFO */ + 4, /* ID_SGC */ + 8, /* ID_GF_DATA */ }; unsigned int logversion = 0; @@ -372,6 +376,15 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac return DC_STATUS_DATAFORMAT; } + // Since log version 1.9, the ID_GF_INFO record has been deprecated and + // replaced with the larger ID_GF_DATA record. Unfortunately some + // earlier firmware versions produced records with the new type, but + // with the old size. This has been fixed in log version 1.12. + // Correct the record type to workaround this bug. + if (type == ID_GF_DATA && length == lengths[ID_GF_INFO]) { + type = ID_GF_INFO; + } + if (type < C_ARRAY_SIZE(lengths)) { if (length != lengths[type]) { ERROR (abstract->context, "Unexpected record size (%u %u).", length, lengths[type]); @@ -703,9 +716,15 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac sample.pressure.tank = tank_idx; sample.pressure.value = pressure / 10.0; if (callback) callback(DC_SAMPLE_PRESSURE, &sample, userdata); - } else if (type == ID_GF_INFO) { + } else if (type == ID_GF_INFO || type == ID_GF_DATA) { unsigned int DC_ATTR_UNUSED gf_now = array_uint16_le (data + offset + 2); unsigned int DC_ATTR_UNUSED gf_surface = array_uint16_le (data + offset + 4); + if (type == ID_GF_DATA) { + unsigned int DC_ATTR_UNUSED leading_tissue_gf_now = data[offset + 6]; + unsigned int DC_ATTR_UNUSED leading_tissue_gf_surface = data[offset + 7]; + } + } else if (type == ID_SGC) { + unsigned int DC_ATTR_UNUSED sgc = array_uint16_le (data + offset + 2); } else { WARNING (abstract->context, "Unknown record (type=%u, size=%u", type, length); } From f13cfc238c194477ae17e56ab58cc3b6359ccffb Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sat, 3 Jan 2026 19:22:24 +0100 Subject: [PATCH 09/33] Fix a typo in the log message --- src/halcyon_symbios_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/halcyon_symbios_parser.c b/src/halcyon_symbios_parser.c index e5f6a540..6443ed66 100644 --- a/src/halcyon_symbios_parser.c +++ b/src/halcyon_symbios_parser.c @@ -726,7 +726,7 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac } else if (type == ID_SGC) { unsigned int DC_ATTR_UNUSED sgc = array_uint16_le (data + offset + 2); } else { - WARNING (abstract->context, "Unknown record (type=%u, size=%u", type, length); + WARNING (abstract->context, "Unknown record (type=%u, size=%u)", type, length); } offset += length; From 1640402c1811f4141b94ebd018e8e5eb285527fd Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 6 Nov 2025 19:02:22 +0100 Subject: [PATCH 10/33] Add support for the OSTC Nano The OSTC Nano is a new hwOS based model with a USB-C port (supporting both data communication and battery charging) and a bluetooth module (BLE only). The bluetooth device name is "OSTC nano" (without a serial number). --- src/descriptor.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/descriptor.c b/src/descriptor.c index 3f581225..572cb70e 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -341,6 +341,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Heinrichs Weikamp", "OSTC Sport", DC_FAMILY_HW_OSTC3, 0x12, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, {"Heinrichs Weikamp", "OSTC Sport", DC_FAMILY_HW_OSTC3, 0x13, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, {"Heinrichs Weikamp", "OSTC 2 TR", DC_FAMILY_HW_OSTC3, 0x33, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, + {"Heinrichs Weikamp", "OSTC Nano", DC_FAMILY_HW_OSTC3, 0x11, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_hw}, /* Cressi Edy */ {"Cressi", "Archimede", DC_FAMILY_CRESSI_EDY, 0x01, DC_TRANSPORT_SERIAL, NULL}, {"Tusa", "IQ-700", DC_FAMILY_CRESSI_EDY, 0x05, DC_TRANSPORT_SERIAL, NULL}, From 778518471b23c1175538a7b3e361dd2a34d60ab4 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 30 Dec 2025 11:02:11 +0100 Subject: [PATCH 11/33] Report the TTS information The excursion V1 data format stores TTS information. Report this info when available. This also fixes a bug where the tts field wasn't correctly initialized to zero. --- src/deepsix_excursion_parser.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/deepsix_excursion_parser.c b/src/deepsix_excursion_parser.c index b448cfe5..cd6f7943 100644 --- a/src/deepsix_excursion_parser.c +++ b/src/deepsix_excursion_parser.c @@ -743,14 +743,17 @@ deepsix_excursion_parser_samples_foreach_v1 (dc_parser_t *abstract, dc_sample_ca sample.deco.type = DC_DECO_DECOSTOP; sample.deco.depth = pressure_to_depth(deco_depth, atmospheric, density); sample.deco.time = deco_time; + sample.deco.tts = deco_ndl_tts; } else if (deco_flags & SAFETYSTOP) { sample.deco.type = DC_DECO_SAFETYSTOP; sample.deco.depth = pressure_to_depth(deco_depth, atmospheric, density); sample.deco.time = deco_time; + sample.deco.tts = 0; } else { sample.deco.type = DC_DECO_NDL; sample.deco.depth = 0; sample.deco.time = deco_ndl_tts; + sample.deco.tts = 0; } if (callback) callback (DC_SAMPLE_DECO, &sample, userdata); break; From 959dbb20f1c95598a12266804f45ad57ba1f2fea Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 5 Jan 2026 18:45:29 +0100 Subject: [PATCH 12/33] Use a macro to detect the hwOS variant The macro makes it easier to update the logic in a central place. It also fixes some small bugs in the OSTC4 detection logic in the parser. Since the parser code is shared with the older non-hwOS models, each check for the model number should be preceeded with a check for the hwos flag. --- src/hw_ostc3.c | 22 ++++++++++++---------- src/hw_ostc_parser.c | 13 ++++++++----- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 1b2fca54..1c2a0c9b 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -34,6 +34,8 @@ #define ISINSTANCE(device) dc_device_isinstance((device), &hw_ostc3_device_vtable) +#define ISHWOS4(hardware) ((hardware) == OSTC4) + #define OSTC3FW(major,minor) ( \ (((major) & 0xFF) << 8) | \ ((minor) & 0xFF)) @@ -240,7 +242,7 @@ hw_ostc3_write (hw_ostc3_device_t *device, dc_event_progress_t *progress, const size_t nbytes = 0; while (nbytes < size) { // Set the maximum packet size. - size_t length = (device->hardware == OSTC4) ? 64 : 1024; + size_t length = ISHWOS4(device->hardware) ? 64 : 1024; // Limit the packet size to the total size. if (nbytes + length > size) @@ -622,7 +624,7 @@ hw_ostc3_device_init (hw_ostc3_device_t *device, hw_ostc3_state_t state) device->feature = array_uint16_be(hardware + 2); device->model = hardware[4]; device->serial = array_uint16_le (version + 0); - if (device->hardware == OSTC4) { + if (ISHWOS4(device->hardware)) { device->firmware = array_uint16_le (version + 2); } else { device->firmware = array_uint16_be (version + 2); @@ -802,7 +804,7 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi // Get the internal dive number. unsigned int current = array_uint16_le (header + offset + logbook->number); - if (current > maximum || device->hardware == OSTC4) { + if (current > maximum || ISHWOS4(device->hardware)) { maximum = current; latest = i; } @@ -904,7 +906,7 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi } // Detect invalid profile data. - unsigned int delta = device->hardware == OSTC4 ? 3 : 0; + unsigned int delta = ISHWOS4(device->hardware) ? 3 : 0; if (length < RB_LOGBOOK_SIZE_FULL + 2 || profile[length - 2] != 0xFD || profile[length - 1] != 0xFD) { // A valid profile should have at least a correct 2 byte @@ -1022,7 +1024,7 @@ hw_ostc3_device_config_read (dc_device_t *abstract, unsigned int config, unsigne if (rc != DC_STATUS_SUCCESS) return rc; - if (device->hardware == OSTC4 ? size != SZ_CONFIG : size > SZ_CONFIG) { + if (ISHWOS4(device->hardware) ? size != SZ_CONFIG : size > SZ_CONFIG) { ERROR (abstract->context, "Invalid parameter specified."); return DC_STATUS_INVALIDARGS; } @@ -1048,7 +1050,7 @@ hw_ostc3_device_config_write (dc_device_t *abstract, unsigned int config, const if (rc != DC_STATUS_SUCCESS) return rc; - if (device->hardware == OSTC4 ? size != SZ_CONFIG : size > SZ_CONFIG) { + if (ISHWOS4(device->hardware) ? size != SZ_CONFIG : size > SZ_CONFIG) { ERROR (abstract->context, "Invalid parameter specified."); return DC_STATUS_INVALIDARGS; } @@ -1615,7 +1617,7 @@ hw_ostc3_device_fwupdate (dc_device_t *abstract, const char *filename) return status; } - if (device->hardware == OSTC4) { + if (ISHWOS4(device->hardware)) { return hw_ostc3_device_fwupdate4 (abstract, filename); } else { return hw_ostc3_device_fwupdate3 (abstract, filename); @@ -1640,7 +1642,7 @@ hw_ostc3_device_read (dc_device_t *abstract, unsigned int address, unsigned char return status; } - if (device->hardware == OSTC4) { + if (ISHWOS4(device->hardware)) { return DC_STATUS_UNSUPPORTED; } @@ -1677,7 +1679,7 @@ hw_ostc3_device_write (dc_device_t *abstract, unsigned int address, const unsign return status; } - if (device->hardware == OSTC4) { + if (ISHWOS4(device->hardware)) { return DC_STATUS_UNSUPPORTED; } @@ -1719,7 +1721,7 @@ hw_ostc3_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) return rc; } - if (device->hardware == OSTC4) { + if (ISHWOS4(device->hardware)) { return DC_STATUS_UNSUPPORTED; } diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index 47590b0a..1273cc5e 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -81,6 +81,9 @@ #define OSTC4_GNSS_DUMMY_LATITUDE 8.99f #define OSTC4_GNSS_DUMMY_LONGITUDE 47.77f +#define ISHWOS3(hwos,model) ((hwos) && (model) != OSTC4) +#define ISHWOS4(hwos,model) ((hwos) && (model) == OSTC4) + #define OSTC3FW(major,minor) ( \ (((major) & 0xFF) << 8) | \ ((minor) & 0xFF)) @@ -818,7 +821,7 @@ hw_ostc_parser_internal_foreach (hw_ostc_parser_t *parser, dc_sample_callback_t // Get the firmware version. unsigned int firmware = 0; - if (parser->model == OSTC4) { + if (ISHWOS4(parser->hwos, parser->model)) { firmware = array_uint16_le (data + layout->firmware); DEBUG (abstract->context, "Device: firmware=%u (%u.%u.%u.%u)", firmware, @@ -979,7 +982,7 @@ hw_ostc_parser_internal_foreach (hw_ostc_parser_t *parser, dc_sample_callback_t return DC_STATUS_DATAFORMAT; } unsigned int id = data[offset]; - if (parser->model == OSTC4 && ccr && id > parser->nfixed) { + if (ISHWOS4(parser->hwos, parser->model) && ccr && id > parser->nfixed) { // Fix the OSTC4 diluent index. id -= parser->nfixed; } @@ -1107,7 +1110,7 @@ hw_ostc_parser_internal_foreach (hw_ostc_parser_t *parser, dc_sample_callback_t // the hwOS Sport firmware v10.57 to v10.63, the ppO2 divisor // is sometimes not correctly reset to zero when no ppO2 // samples are being recorded. - if (info[i].type == PPO2 && parser->hwos && parser->model != OSTC4 && + if (info[i].type == PPO2 && ISHWOS3(parser->hwos, parser->model) && ((firmware >= OSTC3FW(3,3) && firmware <= OSTC3FW(3,8)) || (firmware >= OSTC3FW(10,57) && firmware <= OSTC3FW(10,63)))) { WARNING (abstract->context, "Reset invalid ppO2 divisor to zero."); @@ -1130,7 +1133,7 @@ hw_ostc_parser_internal_foreach (hw_ostc_parser_t *parser, dc_sample_callback_t case DECO: // Due to a firmware bug, the deco/ndl info is incorrect for // all OSTC4 dives with a firmware older than version 1.0.8. - if (parser->model == OSTC4 && firmware < OSTC4FW(1,0,8,0)) + if (ISHWOS4(parser->hwos, parser->model) && firmware < OSTC4FW(1,0,8,0)) break; if (data[offset]) { sample.deco.type = DC_DECO_DECOSTOP; @@ -1175,7 +1178,7 @@ hw_ostc_parser_internal_foreach (hw_ostc_parser_t *parser, dc_sample_callback_t sample.pressure.value = value; // The hwOS Sport firmware used a resolution of // 0.1 bar between versions 10.40 and 10.50. - if (parser->hwos && parser->model != OSTC4 && + if (ISHWOS3(parser->hwos, parser->model) && (firmware >= OSTC3FW(10,40) && firmware <= OSTC3FW(10,50))) { sample.pressure.value /= 10.0; } From a7063ef1c1d4826dfc17a80b73ace102707f204b Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 8 Jan 2026 20:54:24 +0100 Subject: [PATCH 13/33] Add new Shearwater hardware models Source of the information is the Shearwater firmware update file: https://downloads.shearwater.com/liveupdates/firmwareupdate.xml --- src/shearwater_common.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/shearwater_common.c b/src/shearwater_common.c index 7b667f32..37c75e5b 100644 --- a/src/shearwater_common.c +++ b/src/shearwater_common.c @@ -720,6 +720,7 @@ shearwater_common_get_model (shearwater_common_device_t *device, unsigned int ha model = PETREL2; break; case 0xB407: + case 0xB429: model = PETREL3; break; case 0x0606: @@ -734,25 +735,31 @@ shearwater_common_get_model (shearwater_common_device_t *device, unsigned int ha model = PERDIX; break; case 0x0C0D: + case 0x425B: case 0x7C2D: case 0x8D6C: - case 0x425B: model = PERDIXAI; break; case 0x704C: + case 0x924C: + case 0x9C64: case 0xC407: + case 0xC429: case 0xC964: - case 0x9C64: - case 0x924C: 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: From 3ca5e79ef08b6fac645ffb7fd9c20a9ae7be5707 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 20 Jan 2026 20:23:06 +0100 Subject: [PATCH 14/33] Fix the setpoint value Read the setpoint value from the sample instead of at a fixed offset somewhere in the dive header (where the dive number happens to be located). --- src/divesoft_freedom_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/divesoft_freedom_parser.c b/src/divesoft_freedom_parser.c index 83bb0c13..e5e441f7 100644 --- a/src/divesoft_freedom_parser.c +++ b/src/divesoft_freedom_parser.c @@ -1019,7 +1019,7 @@ divesoft_freedom_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callba sample.cns = array_uint16_le (data + offset + 6) / 100.0; if (callback) callback(DC_SAMPLE_CNS, &sample, userdata); } else if (event == EVENT_SETPOINT_MANUAL || event == EVENT_SETPOINT_AUTO) { - sample.setpoint = data[6] / 100.0; + sample.setpoint = data[offset + 6] / 100.0; if (callback) callback(DC_SAMPLE_SETPOINT, &sample, userdata); } } else if (type == LREC_MEASURE) { From 69f3746ca9679adae06239ceaa7b3a94b733eaa4 Mon Sep 17 00:00:00 2001 From: Simone Carletti Date: Fri, 16 Jan 2026 11:37:20 +0000 Subject: [PATCH 15/33] Fix Halcyon Symbios dive mode parsing The Halcyon Symbios has five dive modes: - OC (Open Circuit) = 0 - CCR (Closed Circuit Rebreather) = 1 - CCR FSP (CCR Fixed Set Point) = 2 - Sidemount = 3 - Bottom Timer (Gauge) = 4 The previous code incorrectly assigned GAUGE = 2, which caused CCR FSP dives to be reported as gauge mode instead of CCR mode. CCR FSP is a CCR mode where the setpoint is fixed (typically 1.3 ppO2) rather than manually adjusted by the diver. Both CCR and CCR FSP should map to DC_DIVEMODE_CCR. Signed-off-by: Simone Carletti --- src/halcyon_symbios_parser.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/halcyon_symbios_parser.c b/src/halcyon_symbios_parser.c index 6443ed66..e871fec8 100644 --- a/src/halcyon_symbios_parser.c +++ b/src/halcyon_symbios_parser.c @@ -66,9 +66,10 @@ #define EPOCH 1609459200 /* 2021-01-01 00:00:00 */ #define OC 0 -#define CC 1 -#define GAUGE 2 +#define CCR 1 +#define CCR_FSP 2 #define SIDEMOUNT 3 +#define GAUGE 4 #define NGASMIXES 10 #define NTANKS 10 @@ -250,7 +251,8 @@ halcyon_symbios_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, u case SIDEMOUNT: *((dc_divemode_t *) value) = DC_DIVEMODE_OC; break; - case CC: + case CCR: + case CCR_FSP: *((dc_divemode_t *) value) = DC_DIVEMODE_CCR; break; case GAUGE: From 65d08130f950328a59455d535a017594370c55d8 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 21 Jan 2026 21:27:46 +0100 Subject: [PATCH 16/33] Report the correct compass headings The Divesoft dive computers support two types of orientation related records: - The EVENT_WAYPOINT record contains the compass heading and is manually bookmarked by the diver on the (virtual) compass. - The POINT_2 record contains spatial orientation data and is recorded continuously throughout the dive by the dive computer. This orientation data does not only include the heading, but also pitch and roll info. At the moment, the libdivecomputer compass bearing sample only supports the first type of orientation data. Supporting the second type would require a new sample type, to be able to include the pitch and roll info. --- src/divesoft_freedom_parser.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/divesoft_freedom_parser.c b/src/divesoft_freedom_parser.c index e5e441f7..eb6621a4 100644 --- a/src/divesoft_freedom_parser.c +++ b/src/divesoft_freedom_parser.c @@ -949,12 +949,7 @@ divesoft_freedom_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callba if (callback) callback(DC_SAMPLE_PPO2, &sample, userdata); } - if (id == POINT_2) { - unsigned int orientation = array_uint32_le (data + offset + 8); - unsigned int heading = orientation & 0x1FF; - sample.bearing = heading; - if (callback) callback (DC_SAMPLE_BEARING, &sample, userdata); - } else if (id == POINT_1 || id == POINT_1_OLD) { + if (id == POINT_1 || id == POINT_1_OLD) { unsigned int misc = array_uint32_le (data + offset + 8); unsigned int ceiling = array_uint16_le (data + offset + 12); unsigned int setpoint = data[offset + 15]; @@ -1021,6 +1016,12 @@ divesoft_freedom_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callba } else if (event == EVENT_SETPOINT_MANUAL || event == EVENT_SETPOINT_AUTO) { sample.setpoint = data[offset + 6] / 100.0; if (callback) callback(DC_SAMPLE_SETPOINT, &sample, userdata); + } else if (event == EVENT_WAYPOINT) { + unsigned int heading = array_uint16_le (data + offset + 8); + if ((heading & 0x8000) == 0) { + sample.bearing = heading; + if (callback) callback (DC_SAMPLE_BEARING, &sample, userdata); + } } } else if (type == LREC_MEASURE) { // Measurement record. From ca169948006e49290bb34092c430a90cf0e7c120 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 22 Jan 2026 19:09:17 +0100 Subject: [PATCH 17/33] Remove the deprecated heading events The bearing sample has been introduced long time ago already, but the Suunto backends were never updated and are still using the legacy heading event. Switch to the bearing sample and mark the heading event as deprecated. --- doc/man/dc_parser_samples_foreach.3 | 1 - include/libdivecomputer/parser.h | 2 +- src/suunto_d9_parser.c | 8 ++++---- src/suunto_eonsteel_parser.c | 5 ++--- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/doc/man/dc_parser_samples_foreach.3 b/doc/man/dc_parser_samples_foreach.3 index 69a637f2..459aca8a 100644 --- a/doc/man/dc_parser_samples_foreach.3 +++ b/doc/man/dc_parser_samples_foreach.3 @@ -117,7 +117,6 @@ structure set to .Dv SAMPLE_EVENT_PO2 , .Dv SAMPLE_EVENT_AIRTIME , .Dv SAMPLE_EVENT_RGBM , -.Dv SAMPLE_EVENT_HEADING , or .Dv SAMPLE_EVENT_TISSUELEVEL . .It Dv DC_SAMPLE_RBT diff --git a/include/libdivecomputer/parser.h b/include/libdivecomputer/parser.h index 09b52ac6..14dbe348 100644 --- a/include/libdivecomputer/parser.h +++ b/include/libdivecomputer/parser.h @@ -91,7 +91,7 @@ typedef enum parser_sample_event_t { SAMPLE_EVENT_PO2, SAMPLE_EVENT_AIRTIME, SAMPLE_EVENT_RGBM, - SAMPLE_EVENT_HEADING, + SAMPLE_EVENT_HEADING, /* Deprecated: replaced by DC_SAMPLE_BEARING. */ SAMPLE_EVENT_TISSUELEVEL, SAMPLE_EVENT_GASCHANGE2, /* Deprecated: replaced by DC_SAMPLE_GASMIX. */ } parser_sample_event_t; diff --git a/src/suunto_d9_parser.c b/src/suunto_d9_parser.c index 9973af29..6596d143 100644 --- a/src/suunto_d9_parser.c +++ b/src/suunto_d9_parser.c @@ -706,12 +706,12 @@ suunto_d9_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t ca if (heading == 0xFFFF) { sample.event.type = SAMPLE_EVENT_BOOKMARK; sample.event.value = 0; + sample.event.time = seconds; + if (callback) callback (DC_SAMPLE_EVENT, &sample, userdata); } else { - sample.event.type = SAMPLE_EVENT_HEADING; - sample.event.value = heading / 2; + sample.bearing = heading / 2; + if (callback) callback (DC_SAMPLE_BEARING, &sample, userdata); } - sample.event.time = seconds; - if (callback) callback (DC_SAMPLE_EVENT, &sample, userdata); offset += 4; break; case 0x05: // Gas Change diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index d6fd0279..c9fe649c 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -534,9 +534,8 @@ static void sample_heading(struct sample_data *info, unsigned short heading) if (heading == 0xffff) return; - sample.event.type = SAMPLE_EVENT_HEADING; - sample.event.value = heading; - if (info->callback) info->callback(DC_SAMPLE_EVENT, &sample, info->userdata); + sample.bearing = heading; + if (info->callback) info->callback(DC_SAMPLE_BEARING, &sample, info->userdata); } static void sample_abspressure(struct sample_data *info, unsigned short pressure) From db0e04bb7b6e20961913b88014bb17c018430ada Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 26 Jan 2026 18:16:57 +0100 Subject: [PATCH 18/33] Add some extra debug logging --- src/hw_ostc3.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 1c2a0c9b..e8ad19d1 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -630,6 +630,25 @@ hw_ostc3_device_init (hw_ostc3_device_t *device, hw_ostc3_state_t state) device->firmware = array_uint16_be (version + 2); } + DEBUG (abstract->context, "Device: hardware=%04x, feature=%04x, model=%02x", + device->hardware, device->feature, device->model); + + if (ISHWOS4(device->hardware)) { + DEBUG (abstract->context, "Device: serial=%u, firmware=%u (%u.%u.%u.%u)", + device->serial, + device->firmware, + (device->firmware >> 11) & 0x1F, + (device->firmware >> 6) & 0x1F, + (device->firmware >> 1) & 0x1F, + (device->firmware ) & 0x01); + } else { + DEBUG (abstract->context, "Device: serial=%u, firmware=%u (%u.%u)", + device->serial, + device->firmware, + (device->firmware >> 8) & 0xFF, + (device->firmware ) & 0xFF); + } + return DC_STATUS_SUCCESS; } From 40fc085e0d6a29eaa956ba737a50b78f028be6a8 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 4 Feb 2026 16:43:32 +0100 Subject: [PATCH 19/33] Log the received hardware response Log only the hardware bytes that were actually received from the dive computer and not the full internal buffer. --- src/hw_ostc3.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index e8ad19d1..d6d64e2e 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -600,14 +600,28 @@ hw_ostc3_device_init (hw_ostc3_device_t *device, hw_ostc3_state_t state) return DC_STATUS_SUCCESS; // Read the hardware descriptor. - unsigned char hardware[SZ_HARDWARE2] = {0, UNKNOWN}; - rc = hw_ostc3_device_id (device, hardware, sizeof(hardware)); - if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { - ERROR (abstract->context, "Failed to read the hardware descriptor."); - return rc; + // Try reading the extended 5 byte variant first and if it's not supported, + // fall back to reading the 1 byte variant. Some (older) firmware versions + // don't support reading the hardware descriptor at all. + unsigned char hardware[SZ_HARDWARE2] = {0}; + unsigned int hardware_offset = 0, hardware_size = 0; + const unsigned char hardware_cmd[] = {HARDWARE2, HARDWARE}; + const unsigned int hardware_len[] = {SZ_HARDWARE2, SZ_HARDWARE}; + for (unsigned int i = 0; i < 2; ++i) { + rc = hw_ostc3_transfer (device, NULL, hardware_cmd[i], NULL, 0, hardware + i, hardware_len[i], NULL, NODELAY); + if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { + ERROR (abstract->context, "Failed to read the hardware descriptor."); + return rc; + } + + if (rc == DC_STATUS_SUCCESS) { + hardware_offset = i; + hardware_size = hardware_len[i]; + break; + } } - HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Hardware", hardware, sizeof(hardware)); + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Hardware", hardware + hardware_offset, hardware_size); // Read the version information. unsigned char version[SZ_VERSION] = {0}; From 045598b6e812a134499f697ddda69608f6b2a45e Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 6 Feb 2026 19:16:12 +0100 Subject: [PATCH 20/33] Remove the automatic fallback for the hardware command The automatic fallback from the 5 byte HARDWARE2 (0x60) command to the 1 byte HARDWARE (0x6A) command makes it impossible for the caller to know which bytes in the response are actually valid. Fixed by removing the automatic fallback. An application trying to read the 5 byte variant can now detect whether the command isn't supported (by means of the DC_STATUS_UNSUPPORTED return value) and manually fallback to the 1 byte variant. If only the 1 byte hardware descriptor is needed, there is no need to even try reading the 5 byte variant. --- src/hw_ostc3.c | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index d6d64e2e..bc9065c0 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -482,33 +482,6 @@ hw_ostc3_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *i } -static dc_status_t -hw_ostc3_device_id (hw_ostc3_device_t *device, unsigned char data[], unsigned int size) -{ - dc_status_t status = DC_STATUS_SUCCESS; - - if (size != SZ_HARDWARE && size != SZ_HARDWARE2) - return DC_STATUS_INVALIDARGS; - - // Send the command. - unsigned char hardware[SZ_HARDWARE2] = {0}; - status = hw_ostc3_transfer (device, NULL, HARDWARE2, NULL, 0, hardware, SZ_HARDWARE2, NULL, NODELAY); - if (status == DC_STATUS_UNSUPPORTED) { - status = hw_ostc3_transfer (device, NULL, HARDWARE, NULL, 0, hardware + 1, SZ_HARDWARE, NULL, NODELAY); - } - if (status != DC_STATUS_SUCCESS) - return status; - - if (size == SZ_HARDWARE2) { - memcpy (data, hardware, SZ_HARDWARE2); - } else { - memcpy (data, hardware + 1, SZ_HARDWARE); - } - - return DC_STATUS_SUCCESS; -} - - static dc_status_t hw_ostc3_device_init_download (hw_ostc3_device_t *device) { @@ -753,7 +726,8 @@ hw_ostc3_device_hardware (dc_device_t *abstract, unsigned char data[], unsigned return rc; // Send the command. - rc = hw_ostc3_device_id (device, data, size); + const unsigned char cmd = size == SZ_HARDWARE2 ? HARDWARE2 : HARDWARE; + rc = hw_ostc3_transfer (device, NULL, cmd, NULL, 0, data, size, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; From 40cf2fa878d71d0d324069231aa91b415ddca908 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 3 Dec 2025 19:59:34 +0100 Subject: [PATCH 21/33] Replace the hardware descriptor with the model number The different bits in the hardware descriptor describe the available hardware features: 0 Rechargeable battery with battery management chip 1 Ambient sensor 2 Analog inputs and S8 digital 3 Digital optical input 4 BLE module 5 32bit dual core 6 Low voltage core 7 External flash memory with block write support Since different models can share the same hardware features, the hardware descriptor can no longer be used to reliably identify the exact model. Trying to maintain this relationship anyway, only results in many duplicated entries in the list of supported devices with the same name but a different model number. For this reason, the hwOS firmware even masks off the upper 2 bits of the hardware descriptor before sending the value. Use the model descriptor as the internal model number instead. All the hwOS models, except for the OSTC 4 and 5, report a zero value here. --- examples/common.c | 2 +- src/descriptor.c | 23 +++++++++-------------- src/hw_ostc3.c | 38 ++++++++++++++------------------------ src/hw_ostc_parser.c | 7 ++++--- 4 files changed, 28 insertions(+), 42 deletions(-) diff --git a/examples/common.c b/examples/common.c index a1eeb44e..493ebe5e 100644 --- a/examples/common.c +++ b/examples/common.c @@ -79,7 +79,7 @@ static const backend_table_t g_backends[] = { {"iconhd", DC_FAMILY_MARES_ICONHD, 0x14}, {"ostc", DC_FAMILY_HW_OSTC, 0}, {"frog", DC_FAMILY_HW_FROG, 0}, - {"ostc3", DC_FAMILY_HW_OSTC3, 0x11}, + {"ostc3", DC_FAMILY_HW_OSTC3, 0}, {"edy", DC_FAMILY_CRESSI_EDY, 0x08}, {"leonardo", DC_FAMILY_CRESSI_LEONARDO, 1}, {"goa", DC_FAMILY_CRESSI_GOA, 2}, diff --git a/src/descriptor.c b/src/descriptor.c index 572cb70e..a551cef2 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -328,20 +328,15 @@ static const dc_descriptor_t g_descriptors[] = { {"Heinrichs Weikamp", "OSTC 2N", DC_FAMILY_HW_OSTC, 2, DC_TRANSPORT_SERIAL, NULL}, {"Heinrichs Weikamp", "OSTC 2C", DC_FAMILY_HW_OSTC, 3, DC_TRANSPORT_SERIAL, NULL}, {"Heinrichs Weikamp", "Frog", DC_FAMILY_HW_FROG, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_hw}, - {"Heinrichs Weikamp", "OSTC 2", DC_FAMILY_HW_OSTC3, 0x11, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, - {"Heinrichs Weikamp", "OSTC 2", DC_FAMILY_HW_OSTC3, 0x13, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, - {"Heinrichs Weikamp", "OSTC 2", DC_FAMILY_HW_OSTC3, 0x1B, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, - {"Heinrichs Weikamp", "OSTC 3", DC_FAMILY_HW_OSTC3, 0x0A, DC_TRANSPORT_SERIAL, NULL}, - {"Heinrichs Weikamp", "OSTC Plus", DC_FAMILY_HW_OSTC3, 0x13, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, - {"Heinrichs Weikamp", "OSTC Plus", DC_FAMILY_HW_OSTC3, 0x1A, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, - {"Heinrichs Weikamp", "OSTC 4", DC_FAMILY_HW_OSTC3, 0x3B, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, - {"Heinrichs Weikamp", "OSTC 5", DC_FAMILY_HW_OSTC3, 0x3B, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, - {"Heinrichs Weikamp", "OSTC cR", DC_FAMILY_HW_OSTC3, 0x05, DC_TRANSPORT_SERIAL, NULL}, - {"Heinrichs Weikamp", "OSTC cR", DC_FAMILY_HW_OSTC3, 0x07, DC_TRANSPORT_SERIAL, NULL}, - {"Heinrichs Weikamp", "OSTC Sport", DC_FAMILY_HW_OSTC3, 0x12, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, - {"Heinrichs Weikamp", "OSTC Sport", DC_FAMILY_HW_OSTC3, 0x13, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, - {"Heinrichs Weikamp", "OSTC 2 TR", DC_FAMILY_HW_OSTC3, 0x33, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, - {"Heinrichs Weikamp", "OSTC Nano", DC_FAMILY_HW_OSTC3, 0x11, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_hw}, + {"Heinrichs Weikamp", "OSTC 2", DC_FAMILY_HW_OSTC3, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, + {"Heinrichs Weikamp", "OSTC 2 TR", DC_FAMILY_HW_OSTC3, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, + {"Heinrichs Weikamp", "OSTC 3", DC_FAMILY_HW_OSTC3, 0, DC_TRANSPORT_SERIAL, NULL}, + {"Heinrichs Weikamp", "OSTC cR", DC_FAMILY_HW_OSTC3, 0, DC_TRANSPORT_SERIAL, NULL}, + {"Heinrichs Weikamp", "OSTC Plus", DC_FAMILY_HW_OSTC3, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, + {"Heinrichs Weikamp", "OSTC Sport", DC_FAMILY_HW_OSTC3, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, + {"Heinrichs Weikamp", "OSTC Nano", DC_FAMILY_HW_OSTC3, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_hw}, + {"Heinrichs Weikamp", "OSTC 4", DC_FAMILY_HW_OSTC3, 0x43, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, + {"Heinrichs Weikamp", "OSTC 5", DC_FAMILY_HW_OSTC3, 0x44, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, /* Cressi Edy */ {"Cressi", "Archimede", DC_FAMILY_CRESSI_EDY, 0x01, DC_TRANSPORT_SERIAL, NULL}, {"Tusa", "IQ-700", DC_FAMILY_CRESSI_EDY, 0x05, DC_TRANSPORT_SERIAL, NULL}, diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index bc9065c0..6bd85609 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -34,7 +34,7 @@ #define ISINSTANCE(device) dc_device_isinstance((device), &hw_ostc3_device_vtable) -#define ISHWOS4(hardware) ((hardware) == OSTC4) +#define ISHWOS4(hardware) ((hardware) == 0x3B) #define OSTC3FW(major,minor) ( \ (((major) & 0xFF) << 8) | \ @@ -83,11 +83,9 @@ #define EXIT 0xFF #define INVALID 0xFFFFFFFF -#define UNKNOWN 0x00 -#define OSTC3 0x0A -#define OSTC4 0x3B -#define SPORT 0x12 -#define CR 0x05 + +#define OSTC4 0x43 +#define OSTC5 0x44 #define NODELAY 0 #define TIMEOUT 400 @@ -612,6 +610,14 @@ hw_ostc3_device_init (hw_ostc3_device_t *device, hw_ostc3_state_t state) device->model = hardware[4]; device->serial = array_uint16_le (version + 0); if (ISHWOS4(device->hardware)) { + // The parser uses the model number to distinguish the OSTC 4 and 5 + // from the other hwOS models. Therefore, set the model number manually + // if it's not available because the HARDWARE2 command isn't supported. + // Detecting the difference between the OSTC 4 and 5 isn't possible + // here, so assume an OSTC 4. + if (hardware_size != SZ_HARDWARE2) { + device->model = OSTC4; + } device->firmware = array_uint16_le (version + 2); } else { device->firmware = array_uint16_be (version + 2); @@ -753,15 +759,7 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi dc_event_devinfo_t devinfo; devinfo.firmware = device->firmware; devinfo.serial = device->serial; - if (device->hardware != UNKNOWN) { - devinfo.model = device->hardware; - } else { - // Fallback to the serial number. - if (devinfo.serial > 10000) - devinfo.model = SPORT; - else - devinfo.model = OSTC3; - } + devinfo.model = device->model; device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); // Allocate memory. @@ -1736,15 +1734,7 @@ hw_ostc3_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) dc_event_devinfo_t devinfo; devinfo.firmware = device->firmware; devinfo.serial = device->serial; - if (device->hardware != UNKNOWN) { - devinfo.model = device->hardware; - } else { - // Fallback to the serial number. - if (devinfo.serial > 10000) - devinfo.model = SPORT; - else - devinfo.model = OSTC3; - } + devinfo.model = device->model; device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); // Allocate the required amount of memory. diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index 1273cc5e..37309f3d 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -70,7 +70,8 @@ #define OSTC3_ZHL16_GF 1 #define OSTC4_VPM 2 -#define OSTC4 0x3B +#define OSTC4 0x43 +#define OSTC5 0x44 #define OSTC4_COMPASS_SET 0x4000 #define OSTC4_COMPASS_CLEARED 0x8000 @@ -81,8 +82,8 @@ #define OSTC4_GNSS_DUMMY_LATITUDE 8.99f #define OSTC4_GNSS_DUMMY_LONGITUDE 47.77f -#define ISHWOS3(hwos,model) ((hwos) && (model) != OSTC4) -#define ISHWOS4(hwos,model) ((hwos) && (model) == OSTC4) +#define ISHWOS3(hwos,model) ((hwos) && ((model) != OSTC4 && (model) != OSTC5)) +#define ISHWOS4(hwos,model) ((hwos) && ((model) == OSTC4 || (model) == OSTC5)) #define OSTC3FW(major,minor) ( \ (((major) & 0xFF) << 8) | \ From 415778c314c052bcfbfac8be89f46cae416230a3 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 7 May 2025 19:21:18 +0200 Subject: [PATCH 22/33] Add BLE support for the Seac Tablet The Seac Tablet supports both usb-serial and BLE communication. Both transports share the same high-level communication protocol. Only the packetization is different. The BLE communication is based on the typical UART-over-BLE protocol, featuring the following service and Rx/Tx characteristics: Service: 84968ffe-d26d-478a-b953-5010bcf58bca Characteristics: - Rx/Tx: 43c620c2-1b09-4951-bc1e-9c75298cddeb (read, write) Remarkably, the Tx characteristic does not support notifications or indications and must be read instead. The bluetooth device name is "TabletXXXXXX" where XXXXXX is the serial number of the dive computer. --- src/descriptor.c | 17 ++++++++++++++++- src/seac_screen.c | 43 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index a551cef2..1a75aed4 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -63,6 +63,7 @@ static int dc_filter_oceans (const dc_descriptor_t *descriptor, dc_transport_t t static int dc_filter_divesoft (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); static int dc_filter_cressi (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); static int dc_filter_halcyon (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); +static int dc_filter_seac (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); static dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item); @@ -478,7 +479,7 @@ static const dc_descriptor_t g_descriptors[] = { /* Seac Screen */ {"Seac", "Action", DC_FAMILY_SEAC_SCREEN, 0x01, DC_TRANSPORT_SERIAL, NULL}, {"Seac", "Screen", DC_FAMILY_SEAC_SCREEN, 0x02, DC_TRANSPORT_SERIAL, NULL}, - {"Seac", "Tablet", DC_FAMILY_SEAC_SCREEN, 0x10, DC_TRANSPORT_SERIAL, NULL}, + {"Seac", "Tablet", DC_FAMILY_SEAC_SCREEN, 0x10, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_seac}, /* Deepblu Cosmiq */ {"Deepblu", "Cosmiq+", DC_FAMILY_DEEPBLU_COSMIQ, 0, DC_TRANSPORT_BLE, dc_filter_deepblu}, /* Oceans S1 */ @@ -960,6 +961,20 @@ dc_filter_halcyon (const dc_descriptor_t *descriptor, dc_transport_t transport, return 1; } +static int +dc_filter_seac (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +{ + static const char * const bluetooth[] = { + "Tablet", + }; + + if (transport == DC_TRANSPORT_BLE) { + return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_prefix_with_number); + } + + return 1; +} + dc_status_t dc_descriptor_iterator_new (dc_iterator_t **out, dc_context_t *context) { diff --git a/src/seac_screen.c b/src/seac_screen.c index 06ac942a..fbad09cf 100644 --- a/src/seac_screen.c +++ b/src/seac_screen.c @@ -28,6 +28,7 @@ #include "device-private.h" #include "ringbuffer.h" #include "rbstream.h" +#include "packet.h" #include "checksum.h" #include "array.h" @@ -101,6 +102,7 @@ static dc_status_t seac_screen_device_set_fingerprint (dc_device_t *abstract, co 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); static dc_status_t seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +static dc_status_t seac_screen_device_close (dc_device_t *abstract); static const dc_device_vtable_t seac_screen_device_vtable = { sizeof(seac_screen_device_t), @@ -111,7 +113,7 @@ static const dc_device_vtable_t seac_screen_device_vtable = { seac_screen_device_dump, /* dump */ seac_screen_device_foreach, /* foreach */ NULL, /* timesync */ - NULL, /* close */ + seac_screen_device_close, /* close */ }; static const seac_screen_commands_t cmds_screen = { @@ -307,6 +309,7 @@ seac_screen_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t { dc_status_t status = DC_STATUS_SUCCESS; seac_screen_device_t *device = NULL; + dc_transport_t transport = dc_iostream_get_transport (iostream); if (out == NULL) return DC_STATUS_INVALIDARGS; @@ -319,24 +322,34 @@ seac_screen_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t } // Set the default values. - device->iostream = iostream; device->cmds = NULL; device->layout = NULL; device->fingerprint = 0; memset (device->info, 0, sizeof (device->info)); + // Create the packet stream. + if (transport == DC_TRANSPORT_BLE) { + status = dc_packet_open (&device->iostream, context, iostream, 244, 244); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to create the packet stream."); + goto error_free; + } + } else { + device->iostream = iostream; + } + // 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; + goto error_free_iostream; } // Set the timeout for receiving data (1000ms). status = dc_iostream_set_timeout (device->iostream, 1000); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to set the timeout."); - goto error_free; + goto error_free_iostream; } // Make sure everything is in a sane state. @@ -351,7 +364,7 @@ seac_screen_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t status = seac_screen_transfer (device, CMD_HWINFO, NULL, 0, device->info, SZ_HWINFO); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to read the hardware info."); - goto error_free; + goto error_free_iostream; } HEXDUMP (context, DC_LOGLEVEL_DEBUG, "Hardware", device->info, SZ_HWINFO); @@ -360,7 +373,7 @@ seac_screen_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t status = seac_screen_transfer (device, CMD_SWINFO, NULL, 0, device->info + SZ_HWINFO, SZ_SWINFO); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to read the software info."); - goto error_free; + goto error_free_iostream; } HEXDUMP (context, DC_LOGLEVEL_DEBUG, "Software", device->info + SZ_HWINFO, SZ_SWINFO); @@ -378,11 +391,29 @@ seac_screen_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t return DC_STATUS_SUCCESS; +error_free_iostream: + if (transport == DC_TRANSPORT_BLE) { + dc_iostream_close (device->iostream); + } error_free: dc_device_deallocate ((dc_device_t *) device); return status; } +static dc_status_t +seac_screen_device_close (dc_device_t *abstract) +{ + seac_screen_device_t *device = (seac_screen_device_t *) abstract; + dc_transport_t transport = dc_iostream_get_transport (device->iostream); + + // Close the packet stream. + if (transport == DC_TRANSPORT_BLE) { + return dc_iostream_close (device->iostream); + } + + return DC_STATUS_SUCCESS; +} + static dc_status_t seac_screen_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size) { From 15d6f6c975eadab25fe27810fd3e52a04c6a49e0 Mon Sep 17 00:00:00 2001 From: Simone Carletti Date: Mon, 23 Feb 2026 13:49:29 +0100 Subject: [PATCH 23/33] Increase the Seac BLE timeout After sending a command to the dive computer, the first BLE data packet always arrives after a delay of about 2.1 to 2.3 seconds. All following data packets arrive within a few tens of milliseconds: [40.438010] [0.017709] INFO: Write: size=15, data=.. [42.624359] [2.186349] INFO: Read: size=128, data=.. [42.640406] [0.016047] INFO: Read: size=128, data=.. [42.661683] [0.021277] INFO: Read: size=128, data=.. [42.675335] [0.013652] INFO: Read: size=128, data=.. [42.690446] [0.015111] INFO: Read: size=128, data=.. [42.705340] [0.014894] INFO: Read: size=128, data=.. [42.720271] [0.014931] INFO: Read: size=128, data=.. [42.735221] [0.014950] INFO: Read: size=128, data=.. [42.752563] [0.017342] INFO: Read: size=128, data=.. [42.774315] [0.021752] INFO: Read: size=128, data=.. [42.788240] [0.013925] INFO: Read: size=128, data=.. [42.803374] [0.015134] INFO: Read: size=128, data=.. [42.819374] [0.016000] INFO: Read: size=128, data=.. [42.834397] [0.015023] INFO: Read: size=128, data=.. [42.850315] [0.015918] INFO: Read: size=128, data=.. [42.870331] [0.020016] INFO: Read: size=128, data=.. [42.883544] [0.013213] INFO: Read: size=8, data=.. Occasionally, this first packet delay even increases to around 5.6 seconds! Therefore, the BLE timeout needs to be increased to a larger value. For the USB communication, the default timeout of 1 seconds is more than sufficient because the response is typically received within 200 milliseconds. Due to the lack of notification/indication support for the Seac characteristic, read timeouts are even more problematic. Whenever a read timeout happens: [0.000000] [0.000000] INFO: Write: size=7, data=55000618331923 [3.007000] [3.007000] INFO: Read: size=0, data= it also causes the next retry to fail: [3.014000] [0.003000] INFO: Sleep: value=100 [3.130000] [0.116000] INFO: Purge: direction=1 [4.266000] [1.136000] INFO: Write: size=7, data=55000618331923 [4.269000] [0.003000] INFO: Read: size=128, data=5501071833.. [6.492000] [2.223000] INFO: Read: size=128, data=5501071833.. [6.508000] [0.016000] INFO: Read: size=128, data=.. The first BLE packet arrives immediately without the usual 2 second delay and the payload of this packet is identical to the payload of the second packet, which happens to arrive after the usual 2 second delay. This indicates the first packet is actually the response to the first attempt. The purge operation doesn't help to discard this packet, because without notifications the packet remains queued on the device side until we retrieve it by reading the characteristic. Since the response is now malformed due to the presence of the extra packet, this condition is detected by means of a checksum error and another attempt is needed: [6.515000] [0.002000] INFO: Sleep: value=100 [6.618000] [0.103000] INFO: Purge: direction=1 [6.649000] [0.031000] INFO: Write: size=7, data=55000618331923 [8.842000] [2.193000] INFO: Read: size=128, data=5501071833.. [8.874000] [0.032000] INFO: Read: size=128, data=.. [8.905000] [0.031000] INFO: Read: size=8, data=.. Co-authored-by: Jef Driesen --- src/seac_screen.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/seac_screen.c b/src/seac_screen.c index fbad09cf..9632f097 100644 --- a/src/seac_screen.c +++ b/src/seac_screen.c @@ -345,8 +345,9 @@ seac_screen_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t goto error_free_iostream; } - // Set the timeout for receiving data (1000ms). - status = dc_iostream_set_timeout (device->iostream, 1000); + // Set the timeout for receiving data. + int timeout = transport == DC_TRANSPORT_BLE ? 6000 : 1000; + status = dc_iostream_set_timeout (device->iostream, timeout); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to set the timeout."); goto error_free_iostream; From 45d5faf8e4e135e3fa2a7e4fdacec33eb97c55cb Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Fri, 20 Feb 2026 12:20:33 -0400 Subject: [PATCH 24/33] Support time sync for Icon HD family. Source is a BT snoop log from the official Mares app against a Mares Sirius. --- src/mares_iconhd.c | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index c34859cc..159264c7 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -92,6 +92,7 @@ #define CMD_OBJ_INIT 0xBF #define CMD_OBJ_EVEN 0xAC #define CMD_OBJ_ODD 0xFE +#define CMD_SET_TIME 0xB0 #define OBJ_DEVICE 0x2000 #define OBJ_DEVICE_MODEL 0x02 @@ -134,6 +135,7 @@ static dc_status_t mares_iconhd_device_set_fingerprint (dc_device_t *abstract, c static dc_status_t mares_iconhd_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size); static dc_status_t mares_iconhd_device_dump (dc_device_t *abstract, dc_buffer_t *buffer); static dc_status_t mares_iconhd_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +static dc_status_t mares_iconhd_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime); static dc_status_t mares_iconhd_device_close (dc_device_t *abstract); static const dc_device_vtable_t mares_iconhd_device_vtable = { @@ -144,7 +146,7 @@ static const dc_device_vtable_t mares_iconhd_device_vtable = { NULL, /* write */ mares_iconhd_device_dump, /* dump */ mares_iconhd_device_foreach, /* foreach */ - NULL, /* timesync */ + mares_iconhd_device_timesync, /* timesync */ mares_iconhd_device_close /* close */ }; @@ -1163,3 +1165,36 @@ mares_iconhd_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, return mares_iconhd_device_foreach_raw (abstract, callback, userdata); } } + + +static dc_status_t +mares_iconhd_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) +{ + dc_status_t status = DC_STATUS_SUCCESS; + mares_iconhd_device_t *device = (mares_iconhd_device_t *) abstract; + + // Convert to local time. + dc_datetime_t local = *datetime; + local.timezone = DC_TIMEZONE_NONE; + + dc_ticks_t ticks = dc_datetime_mktime (&local); + if (ticks == -1) { + ERROR (abstract->context, "Invalid date/time value specified."); + return DC_STATUS_INVALIDARGS; + } + + const unsigned char timestamp[] = { + (ticks ) & 0xFF, + (ticks >> 8) & 0xFF, + (ticks >> 16) & 0xFF, + (ticks >> 24) & 0xFF, + }; + + status = mares_iconhd_transfer (device, CMD_SET_TIME, timestamp, sizeof (timestamp), NULL, 0, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to set the local time."); + return status; + } + + return status; +} From 710f9218fad13942a3d4a875bdf5579c37f20772 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 2 Mar 2026 18:01:33 +0100 Subject: [PATCH 25/33] Update the Github actions All Github actions using Node.js 20 are deprecated [1]. Update the following actions to the latest version using Node.js 24: - actions/checkout - actions/upload-artifact - microsoft/setup-msbuild [1] https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/ --- .github/workflows/build.yml | 22 +++++++++++----------- .github/workflows/release.yml | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 86552e10..bede04f2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: CC: ${{ matrix.compiler }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install dependencies run: | sudo apt-get update @@ -32,7 +32,7 @@ jobs: run: | make install DESTDIR=$PWD/artifacts tar -czf ${{ github.job }}-${{ matrix.compiler }}.tar.gz -C artifacts usr/local - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: ${{ github.job }}-${{ matrix.compiler }} path: ${{ github.job }}-${{ matrix.compiler }}.tar.gz @@ -52,7 +52,7 @@ jobs: CC: ${{ matrix.compiler }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install dependencies run: brew install autoconf automake libtool hidapi libusb - run: autoreconf --install --force @@ -63,7 +63,7 @@ jobs: run: | make install DESTDIR=$PWD/artifacts tar -czf ${{ github.job }}-${{ matrix.compiler }}.tar.gz -C artifacts usr/local - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: ${{ github.job }}-${{ matrix.compiler }} path: ${{ github.job }}-${{ matrix.compiler }}.tar.gz @@ -80,7 +80,7 @@ jobs: arch: [i686, x86_64] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install dependencies run: | sudo apt-get update @@ -122,7 +122,7 @@ jobs: run: | make install DESTDIR=$PWD/artifacts tar -czf ${{ github.job }}-${{ matrix.arch }}.tar.gz -C artifacts usr/local - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: ${{ github.job }}-${{ matrix.arch }} path: ${{ github.job }}-${{ matrix.arch }}.tar.gz @@ -142,7 +142,7 @@ jobs: CONFIGURATION: Release steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: msys2/setup-msys2@v2 with: install: autoconf automake libtool pkg-config make gcc @@ -151,9 +151,9 @@ jobs: ./configure make -C src revision.h shell: msys2 {0} - - uses: microsoft/setup-msbuild@v2 + - uses: microsoft/setup-msbuild@v3 - run: msbuild -m -p:Platform=${{ matrix.platform }} -p:Configuration=${{ env.CONFIGURATION }} contrib/msvc/libdivecomputer.vcxproj - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: ${{ github.job }}-${{ matrix.platform }} path: contrib/msvc/${{ matrix.platform }}/${{ env.CONFIGURATION }}/bin @@ -165,13 +165,13 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - run: | autoreconf --install --force ./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 + - uses: actions/upload-artifact@v7 with: name: ${{ github.job }} path: contrib/android/libs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f8f2b342..f9b2d789 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ jobs: name: Release runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Version number id: version From 8121b9cbed5327553a17f911cc912444a215ea95 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 2 Mar 2026 18:03:15 +0100 Subject: [PATCH 26/33] Upload non-zipped artifacts The latest upload-artifact@v7 action added support for uploading non-zipped artifacts [1]. This prevents the uploading of a compressed tar.gz file within another compressed zip file. To indicate the type of the uploaded file, the file extension is added to the artifact name. For the new non-zipped artifacts this is already the default. [1] https://github.blog/changelog/2026-02-26-github-actions-now-supports-uploading-and-downloading-non-zipped-artifacts/ --- .github/workflows/build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bede04f2..b8b29689 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,8 +34,8 @@ jobs: tar -czf ${{ github.job }}-${{ matrix.compiler }}.tar.gz -C artifacts usr/local - uses: actions/upload-artifact@v7 with: - name: ${{ github.job }}-${{ matrix.compiler }} path: ${{ github.job }}-${{ matrix.compiler }}.tar.gz + archive: false mac: @@ -65,8 +65,8 @@ jobs: tar -czf ${{ github.job }}-${{ matrix.compiler }}.tar.gz -C artifacts usr/local - uses: actions/upload-artifact@v7 with: - name: ${{ github.job }}-${{ matrix.compiler }} path: ${{ github.job }}-${{ matrix.compiler }}.tar.gz + archive: false windows: @@ -124,8 +124,8 @@ jobs: tar -czf ${{ github.job }}-${{ matrix.arch }}.tar.gz -C artifacts usr/local - uses: actions/upload-artifact@v7 with: - name: ${{ github.job }}-${{ matrix.arch }} path: ${{ github.job }}-${{ matrix.arch }}.tar.gz + archive: false msvc: @@ -155,7 +155,7 @@ jobs: - run: msbuild -m -p:Platform=${{ matrix.platform }} -p:Configuration=${{ env.CONFIGURATION }} contrib/msvc/libdivecomputer.vcxproj - uses: actions/upload-artifact@v7 with: - name: ${{ github.job }}-${{ matrix.platform }} + name: ${{ github.job }}-${{ matrix.platform }}.zip path: contrib/msvc/${{ matrix.platform }}/${{ env.CONFIGURATION }}/bin android: @@ -173,5 +173,5 @@ jobs: - run: $ANDROID_NDK/ndk-build -C contrib/android NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=Android.mk - uses: actions/upload-artifact@v7 with: - name: ${{ github.job }} + name: ${{ github.job }}.zip path: contrib/android/libs From b74547a6585c9dd374286ccca1da1b6cc8c96c58 Mon Sep 17 00:00:00 2001 From: Simone Carletti Date: Sun, 29 Mar 2026 17:43:16 +0200 Subject: [PATCH 27/33] Fix tank overflow guards in Symbios parser It is referencing the wrong variable, the gasmix counter instead of the tanks counter. --- src/halcyon_symbios_parser.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/halcyon_symbios_parser.c b/src/halcyon_symbios_parser.c index e871fec8..e1b01e0e 100644 --- a/src/halcyon_symbios_parser.c +++ b/src/halcyon_symbios_parser.c @@ -512,7 +512,7 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac // Add a new tank if necessary. if (idx >= ntanks) { - if (ngasmixes >= NTANKS) { + if (ntanks >= NTANKS) { ERROR (abstract->context, "Maximum number of tanks reached."); return DC_STATUS_NOMEMORY; } @@ -616,7 +616,7 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac // Add a new tank if necessary. if (idx >= ntanks) { - if (ngasmixes >= NTANKS) { + if (ntanks >= NTANKS) { ERROR (abstract->context, "Maximum number of tanks reached."); return DC_STATUS_NOMEMORY; } @@ -697,7 +697,7 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac // Add a new tank if necessary. if (idx >= ntanks) { - if (ngasmixes >= NTANKS) { + if (ntanks >= NTANKS) { ERROR (abstract->context, "Maximum number of tanks reached."); return DC_STATUS_NOMEMORY; } From 747ebd5a828496ab0da92480517619cfa701fa2c Mon Sep 17 00:00:00 2001 From: Simone Carletti Date: Tue, 31 Mar 2026 23:06:09 +0200 Subject: [PATCH 28/33] Fix wrong compass offset in Halcyon Symbios parser The ID_COMPASS (0x0E) record is 4 bytes: id, size, uint16_le heading. The heading payload starts at offset+2, but the code reads from offset+4 which is past the record boundary into the next record's id and size bytes, producing invalid bearings. --- src/halcyon_symbios_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/halcyon_symbios_parser.c b/src/halcyon_symbios_parser.c index e1b01e0e..86d7fa6a 100644 --- a/src/halcyon_symbios_parser.c +++ b/src/halcyon_symbios_parser.c @@ -638,7 +638,7 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac sample.pressure.value = pressure / 10.0; if (callback) callback(DC_SAMPLE_PRESSURE, &sample, userdata); } else if (type == ID_COMPASS) { - unsigned int heading = array_uint16_le (data + offset + 4); + unsigned int heading = array_uint16_le (data + offset + 2); sample.bearing = heading; if (callback) callback(DC_SAMPLE_BEARING, &sample, userdata); } else if (type == ID_TRIM) { From 446f66bc575792e0a0fd033aa5fae7bc8758f3ae Mon Sep 17 00:00:00 2001 From: Simone Carletti Date: Wed, 1 Apr 2026 00:18:05 +0200 Subject: [PATCH 29/33] Extract firmware version from Symbios device status The firmware version (major.minor.bugfix) is available at bytes 16-18 of the BT_device_status_t response, but was hardcoded to 0. --- src/halcyon_symbios.c | 8 +++++--- src/halcyon_symbios_parser.c | 8 ++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/halcyon_symbios.c b/src/halcyon_symbios.c index 3eabf806..07d6c45e 100644 --- a/src/halcyon_symbios.c +++ b/src/halcyon_symbios.c @@ -469,13 +469,15 @@ halcyon_symbios_device_foreach (dc_device_t *abstract, dc_dive_callback_t callba // Emit a device info event. dc_event_devinfo_t devinfo; devinfo.model = info[5]; - devinfo.firmware = 0; + devinfo.firmware = array_uint24_be (info + 16); devinfo.serial = array_uint32_le (info); device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); - DEBUG (abstract->context, "Device: serial=%u, hw=%u, model=%u, bt=%u.%u, battery=%u, pressure=%u, errorbits=%u", + DEBUG (abstract->context, "Device: model=%u, serial=%u, firmware=%u.%u.%u, hw=%u, bt=%u.%u, battery=%u, pressure=%u, errorbits=%u", + info[5], array_uint32_le (info), - info[4], info[5], info[6], info[7], + info[16], info[17], info[18], + info[4], info[6], info[7], array_uint16_le (info + 8), array_uint16_le (info + 10), array_uint32_le (info + 12)); diff --git a/src/halcyon_symbios_parser.c b/src/halcyon_symbios_parser.c index 86d7fa6a..5fc0122e 100644 --- a/src/halcyon_symbios_parser.c +++ b/src/halcyon_symbios_parser.c @@ -431,12 +431,12 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac unsigned int DC_ATTR_UNUSED battery = array_uint16_le(data + offset + 20); time_start = array_uint32_le(data + offset + 24); unsigned int serial = array_uint32_le(data + offset + 28); - DEBUG (abstract->context, "Device: model=%u, hw=%u.%u, fw=%u.%u.%u, deco=%u.%u, serial=%u", + DEBUG (abstract->context, "Device: model=%u, serial=%u, firmware=%u.%u.%u, hw=%u.%u, deco=%u.%u", model, - hw_major, hw_minor, + serial, fw_major, fw_minor, fw_bugfix, - deco_major, deco_minor, - serial); + hw_major, hw_minor, + deco_major, deco_minor); } else if (type == ID_GAS_SWITCH) { unsigned int id = UNDEFINED; unsigned int o2 = data[offset + 2]; From 9f114e9ac1afa5dc45b0b687ffcbe00bddf2ca55 Mon Sep 17 00:00:00 2001 From: Joao Ventura Date: Sat, 18 Apr 2026 23:29:59 +0200 Subject: [PATCH 30/33] Enable bluetooth for the OSTC 3 and cR Later models of the OSTC 3 and cR use Bluetooth instead of USB communication. This change allows owners of these computers to select the correct model to connect via Bluetooth, instead of having to choose one of the other computers from the same family. --- src/descriptor.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index 1a75aed4..fe933ef5 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -331,8 +331,8 @@ static const dc_descriptor_t g_descriptors[] = { {"Heinrichs Weikamp", "Frog", DC_FAMILY_HW_FROG, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_hw}, {"Heinrichs Weikamp", "OSTC 2", DC_FAMILY_HW_OSTC3, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, {"Heinrichs Weikamp", "OSTC 2 TR", DC_FAMILY_HW_OSTC3, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, - {"Heinrichs Weikamp", "OSTC 3", DC_FAMILY_HW_OSTC3, 0, DC_TRANSPORT_SERIAL, NULL}, - {"Heinrichs Weikamp", "OSTC cR", DC_FAMILY_HW_OSTC3, 0, DC_TRANSPORT_SERIAL, NULL}, + {"Heinrichs Weikamp", "OSTC 3", DC_FAMILY_HW_OSTC3, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, + {"Heinrichs Weikamp", "OSTC cR", DC_FAMILY_HW_OSTC3, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, {"Heinrichs Weikamp", "OSTC Plus", DC_FAMILY_HW_OSTC3, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, {"Heinrichs Weikamp", "OSTC Sport", DC_FAMILY_HW_OSTC3, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_hw}, {"Heinrichs Weikamp", "OSTC Nano", DC_FAMILY_HW_OSTC3, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_hw}, From dcd15412f34af4b5fc39aa0b5e783513646c63ff Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Fri, 15 May 2026 11:26:39 +1200 Subject: [PATCH 31/33] Restore Compass Direction Reporting on Suunto. Report user initiated compass changes as HEADING events. Signed-off-by: Michael Keller --- .github/FUNDING.yml | 2 -- src/suunto_d9_parser.c | 8 ++++---- src/suunto_eonsteel_parser.c | 5 +++-- 3 files changed, 7 insertions(+), 8 deletions(-) delete mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index cdc141be..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,2 +0,0 @@ -github: jefdriesen -custom: 'https://paypal.me/libdivecomputer' diff --git a/src/suunto_d9_parser.c b/src/suunto_d9_parser.c index 6525666a..0bbf3b6b 100644 --- a/src/suunto_d9_parser.c +++ b/src/suunto_d9_parser.c @@ -724,12 +724,12 @@ suunto_d9_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t ca if (heading == 0xFFFF) { sample.event.type = SAMPLE_EVENT_BOOKMARK; sample.event.value = 0; - sample.event.time = seconds; - if (callback) callback (DC_SAMPLE_EVENT, &sample, userdata); } else { - sample.bearing = heading / 2; - if (callback) callback (DC_SAMPLE_BEARING, &sample, userdata); + sample.event.type = SAMPLE_EVENT_HEADING; + sample.event.value = heading / 2; } + sample.event.time = seconds; + if (callback) callback (DC_SAMPLE_EVENT, &sample, userdata); offset += 4; break; case 0x05: // Gas Change diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index 6904064d..f6470787 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -517,8 +517,9 @@ static void sample_heading(struct sample_data *info, unsigned short heading) if (heading == 0xffff) return; - sample.bearing = heading; - if (info->callback) info->callback(DC_SAMPLE_BEARING, &sample, info->userdata); + sample.event.type = SAMPLE_EVENT_HEADING; + sample.event.value = heading; + if (info->callback) info->callback(DC_SAMPLE_EVENT, &sample, info->userdata); } static void sample_abspressure(struct sample_data *info, unsigned short pressure) From b980e0a03e853816fb596d263e38452dbb56dbb4 Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Fri, 15 May 2026 11:39:57 +1200 Subject: [PATCH 32/33] Fix the Heinrichs Weikamp Model Detection. Correctly detect the full model type. Signed-off-by: Michael Keller --- src/hw_ostc3.c | 23 +++++++++++++++++++---- src/hw_ostc3.h | 2 +- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 00a65b52..cc77b098 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -763,8 +763,7 @@ hw_ostc3_device_hardware (dc_device_t *abstract, unsigned char data[], unsigned return rc; // Send the command. - const unsigned char cmd = size == SZ_HARDWARE2 ? HARDWARE2 : HARDWARE; - rc = hw_ostc3_transfer (device, NULL, cmd, NULL, 0, data, size, NULL, NODELAY); + rc = hw_ostc3_device_id (device, data, size); if (rc != DC_STATUS_SUCCESS) return rc; @@ -790,7 +789,15 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi dc_event_devinfo_t devinfo; devinfo.firmware = device->firmware; devinfo.serial = device->serial; - devinfo.model = device->model; + if (device->hardware != UNKNOWN) { + devinfo.model = device->hardware; + } else { + // Fallback to the serial number. + if (devinfo.serial > 10000) + devinfo.model = SPORT; + else + devinfo.model = OSTC3; + } device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); // Allocate memory. @@ -1769,7 +1776,15 @@ hw_ostc3_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) dc_event_devinfo_t devinfo; devinfo.firmware = device->firmware; devinfo.serial = device->serial; - devinfo.model = device->model; + if (device->hardware != UNKNOWN) { + devinfo.model = device->hardware; + } else { + // Fallback to the serial number. + if (devinfo.serial > 10000) + devinfo.model = SPORT; + else + devinfo.model = OSTC3; + } device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); // Allocate the required amount of memory. diff --git a/src/hw_ostc3.h b/src/hw_ostc3.h index 6030cff9..7de52715 100644 --- a/src/hw_ostc3.h +++ b/src/hw_ostc3.h @@ -33,7 +33,7 @@ extern "C" { #endif /* __cplusplus */ #define OSTC4_5_PREFIX 0x3B -#define ISHWOS4(hardware) ((hardware >> 8) == OSTC4_5_PREFIX) +#define ISHWOS4(hardware) (((hardware) >> 8) == OSTC4_5_PREFIX) dc_status_t hw_ostc3_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); From f7d97d58d2f34ed8e5a3483ae2850b3f7b8d2d3d Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Fri, 15 May 2026 12:03:17 +1200 Subject: [PATCH 33/33] Fix TTS for DeepSix. Fix setting of the TTS value. Signed-off-by: Michael Keller --- src/deepsix_excursion_parser.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/deepsix_excursion_parser.c b/src/deepsix_excursion_parser.c index 5b9ad42b..cd6f7943 100644 --- a/src/deepsix_excursion_parser.c +++ b/src/deepsix_excursion_parser.c @@ -755,7 +755,6 @@ deepsix_excursion_parser_samples_foreach_v1 (dc_parser_t *abstract, dc_sample_ca sample.deco.time = deco_ndl_tts; sample.deco.tts = 0; } - sample.deco.tts = 0; if (callback) callback (DC_SAMPLE_DECO, &sample, userdata); break; default: