From d5fc1246844afa5f776bfbd1b383d1cfa35f3a12 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 14 Mar 2024 18:10:35 +0100 Subject: [PATCH 01/52] Wait longer before retrying and ignore invalid bytes When an error occurs, simply re-sending the failed command doesn't always work. Sometimes we appear to receive some stale data from the previous attempt, despite the fact that the input queue is purged before retrying. To improve the chances of re-synchronizing on the start of the next packet, wait a bit longer before re-trying and also ignore all received bytes until an ACK byte is encountered. This isn't entirely reliable, because the payload data isn't escaped and can also contain ACK bytes, but it's better than nothing. Below is a real-life example of such a case. First, we hit a timeout: [8.122494] INFO: Write: size=2, data=E742 [8.122663] INFO: Write: size=8, data=708F020080000000 [11.130287] INFO: Read: size=0, data= [11.130308] ERROR: Failed to receive the answer. [in src/mares_iconhd.c:213 (mares_iconhd_packet)] Next, we retry a few times: [11.130321] INFO: Sleep: value=100 [11.230536] INFO: Purge: direction=1 [11.231117] INFO: Write: size=2, data=E742 [11.240494] INFO: Write: size=8, data=708F020080000000 [11.415317] INFO: Read: size=20, data=0000000035004F000C00000037004F0000000000 [11.415331] ERROR: Unexpected answer byte. [in src/mares_iconhd.c:219 (mares_iconhd_packet)] [11.415339] INFO: Sleep: value=100 [11.515889] INFO: Purge: direction=1 [11.517474] INFO: Write: size=2, data=E742 [11.517628] INFO: Write: size=8, data=708F020080000000 [11.517798] INFO: Read: size=13, data=1300000036004F000A00000037 [11.517803] ERROR: Unexpected answer byte. [in src/mares_iconhd.c:219 (mares_iconhd_packet)] That didn't really work, but the interesting part is that we did receive some data now. We also see the same data appear in the next retry: [11.517811] INFO: Sleep: value=100 [11.618398] INFO: Purge: direction=1 [11.618760] INFO: Write: size=2, data=E742 [11.619100] INFO: Write: size=8, data=708F020080000000 [11.619341] INFO: Read: size=1, data=AA [11.619349] INFO: Read: size=20, data=0000000035004F000C00000037004F0000000000 [11.619355] INFO: Read: size=20, data=37004F001E00000036004F001000000037004F00 [11.619360] INFO: Read: size=20, data=2C00000037004F001200000034004F0033000000 [11.619366] INFO: Read: size=20, data=37004F000000000037004F001E00000036004F00 [11.630104] INFO: Read: size=12, data=1300000036004F000A000000 [11.630122] INFO: Read: size=20, data=37004F002000000038004F001D00000038004F00 [11.630131] INFO: Read: size=17, data=2300000039004F000600000037004F00EA This time, the retry seems to have worked and we move on to the next request: [11.635160] INFO: Write: size=2, data=E742 [11.635359] INFO: Write: size=8, data=F08E020080000000 [11.687620] INFO: Read: size=1, data=AA [11.708476] INFO: Read: size=20, data=0000000035004F000C00000037004F0000000000 [11.719214] INFO: Read: size=20, data=37004F001E00000036004F001000000037004F00 [11.719231] INFO: Read: size=20, data=2C00000037004F001200000034004F0033000000 [11.719240] INFO: Read: size=20, data=37004F000000000037004F001E00000036004F00 [11.740569] INFO: Read: size=13, data=1300000036004F000A00000037 [11.740585] INFO: Read: size=20, data=004F002000000038004F001D00000038004F0023 [11.740592] INFO: Read: size=20, data=00000039004F000600000037004F00EAAA090010 This appears to have worked, but upon closer inspection you can see that the very last packet already contains the start of the response to the next request (AA090010). But we haven't send the next request yet! That means the response we received here is actually the response to one of the earlier (failed) request and this start of the next packet is our real response. So we're getting completely out of sync! Unfortunately the Mares protocol doesn't use checksums or sequence numbers, so we can't really detect this problem. --- src/mares_iconhd.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index c968685e..6cf1638a 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -244,17 +244,18 @@ mares_iconhd_packet_fixed (mares_iconhd_device_t *device, } // Receive the header byte. - unsigned char header[1] = {0}; - status = dc_iostream_read (device->iostream, header, sizeof (header), NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to receive the packet header."); - return status; - } + while (1) { + unsigned char header[1] = {0}; + status = dc_iostream_read (device->iostream, header, sizeof (header), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the packet header."); + return status; + } - // Verify the header byte. - if (header[0] != ACK) { - ERROR (abstract->context, "Unexpected packet header byte (%02x).", header[0]); - return DC_STATUS_PROTOCOL; + if (header[0] == ACK) + break; + + WARNING (abstract->context, "Unexpected packet header byte (%02x).", header[0]); } // Send the command payload to the dive computer. @@ -411,7 +412,7 @@ mares_iconhd_transfer (mares_iconhd_device_t *device, unsigned char cmd, const u return rc; // Discard any garbage bytes. - dc_iostream_sleep (device->iostream, 100); + dc_iostream_sleep (device->iostream, 1000); dc_iostream_purge (device->iostream, DC_DIRECTION_INPUT); } From de76a44ae6293faa4ad3ce3e4f7013d534cd5af7 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 20 Apr 2023 14:55:18 +0200 Subject: [PATCH 02/52] Add support for the Halcyon Symbios The Halcyon Symbios HUD and Handset support both BLE communication and USB mass storage mode. In both cases the dives have the same data format. Tested-by: Benjamin Kuch --- contrib/android/Android.mk | 2 + contrib/msvc/libdivecomputer.vcxproj | 3 + examples/common.c | 1 + include/libdivecomputer/common.h | 2 + src/Makefile.am | 1 + src/checksum.c | 49 ++ src/checksum.h | 3 + src/descriptor.c | 19 + src/device.c | 4 + src/halcyon_symbios.c | 575 +++++++++++++++++++++++ src/halcyon_symbios.h | 43 ++ src/halcyon_symbios_parser.c | 660 +++++++++++++++++++++++++++ src/parser.c | 4 + 13 files changed, 1366 insertions(+) create mode 100644 src/halcyon_symbios.c create mode 100644 src/halcyon_symbios.h create mode 100644 src/halcyon_symbios_parser.c diff --git a/contrib/android/Android.mk b/contrib/android/Android.mk index fcefbb98..4a497fd2 100644 --- a/contrib/android/Android.mk +++ b/contrib/android/Android.mk @@ -39,6 +39,8 @@ LOCAL_SRC_FILES := \ src/divesoft_freedom_parser.c \ src/divesystem_idive.c \ src/divesystem_idive_parser.c \ + src/halcyon_symbios.c \ + src/halcyon_symbios_parser.c \ src/hdlc.c \ src/hw_frog.c \ src/hw_ostc3.c \ diff --git a/contrib/msvc/libdivecomputer.vcxproj b/contrib/msvc/libdivecomputer.vcxproj index d9176e7a..81d6df3a 100644 --- a/contrib/msvc/libdivecomputer.vcxproj +++ b/contrib/msvc/libdivecomputer.vcxproj @@ -207,6 +207,8 @@ + + @@ -337,6 +339,7 @@ + diff --git a/examples/common.c b/examples/common.c index 767acb21..71b17875 100644 --- a/examples/common.c +++ b/examples/common.c @@ -100,6 +100,7 @@ static const backend_table_t g_backends[] = { {"cosmiq", DC_FAMILY_DEEPBLU_COSMIQ, 0}, {"s1", DC_FAMILY_OCEANS_S1, 0}, {"freedom", DC_FAMILY_DIVESOFT_FREEDOM, 19}, + {"symbios", DC_FAMILY_HALCYON_SYMBIOS, 1}, }; static const transport_table_t g_transports[] = { diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h index dbb463b9..0fcb11cc 100644 --- a/include/libdivecomputer/common.h +++ b/include/libdivecomputer/common.h @@ -121,6 +121,8 @@ typedef enum dc_family_t { DC_FAMILY_OCEANS_S1 = (22 << 16), /* Divesoft Freedom */ DC_FAMILY_DIVESOFT_FREEDOM = (23 << 16), + /* Halcyon Symbios */ + DC_FAMILY_HALCYON_SYMBIOS = (24 << 16), } dc_family_t; #ifdef __cplusplus diff --git a/src/Makefile.am b/src/Makefile.am index 817da96d..acd6a511 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -82,6 +82,7 @@ libdivecomputer_la_SOURCES = \ oceans_s1_common.h oceans_s1_common.c \ oceans_s1.h oceans_s1.c oceans_s1_parser.c \ divesoft_freedom.h divesoft_freedom.c divesoft_freedom_parser.c \ + halcyon_symbios.h halcyon_symbios.c halcyon_symbios_parser.c \ hdlc.h hdlc.c \ packet.h packet.c \ socket.h socket.c \ diff --git a/src/checksum.c b/src/checksum.c index eeda9129..1a73a0c5 100644 --- a/src/checksum.c +++ b/src/checksum.c @@ -67,6 +67,55 @@ checksum_xor_uint8 (const unsigned char data[], unsigned int size, unsigned char return crc; } +/* + * Polynomial: 0x07 + * RefIn: False + * RefOut: False + */ +unsigned char +checksum_crc8 (const unsigned char data[], unsigned int size, unsigned char init, unsigned char xorout) +{ + static const unsigned char crc_table[] = { + 0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, + 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d, + 0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65, + 0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d, + 0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, 0xf2, 0xf5, + 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd, + 0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, + 0xa8, 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd, + 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2, + 0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea, + 0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2, + 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a, + 0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32, + 0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a, + 0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42, + 0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, + 0x89, 0x8e, 0x87, 0x80, 0x95, 0x92, 0x9b, 0x9c, + 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4, + 0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec, + 0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4, + 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c, + 0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44, + 0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c, + 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34, + 0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, + 0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d, 0x64, 0x63, + 0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b, + 0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, + 0xae, 0xa9, 0xa0, 0xa7, 0xb2, 0xb5, 0xbc, 0xbb, + 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83, + 0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb, + 0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3, + }; + + unsigned char crc = init; + for (unsigned int i = 0; i < size; ++i) + crc = crc_table[data[i] ^ crc]; + + return crc ^ xorout; +} /* * Polynomial: 0x1021 diff --git a/src/checksum.h b/src/checksum.h index f9e54bb5..c75385db 100644 --- a/src/checksum.h +++ b/src/checksum.h @@ -38,6 +38,9 @@ checksum_add_uint16 (const unsigned char data[], unsigned int size, unsigned sho unsigned char checksum_xor_uint8 (const unsigned char data[], unsigned int size, unsigned char init); +unsigned char +checksum_crc8 (const unsigned char data[], unsigned int size, unsigned char init, unsigned char xorout); + unsigned short checksum_crc16_ccitt (const unsigned char data[], unsigned int size, unsigned short init, unsigned short xorout); diff --git a/src/descriptor.c b/src/descriptor.c index 92d28278..74717c41 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -60,6 +60,7 @@ static int dc_filter_deepblu (dc_descriptor_t *descriptor, dc_transport_t transp static int dc_filter_oceans (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); static int dc_filter_divesoft (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); static int dc_filter_cressi (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); +static int dc_filter_halcyon (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); @@ -480,6 +481,9 @@ static const dc_descriptor_t g_descriptors[] = { /* Divesoft Freedom */ {"Divesoft", "Freedom", DC_FAMILY_DIVESOFT_FREEDOM, 19, DC_TRANSPORT_BLE, dc_filter_divesoft}, {"Divesoft", "Liberty", DC_FAMILY_DIVESOFT_FREEDOM, 10, DC_TRANSPORT_BLE, dc_filter_divesoft}, + /* Halcyon Symbios */ + {"Halcyon", "Symbios HUD", DC_FAMILY_HALCYON_SYMBIOS, 1, DC_TRANSPORT_BLE, dc_filter_halcyon}, + {"Halcyon", "Symbios Handset", DC_FAMILY_HALCYON_SYMBIOS, 7, DC_TRANSPORT_BLE, dc_filter_halcyon}, }; static int @@ -927,6 +931,21 @@ dc_filter_cressi (dc_descriptor_t *descriptor, dc_transport_t transport, const v return 1; } +static int +dc_filter_halcyon (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +{ + static const char * const bluetooth[] = { + "H01", // Symbios HUD + "H07", // Symbios Handset + }; + + if (transport == DC_TRANSPORT_BLE) { + return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_prefix); + } + + return 1; +} + dc_status_t dc_descriptor_iterator (dc_iterator_t **out) { diff --git a/src/device.c b/src/device.c index a7e60b74..92c36e09 100644 --- a/src/device.c +++ b/src/device.c @@ -66,6 +66,7 @@ #include "deepblu_cosmiq.h" #include "oceans_s1.h" #include "divesoft_freedom.h" +#include "halcyon_symbios.h" #include "device-private.h" #include "context-private.h" @@ -247,6 +248,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr case DC_FAMILY_DIVESOFT_FREEDOM: rc = divesoft_freedom_device_open (&device, context, iostream); break; + case DC_FAMILY_HALCYON_SYMBIOS: + rc = halcyon_symbios_device_open (&device, context, iostream); + break; default: return DC_STATUS_INVALIDARGS; } diff --git a/src/halcyon_symbios.c b/src/halcyon_symbios.c new file mode 100644 index 00000000..a3ed05cd --- /dev/null +++ b/src/halcyon_symbios.c @@ -0,0 +1,575 @@ +/* + * libdivecomputer + * + * Copyright (C) 2023 Jef Driesen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include +#include +#include + +#include "halcyon_symbios.h" +#include "context-private.h" +#include "device-private.h" +#include "platform.h" +#include "checksum.h" +#include "array.h" + +#define CMD_GET_STATUS 0x01 +#define CMD_GET_SETTINGS 0x02 +#define CMD_SET_SETTINGS 0x03 +#define CMD_LOGBOOK_REQUEST 0x04 +#define CMD_DIVELOG_REQUEST 0x05 +#define CMD_SET_TIME 0x07 +#define CMD_LOGBOOK_BLOCK 0x08 +#define CMD_DIVELOG_BLOCK 0x09 + +#define CMD_RESPONSE 0x80 + +#define ERR_BASE 0x80000000 +#define ERR_CRC 0 +#define ERR_BOUNDARY 1 +#define ERR_CMD_LENGTH 2 +#define ERR_CMD_UNKNOWN 3 +#define ERR_TIMEOUT 4 +#define ERR_FILE 5 +#define ERR_UNKNOWN 6 + +#define ACK 0x06 +#define NAK 0x15 + +#define MAXRETRIES 3 + +#define MAXPACKET 256 + +#define SZ_BLOCK 200 +#define SZ_LOGBOOK 32 + +#define FP_OFFSET 20 +#define FP_SIZE 4 + +#define NSTEPS 1000 +#define STEP(i,n) (NSTEPS * (i) / (n)) + +typedef struct halcyon_symbios_device_t { + dc_device_t base; + dc_iostream_t *iostream; + unsigned char fingerprint[FP_SIZE]; +} halcyon_symbios_device_t; + +static dc_status_t halcyon_symbios_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); +static dc_status_t halcyon_symbios_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +static dc_status_t halcyon_symbios_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime); + +static const dc_device_vtable_t halcyon_symbios_device_vtable = { + sizeof(halcyon_symbios_device_t), + DC_FAMILY_HALCYON_SYMBIOS, + halcyon_symbios_device_set_fingerprint, /* set_fingerprint */ + NULL, /* read */ + NULL, /* write */ + NULL, /* dump */ + halcyon_symbios_device_foreach, /* foreach */ + halcyon_symbios_device_timesync, /* timesync */ + NULL, /* close */ +}; + +static dc_status_t +halcyon_symbios_send (halcyon_symbios_device_t *device, unsigned char cmd, const unsigned char data[], unsigned int size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + unsigned char packet[1 + MAXPACKET + 1] = {0}; + unsigned int length = 1; + + if (device_is_cancelled (abstract)) + return DC_STATUS_CANCELLED; + + if (size > MAXPACKET) + return DC_STATUS_INVALIDARGS; + + // Setup the data packet + packet[0] = cmd; + if (size) { + memcpy (packet + 1, data, size); + packet[1 + size] = checksum_crc8 (data, size, 0x00, 0x00); + length += size + 1; + } + + // Send the data packet. + status = dc_iostream_write (device->iostream, packet, length, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the command."); + return status; + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +halcyon_symbios_recv (halcyon_symbios_device_t *device, unsigned char cmd, unsigned char data[], unsigned int size, unsigned int *actual, unsigned int *errorcode) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + unsigned char packet[2 + MAXPACKET + 1] = {0}; + unsigned int length = 0; + unsigned int errcode = 0; + + // Receive the answer. + size_t len = 0; + status = dc_iostream_read (device->iostream, packet, sizeof(packet), &len); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the packet."); + goto error_exit; + } + + // Verify the minimum length of the packet. + if (len < 3) { + ERROR (abstract->context, "Unexpected packet length (" DC_PRINTF_SIZE ").", len); + status = DC_STATUS_PROTOCOL; + goto error_exit; + } + + // Verify the checksum. + unsigned char crc = packet[len - 1]; + unsigned char ccrc = checksum_crc8 (packet + 1, len - 2, 0x00, 0x00); + if (crc != ccrc) { + ERROR (abstract->context, "Unexpected packet checksum (%02x %02x).", crc, ccrc); + status = DC_STATUS_PROTOCOL; + goto error_exit; + } + + // Verify the command byte. + unsigned int rsp = packet[0]; + unsigned int expected = cmd | CMD_RESPONSE; + if (rsp != expected) { + ERROR (abstract->context, "Unexpected command byte (%02x).", rsp); + status = DC_STATUS_PROTOCOL; + goto error_exit; + } + + // Verify the ACK/NAK byte. + unsigned int type = packet[1]; + if (type != ACK && type != NAK) { + ERROR (abstract->context, "Unexpected ACK/NAK byte (%02x).", type); + status = DC_STATUS_PROTOCOL; + goto error_exit; + } + + // Get the error code from a NAK packet. + if (type == NAK) { + // Verify the length of the NAK packet. + if (len != 4) { + ERROR (abstract->context, "Unexpected NAK packet length (" DC_PRINTF_SIZE ").", len); + status = DC_STATUS_PROTOCOL; + goto error_exit; + } + + // Set the ERR_BASE bit to indicate an error code is available. + errcode = packet[2] | ERR_BASE; + + ERROR (abstract->context, "Received NAK packet with error code %u.", errcode & ~ERR_BASE); + status = DC_STATUS_PROTOCOL; + goto error_exit; + } + + // Verify the maximum length of the packet. + if (len - 3 > size) { + ERROR (abstract->context, "Unexpected packet length (" DC_PRINTF_SIZE ").", len); + status = DC_STATUS_PROTOCOL; + goto error_exit; + } + + if (len - 3) { + memcpy (data, packet + 2, len - 3); + } + length = len - 3; + +error_exit: + if (actual) { + *actual = length; + } + if (errorcode) { + *errorcode = errcode; + } + return status; +} + +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) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + unsigned int errcode = 0; + + // Send the command. + status = halcyon_symbios_send (device, cmd, data, size); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the command."); + goto error_exit; + } + + // Receive the answer. + unsigned int length = 0; + status = halcyon_symbios_recv (device, cmd, answer, asize, &length, &errcode); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the answer."); + 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; + } + +error_exit: + if (errorcode) { + *errorcode = errcode; + } + return status; +} + +static dc_status_t +halcyon_symbios_download (halcyon_symbios_device_t *device, dc_event_progress_t *progress, unsigned char request, const unsigned char data[], unsigned int size, unsigned char block, dc_buffer_t *buffer, unsigned int *errorcode) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + unsigned int errcode = 0; + + // Clear the buffer. + dc_buffer_clear (buffer); + + // Request the data. + unsigned char response[4] = {0}; + status = halcyon_symbios_transfer (device, request, data, size, response, sizeof(response), &errcode); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to request the data."); + goto error_exit; + } + + // Get the total length. + unsigned int length = array_uint32_le (response); + + // Resize the buffer. + if (!dc_buffer_reserve (buffer, length)) { + ERROR (abstract->context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_exit; + } + + // Send the request for the first data block. + status = halcyon_symbios_send (device, block, NULL, 0); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the command."); + goto error_exit; + } + + const unsigned int initial = progress ? progress->current : 0; + + unsigned int counter = 1; + unsigned int nbytes = 0; + while (1) { + // Receive the data block. + unsigned int len = 0; + unsigned char payload[2 + SZ_BLOCK] = {0}; + unsigned int nretries = 0; + while ((status = halcyon_symbios_recv (device, block, payload, sizeof(payload), &len, NULL)) != DC_STATUS_SUCCESS) { + // Abort if the error is fatal. + if (status != DC_STATUS_PROTOCOL) { + ERROR (abstract->context, "Failed to receive the answer."); + goto error_exit; + } + + // Abort if the maximum number of retries is reached. + if (nretries++ >= MAXRETRIES) { + ERROR (abstract->context, "Reached the maximum number of retries."); + goto error_exit; + } + + // Send a NAK to request a re-transmission. + status = halcyon_symbios_send (device, NAK, NULL, 0); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the NAK."); + goto error_exit; + } + } + + // Verify the minimum block length. + if (len < 2) { + ERROR (abstract->context, "Unexpected block length (%u).", len); + status = DC_STATUS_PROTOCOL; + goto error_exit; + } + + // Verify the sequence number. + unsigned int id = array_uint16_le (payload); + unsigned int seqnum = id & 0x7FFF; + if (seqnum != counter) { + ERROR (abstract->context, "Unexpected block sequence number (%04x %04x).", seqnum, counter); + status = DC_STATUS_PROTOCOL; + goto error_exit; + } + + // Append the payload data to the output buffer. + if (!dc_buffer_append (buffer, payload + 2, len - 2)) { + ERROR (abstract->context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_exit; + } + + nbytes += len - 2; + counter += 1; + counter &= 0x7FFF; + + // Update and emit a progress event. + if (progress) { + // Limit the progress events to the announced length. + unsigned int n = nbytes > length ? length : nbytes; + progress->current = initial + STEP(n, length); + device_event_emit (abstract, DC_EVENT_PROGRESS, progress); + } + + // Send an ACK to request the next block or finalize the download. + status = halcyon_symbios_send (device, ACK, NULL, 0); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the ACK."); + goto error_exit; + } + + // Check for the last packet. + if (id & 0x8000) + break; + } + + // Verify the length of the data. + if (nbytes != length) { + ERROR (abstract->context, "Unexpected data length (%u %u).", nbytes, length); + status = DC_STATUS_PROTOCOL; + goto error_exit; + } + +error_exit: + if (errorcode) { + *errorcode = errcode; + } + return status; +} + +dc_status_t +halcyon_symbios_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) +{ + dc_status_t status = DC_STATUS_SUCCESS; + halcyon_symbios_device_t *device = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + device = (halcyon_symbios_device_t *) dc_device_allocate (context, &halcyon_symbios_device_vtable); + if (device == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Set the default values. + device->iostream = iostream; + memset(device->fingerprint, 0, sizeof(device->fingerprint)); + + // Set the timeout for receiving data (3000ms). + status = dc_iostream_set_timeout (device->iostream, 3000); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the timeout."); + goto error_free; + } + + // Make sure everything is in a sane state. + dc_iostream_purge (device->iostream, DC_DIRECTION_ALL); + + *out = (dc_device_t *) device; + + return DC_STATUS_SUCCESS; + +error_free: + dc_device_deallocate ((dc_device_t *) device); + return status; +} + +static dc_status_t +halcyon_symbios_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size) +{ + halcyon_symbios_device_t *device = (halcyon_symbios_device_t *) abstract; + + if (size && size != sizeof (device->fingerprint)) + return DC_STATUS_INVALIDARGS; + + if (size) + memcpy (device->fingerprint, data, sizeof (device->fingerprint)); + else + memset (device->fingerprint, 0, sizeof (device->fingerprint)); + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +halcyon_symbios_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) +{ + dc_status_t status = DC_STATUS_SUCCESS; + halcyon_symbios_device_t *device = (halcyon_symbios_device_t *) abstract; + unsigned int errcode = 0; + + // Enable progress notifications. + dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; + 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); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the device status."); + goto error_exit; + } + + // Emit a vendor event. + dc_event_vendor_t vendor; + vendor.data = info; + vendor.size = sizeof(info); + device_event_emit (abstract, DC_EVENT_VENDOR, &vendor); + + // Emit a device info event. + dc_event_devinfo_t devinfo; + devinfo.model = info[5]; + devinfo.firmware = 0; + 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", + array_uint32_le (info), + info[4], info[5], info[6], info[7], + array_uint16_le (info + 8), + array_uint16_le (info + 10), + array_uint32_le (info + 12)); + + dc_buffer_t *logbook = dc_buffer_new (0); + dc_buffer_t *dive = dc_buffer_new (0); + if (logbook == NULL || dive == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_free; + } + + // Download the logbook. + status = halcyon_symbios_download (device, &progress, CMD_LOGBOOK_REQUEST, NULL, 0, CMD_LOGBOOK_BLOCK, logbook, &errcode); + if (status != DC_STATUS_SUCCESS) { + if (errcode == (ERR_FILE | ERR_BASE)) { + WARNING (abstract->context, "Logbook not available!"); + + // Update and emit a progress event. + progress.current = NSTEPS; + progress.maximum = NSTEPS; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + status = DC_STATUS_SUCCESS; + goto error_free; + } + ERROR (abstract->context, "Failed to download the logbook."); + goto error_free; + } + + const unsigned char *data = dc_buffer_get_data (logbook); + size_t size = dc_buffer_get_size (logbook); + + // Get the number of dives. + unsigned int ndives = 0; + unsigned int offset = size; + while (offset >= SZ_LOGBOOK) { + offset -= SZ_LOGBOOK; + + // Compare the fingerprint to identify previously downloaded entries. + if (memcmp (data + offset + FP_OFFSET, device->fingerprint, sizeof(device->fingerprint)) == 0) { + break; + } + + ndives++; + } + + // Update and emit a progress event. + progress.current = 1 * NSTEPS; + progress.maximum = (ndives + 1) * NSTEPS; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + offset = size; + for (unsigned int i = 0; i < ndives; ++i) { + offset -= SZ_LOGBOOK; + + // Clear the buffer. + dc_buffer_clear (dive); + + // Download the dive. + status = halcyon_symbios_download (device, &progress, CMD_DIVELOG_REQUEST, data + offset + 16, 2, CMD_DIVELOG_BLOCK, dive, &errcode); + if (status != DC_STATUS_SUCCESS) { + if (errcode == (ERR_FILE | ERR_BASE)) { + WARNING (abstract->context, "Dive #%u not available!", + array_uint16_le (data + offset + 16)); + + // Update and emit a progress event. + progress.current = (i + 2) * NSTEPS; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + status = DC_STATUS_SUCCESS; + continue; + } + ERROR (abstract->context, "Failed to download the dive."); + goto error_free; + } + + if (callback && !callback (dc_buffer_get_data (dive), dc_buffer_get_size (dive), data + offset + FP_OFFSET, FP_SIZE, userdata)) { + break; + } + } + +error_free: + dc_buffer_free (dive); + dc_buffer_free (logbook); +error_exit: + return status; +} + +static dc_status_t +halcyon_symbios_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) +{ + dc_status_t status = DC_STATUS_SUCCESS; + halcyon_symbios_device_t *device = (halcyon_symbios_device_t *) abstract; + + unsigned char request[] = { + datetime->year - 2000, + datetime->month, + datetime->day, + datetime->hour, + datetime->minute, + datetime->second, + }; + status = halcyon_symbios_transfer (device, CMD_SET_TIME, request, sizeof(request), NULL, 0, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to set the time."); + goto error_exit; + } + +error_exit: + return status; +} diff --git a/src/halcyon_symbios.h b/src/halcyon_symbios.h new file mode 100644 index 00000000..d49770b3 --- /dev/null +++ b/src/halcyon_symbios.h @@ -0,0 +1,43 @@ +/* + * libdivecomputer + * + * Copyright (C) 2023 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 OXYGENSCIENTIFIC_RNO_H +#define OXYGENSCIENTIFIC_RNO_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +dc_status_t +halcyon_symbios_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); + +dc_status_t +halcyon_symbios_parser_create (dc_parser_t **parser, dc_context_t *context, const unsigned char data[], size_t size); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* OXYGENSCIENTIFIC_RNO_H */ diff --git a/src/halcyon_symbios_parser.c b/src/halcyon_symbios_parser.c new file mode 100644 index 00000000..e88121fe --- /dev/null +++ b/src/halcyon_symbios_parser.c @@ -0,0 +1,660 @@ +/* + * libdivecomputer + * + * Copyright (C) 2023 Jef Driesen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include +#include + +#include + +#include "halcyon_symbios.h" +#include "context-private.h" +#include "parser-private.h" +#include "array.h" + +#define ID_HEADER 0x01 +#define ID_GAS_SWITCH 0x02 +#define ID_DEPTH 0x03 +#define ID_TEMPERATURE 0x04 +#define ID_OC_CC_SWITCH 0x05 +#define ID_GAS_TRANSMITTER 0x06 +#define ID_COMPARTMENTS 0x07 +#define ID_GPS 0x08 +#define ID_PO2_BOARD 0x09 +#define ID_DECO 0x0A +#define ID_GF 0x0B +#define ID_FOOTER 0x0C +#define ID_PO2_REBREATHER 0x0D +#define ID_COMPASS 0x0E +#define ID_LOG_VERSION 0x0F +#define ID_TRIM 0x10 +#define ID_GAS_CONFIG 0x11 + +#define ISCONFIG(type) ( \ + (type) == ID_LOG_VERSION || \ + (type) == ID_HEADER || \ + (type) == ID_FOOTER) + +#define UNDEFINED 0xFFFFFFFF + +#define EPOCH 1609459200 /* 2021-01-01 00:00:00 */ + +#define OC 0 +#define CC 1 +#define GAUGE 2 +#define SIDEMOUNT 3 + +#define NGASMIXES 10 +#define NTANKS 10 + +typedef struct halcyon_symbios_gasmix_t { + unsigned int id; + unsigned int oxygen; + unsigned int helium; +} halcyon_symbios_gasmix_t; + +typedef struct halcyon_symbios_tank_t { + unsigned int id; + unsigned int beginpressure; + unsigned int endpressure; + dc_usage_t usage; +} halcyon_symbios_tank_t; + +typedef struct halcyon_symbios_parser_t { + dc_parser_t base; + // Cached fields. + unsigned int cached; + unsigned int datetime; + unsigned int divetime; + unsigned int maxdepth; + unsigned int divemode; + unsigned int atmospheric; + unsigned int ngasmixes; + unsigned int ntanks; + halcyon_symbios_gasmix_t gasmix[NGASMIXES]; + halcyon_symbios_tank_t tank[NTANKS]; + unsigned int gf_lo; + unsigned int gf_hi; + unsigned int have_location; + int latitude, longitude; +} halcyon_symbios_parser_t; + +static dc_status_t halcyon_symbios_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); +static dc_status_t halcyon_symbios_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); +static dc_status_t halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); + +static const dc_parser_vtable_t halcyon_symbios_parser_vtable = { + sizeof(halcyon_symbios_parser_t), + DC_FAMILY_HALCYON_SYMBIOS, + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ + halcyon_symbios_parser_get_datetime, /* datetime */ + halcyon_symbios_parser_get_field, /* fields */ + halcyon_symbios_parser_samples_foreach, /* samples_foreach */ + NULL /* destroy */ +}; + +dc_status_t +halcyon_symbios_parser_create (dc_parser_t **out, dc_context_t *context, const unsigned char data[], size_t size) +{ + halcyon_symbios_parser_t *parser = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + parser = (halcyon_symbios_parser_t *) dc_parser_allocate (context, &halcyon_symbios_parser_vtable, data, size); + if (parser == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Set the default values. + parser->cached = 0; + parser->datetime = UNDEFINED; + parser->divetime = 0; + parser->maxdepth = 0; + parser->divemode = UNDEFINED; + parser->atmospheric = UNDEFINED; + parser->gf_lo = UNDEFINED; + parser->gf_hi = UNDEFINED; + parser->have_location = 0; + parser->latitude = 0; + parser->longitude = 0; + parser->ngasmixes = 0; + parser->ntanks = 0; + for (unsigned int i = 0; i < NGASMIXES; ++i) { + parser->gasmix[i].oxygen = 0; + parser->gasmix[i].helium = 0; + } + for (unsigned int i = 0; i < NTANKS; ++i) { + parser->tank[i].id = 0; + parser->tank[i].beginpressure = 0; + parser->tank[i].endpressure = 0; + parser->tank[i].usage = DC_USAGE_NONE; + } + + *out = (dc_parser_t *) parser; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +halcyon_symbios_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) +{ + dc_status_t status = DC_STATUS_SUCCESS; + halcyon_symbios_parser_t *parser = (halcyon_symbios_parser_t *) abstract; + + // Cache the profile data. + if (!parser->cached) { + status = halcyon_symbios_parser_samples_foreach (abstract, NULL, NULL); + if (status != DC_STATUS_SUCCESS) + return status; + } + + if (parser->datetime == UNDEFINED) + return DC_STATUS_UNSUPPORTED; + + dc_ticks_t ticks = (dc_ticks_t) parser->datetime + EPOCH; + + if (!dc_datetime_gmtime (datetime, ticks)) + return DC_STATUS_DATAFORMAT; + + datetime->timezone = DC_TIMEZONE_NONE; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +halcyon_symbios_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) +{ + dc_status_t status = DC_STATUS_SUCCESS; + halcyon_symbios_parser_t *parser = (halcyon_symbios_parser_t *) abstract; + + // Cache the profile data. + if (!parser->cached) { + status = halcyon_symbios_parser_samples_foreach (abstract, NULL, NULL); + if (status != DC_STATUS_SUCCESS) + return status; + } + + dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + dc_tank_t *tank = (dc_tank_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; + dc_location_t *location = (dc_location_t *) value; + + if (value) { + switch (type) { + case DC_FIELD_DIVETIME: + *((unsigned int *) value) = parser->divetime; + break; + case DC_FIELD_MAXDEPTH: + *((double *) value) = parser->maxdepth / 100.0; + break; + case DC_FIELD_ATMOSPHERIC: + if (parser->atmospheric == UNDEFINED) + return DC_STATUS_UNSUPPORTED; + *((double *) value) = parser->atmospheric / 1000.0; + break; + case DC_FIELD_DIVEMODE: + switch (parser->divemode) { + case OC: + case SIDEMOUNT: + *((dc_divemode_t *) value) = DC_DIVEMODE_OC; + break; + case CC: + *((dc_divemode_t *) value) = DC_DIVEMODE_CCR; + break; + case GAUGE: + *((dc_divemode_t *) value) = DC_DIVEMODE_GAUGE; + break; + case UNDEFINED: + return DC_STATUS_UNSUPPORTED; + default: + return DC_STATUS_DATAFORMAT; + } + break; + case DC_FIELD_GASMIX_COUNT: + *((unsigned int *) value) = parser->ngasmixes; + break; + case DC_FIELD_GASMIX: + gasmix->helium = parser->gasmix[flags].helium / 100.0; + gasmix->oxygen = parser->gasmix[flags].oxygen / 100.0; + gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; + gasmix->usage = DC_USAGE_NONE; + break; + case DC_FIELD_TANK_COUNT: + *((unsigned int *) value) = parser->ntanks; + break; + case DC_FIELD_TANK: + tank->type = DC_TANKVOLUME_NONE; + tank->volume = 0.0; + tank->workpressure = 0.0; + tank->beginpressure = parser->tank[flags].beginpressure / 10.0; + tank->endpressure = parser->tank[flags].endpressure / 10.0; + tank->usage = parser->tank[flags].usage; + tank->gasmix = DC_GASMIX_UNKNOWN; + break; + case DC_FIELD_DECOMODEL: + if (parser->gf_lo == UNDEFINED || parser->gf_hi == UNDEFINED) + return DC_STATUS_UNSUPPORTED; + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->conservatism = 0; + decomodel->params.gf.low = parser->gf_lo; + decomodel->params.gf.high = parser->gf_hi; + break; + case DC_FIELD_LOCATION: + if (!parser->have_location) + return DC_STATUS_UNSUPPORTED; + location->latitude = parser->latitude / 1000000.0; + location->longitude = parser->longitude / 1000000.0; + location->altitude = 0.0; + break; + default: + return DC_STATUS_UNSUPPORTED; + } + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +{ + halcyon_symbios_parser_t *parser = (halcyon_symbios_parser_t *) abstract; + const unsigned char *data = abstract->data; + unsigned int size = abstract->size; + + static const unsigned int lengths[] = { + 4, /* ID_LOG_VERSION */ + 64, /* ID_HEADER */ + 4, /* ID_GAS_SWITCH */ + 4, /* ID_DEPTH */ + 4, /* ID_TEMPERATURE */ + 4, /* ID_OC_CC_SWITCH */ + 12, /* ID_GAS_TRANSMITTER */ + 68, /* ID_COMPARTMENTS */ + 12, /* ID_GPS */ + 8, /* ID_PO2_BOARD */ + 16, /* ID_DECO */ + 4, /* ID_GF */ + 16, /* ID_FOOTER */ + 12, /* ID_PO2_REBREATHER */ + 4, /* ID_COMPASS */ + 4, /* ID_LOG_VERSION */ + 4, /* ID_TRIM */ + 8, /* ID_GAS_CONFIG */ + }; + + unsigned int time_start = UNDEFINED, time_end = UNDEFINED; + unsigned int maxdepth = 0; + unsigned int divemode = UNDEFINED; + unsigned int atmospheric = UNDEFINED; + unsigned int gf_lo = UNDEFINED; + unsigned int gf_hi = UNDEFINED; + unsigned int have_location = 0; + int latitude = 0, longitude = 0; + unsigned int ngasmixes = 0; + unsigned int ntanks = 0; + halcyon_symbios_gasmix_t gasmix[NGASMIXES] = {0}; + halcyon_symbios_tank_t tank[NTANKS] = {0}; + unsigned int gasmix_id_previous = UNDEFINED; + unsigned int tank_id_previous = UNDEFINED; + unsigned int tank_usage_previous = UNDEFINED; + unsigned int tank_idx = UNDEFINED; + unsigned int interval = 0; + + unsigned int have_time = 0; + unsigned int have_depth = 0; + unsigned int have_gasmix = 0; + + unsigned int time = 0; + unsigned int offset = 0; + while (offset + 2 <= size) { + dc_sample_value_t sample = {0}; + + unsigned int type = data[offset + 0]; + unsigned int length = data[offset + 1]; + + if (length < 2 || offset + length > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + if (type < C_ARRAY_SIZE(lengths)) { + if (length != lengths[type]) { + ERROR (abstract->context, "Unexpected record size (%u %u).", length, lengths[type]); + return DC_STATUS_DATAFORMAT; + } + } + + // Generate a timestamp for the first non-config record and every + // depth record, except the first one. The first depth record must be + // excluded because the sample already has a timestamp from the first + // non-config record. + if ((!have_time && !ISCONFIG(type)) || + (have_depth && type == ID_DEPTH)) { + time += interval; + sample.time = time * 1000; + if (callback) callback(DC_SAMPLE_TIME, &sample, userdata); + have_time = 1; + } + + if (type == ID_LOG_VERSION) { + unsigned int version_major = data[offset + 2]; + unsigned int version_minor = data[offset + 3]; + DEBUG (abstract->context, "Version: %u.%u", + version_major, version_minor); + } else if (type == ID_HEADER) { + unsigned int model = data[offset + 2]; + unsigned int hw_major = data[offset + 3]; + unsigned int hw_minor = data[offset + 4]; + unsigned int fw_major = data[offset + 5]; + unsigned int fw_minor = data[offset + 6]; + unsigned int fw_bugfix = data[offset + 7]; + unsigned int deco_major = data[offset + 8]; + unsigned int deco_minor = data[offset + 9]; + interval = data[offset + 10]; + unsigned int detection = data[offset + 11]; + unsigned int noflytime = data[offset + 12]; + divemode = data[offset + 13]; + atmospheric = array_uint16_le(data + offset + 16); + unsigned int number = array_uint16_le(data + offset + 18); + unsigned int 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", + model, + hw_major, hw_minor, + fw_major, fw_minor, fw_bugfix, + deco_major, deco_minor, + serial); + } else if (type == ID_GAS_SWITCH) { + unsigned int id = UNDEFINED; + unsigned int o2 = data[offset + 2]; + unsigned int he = data[offset + 3]; + + unsigned int idx = 0; + while (idx < ngasmixes) { + if (id == gasmix[idx].id && + o2 == gasmix[idx].oxygen && + he == gasmix[idx].helium) + break; + idx++; + } + if (idx >= ngasmixes) { + if (ngasmixes >= NGASMIXES) { + ERROR (abstract->context, "Maximum number of gas mixes reached."); + return DC_STATUS_NOMEMORY; + } + gasmix[ngasmixes].id = id; + gasmix[ngasmixes].oxygen = o2; + gasmix[ngasmixes].helium = he; + ngasmixes++; + } + sample.gasmix = idx; + if (callback) callback(DC_SAMPLE_GASMIX, &sample, userdata); + } else if (type == ID_DEPTH) { + unsigned int depth = array_uint16_le (data + offset + 2); + if (maxdepth < depth) + maxdepth = depth; + sample.depth = depth / 100.0; + if (callback) callback (DC_SAMPLE_DEPTH, &sample, userdata); + have_depth = 1; + } else if (type == ID_TEMPERATURE) { + unsigned int temperature = array_uint16_le (data + offset + 2); + sample.depth = temperature / 10.0; + if (callback) callback (DC_SAMPLE_TEMPERATURE, &sample, userdata); + } else if (type == ID_OC_CC_SWITCH) { + unsigned int ccr = data[offset + 2]; + } else if (type == ID_GAS_TRANSMITTER) { + unsigned int gas_id = data[offset + 2]; + unsigned int battery = array_uint16_le (data + offset + 4); + unsigned int pressure = array_uint16_le (data + offset + 6); + unsigned int transmitter = array_uint16_le (data + offset + 8); + dc_usage_t usage = DC_USAGE_NONE; + + if (have_gasmix && gasmix_id_previous != gas_id) { + unsigned int idx = 0; + while (idx < ngasmixes) { + if (gas_id == gasmix[idx].id) + break; + idx++; + } + if (idx >= ngasmixes) { + ERROR (abstract->context, "Invalid gas mix id (%u).", gas_id); + return DC_STATUS_DATAFORMAT; + } + sample.gasmix = idx; + if (callback) callback(DC_SAMPLE_GASMIX, &sample, userdata); + gasmix_id_previous = gas_id; + } + + if (tank_id_previous != transmitter || + tank_usage_previous != usage) { + // Find the tank in the list. + unsigned int idx = 0; + while (idx < ntanks) { + if (tank[idx].id == transmitter && + tank[idx].usage == usage) + break; + idx++; + } + + // Add a new tank if necessary. + if (idx >= ntanks) { + if (ngasmixes >= NTANKS) { + ERROR (abstract->context, "Maximum number of tanks reached."); + return DC_STATUS_NOMEMORY; + } + tank[ntanks].id = transmitter; + tank[ntanks].beginpressure = pressure; + tank[ntanks].endpressure = pressure; + tank[ntanks].usage = usage; + ntanks++; + } + + tank_id_previous = transmitter; + tank_usage_previous = usage; + tank_idx = idx; + } + tank[tank_idx].endpressure = pressure; + + sample.pressure.tank = tank_idx; + sample.pressure.value = pressure / 10.0; + if (callback) callback(DC_SAMPLE_PRESSURE, &sample, userdata); + } else if (type == ID_COMPARTMENTS) { + for (unsigned int i = 0; i < 16; ++i) { + unsigned int n2 = array_uint16_le (data + offset + 4 + i * 2); + unsigned int he = array_uint16_le (data + offset + 36 + i * 2); + } + } else if (type == ID_GPS) { + if (!have_location) { + longitude = (signed int) array_uint32_le (data + offset + 4); + latitude = (signed int) array_uint32_le (data + offset + 8); + have_location = 1; + } else { + WARNING (abstract->context, "Multiple GPS locations present."); + } + } else if (type == ID_PO2_BOARD) { + unsigned int serial = array_uint16_le (data + offset + 6); + for (unsigned int i = 0; i < 3; ++i) { + unsigned int ppo2 = data[offset + 2 + i]; + sample.ppo2.sensor = i; + sample.ppo2.value = ppo2 / 10.0; + if (callback) callback(DC_SAMPLE_PPO2, &sample, userdata); + } + } else if (type == ID_DECO) { + unsigned int ndt = data[offset + 2]; + unsigned int ceiling = data[offset + 3]; + unsigned int cns = data[offset + 4]; + unsigned int safetystop = data[offset + 5]; + unsigned int ceiling_max = array_uint16_le (data + offset + 6); + unsigned int tts = array_uint16_le (data + offset + 8); + unsigned int otu = array_uint16_le (data + offset + 10); + + // Deco / NDL + if (ceiling) { + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.time = 0; + sample.deco.depth = ceiling; + } else { + sample.deco.type = DC_DECO_NDL; + sample.deco.time = ndt * 60; + sample.deco.depth = 0.0; + } + sample.deco.tts = tts; + if (callback) callback(DC_SAMPLE_DECO, &sample, userdata); + + sample.cns = cns / 100.0; + if (callback) callback(DC_SAMPLE_CNS, &sample, userdata); + } else if (type == ID_GF) { + if (gf_lo == UNDEFINED && gf_hi == UNDEFINED) { + gf_lo = data[offset + 2]; + gf_hi = data[offset + 3]; + } else { + WARNING (abstract->context, "Multiple GF values present."); + } + } else if (type == ID_FOOTER) { + unsigned int cns = data[offset + 2]; + unsigned int violations = data[offset + 3]; + unsigned int otu = array_uint16_le (data + offset + 4); + unsigned int battery = array_uint16_le (data + offset + 6); + time_end = array_uint32_le(data + offset + 8); + unsigned int desaturation = array_uint32_le (data + offset + 12); + } else if (type == ID_PO2_REBREATHER) { + for (unsigned int i = 0; i < 3; ++i) { + unsigned int ppo2 = data[offset + 2 + i]; + sample.ppo2.sensor = i; + sample.ppo2.value = ppo2 / 10.0; + if (callback) callback(DC_SAMPLE_PPO2, &sample, userdata); + } + unsigned int pressure = array_uint16_le (data + offset + 8); + unsigned int serial = array_uint16_le (data + offset + 10); + dc_usage_t usage = DC_USAGE_OXYGEN; + + if (tank_id_previous != serial || + tank_usage_previous != usage) { + // Find the tank in the list. + unsigned int idx = 0; + while (idx < ntanks) { + if (tank[idx].id == serial && + tank[idx].usage == usage) + break; + idx++; + } + + // Add a new tank if necessary. + if (idx >= ntanks) { + if (ngasmixes >= NTANKS) { + ERROR (abstract->context, "Maximum number of tanks reached."); + return DC_STATUS_NOMEMORY; + } + tank[ntanks].id = serial; + tank[ntanks].beginpressure = pressure; + tank[ntanks].endpressure = pressure; + tank[ntanks].usage = usage; + ntanks++; + } + + tank_id_previous = serial; + tank_usage_previous = usage; + tank_idx = idx; + } + tank[tank_idx].endpressure = pressure; + + sample.pressure.tank = tank_idx; + 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); + sample.bearing = heading; + if (callback) callback(DC_SAMPLE_BEARING, &sample, userdata); + } else if (type == ID_TRIM) { + int trim = (signed int) data[offset + 2]; + } else if (type == ID_GAS_CONFIG) { + unsigned int id = data[offset + 2]; + unsigned int o2 = data[offset + 3]; + unsigned int he = data[offset + 4]; + if (o2 != 0 || he != 0) { + unsigned int idx = 0; + while (idx < ngasmixes) { + if (id == gasmix[idx].id) + break; + idx++; + } + if (idx >= ngasmixes) { + if (ngasmixes >= NGASMIXES) { + ERROR (abstract->context, "Maximum number of gas mixes reached."); + return DC_STATUS_NOMEMORY; + } + gasmix[ngasmixes].id = id; + gasmix[ngasmixes].oxygen = o2; + gasmix[ngasmixes].helium = he; + ngasmixes++; + have_gasmix = 1; + } else { + if (gasmix[idx].oxygen != o2 || + gasmix[idx].helium != he) { + ERROR (abstract->context, "Gas mix %u changed (%u/%u -> %u/%u).", + gasmix[idx].id, + gasmix[idx].oxygen, gasmix[idx].helium, + o2, he); + return DC_STATUS_DATAFORMAT; + } + + sample.gasmix = idx; + if (callback) callback(DC_SAMPLE_GASMIX, &sample, userdata); + } + } + } else { + WARNING (abstract->context, "Unknown record (type=%u, size=%u", type, length); + } + + offset += length; + } + + parser->cached = 1; + parser->datetime = time_start; + if (time_start != UNDEFINED && time_end != UNDEFINED) { + parser->divetime = time_end - time_start; + } else { + parser->divetime = time; + } + parser->maxdepth = maxdepth; + parser->divemode = divemode; + parser->atmospheric = atmospheric; + parser->gf_lo = gf_lo; + parser->gf_hi = gf_hi; + parser->have_location = have_location; + parser->latitude = latitude; + parser->longitude = longitude; + parser->ngasmixes = ngasmixes; + parser->ntanks = ntanks; + for (unsigned int i = 0; i < NGASMIXES; ++i) { + parser->gasmix[i] = gasmix[i]; + } + for (unsigned int i = 0; i < NTANKS; ++i) { + parser->tank[i] = tank[i]; + } + + return DC_STATUS_SUCCESS; +} diff --git a/src/parser.c b/src/parser.c index 1fea1654..48a177b0 100644 --- a/src/parser.c +++ b/src/parser.c @@ -65,6 +65,7 @@ #include "deepblu_cosmiq.h" #include "oceans_s1.h" #include "divesoft_freedom.h" +#include "halcyon_symbios.h" #include "context-private.h" #include "parser-private.h" @@ -205,6 +206,9 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, const unsigned case DC_FAMILY_DIVESOFT_FREEDOM: rc = divesoft_freedom_parser_create (&parser, context, data, size); break; + case DC_FAMILY_HALCYON_SYMBIOS: + rc = halcyon_symbios_parser_create (&parser, context, data, size); + break; default: return DC_STATUS_INVALIDARGS; } From 57cb60bcc4b6508493cb0eb704646b4da0a45a9d Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 12 Jan 2025 22:25:58 +0100 Subject: [PATCH 03/52] Remove some unused variables --- src/divesoft_freedom.c | 2 -- src/oceanic_atom2_parser.c | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/divesoft_freedom.c b/src/divesoft_freedom.c index ddf9162f..3d4c7c55 100644 --- a/src/divesoft_freedom.c +++ b/src/divesoft_freedom.c @@ -436,7 +436,6 @@ divesoft_freedom_device_foreach (dc_device_t *abstract, dc_dive_callback_t callb unsigned int recordsize = 0; // Download the dive list. - unsigned int ndives = 0; unsigned int total = 0; unsigned int maxsize = 0; unsigned int current = INVALID; @@ -509,7 +508,6 @@ divesoft_freedom_device_foreach (dc_device_t *abstract, dc_dive_callback_t callb offset += recordsize; count++; - ndives++; } // Append the records to the dive list buffer. diff --git a/src/oceanic_atom2_parser.c b/src/oceanic_atom2_parser.c index cd27b357..c67aae89 100644 --- a/src/oceanic_atom2_parser.c +++ b/src/oceanic_atom2_parser.c @@ -759,7 +759,6 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ // Initial gas mix. unsigned int gasmix_previous = 0xFFFFFFFF; - unsigned int count = 0; unsigned int complete = 1; unsigned int previous = 0; unsigned int offset = parser->headersize; @@ -1126,7 +1125,6 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ if (callback) callback (DC_SAMPLE_EVENT, &sample, userdata); } - count++; complete = 1; } From 1898ab886a26a8e1108928501d7a9886362f89b2 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 12 Jan 2025 22:30:15 +0100 Subject: [PATCH 04/52] Use the GCC unused attribute to silence warnings Mark some variable with the GCC 'unused' attribute to silence (harmless) compiler warnings (-Wunused-variable and -Wunused-but-set-variable). The variables are kept because they are valuable as documentation or for debugging purposes. --- src/cochran_commander_parser.c | 3 ++- src/deepsix_excursion_parser.c | 13 +++++------ src/halcyon_symbios_parser.c | 37 ++++++++++++++++---------------- src/liquivision_lynx_parser.c | 3 ++- src/mares_iconhd_parser.c | 5 +++-- src/platform.h | 2 ++ src/shearwater_predator_parser.c | 3 ++- src/suunto_d9_parser.c | 3 ++- src/uwatec_memomouse_parser.c | 5 +++-- 9 files changed, 42 insertions(+), 32 deletions(-) diff --git a/src/cochran_commander_parser.c b/src/cochran_commander_parser.c index aeaf25c8..503b4f41 100644 --- a/src/cochran_commander_parser.c +++ b/src/cochran_commander_parser.c @@ -27,6 +27,7 @@ #include "cochran_commander.h" #include "context-private.h" #include "parser-private.h" +#include "platform.h" #include "array.h" #define COCHRAN_MODEL_COMMANDER_TM 0 @@ -819,7 +820,7 @@ cochran_commander_parser_samples_foreach_emc (dc_parser_t *abstract, dc_sample_c // Ascent rate is logged in the 0th sample, temp in the 1st, repeat. if (time % 2 == 0) { // Ascent rate - double ascent_rate = 0.0; + double DC_ATTR_UNUSED ascent_rate = 0.0; if (s[1] & 0x80) ascent_rate = (s[1] & 0x7f); else diff --git a/src/deepsix_excursion_parser.c b/src/deepsix_excursion_parser.c index e197891e..b448cfe5 100644 --- a/src/deepsix_excursion_parser.c +++ b/src/deepsix_excursion_parser.c @@ -29,6 +29,7 @@ #include "deepsix_excursion.h" #include "context-private.h" #include "parser-private.h" +#include "platform.h" #include "array.h" #define HEADERSIZE_MIN 128 @@ -403,7 +404,7 @@ deepsix_excursion_parser_samples_foreach_v0 (dc_parser_t *abstract, dc_sample_ca break; } - unsigned int misc = data[offset + 1]; + unsigned int DC_ATTR_UNUSED misc = data[offset + 1]; unsigned int depth = array_uint16_le(data + offset + 2); if (type == TEMPERATURE) { @@ -416,8 +417,8 @@ deepsix_excursion_parser_samples_foreach_v0 (dc_parser_t *abstract, dc_sample_ca } if (type == ALARM) { - unsigned int alarm_time = array_uint16_le(data + offset + 4); - unsigned int alarm_value = array_uint16_le(data + offset + 6); + unsigned int DC_ATTR_UNUSED alarm_time = array_uint16_le(data + offset + 4); + unsigned int DC_ATTR_UNUSED alarm_value = array_uint16_le(data + offset + 6); } else if (type == TEMPERATURE) { unsigned int temperature = array_uint16_le(data + offset + 4); if (firmware4c) { @@ -432,10 +433,10 @@ deepsix_excursion_parser_samples_foreach_v0 (dc_parser_t *abstract, dc_sample_ca if (callback) callback(DC_SAMPLE_TEMPERATURE, &sample, userdata); } } else if (type == DECO) { - unsigned int deco = array_uint16_le(data + offset + 4); + unsigned int DC_ATTR_UNUSED deco = array_uint16_le(data + offset + 4); } else if (type == CEILING) { - unsigned int ceiling_depth = array_uint16_le(data + offset + 4); - unsigned int ceiling_time = array_uint16_le(data + offset + 6); + unsigned int DC_ATTR_UNUSED ceiling_depth = array_uint16_le(data + offset + 4); + unsigned int DC_ATTR_UNUSED ceiling_time = array_uint16_le(data + offset + 6); } else if (type == CNS) { unsigned int cns = array_uint16_le(data + offset + 4); sample.cns = cns / 100.0; diff --git a/src/halcyon_symbios_parser.c b/src/halcyon_symbios_parser.c index e88121fe..2982ce96 100644 --- a/src/halcyon_symbios_parser.c +++ b/src/halcyon_symbios_parser.c @@ -27,6 +27,7 @@ #include "halcyon_symbios.h" #include "context-private.h" #include "parser-private.h" +#include "platform.h" #include "array.h" #define ID_HEADER 0x01 @@ -373,12 +374,12 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac unsigned int deco_major = data[offset + 8]; unsigned int deco_minor = data[offset + 9]; interval = data[offset + 10]; - unsigned int detection = data[offset + 11]; - unsigned int noflytime = data[offset + 12]; + unsigned int DC_ATTR_UNUSED detection = data[offset + 11]; + unsigned int DC_ATTR_UNUSED noflytime = data[offset + 12]; divemode = data[offset + 13]; atmospheric = array_uint16_le(data + offset + 16); - unsigned int number = array_uint16_le(data + offset + 18); - unsigned int battery = array_uint16_le(data + offset + 20); + unsigned int DC_ATTR_UNUSED number = array_uint16_le(data + offset + 18); + 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", @@ -424,10 +425,10 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac sample.depth = temperature / 10.0; if (callback) callback (DC_SAMPLE_TEMPERATURE, &sample, userdata); } else if (type == ID_OC_CC_SWITCH) { - unsigned int ccr = data[offset + 2]; + unsigned int DC_ATTR_UNUSED ccr = data[offset + 2]; } else if (type == ID_GAS_TRANSMITTER) { unsigned int gas_id = data[offset + 2]; - unsigned int battery = array_uint16_le (data + offset + 4); + unsigned int DC_ATTR_UNUSED battery = array_uint16_le (data + offset + 4); unsigned int pressure = array_uint16_le (data + offset + 6); unsigned int transmitter = array_uint16_le (data + offset + 8); dc_usage_t usage = DC_USAGE_NONE; @@ -483,8 +484,8 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac if (callback) callback(DC_SAMPLE_PRESSURE, &sample, userdata); } else if (type == ID_COMPARTMENTS) { for (unsigned int i = 0; i < 16; ++i) { - unsigned int n2 = array_uint16_le (data + offset + 4 + i * 2); - unsigned int he = array_uint16_le (data + offset + 36 + i * 2); + unsigned int DC_ATTR_UNUSED n2 = array_uint16_le (data + offset + 4 + i * 2); + unsigned int DC_ATTR_UNUSED he = array_uint16_le (data + offset + 36 + i * 2); } } else if (type == ID_GPS) { if (!have_location) { @@ -495,7 +496,7 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac WARNING (abstract->context, "Multiple GPS locations present."); } } else if (type == ID_PO2_BOARD) { - unsigned int serial = array_uint16_le (data + offset + 6); + unsigned int DC_ATTR_UNUSED serial = array_uint16_le (data + offset + 6); for (unsigned int i = 0; i < 3; ++i) { unsigned int ppo2 = data[offset + 2 + i]; sample.ppo2.sensor = i; @@ -506,10 +507,10 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac unsigned int ndt = data[offset + 2]; unsigned int ceiling = data[offset + 3]; unsigned int cns = data[offset + 4]; - unsigned int safetystop = data[offset + 5]; - unsigned int ceiling_max = array_uint16_le (data + offset + 6); + unsigned int DC_ATTR_UNUSED safetystop = data[offset + 5]; + unsigned int DC_ATTR_UNUSED ceiling_max = array_uint16_le (data + offset + 6); unsigned int tts = array_uint16_le (data + offset + 8); - unsigned int otu = array_uint16_le (data + offset + 10); + unsigned int DC_ATTR_UNUSED otu = array_uint16_le (data + offset + 10); // Deco / NDL if (ceiling) { @@ -534,12 +535,12 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac WARNING (abstract->context, "Multiple GF values present."); } } else if (type == ID_FOOTER) { - unsigned int cns = data[offset + 2]; - unsigned int violations = data[offset + 3]; - unsigned int otu = array_uint16_le (data + offset + 4); - unsigned int battery = array_uint16_le (data + offset + 6); + unsigned int DC_ATTR_UNUSED cns = data[offset + 2]; + unsigned int DC_ATTR_UNUSED violations = data[offset + 3]; + unsigned int DC_ATTR_UNUSED otu = array_uint16_le (data + offset + 4); + unsigned int DC_ATTR_UNUSED battery = array_uint16_le (data + offset + 6); time_end = array_uint32_le(data + offset + 8); - unsigned int desaturation = array_uint32_le (data + offset + 12); + unsigned int DC_ATTR_UNUSED desaturation = array_uint32_le (data + offset + 12); } else if (type == ID_PO2_REBREATHER) { for (unsigned int i = 0; i < 3; ++i) { unsigned int ppo2 = data[offset + 2 + i]; @@ -589,7 +590,7 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac sample.bearing = heading; if (callback) callback(DC_SAMPLE_BEARING, &sample, userdata); } else if (type == ID_TRIM) { - int trim = (signed int) data[offset + 2]; + int DC_ATTR_UNUSED trim = (signed int) data[offset + 2]; } else if (type == ID_GAS_CONFIG) { unsigned int id = data[offset + 2]; unsigned int o2 = data[offset + 3]; diff --git a/src/liquivision_lynx_parser.c b/src/liquivision_lynx_parser.c index c336b6a0..7b5e3163 100644 --- a/src/liquivision_lynx_parser.c +++ b/src/liquivision_lynx_parser.c @@ -27,6 +27,7 @@ #include "liquivision_lynx.h" #include "context-private.h" #include "parser-private.h" +#include "platform.h" #include "array.h" #define ISINSTANCE(parser) dc_parser_isinstance((parser), &liquivision_lynx_parser_vtable) @@ -376,7 +377,7 @@ liquivision_lynx_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callba } unsigned int type = value & 0x7FFF; - unsigned int timestamp = array_uint32_le (data + offset + 2); + unsigned int DC_ATTR_UNUSED timestamp = array_uint32_le (data + offset + 2); offset += 4; // Get the sample length. diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index 855008ab..bdaba2fc 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -26,6 +26,7 @@ #include "mares_iconhd.h" #include "context-private.h" #include "parser-private.h" +#include "platform.h" #include "array.h" #include "checksum.h" @@ -575,7 +576,7 @@ mares_genius_cache (mares_iconhd_parser_t *parser) unsigned int n2 = (gasmixparams >> 7) & 0x7F; unsigned int he = (gasmixparams >> 14) & 0x7F; unsigned int state = (gasmixparams >> 21) & 0x03; - unsigned int changed = (gasmixparams >> 23) & 0x01; + unsigned int DC_ATTR_UNUSED changed = (gasmixparams >> 23) & 0x01; if (o2 + n2 + he != 100) { WARNING (abstract->context, "Invalid gas mix (%u%% He, %u%% O2, %u%% N2).", he, o2, n2); @@ -920,7 +921,7 @@ mares_iconhd_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void dc_sample_value_t sample = {0}; if (parser->model == SMARTAPNEA) { - unsigned int maxdepth = array_uint16_le (data + offset + 0); + unsigned int DC_ATTR_UNUSED maxdepth = array_uint16_le (data + offset + 0); unsigned int divetime = array_uint16_le (data + offset + 2); unsigned int surftime = array_uint16_le (data + offset + 4); diff --git a/src/platform.h b/src/platform.h index e4a5cd58..f98f0d7a 100644 --- a/src/platform.h +++ b/src/platform.h @@ -30,8 +30,10 @@ extern "C" { #if defined(__GNUC__) #define DC_ATTR_FORMAT_PRINTF(a,b) __attribute__((format(printf, a, b))) +#define DC_ATTR_UNUSED __attribute__((unused)) #else #define DC_ATTR_FORMAT_PRINTF(a,b) +#define DC_ATTR_UNUSED #endif #ifdef _WIN32 diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index 167f5850..9cccd8a6 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -28,6 +28,7 @@ #include "shearwater_petrel.h" #include "context-private.h" #include "parser-private.h" +#include "platform.h" #include "array.h" #define ISINSTANCE(parser) ( \ @@ -1092,7 +1093,7 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal } } else if (type == LOG_RECORD_INFO_EVENT) { unsigned int event = data[offset + 1]; - unsigned int timestamp = array_uint32_be (data + offset + 4); + unsigned int DC_ATTR_UNUSED timestamp = array_uint32_be (data + offset + 4); unsigned int w1 = array_uint32_be (data + offset + 8); unsigned int w2 = array_uint32_be (data + offset + 12); diff --git a/src/suunto_d9_parser.c b/src/suunto_d9_parser.c index 8445d117..9973af29 100644 --- a/src/suunto_d9_parser.c +++ b/src/suunto_d9_parser.c @@ -25,6 +25,7 @@ #include "suunto_d9.h" #include "context-private.h" #include "parser-private.h" +#include "platform.h" #include "array.h" #define ISINSTANCE(parser) dc_parser_isinstance((parser), &suunto_d9_parser_vtable) @@ -551,7 +552,7 @@ suunto_d9_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t ca if ((nsamples + 1) == marker) { while (offset < size) { unsigned int event = data[offset++]; - unsigned int seconds, type, state, number, heading; + unsigned int seconds, type, DC_ATTR_UNUSED state, DC_ATTR_UNUSED number, heading; unsigned int current, next; unsigned int he, o2, ppo2, idx; unsigned int length; diff --git a/src/uwatec_memomouse_parser.c b/src/uwatec_memomouse_parser.c index 0849a3c6..f9fc2536 100644 --- a/src/uwatec_memomouse_parser.c +++ b/src/uwatec_memomouse_parser.c @@ -26,6 +26,7 @@ #include "uwatec_memomouse.h" #include "context-private.h" #include "parser-private.h" +#include "platform.h" #include "array.h" #define ISINSTANCE(parser) dc_parser_isinstance((parser), &uwatec_memomouse_parser_vtable) @@ -128,7 +129,7 @@ uwatec_memomouse_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int model = data[3]; - int is_nitrox = 0, is_oxygen = 0, is_air = 0; + int is_nitrox = 0, is_oxygen = 0, DC_ATTR_UNUSED is_air = 0; if ((model & 0xF0) == 0xF0) is_nitrox = 1; if ((model & 0xF0) == 0xA0) @@ -214,7 +215,7 @@ uwatec_memomouse_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callba unsigned int model = data[3]; - int is_nitrox = 0, is_oxygen = 0, is_air = 0; + int is_nitrox = 0, is_oxygen = 0, DC_ATTR_UNUSED is_air = 0; if ((model & 0xF0) == 0xF0) is_nitrox = 1; if ((model & 0xF0) == 0xA0) From 92b13554a7e6dbd055141633c8fb88c4185ba8da Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 23 Feb 2025 12:16:34 +0100 Subject: [PATCH 05/52] Remove the duplicated C_ARRAY_SIZE macros Use the common macro defined in the array.h header instead of duplicating the same macro. The C_ARRAY_ITEMSIZE macro is also moved to the common header. --- src/array.h | 1 + src/bluetooth.c | 3 +-- src/descriptor.c | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/array.h b/src/array.h index fab1475e..a27e9ef5 100644 --- a/src/array.h +++ b/src/array.h @@ -23,6 +23,7 @@ #define ARRAY_H #define C_ARRAY_SIZE(a) (sizeof (a) / sizeof *(a)) +#define C_ARRAY_ITEMSIZE(a) (sizeof *(a)) #ifdef __cplusplus extern "C" { diff --git a/src/bluetooth.c b/src/bluetooth.c index 309953cc..f2e55224 100644 --- a/src/bluetooth.c +++ b/src/bluetooth.c @@ -52,6 +52,7 @@ #include "iostream-private.h" #include "iterator-private.h" #include "platform.h" +#include "array.h" #ifdef _WIN32 #define DC_ADDRESS_FORMAT "%012I64X" @@ -59,8 +60,6 @@ #define DC_ADDRESS_FORMAT "%012llX" #endif -#define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array)) - #define MAX_DEVICES 255 #define MAX_PERIODS 8 diff --git a/src/descriptor.c b/src/descriptor.c index 74717c41..d2df18f3 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -29,9 +29,7 @@ #include "iterator-private.h" #include "platform.h" - -#define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array)) -#define C_ARRAY_ITEMSIZE(array) (sizeof *(array)) +#include "array.h" #define DC_FILTER_INTERNAL(key, values, isnullterminated, match) \ dc_filter_internal( \ From 9108abfb0db27f45b78771235660cea6b3f516bf Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 23 Feb 2025 12:45:12 +0100 Subject: [PATCH 06/52] Replace the dc_descriptor_iterator function The existing dc_descriptor_iterator function is missing a context parameter, and also doesn't follow the standard naming convention. Fixed by adding a new dc_descriptor_iterator_new function. For backwards compatibility, the old function remains present in the library and a macro is used to automatically redirect to the new function. --- doc/man/Makefile.am | 2 +- doc/man/dc_bluetooth_iterator_new.3 | 2 +- doc/man/dc_descriptor_free.3 | 4 ++-- doc/man/dc_descriptor_get_model.3 | 2 +- doc/man/dc_descriptor_get_transports.3 | 4 ++-- ...iptor_iterator.3 => dc_descriptor_iterator_new.3} | 7 ++++--- doc/man/dc_device_open.3 | 4 ++-- doc/man/dc_irda_iterator_new.3 | 2 +- doc/man/dc_iterator_free.3 | 2 +- doc/man/dc_serial_iterator_new.3 | 2 +- doc/man/dc_usbhid_iterator_new.3 | 2 +- doc/man/libdivecomputer.3 | 4 ++-- examples/common.c | 2 +- examples/dctool_list.c | 2 +- include/libdivecomputer/descriptor.h | 7 ++++++- src/descriptor.c | 12 +++++++++++- src/libdivecomputer.symbols | 1 + 17 files changed, 39 insertions(+), 22 deletions(-) rename doc/man/{dc_descriptor_iterator.3 => dc_descriptor_iterator_new.3} (93%) diff --git a/doc/man/Makefile.am b/doc/man/Makefile.am index 4ea44de2..95c3bb2c 100644 --- a/doc/man/Makefile.am +++ b/doc/man/Makefile.am @@ -18,7 +18,7 @@ MANPAGES = \ dc_descriptor_get_product.3 \ dc_descriptor_get_vendor.3 \ dc_descriptor_get_transports.3 \ - dc_descriptor_iterator.3 \ + dc_descriptor_iterator_new.3 \ dc_device_close.3 \ dc_device_foreach.3 \ dc_device_open.3 \ diff --git a/doc/man/dc_bluetooth_iterator_new.3 b/doc/man/dc_bluetooth_iterator_new.3 index e5322061..89a85aa4 100644 --- a/doc/man/dc_bluetooth_iterator_new.3 +++ b/doc/man/dc_bluetooth_iterator_new.3 @@ -44,7 +44,7 @@ opened with and a .Fa descriptor usually found by searching through -.Xr dc_descriptor_iterator 3 . +.Xr dc_descriptor_iterator_new 3 . .Pp On returning .Dv DC_STATUS_SUCCESS diff --git a/doc/man/dc_descriptor_free.3 b/doc/man/dc_descriptor_free.3 index e7f4fde2..6307d3fc 100644 --- a/doc/man/dc_descriptor_free.3 +++ b/doc/man/dc_descriptor_free.3 @@ -36,12 +36,12 @@ Frees a descriptor usually returned with .Xr dc_iterator_next 3 as created with -.Xr dc_descriptor_iterator 3 . +.Xr dc_descriptor_iterator_new 3 . It's safe to pass .Dv NULL to this function. .Sh SEE ALSO -.Xr dc_descriptor_iterator 3 , +.Xr dc_descriptor_iterator_new 3 , .Xr dc_iterator_free 3 .Sh AUTHORS The diff --git a/doc/man/dc_descriptor_get_model.3 b/doc/man/dc_descriptor_get_model.3 index 24ee9917..1cfab3e0 100644 --- a/doc/man/dc_descriptor_get_model.3 +++ b/doc/man/dc_descriptor_get_model.3 @@ -40,7 +40,7 @@ defined for the computer. .Sh RETURN VALUES This returns the model number or 0 if none exists. .Sh SEE ALSO -.Xr dc_descriptor_iterator 3 +.Xr dc_descriptor_iterator_new 3 .Sh AUTHORS The .Lb libdivecomputer diff --git a/doc/man/dc_descriptor_get_transports.3 b/doc/man/dc_descriptor_get_transports.3 index 93bef418..9e5a043f 100644 --- a/doc/man/dc_descriptor_get_transports.3 +++ b/doc/man/dc_descriptor_get_transports.3 @@ -38,7 +38,7 @@ Gets the transports supported by the given The .Fa descriptor usually found by searching through -.Xr dc_descriptor_iterator 3 . +.Xr dc_descriptor_iterator_new 3 . .Sh RETURN VALUES Returns a union (bitwise OR) of the transports supported by the given .Fa descriptor . @@ -59,7 +59,7 @@ if(transports & DC_TRANSPORT_USBHID) { } .Ed .Sh SEE ALSO -.Xr dc_descriptor_iterator 3 . +.Xr dc_descriptor_iterator_new 3 . .Sh AUTHORS The .Lb libdivecomputer diff --git a/doc/man/dc_descriptor_iterator.3 b/doc/man/dc_descriptor_iterator_new.3 similarity index 93% rename from doc/man/dc_descriptor_iterator.3 rename to doc/man/dc_descriptor_iterator_new.3 index 5d6619dc..1d49e106 100644 --- a/doc/man/dc_descriptor_iterator.3 +++ b/doc/man/dc_descriptor_iterator_new.3 @@ -22,15 +22,16 @@ .Dt DC_DESCRIPTOR_ITERATOR 3 .Os .Sh NAME -.Nm dc_descriptor_iterator +.Nm dc_descriptor_iterator_new .Nd get all supported dive computers .Sh LIBRARY .Lb libdivecomputer .Sh SYNOPSIS .In libdivecomputer/descriptor.h .Ft dc_status_t -.Fo dc_descriptor_iterator +.Fo dc_descriptor_iterator_new .Fa "dc_iterator_t **iterator" +.Fa "dc_descriptor_t *descriptor" .Fc .Sh DESCRIPTION Gets all descriptors available to @@ -56,7 +57,7 @@ The following iterates over all descriptors, printing the vendor, then frees the iterator. It does no error checking. .Bd -literal -dc_descriptor_iterator(&iter)); +dc_descriptor_iterator_new(&iter, context)); while (dc_iterator_next(iter, &desc) == DC_STATUS_SUCCESS) { printf("%s\en", dc_descriptor_get_vendor(desc)); dc_descriptor_free(desc); diff --git a/doc/man/dc_device_open.3 b/doc/man/dc_device_open.3 index 3ba335fd..b85451c1 100644 --- a/doc/man/dc_device_open.3 +++ b/doc/man/dc_device_open.3 @@ -44,7 +44,7 @@ opened with a dive computer .Fa descriptor usually found by searching through -.Xr dc_descriptor_iterator 3 , +.Xr dc_descriptor_iterator_new 3 , and a .Fa iostream opened with a transport specific open function like @@ -70,7 +70,7 @@ On success, the pointer is filled in with an open handle. .Sh SEE ALSO .Xr dc_context_new 3 , -.Xr dc_descriptor_iterator 3 , +.Xr dc_descriptor_iterator_new 3 , .Xr dc_device_close 3 .Sh AUTHORS The diff --git a/doc/man/dc_irda_iterator_new.3 b/doc/man/dc_irda_iterator_new.3 index b24f73dc..38c1eba1 100644 --- a/doc/man/dc_irda_iterator_new.3 +++ b/doc/man/dc_irda_iterator_new.3 @@ -44,7 +44,7 @@ opened with and a .Fa descriptor usually found by searching through -.Xr dc_descriptor_iterator 3 . +.Xr dc_descriptor_iterator_new 3 . .Pp On returning .Dv DC_STATUS_SUCCESS diff --git a/doc/man/dc_iterator_free.3 b/doc/man/dc_iterator_free.3 index b086aaa9..745b8d2a 100644 --- a/doc/man/dc_iterator_free.3 +++ b/doc/man/dc_iterator_free.3 @@ -36,7 +36,7 @@ Frees the iterator .Fa iterator . It may be invoked on any iterator type, e.g., one created with -.Xr dc_descriptor_iterator 3 . +.Xr dc_descriptor_iterator_new 3 . .Sh RETURN VALUES This returns .Dv DC_STATUS_SUCCESS diff --git a/doc/man/dc_serial_iterator_new.3 b/doc/man/dc_serial_iterator_new.3 index 91ad54cb..2e35a8f5 100644 --- a/doc/man/dc_serial_iterator_new.3 +++ b/doc/man/dc_serial_iterator_new.3 @@ -44,7 +44,7 @@ opened with and a .Fa descriptor usually found by searching through -.Xr dc_descriptor_iterator 3 . +.Xr dc_descriptor_iterator_new 3 . .Pp On returning .Dv DC_STATUS_SUCCESS diff --git a/doc/man/dc_usbhid_iterator_new.3 b/doc/man/dc_usbhid_iterator_new.3 index 0778d389..5a100b96 100644 --- a/doc/man/dc_usbhid_iterator_new.3 +++ b/doc/man/dc_usbhid_iterator_new.3 @@ -44,7 +44,7 @@ opened with and a .Fa descriptor usually found by searching through -.Xr dc_descriptor_iterator 3 . +.Xr dc_descriptor_iterator_new 3 . .Pp On returning .Dv DC_STATUS_SUCCESS diff --git a/doc/man/libdivecomputer.3 b/doc/man/libdivecomputer.3 index cbe22eb9..c9a90a8a 100644 --- a/doc/man/libdivecomputer.3 +++ b/doc/man/libdivecomputer.3 @@ -48,7 +48,7 @@ and .Xr dc_context_set_loglevel 3 . .It Find a descriptor for their dive computer by iterating through -.Xr dc_descriptor_iterator 3 +.Xr dc_descriptor_iterator_new 3 and searching by name, vendor, or product family. .It Find the transport to use for the communication. To determine the supported transports use @@ -145,7 +145,7 @@ return .El .Sh SEE ALSO .Xr dc_context_new 3 , -.Xr dc_descriptor_iterator 3 +.Xr dc_descriptor_iterator_new 3 .Xr dc_device_open 3 .Xr dc_parser_new 3 .Sh AUTHORS diff --git a/examples/common.c b/examples/common.c index 71b17875..f86278d4 100644 --- a/examples/common.c +++ b/examples/common.c @@ -255,7 +255,7 @@ dctool_descriptor_search (dc_descriptor_t **out, const char *name, dc_family_t f dc_status_t rc = DC_STATUS_SUCCESS; dc_iterator_t *iterator = NULL; - rc = dc_descriptor_iterator (&iterator); + rc = dc_descriptor_iterator_new (&iterator, NULL); if (rc != DC_STATUS_SUCCESS) { ERROR ("Error creating the device descriptor iterator."); return rc; diff --git a/examples/dctool_list.c b/examples/dctool_list.c index bc70d13f..e40c0895 100644 --- a/examples/dctool_list.c +++ b/examples/dctool_list.c @@ -76,7 +76,7 @@ dctool_list_run (int argc, char *argv[], dc_context_t *context, dc_descriptor_t dc_iterator_t *iterator = NULL; dc_descriptor_t *descriptor = NULL; - dc_descriptor_iterator (&iterator); + dc_descriptor_iterator_new (&iterator, context); while (dc_iterator_next (iterator, &descriptor) == DC_STATUS_SUCCESS) { printf ("%s %s\n", dc_descriptor_get_vendor (descriptor), diff --git a/include/libdivecomputer/descriptor.h b/include/libdivecomputer/descriptor.h index bdd8c73c..5c797549 100644 --- a/include/libdivecomputer/descriptor.h +++ b/include/libdivecomputer/descriptor.h @@ -23,6 +23,7 @@ #define DC_DESCRIPTOR_H #include "common.h" +#include "context.h" #include "iterator.h" #ifdef __cplusplus @@ -38,11 +39,15 @@ typedef struct dc_descriptor_t dc_descriptor_t; * Create an iterator to enumerate the supported dive computers. * * @param[out] iterator A location to store the iterator. + * @param[in] context A valid device descriptor. * @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code * on failure. */ dc_status_t -dc_descriptor_iterator (dc_iterator_t **iterator); +dc_descriptor_iterator_new (dc_iterator_t **iterator, dc_context_t *context); + +/* For backwards compatibility */ +#define dc_descriptor_iterator(iterator) dc_descriptor_iterator_new(iterator, NULL) /** * Free the device descriptor. diff --git a/src/descriptor.c b/src/descriptor.c index d2df18f3..63d610f5 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -39,6 +39,10 @@ C_ARRAY_ITEMSIZE(values), \ match) +#undef dc_descriptor_iterator + +dc_status_t dc_descriptor_iterator (dc_iterator_t **out); + typedef int (*dc_match_t)(const void *, const void *); typedef int (*dc_filter_t) (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); @@ -945,7 +949,7 @@ dc_filter_halcyon (dc_descriptor_t *descriptor, dc_transport_t transport, const } dc_status_t -dc_descriptor_iterator (dc_iterator_t **out) +dc_descriptor_iterator_new (dc_iterator_t **out, dc_context_t *context) { dc_descriptor_iterator_t *iterator = NULL; @@ -963,6 +967,12 @@ dc_descriptor_iterator (dc_iterator_t **out) return DC_STATUS_SUCCESS; } +dc_status_t +dc_descriptor_iterator (dc_iterator_t **out) +{ + return dc_descriptor_iterator_new (out, NULL); +} + static dc_status_t dc_descriptor_iterator_next (dc_iterator_t *abstract, void *out) { diff --git a/src/libdivecomputer.symbols b/src/libdivecomputer.symbols index 36bd5f89..e04c01b8 100644 --- a/src/libdivecomputer.symbols +++ b/src/libdivecomputer.symbols @@ -28,6 +28,7 @@ dc_iterator_next dc_iterator_free dc_descriptor_iterator +dc_descriptor_iterator_new dc_descriptor_free dc_descriptor_get_vendor dc_descriptor_get_product From 951df7cdca80c455dc983330a304d3327cceb585 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 3 Feb 2025 19:14:18 +0100 Subject: [PATCH 07/52] Mark the descriptor parameter as const The descriptor functions do not modify the passed descriptor. This is now made explicit by marking the parameter as const. --- doc/man/dc_descriptor_get_model.3 | 2 +- doc/man/dc_descriptor_get_product.3 | 2 +- doc/man/dc_descriptor_get_transports.3 | 2 +- doc/man/dc_descriptor_get_vendor.3 | 2 +- include/libdivecomputer/descriptor.h | 12 ++-- src/descriptor.c | 80 +++++++++++++------------- 6 files changed, 50 insertions(+), 50 deletions(-) diff --git a/doc/man/dc_descriptor_get_model.3 b/doc/man/dc_descriptor_get_model.3 index 1cfab3e0..245113cd 100644 --- a/doc/man/dc_descriptor_get_model.3 +++ b/doc/man/dc_descriptor_get_model.3 @@ -31,7 +31,7 @@ .In libdivecomputer/descriptor.h .Ft "unsigned int" .Fo dc_descriptor_get_model -.Fa "dc_descriptor_t *descriptor" +.Fa "const dc_descriptor_t *descriptor" .Fc .Sh DESCRIPTION Gets the model number of a dive computer descriptor or 0 if none was diff --git a/doc/man/dc_descriptor_get_product.3 b/doc/man/dc_descriptor_get_product.3 index 541da7c9..8b63df46 100644 --- a/doc/man/dc_descriptor_get_product.3 +++ b/doc/man/dc_descriptor_get_product.3 @@ -30,7 +30,7 @@ .In libdivecomputer/descriptor.h .Ft "const char *" .Fo dc_descriptor_get_product -.Fa "dc_descriptor_t *descriptor" +.Fa "const dc_descriptor_t *descriptor" .Fc .Sh DESCRIPTION Gets the product diff --git a/doc/man/dc_descriptor_get_transports.3 b/doc/man/dc_descriptor_get_transports.3 index 9e5a043f..fe663bae 100644 --- a/doc/man/dc_descriptor_get_transports.3 +++ b/doc/man/dc_descriptor_get_transports.3 @@ -30,7 +30,7 @@ .In libdivecomputer/descriptor.h .Ft "unsigned int" .Fo dc_descriptor_get_transports -.Fa "dc_descriptor_t *descriptor" +.Fa "const dc_descriptor_t *descriptor" .Fc .Sh DESCRIPTION Gets the transports supported by the given diff --git a/doc/man/dc_descriptor_get_vendor.3 b/doc/man/dc_descriptor_get_vendor.3 index 6c5de335..146f2f9f 100644 --- a/doc/man/dc_descriptor_get_vendor.3 +++ b/doc/man/dc_descriptor_get_vendor.3 @@ -30,7 +30,7 @@ .In libdivecomputer/descriptor.h .Ft "const char *" .Fo dc_descriptor_get_vendor -.Fa "dc_descriptor_t *descriptor" +.Fa "const dc_descriptor_t *descriptor" .Fc .Sh DESCRIPTION Gets the vendor diff --git a/include/libdivecomputer/descriptor.h b/include/libdivecomputer/descriptor.h index 5c797549..6eaba8ed 100644 --- a/include/libdivecomputer/descriptor.h +++ b/include/libdivecomputer/descriptor.h @@ -64,7 +64,7 @@ dc_descriptor_free (dc_descriptor_t *descriptor); * @returns The vendor name of the dive computer on success, or NULL on failure. */ const char * -dc_descriptor_get_vendor (dc_descriptor_t *descriptor); +dc_descriptor_get_vendor (const dc_descriptor_t *descriptor); /** * Get the product name of the dive computer. @@ -74,7 +74,7 @@ dc_descriptor_get_vendor (dc_descriptor_t *descriptor); * failure. */ const char * -dc_descriptor_get_product (dc_descriptor_t *descriptor); +dc_descriptor_get_product (const dc_descriptor_t *descriptor); /** * Get the family type of the dive computer. @@ -84,7 +84,7 @@ dc_descriptor_get_product (dc_descriptor_t *descriptor); * on failure. */ dc_family_t -dc_descriptor_get_type (dc_descriptor_t *descriptor); +dc_descriptor_get_type (const dc_descriptor_t *descriptor); /** * Get the model number of the dive computer. @@ -94,7 +94,7 @@ dc_descriptor_get_type (dc_descriptor_t *descriptor); * failure. */ unsigned int -dc_descriptor_get_model (dc_descriptor_t *descriptor); +dc_descriptor_get_model (const dc_descriptor_t *descriptor); /** * Get all transports supported by the dive computer. @@ -104,7 +104,7 @@ dc_descriptor_get_model (dc_descriptor_t *descriptor); * success, or DC_TRANSPORT_NONE on failure. */ unsigned int -dc_descriptor_get_transports (dc_descriptor_t *descriptor); +dc_descriptor_get_transports (const dc_descriptor_t *descriptor); /** * Check if a low-level I/O device matches a supported dive computer. @@ -122,7 +122,7 @@ dc_descriptor_get_transports (dc_descriptor_t *descriptor); * there is no match. */ int -dc_descriptor_filter (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); +dc_descriptor_filter (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); #ifdef __cplusplus } diff --git a/src/descriptor.c b/src/descriptor.c index 63d610f5..7181ee7b 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -45,24 +45,24 @@ dc_status_t dc_descriptor_iterator (dc_iterator_t **out); typedef int (*dc_match_t)(const void *, const void *); -typedef int (*dc_filter_t) (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); - -static int dc_filter_uwatec (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); -static int dc_filter_suunto (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); -static int dc_filter_shearwater (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); -static int dc_filter_hw (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); -static int dc_filter_tecdiving (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); -static int dc_filter_mares (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); -static int dc_filter_divesystem (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); -static int dc_filter_oceanic (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); -static int dc_filter_mclean (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); -static int dc_filter_atomic (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); -static int dc_filter_deepsix (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); -static int dc_filter_deepblu (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); -static int dc_filter_oceans (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); -static int dc_filter_divesoft (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); -static int dc_filter_cressi (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); -static int dc_filter_halcyon (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); +typedef int (*dc_filter_t) (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); + +static int dc_filter_uwatec (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); +static int dc_filter_suunto (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); +static int dc_filter_shearwater (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); +static int dc_filter_hw (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); +static int dc_filter_tecdiving (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); +static int dc_filter_mares (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); +static int dc_filter_divesystem (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); +static int dc_filter_oceanic (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); +static int dc_filter_mclean (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); +static int dc_filter_atomic (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); +static int dc_filter_deepsix (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); +static int dc_filter_deepblu (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); +static int dc_filter_oceans (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); +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 dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item); @@ -634,7 +634,7 @@ static const char * const rfcomm[] = { }; static int -dc_filter_uwatec (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +dc_filter_uwatec (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) { static const char * const irda[] = { "Aladin Smart Com", @@ -675,7 +675,7 @@ dc_filter_uwatec (dc_descriptor_t *descriptor, dc_transport_t transport, const v } static int -dc_filter_suunto (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +dc_filter_suunto (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) { static const dc_usbhid_desc_t usbhid[] = { {0x1493, 0x0030}, // Eon Steel @@ -700,7 +700,7 @@ dc_filter_suunto (dc_descriptor_t *descriptor, dc_transport_t transport, const v } static int -dc_filter_hw (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +dc_filter_hw (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) { static const char * const bluetooth[] = { "OSTC", @@ -717,7 +717,7 @@ dc_filter_hw (dc_descriptor_t *descriptor, dc_transport_t transport, const void } static int -dc_filter_shearwater (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +dc_filter_shearwater (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) { static const char * const bluetooth[] = { "Predator", @@ -743,7 +743,7 @@ dc_filter_shearwater (dc_descriptor_t *descriptor, dc_transport_t transport, con } static int -dc_filter_tecdiving (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +dc_filter_tecdiving (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) { static const char * const bluetooth[] = { "DiveComputer", @@ -759,7 +759,7 @@ dc_filter_tecdiving (dc_descriptor_t *descriptor, dc_transport_t transport, cons } static int -dc_filter_mares (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +dc_filter_mares (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) { static const char * const bluetooth[] = { "Mares bluelink pro", @@ -777,7 +777,7 @@ dc_filter_mares (dc_descriptor_t *descriptor, dc_transport_t transport, const vo } static int -dc_filter_divesystem (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +dc_filter_divesystem (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) { static const char * const bluetooth[] = { "DS", @@ -793,7 +793,7 @@ dc_filter_divesystem (dc_descriptor_t *descriptor, dc_transport_t transport, con } static int -dc_filter_oceanic (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +dc_filter_oceanic (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) { static const unsigned int model[] = { 0x4552, // Oceanic Pro Plus X @@ -824,7 +824,7 @@ dc_filter_oceanic (dc_descriptor_t *descriptor, dc_transport_t transport, const } static int -dc_filter_mclean(dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +dc_filter_mclean(const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) { static const char * const bluetooth[] = { "McLean Extreme", @@ -840,7 +840,7 @@ dc_filter_mclean(dc_descriptor_t *descriptor, dc_transport_t transport, const vo } static int -dc_filter_atomic (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +dc_filter_atomic (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) { static const dc_usb_desc_t usb[] = { {0x0471, 0x0888}, // Atomic Aquatics Cobalt @@ -854,7 +854,7 @@ dc_filter_atomic (dc_descriptor_t *descriptor, dc_transport_t transport, const v } static int -dc_filter_deepsix (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +dc_filter_deepsix (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) { static const char * const bluetooth[] = { "EXCURSION", @@ -871,7 +871,7 @@ dc_filter_deepsix (dc_descriptor_t *descriptor, dc_transport_t transport, const } static int -dc_filter_deepblu (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +dc_filter_deepblu (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) { static const char * const bluetooth[] = { "COSMIQ", @@ -885,7 +885,7 @@ dc_filter_deepblu (dc_descriptor_t *descriptor, dc_transport_t transport, const } static int -dc_filter_oceans (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +dc_filter_oceans (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) { static const char * const bluetooth[] = { "S1", @@ -899,7 +899,7 @@ dc_filter_oceans (dc_descriptor_t *descriptor, dc_transport_t transport, const v } static int -dc_filter_divesoft (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +dc_filter_divesoft (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) { static const char * const bluetooth[] = { "Freedom", @@ -914,7 +914,7 @@ dc_filter_divesoft (dc_descriptor_t *descriptor, dc_transport_t transport, const } static int -dc_filter_cressi (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +dc_filter_cressi (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) { static const unsigned int model[] = { 1, // Cartesio @@ -934,7 +934,7 @@ dc_filter_cressi (dc_descriptor_t *descriptor, dc_transport_t transport, const v } static int -dc_filter_halcyon (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +dc_filter_halcyon (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) { static const char * const bluetooth[] = { "H01", // Symbios HUD @@ -1001,7 +1001,7 @@ dc_descriptor_free (dc_descriptor_t *descriptor) } const char * -dc_descriptor_get_vendor (dc_descriptor_t *descriptor) +dc_descriptor_get_vendor (const dc_descriptor_t *descriptor) { if (descriptor == NULL) return NULL; @@ -1010,7 +1010,7 @@ dc_descriptor_get_vendor (dc_descriptor_t *descriptor) } const char * -dc_descriptor_get_product (dc_descriptor_t *descriptor) +dc_descriptor_get_product (const dc_descriptor_t *descriptor) { if (descriptor == NULL) return NULL; @@ -1019,7 +1019,7 @@ dc_descriptor_get_product (dc_descriptor_t *descriptor) } dc_family_t -dc_descriptor_get_type (dc_descriptor_t *descriptor) +dc_descriptor_get_type (const dc_descriptor_t *descriptor) { if (descriptor == NULL) return DC_FAMILY_NULL; @@ -1028,7 +1028,7 @@ dc_descriptor_get_type (dc_descriptor_t *descriptor) } unsigned int -dc_descriptor_get_model (dc_descriptor_t *descriptor) +dc_descriptor_get_model (const dc_descriptor_t *descriptor) { if (descriptor == NULL) return 0; @@ -1037,7 +1037,7 @@ dc_descriptor_get_model (dc_descriptor_t *descriptor) } unsigned int -dc_descriptor_get_transports (dc_descriptor_t *descriptor) +dc_descriptor_get_transports (const dc_descriptor_t *descriptor) { if (descriptor == NULL) return DC_TRANSPORT_NONE; @@ -1046,7 +1046,7 @@ dc_descriptor_get_transports (dc_descriptor_t *descriptor) } int -dc_descriptor_filter (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +dc_descriptor_filter (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) { if (descriptor == NULL || descriptor->filter == NULL || userdata == NULL) return 1; From ce2c3c696a3f868850647525ffaed029597b29c8 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 3 Feb 2025 19:31:20 +0100 Subject: [PATCH 08/52] Check the descriptor transport in the filter The filter function should take into account the transports supported by the device descriptor. If a transport isn't supported, the filter function shouldn't indicate a match. --- src/descriptor.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/descriptor.c b/src/descriptor.c index 7181ee7b..9561f692 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -1048,7 +1048,13 @@ dc_descriptor_get_transports (const dc_descriptor_t *descriptor) int dc_descriptor_filter (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) { - if (descriptor == NULL || descriptor->filter == NULL || userdata == NULL) + if (descriptor == NULL) + return 1; + + if ((descriptor->transports & transport) == 0) + return 0; + + if (descriptor->filter == NULL || userdata == NULL) return 1; return descriptor->filter (descriptor, transport, userdata); From 3ae8bf6eff1ece9d05a8de5dfb317fabcc944d3a Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 3 Feb 2025 19:32:48 +0100 Subject: [PATCH 09/52] Remove the rfcomm filter function The rfcomm filter function is not only platform dependent (Linux only), but also difficult to use correctly. It should only be enabled for devices which support DC_TRANSPORT_SERIAL due to the serial port emulation layer of the bluetooth Serial Port Profile (SPP/rfcomm), and not for usb-serial communication. However, some dive computers models like the OSTC3 have both rfcomm and usb-serial enabled variants. Since the filter functions are shared by all models, the result isn't always reliable. Fixed by simply removing the rfcomm filter. For serial communication, every string will now be considered a match, which is the same result as with no filter function present. --- src/descriptor.c | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index 9561f692..f3459d1c 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -506,15 +506,6 @@ dc_match_prefix (const void *key, const void *value) return strncasecmp (k, v, strlen (v)) == 0; } -static int -dc_match_devname (const void *key, const void *value) -{ - const char *k = (const char *) key; - const char *v = *(const char * const *) value; - - return strncmp (k, v, strlen (v)) == 0; -} - static int dc_match_usb (const void *key, const void *value) { @@ -626,13 +617,6 @@ dc_filter_internal (const void *key, const void *values, size_t count, size_t si return count == 0; } -static const char * const rfcomm[] = { -#if defined (__linux__) - "/dev/rfcomm", -#endif - NULL -}; - static int dc_filter_uwatec (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) { @@ -709,8 +693,6 @@ dc_filter_hw (const dc_descriptor_t *descriptor, dc_transport_t transport, const if (transport == DC_TRANSPORT_BLUETOOTH || transport == DC_TRANSPORT_BLE) { return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_prefix); - } else if (transport == DC_TRANSPORT_SERIAL) { - return DC_FILTER_INTERNAL (userdata, rfcomm, 1, dc_match_devname); } return 1; @@ -735,8 +717,6 @@ dc_filter_shearwater (const dc_descriptor_t *descriptor, dc_transport_t transpor if (transport == DC_TRANSPORT_BLUETOOTH || transport == DC_TRANSPORT_BLE) { return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_name); - } else if (transport == DC_TRANSPORT_SERIAL) { - return DC_FILTER_INTERNAL (userdata, rfcomm, 1, dc_match_devname); } return 1; @@ -751,8 +731,6 @@ dc_filter_tecdiving (const dc_descriptor_t *descriptor, dc_transport_t transport if (transport == DC_TRANSPORT_BLUETOOTH) { return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_name); - } else if (transport == DC_TRANSPORT_SERIAL) { - return DC_FILTER_INTERNAL (userdata, rfcomm, 1, dc_match_devname); } return 1; @@ -832,8 +810,6 @@ dc_filter_mclean(const dc_descriptor_t *descriptor, dc_transport_t transport, co if (transport == DC_TRANSPORT_BLUETOOTH || transport == DC_TRANSPORT_BLE) { return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_name); - } else if (transport == DC_TRANSPORT_SERIAL) { - return DC_FILTER_INTERNAL(userdata, rfcomm, 1, dc_match_devname); } return 1; From f4e0f02390bb14bf3b8478f8f87f00dfeab5e8c8 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 3 Feb 2025 20:24:43 +0100 Subject: [PATCH 10/52] Select a bluetooth enabled model as the default Using a bluetooth enabled model (e.g the OSTC 2) as the default choice is very convenient when using only the --family command-line option to scan for bluetooth devices or establish a connection. That's because after commit ce2c3c696a3f868850647525ffaed029597b29c8, the filter will no longer result in a match for the non-bluetooth enabled models. --- examples/common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/common.c b/examples/common.c index f86278d4..a1eeb44e 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, 0x0A}, + {"ostc3", DC_FAMILY_HW_OSTC3, 0x11}, {"edy", DC_FAMILY_CRESSI_EDY, 0x08}, {"leonardo", DC_FAMILY_CRESSI_LEONARDO, 1}, {"goa", DC_FAMILY_CRESSI_GOA, 2}, From 0af7a7e75857734794d201fcad4ffdde3625e0de Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 8 Nov 2024 16:07:59 +0100 Subject: [PATCH 11/52] Verify the checksum in the dive header The checksum can help to detect corrupt dives and/or bugs in the download code. --- src/cressi_goa_parser.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/cressi_goa_parser.c b/src/cressi_goa_parser.c index 22e9c659..80ac3ff6 100644 --- a/src/cressi_goa_parser.c +++ b/src/cressi_goa_parser.c @@ -24,6 +24,7 @@ #include "cressi_goa.h" #include "context-private.h" #include "parser-private.h" +#include "checksum.h" #include "array.h" #define ISINSTANCE(parser) dc_device_isinstance((parser), &cressi_goa_parser_vtable) @@ -366,6 +367,13 @@ cressi_goa_init(cressi_goa_parser_t *parser) return DC_STATUS_DATAFORMAT; } + unsigned short crc = array_uint16_le (data + headersize + layout->headersize - 2); + unsigned short ccrc = checksum_crc16_ccitt (data + headersize, layout->headersize - 2, 0xffff, 0x0000); + if (crc != ccrc) { + ERROR (abstract->context, "Unexpected header checksum (%04x %04x).", crc, ccrc); + return DC_STATUS_DATAFORMAT; + } + parser->layout = layout; parser->headersize = headersize; parser->divemode = divemode; From b489a180962f02d9584850d71c9395dcdbe121c9 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 3 May 2024 23:44:44 +0200 Subject: [PATCH 12/52] Read the model number for diagnostics purposes The model number isn't strictly needed anymore, because libdivecomputer uses the information in the version packet to the detect the dive computer model. However, since the internal lookup table is still based on the model number, having easy access to the model number in the debug logs is very convenient when adding support for new devices. --- src/mares_iconhd.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index 6cf1638a..bde4a51a 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -91,6 +91,7 @@ #define CMD_OBJ_ODD 0xFE #define OBJ_DEVICE 0x2000 +#define OBJ_DEVICE_MODEL 0x02 #define OBJ_DEVICE_SERIAL 0x04 #define OBJ_LOGBOOK 0x2008 #define OBJ_LOGBOOK_COUNT 0x01 @@ -1021,6 +1022,26 @@ mares_iconhd_device_foreach_object (dc_device_t *abstract, dc_dive_callback_t ca return DC_STATUS_NOMEMORY; } + // Read the model number. + rc = mares_iconhd_read_object (device, NULL, buffer, OBJ_DEVICE, OBJ_DEVICE_MODEL); + if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the model number."); + dc_buffer_free (buffer); + return rc; + } + + if (dc_buffer_get_size (buffer) < 4) { + ERROR (abstract->context, "Unexpected number of bytes received (" DC_PRINTF_SIZE ").", + dc_buffer_get_size (buffer)); + dc_buffer_free (buffer); + return DC_STATUS_PROTOCOL; + } + + unsigned int DC_ATTR_UNUSED model = array_uint32_le (dc_buffer_get_data (buffer)); + + // Erase the buffer. + dc_buffer_clear (buffer); + // Read the serial number. rc = mares_iconhd_read_object (device, NULL, buffer, OBJ_DEVICE, OBJ_DEVICE_SERIAL); if (rc != DC_STATUS_SUCCESS) { From 95a2e47e027fef18ec4c64e95f1f7dd6de56072a Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 10 Mar 2025 18:36:38 +0100 Subject: [PATCH 13/52] Add support for the Puck Lite The Puck Lite 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 f3459d1c..5b15815e 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -318,6 +318,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Mares", "Sirius", DC_FAMILY_MARES_ICONHD , 0x2F, DC_TRANSPORT_BLE, dc_filter_mares}, {"Mares", "Quad Ci", DC_FAMILY_MARES_ICONHD , 0x31, DC_TRANSPORT_BLE, dc_filter_mares}, {"Mares", "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}, /* 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}, @@ -745,6 +746,7 @@ dc_filter_mares (const dc_descriptor_t *descriptor, dc_transport_t transport, co "Sirius", "Quad Ci", "Puck4", + "Puck Lite", }; if (transport == DC_TRANSPORT_BLE) { diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index bde4a51a..d23192d0 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -196,6 +196,7 @@ mares_iconhd_get_model (mares_iconhd_device_t *device) {"Sirius", SIRIUS}, {"Quad Ci", QUADCI}, {"Puck4", PUCK4}, + {"Puck Lite", PUCK4}, }; // Check the product name in the version packet against the list From d8cbca9328ac2f9c5f80c5f2c1608eb94fa1e8e2 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 24 Mar 2025 20:51:13 +0100 Subject: [PATCH 14/52] Limit the size of the fingerprint to 4 bytes For the Sirius (and compatible models), the data/time information is only 4 bytes large instead of the default 10 bytes. --- src/mares_iconhd.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index d23192d0..09084f3d 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -661,6 +661,10 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_ break; case GENIUS: case HORIZON: + case PUCKAIR2: + case SIRIUS: + case QUADCI: + case PUCK4: device->layout = &mares_genius_layout; device->packetsize = 4096; device->fingerprint_size = 4; From 326dea3d05cd63d9b088fa2f4f2b22a7cce99f2d Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 24 Mar 2025 21:02:09 +0100 Subject: [PATCH 15/52] Include the fingerprint data in the logs The fingerprint data is valuable information when trying to reproduce some issues reported by users. --- src/device.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/device.c b/src/device.c index 92c36e09..ec11b93d 100644 --- a/src/device.c +++ b/src/device.c @@ -317,6 +317,8 @@ dc_device_set_fingerprint (dc_device_t *device, const unsigned char data[], unsi if (device->vtable->set_fingerprint == NULL) return DC_STATUS_UNSUPPORTED; + HEXDUMP (device->context, DC_LOGLEVEL_INFO, "Fingerprint", data, size); + return device->vtable->set_fingerprint (device, data, size); } From 32709e6d7afc9e0bbb1842bbb78618fe0c6868d2 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 17 Mar 2025 21:51:15 +0100 Subject: [PATCH 16/52] Ignore zero pressure values Occasionally there are a few samples with a zero pressure value at the begin and/or the end of a dive. Those values result in an incorrect begin/end pressure. Fixed by ignoring the zero pressure values. --- src/shearwater_predator_parser.c | 36 +++++++++++++++++++------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index 9cccd8a6..1f1a4226 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -472,12 +472,14 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) unsigned int id = (aimode == AI_HPCCR ? 4 : 0) + i; if (pressure < 0xFFF0) { pressure &= 0x0FFF; - if (!tank[id].active) { - tank[id].active = 1; - tank[id].beginpressure = pressure; + if (pressure) { + if (!tank[id].active) { + tank[id].active = 1; + tank[id].beginpressure = pressure; + tank[id].endpressure = pressure; + } tank[id].endpressure = pressure; } - tank[id].endpressure = pressure; } } } @@ -489,12 +491,14 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) unsigned int id = 2 + i; if (pressure < 0xFFF0) { pressure &= 0x0FFF; - if (!tank[id].active) { - tank[id].active = 1; - tank[id].beginpressure = pressure; + if (pressure) { + if (!tank[id].active) { + tank[id].active = 1; + tank[id].beginpressure = pressure; + tank[id].endpressure = pressure; + } tank[id].endpressure = pressure; } - tank[id].endpressure = pressure; } } } @@ -1020,9 +1024,11 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal unsigned int id = (parser->aimode == AI_HPCCR ? 4 : 0) + i; if (pressure < 0xFFF0) { pressure &= 0x0FFF; - sample.pressure.tank = parser->tankidx[id]; - sample.pressure.value = pressure * 2 * PSI / BAR; - if (callback) callback (DC_SAMPLE_PRESSURE, &sample, userdata); + if (pressure) { + sample.pressure.tank = parser->tankidx[id]; + sample.pressure.value = pressure * 2 * PSI / BAR; + if (callback) callback (DC_SAMPLE_PRESSURE, &sample, userdata); + } } } @@ -1046,9 +1052,11 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal unsigned int id = 2 + i; if (pressure < 0xFFF0) { pressure &= 0x0FFF; - sample.pressure.tank = parser->tankidx[id]; - sample.pressure.value = pressure * 2 * PSI / BAR; - if (callback) callback (DC_SAMPLE_PRESSURE, &sample, userdata); + if (pressure) { + sample.pressure.tank = parser->tankidx[id]; + sample.pressure.value = pressure * 2 * PSI / BAR; + if (callback) callback (DC_SAMPLE_PRESSURE, &sample, userdata); + } } } } From 82712ce8f83b2448d01ad26a949a82c27452ba5d Mon Sep 17 00:00:00 2001 From: rmultan Date: Mon, 24 Mar 2025 17:07:56 +0100 Subject: [PATCH 17/52] Add support for a new Sirius profile version After upgrading a Mares Sirius to firmware version 1.6.16, the profile version changed to v2.0. Surprisingly, despite the change in the major version number, there appear to be no functional changes in the data format. Signed-off-by: rmultan --- src/mares_iconhd_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index bdaba2fc..dc25a4db 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -1054,7 +1054,7 @@ mares_genius_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void unsigned int profile_minor = data[offset + 2]; unsigned int profile_major = data[offset + 3]; if (profile_type > 1 || - (profile_type == 0 && OBJVERSION(profile_major,profile_minor) > OBJVERSION(1,0)) || + (profile_type == 0 && OBJVERSION(profile_major,profile_minor) > OBJVERSION(2,0)) || (profile_type == 1 && OBJVERSION(profile_major,profile_minor) > OBJVERSION(0,2))) { ERROR (abstract->context, "Unsupported object type (%u) or version (%u.%u).", profile_type, profile_major, profile_minor); From bfce6e00ef2525ca45002448c1704e4f965205c5 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 7 Apr 2025 22:50:13 +0200 Subject: [PATCH 18/52] Add support for the OSTC 5 The OSTC 5 uses the same firmware as the OSTC 4 and is therefore fully compatible. The new bluetooth name "OSTC5-XXXXX" is already recognized, because the filter checks for the generic "OSTC" prefix. --- src/descriptor.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/descriptor.c b/src/descriptor.c index 5b15815e..cb8ba112 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -332,6 +332,7 @@ static const dc_descriptor_t g_descriptors[] = { {"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}, From 922990bbb879cc544f16092eed367c21541b45c7 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 24 Mar 2025 19:42:52 +0100 Subject: [PATCH 19/52] Add support for the average depth --- src/cressi_edy_parser.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cressi_edy_parser.c b/src/cressi_edy_parser.c index 951c032b..84121042 100644 --- a/src/cressi_edy_parser.c +++ b/src/cressi_edy_parser.c @@ -141,6 +141,9 @@ cressi_edy_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsign case DC_FIELD_MAXDEPTH: *((double *) value) = (bcd2dec (p[0x02] & 0x0F) * 100 + bcd2dec (p[0x03])) / 10.0; break; + case DC_FIELD_AVGDEPTH: + *((double *) value) = (bcd2dec (p[0x00] & 0x0F) * 100 + bcd2dec (p[0x01])) / 10.0; + break; case DC_FIELD_GASMIX_COUNT: *((unsigned int *) value) = cressi_edy_parser_count_gasmixes(p); break; From 63662d9f94f26acd23306892396607bb800a2044 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 24 Mar 2025 20:07:11 +0100 Subject: [PATCH 20/52] Fix the decoding of the temperature The lower nibble at offset 0x0C contains part of the dive time and only the higher nibble contains part of the temperature. Since this extra nibble ended up as the second decimal figure, the error is actually very small and nobody really noticed the mistake. --- src/cressi_edy_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cressi_edy_parser.c b/src/cressi_edy_parser.c index 84121042..3a0343f7 100644 --- a/src/cressi_edy_parser.c +++ b/src/cressi_edy_parser.c @@ -154,7 +154,7 @@ cressi_edy_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsign gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; break; case DC_FIELD_TEMPERATURE_MINIMUM: - *((double *) value) = (bcd2dec (p[0x0B]) * 100 + bcd2dec (p[0x0C])) / 100.0; + *((double *) value) = (bcd2dec (p[0x0B]) * 10 + ((p[0x0C] & 0xF0) >> 4)) / 10.0; break; default: return DC_STATUS_UNSUPPORTED; From c06073de49f4d10446d512d360bbf2f8bf9e531c Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 25 Mar 2025 19:18:45 +0100 Subject: [PATCH 21/52] Fix the active gasmix detection The Cressi Edy appears to set the disabled gas mixes to 0xFF. The check for the magic value 0xF0 doesn't catch this case and therefore the value 0xFF results in a gas mix with 165% O2, which is obviously invalid. Fixed by checking the high nibble only. --- src/cressi_edy_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cressi_edy_parser.c b/src/cressi_edy_parser.c index 3a0343f7..c79ce2d2 100644 --- a/src/cressi_edy_parser.c +++ b/src/cressi_edy_parser.c @@ -65,7 +65,7 @@ cressi_edy_parser_count_gasmixes (const unsigned char *data) // as the first gas marked as disabled is found. unsigned int i = 0; while (i < 3) { - if (data[0x17 - i] == 0xF0) + if ((data[0x17 - i] & 0xF0) == 0xF0) break; i++; } From a7696b691f8526b55aed4d78bb8da26b99b5308d Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 25 Mar 2025 19:43:47 +0100 Subject: [PATCH 22/52] Use a layout descriptor with a generic decode function Replace hardcoded constants with a layout descriptor. This reduces the amount of model specific conditions, and makes it easier to add support for new models. The Cressi data format uses nibbles (4 bits) as the smallest unit of storage. Larger values are stored as one or more nibble, typically with a BCD (binary coded decimal) encoding applied. To decode such variable sized numbers, a generic function is added. The offsets in the layout descriptor are also specified in nibbles instead of bytes. --- src/cressi_edy_parser.c | 93 +++++++++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 21 deletions(-) diff --git a/src/cressi_edy_parser.c b/src/cressi_edy_parser.c index c79ce2d2..ad241255 100644 --- a/src/cressi_edy_parser.c +++ b/src/cressi_edy_parser.c @@ -35,9 +35,22 @@ typedef struct cressi_edy_parser_t cressi_edy_parser_t; +typedef struct cressi_edy_layout_t { + unsigned int datetime_y; + unsigned int datetime_md; + unsigned int datetime_hm; + unsigned int avgdepth; + unsigned int maxdepth; + unsigned int temperature; + unsigned int divetime; + unsigned int gasmix; + unsigned int gasmix_count; +} cressi_edy_layout_t; + struct cressi_edy_parser_t { dc_parser_t base; unsigned int model; + const cressi_edy_layout_t *layout; }; static dc_status_t cressi_edy_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); @@ -56,16 +69,50 @@ static const dc_parser_vtable_t cressi_edy_parser_vtable = { NULL /* destroy */ }; +static const cressi_edy_layout_t edy = { + 8, /* datetime_y */ + 10, /* datetime_md */ + 28, /* datetime_hm */ + 1, /* avgdepth */ + 5, /* maxdepth */ + 22, /* temperature */ + 25, /* divetime */ + 46, 3, /* gasmix */ +}; + +static unsigned int +decode (const unsigned char data[], unsigned int offset, unsigned int n) +{ + unsigned int result = 0; + + for (unsigned int i = 0; i < n; ++i) { + unsigned char byte = data[offset / 2]; + + unsigned char nibble = 0; + if ((offset & 1) == 0) { + nibble = (byte >> 4) & 0x0F; + } else { + nibble = byte & 0x0F; + } + + result *= 10; + result += nibble; + offset++; + } + + return result; +} static unsigned int -cressi_edy_parser_count_gasmixes (const unsigned char *data) +cressi_edy_parser_count_gasmixes (const unsigned char data[], const cressi_edy_layout_t *layout) { // Count the number of active gas mixes. The active gas // mixes are always first, so we stop counting as soon // as the first gas marked as disabled is found. unsigned int i = 0; - while (i < 3) { - if ((data[0x17 - i] & 0xF0) == 0xF0) + while (i < layout->gasmix_count) { + unsigned int state = decode(data, layout->gasmix - i * 2, 1); + if (state == 0x0F) break; i++; } @@ -89,6 +136,7 @@ cressi_edy_parser_create (dc_parser_t **out, dc_context_t *context, const unsign // Set the default values. parser->model = model; + parser->layout = &edy; *out = (dc_parser_t*) parser; @@ -99,17 +147,19 @@ cressi_edy_parser_create (dc_parser_t **out, dc_context_t *context, const unsign static dc_status_t cressi_edy_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) { + cressi_edy_parser_t *parser = (cressi_edy_parser_t *) abstract; + const cressi_edy_layout_t *layout = parser->layout; + const unsigned char *data = abstract->data; + if (abstract->size < SZ_HEADER) return DC_STATUS_DATAFORMAT; - const unsigned char *p = abstract->data; - if (datetime) { - datetime->year = bcd2dec (p[4]) + 2000; - datetime->month = (p[5] & 0xF0) >> 4; - datetime->day = (p[5] & 0x0F) * 10 + ((p[6] & 0xF0) >> 4); - datetime->hour = bcd2dec (p[14]); - datetime->minute = bcd2dec (p[15]); + datetime->year = decode(data, layout->datetime_y, 2) + 2000; + datetime->month = decode(data, layout->datetime_md, 1); + datetime->day = decode(data, layout->datetime_md + 1, 2); + datetime->hour = decode(data, layout->datetime_hm, 2); + datetime->minute = decode(data, layout->datetime_hm + 2, 2); datetime->second = 0; datetime->timezone = DC_TIMEZONE_NONE; } @@ -122,39 +172,39 @@ static dc_status_t cressi_edy_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) { cressi_edy_parser_t *parser = (cressi_edy_parser_t *) abstract; + const cressi_edy_layout_t *layout = parser->layout; + const unsigned char *data = abstract->data; if (abstract->size < SZ_HEADER) return DC_STATUS_DATAFORMAT; - const unsigned char *p = abstract->data; - dc_gasmix_t *gasmix = (dc_gasmix_t *) value; if (value) { switch (type) { case DC_FIELD_DIVETIME: if (parser->model == EDY) - *((unsigned int *) value) = bcd2dec (p[0x0C] & 0x0F) * 60 + bcd2dec (p[0x0D]); + *((unsigned int *) value) = decode(data, layout->divetime, 1) * 60 + decode(data, layout->divetime + 1, 2); else - *((unsigned int *) value) = (bcd2dec (p[0x0C] & 0x0F) * 100 + bcd2dec (p[0x0D])) * 60; + *((unsigned int *) value) = decode(data, layout->divetime, 3) * 60; break; case DC_FIELD_MAXDEPTH: - *((double *) value) = (bcd2dec (p[0x02] & 0x0F) * 100 + bcd2dec (p[0x03])) / 10.0; + *((double *) value) = decode(data, layout->maxdepth, 3) / 10.0; break; case DC_FIELD_AVGDEPTH: - *((double *) value) = (bcd2dec (p[0x00] & 0x0F) * 100 + bcd2dec (p[0x01])) / 10.0; + *((double *) value) = decode(data, layout->avgdepth, 3) / 10.0; break; case DC_FIELD_GASMIX_COUNT: - *((unsigned int *) value) = cressi_edy_parser_count_gasmixes(p); + *((unsigned int *) value) = cressi_edy_parser_count_gasmixes(data, layout); break; case DC_FIELD_GASMIX: gasmix->usage = DC_USAGE_NONE; gasmix->helium = 0.0; - gasmix->oxygen = bcd2dec (p[0x17 - flags]) / 100.0; + gasmix->oxygen = decode(data, layout->gasmix - flags * 2, 2) / 100.0; gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; break; case DC_FIELD_TEMPERATURE_MINIMUM: - *((double *) value) = (bcd2dec (p[0x0B]) * 10 + ((p[0x0C] & 0xF0) >> 4)) / 10.0; + *((double *) value) = decode(data, layout->temperature, 3) / 10.0; break; default: return DC_STATUS_UNSUPPORTED; @@ -169,6 +219,7 @@ static dc_status_t cressi_edy_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) { cressi_edy_parser_t *parser = (cressi_edy_parser_t *) abstract; + const cressi_edy_layout_t *layout = parser->layout; const unsigned char *data = abstract->data; unsigned int size = abstract->size; @@ -182,7 +233,7 @@ cressi_edy_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t c interval = 15; } - unsigned int ngasmixes = cressi_edy_parser_count_gasmixes(data); + unsigned int ngasmixes = cressi_edy_parser_count_gasmixes(data, layout); unsigned int gasmix = 0xFFFFFFFF; unsigned int offset = SZ_HEADER; @@ -202,7 +253,7 @@ cressi_edy_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t c if (callback) callback (DC_SAMPLE_TIME, &sample, userdata); // Depth (1/10 m). - unsigned int depth = bcd2dec (data[offset + 0] & 0x0F) * 100 + bcd2dec (data[offset + 1]); + unsigned int depth = decode(data + offset, 1, 3); sample.depth = depth / 10.0; if (callback) callback (DC_SAMPLE_DEPTH, &sample, userdata); From c343432022f509cfe8dafd5524d0e2908cdacb7f Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 18 Mar 2025 20:28:26 +0100 Subject: [PATCH 23/52] Read the payload and trailer byte separately Reading the payload and the trailer byte separately simplifies the code in the caller because the destination buffer doesn't need space to store the extra trailer byte. --- src/cressi_edy.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/cressi_edy.c b/src/cressi_edy.c index c4e78c13..24ec9e42 100644 --- a/src/cressi_edy.c +++ b/src/cressi_edy.c @@ -141,9 +141,19 @@ cressi_edy_packet (cressi_edy_device_t *device, const unsigned char command[], u ERROR (abstract->context, "Failed to receive the answer."); return status; } + } + + if (trailer) { + // Receive the trailer byte. + unsigned char end = 0; + status = dc_iostream_read (device->iostream, &end, 1, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the answer."); + return status; + } // Verify the trailer of the packet. - if (trailer && answer[asize - 1] != 0x45) { + if (end != 0x45) { ERROR (abstract->context, "Unexpected answer trailer byte."); return DC_STATUS_PROTOCOL; } @@ -203,9 +213,8 @@ static dc_status_t cressi_edy_init3 (cressi_edy_device_t *device) { unsigned char command[1] = {0x0C}; - unsigned char answer[1] = {0}; - return cressi_edy_transfer (device, command, sizeof (command), answer, sizeof (answer), 1); + return cressi_edy_transfer (device, command, sizeof (command), NULL, 0, 1); } @@ -324,6 +333,7 @@ cressi_edy_device_close (dc_device_t *abstract) static dc_status_t cressi_edy_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size) { + dc_status_t status = DC_STATUS_SUCCESS; cressi_edy_device_t *device = (cressi_edy_device_t*) abstract; if ((address % SZ_PAGE != 0) || @@ -334,22 +344,18 @@ cressi_edy_device_read (dc_device_t *abstract, unsigned int address, unsigned ch while (nbytes < size) { // Read the package. unsigned int number = address / SZ_PAGE; - unsigned char answer[SZ_PACKET + 1] = {0}; unsigned char command[3] = {0x52, (number >> 8) & 0xFF, // high (number ) & 0xFF}; // low - dc_status_t rc = cressi_edy_transfer (device, command, sizeof (command), answer, sizeof (answer), 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - - memcpy (data, answer, SZ_PACKET); + status = cressi_edy_transfer (device, command, sizeof (command), data + nbytes, SZ_PACKET, 1); + if (status != DC_STATUS_SUCCESS) + return status; nbytes += SZ_PACKET; address += SZ_PACKET; - data += SZ_PACKET; } - return DC_STATUS_SUCCESS; + return status; } From 52600e7f58b9309791c2b72fc1cb238b027d123d Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 17 Mar 2025 20:45:09 +0100 Subject: [PATCH 24/52] Add support for the Cressi Archimede The Cressi Archimede protocol is very similar to the existing Edy protocol, with a few smaller differences: - No init3 command and baudrate change - Smaller packet size (32 instead of 128 bytes) - Modified read command with a 1 byte address The most curious change is the fact that the nibbles of all bytes are reversed (little endian). To simplify the processing of the data, the nibbles are swapped again immediately after downloading. --- src/array.c | 8 ++++++ src/array.h | 3 ++ src/cressi_edy.c | 64 +++++++++++++++++++++++++++-------------- src/cressi_edy_parser.c | 21 ++++++++++++-- src/descriptor.c | 1 + 5 files changed, 73 insertions(+), 24 deletions(-) diff --git a/src/array.c b/src/array.c index f47c3eb0..6d97557c 100644 --- a/src/array.c +++ b/src/array.c @@ -51,6 +51,14 @@ array_reverse_bits (unsigned char data[], unsigned int size) } } +void +array_reverse_nibbles (unsigned char data[], unsigned int size) +{ + for (unsigned int i = 0; i < size; ++i) { + unsigned char tmp = data[i]; + data[i] = ((tmp & 0xF0) >> 4) | ((tmp & 0x0F) << 4); + } +} int array_isequal (const unsigned char data[], unsigned int size, unsigned char value) diff --git a/src/array.h b/src/array.h index a27e9ef5..81268779 100644 --- a/src/array.h +++ b/src/array.h @@ -35,6 +35,9 @@ array_reverse_bytes (unsigned char data[], unsigned int size); void array_reverse_bits (unsigned char data[], unsigned int size); +void +array_reverse_nibbles (unsigned char data[], unsigned int size); + int array_isequal (const unsigned char data[], unsigned int size, unsigned char value); diff --git a/src/cressi_edy.c b/src/cressi_edy.c index 24ec9e42..16b51595 100644 --- a/src/cressi_edy.c +++ b/src/cressi_edy.c @@ -35,11 +35,11 @@ #define MAXRETRIES 4 -#define SZ_PACKET 0x80 -#define SZ_PAGE (SZ_PACKET / 4) +#define SZ_PAGE 32 #define SZ_HEADER 32 +#define ARCHIMEDE 0x01 #define IQ700 0x05 #define EDY 0x08 @@ -60,6 +60,7 @@ typedef struct cressi_edy_device_t { const cressi_edy_layout_t *layout; unsigned char fingerprint[SZ_PAGE / 2]; unsigned int model; + unsigned int packetsize; } cressi_edy_device_t; static dc_status_t cressi_edy_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); @@ -247,6 +248,7 @@ cressi_edy_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t device->iostream = iostream; device->layout = NULL; device->model = 0; + device->packetsize = 0; memset (device->fingerprint, 0, sizeof (device->fingerprint)); // Set the serial communication protocol (1200 8N1). @@ -284,19 +286,26 @@ cressi_edy_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t // Send the init commands. cressi_edy_init1 (device); cressi_edy_init2 (device); - cressi_edy_init3 (device); + if (device->model != ARCHIMEDE) { + cressi_edy_init3 (device); + } + + device->packetsize = device->model == ARCHIMEDE ? + SZ_PAGE : SZ_PAGE * 4; - if (device->model == IQ700) { + if (device->model == IQ700 || device->model == ARCHIMEDE) { device->layout = &tusa_iq700_layout; } else { device->layout = &cressi_edy_layout; } - // Set the serial communication protocol (4800 8N1). - status = dc_iostream_configure (device->iostream, 4800, 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; + if (device->model != ARCHIMEDE) { + // Set the serial communication protocol (4800 8N1). + status = dc_iostream_configure (device->iostream, 4800, 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; + } } // Make sure everything is in a sane state. @@ -337,22 +346,33 @@ cressi_edy_device_read (dc_device_t *abstract, unsigned int address, unsigned ch cressi_edy_device_t *device = (cressi_edy_device_t*) abstract; if ((address % SZ_PAGE != 0) || - (size % SZ_PACKET != 0)) + (size % device->packetsize != 0)) return DC_STATUS_INVALIDARGS; unsigned int nbytes = 0; while (nbytes < size) { // Read the package. unsigned int number = address / SZ_PAGE; - unsigned char command[3] = {0x52, - (number >> 8) & 0xFF, // high - (number ) & 0xFF}; // low - status = cressi_edy_transfer (device, command, sizeof (command), data + nbytes, SZ_PACKET, 1); + unsigned char command[3] = {0}; + if (device->model == ARCHIMEDE) { + command[0] = 0x45; + command[1] = 0x52; + command[2] = number & 0xFF; + } else { + command[0] = 0x52; + command[1] = (number >> 8) & 0xFF; // high; + command[2] = (number ) & 0xFF; // low + } + status = cressi_edy_transfer (device, command, sizeof (command), data + nbytes, device->packetsize, 1); if (status != DC_STATUS_SUCCESS) return status; - nbytes += SZ_PACKET; - address += SZ_PACKET; + nbytes += device->packetsize; + address += device->packetsize; + } + + if (device->model == ARCHIMEDE) { + array_reverse_nibbles (data, size); } return status; @@ -395,7 +415,7 @@ cressi_edy_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); return device_dump_read (abstract, 0, dc_buffer_get_data (buffer), - dc_buffer_get_size (buffer), SZ_PACKET); + dc_buffer_get_size (buffer), device->packetsize); } @@ -407,7 +427,7 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v // Enable progress notifications. dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; - progress.maximum = SZ_PACKET + + progress.maximum = 4 * SZ_PAGE + (layout->rb_profile_end - layout->rb_profile_begin); device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); @@ -419,7 +439,7 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); // Read the logbook data. - unsigned char logbook[SZ_PACKET] = {0}; + unsigned char logbook[4 * SZ_PAGE] = {0}; dc_status_t rc = cressi_edy_device_read (abstract, layout->rb_logbook_offset, logbook, sizeof (logbook)); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the logbook data."); @@ -481,13 +501,13 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v } // Update and emit a progress event. - progress.current += SZ_PACKET; - progress.maximum = SZ_PACKET + total; + progress.current += 4 * SZ_PAGE; + progress.maximum = 4 * SZ_PAGE + total; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); // Create the ringbuffer stream. dc_rbstream_t *rbstream = NULL; - rc = dc_rbstream_new (&rbstream, abstract, SZ_PAGE, SZ_PACKET, layout->rb_profile_begin, layout->rb_profile_end, eop, DC_RBSTREAM_BACKWARD); + rc = dc_rbstream_new (&rbstream, abstract, SZ_PAGE, device->packetsize, layout->rb_profile_begin, layout->rb_profile_end, eop, DC_RBSTREAM_BACKWARD); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to create the ringbuffer stream."); return rc; diff --git a/src/cressi_edy_parser.c b/src/cressi_edy_parser.c index ad241255..e45cfb1e 100644 --- a/src/cressi_edy_parser.c +++ b/src/cressi_edy_parser.c @@ -28,6 +28,7 @@ #define ISINSTANCE(parser) dc_parser_isinstance((parser), &cressi_edy_parser_vtable) +#define ARCHIMEDE 0x01 #define IQ700 0x05 #define EDY 0x08 @@ -80,6 +81,17 @@ static const cressi_edy_layout_t edy = { 46, 3, /* gasmix */ }; +static const cressi_edy_layout_t archimede = { + 2, /* datetime_y */ + 5, /* datetime_md */ + 25, /* datetime_hm */ + 22, /* avgdepth */ + 9, /* maxdepth */ + 45, /* temperature */ + 29, /* divetime */ + 43, 1, /* gasmix */ +}; + static unsigned int decode (const unsigned char data[], unsigned int offset, unsigned int n) { @@ -136,7 +148,12 @@ cressi_edy_parser_create (dc_parser_t **out, dc_context_t *context, const unsign // Set the default values. parser->model = model; - parser->layout = &edy; + + if (model == ARCHIMEDE) { + parser->layout = &archimede; + } else { + parser->layout = &edy; + } *out = (dc_parser_t*) parser; @@ -260,7 +277,7 @@ cressi_edy_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t c // Current gasmix if (ngasmixes) { unsigned int idx = (data[offset + 0] & 0x60) >> 5; - if (parser->model == IQ700) + if (parser->model == IQ700 || parser->model == ARCHIMEDE) idx = 0; /* FIXME */ if (idx >= ngasmixes) { ERROR (abstract->context, "Invalid gas mix index."); diff --git a/src/descriptor.c b/src/descriptor.c index cb8ba112..9dc7a977 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -339,6 +339,7 @@ static const dc_descriptor_t g_descriptors[] = { {"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}, /* 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}, {"Cressi", "Edy", DC_FAMILY_CRESSI_EDY, 0x08, DC_TRANSPORT_SERIAL, NULL}, /* Cressi Leonardo */ From 7b75b8fdf558b179bcd288a335dfccc7e22cb1d6 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 16 Apr 2025 19:46:42 +0200 Subject: [PATCH 25/52] Add support for time synchronization The Suunto d9/vyper2 protocol supports a command to synchronize the clock. --- src/suunto_common2.c | 29 +++++++++++++++++++++++++++++ src/suunto_common2.h | 3 +++ src/suunto_d9.c | 2 +- src/suunto_vyper2.c | 2 +- 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/suunto_common2.c b/src/suunto_common2.c index c26eb54e..f80bda84 100644 --- a/src/suunto_common2.c +++ b/src/suunto_common2.c @@ -397,3 +397,32 @@ suunto_common2_device_foreach (dc_device_t *abstract, dc_dive_callback_t callbac return status; } + +dc_status_t +suunto_common2_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) +{ + dc_status_t status = DC_STATUS_SUCCESS; + + unsigned char answer[5] = {0}; + unsigned char command[11] = {0x10, 0x00, 0x07, + (datetime->year >> 8) & 0xFF, + (datetime->year ) & 0xFF, + datetime->month, + datetime->day, + datetime->hour, + datetime->minute, + datetime->second, + 0}; // CRC + command[10] = checksum_xor_uint8 (command, 10, 0x00); + + status = suunto_common2_transfer (abstract, command, sizeof (command), answer, sizeof (answer), 1); + if (status != DC_STATUS_SUCCESS) + return status; + + if (answer[3] != 1) { + ERROR (abstract->context, "Unexpected response code (%u).", answer[3]); + return DC_STATUS_PROTOCOL; + } + + return status; +} diff --git a/src/suunto_common2.h b/src/suunto_common2.h index 5eff8d29..6359db53 100644 --- a/src/suunto_common2.h +++ b/src/suunto_common2.h @@ -73,6 +73,9 @@ suunto_common2_device_dump (dc_device_t *device, dc_buffer_t *buffer); dc_status_t suunto_common2_device_foreach (dc_device_t *device, dc_dive_callback_t callback, void *userdata); +dc_status_t +suunto_common2_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime); + dc_status_t suunto_common2_device_reset_maxdepth (dc_device_t *device); diff --git a/src/suunto_d9.c b/src/suunto_d9.c index 9622975a..8ee93924 100644 --- a/src/suunto_d9.c +++ b/src/suunto_d9.c @@ -56,7 +56,7 @@ static const suunto_common2_device_vtable_t suunto_d9_device_vtable = { suunto_common2_device_write, /* write */ suunto_common2_device_dump, /* dump */ suunto_common2_device_foreach, /* foreach */ - NULL, /* timesync */ + suunto_common2_device_timesync, /* timesync */ NULL /* close */ }, suunto_d9_device_packet diff --git a/src/suunto_vyper2.c b/src/suunto_vyper2.c index 7a9f7ce7..d6784eb8 100644 --- a/src/suunto_vyper2.c +++ b/src/suunto_vyper2.c @@ -51,7 +51,7 @@ static const suunto_common2_device_vtable_t suunto_vyper2_device_vtable = { suunto_common2_device_write, /* write */ suunto_common2_device_dump, /* dump */ suunto_common2_device_foreach, /* foreach */ - NULL, /* timesync */ + suunto_common2_device_timesync, /* timesync */ suunto_vyper2_device_close /* close */ }, suunto_vyper2_device_packet From 893934e52af7ea8c6e0658c7683b02ef30301382 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 24 Mar 2025 19:03:16 +0100 Subject: [PATCH 26/52] Add extra debug information to the log --- src/atomics_cobalt.c | 2 ++ src/cressi_edy.c | 2 ++ src/cressi_goa.c | 2 ++ src/deepblu_cosmiq.c | 4 ++++ src/deepsix_excursion.c | 6 ++++++ src/diverite_nitekq.c | 2 ++ src/divesoft_freedom.c | 4 ++++ src/divesystem_idive.c | 2 ++ src/halcyon_symbios.c | 4 ++++ src/hw_frog.c | 2 ++ src/hw_ostc3.c | 4 ++++ src/liquivision_lynx.c | 4 ++++ src/mares_iconhd.c | 6 ++++++ src/mclean_extreme.c | 6 ++++++ src/oceanic_atom2.c | 2 ++ src/oceanic_veo250.c | 2 ++ src/oceanic_vtpro.c | 2 ++ src/reefnet_sensus.c | 2 ++ src/reefnet_sensuspro.c | 2 ++ src/reefnet_sensusultra.c | 2 ++ src/seac_screen.c | 4 ++++ src/shearwater_petrel.c | 8 ++++++++ src/sporasub_sp2.c | 2 ++ src/suunto_d9.c | 2 ++ src/suunto_vyper2.c | 2 ++ src/tecdiving_divecomputereu.c | 2 ++ src/uwatec_smart.c | 10 ++++++++++ 27 files changed, 92 insertions(+) diff --git a/src/atomics_cobalt.c b/src/atomics_cobalt.c index 8c386965..e9fdfd8b 100644 --- a/src/atomics_cobalt.c +++ b/src/atomics_cobalt.c @@ -105,6 +105,8 @@ atomics_cobalt_device_open (dc_device_t **out, dc_context_t *context, dc_iostrea goto error_free; } + HEXDUMP (context, DC_LOGLEVEL_DEBUG, "Version", device->version, sizeof (device->version)); + *out = (dc_device_t*) device; return DC_STATUS_SUCCESS; diff --git a/src/cressi_edy.c b/src/cressi_edy.c index 16b51595..6845a0ff 100644 --- a/src/cressi_edy.c +++ b/src/cressi_edy.c @@ -204,6 +204,8 @@ cressi_edy_init2 (cressi_edy_device_t *device) if (rc != DC_STATUS_SUCCESS) return rc; + HEXDUMP (device->base.context, DC_LOGLEVEL_DEBUG, "Model", answer, sizeof (answer)); + device->model = answer[0]; return DC_STATUS_SUCCESS; diff --git a/src/cressi_goa.c b/src/cressi_goa.c index 2b977767..6156184f 100644 --- a/src/cressi_goa.c +++ b/src/cressi_goa.c @@ -522,6 +522,8 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v const unsigned char *id_data = dc_buffer_get_data(id); size_t id_size = dc_buffer_get_size(id); + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Version", id_data, id_size); + if (id_size < 9) { ERROR (abstract->context, "Unexpected version length (" DC_PRINTF_SIZE ").", id_size); status = DC_STATUS_DATAFORMAT; diff --git a/src/deepblu_cosmiq.c b/src/deepblu_cosmiq.c index 54548423..78c91ce6 100644 --- a/src/deepblu_cosmiq.c +++ b/src/deepblu_cosmiq.c @@ -391,6 +391,8 @@ deepblu_cosmiq_device_foreach (dc_device_t *abstract, dc_dive_callback_t callbac goto error_exit; } + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Firmware", fw, sizeof(fw)); + // Read the MAC address. unsigned char mac[6] = {0}; status = deepblu_cosmiq_transfer (device, CMD_SYSTEM_MAC, &zero, 1, mac, sizeof(mac)); @@ -399,6 +401,8 @@ deepblu_cosmiq_device_foreach (dc_device_t *abstract, dc_dive_callback_t callbac goto error_exit; } + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Serial", mac, sizeof(mac)); + // Emit a device info event. dc_event_devinfo_t devinfo; devinfo.model = 0; diff --git a/src/deepsix_excursion.c b/src/deepsix_excursion.c index de0bb649..1ed78677 100644 --- a/src/deepsix_excursion.c +++ b/src/deepsix_excursion.c @@ -283,6 +283,8 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call return status; } + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Hardware", rsp_hardware, sizeof(rsp_hardware)); + // Read the software version. unsigned char rsp_software[6] = {0}; status = deepsix_excursion_transfer (device, GRP_INFO, CMD_INFO_SOFTWARE, DIR_READ, NULL, 0, rsp_software, sizeof(rsp_software), NULL); @@ -291,6 +293,8 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call return status; } + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Software", rsp_software, sizeof(rsp_software)); + // Read the serial number unsigned char rsp_serial[12] = {0}; status = deepsix_excursion_transfer (device, GRP_INFO, CMD_INFO_SERIAL, DIR_READ, NULL, 0, rsp_serial, sizeof(rsp_serial), NULL); @@ -299,6 +303,8 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call return status; } + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Serial", rsp_serial, sizeof(rsp_serial)); + // Emit a device info event. dc_event_devinfo_t devinfo; devinfo.model = 0; diff --git a/src/diverite_nitekq.c b/src/diverite_nitekq.c index 676fb90e..aee0784f 100644 --- a/src/diverite_nitekq.c +++ b/src/diverite_nitekq.c @@ -191,6 +191,8 @@ diverite_nitekq_device_open (dc_device_t **out, dc_context_t *context, dc_iostre goto error_free; } + HEXDUMP (context, DC_LOGLEVEL_DEBUG, "Version", device->version, sizeof (device->version)); + *out = (dc_device_t*) device; return DC_STATUS_SUCCESS; diff --git a/src/divesoft_freedom.c b/src/divesoft_freedom.c index 3d4c7c55..80117769 100644 --- a/src/divesoft_freedom.c +++ b/src/divesoft_freedom.c @@ -344,6 +344,8 @@ divesoft_freedom_device_open (dc_device_t **out, dc_context_t *context, dc_iostr goto error_free_hdlc; } + HEXDUMP (context, DC_LOGLEVEL_DEBUG, "Connection", rsp_connect, sizeof(rsp_connect)); + DEBUG (context, "Connection: compression=%u, protocol=%u.%u, serial=%.16s", array_uint16_le (rsp_connect), rsp_connect[2], rsp_connect[3], @@ -402,6 +404,8 @@ divesoft_freedom_device_foreach (dc_device_t *abstract, dc_dive_callback_t callb goto error_exit; } + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Version", rsp_version, sizeof(rsp_version)); + DEBUG (abstract->context, "Device: model=%u, hw=%u.%u, sw=%u.%u.%u.%u serial=%.16s", rsp_version[0], rsp_version[1], rsp_version[2], diff --git a/src/divesystem_idive.c b/src/divesystem_idive.c index 9841a6f9..ece18e1d 100644 --- a/src/divesystem_idive.c +++ b/src/divesystem_idive.c @@ -453,6 +453,8 @@ divesystem_idive_device_foreach (dc_device_t *abstract, dc_dive_callback_t callb if (rc != DC_STATUS_SUCCESS) return rc; + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Version", packet, commands->id.size); + // Emit a device info event. dc_event_devinfo_t devinfo; devinfo.model = array_uint16_le (packet); diff --git a/src/halcyon_symbios.c b/src/halcyon_symbios.c index a3ed05cd..1ecdb992 100644 --- a/src/halcyon_symbios.c +++ b/src/halcyon_symbios.c @@ -445,6 +445,8 @@ halcyon_symbios_device_foreach (dc_device_t *abstract, dc_dive_callback_t callba goto error_exit; } + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Version", info, sizeof(info)); + // Emit a vendor event. dc_event_vendor_t vendor; vendor.data = info; @@ -494,6 +496,8 @@ halcyon_symbios_device_foreach (dc_device_t *abstract, dc_dive_callback_t callba const unsigned char *data = dc_buffer_get_data (logbook); size_t size = dc_buffer_get_size (logbook); + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Logbook", data, size); + // Get the number of dives. unsigned int ndives = 0; unsigned int offset = size; diff --git a/src/hw_frog.c b/src/hw_frog.c index 2de4ebac..dcd9dec3 100644 --- a/src/hw_frog.c +++ b/src/hw_frog.c @@ -326,6 +326,8 @@ hw_frog_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void return rc; } + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Version", id, sizeof (id)); + // Emit a device info event. dc_event_devinfo_t devinfo; devinfo.model = 0; diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 4ec278d9..1b2fca54 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -605,6 +605,8 @@ hw_ostc3_device_init (hw_ostc3_device_t *device, hw_ostc3_state_t state) return rc; } + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Hardware", hardware, sizeof(hardware)); + // Read the version information. unsigned char version[SZ_VERSION] = {0}; rc = hw_ostc3_transfer (device, NULL, IDENTITY, NULL, 0, version, sizeof(version), NULL, NODELAY); @@ -613,6 +615,8 @@ hw_ostc3_device_init (hw_ostc3_device_t *device, hw_ostc3_state_t state) return rc; } + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Version", version, sizeof(version)); + // Cache the descriptor. device->hardware = array_uint16_be(hardware + 0); device->feature = array_uint16_be(hardware + 2); diff --git a/src/liquivision_lynx.c b/src/liquivision_lynx.c index 3ce87438..74916583 100644 --- a/src/liquivision_lynx.c +++ b/src/liquivision_lynx.c @@ -284,6 +284,8 @@ liquivision_lynx_device_open (dc_device_t **out, dc_context_t *context, dc_iostr goto error_free; } + HEXDUMP (context, DC_LOGLEVEL_DEBUG, "Info", device->info, sizeof(device->info)); + // Send the more info command. const unsigned char cmd_more[] = {0x4D, 0x4F, 0x52, 0x45, 0x49, 0x4E, 0x46, 0x4F, 0x4D, 0x4F, 0x52, 0x45}; status = liquivision_lynx_transfer (device, cmd_more, sizeof(cmd_more), device->more, sizeof(device->more)); @@ -292,6 +294,8 @@ liquivision_lynx_device_open (dc_device_t **out, dc_context_t *context, dc_iostr goto error_free; } + HEXDUMP (context, DC_LOGLEVEL_DEBUG, "More", device->more, sizeof(device->more)); + *out = (dc_device_t *) device; return DC_STATUS_SUCCESS; diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index 09084f3d..f9986f1a 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -616,6 +616,8 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_ goto error_free_iostream; } + HEXDUMP (context, DC_LOGLEVEL_DEBUG, "Version", device->version, sizeof (device->version)); + // Autodetect the model using the version packet. device->model = mares_iconhd_get_model (device); @@ -1035,6 +1037,8 @@ mares_iconhd_device_foreach_object (dc_device_t *abstract, dc_dive_callback_t ca return rc; } + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Model", dc_buffer_get_data (buffer), dc_buffer_get_size (buffer)); + if (dc_buffer_get_size (buffer) < 4) { ERROR (abstract->context, "Unexpected number of bytes received (" DC_PRINTF_SIZE ").", dc_buffer_get_size (buffer)); @@ -1055,6 +1059,8 @@ mares_iconhd_device_foreach_object (dc_device_t *abstract, dc_dive_callback_t ca return rc; } + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Serial", dc_buffer_get_data (buffer), dc_buffer_get_size (buffer)); + if (dc_buffer_get_size (buffer) < 16) { ERROR (abstract->context, "Unexpected number of bytes received (" DC_PRINTF_SIZE ").", dc_buffer_get_size (buffer)); diff --git a/src/mclean_extreme.c b/src/mclean_extreme.c index 2ef1a879..8dbe7c23 100644 --- a/src/mclean_extreme.c +++ b/src/mclean_extreme.c @@ -541,6 +541,8 @@ mclean_extreme_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback return status; } + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Firmware", firmware, sizeof(firmware)); + // Read the serial number. size_t serial_len = 0; unsigned char serial[SZ_PACKET] = {0}; @@ -550,6 +552,8 @@ mclean_extreme_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback return status; } + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Serial", serial, serial_len); + // Emit a device info event. dc_event_devinfo_t devinfo; devinfo.model = 0; @@ -565,6 +569,8 @@ mclean_extreme_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback return status; } + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Config", computer, sizeof(computer)); + // Verify the format version. unsigned int format = computer[0x0000]; if (format != 0) { diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index 82b68ffa..d9214bc8 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -948,6 +948,8 @@ oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream goto error_free; } + HEXDUMP (context, DC_LOGLEVEL_DEBUG, "Version", device->base.version, sizeof (device->base.version)); + if (dc_iostream_get_transport (device->iostream) == DC_TRANSPORT_BLE) { status = oceanic_atom2_ble_handshake(device); if (status != DC_STATUS_SUCCESS) { diff --git a/src/oceanic_veo250.c b/src/oceanic_veo250.c index f39b1cdb..58ba0c13 100644 --- a/src/oceanic_veo250.c +++ b/src/oceanic_veo250.c @@ -318,6 +318,8 @@ oceanic_veo250_device_open (dc_device_t **out, dc_context_t *context, dc_iostrea goto error_free; } + HEXDUMP (context, DC_LOGLEVEL_DEBUG, "Version", device->base.version, sizeof (device->base.version)); + // Detect the memory layout. const oceanic_common_version_t *version = OCEANIC_COMMON_MATCH(device->base.version, versions, &device->base.firmware); if (version == NULL) { diff --git a/src/oceanic_vtpro.c b/src/oceanic_vtpro.c index b63c0fb4..14c25656 100644 --- a/src/oceanic_vtpro.c +++ b/src/oceanic_vtpro.c @@ -526,6 +526,8 @@ oceanic_vtpro_device_open (dc_device_t **out, dc_context_t *context, dc_iostream goto error_free; } + HEXDUMP (context, DC_LOGLEVEL_DEBUG, "Version", device->base.version, sizeof (device->base.version)); + // Calibrate the device. Although calibration is optional, it's highly // recommended because it reduces the transfer time considerably, even // when processing the command itself is quite slow. diff --git a/src/reefnet_sensus.c b/src/reefnet_sensus.c index 675a4cb5..0e0345c5 100644 --- a/src/reefnet_sensus.c +++ b/src/reefnet_sensus.c @@ -219,6 +219,8 @@ reefnet_sensus_handshake (reefnet_sensus_device_t *device) return DC_STATUS_PROTOCOL; } + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Handshake", handshake + 2, sizeof(handshake) - 2); + // The device is now waiting for a data request. device->waiting = 1; diff --git a/src/reefnet_sensuspro.c b/src/reefnet_sensuspro.c index 27c2fdbc..4ba4b20c 100644 --- a/src/reefnet_sensuspro.c +++ b/src/reefnet_sensuspro.c @@ -183,6 +183,8 @@ reefnet_sensuspro_handshake (reefnet_sensuspro_device_t *device) return DC_STATUS_PROTOCOL; } + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Handshake", handshake, SZ_HANDSHAKE); + // Store the clock calibration values. device->systime = dc_datetime_now (); device->devtime = array_uint32_le (handshake + 6); diff --git a/src/reefnet_sensusultra.c b/src/reefnet_sensusultra.c index bfe1c6a4..a29d853c 100644 --- a/src/reefnet_sensusultra.c +++ b/src/reefnet_sensusultra.c @@ -243,6 +243,8 @@ reefnet_sensusultra_handshake (reefnet_sensusultra_device_t *device, unsigned sh if (rc != DC_STATUS_SUCCESS) return rc; + HEXDUMP (device->base.context, DC_LOGLEVEL_DEBUG, "Handshake", handshake, sizeof(handshake) - 2); + // Store the clock calibration values. device->systime = dc_datetime_now (); device->devtime = array_uint32_le (handshake + 4); diff --git a/src/seac_screen.c b/src/seac_screen.c index 3015fd65..92af8f25 100644 --- a/src/seac_screen.c +++ b/src/seac_screen.c @@ -282,6 +282,8 @@ seac_screen_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t goto error_free; } + HEXDUMP (context, DC_LOGLEVEL_DEBUG, "Hardware", device->info, SZ_HWINFO); + // Read the software info. status = seac_screen_transfer (device, CMD_SWINFO, NULL, 0, device->info + SZ_HWINFO, SZ_SWINFO); if (status != DC_STATUS_SUCCESS) { @@ -289,6 +291,8 @@ seac_screen_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t goto error_free; } + HEXDUMP (context, DC_LOGLEVEL_DEBUG, "Software", device->info + SZ_HWINFO, SZ_SWINFO); + *out = (dc_device_t *) device; return DC_STATUS_SUCCESS; diff --git a/src/shearwater_petrel.c b/src/shearwater_petrel.c index 7ec18ff8..fc4a69b3 100644 --- a/src/shearwater_petrel.c +++ b/src/shearwater_petrel.c @@ -166,6 +166,8 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call return rc; } + HEXDUMP(abstract->context, DC_LOGLEVEL_DEBUG, "Serial", rsp_serial, sizeof(rsp_serial)); + // Convert to a number. unsigned char serial[4] = {0}; if (array_convert_hex2bin (rsp_serial, sizeof(rsp_serial), serial, sizeof (serial)) != 0 ) { @@ -181,6 +183,8 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call return rc; } + HEXDUMP(abstract->context, DC_LOGLEVEL_DEBUG, "Firmware", rsp_firmware, sizeof(rsp_firmware)); + // Convert to a number. unsigned int firmware = str2num (rsp_firmware, sizeof(rsp_firmware), 1); @@ -192,6 +196,8 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call return rc; } + HEXDUMP(abstract->context, DC_LOGLEVEL_DEBUG, "Hardware", rsp_hardware, sizeof(rsp_hardware)); + // Convert and map to the model number. unsigned int hardware = array_uint16_be (rsp_hardware); unsigned int model = shearwater_common_get_model (&device->base, hardware); @@ -256,6 +262,8 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call return rc; } + HEXDUMP(abstract->context, DC_LOGLEVEL_DEBUG, "Manifest", dc_buffer_get_data (buffer), dc_buffer_get_size (buffer)); + // Cache the buffer pointer and size. unsigned char *data = dc_buffer_get_data (buffer); unsigned int size = dc_buffer_get_size (buffer); diff --git a/src/sporasub_sp2.c b/src/sporasub_sp2.c index 8be2e8a5..e34966f6 100644 --- a/src/sporasub_sp2.c +++ b/src/sporasub_sp2.c @@ -282,6 +282,8 @@ sporasub_sp2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_ goto error_free; } + HEXDUMP (context, DC_LOGLEVEL_DEBUG, "Version", device->version, sizeof(device)->version); + *out = (dc_device_t *) device; return DC_STATUS_SUCCESS; diff --git a/src/suunto_d9.c b/src/suunto_d9.c index 8ee93924..d2b29c4e 100644 --- a/src/suunto_d9.c +++ b/src/suunto_d9.c @@ -180,6 +180,8 @@ suunto_d9_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t * goto error_free; } + HEXDUMP (context, DC_LOGLEVEL_DEBUG, "Version", device->base.version, sizeof (device->base.version)); + // Override the base class values. model = device->base.version[0]; if (model == D4i || model == D6i || model == D9tx || diff --git a/src/suunto_vyper2.c b/src/suunto_vyper2.c index d6784eb8..2c1b6ef3 100644 --- a/src/suunto_vyper2.c +++ b/src/suunto_vyper2.c @@ -141,6 +141,8 @@ suunto_vyper2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream goto error_timer_free; } + HEXDUMP (context, DC_LOGLEVEL_DEBUG, "Version", device->base.version, sizeof (device->base.version)); + // Override the base class values. unsigned int model = device->base.version[0]; if (model == HELO2) diff --git a/src/tecdiving_divecomputereu.c b/src/tecdiving_divecomputereu.c index 93190e29..a430e0f2 100644 --- a/src/tecdiving_divecomputereu.c +++ b/src/tecdiving_divecomputereu.c @@ -394,6 +394,8 @@ tecdiving_divecomputereu_device_open (dc_device_t **out, dc_context_t *context, goto error_free; } + HEXDUMP (context, DC_LOGLEVEL_DEBUG, "Version", device->version, sizeof(device->version)); + *out = (dc_device_t *) device; return DC_STATUS_SUCCESS; diff --git a/src/uwatec_smart.c b/src/uwatec_smart.c index 589bbdb6..daae0ded 100644 --- a/src/uwatec_smart.c +++ b/src/uwatec_smart.c @@ -570,30 +570,40 @@ uwatec_smart_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) if (rc != DC_STATUS_SUCCESS) return rc; + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Model", model, sizeof (model)); + // Read the hardware version. unsigned char hardware[1] = {0}; rc = uwatec_smart_transfer (device, CMD_HARDWARE, NULL, 0, hardware, sizeof (hardware)); if (rc != DC_STATUS_SUCCESS) return rc; + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Hardware", hardware, sizeof (hardware)); + // Read the software version. unsigned char software[1] = {0}; rc = uwatec_smart_transfer (device, CMD_SOFTWARE, NULL, 0, software, sizeof (software)); if (rc != DC_STATUS_SUCCESS) return rc; + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Software", software, sizeof (software)); + // Read the serial number. unsigned char serial[4] = {0}; rc = uwatec_smart_transfer (device, CMD_SERIAL, NULL, 0, serial, sizeof (serial)); if (rc != DC_STATUS_SUCCESS) return rc; + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Serial", serial, sizeof (serial)); + // Read the device clock. unsigned char devtime[4] = {0}; rc = uwatec_smart_transfer (device, CMD_DEVTIME, NULL, 0, devtime, sizeof (devtime)); if (rc != DC_STATUS_SUCCESS) return rc; + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Clock", devtime, sizeof (devtime)); + // Store the clock calibration values. device->systime = dc_datetime_now (); device->devtime = array_uint32_le (devtime); From 45fcb6826a7579be15b8c08661f6bee7e919ab7d Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sat, 3 May 2025 00:12:28 +0200 Subject: [PATCH 27/52] Add support for a new Sirius header version The Mares Sirius firmare upgrade to v1.6.16 also changed the header version to v2.0. Again, there appear to be no functional changes in the data format. See also commit 82712ce8f83b2448d01ad26a949a82c27452ba5d. --- src/mares_iconhd_parser.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index dc25a4db..f26d010b 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -502,7 +502,7 @@ mares_genius_cache (mares_iconhd_parser_t *parser) unsigned int type = array_uint16_le (data); unsigned int minor = data[2]; unsigned int major = data[3]; - if (type != 1 || OBJVERSION(major,minor) > OBJVERSION(1,1)) { + if (type != 1 || OBJVERSION(major,minor) > OBJVERSION(2,0)) { ERROR (abstract->context, "Unsupported object type (%u) or version (%u.%u).", type, major, minor); return DC_STATUS_DATAFORMAT; @@ -521,7 +521,7 @@ mares_genius_cache (mares_iconhd_parser_t *parser) // The Genius header (v1.x) has 10 bytes more at the end. unsigned int more = 0; - if (major == 1) { + if (major >= 1) { more = 16; } From dc9f0878dc4d7f851d20527ef2d1041292eb090e Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 24 Apr 2025 18:14:35 +0200 Subject: [PATCH 28/52] Fix the name of the include guard --- src/halcyon_symbios.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/halcyon_symbios.h b/src/halcyon_symbios.h index d49770b3..84a2c962 100644 --- a/src/halcyon_symbios.h +++ b/src/halcyon_symbios.h @@ -19,8 +19,8 @@ * MA 02110-1301 USA */ -#ifndef OXYGENSCIENTIFIC_RNO_H -#define OXYGENSCIENTIFIC_RNO_H +#ifndef HALCYON_SYMBIOS_H +#define HALCYON_SYMBIOS_H #include #include @@ -40,4 +40,4 @@ halcyon_symbios_parser_create (dc_parser_t **parser, dc_context_t *context, cons #ifdef __cplusplus } #endif /* __cplusplus */ -#endif /* OXYGENSCIENTIFIC_RNO_H */ +#endif /* HALCYON_SYMBIOS_H */ From 524d7f48c89ca9d2d1784d7935cfae85864bd909 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 28 Apr 2025 22:06:16 +0200 Subject: [PATCH 29/52] Fix the ppO2 scaling factor The ppO2 values are stored with a unit of 0.01 bar and not 0.1 bar. --- src/halcyon_symbios_parser.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/halcyon_symbios_parser.c b/src/halcyon_symbios_parser.c index 2982ce96..a06b33bb 100644 --- a/src/halcyon_symbios_parser.c +++ b/src/halcyon_symbios_parser.c @@ -500,7 +500,7 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac for (unsigned int i = 0; i < 3; ++i) { unsigned int ppo2 = data[offset + 2 + i]; sample.ppo2.sensor = i; - sample.ppo2.value = ppo2 / 10.0; + sample.ppo2.value = ppo2 / 100.0; if (callback) callback(DC_SAMPLE_PPO2, &sample, userdata); } } else if (type == ID_DECO) { @@ -545,7 +545,7 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac for (unsigned int i = 0; i < 3; ++i) { unsigned int ppo2 = data[offset + 2 + i]; sample.ppo2.sensor = i; - sample.ppo2.value = ppo2 / 10.0; + sample.ppo2.value = ppo2 / 100.0; if (callback) callback(DC_SAMPLE_PPO2, &sample, userdata); } unsigned int pressure = array_uint16_le (data + offset + 8); From 4a775177b82be7229b28fa1c5fa55282bab7855f Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 29 Apr 2025 16:25:11 +0200 Subject: [PATCH 30/52] Link the tank to the corresponding gas mix The ID_GAS_TRANSMITTER record reports a tank pressure from a transmitter linked to a gas mix. Use this information to link the tank to the corresponding gas mix. --- src/halcyon_symbios_parser.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/halcyon_symbios_parser.c b/src/halcyon_symbios_parser.c index a06b33bb..dcbcb0da 100644 --- a/src/halcyon_symbios_parser.c +++ b/src/halcyon_symbios_parser.c @@ -75,6 +75,7 @@ typedef struct halcyon_symbios_tank_t { unsigned int id; unsigned int beginpressure; unsigned int endpressure; + unsigned int gasmix; dc_usage_t usage; } halcyon_symbios_tank_t; @@ -150,6 +151,7 @@ halcyon_symbios_parser_create (dc_parser_t **out, dc_context_t *context, const u parser->tank[i].id = 0; parser->tank[i].beginpressure = 0; parser->tank[i].endpressure = 0; + parser->tank[i].gasmix = DC_GASMIX_UNKNOWN; parser->tank[i].usage = DC_USAGE_NONE; } @@ -251,8 +253,8 @@ halcyon_symbios_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, u tank->workpressure = 0.0; tank->beginpressure = parser->tank[flags].beginpressure / 10.0; tank->endpressure = parser->tank[flags].endpressure / 10.0; + tank->gasmix = parser->tank[flags].gasmix; tank->usage = parser->tank[flags].usage; - tank->gasmix = DC_GASMIX_UNKNOWN; break; case DC_FIELD_DECOMODEL: if (parser->gf_lo == UNDEFINED || parser->gf_hi == UNDEFINED) @@ -318,6 +320,7 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac halcyon_symbios_gasmix_t gasmix[NGASMIXES] = {0}; halcyon_symbios_tank_t tank[NTANKS] = {0}; unsigned int gasmix_id_previous = UNDEFINED; + unsigned int gasmix_idx = DC_GASMIX_UNKNOWN; unsigned int tank_id_previous = UNDEFINED; unsigned int tank_usage_previous = UNDEFINED; unsigned int tank_idx = UNDEFINED; @@ -447,6 +450,7 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac sample.gasmix = idx; if (callback) callback(DC_SAMPLE_GASMIX, &sample, userdata); gasmix_id_previous = gas_id; + gasmix_idx = idx; } if (tank_id_previous != transmitter || @@ -469,6 +473,7 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac tank[ntanks].id = transmitter; tank[ntanks].beginpressure = pressure; tank[ntanks].endpressure = pressure; + tank[ntanks].gasmix = gasmix_idx; tank[ntanks].usage = usage; ntanks++; } @@ -572,6 +577,7 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac tank[ntanks].id = serial; tank[ntanks].beginpressure = pressure; tank[ntanks].endpressure = pressure; + tank[ntanks].gasmix = DC_GASMIX_UNKNOWN; tank[ntanks].usage = usage; ntanks++; } From e7d62614309e0b4d6601e0fee50bdbd33760033b Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 28 Apr 2025 22:12:23 +0200 Subject: [PATCH 31/52] Add support for new record types Log format version 1.6 introduces some new record types. The ID_TANK_TRANSMITTER record reports a tank pressure from a transmitter not linked to a gas mix. Unlike the previous ID_GAS_TRANSMITTER record, the tank id is no longer the transmitter serial number, but a transmitter index (0-3). To avoid conflicts and being able to distinguish the two types, one of the higher bit is set internally. --- src/halcyon_symbios_parser.c | 49 ++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/halcyon_symbios_parser.c b/src/halcyon_symbios_parser.c index dcbcb0da..f9cce257 100644 --- a/src/halcyon_symbios_parser.c +++ b/src/halcyon_symbios_parser.c @@ -47,6 +47,8 @@ #define ID_LOG_VERSION 0x0F #define ID_TRIM 0x10 #define ID_GAS_CONFIG 0x11 +#define ID_TANK_TRANSMITTER 0x12 +#define ID_GF_INFO 0x13 #define ISCONFIG(type) ( \ (type) == ID_LOG_VERSION || \ @@ -65,6 +67,8 @@ #define NGASMIXES 10 #define NTANKS 10 +#define TRANSMITTER_ID (1u << 16) + typedef struct halcyon_symbios_gasmix_t { unsigned int id; unsigned int oxygen; @@ -305,6 +309,8 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac 4, /* ID_LOG_VERSION */ 4, /* ID_TRIM */ 8, /* ID_GAS_CONFIG */ + 8, /* ID_TANK_TRANSMITTER */ + 6, /* ID_GF_INFO */ }; unsigned int time_start = UNDEFINED, time_end = UNDEFINED; @@ -632,6 +638,49 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac if (callback) callback(DC_SAMPLE_GASMIX, &sample, userdata); } } + } else if (type == ID_TANK_TRANSMITTER) { + unsigned int id = data[offset + 2] | TRANSMITTER_ID; + unsigned int DC_ATTR_UNUSED battery = array_uint16_le (data + offset + 4); + unsigned int pressure = array_uint16_le (data + offset + 6) / 10; + dc_usage_t usage = DC_USAGE_NONE; + + if (tank_id_previous != id || + tank_usage_previous != usage) { + // Find the tank in the list. + unsigned int idx = 0; + while (idx < ntanks) { + if (tank[idx].id == id && + tank[idx].usage == usage) + break; + idx++; + } + + // Add a new tank if necessary. + if (idx >= ntanks) { + if (ngasmixes >= NTANKS) { + ERROR (abstract->context, "Maximum number of tanks reached."); + return DC_STATUS_NOMEMORY; + } + tank[ntanks].id = id; + tank[ntanks].beginpressure = pressure; + tank[ntanks].endpressure = pressure; + tank[ntanks].gasmix = DC_GASMIX_UNKNOWN; + tank[ntanks].usage = usage; + ntanks++; + } + + tank_id_previous = id; + tank_usage_previous = usage; + tank_idx = idx; + } + tank[tank_idx].endpressure = pressure; + + 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) { + 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); } else { WARNING (abstract->context, "Unknown record (type=%u, size=%u", type, length); } From b3d20fd74e2275d915d01c76ee121f208cae04dd Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 4 May 2025 10:12:08 +0200 Subject: [PATCH 32/52] Update the filter function Halcyon changed the bluetooth device name to the full serial number without a prefix. The serial number uses the format YYMMPCNNNN (e.g. 2412070123) with: - YY = Production year - MM = Production month - PC = Product code (01 = HUD, 07 = handset) - NNNN = Continuous number The existing filter with the prefix is kept for backwards compatibility. --- src/descriptor.c | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index 9dc7a977..a23cc54f 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -605,6 +605,35 @@ dc_match_cressi (const void *key, const void *value) return dc_match_hex_with_prefix (key, &p); } +static int +dc_match_halcyon (const void *key, const void *value) +{ + const char *str = (const char *) key; + unsigned int model = *(const unsigned int *) value; + + char prefix[16] = {0}; + dc_platform_snprintf(prefix, sizeof(prefix), "H%02u", model); + + if (strncasecmp (str, prefix, 3) == 0) { + return 1; + } + + size_t n = 0; + while (str[n] != 0) { + const char c = str[n]; + if (c < '0' || c > '9') { + return 0; + } + n++; + } + + if (n < 10) { + return 0; + } + + return strncasecmp (str + 4, prefix + 1, 2) == 0; +} + static int dc_filter_internal (const void *key, const void *values, size_t count, size_t size, dc_match_t match) { @@ -916,13 +945,13 @@ dc_filter_cressi (const dc_descriptor_t *descriptor, dc_transport_t transport, c static int dc_filter_halcyon (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) { - static const char * const bluetooth[] = { - "H01", // Symbios HUD - "H07", // Symbios Handset + static const unsigned int model[] = { + 1, // Symbios HUD + 7, // Symbios Handset }; if (transport == DC_TRANSPORT_BLE) { - return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_prefix); + return DC_FILTER_INTERNAL (userdata, model, 0, dc_match_halcyon); } return 1; From edab044d21fbf40918f7f296f5da3cf22625430d Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 5 May 2025 20:27:09 +0200 Subject: [PATCH 33/52] Change the order of the functions The USB matching functions have been moved to the top so that all the string based matching functions are grouped together. --- src/descriptor.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index a23cc54f..e831b222 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -492,39 +492,39 @@ static const dc_descriptor_t g_descriptors[] = { }; static int -dc_match_name (const void *key, const void *value) +dc_match_usb (const void *key, const void *value) { - const char *k = (const char *) key; - const char *v = *(const char * const *) value; + const dc_usb_desc_t *k = (const dc_usb_desc_t *) key; + const dc_usb_desc_t *v = (const dc_usb_desc_t *) value; - return strcasecmp (k, v) == 0; + return k->vid == v->vid && k->pid == v->pid; } static int -dc_match_prefix (const void *key, const void *value) +dc_match_usbhid (const void *key, const void *value) { - const char *k = (const char *) key; - const char *v = *(const char * const *) value; + const dc_usbhid_desc_t *k = (const dc_usbhid_desc_t *) key; + const dc_usbhid_desc_t *v = (const dc_usbhid_desc_t *) value; - return strncasecmp (k, v, strlen (v)) == 0; + return k->vid == v->vid && k->pid == v->pid; } static int -dc_match_usb (const void *key, const void *value) +dc_match_name (const void *key, const void *value) { - const dc_usb_desc_t *k = (const dc_usb_desc_t *) key; - const dc_usb_desc_t *v = (const dc_usb_desc_t *) value; + const char *k = (const char *) key; + const char *v = *(const char * const *) value; - return k->vid == v->vid && k->pid == v->pid; + return strcasecmp (k, v) == 0; } static int -dc_match_usbhid (const void *key, const void *value) +dc_match_prefix (const void *key, const void *value) { - const dc_usbhid_desc_t *k = (const dc_usbhid_desc_t *) key; - const dc_usbhid_desc_t *v = (const dc_usbhid_desc_t *) value; + const char *k = (const char *) key; + const char *v = *(const char * const *) value; - return k->vid == v->vid && k->pid == v->pid; + return strncasecmp (k, v, strlen (v)) == 0; } static int From 5ec19a4581cef8af6030013fcaf881e33a0e9b22 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 5 May 2025 20:28:37 +0200 Subject: [PATCH 34/52] Rename the prefix functions The prefix based matching functions have been renamed so that their function names share the same prefix. --- src/descriptor.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index e831b222..63fa43af 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -528,7 +528,7 @@ dc_match_prefix (const void *key, const void *value) } static int -dc_match_number_with_prefix (const void *key, const void *value) +dc_match_prefix_with_number (const void *key, const void *value) { const char *str = (const char *) key; const char *prefix = *(const char * const *) value; @@ -551,7 +551,7 @@ dc_match_number_with_prefix (const void *key, const void *value) } static int -dc_match_hex_with_prefix (const void *key, const void *value) +dc_match_prefix_with_hex (const void *key, const void *value) { const char *str = (const char *) key; const char *prefix = *(const char * const *) value; @@ -588,7 +588,7 @@ dc_match_oceanic (const void *key, const void *value) const char *p = prefix; - return dc_match_number_with_prefix (key, &p); + return dc_match_prefix_with_number (key, &p); } static int @@ -602,7 +602,7 @@ dc_match_cressi (const void *key, const void *value) const char *p = prefix; - return dc_match_hex_with_prefix (key, &p); + return dc_match_prefix_with_hex (key, &p); } static int @@ -797,7 +797,7 @@ dc_filter_divesystem (const dc_descriptor_t *descriptor, dc_transport_t transpor }; if (transport == DC_TRANSPORT_BLUETOOTH || transport == DC_TRANSPORT_BLE) { - return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_number_with_prefix); + return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_prefix_with_number); } return 1; From 60624b470e3f928ad040c1e3a239aafc357be3eb Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 5 May 2025 20:30:00 +0200 Subject: [PATCH 35/52] Use a case-insensitive comparision The other prefix matching function already use a case-insensitive comparision. There is no reason for a different behaviour. --- src/descriptor.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index 63fa43af..d3c4e02c 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -535,7 +535,7 @@ dc_match_prefix_with_number (const void *key, const void *value) size_t n = strlen (prefix); - if (strncmp (str, prefix, n) != 0) { + if (strncasecmp (str, prefix, n) != 0) { return 0; } @@ -558,7 +558,7 @@ dc_match_prefix_with_hex (const void *key, const void *value) size_t n = strlen (prefix); - if (strncmp (str, prefix, n) != 0) { + if (strncasecmp (str, prefix, n) != 0) { return 0; } From 28f6e3d3b39a7975b56cd45347579b2fabc63fc4 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 29 Apr 2025 22:36:47 +0200 Subject: [PATCH 36/52] Add support for a new Cressi Nepto firmware The Cressi Nepto with firmware v209 uses format version 4. Since the exact range isn't known, assume all firmware versions between 200 and 299 use the same format. --- src/cressi_goa.c | 2 +- src/cressi_goa_parser.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cressi_goa.c b/src/cressi_goa.c index 6156184f..0fe88519 100644 --- a/src/cressi_goa.c +++ b/src/cressi_goa.c @@ -548,7 +548,7 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v version = 2; } else if (firmware >= 100 && firmware <= 110) { version = 3; - } else if (firmware >= 200 && firmware <= 205) { + } else if (firmware >= 200 && firmware <= 299) { version = 4; } else if (firmware >= 300) { version = 5; diff --git a/src/cressi_goa_parser.c b/src/cressi_goa_parser.c index 80ac3ff6..1de995b5 100644 --- a/src/cressi_goa_parser.c +++ b/src/cressi_goa_parser.c @@ -333,7 +333,7 @@ cressi_goa_init(cressi_goa_parser_t *parser) version = 2; } else if (firmware >= 100 && firmware <= 110) { version = 3; - } else if (firmware >= 200 && firmware <= 205) { + } else if (firmware >= 200 && firmware <= 299) { version = 4; } else if (firmware >= 300) { version = 5; From 2247a3a9b7fe7dbc53c8e6351dfbabae44a840e1 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 14 May 2025 17:11:16 +0200 Subject: [PATCH 37/52] Fix the Cressi Nepto filter function The model number in the bluetooth device name appears to be encoded as a hexadecimal number instead of a decimal number (e.g. a_xxxx vs 10_xxxx for the Nepto). --- src/descriptor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/descriptor.c b/src/descriptor.c index d3c4e02c..cf3f52b7 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -598,7 +598,7 @@ dc_match_cressi (const void *key, const void *value) char prefix[16] = {0}; - dc_platform_snprintf(prefix, sizeof(prefix), "%u_", model); + dc_platform_snprintf(prefix, sizeof(prefix), "%x_", model); const char *p = prefix; From fcea2d88918e77e6c5082c9dec8c7836db67b729 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 18 May 2025 19:11:33 +0200 Subject: [PATCH 38/52] Fix the ppO2 sensor index The code used the wrong loop variable as the ppO2 sensor index. --- 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 f1e306d4..5e0c7ca3 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -1064,7 +1064,7 @@ hw_ostc_parser_internal_foreach (hw_ostc_parser_t *parser, dc_sample_callback_t } if (count) { for (unsigned int j = 0; j < 3; ++j) { - sample.ppo2.sensor = i; + sample.ppo2.sensor = j; sample.ppo2.value = ppo2[j] / 100.0; if (callback) callback (DC_SAMPLE_PPO2, &sample, userdata); } From 1870f5adfbd1095d30bfdc9828e77a573f464025 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 13 May 2025 19:58:46 +0200 Subject: [PATCH 39/52] Always initialize all fields to zero --- src/seac_screen.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/seac_screen.c b/src/seac_screen.c index 92af8f25..dec26bc7 100644 --- a/src/seac_screen.c +++ b/src/seac_screen.c @@ -251,6 +251,7 @@ seac_screen_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t // Set the default values. device->iostream = iostream; + memset (device->info, 0, sizeof (device->info)); memset (device->fingerprint, 0, sizeof (device->fingerprint)); // Set the serial communication protocol (115200 8N1). From f35b26f2d78bf9a756101cba7c0f7789e5860823 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 13 May 2025 20:38:26 +0200 Subject: [PATCH 40/52] Fix the deco stop depth scaling factor The decompression stop depth values are stored with a unit of centimeters and not 1 meter. --- src/seac_screen_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seac_screen_parser.c b/src/seac_screen_parser.c index 07553d6c..94db6e70 100644 --- a/src/seac_screen_parser.c +++ b/src/seac_screen_parser.c @@ -353,7 +353,7 @@ seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t if (decodepth) { sample.deco.type = DC_DECO_DECOSTOP; sample.deco.time = decotime; - sample.deco.depth = decodepth; + sample.deco.depth = decodepth / 100.0; } else { sample.deco.type = DC_DECO_NDL; sample.deco.time = ndl_tts; From 9065d6e12e2c340ee52b08309aa3797a860b56b3 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 13 May 2025 21:12:45 +0200 Subject: [PATCH 41/52] Report the device model number The hardware info contains a 32 bit model number that can be used to distinguish the different models. There is also a serial number prefix (at offset 8) which contains a single letter that serves the same purpose: A = Action B = Screen E = Tablet --- src/descriptor.c | 4 ++-- src/seac_screen.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index cf3f52b7..f8496d25 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -477,8 +477,8 @@ static const dc_descriptor_t g_descriptors[] = { {"Genesis", "Centauri", DC_FAMILY_DEEPSIX_EXCURSION, 0, DC_TRANSPORT_BLE, dc_filter_deepsix}, {"Scorpena", "Alpha", DC_FAMILY_DEEPSIX_EXCURSION, 0, DC_TRANSPORT_BLE, dc_filter_deepsix}, /* Seac Screen */ - {"Seac", "Screen", DC_FAMILY_SEAC_SCREEN, 0, DC_TRANSPORT_SERIAL, NULL}, - {"Seac", "Action", DC_FAMILY_SEAC_SCREEN, 0, DC_TRANSPORT_SERIAL, NULL}, + {"Seac", "Action", DC_FAMILY_SEAC_SCREEN, 0x01, DC_TRANSPORT_SERIAL, NULL}, + {"Seac", "Screen", DC_FAMILY_SEAC_SCREEN, 0x02, DC_TRANSPORT_SERIAL, NULL}, /* Deepblu Cosmiq */ {"Deepblu", "Cosmiq+", DC_FAMILY_DEEPBLU_COSMIQ, 0, DC_TRANSPORT_BLE, dc_filter_deepblu}, /* Oceans S1 */ diff --git a/src/seac_screen.c b/src/seac_screen.c index dec26bc7..fba6a07c 100644 --- a/src/seac_screen.c +++ b/src/seac_screen.c @@ -370,7 +370,7 @@ seac_screen_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) // Emit a device info event. dc_event_devinfo_t devinfo; - devinfo.model = 0; + devinfo.model = array_uint32_le (device->info + 4); devinfo.firmware = array_uint32_le (device->info + 0x11C); devinfo.serial = array_uint32_le (device->info + 0x10); device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); @@ -404,7 +404,7 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, // Emit a device info event. dc_event_devinfo_t devinfo; - devinfo.model = 0; + devinfo.model = array_uint32_le (device->info + 4); devinfo.firmware = array_uint32_le (device->info + 0x11C); devinfo.serial = array_uint32_le (device->info + 0x010); device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); From 69d9b501c5c3dc119c81b4d34fda8bc62a9771f5 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 21 May 2025 19:42:33 +0200 Subject: [PATCH 42/52] Detect NAK response packets Errors are reported by the dive computer with a NAK packet containing an error code. Detect and handle such packets and log the error code. --- src/seac_screen.c | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/seac_screen.c b/src/seac_screen.c index fba6a07c..70232ab3 100644 --- a/src/seac_screen.c +++ b/src/seac_screen.c @@ -34,6 +34,16 @@ #define MAXRETRIES 4 +#define START 0x55 +#define ACK 0x09 +#define NAK 0x30 + +#define ERR_INVALID_CMD 0x02 +#define ERR_INVALID_LENGTH 0x03 +#define ERR_INVALID_DATA 0x04 +#define ERR_BATTERY_LOW 0x05 +#define ERR_BUSY 0x06 + #define SZ_MAXCMD 8 #define SZ_MAXRSP SZ_READ @@ -106,7 +116,7 @@ seac_screen_send (seac_screen_device_t *device, unsigned short cmd, const unsign // Setup the data packet unsigned len = size + 6; unsigned char packet[SZ_MAXCMD + 7] = { - 0x55, + START, (len >> 8) & 0xFF, (len ) & 0xFF, (cmd >> 8) & 0xFF, @@ -144,7 +154,7 @@ seac_screen_receive (seac_screen_device_t *device, unsigned short cmd, unsigned } // Verify the start byte. - if (packet[0] != 0x55) { + if (packet[0] != START) { ERROR (abstract->context, "Unexpected start byte (%02x).", packet[0]); return DC_STATUS_PROTOCOL; } @@ -173,17 +183,32 @@ seac_screen_receive (seac_screen_device_t *device, unsigned short cmd, unsigned // Verify the command response. unsigned int rsp = array_uint16_be (packet + 3); - unsigned int misc = packet[1 + length - 3]; - if (rsp != cmd || misc != 0x09) { - ERROR (abstract->context, "Unexpected command response (%04x %02x).", rsp, misc); + if (rsp != cmd) { + ERROR (abstract->context, "Unexpected command response (%04x).", rsp); + return DC_STATUS_PROTOCOL; + } + + // Verify the ACK/NAK byte. + unsigned int type = packet[1 + length - 3]; + if (type != ACK && type != NAK) { + ERROR (abstract->context, "Unexpected ACK/NAK byte (%02x).", type); return DC_STATUS_PROTOCOL; } - if (length - 7 != size) { + // Verify the length of the packet. + unsigned int expected = (type == ACK ? size : 1) + 7; + if (length != expected) { ERROR (abstract->context, "Unexpected packet length (%u).", length); return DC_STATUS_PROTOCOL; } + // Get the error code from a NAK packet. + if (type == NAK) { + unsigned int errcode = packet[5]; + ERROR (abstract->context, "Received NAK packet with error code %02x.", errcode); + return DC_STATUS_PROTOCOL; + } + memcpy (data, packet + 5, length - 7); return DC_STATUS_SUCCESS; From 426563e095248327c250b785f93dfd23df59f98c Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 26 May 2025 20:12:25 +0200 Subject: [PATCH 43/52] Ignore invalid start bytes Ignoring all received bytes until an START bytes is encountered makes the re-synchronization on the start of the packet a bit more robust. --- src/seac_screen.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/seac_screen.c b/src/seac_screen.c index 70232ab3..c376ee6a 100644 --- a/src/seac_screen.c +++ b/src/seac_screen.c @@ -146,17 +146,25 @@ seac_screen_receive (seac_screen_device_t *device, unsigned short cmd, unsigned dc_device_t *abstract = (dc_device_t *) device; unsigned char packet[SZ_MAXRSP + 8] = {0}; - // Read the packet header. - status = dc_iostream_read (device->iostream, packet, 3, NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to receive the packet header."); - return status; + // Read the packet start byte. + while (1) { + status = dc_iostream_read (device->iostream, packet + 0, 1, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the packet start byte."); + return status; + } + + if (packet[0] == START) + break; + + WARNING (abstract->context, "Unexpected packet header byte (%02x).", packet[0]); } - // Verify the start byte. - if (packet[0] != START) { - ERROR (abstract->context, "Unexpected start byte (%02x).", packet[0]); - return DC_STATUS_PROTOCOL; + // Read the packet length. + status = dc_iostream_read (device->iostream, packet + 1, 2, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the packet length."); + return status; } // Verify the length. From 8b624015931807ae6505c0420dff504f4d6b2dc8 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 7 May 2025 19:21:18 +0200 Subject: [PATCH 44/52] Add support for the Seac Tablet The Seac Tablet uses almost the same protocol as the Seac Screen and Action, but with a few small differences: - A slightly different set of commands, but with exactly the same functionality. - The new variant of the READ command does no longer pad the response packet to 2048 bytes. - The profile area starts at address 0x0A0000 instead of 0x010000. The Seac Tablet also support tank pressure transmitters. --- src/descriptor.c | 1 + src/parser.c | 2 +- src/seac_screen.c | 111 +++++++++++++++++++++++++++++++-------- src/seac_screen.h | 2 +- src/seac_screen_parser.c | 16 +++++- 5 files changed, 106 insertions(+), 26 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index f8496d25..b37ad23c 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -479,6 +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}, /* Deepblu Cosmiq */ {"Deepblu", "Cosmiq+", DC_FAMILY_DEEPBLU_COSMIQ, 0, DC_TRANSPORT_BLE, dc_filter_deepblu}, /* Oceans S1 */ diff --git a/src/parser.c b/src/parser.c index 48a177b0..6beb5a89 100644 --- a/src/parser.c +++ b/src/parser.c @@ -195,7 +195,7 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, const unsigned rc = deepsix_excursion_parser_create (&parser, context, data, size); break; case DC_FAMILY_SEAC_SCREEN: - rc = seac_screen_parser_create (&parser, context, data, size); + rc = seac_screen_parser_create (&parser, context, data, size, model); break; case DC_FAMILY_DEEPBLU_COSMIQ: rc = deepblu_cosmiq_parser_create (&parser, context, data, size); diff --git a/src/seac_screen.c b/src/seac_screen.c index c376ee6a..e8f91c67 100644 --- a/src/seac_screen.c +++ b/src/seac_screen.c @@ -49,9 +49,16 @@ #define CMD_HWINFO 0x1833 #define CMD_SWINFO 0x1834 -#define CMD_RANGE 0x1840 -#define CMD_ADDRESS 0x1841 -#define CMD_READ 0x1842 + +// Screen +#define CMD_SCREEN_RANGE 0x1840 +#define CMD_SCREEN_ADDRESS 0x1841 +#define CMD_SCREEN_READ 0x1842 + +// Tablet +#define CMD_TABLET_RANGE 0x1850 +#define CMD_TABLET_ADDRESS 0x1851 +#define CMD_TABLET_READ 0x1852 #define SZ_HWINFO 256 #define SZ_SWINFO 256 @@ -65,15 +72,29 @@ #define FP_OFFSET 0x0A #define FP_SIZE 7 -#define RB_PROFILE_BEGIN 0x010000 -#define RB_PROFILE_END 0x200000 -#define RB_PROFILE_SIZE (RB_PROFILE_END - RB_PROFILE_BEGIN) -#define RB_PROFILE_DISTANCE(a,b) ringbuffer_distance (a, b, DC_RINGBUFFER_FULL, RB_PROFILE_BEGIN, RB_PROFILE_END) -#define RB_PROFILE_INCR(a,d) ringbuffer_increment (a, d, RB_PROFILE_BEGIN, RB_PROFILE_END) +#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) + +#define ACTION 0x01 +#define SCREEN 0x02 +#define TABLET 0x10 + +typedef struct seac_screen_commands_t { + unsigned short range; + unsigned short address; + unsigned short read; +} seac_screen_commands_t; + +typedef struct seac_screen_layout_t { + unsigned int rb_profile_begin; + unsigned int rb_profile_end; +} seac_screen_layout_t; typedef struct seac_screen_device_t { dc_device_t base; dc_iostream_t *iostream; + const seac_screen_commands_t *cmds; + const seac_screen_layout_t *layout; unsigned char info[SZ_HWINFO + SZ_SWINFO]; unsigned char fingerprint[FP_SIZE]; } seac_screen_device_t; @@ -100,6 +121,28 @@ static const dc_device_vtable_t seac_screen_device_vtable = { NULL, /* close */ }; +static const seac_screen_commands_t cmds_screen = { + CMD_SCREEN_RANGE, + CMD_SCREEN_ADDRESS, + CMD_SCREEN_READ, +}; + +static const seac_screen_commands_t cmds_tablet = { + CMD_TABLET_RANGE, + CMD_TABLET_ADDRESS, + CMD_TABLET_READ, +}; + +static const seac_screen_layout_t layout_screen = { + 0x010000, /* rb_profile_begin */ + 0x200000, /* rb_profile_end */ +}; + +static const seac_screen_layout_t layout_tablet = { + 0x0A0000, /* rb_profile_begin */ + 0x200000, /* rb_profile_end */ +}; + static dc_status_t seac_screen_send (seac_screen_device_t *device, unsigned short cmd, const unsigned char data[], size_t size) { @@ -284,6 +327,8 @@ 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; memset (device->info, 0, sizeof (device->info)); memset (device->fingerprint, 0, sizeof (device->fingerprint)); @@ -327,6 +372,15 @@ seac_screen_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t HEXDUMP (context, DC_LOGLEVEL_DEBUG, "Software", device->info + SZ_HWINFO, SZ_SWINFO); + unsigned int model = array_uint32_le (device->info + 4); + if (model == TABLET) { + device->cmds = &cmds_tablet; + device->layout = &layout_tablet; + } else { + device->cmds = &cmds_screen; + device->layout = &layout_screen; + } + *out = (dc_device_t *) device; return DC_STATUS_SUCCESS; @@ -379,7 +433,8 @@ seac_screen_device_read (dc_device_t *abstract, unsigned int address, unsigned c (len ) & 0xFF, }; unsigned char packet[SZ_READ] = {0}; - status = seac_screen_transfer (device, CMD_READ, params, sizeof(params), packet, sizeof(packet)); + const unsigned int packetsize = device->cmds->read == CMD_TABLET_READ ? len : sizeof(packet); + status = seac_screen_transfer (device, device->cmds->read, params, sizeof(params), packet, packetsize); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the read command."); return status; @@ -400,11 +455,16 @@ static dc_status_t seac_screen_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) { seac_screen_device_t *device = (seac_screen_device_t *) abstract; + const seac_screen_layout_t *layout = device->layout; // Emit a device info event. dc_event_devinfo_t devinfo; devinfo.model = array_uint32_le (device->info + 4); - devinfo.firmware = array_uint32_le (device->info + 0x11C); + if (devinfo.model == TABLET) { + devinfo.firmware = array_uint32_le (device->info + 0x114); + } else { + devinfo.firmware = array_uint32_le (device->info + 0x11C); + } devinfo.serial = array_uint32_le (device->info + 0x10); device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); @@ -415,12 +475,12 @@ seac_screen_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) device_event_emit (abstract, DC_EVENT_VENDOR, &vendor); // Allocate the required amount of memory. - if (!dc_buffer_resize (buffer, RB_PROFILE_SIZE)) { + if (!dc_buffer_resize (buffer, layout->rb_profile_end - layout->rb_profile_begin)) { ERROR (abstract->context, "Insufficient buffer space available."); return DC_STATUS_NOMEMORY; } - return device_dump_read (abstract, RB_PROFILE_BEGIN, dc_buffer_get_data (buffer), + return device_dump_read (abstract, layout->rb_profile_begin, dc_buffer_get_data (buffer), dc_buffer_get_size (buffer), SZ_READ); } @@ -429,16 +489,21 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, { dc_status_t status = DC_STATUS_SUCCESS; seac_screen_device_t *device = (seac_screen_device_t *) abstract; + const seac_screen_layout_t *layout = device->layout; // Enable progress notifications. dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; - progress.maximum = RB_PROFILE_SIZE; + progress.maximum = layout->rb_profile_end - layout->rb_profile_begin; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); // Emit a device info event. dc_event_devinfo_t devinfo; devinfo.model = array_uint32_le (device->info + 4); - devinfo.firmware = array_uint32_le (device->info + 0x11C); + if (devinfo.model == TABLET) { + devinfo.firmware = array_uint32_le (device->info + 0x114); + } else { + devinfo.firmware = array_uint32_le (device->info + 0x11C); + } devinfo.serial = array_uint32_le (device->info + 0x010); device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); @@ -450,7 +515,7 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, // Read the range of the available dive numbers. unsigned char range[SZ_RANGE] = {0}; - status = seac_screen_transfer (device, CMD_RANGE, NULL, 0, range, sizeof(range)); + status = seac_screen_transfer (device, device->cmds->range, NULL, 0, range, sizeof(range)); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the range command."); goto error_exit; @@ -486,7 +551,7 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, unsigned int count = 0; unsigned int skip = 0; unsigned int rb_profile_size = 0; - unsigned int remaining = RB_PROFILE_SIZE; + unsigned int remaining = layout->rb_profile_end - layout->rb_profile_begin; for (unsigned int i = 0; i < ndives; ++i) { unsigned int number = last - i; @@ -498,7 +563,7 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, (number ) & 0xFF, }; unsigned char rsp_address[SZ_ADDRESS] = {0}; - status = seac_screen_transfer (device, CMD_ADDRESS, cmd_address, sizeof(cmd_address), rsp_address, sizeof(rsp_address)); + status = seac_screen_transfer (device, device->cmds->address, cmd_address, sizeof(cmd_address), rsp_address, sizeof(rsp_address)); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the dive address."); goto error_free_logbook; @@ -506,7 +571,7 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, // Get the dive address. logbook[i].address = array_uint32_be (rsp_address); - if (logbook[i].address < RB_PROFILE_BEGIN || logbook[i].address >= RB_PROFILE_END) { + 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; @@ -543,11 +608,11 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, // Get the end-of-profile pointer. if (eop == 0) { - eop = previous = RB_PROFILE_INCR (logbook[i].address, nbytes); + eop = previous = RB_PROFILE_INCR (logbook[i].address, nbytes, layout); } // Calculate the length. - unsigned int length = RB_PROFILE_DISTANCE (logbook[i].address, previous); + unsigned int length = RB_PROFILE_DISTANCE (logbook[i].address, previous, layout); // Check for the end of the ringbuffer. if (length > remaining) { @@ -567,7 +632,7 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, // Update and emit a progress event. progress.maximum -= (ndives - count - skip) * (SZ_ADDRESS + SZ_HEADER) + - (RB_PROFILE_SIZE - rb_profile_size); + ((layout->rb_profile_end - layout->rb_profile_begin) - rb_profile_size); device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); // Exit if no dives to download. @@ -584,7 +649,7 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, // Create the ringbuffer stream. dc_rbstream_t *rbstream = NULL; - status = dc_rbstream_new (&rbstream, abstract, SZ_READ, SZ_READ, RB_PROFILE_BEGIN, RB_PROFILE_END, eop, DC_RBSTREAM_BACKWARD); + status = dc_rbstream_new (&rbstream, abstract, SZ_READ, SZ_READ, layout->rb_profile_begin, layout->rb_profile_end, eop, DC_RBSTREAM_BACKWARD); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to create the ringbuffer stream."); goto error_free_profile; @@ -594,7 +659,7 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, unsigned int offset = rb_profile_size; for (unsigned int i = 0; i < count; ++i) { // Calculate the length. - unsigned int length = RB_PROFILE_DISTANCE (logbook[i].address, previous); + unsigned int length = RB_PROFILE_DISTANCE (logbook[i].address, previous, layout); // Move to the start of the current dive. offset -= length; diff --git a/src/seac_screen.h b/src/seac_screen.h index 4ff05eae..ecab8dfd 100644 --- a/src/seac_screen.h +++ b/src/seac_screen.h @@ -35,7 +35,7 @@ dc_status_t seac_screen_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); dc_status_t -seac_screen_parser_create (dc_parser_t **parser, dc_context_t *context, const unsigned char data[], size_t size); +seac_screen_parser_create (dc_parser_t **parser, dc_context_t *context, const unsigned char data[], size_t size, unsigned int model); #ifdef __cplusplus } diff --git a/src/seac_screen_parser.c b/src/seac_screen_parser.c index 94db6e70..b19c8ea9 100644 --- a/src/seac_screen_parser.c +++ b/src/seac_screen_parser.c @@ -36,10 +36,15 @@ #define INVALID 0xFFFFFFFF +#define ACTION 0x01 +#define SCREEN 0x02 +#define TABLET 0x10 + typedef struct seac_screen_parser_t seac_screen_parser_t; struct seac_screen_parser_t { dc_parser_t base; + unsigned int model; // Cached fields. unsigned int cached; unsigned int ngasmixes; @@ -65,7 +70,7 @@ static const dc_parser_vtable_t seac_screen_parser_vtable = { }; dc_status_t -seac_screen_parser_create (dc_parser_t **out, dc_context_t *context, const unsigned char data[], size_t size) +seac_screen_parser_create (dc_parser_t **out, dc_context_t *context, const unsigned char data[], size_t size, unsigned int model) { seac_screen_parser_t *parser = NULL; @@ -80,6 +85,7 @@ seac_screen_parser_create (dc_parser_t **out, dc_context_t *context, const unsig } // Set the default values. + parser->model = model; parser->cached = 0; parser->ngasmixes = 0; for (unsigned int i = 0; i < NGASMIXES; ++i) { @@ -298,6 +304,7 @@ seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t unsigned int decodepth = array_uint16_le (data + offset + 0x0E); unsigned int decotime = array_uint16_le (data + offset + 0x10); unsigned int ndl_tts = array_uint16_le (data + offset + 0x12); + unsigned int pressure = array_uint16_le (data + offset + 0x14); unsigned int cns = array_uint16_le (data + offset + 0x16); unsigned int gf_hi = data[offset + 0x3B]; unsigned int gf_lo = data[offset + 0x3C]; @@ -366,6 +373,13 @@ seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t sample.cns = cns / 100.0; if (callback) callback (DC_SAMPLE_CNS, &sample, userdata); + // Tank pressure + if (pressure && parser->model == TABLET) { + sample.pressure.tank = 0; + sample.pressure.value = pressure; + if (callback) callback (DC_SAMPLE_PRESSURE, &sample, userdata); + } + // Deco model if (gf_low == 0 && gf_high == 0) { gf_low = gf_lo; From db25c15c8fac7f7fb9a07a33a3fcb59766226fd2 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 18 Jun 2025 18:18:27 +0200 Subject: [PATCH 45/52] Support RDBI responses with a variable size The RDBI commands typically have a response with a fixed size, but apparently some responses can also have a variable size. To support such responses, an extra output parameter is added to report the actual size. --- src/shearwater_common.c | 15 +++++++++++++-- src/shearwater_common.h | 2 +- src/shearwater_petrel.c | 10 +++++----- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/shearwater_common.c b/src/shearwater_common.c index 78d785ea..a747ef0f 100644 --- a/src/shearwater_common.c +++ b/src/shearwater_common.c @@ -520,7 +520,7 @@ shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buf dc_status_t -shearwater_common_rdbi (shearwater_common_device_t *device, unsigned int id, unsigned char data[], unsigned int size) +shearwater_common_rdbi (shearwater_common_device_t *device, unsigned int id, unsigned char data[], unsigned int size, unsigned int *actual) { dc_status_t status = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; @@ -549,11 +549,22 @@ shearwater_common_rdbi (shearwater_common_device_t *device, unsigned int id, uns unsigned int length = n - 3; - if (length != size) { + if (length > size) { ERROR (abstract->context, "Unexpected packet size (%u bytes).", length); return DC_STATUS_PROTOCOL; } + if (actual == NULL) { + // Verify the actual length. + if (length != size) { + ERROR (abstract->context, "Unexpected packet size (%u bytes).", length); + return DC_STATUS_PROTOCOL; + } + } else { + // Return the actual length. + *actual = length; + } + if (length) { memcpy (data, response + 3, length); } diff --git a/src/shearwater_common.h b/src/shearwater_common.h index ea3718c8..d5f425cb 100644 --- a/src/shearwater_common.h +++ b/src/shearwater_common.h @@ -72,7 +72,7 @@ dc_status_t shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buffer, unsigned int address, unsigned int size, unsigned int compression, dc_event_progress_t *progress); dc_status_t -shearwater_common_rdbi (shearwater_common_device_t *device, unsigned int id, unsigned char data[], unsigned int size); +shearwater_common_rdbi (shearwater_common_device_t *device, unsigned int id, unsigned char data[], unsigned int size, unsigned int *actual); dc_status_t shearwater_common_wdbi (shearwater_common_device_t *device, unsigned int id, const unsigned char data[], unsigned int size); diff --git a/src/shearwater_petrel.c b/src/shearwater_petrel.c index fc4a69b3..58cbeafd 100644 --- a/src/shearwater_petrel.c +++ b/src/shearwater_petrel.c @@ -160,7 +160,7 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call // Read the serial number. unsigned char rsp_serial[8] = {0}; - rc = shearwater_common_rdbi (&device->base, ID_SERIAL, rsp_serial, sizeof(rsp_serial)); + rc = shearwater_common_rdbi (&device->base, ID_SERIAL, rsp_serial, sizeof(rsp_serial), NULL); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the serial number."); return rc; @@ -177,7 +177,7 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call // Read the firmware version. unsigned char rsp_firmware[11] = {0}; - rc = shearwater_common_rdbi (&device->base, ID_FIRMWARE, rsp_firmware, sizeof(rsp_firmware)); + rc = shearwater_common_rdbi (&device->base, ID_FIRMWARE, rsp_firmware, sizeof(rsp_firmware), NULL); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the firmware version."); return rc; @@ -190,7 +190,7 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call // Read the hardware type. unsigned char rsp_hardware[2] = {0}; - rc = shearwater_common_rdbi (&device->base, ID_HARDWARE, rsp_hardware, sizeof(rsp_hardware)); + rc = shearwater_common_rdbi (&device->base, ID_HARDWARE, rsp_hardware, sizeof(rsp_hardware), NULL); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the hardware type."); return rc; @@ -211,7 +211,7 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call // Read the logbook type unsigned char rsp_logupload[9] = {0}; - rc = shearwater_common_rdbi (&device->base, ID_LOGUPLOAD, rsp_logupload, sizeof(rsp_logupload)); + rc = shearwater_common_rdbi (&device->base, ID_LOGUPLOAD, rsp_logupload, sizeof(rsp_logupload), NULL); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the logbook type."); return rc; @@ -368,7 +368,7 @@ shearwater_petrel_device_timesync (dc_device_t *abstract, const dc_datetime_t *d // Read the hardware type. unsigned char rsp_hardware[2] = {0}; - status = shearwater_common_rdbi (device, ID_HARDWARE, rsp_hardware, sizeof(rsp_hardware)); + status = shearwater_common_rdbi (device, ID_HARDWARE, rsp_hardware, sizeof(rsp_hardware), NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the hardware type."); return status; From 50fe02f96f40c431698cc89e5451cf87545ec69f Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 18 Jun 2025 18:24:16 +0200 Subject: [PATCH 46/52] Increase the size of the firmware version buffer The latest version of the Perdix 2 firmware is at version 100. Since the firmware version is reported as the string 'V100 Classic' in the RDBI response, it now requires a buffer with space for one more digit. The size of the response is no longer fixed either. --- src/shearwater_petrel.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/shearwater_petrel.c b/src/shearwater_petrel.c index 58cbeafd..a4414ade 100644 --- a/src/shearwater_petrel.c +++ b/src/shearwater_petrel.c @@ -176,17 +176,18 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call } // Read the firmware version. - unsigned char rsp_firmware[11] = {0}; - rc = shearwater_common_rdbi (&device->base, ID_FIRMWARE, rsp_firmware, sizeof(rsp_firmware), NULL); + unsigned char rsp_firmware[12] = {0}; + unsigned int rsp_firmware_length = 0; + rc = shearwater_common_rdbi (&device->base, ID_FIRMWARE, rsp_firmware, sizeof(rsp_firmware), &rsp_firmware_length); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the firmware version."); return rc; } - HEXDUMP(abstract->context, DC_LOGLEVEL_DEBUG, "Firmware", rsp_firmware, sizeof(rsp_firmware)); + HEXDUMP(abstract->context, DC_LOGLEVEL_DEBUG, "Firmware", rsp_firmware, rsp_firmware_length); // Convert to a number. - unsigned int firmware = str2num (rsp_firmware, sizeof(rsp_firmware), 1); + unsigned int firmware = str2num (rsp_firmware, rsp_firmware_length, 1); // Read the hardware type. unsigned char rsp_hardware[2] = {0}; From e77e2071b97394679592d9efaf477d3cb89cb32f Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 19 Jun 2025 15:26:23 +0200 Subject: [PATCH 47/52] Add a new Perdix 2 hardware model --- src/shearwater_common.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shearwater_common.c b/src/shearwater_common.c index a747ef0f..dc689fb3 100644 --- a/src/shearwater_common.c +++ b/src/shearwater_common.c @@ -740,6 +740,7 @@ shearwater_common_get_model (shearwater_common_device_t *device, unsigned int ha break; case 0xC407: case 0xC964: + case 0x9C64: model = PERDIX2; break; case 0x0F0F: From cfea667365fec200c7f94392f674c80d3e68cb5c Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 28 Apr 2025 19:54:42 +0200 Subject: [PATCH 48/52] Fix a small mistake in the debug output The device field in the CFG_ID_VERSION record is not the device model, but the device type (Freedom or Libery). --- 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 0822bdf5..83bb0c13 100644 --- a/src/divesoft_freedom_parser.c +++ b/src/divesoft_freedom_parser.c @@ -423,7 +423,7 @@ divesoft_freedom_cache (divesoft_freedom_parser_t *parser) seawater = misc & 0x02; vpm = misc & 0x20; } else if (id == CFG_ID_VERSION) { - DEBUG (abstract->context, "Device: model=%u, hw=%u.%u, sw=%u.%u.%u.%u flags=%u", + DEBUG (abstract->context, "Device: type=%u, hw=%u.%u, sw=%u.%u.%u.%u flags=%u", data[offset + 4], data[offset + 5], data[offset + 6], data[offset + 7], data[offset + 8], data[offset + 9], From cc70d7ac3fe6430ad0d5c92fedf8d17359c222af Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 15 Jun 2025 17:34:13 +0200 Subject: [PATCH 49/52] Fix the date/time parsing The Symbios stores the date/time value as a UTC timestamp. Since there is no timezone information present, I incorrectly assumed the timestamp actually represented local time. Therefore I used the gmtime function to perform a timezone independent conversion. Fixed by using the localtime function instead. Note: this approach makes the conversion dependent on the current timezone of the host system, but there is no guarantee this timezone is indeed the correct timezone in which the dive was recorded! --- src/halcyon_symbios_parser.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/halcyon_symbios_parser.c b/src/halcyon_symbios_parser.c index f9cce257..54aa635c 100644 --- a/src/halcyon_symbios_parser.c +++ b/src/halcyon_symbios_parser.c @@ -182,11 +182,9 @@ 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_gmtime (datetime, ticks)) + if (!dc_datetime_localtime (datetime, ticks)) return DC_STATUS_DATAFORMAT; - datetime->timezone = DC_TIMEZONE_NONE; - return DC_STATUS_SUCCESS; } From 4d37693807164a6046a70d9357581054c6111101 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 23 Jun 2025 20:46:34 +0200 Subject: [PATCH 50/52] Add a new Perdix 2 hardware model --- src/shearwater_common.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shearwater_common.c b/src/shearwater_common.c index dc689fb3..b8aeaff9 100644 --- a/src/shearwater_common.c +++ b/src/shearwater_common.c @@ -738,6 +738,7 @@ shearwater_common_get_model (shearwater_common_device_t *device, unsigned int ha case 0x8D6C: model = PERDIXAI; break; + case 0x704C: case 0xC407: case 0xC964: case 0x9C64: From 75f2adcccc1caec5a101de2fe9d4bacafd6d77ae Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 23 Jun 2025 20:49:56 +0200 Subject: [PATCH 51/52] Add some extra debug logging --- src/shearwater_predator_parser.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index 1f1a4226..c513f5c2 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -675,6 +675,10 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) // Get the correct model number from the final block. if (parser->final != UNDEFINED) { parser->model = data[parser->final + 13]; + DEBUG (abstract->context, "Device: model=%u, serial=%u, firmware=%u", + data[parser->final + 13], + array_uint32_be (data + parser->final + 2), + bcd2dec (data[parser->final + 10])); } // Fix the Teric tank serial number. From 25b8fed709106dc693baad13eb47297abba46d98 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 30 Jun 2025 22:20:46 +0200 Subject: [PATCH 52/52] Release version 0.9.0 --- NEWS | 43 +++++++++++++++++++++++++++++++++++++++++++ configure.ac | 2 +- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index df264aed..397868da 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,46 @@ +Version 0.9.0 (2025-06-30) +========================== + +The v0.9.0 release adds support for several new devices. It also introduces some +important parser api changes to be able to add some new features and remove a +few deprecated ones. Some BLE enabled dive computers also required some extra +additions to the BLE I/O layer. Due to all these api changes, this release is +not backwards compatible. + +New features: + + * Add support for new backends: + - i330r: Aqualung i330R, Apeks DSX + - symbios: Halcyon Symbios + * Add support for some new devices: + - Aqualung: i100 + - Cressi: Leonardo 2.0, Nepto, Archimede + - Heinrichs Weikamp: OSTC 5 + - Mares: Puck Air 2, Sirius, Quad Ci, Puck 4, Puck Lite + - Ratio: iDive 2, iX3M 2 GPS + - Scubapro: G3, Luna 2.0, Luna 2.0 AI + - Seac: Tablet + - Shearwater: Tern, Tern TX, Peregrine TX + - Uwatec: Aladin One + * Add the sensor index to the ppO2 sample + * Add a TTS field to the deco sample + * Add a usage field to the tank and gas mix + * Export the filter function in the public api + * Add ioctl's for the bluetooth authentication + * Add ioctl's for reading and writing BLE characteristics + * Add helper functions to convert UUID to/from strings + * Add a new field to report the GPS location + +Removed/changed features: + + * Change the units for the sample time to milliseconds + * Pass the sample struct by reference + * Remove the backend specific calibration functions + * Remove the clock parameters from the constructor + * Remove the dc_parser_set_data function + * Use separate data structures for USB and USB HID + * Replace the dc_descriptor_iterator function + Version 0.8.0 (2023-05-11) ========================== diff --git a/configure.ac b/configure.ac index 44a120da..cfb2f2e0 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ m4_define([dc_version_major],[0]) m4_define([dc_version_minor],[9]) m4_define([dc_version_micro],[0]) -m4_define([dc_version_suffix],[devel]) +m4_define([dc_version_suffix],[]) m4_define([dc_version],dc_version_major.dc_version_minor.dc_version_micro[]m4_ifset([dc_version_suffix],-[dc_version_suffix])) # Libtool versioning.