From 89ca1cbea9e5ba63bf80e55fdf32d6d0cac7a1e6 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 17 Jul 2024 23:14:33 +0200 Subject: [PATCH 01/17] Add support for a new variant of the Aqualung i100 --- src/descriptor.c | 1 + src/oceanic_atom2.c | 1 + src/oceanic_atom2_parser.c | 11 ++++++----- src/oceanic_common.h | 1 + 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index 194c59ac..55cb3567 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -270,6 +270,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Sherwood", "Amphos Air 2.0", DC_FAMILY_OCEANIC_ATOM2, 0x4658, DC_TRANSPORT_SERIAL, NULL}, {"Sherwood", "Beacon", DC_FAMILY_OCEANIC_ATOM2, 0x4742, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, {"Aqualung", "i470TC", DC_FAMILY_OCEANIC_ATOM2, 0x4743, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, + {"Aqualung", "i100", DC_FAMILY_OCEANIC_ATOM2, 0x4745, DC_TRANSPORT_SERIAL, NULL}, {"Aqualung", "i200C", DC_FAMILY_OCEANIC_ATOM2, 0x4749, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, {"Oceanic", "Geo Air", DC_FAMILY_OCEANIC_ATOM2, 0x474B, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, /* Pelagic I330R */ diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index 0e3e1872..82b68ffa 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -513,6 +513,7 @@ static const oceanic_common_version_t versions[] = { {"AERISAIR \0\0 1024", 0, A300AI, &oceanic_vt4_layout}, {"SWVISION \0\0 1024", 0, VISION, &oceanic_vt4_layout}, {"XPSUBAIR \0\0 1024", 0, XPAIR, &oceanic_vt4_layout}, + {"AQUAI100 \0\0 1024", 0, I100V2, &oceanic_vt4_layout}, {"HOLLDG04 \0\0 2048", 0, TX1, &hollis_tx1_layout}, diff --git a/src/oceanic_atom2_parser.c b/src/oceanic_atom2_parser.c index ffad638d..38ac4472 100644 --- a/src/oceanic_atom2_parser.c +++ b/src/oceanic_atom2_parser.c @@ -142,7 +142,7 @@ oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, const uns } else if (model == PROPLUSX) { parser->headersize = 3 * PAGESIZE; } else if (model == I550C || model == WISDOM4 || - model == I200CV2) { + model == I200CV2|| model == I100V2) { parser->headersize = 5 * PAGESIZE / 2; } else if (model == I330R) { parser->logbooksize = 64; @@ -221,6 +221,7 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim case I470TC: case I200CV2: case GEOAIR: + case I100V2: datetime->year = ((p[5] & 0xE0) >> 5) + ((p[7] & 0xE0) >> 2) + 2000; datetime->month = (p[3] & 0x0F); datetime->day = ((p[0] & 0x80) >> 3) + ((p[3] & 0xF0) >> 4); @@ -728,7 +729,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I300C || parser->model == TALIS || parser->model == I200C || parser->model == I200CV2 || parser->model == GEO40 || parser->model == VEO40 || - parser->model == I330R) { + parser->model == I330R || parser->model == I100V2) { have_pressure = 0; } @@ -883,7 +884,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I300C || parser->model == I200C || parser->model == GEO40 || parser->model == VEO40 || parser->model == I470TC || parser->model == I200CV2 || - parser->model == GEOAIR) { + parser->model == GEOAIR || parser->model == I100V2) { temperature = data[offset + 3]; } else if (parser->model == OCS || parser->model == TX1) { temperature = data[offset + 1]; @@ -976,7 +977,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I300C || parser->model == I200C || parser->model == GEO40 || parser->model == VEO40 || parser->model == I470TC || parser->model == I200CV2 || - parser->model == GEOAIR) + parser->model == GEOAIR || parser->model == I100V2) depth = (data[offset + 4] + (data[offset + 5] << 8)) & 0x0FFF; else if (parser->model == I330R || parser->model == DSX) depth = array_uint16_le (data + offset + 2); @@ -1043,7 +1044,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I450T || parser->model == I200C || parser->model == GEO40 || parser->model == VEO40 || parser->model == I470TC || parser->model == I200CV2 || - parser->model == GEOAIR) { + parser->model == GEOAIR || parser->model == I100V2) { decostop = (data[offset + 7] & 0xF0) >> 4; decotime = array_uint16_le(data + offset + 6) & 0x0FFF; have_deco = 1; diff --git a/src/oceanic_common.h b/src/oceanic_common.h index 43254a96..2e45f2a1 100644 --- a/src/oceanic_common.h +++ b/src/oceanic_common.h @@ -122,6 +122,7 @@ extern "C" { #define AMPHOSAIR2 0x4658 #define BEACON 0x4742 #define I470TC 0x4743 +#define I100V2 0x4745 #define I200CV2 0x4749 #define GEOAIR 0x474B From 2932809ca4b376cb3909a5c2878983b16f731c7b Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sat, 3 Aug 2024 17:55:10 +0200 Subject: [PATCH 02/17] Add a new Perdix 2 hardware ID --- src/shearwater_common.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shearwater_common.c b/src/shearwater_common.c index e92474bb..78d785ea 100644 --- a/src/shearwater_common.c +++ b/src/shearwater_common.c @@ -728,6 +728,7 @@ shearwater_common_get_model (shearwater_common_device_t *device, unsigned int ha model = PERDIXAI; break; case 0xC407: + case 0xC964: model = PERDIX2; break; case 0x0F0F: From 4d2b3bac2c0ce758a0c8e60617cbea4c281ac2da Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 17 Sep 2024 20:25:41 +0200 Subject: [PATCH 03/17] Add support for the i330R Console The i330R Console is a new variant of the i330R Wrist computer. The only difference is the new model number. Reported-by: Greg McLaughlin --- src/descriptor.c | 2 ++ src/oceanic_atom2_parser.c | 38 +++++++++++++++++++++----------------- src/oceanic_common.h | 1 + 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index 55cb3567..6013d6f1 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -276,6 +276,7 @@ static const dc_descriptor_t g_descriptors[] = { /* Pelagic I330R */ {"Apeks", "DSX", DC_FAMILY_PELAGIC_I330R, 0x4741, DC_TRANSPORT_BLE, dc_filter_oceanic}, {"Aqualung", "i330R", DC_FAMILY_PELAGIC_I330R, 0x4744, DC_TRANSPORT_BLE, dc_filter_oceanic}, + {"Aqualung", "i330R Console", DC_FAMILY_PELAGIC_I330R, 0x474D, DC_TRANSPORT_BLE, dc_filter_oceanic}, /* Mares Nemo */ {"Mares", "Nemo", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Nemo Steel", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL}, @@ -763,6 +764,7 @@ dc_filter_oceanic (dc_descriptor_t *descriptor, dc_transport_t transport, const 0x4744, // Aqualung i330R 0x4749, // Aqualung i200C 0x474B, // Oceanic Geo Air + 0x474D, // Aqualung i330R Console }; if (transport == DC_TRANSPORT_BLE) { diff --git a/src/oceanic_atom2_parser.c b/src/oceanic_atom2_parser.c index 38ac4472..cd27b357 100644 --- a/src/oceanic_atom2_parser.c +++ b/src/oceanic_atom2_parser.c @@ -144,7 +144,7 @@ oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, const uns } else if (model == I550C || model == WISDOM4 || model == I200CV2|| model == I100V2) { parser->headersize = 5 * PAGESIZE / 2; - } else if (model == I330R) { + } else if (model == I330R || model == I330R_C) { parser->logbooksize = 64; parser->headersize = parser->logbooksize + 80; parser->footersize = 48; @@ -195,6 +195,7 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim switch (parser->model) { case I330R: + case I330R_C: case DSX: datetime->year = p[7] + 2000; datetime->month = p[6]; @@ -391,7 +392,7 @@ oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser) } else if (parser->model == VEO20 || parser->model == VEO30 || parser->model == OCS) { mode = (data[1] & 0x60) >> 5; - } else if (parser->model == I330R) { + } else if (parser->model == I330R || parser->model == I330R_C) { mode = data[2]; } else if (parser->model == DSX) { mode = data[45]; @@ -452,7 +453,7 @@ oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser) } else if (parser->model == WISDOM4) { o2_offset = header + 4; ngasmixes = 1; - } else if (parser->model == I330R) { + } else if (parser->model == I330R || parser->model == I330R_C) { ngasmixes = 3; o2_offset = parser->logbooksize + 16; } else if (parser->model == DSX) { @@ -477,7 +478,8 @@ oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser) if (data[o2_offset + i * o2_step]) { parser->oxygen[i] = data[o2_offset + i * o2_step]; // The i330R uses 20 as "Air" and 21 as 21% Nitrox - if (parser->model == I330R && parser->oxygen[i] == 20) { + if ((parser->model == I330R || parser->model == I330R_C) && + parser->oxygen[i] == 20) { parser->oxygen[i] = 21; } } else { @@ -538,13 +540,13 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns parser->model == F11A || parser->model == F11B || parser->model == MUNDIAL2 || parser->model == MUNDIAL3) *((double *) value) = array_uint16_le (data + 4) / 16.0 * FEET; - else if (parser->model == I330R || parser->model == DSX) + else if (parser->model == I330R || parser->model == I330R_C || parser->model == DSX) *((double *) value) = array_uint16_le (data + parser->footer + 10) / 10.0 * FEET; else *((double *) value) = (array_uint16_le (data + parser->footer + 4) & 0x0FFF) / 16.0 * FEET; break; case DC_FIELD_AVGDEPTH: - if (parser->model == I330R || parser->model == DSX) { + if (parser->model == I330R || parser->model == I330R_C || parser->model == DSX) { *((double *) value) = array_uint16_le (data + parser->footer + 12) / 10.0 * FEET; } else { return DC_STATUS_UNSUPPORTED; @@ -568,7 +570,7 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns water->type = DC_WATER_SALT; } water->density = 0.0; - } else if (parser->model == I330R || parser->model == DSX) { + } else if (parser->model == I330R || parser->model == I330R_C || parser->model == DSX) { unsigned int settings = array_uint32_le (data + parser->logbooksize + 12); if (settings & 0x10000) { water->type = DC_WATER_FRESH; @@ -675,7 +677,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ unsigned int time = 0; unsigned int interval = 1000; if (!is_freedive (parser->mode, parser->model)) { - if (parser->model == I330R || parser->model == DSX) { + if (parser->model == I330R || parser->model == I330R_C || parser->model == DSX) { interval = data[parser->logbooksize + 36] * 1000; } else { unsigned int offset = 0x17; @@ -710,7 +712,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I750TC || parser->model == PROPLUSX || parser->model == I770R || parser->model == I470TC || parser->model == SAGE || parser->model == BEACON || - parser->model == GEOAIR || parser->model == I330R) { + parser->model == GEOAIR || parser->model == I330R || + parser->model == I330R_C) { samplesize = PAGESIZE; } else if (parser->model == DSX) { samplesize = 32; @@ -729,7 +732,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I300C || parser->model == TALIS || parser->model == I200C || parser->model == I200CV2 || parser->model == GEO40 || parser->model == VEO40 || - parser->model == I330R || parser->model == I100V2) { + parser->model == I330R || parser->model == I330R_C || + parser->model == I100V2) { have_pressure = 0; } @@ -898,7 +902,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I770R|| parser->model == SAGE || parser->model == BEACON) { temperature = data[offset + 11]; - } else if (parser->model == I330R || parser->model == DSX) { + } else if (parser->model == I330R || parser->model == I330R_C || parser->model == DSX) { temperature = array_uint16_le(data + offset + 10); } else { unsigned int sign; @@ -922,7 +926,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ else temperature += (data[offset + 7] & 0x0C) >> 2; } - if (parser->model == I330R || parser->model == DSX) { + if (parser->model == I330R || parser->model == I330R_C || parser->model == DSX) { sample.temperature = ((temperature / 10.0) - 32.0) * (5.0 / 9.0); } else { sample.temperature = (temperature - 32.0) * (5.0 / 9.0); @@ -979,13 +983,13 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I470TC || parser->model == I200CV2 || parser->model == GEOAIR || parser->model == I100V2) depth = (data[offset + 4] + (data[offset + 5] << 8)) & 0x0FFF; - else if (parser->model == I330R || parser->model == DSX) + else if (parser->model == I330R || parser->model == I330R_C || parser->model == DSX) depth = array_uint16_le (data + offset + 2); else if (parser->model == ATOM1) depth = data[offset + 3] * 16; else depth = (data[offset + 2] + (data[offset + 3] << 8)) & 0x0FFF; - if (parser->model == I330R || parser->model == DSX) { + if (parser->model == I330R || parser->model == I330R_C || parser->model == DSX) { sample.depth = depth / 10.0 * FEET; } else { sample.depth = depth / 16.0 * FEET; @@ -1048,7 +1052,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ decostop = (data[offset + 7] & 0xF0) >> 4; decotime = array_uint16_le(data + offset + 6) & 0x0FFF; have_deco = 1; - } else if (parser->model == I330R || parser->model == DSX) { + } else if (parser->model == I330R || parser->model == I330R_C || parser->model == DSX) { decostop = data[offset + 8]; if (decostop) { // Deco time @@ -1062,7 +1066,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ if (have_deco) { if (decostop) { sample.deco.type = DC_DECO_DECOSTOP; - if (parser->model == I330R || parser->model == DSX) { + if (parser->model == I330R || parser->model == I330R_C || parser->model == DSX) { sample.deco.depth = decostop * FEET; } else { sample.deco.depth = decostop * 10 * FEET; @@ -1101,7 +1105,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ } // PPO2 - if (parser->model == I330R) { + if (parser->model == I330R || parser->model == I330R_C) { sample.ppo2.sensor = DC_SENSOR_NONE; sample.ppo2.value = data[offset + 9] / 100.0; if (callback) callback (DC_SAMPLE_PPO2, &sample, userdata); diff --git a/src/oceanic_common.h b/src/oceanic_common.h index 2e45f2a1..0e184ba9 100644 --- a/src/oceanic_common.h +++ b/src/oceanic_common.h @@ -129,6 +129,7 @@ extern "C" { // i330r #define DSX 0x4741 #define I330R 0x4744 +#define I330R_C 0x474D #define PAGESIZE 0x10 #define FPMAXSIZE 0x200 From ea0cce4d12a3308c1b0780f7f2be8285dd5eaa43 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 6 May 2024 23:14:08 +0200 Subject: [PATCH 04/17] Return the correct error code Return the error code DC_STATUS_NOMEMORY when running out of memory instead of DC_STATUS_SUCCESS. --- src/cressi_goa.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cressi_goa.c b/src/cressi_goa.c index 9df10e8d..d0119f8b 100644 --- a/src/cressi_goa.c +++ b/src/cressi_goa.c @@ -414,6 +414,7 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v logbook = dc_buffer_new(4096); if (logbook == NULL) { ERROR (abstract->context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; goto error_exit; } @@ -455,6 +456,7 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v dive = dc_buffer_new(4096); if (dive == NULL) { ERROR (abstract->context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; goto error_free_logbook; } From 7acf53d5b8edc7812ea2733765a527e60b51e7e1 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 8 Oct 2024 19:03:44 +0200 Subject: [PATCH 05/17] Don't skip the last entry in the logbook buffer Due to an off by one error, the last entry in the logbook buffer wasn't processed at all. In theory that means the oldest dive is skipped and can't be downloaded. However the logbook buffer usually contains some empty entries at the front. Those are already detected by inspecting the dive number for a zero value and thus the download is already aborted before reaching that last entry. Therefore this bug didn't cause any problems yet in practice. --- src/cressi_goa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cressi_goa.c b/src/cressi_goa.c index d0119f8b..002b64b0 100644 --- a/src/cressi_goa.c +++ b/src/cressi_goa.c @@ -431,7 +431,7 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v // Count the number of dives. unsigned int count = 0; unsigned int offset = logbook_size; - while (offset > SZ_HEADER) { + while (offset >= SZ_HEADER) { // Move to the start of the logbook entry. offset -= SZ_HEADER; From 29ec91cdf3c831d551b12f4a0cdd7fe2cd2642ce Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 7 May 2024 10:07:59 +0200 Subject: [PATCH 06/17] Add ioctl's for reading and writing BLE characteristics Most BLE enabled dive computer use a simple BLE service with a TX and RX characteristic, which emulates a basic UART connection over two lines. This allows to use the I/O stream interface for the BLE communication. Unfortunately some dive computers (e.g Cressi) also have some additional characteristics that libdivecomputer must be able to read to retrieve some important data. Therefore, add some new ioctl's for reading and writing those BLE characteristics. --- include/libdivecomputer/ble.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/include/libdivecomputer/ble.h b/include/libdivecomputer/ble.h index a13fe87c..224756ba 100644 --- a/include/libdivecomputer/ble.h +++ b/include/libdivecomputer/ble.h @@ -48,6 +48,23 @@ extern "C" { #define DC_IOCTL_BLE_GET_ACCESSCODE DC_IOCTL_IOR('b', 2, DC_IOCTL_SIZE_VARIABLE) #define DC_IOCTL_BLE_SET_ACCESSCODE DC_IOCTL_IOW('b', 2, DC_IOCTL_SIZE_VARIABLE) +/** + * Perform a BLE characteristic read/write operation. + * + * The UUID of the characteristic must be specified as a #dc_ble_uuid_t + * data structure. If the operation requires additional data as in- or + * output, the buffer must be located immediately after the + * #dc_ble_uuid_t data structure. The size of the ioctl request is the + * total size, including the size of the #dc_ble_uuid_t structure. + */ +#define DC_IOCTL_BLE_CHARACTERISTIC_READ DC_IOCTL_IOR('b', 3, DC_IOCTL_SIZE_VARIABLE) +#define DC_IOCTL_BLE_CHARACTERISTIC_WRITE DC_IOCTL_IOW('b', 3, DC_IOCTL_SIZE_VARIABLE) + +/** + * Bluetooth UUID (128 bits). + */ +typedef unsigned char dc_ble_uuid_t[16]; + #ifdef __cplusplus } #endif /* __cplusplus */ From 88d78a323c468885965803032e195fe0c7f5b615 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 23 Jul 2024 11:00:54 +0200 Subject: [PATCH 07/17] Add helper functions to convert UUID to/from strings Add some helper functions to convert the binary UUID data structure to and from their human readable string representation. --- contrib/android/Android.mk | 1 + contrib/msvc/libdivecomputer.vcxproj | 1 + include/libdivecomputer/ble.h | 36 +++++++++++ src/Makefile.am | 1 + src/ble.c | 97 ++++++++++++++++++++++++++++ src/libdivecomputer.symbols | 3 + 6 files changed, 139 insertions(+) create mode 100644 src/ble.c diff --git a/contrib/android/Android.mk b/contrib/android/Android.mk index 338c6be7..fcefbb98 100644 --- a/contrib/android/Android.mk +++ b/contrib/android/Android.mk @@ -9,6 +9,7 @@ LOCAL_SRC_FILES := \ src/array.c \ src/atomics_cobalt.c \ src/atomics_cobalt_parser.c \ + src/ble.c \ src/bluetooth.c \ src/buffer.c \ src/checksum.c \ diff --git a/contrib/msvc/libdivecomputer.vcxproj b/contrib/msvc/libdivecomputer.vcxproj index 991f1ac8..d9176e7a 100644 --- a/contrib/msvc/libdivecomputer.vcxproj +++ b/contrib/msvc/libdivecomputer.vcxproj @@ -177,6 +177,7 @@ + diff --git a/include/libdivecomputer/ble.h b/include/libdivecomputer/ble.h index 224756ba..6529747f 100644 --- a/include/libdivecomputer/ble.h +++ b/include/libdivecomputer/ble.h @@ -60,11 +60,47 @@ extern "C" { #define DC_IOCTL_BLE_CHARACTERISTIC_READ DC_IOCTL_IOR('b', 3, DC_IOCTL_SIZE_VARIABLE) #define DC_IOCTL_BLE_CHARACTERISTIC_WRITE DC_IOCTL_IOW('b', 3, DC_IOCTL_SIZE_VARIABLE) +/** + * The minimum number of bytes (including the terminating null byte) for + * formatting a bluetooth UUID as a string. + */ +#define DC_BLE_UUID_SIZE 37 + /** * Bluetooth UUID (128 bits). */ typedef unsigned char dc_ble_uuid_t[16]; +/** + * Convert a bluetooth UUID to a string. + * + * The bluetooth UUID is formatted as + * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, where each XX pair is a + * hexadecimal number specifying an octet of the UUID. + * The minimum size for the buffer is #DC_BLE_UUID_SIZE bytes. + * + * @param[in] uuid A bluetooth UUID. + * @param[in] str The memory buffer to store the result. + * @param[in] size The size of the memory buffer. + * @returns The null-terminated string on success, or NULL on failure. + */ +char * +dc_ble_uuid2str (const dc_ble_uuid_t uuid, char *str, size_t size); + +/** + * Convert a string to a bluetooth UUID. + * + * The string is expected to be in the format + * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, where each XX pair is a + * hexadecimal number specifying an octet of the UUID. + * + * @param[in] str A null-terminated string. + * @param[in] uuid The memory buffer to store the result. + * @returns Non-zero on success, or zero on failure. + */ +int +dc_ble_str2uuid (const char *str, dc_ble_uuid_t uuid); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/Makefile.am b/src/Makefile.am index 16680d2a..817da96d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -88,6 +88,7 @@ libdivecomputer_la_SOURCES = \ irda.c \ usb.c \ usbhid.c \ + ble.c \ bluetooth.c \ custom.c diff --git a/src/ble.c b/src/ble.c new file mode 100644 index 00000000..c8944c64 --- /dev/null +++ b/src/ble.c @@ -0,0 +1,97 @@ +/* + * libdivecomputer + * + * Copyright (C) 2024 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include + +#include "platform.h" + +char * +dc_ble_uuid2str (const dc_ble_uuid_t uuid, char *str, size_t size) +{ + if (str == NULL || size < DC_BLE_UUID_SIZE) + return NULL; + + int n = dc_platform_snprintf(str, size, + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + uuid[0], uuid[1], uuid[2], uuid[3], + uuid[4], uuid[5], + uuid[6], uuid[7], + uuid[8], uuid[9], + uuid[10], uuid[11], uuid[12], + uuid[13], uuid[14], uuid[15]); + if (n < 0 || (size_t) n >= size) + return NULL; + + return str; +} + +int +dc_ble_str2uuid (const char *str, dc_ble_uuid_t uuid) +{ + dc_ble_uuid_t tmp = {0}; + + if (str == NULL || uuid == NULL) + return 0; + + unsigned int i = 0; + unsigned char c = 0; + while ((c = *str++) != '\0') { + if (c == '-') { + if (i != 8 && i != 12 && i != 16 && i != 20) { + return 0; /* Invalid character! */ + } + continue; + } else if (c >= '0' && c <= '9') { + c -= '0'; + } else if (c >= 'A' && c <= 'F') { + c -= 'A' - 10; + } else if (c >= 'a' && c <= 'f') { + c -= 'a' - 10; + } else { + return 0; /* Invalid character! */ + } + + if ((i & 1) == 0) { + c <<= 4; + } + + if (i >= 2 * sizeof(tmp)) { + return 0; /* Too many characters! */ + } + + tmp[i / 2] |= c; + i++; + } + + if (i != 2 * sizeof(tmp)) { + return 0; /* Not enough characters! */ + } + + memcpy (uuid, tmp, sizeof(tmp)); + + return 1; +} diff --git a/src/libdivecomputer.symbols b/src/libdivecomputer.symbols index c8298520..36bd5f89 100644 --- a/src/libdivecomputer.symbols +++ b/src/libdivecomputer.symbols @@ -66,6 +66,9 @@ dc_bluetooth_device_free dc_bluetooth_iterator_new dc_bluetooth_open +dc_ble_uuid2str +dc_ble_str2uuid + dc_irda_device_get_address dc_irda_device_get_name dc_irda_device_free From 6f4e4eea6ee6b08fcb158ae5882f1f4c72fcfebd Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 17 May 2024 09:21:33 +0200 Subject: [PATCH 08/17] Add support for the Cressi BLE protocol variant The BLE variant of the Cressi protocol uses a completely different set of commands compared to the existing usb-serial protocol. Most of the packet framing bytes have been removed as well. But the most surprising change is the fact that there is no variant of the CMD_VERSION available. The corresponding information must be obtained by reading some secondary characteristics instead. Co-authored-by: Greg McLaughlin Co-authored-by: Sven Knoch --- src/cressi_goa.c | 203 ++++++++++++++++++++++++++++++++++++----------- src/descriptor.c | 68 ++++++++++++++-- 2 files changed, 220 insertions(+), 51 deletions(-) diff --git a/src/cressi_goa.c b/src/cressi_goa.c index 002b64b0..2c71b8aa 100644 --- a/src/cressi_goa.c +++ b/src/cressi_goa.c @@ -23,6 +23,8 @@ #include // malloc, free #include // assert +#include + #include "cressi_goa.h" #include "context-private.h" #include "device-private.h" @@ -36,6 +38,9 @@ #define CMD_LOGBOOK 0x21 #define CMD_DIVE 0x22 +#define CMD_LOGBOOK_BLE 0x02 +#define CMD_DIVE_BLE 0x03 + #define HEADER 0xAA #define TRAILER 0x55 #define END 0x04 @@ -77,6 +82,7 @@ cressi_goa_device_send (cressi_goa_device_t *device, unsigned char cmd, const un { dc_status_t status = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; + dc_transport_t transport = dc_iostream_get_transport (device->iostream); if (size > SZ_PACKET) { ERROR (abstract->context, "Unexpected payload size (%u).", size); @@ -100,10 +106,15 @@ cressi_goa_device_send (cressi_goa_device_t *device, unsigned char cmd, const un // Wait a small amount of time before sending the command. Without // this delay, the transfer will fail most of the time. - dc_iostream_sleep (device->iostream, 100); + unsigned int delay = transport == DC_TRANSPORT_BLE ? 2000 : 100; + dc_iostream_sleep (device->iostream, delay); // Send the command to the device. - status = dc_iostream_write (device->iostream, packet, size + 8, NULL); + if (transport == DC_TRANSPORT_BLE) { + status = dc_iostream_write (device->iostream, packet + 4, size + 1, NULL); + } else { + status = dc_iostream_write (device->iostream, packet, size + 8, NULL); + } if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the command."); return status; @@ -117,9 +128,18 @@ cressi_goa_device_receive (cressi_goa_device_t *device, unsigned char data[], un { dc_status_t status = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; + dc_transport_t transport = dc_iostream_get_transport (device->iostream); unsigned char packet[SZ_PACKET + 8]; + if (transport == DC_TRANSPORT_BLE) { + if (size) { + return DC_STATUS_INVALIDARGS; + } else { + return DC_STATUS_SUCCESS; + } + } + // Read the header of the data packet. status = dc_iostream_read (device->iostream, packet, 4, NULL); if (status != DC_STATUS_SUCCESS) { @@ -179,6 +199,7 @@ cressi_goa_device_download (cressi_goa_device_t *device, dc_buffer_t *buffer, dc { dc_status_t status = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; + dc_transport_t transport = dc_iostream_get_transport (device->iostream); const unsigned char ack[] = {ACK}; const unsigned int initial = progress ? progress->current : 0; @@ -193,27 +214,43 @@ cressi_goa_device_download (cressi_goa_device_t *device, dc_buffer_t *buffer, dc unsigned int size = 2; unsigned int nbytes = 0; while (nbytes < size) { - // Read the data packet. unsigned char packet[3 + SZ_DATA + 2]; - status = dc_iostream_read (device->iostream, packet, sizeof(packet), NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to receive the answer."); - return status; - } - // Verify the checksum of the packet. - unsigned short crc = array_uint16_le (packet + sizeof(packet) - 2); - unsigned short ccrc = checksum_crc16_ccitt (packet + 3, sizeof(packet) - 5, 0x0000, 0x0000); - if (crc != ccrc) { - ERROR (abstract->context, "Unexpected answer checksum."); - return DC_STATUS_PROTOCOL; - } - - // Send the ack byte to the device. - status = dc_iostream_write (device->iostream, ack, sizeof(ack), NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to send the ack byte."); - return status; + if (transport == DC_TRANSPORT_BLE) { + // Read the data packet. + unsigned int packetsize = 0; + while (packetsize < SZ_DATA) { + size_t len = 0; + status = dc_iostream_read (device->iostream, packet + 3 + packetsize, SZ_DATA - packetsize, &len); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the answer."); + return status; + } + + packetsize += len; + } + } else { + // Read the data packet. + status = dc_iostream_read (device->iostream, packet, sizeof(packet), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the answer."); + return status; + } + + // Verify the checksum of the packet. + unsigned short crc = array_uint16_le (packet + sizeof(packet) - 2); + unsigned short ccrc = checksum_crc16_ccitt (packet + 3, sizeof(packet) - 5, 0x0000, 0x0000); + if (crc != ccrc) { + ERROR (abstract->context, "Unexpected answer checksum."); + return DC_STATUS_PROTOCOL; + } + + // Send the ack byte to the device. + status = dc_iostream_write (device->iostream, ack, sizeof(ack), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the ack byte."); + return status; + } } // Get the total size from the first data packet. @@ -243,25 +280,45 @@ cressi_goa_device_download (cressi_goa_device_t *device, dc_buffer_t *buffer, dc } } - // Read the end 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 end byte."); - return status; - } + if (transport == DC_TRANSPORT_BLE) { + // Read the end bytes. + unsigned char end[16] = {0}; + size_t len = 0; + status = dc_iostream_read (device->iostream, end, sizeof(end), &len); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the end bytes."); + return status; + } - // Verify the end byte. - if (end != END) { - ERROR (abstract->context, "Unexpected end byte (%02x).", end); - return DC_STATUS_PROTOCOL; - } + // Verify the end bytes ("EOT xmodem"). + const unsigned char validate[16] = { + 0x45, 0x4F, 0x54, 0x20, 0x78, 0x6D, 0x6F, 0x64, + 0x65, 0x6D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + if (memcmp (end, validate, sizeof(validate)) != 0) { + ERROR (abstract->context, "Unexpected end bytes."); + return DC_STATUS_PROTOCOL; + } + } else { + // Read the end 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 end byte."); + return status; + } - // Send the ack byte to the device. - status = dc_iostream_write (device->iostream, ack, sizeof(ack), NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to send the ack byte."); - return status; + // Verify the end byte. + if (end != END) { + ERROR (abstract->context, "Unexpected end byte (%02x).", end); + return DC_STATUS_PROTOCOL; + } + + // Send the ack byte to the device. + status = dc_iostream_write (device->iostream, ack, sizeof(ack), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the ack byte."); + return status; + } } return status; @@ -328,8 +385,10 @@ cressi_goa_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t goto error_free; } - // Set the timeout for receiving data (3000 ms). - status = dc_iostream_set_timeout (device->iostream, 3000); + // Set the timeout for receiving data (3000 - 5000 ms). + dc_transport_t transport = dc_iostream_get_transport (device->iostream); + int timeout = transport == DC_TRANSPORT_BLE ? 5000 : 3000; + status = dc_iostream_set_timeout (device->iostream, timeout); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to set the timeout."); goto error_free; @@ -382,6 +441,7 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v { dc_status_t status = DC_STATUS_SUCCESS; cressi_goa_device_t *device = (cressi_goa_device_t *) abstract; + dc_transport_t transport = dc_iostream_get_transport (device->iostream); dc_buffer_t *logbook = NULL; dc_buffer_t *dive = NULL; @@ -391,10 +451,49 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v // Read the version information. unsigned char id[9] = {0}; - status = cressi_goa_device_transfer (device, CMD_VERSION, NULL, 0, id, sizeof(id), NULL, NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the version information."); - goto error_exit; + if (transport == DC_TRANSPORT_BLE) { + /* + * With the BLE communication, there is no variant of the CMD_VERSION + * command available. The corresponding information must be obtained by + * reading some secondary characteristics instead: + * 6E400003-B5A3-F393-E0A9-E50E24DC10B8 - 5 bytes + * 6E400004-B5A3-F393-E0A9-E50E24DC10B8 - 2 bytes + * 6E400005-B5A3-F393-E0A9-E50E24DC10B8 - 2 bytes + */ + const dc_ble_uuid_t characteristics[] = { + {0x6E, 0x40, 0x00, 0x03, 0xB5, 0xA3, 0xF3, 0x93, 0xE0, 0xA9, 0xE5, 0x0E, 0x24, 0xDC, 0x10, 0xB8}, + {0x6E, 0x40, 0x00, 0x04, 0xB5, 0xA3, 0xF3, 0x93, 0xE0, 0xA9, 0xE5, 0x0E, 0x24, 0xDC, 0x10, 0xB8}, + {0x6E, 0x40, 0x00, 0x05, 0xB5, 0xA3, 0xF3, 0x93, 0xE0, 0xA9, 0xE5, 0x0E, 0x24, 0xDC, 0x10, 0xB8}, + }; + const size_t sizes[] = {5, 2, 2}; + + unsigned int offset = 0; + for (size_t i = 0; i < C_ARRAY_SIZE(characteristics); ++i) { + unsigned char request[sizeof(dc_ble_uuid_t) + 5] = {0}; + + // Setup the request. + memcpy (request, characteristics[i], sizeof(dc_ble_uuid_t)); + memset (request + sizeof(dc_ble_uuid_t), 0, sizes[i]); + + // Read the characteristic. + status = dc_iostream_ioctl (device->iostream, DC_IOCTL_BLE_CHARACTERISTIC_READ, request, sizeof(dc_ble_uuid_t) + sizes[i]); + if (status != DC_STATUS_SUCCESS) { + char uuidstr[DC_BLE_UUID_SIZE] = {0}; + ERROR (abstract->context, "Failed to read the characteristic '%s'.", + dc_ble_uuid2str(characteristics[i], uuidstr, sizeof(uuidstr))); + goto error_exit; + } + + // Copy the payload data. + memcpy (id + offset, request + sizeof(dc_ble_uuid_t), sizes[i]); + offset += sizes[i]; + } + } else { + status = cressi_goa_device_transfer (device, CMD_VERSION, NULL, 0, id, sizeof(id), NULL, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the version information."); + goto error_exit; + } } // Emit a vendor event. @@ -419,7 +518,12 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v } // Read the logbook data. - status = cressi_goa_device_transfer (device, CMD_LOGBOOK, NULL, 0, NULL, 0, logbook, &progress); + if (transport == DC_TRANSPORT_BLE) { + unsigned char args[] = {0x00}; + status = cressi_goa_device_transfer (device, CMD_LOGBOOK_BLE, args, sizeof(args), NULL, 0, logbook, &progress); + } else { + status = cressi_goa_device_transfer (device, CMD_LOGBOOK, NULL, 0, NULL, 0, logbook, &progress); + } if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the logbook data."); goto error_free_logbook; @@ -467,7 +571,14 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v offset -= SZ_HEADER; // Read the dive data. - status = cressi_goa_device_transfer (device, CMD_DIVE, logbook_data + offset, 2, NULL, 0, dive, &progress); + if (transport == DC_TRANSPORT_BLE) { + unsigned char number[2] = { + logbook_data[offset + 1], + logbook_data[offset + 0]}; + status = cressi_goa_device_transfer (device, CMD_DIVE_BLE, number, 2, NULL, 0, dive, &progress); + } else { + status = cressi_goa_device_transfer (device, CMD_DIVE, logbook_data + offset, 2, NULL, 0, dive, &progress); + } if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the dive data."); goto error_free_dive; diff --git a/src/descriptor.c b/src/descriptor.c index 6013d6f1..d7e72427 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -59,6 +59,7 @@ static int dc_filter_deepsix (dc_descriptor_t *descriptor, dc_transport_t transp 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 dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item); @@ -340,11 +341,11 @@ static const dc_descriptor_t g_descriptors[] = { {"Cressi", "Newton", DC_FAMILY_CRESSI_LEONARDO, 5, DC_TRANSPORT_SERIAL, NULL}, {"Cressi", "Drake", DC_FAMILY_CRESSI_LEONARDO, 6, DC_TRANSPORT_SERIAL, NULL}, /* Cressi Goa */ - {"Cressi", "Cartesio", DC_FAMILY_CRESSI_GOA, 1, DC_TRANSPORT_SERIAL, NULL}, - {"Cressi", "Goa", DC_FAMILY_CRESSI_GOA, 2, DC_TRANSPORT_SERIAL, NULL}, - {"Cressi", "Donatello", DC_FAMILY_CRESSI_GOA, 4, DC_TRANSPORT_SERIAL, NULL}, - {"Cressi", "Michelangelo", DC_FAMILY_CRESSI_GOA, 5, DC_TRANSPORT_SERIAL, NULL}, - {"Cressi", "Neon", DC_FAMILY_CRESSI_GOA, 9, DC_TRANSPORT_SERIAL, NULL}, + {"Cressi", "Cartesio", DC_FAMILY_CRESSI_GOA, 1, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, + {"Cressi", "Goa", DC_FAMILY_CRESSI_GOA, 2, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, + {"Cressi", "Donatello", DC_FAMILY_CRESSI_GOA, 4, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, + {"Cressi", "Michelangelo", DC_FAMILY_CRESSI_GOA, 5, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, + {"Cressi", "Neon", DC_FAMILY_CRESSI_GOA, 9, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, /* Zeagle N2iTiON3 */ {"Zeagle", "N2iTiON3", DC_FAMILY_ZEAGLE_N2ITION3, 0, DC_TRANSPORT_SERIAL, NULL}, {"Apeks", "Quantum X", DC_FAMILY_ZEAGLE_N2ITION3, 0, DC_TRANSPORT_SERIAL, NULL}, @@ -546,6 +547,31 @@ dc_match_number_with_prefix (const void *key, const void *value) return 1; } +static int +dc_match_hex_with_prefix (const void *key, const void *value) +{ + const char *str = (const char *) key; + const char *prefix = *(const char * const *) value; + + size_t n = strlen (prefix); + + if (strncmp (str, prefix, n) != 0) { + return 0; + } + + while (str[n] != 0) { + const char c = str[n]; + if ((c < '0' || c > '9') && + (c < 'A' || c > 'F') && + (c < 'a' || c > 'f')) { + return 0; + } + n++; + } + + return 1; +} + static int dc_match_oceanic (const void *key, const void *value) { @@ -562,6 +588,20 @@ dc_match_oceanic (const void *key, const void *value) return dc_match_number_with_prefix (key, &p); } +static int +dc_match_cressi (const void *key, const void *value) +{ + unsigned int model = *(const unsigned int *) value; + + char prefix[16] = {0}; + + dc_platform_snprintf(prefix, sizeof(prefix), "%u_", model); + + const char *p = prefix; + + return dc_match_hex_with_prefix (key, &p); +} + static int dc_filter_internal (const void *key, const void *values, size_t count, size_t size, dc_match_t match) { @@ -864,6 +904,24 @@ dc_filter_divesoft (dc_descriptor_t *descriptor, dc_transport_t transport, const return 1; } +static int +dc_filter_cressi (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +{ + static const unsigned int model[] = { + 1, // Cartesio + 2, // Goa + 4, // Donatello + 5, // Michelangelo + 9, // Neon + }; + + if (transport == DC_TRANSPORT_BLE) { + return DC_FILTER_INTERNAL (userdata, model, 0, dc_match_cressi); + } + + return 1; +} + dc_status_t dc_descriptor_iterator (dc_iterator_t **out) { From b6051710c88d7c1cf7eb65a66547eebf66dc01df Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 12 Sep 2024 19:41:19 +0200 Subject: [PATCH 09/17] Remove the unused model parameter --- src/cressi_goa.h | 2 +- src/cressi_goa_parser.c | 5 +---- src/parser.c | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/cressi_goa.h b/src/cressi_goa.h index 63283a07..b7c79de1 100644 --- a/src/cressi_goa.h +++ b/src/cressi_goa.h @@ -35,7 +35,7 @@ dc_status_t cressi_goa_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); dc_status_t -cressi_goa_parser_create (dc_parser_t **parser, dc_context_t *context, const unsigned char data[], size_t size, unsigned int model); +cressi_goa_parser_create (dc_parser_t **parser, dc_context_t *context, const unsigned char data[], size_t size); #ifdef __cplusplus } diff --git a/src/cressi_goa_parser.c b/src/cressi_goa_parser.c index 28986622..cda73990 100644 --- a/src/cressi_goa_parser.c +++ b/src/cressi_goa_parser.c @@ -48,7 +48,6 @@ typedef struct cressi_goa_parser_t cressi_goa_parser_t; struct cressi_goa_parser_t { dc_parser_t base; - unsigned int model; }; typedef struct cressi_goa_layout_t { @@ -126,7 +125,7 @@ static const cressi_goa_layout_t layouts[] = { }; dc_status_t -cressi_goa_parser_create (dc_parser_t **out, dc_context_t *context, const unsigned char data[], size_t size, unsigned int model) +cressi_goa_parser_create (dc_parser_t **out, dc_context_t *context, const unsigned char data[], size_t size) { cressi_goa_parser_t *parser = NULL; @@ -140,8 +139,6 @@ cressi_goa_parser_create (dc_parser_t **out, dc_context_t *context, const unsign return DC_STATUS_NOMEMORY; } - parser->model = model; - *out = (dc_parser_t*) parser; return DC_STATUS_SUCCESS; diff --git a/src/parser.c b/src/parser.c index bc143f45..1fea1654 100644 --- a/src/parser.c +++ b/src/parser.c @@ -155,7 +155,7 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, const unsigned rc = cressi_leonardo_parser_create (&parser, context, data, size, model); break; case DC_FAMILY_CRESSI_GOA: - rc = cressi_goa_parser_create (&parser, context, data, size, model); + rc = cressi_goa_parser_create (&parser, context, data, size); break; case DC_FAMILY_ATOMICS_COBALT: rc = atomics_cobalt_parser_create (&parser, context, data, size); From 5fa57cf5bb5c4539d1ab9564dcb630a4461a66e0 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 13 Sep 2024 21:26:39 +0200 Subject: [PATCH 10/17] Add the device id and logbook entry Previously, in commit 57cd11fffe4c44d6323a4d5b2bfd04e9e0f5a7a4, the part of the logbook entry containing the dive mode was already inserted back into the dive data, because the dive mode is needed to be able to parse the dive. Unfortunately, the newer models like the Cressi Nepto store even more important information in the logbook entry and the dive computer id data. Therefore, the data format is changed once more to preserve both pieces of data. The parser is updated to handle this new format. This change is not strictly backwards compatible, but if needed existing data can be converted into the new format. --- src/cressi_goa.c | 19 ++-- src/cressi_goa_parser.c | 187 +++++++++++++++++++++++----------------- 2 files changed, 120 insertions(+), 86 deletions(-) diff --git a/src/cressi_goa.c b/src/cressi_goa.c index 2c71b8aa..868fb466 100644 --- a/src/cressi_goa.c +++ b/src/cressi_goa.c @@ -598,10 +598,19 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v goto error_free_dive; } - // Those 5 extra bytes contain the dive mode, which is required for - // parsing the dive data. Therefore, insert all 5 bytes again. The - // remaining 4 bytes appear to be some 32 bit address. - if (!dc_buffer_insert (dive, 2, logbook_data + offset + 2, 5)) { + // The dive computer id data and the logbook entry contain important + // information that is required for parsing the dive data, but is + // not present in the dive data itself. Therefore, both pieces of data + // are prepended to the dive data, along with a small header containing + // their size. + const unsigned char header[] = { + sizeof(id), + SZ_HEADER, + }; + unsigned int headersize = sizeof(header) + sizeof(id) + SZ_HEADER; + if (!dc_buffer_prepend(dive, logbook_data + offset, SZ_HEADER) || + !dc_buffer_prepend(dive, id, sizeof(id)) || + !dc_buffer_prepend(dive, header, sizeof(header))) { ERROR (abstract->context, "Out of memory."); status = DC_STATUS_NOMEMORY; goto error_free_dive; @@ -610,7 +619,7 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v dive_data = dc_buffer_get_data (dive); dive_size = dc_buffer_get_size (dive); - if (callback && !callback(dive_data, dive_size, dive_data + FP_OFFSET, sizeof(device->fingerprint), userdata)) + if (callback && !callback(dive_data, dive_size, dive_data + headersize + FP_OFFSET - 5, sizeof(device->fingerprint), userdata)) break; } diff --git a/src/cressi_goa_parser.c b/src/cressi_goa_parser.c index cda73990..f8fd2a3a 100644 --- a/src/cressi_goa_parser.c +++ b/src/cressi_goa_parser.c @@ -46,10 +46,6 @@ typedef struct cressi_goa_parser_t cressi_goa_parser_t; -struct cressi_goa_parser_t { - dc_parser_t base; -}; - typedef struct cressi_goa_layout_t { unsigned int headersize; unsigned int datetime; @@ -61,6 +57,14 @@ typedef struct cressi_goa_layout_t { unsigned int temperature; } cressi_goa_layout_t; +struct cressi_goa_parser_t { + dc_parser_t base; + const cressi_goa_layout_t *layout; + unsigned int headersize; + unsigned int version; + unsigned int divemode; +}; + static dc_status_t cressi_goa_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); static dc_status_t cressi_goa_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); static dc_status_t cressi_goa_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); @@ -80,53 +84,103 @@ static const dc_parser_vtable_t cressi_goa_parser_vtable = { static const cressi_goa_layout_t layouts[] = { /* SCUBA */ { - 0x61, /* headersize */ - 0x11, /* datetime */ - 0x19, /* divetime */ - 0x1F, /* gasmix */ - 0x23, /* atmospheric */ - 0x4E, /* maxdepth */ - 0x50, /* avgdepth */ - 0x52, /* temperature */ + 92, /* headersize */ + 12, /* datetime */ + 20, /* divetime */ + 26, /* gasmix */ + 30, /* atmospheric */ + 73, /* maxdepth */ + 75, /* avgdepth */ + 77, /* temperature */ }, /* NITROX */ { - 0x61, /* headersize */ - 0x11, /* datetime */ - 0x19, /* divetime */ - 0x1F, /* gasmix */ - 0x23, /* atmospheric */ - 0x4E, /* maxdepth */ - 0x50, /* avgdepth */ - 0x52, /* temperature */ + 92, /* headersize */ + 12, /* datetime */ + 20, /* divetime */ + 26, /* gasmix */ + 30, /* atmospheric */ + 73, /* maxdepth */ + 75, /* avgdepth */ + 77, /* temperature */ }, /* FREEDIVE */ { - 0x2B, /* headersize */ - 0x11, /* datetime */ - 0x19, /* divetime */ + 38, /* headersize */ + 12, /* datetime */ + 20, /* divetime */ UNDEFINED, /* gasmix */ UNDEFINED, /* atmospheric */ - 0x1C, /* maxdepth */ + 23, /* maxdepth */ UNDEFINED, /* avgdepth */ - 0x1E, /* temperature */ + 25, /* temperature */ }, /* GAUGE */ { - 0x2D, /* headersize */ - 0x11, /* datetime */ - 0x19, /* divetime */ + 40, /* headersize */ + 12, /* datetime */ + 20, /* divetime */ UNDEFINED, /* gasmix */ - 0x1B, /* atmospheric */ - 0x1D, /* maxdepth */ - 0x1F, /* avgdepth */ - 0x21, /* temperature */ + 22, /* atmospheric */ + 24, /* maxdepth */ + 26, /* avgdepth */ + 28, /* temperature */ }, }; +static dc_status_t +cressi_goa_init(cressi_goa_parser_t *parser) +{ + dc_parser_t *abstract = (dc_parser_t *) parser; + const unsigned char *data = abstract->data; + unsigned int size = abstract->size; + + if (size < 2) { + ERROR (abstract->context, "Invalid dive length (%u).", size); + return DC_STATUS_DATAFORMAT; + } + + unsigned int id_len = data[0]; + unsigned int logbook_len = data[1]; + if (id_len < 9 || logbook_len < SZ_HEADER) { + ERROR (abstract->context, "Invalid id or logbook length (%u %u).", id_len, logbook_len); + return DC_STATUS_DATAFORMAT; + } + + if (size < 2 + id_len + logbook_len) { + ERROR (abstract->context, "Invalid dive length (%u).", size); + return DC_STATUS_DATAFORMAT; + } + + const unsigned char *logbook = data + 2 + id_len; + + // Get the dive mode. + unsigned int divemode = logbook[2]; + if (divemode >= C_ARRAY_SIZE(layouts)) { + ERROR (abstract->context, "Invalid dive mode (%u).", divemode); + return DC_STATUS_DATAFORMAT; + } + + // Get the layout. + const cressi_goa_layout_t *layout = &layouts[divemode]; + + unsigned int headersize = 2 + id_len + logbook_len; + if (size < headersize + layout->headersize) { + ERROR (abstract->context, "Invalid dive length (%u).", size); + return DC_STATUS_DATAFORMAT; + } + + parser->layout = layout; + parser->headersize = headersize; + parser->divemode = divemode; + + return DC_STATUS_SUCCESS; +} + dc_status_t cressi_goa_parser_create (dc_parser_t **out, dc_context_t *context, const unsigned char data[], size_t size) { + dc_status_t status = DC_STATUS_SUCCESS; cressi_goa_parser_t *parser = NULL; if (out == NULL) @@ -139,31 +193,25 @@ cressi_goa_parser_create (dc_parser_t **out, dc_context_t *context, const unsign return DC_STATUS_NOMEMORY; } + status = cressi_goa_init(parser); + if (status != DC_STATUS_SUCCESS) + goto error_free; + *out = (dc_parser_t*) parser; return DC_STATUS_SUCCESS; + +error_free: + dc_parser_deallocate ((dc_parser_t *) parser); + return status; } static dc_status_t cressi_goa_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) { - const unsigned char *data = abstract->data; - unsigned int size = abstract->size; - - if (size < SZ_HEADER) - return DC_STATUS_DATAFORMAT; + cressi_goa_parser_t *parser = (cressi_goa_parser_t *) abstract; - unsigned int divemode = data[2]; - if (divemode >= C_ARRAY_SIZE(layouts)) { - return DC_STATUS_DATAFORMAT; - } - - const cressi_goa_layout_t *layout = &layouts[divemode]; - - if (size < layout->headersize) - return DC_STATUS_DATAFORMAT; - - const unsigned char *p = abstract->data + layout->datetime; + const unsigned char *p = abstract->data + parser->headersize + parser->layout->datetime; if (datetime) { datetime->year = array_uint16_le(p); @@ -181,21 +229,9 @@ cressi_goa_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) static dc_status_t cressi_goa_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) { - const unsigned char *data = abstract->data; - unsigned int size = abstract->size; - - if (size < SZ_HEADER) - return DC_STATUS_DATAFORMAT; - - unsigned int divemode = data[2]; - if (divemode >= C_ARRAY_SIZE(layouts)) { - return DC_STATUS_DATAFORMAT; - } - - const cressi_goa_layout_t *layout = &layouts[divemode]; - - if (size < layout->headersize) - return DC_STATUS_DATAFORMAT; + cressi_goa_parser_t *parser = (cressi_goa_parser_t *) abstract; + const cressi_goa_layout_t *layout = parser->layout; + const unsigned char *data = abstract->data + parser->headersize; unsigned int ngasmixes = 0; if (layout->gasmix != UNDEFINED) { @@ -245,7 +281,7 @@ cressi_goa_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_DIVEMODE: - switch (divemode) { + switch (parser->divemode) { case SCUBA: case NITROX: *((dc_divemode_t *) value) = DC_DIVEMODE_OC; @@ -271,23 +307,12 @@ cressi_goa_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsign static dc_status_t cressi_goa_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) { - const unsigned char *data = abstract->data; - unsigned int size = abstract->size; - - if (size < SZ_HEADER) - return DC_STATUS_DATAFORMAT; - - unsigned int divemode = data[2]; - if (divemode >= C_ARRAY_SIZE(layouts)) { - return DC_STATUS_DATAFORMAT; - } - - const cressi_goa_layout_t *layout = &layouts[divemode]; - - if (size < layout->headersize) - return DC_STATUS_DATAFORMAT; + cressi_goa_parser_t *parser = (cressi_goa_parser_t *) abstract; + const cressi_goa_layout_t *layout = parser->layout; + const unsigned char *data = abstract->data + parser->headersize; + unsigned int size = abstract->size - parser->headersize; - unsigned int interval = divemode == FREEDIVE ? 2 : 5; + unsigned int interval = parser->divemode == FREEDIVE ? 2 : 5; unsigned int time = 0; unsigned int depth = 0; @@ -334,7 +359,7 @@ cressi_goa_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t c if (callback) callback (DC_SAMPLE_DEPTH, &sample, userdata); // Gas change - if (divemode == SCUBA || divemode == NITROX) { + if (parser->divemode == SCUBA || parser->divemode == NITROX) { if (gasmix != gasmix_previous) { sample.gasmix = gasmix; if (callback) callback (DC_SAMPLE_GASMIX, &sample, userdata); From f65c90bf8b6381978cd1479365df7e1e88b3b5ba Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Mon, 22 Apr 2024 20:36:36 +0200 Subject: [PATCH 11/17] Add an extra surface sample The extra surface sample results in a nicer and more correct graph when plotting the depth profile. --- src/cressi_goa_parser.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/cressi_goa_parser.c b/src/cressi_goa_parser.c index f8fd2a3a..5c6e9d21 100644 --- a/src/cressi_goa_parser.c +++ b/src/cressi_goa_parser.c @@ -339,7 +339,21 @@ cressi_goa_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t c temperature = value; have_temperature = 1; } else if (type == TIME) { - time += value; + unsigned int surftime = value; + if (surftime > interval) { + surftime -= interval; + time += interval; + + // Time (seconds). + sample.time = time * 1000; + if (callback) callback (DC_SAMPLE_TIME, &sample, userdata); + // Depth (1/10 m). + sample.depth = 0.0; + if (callback) callback (DC_SAMPLE_DEPTH, &sample, userdata); + } + time += surftime; + depth = 0; + complete = 1; } if (complete) { From 5a24517c84aaafa733d1c6447d8340c3761cf362 Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Mon, 22 Apr 2024 20:50:40 +0200 Subject: [PATCH 12/17] Add timesync support The command to set the date/time does not appear to be supported for the BLE communication. --- examples/dctool_timesync.c | 4 ++-- src/cressi_goa.c | 31 ++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/examples/dctool_timesync.c b/examples/dctool_timesync.c index eeff0bbc..cdd02bc5 100644 --- a/examples/dctool_timesync.c +++ b/examples/dctool_timesync.c @@ -85,10 +85,10 @@ do_timesync (dc_context_t *context, dc_descriptor_t *descriptor, dc_transport_t } // Syncronize the device clock. - message ("Syncronize the device clock.\n"); + message ("Synchronize the device clock.\n"); rc = dc_device_timesync (device, datetime); if (rc != DC_STATUS_SUCCESS) { - ERROR ("Error syncronizing the device clock."); + ERROR ("Error synchronizing the device clock."); goto cleanup; } diff --git a/src/cressi_goa.c b/src/cressi_goa.c index 868fb466..b7e41f10 100644 --- a/src/cressi_goa.c +++ b/src/cressi_goa.c @@ -35,6 +35,7 @@ #define ISINSTANCE(device) dc_device_isinstance((device), &cressi_goa_device_vtable) #define CMD_VERSION 0x00 +#define CMD_SET_TIME 0x13 #define CMD_LOGBOOK 0x21 #define CMD_DIVE 0x22 @@ -64,6 +65,7 @@ typedef struct cressi_goa_device_t { static dc_status_t cressi_goa_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); static dc_status_t cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +static dc_status_t cressi_goa_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime); static const dc_device_vtable_t cressi_goa_device_vtable = { sizeof(cressi_goa_device_t), @@ -73,7 +75,7 @@ static const dc_device_vtable_t cressi_goa_device_vtable = { NULL, /* write */ NULL, /* dump */ cressi_goa_device_foreach, /* foreach */ - NULL, /* timesync */ + cressi_goa_device_timesync, /* timesync */ NULL /* close */ }; @@ -630,3 +632,30 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v error_exit: return status; } + +static dc_status_t +cressi_goa_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) +{ + dc_status_t status = DC_STATUS_SUCCESS; + cressi_goa_device_t *device = (cressi_goa_device_t *) abstract; + dc_transport_t transport = dc_iostream_get_transport (device->iostream); + + if (transport == DC_TRANSPORT_BLE) { + return DC_STATUS_UNSUPPORTED; + } + + unsigned char new_time[7]; + array_uint16_le_set(new_time, datetime->year); + new_time[2] = datetime->month; + new_time[3] = datetime->day; + new_time[4] = datetime->hour; + new_time[5] = datetime->minute; + new_time[6] = datetime->second; + status = cressi_goa_device_transfer (device, CMD_SET_TIME, new_time, sizeof(new_time), NULL, 0, NULL, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to set the new time."); + return status; + } + + return status; +} From 73218b4533f6e0614c34934840d688db30281d27 Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Mon, 22 Apr 2024 21:22:31 +0200 Subject: [PATCH 13/17] Exit PC Link mode when operation is done The command to exit the PC Link mode does not appear to be supported for the BLE communication. --- src/cressi_goa.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/cressi_goa.c b/src/cressi_goa.c index b7e41f10..1eb4f386 100644 --- a/src/cressi_goa.c +++ b/src/cressi_goa.c @@ -36,6 +36,7 @@ #define CMD_VERSION 0x00 #define CMD_SET_TIME 0x13 +#define CMD_EXIT_PCLINK 0x1D #define CMD_LOGBOOK 0x21 #define CMD_DIVE 0x22 @@ -66,6 +67,7 @@ typedef struct cressi_goa_device_t { static dc_status_t cressi_goa_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); static dc_status_t cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); static dc_status_t cressi_goa_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime); +static dc_status_t cressi_goa_device_close (dc_device_t *abstract); static const dc_device_vtable_t cressi_goa_device_vtable = { sizeof(cressi_goa_device_t), @@ -76,7 +78,7 @@ static const dc_device_vtable_t cressi_goa_device_vtable = { NULL, /* dump */ cressi_goa_device_foreach, /* foreach */ cressi_goa_device_timesync, /* timesync */ - NULL /* close */ + cressi_goa_device_close /* close */ }; static dc_status_t @@ -659,3 +661,23 @@ cressi_goa_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime return status; } + +static dc_status_t +cressi_goa_device_close (dc_device_t *abstract) +{ + dc_status_t status = DC_STATUS_SUCCESS; + cressi_goa_device_t *device = (cressi_goa_device_t *) abstract; + dc_transport_t transport = dc_iostream_get_transport (device->iostream); + + if (transport == DC_TRANSPORT_BLE) { + return DC_STATUS_SUCCESS; + } + + status = cressi_goa_device_transfer (device, CMD_EXIT_PCLINK, NULL, 0, NULL, 0, NULL, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to exit PC Link."); + return status; + } + + return status; +} From ec039337a05b05b8bec98fa054960193eb156f18 Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Mon, 22 Apr 2024 20:36:36 +0200 Subject: [PATCH 14/17] Add support for the Cressi Nepto The Cressi 'goa' protocol and data format has different versions. The current implementation only supports version 2 and 3 (both variants are mostly compatible with only some minor differences), but the Nepto uses version 4, which requires some refactoring: - The reply to the CMD_VERSION command has no longer a fixed size, and thus the receive function is changed to accept a dynamically sized buffer. - The size of the entries in the logbook can be 23 bytes (for versions 0-3) or 15 bytes (for version 4). - The Nepto is a freediving computer which supports sample rates less than one second. Therefore the sample time is internally changed from seconds to milliseconds. - Each version needs it own layout descriptor. The version number is either available directly in the device id data, or obtained indirectly from the firmware version. --- src/cressi_goa.c | 172 +++++++++++++------- src/cressi_goa_parser.c | 339 ++++++++++++++++++++++++++++++++-------- src/descriptor.c | 2 + 3 files changed, 389 insertions(+), 124 deletions(-) diff --git a/src/cressi_goa.c b/src/cressi_goa.c index 1eb4f386..8e236da9 100644 --- a/src/cressi_goa.c +++ b/src/cressi_goa.c @@ -34,11 +34,12 @@ #define ISINSTANCE(device) dc_device_isinstance((device), &cressi_goa_device_vtable) -#define CMD_VERSION 0x00 -#define CMD_SET_TIME 0x13 -#define CMD_EXIT_PCLINK 0x1D -#define CMD_LOGBOOK 0x21 -#define CMD_DIVE 0x22 +#define CMD_VERSION 0x00 +#define CMD_SET_TIME 0x13 +#define CMD_EXIT_PCLINK 0x1D +#define CMD_LOGBOOK 0x21 +#define CMD_DIVE 0x22 +#define CMD_LOGBOOK_V4 0x23 #define CMD_LOGBOOK_BLE 0x02 #define CMD_DIVE_BLE 0x03 @@ -49,10 +50,8 @@ #define ACK 0x06 #define SZ_DATA 512 -#define SZ_PACKET 10 -#define SZ_HEADER 23 +#define SZ_PACKET 12 -#define FP_OFFSET 0x11 #define FP_SIZE 6 #define NSTEPS 1000 @@ -64,6 +63,13 @@ typedef struct cressi_goa_device_t { unsigned char fingerprint[FP_SIZE]; } cressi_goa_device_t; +typedef struct cressi_goa_conf_t { + unsigned int logbook_cmd; + unsigned int logbook_len; + unsigned int logbook_fp_offset; + unsigned int dive_fp_offset; +} cressi_goa_conf_t; + static dc_status_t cressi_goa_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); static dc_status_t cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); static dc_status_t cressi_goa_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime); @@ -81,6 +87,11 @@ static const dc_device_vtable_t cressi_goa_device_vtable = { cressi_goa_device_close /* close */ }; +static const cressi_goa_conf_t version_conf[] = { + { CMD_LOGBOOK, 23, 17, 12 }, + { CMD_LOGBOOK_V4, 15, 3, 4 }, +}; + static dc_status_t cressi_goa_device_send (cressi_goa_device_t *device, unsigned char cmd, const unsigned char data[], unsigned int size) { @@ -128,7 +139,7 @@ cressi_goa_device_send (cressi_goa_device_t *device, unsigned char cmd, const un } static dc_status_t -cressi_goa_device_receive (cressi_goa_device_t *device, unsigned char data[], unsigned int size) +cressi_goa_device_receive (cressi_goa_device_t *device, dc_buffer_t *output) { dc_status_t status = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; @@ -137,13 +148,16 @@ cressi_goa_device_receive (cressi_goa_device_t *device, unsigned char data[], un unsigned char packet[SZ_PACKET + 8]; if (transport == DC_TRANSPORT_BLE) { - if (size) { + if (output) { return DC_STATUS_INVALIDARGS; } else { return DC_STATUS_SUCCESS; } } + // Clear the output buffer. + dc_buffer_clear (output); + // Read the header of the data packet. status = dc_iostream_read (device->iostream, packet, 4, NULL); if (status != DC_STATUS_SUCCESS) { @@ -185,14 +199,11 @@ cressi_goa_device_receive (cressi_goa_device_t *device, unsigned char data[], un return DC_STATUS_PROTOCOL; } - // Verify the payload length. - if (length != size) { - ERROR (abstract->context, "Unexpected payload size (%u).", length); - return DC_STATUS_PROTOCOL; - } - - if (length) { - memcpy (data, packet + 5, length); + if (length && output) { + if (!dc_buffer_append (output, packet + 5, length)) { + ERROR (abstract->context, "Could not append received data."); + return DC_STATUS_NOMEMORY; + } } return status; @@ -332,7 +343,7 @@ static dc_status_t cressi_goa_device_transfer (cressi_goa_device_t *device, unsigned char cmd, const unsigned char input[], unsigned int isize, - unsigned char output[], unsigned int osize, + dc_buffer_t *output, dc_buffer_t *buffer, dc_event_progress_t *progress) { @@ -345,7 +356,7 @@ cressi_goa_device_transfer (cressi_goa_device_t *device, } // Receive the answer from the dive computer. - status = cressi_goa_device_receive (device, output, osize); + status = cressi_goa_device_receive (device, output); if (status != DC_STATUS_SUCCESS) { return status; } @@ -453,8 +464,14 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + dc_buffer_t *id = dc_buffer_new(11); + if (id == NULL) { + ERROR (abstract->context, "Failed to allocate memory for the ID."); + status = DC_STATUS_NOMEMORY; + goto error_exit; + } + // Read the version information. - unsigned char id[9] = {0}; if (transport == DC_TRANSPORT_BLE) { /* * With the BLE communication, there is no variant of the CMD_VERSION @@ -471,7 +488,6 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v }; const size_t sizes[] = {5, 2, 2}; - unsigned int offset = 0; for (size_t i = 0; i < C_ARRAY_SIZE(characteristics); ++i) { unsigned char request[sizeof(dc_ble_uuid_t) + 5] = {0}; @@ -485,48 +501,89 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v char uuidstr[DC_BLE_UUID_SIZE] = {0}; ERROR (abstract->context, "Failed to read the characteristic '%s'.", dc_ble_uuid2str(characteristics[i], uuidstr, sizeof(uuidstr))); - goto error_exit; + goto error_free_id; } // Copy the payload data. - memcpy (id + offset, request + sizeof(dc_ble_uuid_t), sizes[i]); - offset += sizes[i]; + if (!dc_buffer_append(id, request + sizeof(dc_ble_uuid_t), sizes[i])) { + ERROR (abstract->context, "Insufficient buffer space available."); + status = DC_STATUS_NOMEMORY; + goto error_free_id; + } } } else { - status = cressi_goa_device_transfer (device, CMD_VERSION, NULL, 0, id, sizeof(id), NULL, NULL); + status = cressi_goa_device_transfer (device, CMD_VERSION, NULL, 0, id, NULL, NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the version information."); - goto error_exit; + goto error_free_id; } } + const unsigned char *id_data = dc_buffer_get_data(id); + size_t id_size = dc_buffer_get_size(id); + + if (id_size < 9) { + ERROR (abstract->context, "Unexpected version length (" DC_PRINTF_SIZE ").", id_size); + status = DC_STATUS_DATAFORMAT; + goto error_free_id; + } + + // Get the device info. + unsigned int model = id_data[4]; + unsigned int firmware = array_uint16_le (id_data + 5); + unsigned int serial = array_uint32_le (id_data + 0); + + // Get the data format version. + unsigned int version = 0; + if (id_size == 11) { + version = array_uint16_le (id_data + 9); + } else { + if (firmware >= 161 && firmware <= 165) { + version = 0; + } else if (firmware >= 166 && firmware <= 169) { + version = 1; + } else if (firmware >= 170 && firmware <= 179) { + version = 2; + } else if (firmware >= 100 && firmware <= 110) { + version = 3; + } else if (firmware >= 200 && firmware <= 205) { + version = 4; + } else { + ERROR (abstract->context, "Unknown firmware version (%u).", firmware); + status = DC_STATUS_DATAFORMAT; + goto error_free_id; + } + } + + const cressi_goa_conf_t *conf = &version_conf[version >= 4]; + // Emit a vendor event. dc_event_vendor_t vendor; - vendor.data = id; - vendor.size = sizeof (id); + vendor.data = id_data; + vendor.size = id_size; device_event_emit (abstract, DC_EVENT_VENDOR, &vendor); // Emit a device info event. dc_event_devinfo_t devinfo; - devinfo.model = id[4]; - devinfo.firmware = array_uint16_le (id + 5); - devinfo.serial = array_uint32_le (id + 0); + devinfo.model = model; + devinfo.firmware = firmware; + devinfo.serial = serial; device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); // Allocate memory for the logbook data. logbook = dc_buffer_new(4096); if (logbook == NULL) { - ERROR (abstract->context, "Failed to allocate memory."); + ERROR (abstract->context, "Failed to allocate memory for the logbook."); status = DC_STATUS_NOMEMORY; - goto error_exit; + goto error_free_id; } // Read the logbook data. if (transport == DC_TRANSPORT_BLE) { unsigned char args[] = {0x00}; - status = cressi_goa_device_transfer (device, CMD_LOGBOOK_BLE, args, sizeof(args), NULL, 0, logbook, &progress); + status = cressi_goa_device_transfer (device, CMD_LOGBOOK_BLE, args, sizeof(args), NULL, logbook, &progress); } else { - status = cressi_goa_device_transfer (device, CMD_LOGBOOK, NULL, 0, NULL, 0, logbook, &progress); + status = cressi_goa_device_transfer (device, conf->logbook_cmd, NULL, 0, NULL, logbook, &progress); } if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the logbook data."); @@ -539,9 +596,9 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v // Count the number of dives. unsigned int count = 0; unsigned int offset = logbook_size; - while (offset >= SZ_HEADER) { + while (offset >= conf->logbook_len) { // Move to the start of the logbook entry. - offset -= SZ_HEADER; + offset -= conf->logbook_len; // Get the dive number. unsigned int number= array_uint16_le (logbook_data + offset); @@ -549,7 +606,7 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v break; // Compare the fingerprint to identify previously downloaded entries. - if (memcmp (logbook_data + offset + FP_OFFSET, device->fingerprint, sizeof(device->fingerprint)) == 0) { + if (memcmp (logbook_data + offset + conf->logbook_fp_offset, device->fingerprint, sizeof(device->fingerprint)) == 0) { break; } @@ -563,7 +620,7 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v // Allocate memory for the dive data. dive = dc_buffer_new(4096); if (dive == NULL) { - ERROR (abstract->context, "Failed to allocate memory."); + ERROR (abstract->context, "Failed to allocate memory for the dive."); status = DC_STATUS_NOMEMORY; goto error_free_logbook; } @@ -572,16 +629,16 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v offset = logbook_size; for (unsigned int i = 0; i < count; ++i) { // Move to the start of the logbook entry. - offset -= SZ_HEADER; + offset -= conf->logbook_len; // Read the dive data. if (transport == DC_TRANSPORT_BLE) { unsigned char number[2] = { logbook_data[offset + 1], logbook_data[offset + 0]}; - status = cressi_goa_device_transfer (device, CMD_DIVE_BLE, number, 2, NULL, 0, dive, &progress); + status = cressi_goa_device_transfer (device, CMD_DIVE_BLE, number, 2, NULL, dive, &progress); } else { - status = cressi_goa_device_transfer (device, CMD_DIVE, logbook_data + offset, 2, NULL, 0, dive, &progress); + status = cressi_goa_device_transfer (device, CMD_DIVE, logbook_data + offset, 2, NULL, dive, &progress); } if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the dive data."); @@ -591,12 +648,11 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v const unsigned char *dive_data = dc_buffer_get_data (dive); size_t dive_size = dc_buffer_get_size (dive); - // Verify the header in the logbook and dive data are identical. - // After the 2 byte dive number, the logbook header has 5 bytes - // extra, which are not present in the dive header. - if (dive_size < SZ_HEADER - 5 || - memcmp (dive_data + 0, logbook_data + offset + 0, 2) != 0 || - memcmp (dive_data + 2, logbook_data + offset + 7, SZ_HEADER - 7) != 0) { + // Verify the dive number and the fingerprint in the logbook and dive + // data are identical. + if (dive_size < conf->dive_fp_offset + FP_SIZE || + memcmp (dive_data, logbook_data + offset, 2) != 0 || + memcmp (dive_data + conf->dive_fp_offset, logbook_data + offset + conf->logbook_fp_offset, FP_SIZE) != 0) { ERROR (abstract->context, "Unexpected dive header."); status = DC_STATUS_DATAFORMAT; goto error_free_dive; @@ -608,12 +664,12 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v // are prepended to the dive data, along with a small header containing // their size. const unsigned char header[] = { - sizeof(id), - SZ_HEADER, + id_size, + conf->logbook_len, }; - unsigned int headersize = sizeof(header) + sizeof(id) + SZ_HEADER; - if (!dc_buffer_prepend(dive, logbook_data + offset, SZ_HEADER) || - !dc_buffer_prepend(dive, id, sizeof(id)) || + unsigned int headersize = sizeof(header) + id_size + conf->logbook_len; + if (!dc_buffer_prepend(dive, logbook_data + offset, conf->logbook_len) || + !dc_buffer_prepend(dive, id_data, id_size) || !dc_buffer_prepend(dive, header, sizeof(header))) { ERROR (abstract->context, "Out of memory."); status = DC_STATUS_NOMEMORY; @@ -623,7 +679,7 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v dive_data = dc_buffer_get_data (dive); dive_size = dc_buffer_get_size (dive); - if (callback && !callback(dive_data, dive_size, dive_data + headersize + FP_OFFSET - 5, sizeof(device->fingerprint), userdata)) + if (callback && !callback(dive_data, dive_size, dive_data + headersize + conf->dive_fp_offset, sizeof(device->fingerprint), userdata)) break; } @@ -631,6 +687,8 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v dc_buffer_free(dive); error_free_logbook: dc_buffer_free(logbook); +error_free_id: + dc_buffer_free(id); error_exit: return status; } @@ -653,7 +711,7 @@ cressi_goa_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime new_time[4] = datetime->hour; new_time[5] = datetime->minute; new_time[6] = datetime->second; - status = cressi_goa_device_transfer (device, CMD_SET_TIME, new_time, sizeof(new_time), NULL, 0, NULL, NULL); + status = cressi_goa_device_transfer (device, CMD_SET_TIME, new_time, sizeof(new_time), NULL, NULL, NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to set the new time."); return status; @@ -673,7 +731,7 @@ cressi_goa_device_close (dc_device_t *abstract) return DC_STATUS_SUCCESS; } - status = cressi_goa_device_transfer (device, CMD_EXIT_PCLINK, NULL, 0, NULL, 0, NULL, NULL); + status = cressi_goa_device_transfer (device, CMD_EXIT_PCLINK, NULL, 0, NULL, NULL, NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to exit PC Link."); return status; diff --git a/src/cressi_goa_parser.c b/src/cressi_goa_parser.c index 5c6e9d21..78ed758d 100644 --- a/src/cressi_goa_parser.c +++ b/src/cressi_goa_parser.c @@ -28,19 +28,20 @@ #define ISINSTANCE(parser) dc_device_isinstance((parser), &cressi_goa_parser_vtable) -#define SZ_HEADER 23 - -#define DEPTH 0 -#define DEPTH2 1 -#define TIME 2 +#define DEPTH_SCUBA 0 +#define DEPTH_FREE 1 +#define SURFACE 2 #define TEMPERATURE 3 -#define SCUBA 0 -#define NITROX 1 -#define FREEDIVE 2 -#define GAUGE 3 +#define SCUBA 0 +#define NITROX 1 +#define FREEDIVE 2 +#define GAUGE 3 +#define FREEDIVE_ADV 5 -#define NGASMIXES 2 +#define NGASMIXES 3 +#define NVERSIONS 5 +#define NDIVEMODES 6 #define UNDEFINED 0xFFFFFFFF @@ -48,9 +49,11 @@ typedef struct cressi_goa_parser_t cressi_goa_parser_t; typedef struct cressi_goa_layout_t { unsigned int headersize; + unsigned int nsamples; + unsigned int samplerate; unsigned int datetime; unsigned int divetime; - unsigned int gasmix; + unsigned int gasmix[NGASMIXES]; unsigned int atmospheric; unsigned int maxdepth; unsigned int avgdepth; @@ -81,50 +84,189 @@ static const dc_parser_vtable_t cressi_goa_parser_vtable = { NULL /* destroy */ }; -static const cressi_goa_layout_t layouts[] = { - /* SCUBA */ +static const cressi_goa_layout_t scuba_nitrox_layout_v0 = { + 90, /* headersize */ + 10, /* nsamples */ + UNDEFINED, /* samplerate */ + 12, /* datetime */ + 20, /* divetime */ + { 26, 28, UNDEFINED }, /* gasmix */ + 30, /* atmospheric */ + 73, /* maxdepth */ + 75, /* avgdepth */ + 77, /* temperature */ +}; + +static const cressi_goa_layout_t scuba_nitrox_layout_v1v2 = { + 92, /* headersize */ + 10, /* nsamples */ + UNDEFINED, /* samplerate */ + 12, /* datetime */ + 20, /* divetime */ + { 26, 28, UNDEFINED }, /* gasmix */ + 30, /* atmospheric */ + 73, /* maxdepth */ + 75, /* avgdepth */ + 77, /* temperature */ +}; + +static const cressi_goa_layout_t scuba_nitrox_layout_v3 = { + 92, /* headersize */ + 10, /* nsamples */ + UNDEFINED, /* samplerate */ + 12, /* datetime */ + 20, /* divetime */ + { 26, 28, 87 }, /* gasmix */ + 30, /* atmospheric */ + 73, /* maxdepth */ + 75, /* avgdepth */ + 77, /* temperature */ +}; + +static const cressi_goa_layout_t scuba_nitrox_layout_v4 = { + 82, /* headersize */ + 10, /* nsamples */ + 2, /* samplerate */ + 4, /* datetime */ + 11, /* divetime */ + { 17, 19, 21 }, /* gasmix */ + 23, /* atmospheric */ + 66, /* maxdepth */ + 68, /* avgdepth */ + 70, /* temperature */ +}; + +static const cressi_goa_layout_t freedive_layout_v0 = { + 34, /* headersize */ + 10, /* nsamples */ + UNDEFINED, /* samplerate */ + 12, /* datetime */ + 20, /* divetime */ + { UNDEFINED, UNDEFINED, UNDEFINED }, /* gasmix */ + UNDEFINED, /* atmospheric */ + 23, /* maxdepth */ + UNDEFINED, /* avgdepth */ + 25, /* temperature */ +}; + +static const cressi_goa_layout_t freedive_layout_v1v2v3 = { + 38, /* headersize */ + 10, /* nsamples */ + UNDEFINED, /* samplerate */ + 12, /* datetime */ + 20, /* divetime */ + { UNDEFINED, UNDEFINED, UNDEFINED }, /* gasmix */ + UNDEFINED, /* atmospheric */ + 23, /* maxdepth */ + UNDEFINED, /* avgdepth */ + 25, /* temperature */ +}; + +static const cressi_goa_layout_t freedive_layout_v4 = { + 27, /* headersize */ + 2, /* nsamples */ + 10, /* samplerate */ + 4, /* datetime */ + 11, /* divetime */ + { UNDEFINED, UNDEFINED, UNDEFINED }, /* gasmix */ + UNDEFINED, /* atmospheric */ + 15, /* maxdepth */ + UNDEFINED, /* avgdepth */ + 17, /* temperature */ +}; + +static const cressi_goa_layout_t gauge_layout_v0 = { + 38, /* headersize */ + 10, /* nsamples */ + UNDEFINED, /* samplerate */ + 12, /* datetime */ + 20, /* divetime */ + { UNDEFINED, UNDEFINED, UNDEFINED }, /* gasmix */ + 22, /* atmospheric */ + 24, /* maxdepth */ + 26, /* avgdepth */ + 28, /* temperature */ +}; + +static const cressi_goa_layout_t gauge_layout_v1v2v3 = { + 40, /* headersize */ + 10, /* nsamples */ + UNDEFINED, /* samplerate */ + 12, /* datetime */ + 20, /* divetime */ + { UNDEFINED, UNDEFINED, UNDEFINED }, /* gasmix */ + 22, /* atmospheric */ + 24, /* maxdepth */ + 26, /* avgdepth */ + 28, /* temperature */ +}; + +static const cressi_goa_layout_t gauge_layout_v4 = { + 28, /* headersize */ + 2, /* nsamples */ + 10, /* samplerate */ + 4, /* datetime */ + 11, /* divetime */ + { UNDEFINED, UNDEFINED, UNDEFINED }, /* gasmix */ + 13, /* atmospheric */ + 15, /* maxdepth */ + 17, /* avgdepth */ + 19, /* temperature */ +}; + +static const cressi_goa_layout_t advanced_freedive_layout_v4 = { + 28, /* headersize */ + 2, /* nsamples */ + 10, /* samplerate */ + 4, /* datetime */ + 22, /* divetime */ + { UNDEFINED, UNDEFINED, UNDEFINED }, /* gasmix */ + UNDEFINED, /* atmospheric */ + 16, /* maxdepth */ + UNDEFINED, /* avgdepth */ + 18, /* temperature */ +}; + +static const cressi_goa_layout_t * const layouts[NVERSIONS][NDIVEMODES] = { + { + &scuba_nitrox_layout_v0, /* SCUBA */ + &scuba_nitrox_layout_v0, /* NITROX */ + &freedive_layout_v0, /* FREEDIVE */ + &gauge_layout_v0, /* GAUGE */ + NULL, /* UNUSED */ + NULL, /* FREEDIVE_ADV */ + }, { - 92, /* headersize */ - 12, /* datetime */ - 20, /* divetime */ - 26, /* gasmix */ - 30, /* atmospheric */ - 73, /* maxdepth */ - 75, /* avgdepth */ - 77, /* temperature */ + &scuba_nitrox_layout_v1v2, /* SCUBA */ + &scuba_nitrox_layout_v1v2, /* NITROX */ + &freedive_layout_v1v2v3, /* FREEDIVE */ + &gauge_layout_v1v2v3, /* GAUGE */ + NULL, /* UNUSED */ + NULL, /* FREEDIVE_ADV */ }, - /* NITROX */ { - 92, /* headersize */ - 12, /* datetime */ - 20, /* divetime */ - 26, /* gasmix */ - 30, /* atmospheric */ - 73, /* maxdepth */ - 75, /* avgdepth */ - 77, /* temperature */ + &scuba_nitrox_layout_v1v2, /* SCUBA */ + &scuba_nitrox_layout_v1v2, /* NITROX */ + &freedive_layout_v1v2v3, /* FREEDIVE */ + &gauge_layout_v1v2v3, /* GAUGE */ + NULL, /* UNUSED */ + NULL, /* FREEDIVE_ADV */ }, - /* FREEDIVE */ { - 38, /* headersize */ - 12, /* datetime */ - 20, /* divetime */ - UNDEFINED, /* gasmix */ - UNDEFINED, /* atmospheric */ - 23, /* maxdepth */ - UNDEFINED, /* avgdepth */ - 25, /* temperature */ + &scuba_nitrox_layout_v3, /* SCUBA */ + &scuba_nitrox_layout_v3, /* NITROX */ + &freedive_layout_v1v2v3, /* FREEDIVE */ + &gauge_layout_v1v2v3, /* GAUGE */ + NULL, /* UNUSED */ + NULL, /* FREEDIVE_ADV */ }, - /* GAUGE */ { - 40, /* headersize */ - 12, /* datetime */ - 20, /* divetime */ - UNDEFINED, /* gasmix */ - 22, /* atmospheric */ - 24, /* maxdepth */ - 26, /* avgdepth */ - 28, /* temperature */ + &scuba_nitrox_layout_v4, /* SCUBA */ + &scuba_nitrox_layout_v4, /* NITROX */ + &freedive_layout_v4, /* FREEDIVE */ + &gauge_layout_v4, /* GAUGE */ + NULL, /* UNUSED */ + &advanced_freedive_layout_v4, /* FREEDIVE_ADV */ }, }; @@ -142,7 +284,7 @@ cressi_goa_init(cressi_goa_parser_t *parser) unsigned int id_len = data[0]; unsigned int logbook_len = data[1]; - if (id_len < 9 || logbook_len < SZ_HEADER) { + if (id_len < 9 || logbook_len < 15) { ERROR (abstract->context, "Invalid id or logbook length (%u %u).", id_len, logbook_len); return DC_STATUS_DATAFORMAT; } @@ -152,17 +294,48 @@ cressi_goa_init(cressi_goa_parser_t *parser) return DC_STATUS_DATAFORMAT; } + const unsigned char *id = data + 2; const unsigned char *logbook = data + 2 + id_len; + // Get the data format version. + unsigned int version = 0; + unsigned int firmware = array_uint16_le (id + 5); + if (id_len == 11) { + version = array_uint16_le (id + 9); + } else { + if (firmware >= 161 && firmware <= 165) { + version = 0; + } else if (firmware >= 166 && firmware <= 169) { + version = 1; + } else if (firmware >= 170 && firmware <= 179) { + version = 2; + } else if (firmware >= 100 && firmware <= 110) { + version = 3; + } else if (firmware >= 200 && firmware <= 205) { + version = 4; + } else { + ERROR (abstract->context, "Unknown firmware version (%u).", firmware); + return DC_STATUS_DATAFORMAT; + } + } + if (version >= NVERSIONS) { + ERROR (abstract->context, "Invalid data format version (%u).", version); + return DC_STATUS_DATAFORMAT; + } + // Get the dive mode. unsigned int divemode = logbook[2]; - if (divemode >= C_ARRAY_SIZE(layouts)) { + if (divemode >= NDIVEMODES) { ERROR (abstract->context, "Invalid dive mode (%u).", divemode); return DC_STATUS_DATAFORMAT; } // Get the layout. - const cressi_goa_layout_t *layout = &layouts[divemode]; + const cressi_goa_layout_t *layout = layouts[version][divemode]; + if (layout == NULL) { + ERROR (abstract->context, "Unsupported dive mode for data format version %u (%u).", version, divemode); + return DC_STATUS_DATAFORMAT; + } unsigned int headersize = 2 + id_len + logbook_len; if (size < headersize + layout->headersize) { @@ -173,6 +346,7 @@ cressi_goa_init(cressi_goa_parser_t *parser) parser->layout = layout; parser->headersize = headersize; parser->divemode = divemode; + parser->version = version; return DC_STATUS_SUCCESS; } @@ -234,12 +408,12 @@ cressi_goa_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsign const unsigned char *data = abstract->data + parser->headersize; unsigned int ngasmixes = 0; - if (layout->gasmix != UNDEFINED) { - for (unsigned int i = 0; i < NGASMIXES; ++i) { - if (data[layout->gasmix + 2 * i + 1] == 0) - break; - ngasmixes++; - } + for (unsigned int i = 0; i < NGASMIXES; ++i) { + if (layout->gasmix[i] == UNDEFINED) + break; + if (data[layout->gasmix[i] + 1] == 0) + break; + ngasmixes++; } dc_gasmix_t *gasmix = (dc_gasmix_t *) value; @@ -275,9 +449,11 @@ cressi_goa_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsign *((unsigned int *) value) = ngasmixes; break; case DC_FIELD_GASMIX: + if (ngasmixes <= flags) + return DC_STATUS_INVALIDARGS; gasmix->usage = DC_USAGE_NONE; gasmix->helium = 0.0; - gasmix->oxygen = data[layout->gasmix + 2 * flags + 1] / 100.0; + gasmix->oxygen = data[layout->gasmix[flags] + 1] / 100.0; gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; break; case DC_FIELD_DIVEMODE: @@ -290,6 +466,7 @@ cressi_goa_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsign *((dc_divemode_t *) value) = DC_DIVEMODE_GAUGE; break; case FREEDIVE: + case FREEDIVE_ADV: *((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE; break; default: @@ -312,40 +489,68 @@ cressi_goa_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t c const unsigned char *data = abstract->data + parser->headersize; unsigned int size = abstract->size - parser->headersize; - unsigned int interval = parser->divemode == FREEDIVE ? 2 : 5; + unsigned int interval = parser->divemode == FREEDIVE ? 2000 : 5000; + if (layout->samplerate != UNDEFINED) { + const unsigned int samplerates[] = { + 500, 1000, 2000, 5000 + }; + unsigned int samplerate = data[layout->samplerate]; + if (samplerate == 0 || samplerate > C_ARRAY_SIZE(samplerates)) { + ERROR (abstract->context, "Unknown sample rate index (%u).", samplerate); + return DC_STATUS_DATAFORMAT; + } + interval = samplerates[samplerate - 1]; + } + + // In advanced freedive mode, there is an extra header present after the + // samples containing the advanced freedive dip stats. + unsigned int trailer = parser->divemode == FREEDIVE_ADV ? 13 : 0; + + unsigned int nsamples = array_uint16_le (data + layout->nsamples); unsigned int time = 0; unsigned int depth = 0; + unsigned int depth_mask = ((parser->version < 4) ? 0x07FF : 0x0FFF); unsigned int gasmix = 0, gasmix_previous = 0xFFFFFFFF; + unsigned int gasmix_mask = ((parser->version < 3) ? 0x0800 : 0x1800); unsigned int temperature = 0; unsigned int have_temperature = 0; unsigned int complete = 0; unsigned int offset = layout->headersize; - while (offset + 2 <= size) { + for (unsigned int i = 0; i < nsamples; ++i) { dc_sample_value_t sample = {0}; + if (offset + 2 + trailer > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + // Get the sample type and value. unsigned int raw = array_uint16_le (data + offset); unsigned int type = (raw & 0x0003); unsigned int value = (raw & 0xFFFC) >> 2; - if (type == DEPTH || type == DEPTH2) { - depth = (value & 0x07FF); - gasmix = (value & 0x0800) >> 11; + if (type == DEPTH_SCUBA) { + depth = value & 0x07FF; + gasmix = (value & gasmix_mask) >> 11; + time += interval; + complete = 1; + } else if (type == DEPTH_FREE) { + depth = value & depth_mask; time += interval; complete = 1; } else if (type == TEMPERATURE) { temperature = value; have_temperature = 1; - } else if (type == TIME) { - unsigned int surftime = value; + } else if (type == SURFACE) { + unsigned int surftime = value * 1000; if (surftime > interval) { surftime -= interval; time += interval; // Time (seconds). - sample.time = time * 1000; + sample.time = time; if (callback) callback (DC_SAMPLE_TIME, &sample, userdata); // Depth (1/10 m). sample.depth = 0.0; @@ -358,7 +563,7 @@ cressi_goa_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t c if (complete) { // Time (seconds). - sample.time = time * 1000; + sample.time = time; if (callback) callback (DC_SAMPLE_TIME, &sample, userdata); // Temperature (1/10 °C). diff --git a/src/descriptor.c b/src/descriptor.c index d7e72427..22353988 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -346,6 +346,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Cressi", "Donatello", DC_FAMILY_CRESSI_GOA, 4, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, {"Cressi", "Michelangelo", DC_FAMILY_CRESSI_GOA, 5, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, {"Cressi", "Neon", DC_FAMILY_CRESSI_GOA, 9, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, + {"Cressi", "Nepto", DC_FAMILY_CRESSI_GOA, 10, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, /* Zeagle N2iTiON3 */ {"Zeagle", "N2iTiON3", DC_FAMILY_ZEAGLE_N2ITION3, 0, DC_TRANSPORT_SERIAL, NULL}, {"Apeks", "Quantum X", DC_FAMILY_ZEAGLE_N2ITION3, 0, DC_TRANSPORT_SERIAL, NULL}, @@ -913,6 +914,7 @@ dc_filter_cressi (dc_descriptor_t *descriptor, dc_transport_t transport, const v 4, // Donatello 5, // Michelangelo 9, // Neon + 10, // Nepto }; if (transport == DC_TRANSPORT_BLE) { From 8b365970ad59aabeec86b3a0ba843d963dc27627 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 22 Sep 2024 16:00:41 +0200 Subject: [PATCH 15/17] Add support for the Cressi Leonardo 2.0 The Cressi Leonardo 2.0 switched from the older 'leonardo' protocol to the newer 'goa' protocol. At the same time, the device firmware also uses a new V5 variant of the data format. Some of the other models, like the Donatello, also received a firmware update which uses this new V5 data format. --- src/cressi_goa.c | 2 ++ src/cressi_goa_parser.c | 25 ++++++++++++++++++++++++- src/descriptor.c | 2 ++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/cressi_goa.c b/src/cressi_goa.c index 8e236da9..2b977767 100644 --- a/src/cressi_goa.c +++ b/src/cressi_goa.c @@ -548,6 +548,8 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v version = 3; } else if (firmware >= 200 && firmware <= 205) { version = 4; + } else if (firmware >= 300) { + version = 5; } else { ERROR (abstract->context, "Unknown firmware version (%u).", firmware); status = DC_STATUS_DATAFORMAT; diff --git a/src/cressi_goa_parser.c b/src/cressi_goa_parser.c index 78ed758d..22e9c659 100644 --- a/src/cressi_goa_parser.c +++ b/src/cressi_goa_parser.c @@ -40,7 +40,7 @@ #define FREEDIVE_ADV 5 #define NGASMIXES 3 -#define NVERSIONS 5 +#define NVERSIONS 6 #define NDIVEMODES 6 #define UNDEFINED 0xFFFFFFFF @@ -136,6 +136,19 @@ static const cressi_goa_layout_t scuba_nitrox_layout_v4 = { 70, /* temperature */ }; +static const cressi_goa_layout_t scuba_nitrox_layout_v5 = { + 83, /* headersize */ + 2, /* nsamples */ + UNDEFINED, /* samplerate */ + 4, /* datetime */ + 11, /* divetime */ + { 17, 19, 21 }, /* gasmix */ + 23, /* atmospheric */ + 66, /* maxdepth */ + 68, /* avgdepth */ + 70, /* temperature */ +}; + static const cressi_goa_layout_t freedive_layout_v0 = { 34, /* headersize */ 10, /* nsamples */ @@ -268,6 +281,14 @@ static const cressi_goa_layout_t * const layouts[NVERSIONS][NDIVEMODES] = { NULL, /* UNUSED */ &advanced_freedive_layout_v4, /* FREEDIVE_ADV */ }, + { + &scuba_nitrox_layout_v5, /* SCUBA */ + &scuba_nitrox_layout_v5, /* NITROX */ + &freedive_layout_v4, /* FREEDIVE */ + &gauge_layout_v4, /* GAUGE */ + NULL, /* UNUSED */ + NULL, /* FREEDIVE_ADV */ + }, }; static dc_status_t @@ -313,6 +334,8 @@ cressi_goa_init(cressi_goa_parser_t *parser) version = 3; } else if (firmware >= 200 && firmware <= 205) { version = 4; + } else if (firmware >= 300) { + version = 5; } else { ERROR (abstract->context, "Unknown firmware version (%u).", firmware); return DC_STATUS_DATAFORMAT; diff --git a/src/descriptor.c b/src/descriptor.c index 22353988..e1615574 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -343,6 +343,7 @@ static const dc_descriptor_t g_descriptors[] = { /* Cressi Goa */ {"Cressi", "Cartesio", DC_FAMILY_CRESSI_GOA, 1, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, {"Cressi", "Goa", DC_FAMILY_CRESSI_GOA, 2, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, + {"Cressi", "Leonardo 2.0", DC_FAMILY_CRESSI_GOA, 3, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, {"Cressi", "Donatello", DC_FAMILY_CRESSI_GOA, 4, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, {"Cressi", "Michelangelo", DC_FAMILY_CRESSI_GOA, 5, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, {"Cressi", "Neon", DC_FAMILY_CRESSI_GOA, 9, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, @@ -911,6 +912,7 @@ dc_filter_cressi (dc_descriptor_t *descriptor, dc_transport_t transport, const v static const unsigned int model[] = { 1, // Cartesio 2, // Goa + 3, // Leonardo 2.0 4, // Donatello 5, // Michelangelo 9, // Neon From 0a9fad13fa92f8ecac41b99926715edc2918b052 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 1 Oct 2024 11:37:10 +0200 Subject: [PATCH 16/17] Add CCR support for the Excursion There is a minor update to the dataformat to support CCR dives. For the current firmware, the setpoint change event was originally defined as a 4 byte event, but the new implementation changed this to only 2 bytes. Since the current firmware doesn't support CCR dives yet, the 4 bytes variant is not supposed to appear in the sample data and should be ignored. --- src/deepsix_excursion_parser.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/deepsix_excursion_parser.c b/src/deepsix_excursion_parser.c index 52d06af9..e197891e 100644 --- a/src/deepsix_excursion_parser.c +++ b/src/deepsix_excursion_parser.c @@ -334,6 +334,9 @@ deepsix_excursion_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, case 2: *((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE; break; + case 3: + *((dc_divemode_t *) value) = DC_DIVEMODE_CCR; + break; default: return DC_STATUS_DATAFORMAT; } @@ -550,6 +553,12 @@ deepsix_excursion_parser_samples_foreach_v1 (dc_parser_t *abstract, dc_sample_ca return DC_STATUS_DATAFORMAT; } break; + case EVENT_CHANGE_SETPOINT: + if (event_info[i].size != 4 && event_info[i].size != 2) { + ERROR(abstract->context, "Unexpected event size (%u).", event_info[i].size); + return DC_STATUS_DATAFORMAT; + } + break; case EVENT_SAMPLES_MISSED: if (event_info[i].size != 6) { ERROR(abstract->context, "Unexpected event size (%u).", event_info[i].size); @@ -670,6 +679,14 @@ deepsix_excursion_parser_samples_foreach_v1 (dc_parser_t *abstract, dc_sample_ca sample.gasmix = mix_idx; if (callback) callback(DC_SAMPLE_GASMIX, &sample, userdata); break; + case EVENT_CHANGE_SETPOINT: + // Ignore the 4 byte variant because it's not + // supposed to be present in the sample data. + if (event_info[i].size == 2) { + sample.setpoint = data[offset + event_offset] / 10.0; + if (callback) callback(DC_SAMPLE_SETPOINT, &sample, userdata); + } + break; case EVENT_SAMPLES_MISSED: count = array_uint16_le(data + offset + event_offset); timestamp = array_uint32_le(data + offset + event_offset + 2); From 02feecc5c87335ffbdc439d11892469daccdbf05 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 3 Dec 2024 22:13:43 +0100 Subject: [PATCH 17/17] Add support for the Uwatec Aladin One The Uwatec Aladin One shares the same model number with the Tec and Prime and is therefore already supported. Reported-by: Sven Knoch --- src/descriptor.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/descriptor.c b/src/descriptor.c index e1615574..92d28278 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -147,6 +147,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Uwatec", "Galileo Terra", DC_FAMILY_UWATEC_SMART, 0x11, DC_TRANSPORT_IRDA, dc_filter_uwatec}, {"Uwatec", "Aladin Tec", DC_FAMILY_UWATEC_SMART, 0x12, DC_TRANSPORT_IRDA, dc_filter_uwatec}, {"Uwatec", "Aladin Prime", DC_FAMILY_UWATEC_SMART, 0x12, DC_TRANSPORT_IRDA, dc_filter_uwatec}, + {"Uwatec", "Aladin One", DC_FAMILY_UWATEC_SMART, 0x12, DC_TRANSPORT_IRDA, dc_filter_uwatec}, {"Uwatec", "Aladin Tec 2G", DC_FAMILY_UWATEC_SMART, 0x13, DC_TRANSPORT_IRDA, dc_filter_uwatec}, {"Uwatec", "Aladin 2G", DC_FAMILY_UWATEC_SMART, 0x13, DC_TRANSPORT_IRDA, dc_filter_uwatec}, {"Subgear", "XP-10", DC_FAMILY_UWATEC_SMART, 0x13, DC_TRANSPORT_IRDA, dc_filter_uwatec},