From b9a99de1582bf6d799426ee301626173a32345df Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 24 Jan 2021 08:23:34 +0100 Subject: [PATCH 01/22] Fix the oceanic filter function The matching functions expect a pointer to the value as argument, and not the value itself. Since a C string is already a pointer (to a NULL terminated character array), an extra pointer indirection is required. --- src/descriptor.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/descriptor.c b/src/descriptor.c index 7f4a966..60a8fbd 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -486,7 +486,9 @@ dc_match_oceanic (const void *key, const void *value) 0 }; - return dc_match_number_with_prefix (key, &prefix); + const char *p = prefix; + + return dc_match_number_with_prefix (key, &p); } static int From efc9236fbc869777aad4d308b05b73e90ea18e5f Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 24 Jan 2021 08:26:03 +0100 Subject: [PATCH 02/22] Fix the return value for a NULL key An example where the filter functions can be called with a NULL key is when a bluetooth discovery fails to retrieve the name of the remote device. In such case, we have no information to detect whether the bluetooth device matches a known dive computer or not, and thus it shouldn't be filtered out. --- src/descriptor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/descriptor.c b/src/descriptor.c index 60a8fbd..64715e2 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -495,7 +495,7 @@ static int dc_filter_internal (const void *key, const void *values, size_t count, size_t size, dc_match_t match, void *params_dst, const void *params_src, size_t params_size) { if (key == NULL) - return 0; + return 1; for (size_t i = 0; i < count; ++i) { if (match (key, (const unsigned char *) values + i * size)) { From 0f677fcaac2a9fe746ff74904bdcb2daa64c418c Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 25 Jan 2021 21:37:11 +0100 Subject: [PATCH 03/22] Perform the check for the NULL key earlier The previous commit added a check for a NULL key inside the filter functions, but it's more efficient to handle it early on, before even calling the filter function. --- src/descriptor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/descriptor.c b/src/descriptor.c index 64715e2..7ccff3a 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -803,7 +803,7 @@ dc_descriptor_get_transports (dc_descriptor_t *descriptor) int dc_descriptor_filter (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata, void *params) { - if (descriptor == NULL || descriptor->filter == NULL) + if (descriptor == NULL || descriptor->filter == NULL || userdata == NULL) return 1; return descriptor->filter (transport, userdata, params); From 034819cd2dd4639a5277542a2ec6c7c8c3c1da92 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 7 Feb 2021 23:11:14 +0100 Subject: [PATCH 04/22] Fix the McLean Extreme fingerprint feature Currently the fingerprint feature uses the first 7 bytes of the computer configuration data. Since this information does not uniquely identify a dive, and is actually often identical for several dives, no new dives are detected anymore. Fixed by using the date/time timestamp at the start of the dive configuration data instead. Reported-by: David Carron --- src/mclean_extreme.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mclean_extreme.c b/src/mclean_extreme.c index 5c8060c..b69546f 100644 --- a/src/mclean_extreme.c +++ b/src/mclean_extreme.c @@ -42,7 +42,7 @@ #define CMD_FIRMWARE 0xAD #define SZ_PACKET 512 -#define SZ_FINGERPRINT 7 +#define SZ_FINGERPRINT 4 #define SZ_CFG 0x002D #define SZ_COMPUTER (SZ_CFG + 0x6A) #define SZ_HEADER (SZ_CFG + 0x31) @@ -580,10 +580,10 @@ mclean_extreme_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback unsigned char *data = dc_buffer_get_data(buffer); unsigned int size = dc_buffer_get_size(buffer); - if (memcmp(data, device->fingerprint, sizeof(device->fingerprint)) == 0) + if (memcmp(data + SZ_CFG, device->fingerprint, sizeof(device->fingerprint)) == 0) break; - if (callback && !callback (data, size, data, sizeof(device->fingerprint), userdata)) { + if (callback && !callback (data, size, data + SZ_CFG, sizeof(device->fingerprint), userdata)) { break; } } From d63c6cd04e254ced1fab3e8510a0dd76f1ebee10 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 8 Feb 2021 19:24:00 +0100 Subject: [PATCH 05/22] Add a new Perdix AI hardware type --- src/shearwater_petrel.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shearwater_petrel.c b/src/shearwater_petrel.c index 5ca52e3..ecf3320 100644 --- a/src/shearwater_petrel.c +++ b/src/shearwater_petrel.c @@ -227,6 +227,7 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call model = PERDIX; break; case 0x0C0D: + case 0x7C2D: model = PERDIXAI; break; case 0x0F0F: From ecc23a5a76e5150109ed28000074fbc1260c6e85 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 9 Feb 2021 20:56:51 +0100 Subject: [PATCH 06/22] Simplify the loop for reading the packet header The for loop construct without an increment statement is a bit unusual and thus easy to miss. With an equivalent while loop, the intent becomes a bit more obvious. --- src/uwatec_aladin.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/uwatec_aladin.c b/src/uwatec_aladin.c index 3ebd094..15bcbfd 100644 --- a/src/uwatec_aladin.c +++ b/src/uwatec_aladin.c @@ -164,7 +164,8 @@ uwatec_aladin_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) unsigned char answer[SZ_MEMORY + 2] = {0}; // Receive the header of the package. - for (unsigned int i = 0; i < 4;) { + unsigned int i = 0; + while (i < 4) { if (device_is_cancelled (abstract)) return DC_STATUS_CANCELLED; @@ -173,11 +174,13 @@ uwatec_aladin_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) ERROR (abstract->context, "Failed to receive the answer."); return status; } - if (answer[i] == (i < 3 ? 0x55 : 0x00)) { - i++; // Continue. - } else { - i = 0; // Reset. + + const unsigned char expected = i < 3 ? 0x55 : 0x00; + if (answer[i] != expected) { device_event_emit (abstract, DC_EVENT_WAITING, NULL); + i = 0; + } else { + i++; } } From f42df2d846bc6d079e4ecebebe8ae0c9dc287e01 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 9 Feb 2021 20:57:24 +0100 Subject: [PATCH 07/22] Remove the infinite timeout When an Uwatec Aladin is connected, but the transfer hasn't been started yet, we receive a continuous stream of zero bytes. Approximately every 7-8ms a new zero byte is received. But when the dive computer is (temporary) disconnected, the stream of zero bytes also ends. The consequence is that due to the use of blocking read call with an infinite timeout, the application becomes unresponsive, without any chance to abort the communication. This can eaily be avoided by using a timeout instead. Receiving the main 2048 byte packet takes about 1050ms. Thus a 3000ms timeout should be long enough to not cause the main data transfer to timeout, but still short enough to cancel reasonable fast. --- src/uwatec_aladin.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/uwatec_aladin.c b/src/uwatec_aladin.c index 15bcbfd..1cfec54 100644 --- a/src/uwatec_aladin.c +++ b/src/uwatec_aladin.c @@ -96,8 +96,8 @@ uwatec_aladin_device_open (dc_device_t **out, dc_context_t *context, dc_iostream goto error_free; } - // Set the timeout for receiving data (INFINITE). - status = dc_iostream_set_timeout (device->iostream, -1); + // Set the timeout for receiving data (3000ms). + status = dc_iostream_set_timeout (device->iostream, 3000); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to set the timeout."); goto error_free; @@ -172,11 +172,12 @@ uwatec_aladin_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) status = dc_iostream_read (device->iostream, answer + i, 1, NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to receive the answer."); - return status; + if (status != DC_STATUS_TIMEOUT) + return status; } const unsigned char expected = i < 3 ? 0x55 : 0x00; - if (answer[i] != expected) { + if (status != DC_STATUS_SUCCESS || answer[i] != expected) { device_event_emit (abstract, DC_EVENT_WAITING, NULL); i = 0; } else { From 580e1d5fc513793832b3fa484f287ba9b4de3a08 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 4 Feb 2021 18:03:19 +0100 Subject: [PATCH 08/22] Add support for the Sherwood Beacon The Sherwood Beacon appears to be compatible with the Sherwood Sage. For the BLE communication the handshake also fails and is disabled. --- src/descriptor.c | 2 ++ src/oceanic_atom2.c | 4 +++- src/oceanic_atom2_parser.c | 23 +++++++++++++++-------- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index 7ccff3a..bd1e587 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -263,6 +263,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Oceanic", "Veo 4.0", DC_FAMILY_OCEANIC_ATOM2, 0x4654, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, {"Sherwood", "Wisdom 4", DC_FAMILY_OCEANIC_ATOM2, 0x4655, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, {"Oceanic", "Pro Plus 4", DC_FAMILY_OCEANIC_ATOM2, 0x4656, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, + {"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}, /* Mares Nemo */ {"Mares", "Nemo", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL}, @@ -667,6 +668,7 @@ static int dc_filter_oceanic (dc_transport_t transport, const void *userdata, vo 0x4654, // Oceanic Veo 4.0 0x4655, // Sherwood Wisdom 4 0x4656, // Oceanic Pro Plus 4 + 0x4742, // Sherwood Beacon 0x4743, // Aqualung i470TC }; diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index c4de23d..5534780 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -41,6 +41,7 @@ #define SAGE 0x4647 #define I770R 0x4651 #define GEO40 0x4653 +#define BEACON 0x4742 #define MAXPACKET 256 #define MAXRETRIES 2 @@ -493,6 +494,7 @@ static const oceanic_common_version_t versions[] = { {"OCEANVTX \0\0 2048", 0, &aeris_a300cs_layout}, {"AQUAI750 \0\0 2048", 0, &aeris_a300cs_layout}, {"SWDRAGON \0\0 2048", 0, &aeris_a300cs_layout}, + {"SWBEACON \0\0 2048", 0, &aeris_a300cs_layout}, {"AQUAI450 \0\0 2048", 0, &aqualung_i450t_layout}, @@ -904,7 +906,7 @@ oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream } if (dc_iostream_get_transport (device->iostream) == DC_TRANSPORT_BLE && - model != PROPLUSX && model != SAGE ) { + model != PROPLUSX && model != SAGE && model != BEACON) { status = oceanic_atom2_ble_handshake(device); if (status != DC_STATUS_SUCCESS) { goto error_free; diff --git a/src/oceanic_atom2_parser.c b/src/oceanic_atom2_parser.c index 9ddb1d4..5a96c7c 100644 --- a/src/oceanic_atom2_parser.c +++ b/src/oceanic_atom2_parser.c @@ -97,6 +97,7 @@ #define VEO40 0x4654 #define WISDOM4 0x4655 #define PROPLUS4 0x4656 +#define BEACON 0x4742 #define I470TC 0x4743 #define NORMAL 0 @@ -189,7 +190,8 @@ oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, unsigned parser->footersize = 0; } else if (model == A300CS || model == VTX || model == I450T || model == I750TC || - model == I770R || model == SAGE) { + model == I770R || model == SAGE || + model == BEACON) { parser->headersize = 5 * PAGESIZE; } else if (model == PROPLUSX) { parser->headersize = 3 * PAGESIZE; @@ -343,6 +345,7 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim case PROPLUSX: case I770R: case SAGE: + case BEACON: datetime->year = (p[10]) + 2000; datetime->month = (p[8]); datetime->day = (p[9]); @@ -463,7 +466,8 @@ oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser) he_offset = 0x48; ngasmixes = 6; } else if (parser->model == A300CS || parser->model == VTX || - parser->model == I750TC || parser->model == SAGE) { + parser->model == I750TC || parser->model == SAGE || + parser->model == BEACON) { o2_offset = 0x2A; if (data[0x39] & 0x04) { ngasmixes = 1; @@ -671,7 +675,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ if (parser->model == A300CS || parser->model == VTX || parser->model == I450T || parser->model == I750TC || parser->model == PROPLUSX || parser->model == I770R || - parser->model == SAGE) + parser->model == SAGE || parser->model == BEACON) idx = 0x1f; switch (data[idx] & 0x03) { case 0: @@ -728,7 +732,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == VTX || parser->model == I450T || parser->model == I750TC || parser->model == PROPLUSX || parser->model == I770R || parser->model == I470TC || - parser->model == SAGE) { + parser->model == SAGE || parser->model == BEACON) { samplesize = PAGESIZE; } @@ -813,7 +817,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ tank = 0; pressure = (((data[offset + 7] << 8) + data[offset + 6]) & 0x0FFF); } else if (parser->model == A300CS || parser->model == VTX || - parser->model == I750TC || parser->model == SAGE) { + parser->model == I750TC || parser->model == SAGE || + parser->model == BEACON) { // Tank pressure (1 psi) and number (one based index) tank = (data[offset + 1] & 0x03) - 1; pressure = ((data[offset + 7] << 8) + data[offset + 6]) & 0x0FFF; @@ -915,7 +920,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ temperature = ((data[offset + 7] & 0xF0) >> 4) | ((data[offset + 7] & 0x0C) << 2) | ((data[offset + 5] & 0x0C) << 4); } else if (parser->model == A300CS || parser->model == VTX || parser->model == I750TC || parser->model == PROPLUSX || - parser->model == I770R|| parser->model == SAGE) { + parser->model == I770R|| parser->model == SAGE || + parser->model == BEACON) { temperature = data[offset + 11]; } else { unsigned int sign; @@ -960,7 +966,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ else if (parser->model == TX1 || parser->model == A300CS || parser->model == VTX || parser->model == I750TC || parser->model == PROPLUSX || parser->model == I770R || - parser->model == SAGE) + parser->model == SAGE || parser->model == BEACON) pressure = array_uint16_le (data + offset + 4); else pressure -= data[offset + 1]; @@ -1012,7 +1018,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ unsigned int decostop = 0, decotime = 0; if (parser->model == A300CS || parser->model == VTX || parser->model == I750TC || parser->model == SAGE || - parser->model == PROPLUSX || parser->model == I770R) { + parser->model == PROPLUSX || parser->model == I770R || + parser->model == BEACON) { decostop = (data[offset + 15] & 0x70) >> 4; decotime = array_uint16_le(data + offset + 6) & 0x03FF; have_deco = 1; From 03ddc843843db065e3ca5b0757a669d3836d8e31 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 14 Feb 2021 09:17:33 +0100 Subject: [PATCH 09/22] Mark the new iX3M 2021 models as supporting BLE The new iX3M 2021 models with bluetooth do support BLE communication. Bluetooth Classic (rfcomm), which was the only supported bluetooth variant in the previous models, is not available. --- src/descriptor.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index bd1e587..bb26b62 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -385,12 +385,12 @@ static const dc_descriptor_t g_descriptors[] = { {"Ratio", "iDive Color Deep", DC_FAMILY_DIVESYSTEM_IDIVE, 0x54, DC_TRANSPORT_SERIAL, NULL}, {"Ratio", "iDive Color Tech+",DC_FAMILY_DIVESYSTEM_IDIVE, 0x55, DC_TRANSPORT_SERIAL, NULL}, {"Ratio", "iDive Color Reb", DC_FAMILY_DIVESYSTEM_IDIVE, 0x56, DC_TRANSPORT_SERIAL, NULL}, - {"Ratio", "iX3M 2021 GPS Fancy", DC_FAMILY_DIVESYSTEM_IDIVE, 0x60, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_divesystem}, - {"Ratio", "iX3M 2021 GPS Easy", DC_FAMILY_DIVESYSTEM_IDIVE, 0x61, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_divesystem}, - {"Ratio", "iX3M 2021 GPS Pro ", DC_FAMILY_DIVESYSTEM_IDIVE, 0x62, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_divesystem}, - {"Ratio", "iX3M 2021 GPS Deep", DC_FAMILY_DIVESYSTEM_IDIVE, 0x63, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_divesystem}, - {"Ratio", "iX3M 2021 GPS Tech+", DC_FAMILY_DIVESYSTEM_IDIVE, 0x64, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_divesystem}, - {"Ratio", "iX3M 2021 GPS Reb", DC_FAMILY_DIVESYSTEM_IDIVE, 0x65, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_divesystem}, + {"Ratio", "iX3M 2021 GPS Fancy", DC_FAMILY_DIVESYSTEM_IDIVE, 0x60, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_divesystem}, + {"Ratio", "iX3M 2021 GPS Easy", DC_FAMILY_DIVESYSTEM_IDIVE, 0x61, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_divesystem}, + {"Ratio", "iX3M 2021 GPS Pro ", DC_FAMILY_DIVESYSTEM_IDIVE, 0x62, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_divesystem}, + {"Ratio", "iX3M 2021 GPS Deep", DC_FAMILY_DIVESYSTEM_IDIVE, 0x63, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_divesystem}, + {"Ratio", "iX3M 2021 GPS Tech+", DC_FAMILY_DIVESYSTEM_IDIVE, 0x64, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_divesystem}, + {"Ratio", "iX3M 2021 GPS Reb", DC_FAMILY_DIVESYSTEM_IDIVE, 0x65, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_divesystem}, {"Ratio", "iX3M 2021 Pro Fancy", DC_FAMILY_DIVESYSTEM_IDIVE, 0x70, DC_TRANSPORT_SERIAL, NULL}, {"Ratio", "iX3M 2021 Pro Easy", DC_FAMILY_DIVESYSTEM_IDIVE, 0x71, DC_TRANSPORT_SERIAL, NULL}, {"Ratio", "iX3M 2021 Pro Pro", DC_FAMILY_DIVESYSTEM_IDIVE, 0x72, DC_TRANSPORT_SERIAL, NULL}, @@ -648,7 +648,7 @@ static int dc_filter_divesystem (dc_transport_t transport, const void *userdata, "IX5M", }; - if (transport == DC_TRANSPORT_BLUETOOTH) { + if (transport == DC_TRANSPORT_BLUETOOTH || transport == DC_TRANSPORT_BLE) { return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_number_with_prefix); } From b713136a003bb7d637e03f0583f8a4e9eace07ca Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 15 Jan 2021 19:16:20 +0100 Subject: [PATCH 10/22] Fix -Wcast-qual compiler warning The CBC initialization vector is passed as a const pointer, and then cast to a non-const pointer to store it in the aes state struct. This cast can easily be avoided by changing the struct field into a const pointer. --- src/aes.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aes.c b/src/aes.c index 55eac5c..15a1963 100644 --- a/src/aes.c +++ b/src/aes.c @@ -98,7 +98,7 @@ typedef struct aes_state_t { #if defined(CBC) && CBC // Initial Vector used only for CBC mode - uint8_t* Iv; + const uint8_t* Iv; #endif } aes_state_t; @@ -542,7 +542,7 @@ void AES128_CBC_encrypt_buffer(uint8_t* output, uint8_t* input, uint32_t length, if(iv != 0) { - state.Iv = (uint8_t*)iv; + state.Iv = iv; } for(i = 0; i < length; i += KEYLEN) @@ -584,7 +584,7 @@ void AES128_CBC_decrypt_buffer(uint8_t* output, uint8_t* input, uint32_t length, // If iv is passed as 0, we continue to encrypt without re-setting the Iv if(iv != 0) { - state.Iv = (uint8_t*)iv; + state.Iv = iv; } for(i = 0; i < length; i += KEYLEN) From 3d3271abe100e365584dc3365365dd9774fb2643 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 23 Feb 2021 20:39:47 +0100 Subject: [PATCH 11/22] Mark the McLean Extreme as supporting BLE The McLean Extreme uses a dual stack Bluetooth module from Microchip which supports both Bluetooth Classic and Low Energy. Reported-by: David Carron --- src/descriptor.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index bb26b62..531c5d0 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -409,7 +409,7 @@ static const dc_descriptor_t g_descriptors[] = { /* Tecdiving DiveComputer.eu */ {"Tecdiving", "DiveComputer.eu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_tecdiving}, /* McLean Extreme */ - { "McLean", "Extreme", DC_FAMILY_MCLEAN_EXTREME, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_mclean}, + { "McLean", "Extreme", DC_FAMILY_MCLEAN_EXTREME, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_mclean}, /* Liquivision */ {"Liquivision", "Xen", DC_FAMILY_LIQUIVISION_LYNX, 0, DC_TRANSPORT_SERIAL, NULL}, {"Liquivision", "Xeo", DC_FAMILY_LIQUIVISION_LYNX, 1, DC_TRANSPORT_SERIAL, NULL}, @@ -685,7 +685,7 @@ static int dc_filter_mclean(dc_transport_t transport, const void *userdata, void "McLean Extreme", }; - if (transport == DC_TRANSPORT_BLUETOOTH) { + if (transport == DC_TRANSPORT_BLUETOOTH || transport == DC_TRANSPORT_BLE) { return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_name); } else if (transport == DC_TRANSPORT_SERIAL) { return DC_FILTER_INTERNAL(userdata, rfcomm, 1, dc_match_devname); From 95920af7b7eb2963ef0dacf91ed1afaf8c1644d0 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 4 Mar 2021 12:16:57 +0100 Subject: [PATCH 12/22] Fix the maximum depth The upper bits appear to contain some other (currently unknown) information. The Oceanic VT Pro specifications list a maximum depth of 399 ft (120 m), which requires only 9 bits. The Oceanic application also ignores the higher bits. --- src/oceanic_vtpro_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oceanic_vtpro_parser.c b/src/oceanic_vtpro_parser.c index d09b2f9..54a52d8 100644 --- a/src/oceanic_vtpro_parser.c +++ b/src/oceanic_vtpro_parser.c @@ -181,7 +181,7 @@ oceanic_vtpro_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns maxdepth = data[footer + 1]; } else { oxygen = data[footer + 3]; - maxdepth = array_uint16_le(data + footer + 0) & 0x0FFF; + maxdepth = array_uint16_le(data + footer + 0) & 0x01FF; } dc_gasmix_t *gasmix = (dc_gasmix_t *) value; From d49a8a3e645004b47461f446f6e439752bd91129 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 4 Mar 2021 12:33:14 +0100 Subject: [PATCH 13/22] Implement the ndl/deco sample --- src/oceanic_vtpro_parser.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/oceanic_vtpro_parser.c b/src/oceanic_vtpro_parser.c index 54a52d8..fff7389 100644 --- a/src/oceanic_vtpro_parser.c +++ b/src/oceanic_vtpro_parser.c @@ -359,6 +359,21 @@ oceanic_vtpro_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ sample.temperature = (temperature - 32.0) * (5.0 / 9.0); if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); + // NDL / Deco + if (parser->model != AERIS500AI) { + unsigned int decostop = (data[offset + 5] & 0xF0) >> 4; + unsigned int decotime = array_uint16_le(data + offset + 4) & 0x0FFF; + if (decostop) { + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.depth = decostop * 10 * FEET; + } else { + sample.deco.type = DC_DECO_NDL; + sample.deco.depth = 0.0; + } + sample.deco.time = decotime * 60; + if (callback) callback (DC_SAMPLE_DECO, sample, userdata); + } + offset += PAGESIZE / 2; } From 6bb13a564f91f35ee2555eda55fbc9f2b80daa38 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 21 Feb 2021 19:47:42 +0100 Subject: [PATCH 14/22] Extend the OS detection to non Windows platforms At the moment, only the Windows platform needs some special handling, but this can easily be extended to detect and handle other platforms as well. --- configure.ac | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/configure.ac b/configure.ac index 51592b8..4fc142d 100644 --- a/configure.ac +++ b/configure.ac @@ -72,18 +72,18 @@ AM_CONDITIONAL([HAVE_MANDOC],[test -n "$MANDOC"]) # Enable automake silent build rules. m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) -# Checks for native Windows. -AC_MSG_CHECKING([for native Win32]) +# Checks for operating system. +AC_MSG_CHECKING([for operating system]) case "$host" in *-*-mingw*) - os_win32=yes + platform=windows ;; *) - os_win32=no + platform=default ;; esac -AC_MSG_RESULT([$os_win32]) -AM_CONDITIONAL([OS_WIN32], [test "$os_win32" = "yes"]) +AC_MSG_RESULT([$platform]) +AM_CONDITIONAL([OS_WIN32], [test "$platform" = "windows"]) DEPENDENCIES="" @@ -192,7 +192,7 @@ AX_APPEND_COMPILE_FLAGS([ \ ]) # Windows specific compiler options. -AS_IF([test "$os_win32" = "yes"], [ +AS_IF([test "$platform" = "windows"], [ AX_APPEND_COMPILE_FLAGS([-Wno-pedantic-ms-format]) ]) From 6b576da5ef29be96f098fde48c1a2e99382879da Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 21 Feb 2021 20:08:17 +0100 Subject: [PATCH 15/22] Show a summary after configuration --- configure.ac | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/configure.ac b/configure.ac index 4fc142d..c9462ff 100644 --- a/configure.ac +++ b/configure.ac @@ -78,6 +78,9 @@ case "$host" in *-*-mingw*) platform=windows ;; + *-*-darwin*) + platform=mac + ;; *) platform=default ;; @@ -208,6 +211,25 @@ m4_ifset([dc_version_suffix],[ AC_DEFINE(HAVE_VERSION_SUFFIX, [1], [Define if a version suffix is present.]) ]) +# Supported transports +transport_serial="yes" +transport_usb="${have_libusb-no}" +if test "$have_hidapi" = "yes"; then + transport_usbhid="yes" +elif test "$have_libusb" = "yes" && test "$platform" != "mac"; then + transport_usbhid="yes" +else + transport_usbhid="no" +fi +if test "$platform" = "windows"; then + transport_irda="$ac_cv_header_af_irda_h" + transport_bluetooth="$ac_cv_header_ws2bth_h" +else + transport_irda="$ac_cv_header_linux_irda_h" + transport_bluetooth="${have_bluez-no}" +fi +transport_ble="no" + AC_CONFIG_FILES([ libdivecomputer.pc Makefile @@ -222,3 +244,35 @@ AC_CONFIG_FILES([ examples/Makefile ]) AC_OUTPUT +AC_MSG_NOTICE([ + $PACKAGE $VERSION + =============== + + Compiler: + + CC : $CC + CFLAGS : $CFLAGS + LDFLAGS : $LDFLAGS + + Features: + + Logging : $enable_logging + Pseudo terminal : $enable_pty + Example applications : $enable_examples + Documentation : $enable_doc + + Transports: + + Serial : $transport_serial + USB : $transport_usb + USBHID : $transport_usbhid + IrDA : $transport_irda + Bluetooth : $transport_bluetooth + BLE : $transport_ble + + Building: + + Type 'make' to compile $PACKAGE. + + Type 'make install' to install $PACKAGE. +]) From 007a2bc83568f4429fe727e5b435ed08f105fa07 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 23 Feb 2021 21:02:26 +0100 Subject: [PATCH 16/22] Add Github Actions CI builds and releases The new Github Actions offers similar functionality as the Travis CI integration, but with some interesting extra features: The build action is almost equivalent to the existing Travis build configuration. But as an extra feature, the build artifacts are now available for download. The release action does automatically build a distribution tarball and create a Github release, whenever a new version is tagged and pushed. --- .github/workflows/build.yml | 95 +++++++++++++++++++++++++++++++++++ .github/workflows/release.yml | 61 ++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..8fa7569 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,95 @@ +name: Build + +on: [push, pull_request] + +jobs: + + linux: + + name: Linux + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + compiler: [gcc, clang] + + env: + CC: ${{ matrix.compiler }} + + steps: + - uses: actions/checkout@v2 + - name: Install dependencies + run: sudo apt-get install libbluetooth-dev libusb-1.0-0-dev + - run: autoreconf --install --force + - run: ./configure --prefix=/usr + - run: make + - run: make distcheck + - name: Package artifacts + run: | + make install DESTDIR=$PWD/artifacts + tar -czf ${{ github.job }}-${{ matrix.compiler }}.tar.gz -C artifacts usr + - uses: actions/upload-artifact@v2 + with: + name: ${{ github.job }}-${{ matrix.compiler }} + path: ${{ github.job }}-${{ matrix.compiler }}.tar.gz + + mac: + + name: Mac + + runs-on: macos-latest + + strategy: + fail-fast: false + matrix: + compiler: [gcc, clang] + + env: + CC: ${{ matrix.compiler }} + + steps: + - uses: actions/checkout@v2 + - name: Install dependencies + run: brew install autoconf automake libtool hidapi libusb + - run: autoreconf --install --force + - run: ./configure --prefix=/usr + - run: make + - run: make distcheck + - name: Package artifacts + run: | + make install DESTDIR=$PWD/artifacts + tar -czf ${{ github.job }}-${{ matrix.compiler }}.tar.gz -C artifacts usr + - uses: actions/upload-artifact@v2 + with: + name: ${{ github.job }}-${{ matrix.compiler }} + path: ${{ github.job }}-${{ matrix.compiler }}.tar.gz + + windows: + + name: Windows + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + arch: [i686, x86_64] + + steps: + - uses: actions/checkout@v2 + - name: Install dependencies + run: sudo apt-get install gcc-mingw-w64 binutils-mingw-w64 mingw-w64-tools + - run: autoreconf --install --force + - run: ./configure --host=${{ matrix.arch }}-w64-mingw32 --prefix=/usr + - run: make + - run: make distcheck + - name: Package artifacts + run: | + make install DESTDIR=$PWD/artifacts + tar -czf ${{ github.job }}-${{ matrix.arch }}.tar.gz -C artifacts usr + - uses: actions/upload-artifact@v2 + with: + name: ${{ github.job }}-${{ matrix.arch }} + path: ${{ github.job }}-${{ matrix.arch }}.tar.gz diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..37f578e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,61 @@ +name: Release + +on: + push: + tags: 'v*' + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Version number + id: version + run: | + VERSION="${GITHUB_REF/refs\/tags\/v/}" + if [ "${VERSION}" = "${VERSION%%-*}" ]; then + PRERELEASE=false + else + PRERELEASE=true + fi + echo ::set-output name=version::${VERSION} + echo ::set-output name=prerelease::${PRERELEASE} + + - name: Build distribution tarball + id: build + run: | + sudo apt-get install libbluetooth-dev libusb-1.0-0-dev + autoreconf --install --force + ./configure + make + make distcheck + + - name: Check tarball version number + id: check + run: | + FILENAME="libdivecomputer-${{ steps.version.outputs.version }}.tar.gz" + if [ ! -f "${FILENAME}" ]; then + echo ::error ::Tarball \'${FILENAME}\' not found! + exit 1 + fi + + - uses: actions/create-release@v1 + id: release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: ${{ github.ref }} + prerelease: ${{ steps.version.outputs.prerelease }} + + - uses: actions/upload-release-asset@v1 + id: upload + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.release.outputs.upload_url }} + asset_path: libdivecomputer-${{ steps.version.outputs.version }}.tar.gz + asset_name: libdivecomputer-${{ steps.version.outputs.version }}.tar.gz + asset_content_type: application/gzip From a4d771956aaaabfe03e647f863dcff86a845275a Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 9 Mar 2021 23:01:20 +0100 Subject: [PATCH 17/22] Fix the clang compiler flag detection The clang compiler accepts *any* compiler flags, and just reports an annoying warning instead: warning: unknown warning option '-Wsomething' [-Wunknown-warning-option] Use -Werror=unknown-warning-option to change this warning into an error, and detect the unsupported flag. Since this is a clang specific option, it can't be used unconditionally with other compilers. The option is also only used for checking the compiler flags, and not for compiling the code. --- configure.ac | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index c9462ff..5e84ad0 100644 --- a/configure.ac +++ b/configure.ac @@ -177,6 +177,7 @@ AC_CHECK_FUNCS([clock_gettime mach_absolute_time]) AC_CHECK_FUNCS([getopt_long]) # Checks for supported compiler options. +AX_APPEND_COMPILE_FLAGS([-Werror=unknown-warning-option],[ERROR_CFLAGS]) AX_APPEND_COMPILE_FLAGS([ \ -pedantic \ -Wall \ @@ -192,7 +193,7 @@ AX_APPEND_COMPILE_FLAGS([ \ -Wmissing-declarations \ -Wno-unused-parameter \ -fmacro-prefix-map='$(top_srcdir)/'= \ -]) +],,[$ERROR_CFLAGS]) # Windows specific compiler options. AS_IF([test "$platform" = "windows"], [ From 752a064bb3441ac8cce7e7ec72e2194f70f89bf6 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 23 Mar 2021 21:12:18 +0100 Subject: [PATCH 18/22] Use a common sleep implementation Implement a common sleep function to eliminate some conditional compilation in the rest of the code. --- msvc/libdivecomputer.vcproj | 4 +++ src/Makefile.am | 2 +- src/irda.c | 6 +---- src/platform.c | 51 +++++++++++++++++++++++++++++++++++++ src/platform.h | 2 ++ src/serial_posix.c | 14 +++------- src/serial_win32.c | 7 ++++- src/socket.c | 19 ++++---------- 8 files changed, 74 insertions(+), 31 deletions(-) create mode 100644 src/platform.c diff --git a/msvc/libdivecomputer.vcproj b/msvc/libdivecomputer.vcproj index 5dcad41..da5187d 100644 --- a/msvc/libdivecomputer.vcproj +++ b/msvc/libdivecomputer.vcproj @@ -398,6 +398,10 @@ RelativePath="..\src\parser.c" > + + diff --git a/src/Makefile.am b/src/Makefile.am index b33acd0..e78f37d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -64,7 +64,7 @@ libdivecomputer_la_SOURCES = \ diverite_nitekq.h diverite_nitekq.c diverite_nitekq_parser.c \ citizen_aqualand.h citizen_aqualand.c citizen_aqualand_parser.c \ divesystem_idive.h divesystem_idive.c divesystem_idive_parser.c \ - platform.h \ + platform.h platform.c \ ringbuffer.h ringbuffer.c \ rbstream.h rbstream.c \ checksum.h checksum.c \ diff --git a/src/irda.c b/src/irda.c index 2cc58ab..8c5d612 100644 --- a/src/irda.c +++ b/src/irda.c @@ -202,11 +202,7 @@ dc_irda_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descriptor_ // modified by the previous getsockopt call. size = sizeof (data); -#ifdef _WIN32 - Sleep (1000); -#else - sleep (1); -#endif + dc_platform_sleep (1000); } S_CLOSE (fd); diff --git a/src/platform.c b/src/platform.c new file mode 100644 index 0000000..16a3c08 --- /dev/null +++ b/src/platform.c @@ -0,0 +1,51 @@ +/* + * libdivecomputer + * + * Copyright (C) 2021 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 _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#include +#else +#include +#include +#endif + +#include "platform.h" + +int +dc_platform_sleep (unsigned int milliseconds) +{ +#ifdef _WIN32 + Sleep (milliseconds); +#else + struct timespec ts; + ts.tv_sec = (milliseconds / 1000); + ts.tv_nsec = (milliseconds % 1000) * 1000000; + + while (nanosleep (&ts, &ts) != 0) { + if (errno != EINTR ) { + return -1; + } + } +#endif + + return 0; +} diff --git a/src/platform.h b/src/platform.h index ab82fb7..438aa7d 100644 --- a/src/platform.h +++ b/src/platform.h @@ -45,6 +45,8 @@ extern "C" { #endif #endif +int dc_platform_sleep(unsigned int milliseconds); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/serial_posix.c b/src/serial_posix.c index 4fbcb96..8872982 100644 --- a/src/serial_posix.c +++ b/src/serial_posix.c @@ -30,7 +30,6 @@ #include // fcntl #include // tcgetattr, tcsetattr, cfsetispeed, cfsetospeed, tcflush, tcsendbreak #include // ioctl -#include // nanosleep #ifdef HAVE_LINUX_SERIAL_H #include #endif @@ -59,6 +58,7 @@ #include "iostream-private.h" #include "iterator-private.h" #include "descriptor-private.h" +#include "platform.h" #include "timer.h" #define DIRNAME "/dev" @@ -995,16 +995,10 @@ dc_serial_get_lines (dc_iostream_t *abstract, unsigned int *value) static dc_status_t dc_serial_sleep (dc_iostream_t *abstract, unsigned int timeout) { - struct timespec ts; - ts.tv_sec = (timeout / 1000); - ts.tv_nsec = (timeout % 1000) * 1000000; - - while (nanosleep (&ts, &ts) != 0) { + if (dc_platform_sleep (timeout) != 0) { int errcode = errno; - if (errcode != EINTR ) { - SYSERROR (abstract->context, errcode); - return syserror (errcode); - } + SYSERROR (abstract->context, errcode); + return syserror (errcode); } return DC_STATUS_SUCCESS; diff --git a/src/serial_win32.c b/src/serial_win32.c index 2b009f0..aeeeb83 100644 --- a/src/serial_win32.c +++ b/src/serial_win32.c @@ -32,6 +32,7 @@ #include "iostream-private.h" #include "iterator-private.h" #include "descriptor-private.h" +#include "platform.h" static dc_status_t dc_serial_iterator_next (dc_iterator_t *iterator, void *item); static dc_status_t dc_serial_iterator_free (dc_iterator_t *iterator); @@ -836,7 +837,11 @@ dc_serial_get_lines (dc_iostream_t *abstract, unsigned int *value) static dc_status_t dc_serial_sleep (dc_iostream_t *abstract, unsigned int timeout) { - Sleep (timeout); + if (dc_platform_sleep (timeout) != 0) { + DWORD errcode = GetLastError (); + SYSERROR (abstract->context, errcode); + return syserror (errcode); + } return DC_STATUS_SUCCESS; } diff --git a/src/socket.c b/src/socket.c index 84d58a4..a99a8f2 100644 --- a/src/socket.c +++ b/src/socket.c @@ -20,6 +20,7 @@ */ #include "socket.h" +#include "platform.h" #include "common-private.h" #include "context-private.h" @@ -357,21 +358,11 @@ dc_socket_ioctl (dc_iostream_t *abstract, unsigned int request, void *data, size dc_status_t dc_socket_sleep (dc_iostream_t *abstract, unsigned int timeout) { -#ifdef _WIN32 - Sleep (timeout); -#else - struct timespec ts; - ts.tv_sec = (timeout / 1000); - ts.tv_nsec = (timeout % 1000) * 1000000; - - while (nanosleep (&ts, &ts) != 0) { - int errcode = errno; - if (errcode != EINTR ) { - SYSERROR (abstract->context, errcode); - return dc_socket_syserror (errcode); - } + if (dc_platform_sleep (timeout) != 0) { + s_errcode_t errcode = S_ERRNO; + SYSERROR (abstract->context, errcode); + return dc_socket_syserror(errcode); } -#endif return DC_STATUS_SUCCESS; } From 1418766a1a5d15c36a69fe9e24cceb98c4c754c1 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 1 Apr 2021 19:56:59 +0200 Subject: [PATCH 19/22] Fix an overflow in the progress events The maximum value for the progress events is based on the amount of the flash memory available for storing the dives. But the 8 byte serial number is not stored inside the dive data, and is added dynamically during the data transfer. This extra data needs to be taken into account to avoid overflowing the progress events and trigger an assert in the code. --- src/atomics_cobalt.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/atomics_cobalt.c b/src/atomics_cobalt.c index a67ef82..1c2e3a5 100644 --- a/src/atomics_cobalt.c +++ b/src/atomics_cobalt.c @@ -210,6 +210,15 @@ atomics_cobalt_read_dive (dc_device_t *abstract, dc_buffer_t *buffer, int init, return DC_STATUS_NOMEMORY; } + // Adjust the maximum value to take into account the two byte checksum and + // the 8 byte serial number. Those extra bytes are not stored inside the + // dive header and are added dynamically during the data transfer. Since we + // don't know the total number of dives in advance, we can't calculate the + // total number of extra bytes and adjust the maximum on the fly. + if (progress) { + progress->maximum += 2 + 8; + } + // Send the command to the dive computer. unsigned char bRequest = 0; if (device->simulation) @@ -348,12 +357,6 @@ atomics_cobalt_device_foreach (dc_device_t *abstract, dc_dive_callback_t callbac return DC_STATUS_SUCCESS; } - // Adjust the maximum value to take into account the two checksum bytes - // for the next dive. Since we don't know the total number of dives in - // advance, we can't calculate the total number of checksum bytes and - // adjust the maximum on the fly. - progress.maximum += 2; - ndives++; } From 6ef72ab4203f8bf6189f0e2c81e9a15b4ae771c5 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 10 Mar 2021 21:38:45 +0100 Subject: [PATCH 20/22] Add support for the Sporasub SP2 The Sporasub SP2 uses a very simple communication protocol and memory layout, but with some unusual aspects: Dives are artifically limited to a maximum of 6000 samples. Unlike all other dive computers, the dives are not stored in some kind of ringbuffer structure. Once the memory is full, no new dives can be recorded. The existing dives need to be erased first, and the dive computer will start recording again at te start of the memory area. The Sporasub application has an "Auto-clear watch memory after data transfer" feature for this purpose. I didn't implement a more efficient download algorithm because downloading a full memory dumps takes less than 10 seconds. --- examples/common.c | 1 + include/libdivecomputer/common.h | 2 + msvc/libdivecomputer.vcproj | 12 + src/Makefile.am | 1 + src/descriptor.c | 2 + src/device.c | 4 + src/parser.c | 4 + src/sporasub_sp2.c | 489 +++++++++++++++++++++++++++++++ src/sporasub_sp2.h | 43 +++ src/sporasub_sp2_parser.c | 200 +++++++++++++ 10 files changed, 758 insertions(+) create mode 100644 src/sporasub_sp2.c create mode 100644 src/sporasub_sp2.h create mode 100644 src/sporasub_sp2_parser.c diff --git a/examples/common.c b/examples/common.c index d4e4ad4..73cd1f7 100644 --- a/examples/common.c +++ b/examples/common.c @@ -93,6 +93,7 @@ static const backend_table_t g_backends[] = { {"divecomputereu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0}, {"mclean", DC_FAMILY_MCLEAN_EXTREME, 0}, {"lynx", DC_FAMILY_LIQUIVISION_LYNX, 0}, + {"sp2", DC_FAMILY_SPORASUB_SP2, 0}, }; static const transport_table_t g_transports[] = { diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h index b8216ac..62f62f3 100644 --- a/include/libdivecomputer/common.h +++ b/include/libdivecomputer/common.h @@ -108,6 +108,8 @@ typedef enum dc_family_t { DC_FAMILY_MCLEAN_EXTREME = (16 << 16), /* Liquivision */ DC_FAMILY_LIQUIVISION_LYNX = (17 << 16), + /* Sporasub */ + DC_FAMILY_SPORASUB_SP2 = (18 << 16), } dc_family_t; #ifdef __cplusplus diff --git a/msvc/libdivecomputer.vcproj b/msvc/libdivecomputer.vcproj index da5187d..3482457 100644 --- a/msvc/libdivecomputer.vcproj +++ b/msvc/libdivecomputer.vcproj @@ -458,6 +458,14 @@ RelativePath="..\src\socket.c" > + + + + @@ -824,6 +832,10 @@ RelativePath="..\src\socket.h" > + + diff --git a/src/Makefile.am b/src/Makefile.am index e78f37d..19bca52 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -74,6 +74,7 @@ libdivecomputer_la_SOURCES = \ tecdiving_divecomputereu.h tecdiving_divecomputereu.c tecdiving_divecomputereu_parser.c \ mclean_extreme.h mclean_extreme.c mclean_extreme_parser.c \ liquivision_lynx.h liquivision_lynx.c liquivision_lynx_parser.c \ + sporasub_sp2.h sporasub_sp2.c sporasub_sp2_parser.c \ socket.h socket.c \ irda.c \ usb.c \ diff --git a/src/descriptor.c b/src/descriptor.c index 531c5d0..5a6d1fe 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -415,6 +415,8 @@ static const dc_descriptor_t g_descriptors[] = { {"Liquivision", "Xeo", DC_FAMILY_LIQUIVISION_LYNX, 1, DC_TRANSPORT_SERIAL, NULL}, {"Liquivision", "Lynx", DC_FAMILY_LIQUIVISION_LYNX, 2, DC_TRANSPORT_SERIAL, NULL}, {"Liquivision", "Kaon", DC_FAMILY_LIQUIVISION_LYNX, 3, DC_TRANSPORT_SERIAL, NULL}, + /* Sporasub */ + {"Sporasub", "SP2", DC_FAMILY_SPORASUB_SP2, 0, DC_TRANSPORT_SERIAL, NULL}, }; static int diff --git a/src/device.c b/src/device.c index 62914dc..6c6732d 100644 --- a/src/device.c +++ b/src/device.c @@ -59,6 +59,7 @@ #include "tecdiving_divecomputereu.h" #include "mclean_extreme.h" #include "liquivision_lynx.h" +#include "sporasub_sp2.h" #include "device-private.h" #include "context-private.h" @@ -219,6 +220,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr case DC_FAMILY_LIQUIVISION_LYNX: rc = liquivision_lynx_device_open (&device, context, iostream); break; + case DC_FAMILY_SPORASUB_SP2: + rc = sporasub_sp2_device_open (&device, context, iostream); + break; default: return DC_STATUS_INVALIDARGS; } diff --git a/src/parser.c b/src/parser.c index 2f7251d..2596de4 100644 --- a/src/parser.c +++ b/src/parser.c @@ -59,6 +59,7 @@ #include "tecdiving_divecomputereu.h" #include "mclean_extreme.h" #include "liquivision_lynx.h" +#include "sporasub_sp2.h" #include "context-private.h" #include "parser-private.h" @@ -180,6 +181,9 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa case DC_FAMILY_LIQUIVISION_LYNX: rc = liquivision_lynx_parser_create (&parser, context, model); break; + case DC_FAMILY_SPORASUB_SP2: + rc = sporasub_sp2_parser_create (&parser, context); + break; default: return DC_STATUS_INVALIDARGS; } diff --git a/src/sporasub_sp2.c b/src/sporasub_sp2.c new file mode 100644 index 0000000..c0f6b49 --- /dev/null +++ b/src/sporasub_sp2.c @@ -0,0 +1,489 @@ +/* + * libdivecomputer + * + * Copyright (C) 2021 Jef Driesen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include // memcpy, memcmp +#include // malloc, free + +#include "sporasub_sp2.h" +#include "context-private.h" +#include "device-private.h" +#include "checksum.h" +#include "array.h" + +#define ISINSTANCE(device) dc_device_isinstance((device), &sporasub_sp2_device_vtable) + +#define SZ_MEMORY 0x10000 + +#define RB_PROFILE_BEGIN 0x0060 +#define RB_PROFILE_END SZ_MEMORY + +#define MAXRETRIES 4 +#define MAXPACKET 256 + +#define HEADER_HI 0xA0 +#define HEADER_LO 0xA2 +#define TRAILER_HI 0xB0 +#define TRAILER_LO 0xB3 + +#define CMD_VERSION 0x10 +#define CMD_READ 0x12 +#define CMD_TIMESYNC 0x39 + +#define SZ_VERSION 23 +#define SZ_READ 128 + +#define SZ_HEADER 32 +#define SZ_SAMPLE 4 + +typedef struct sporasub_sp2_device_t { + dc_device_t base; + dc_iostream_t *iostream; + unsigned char version[SZ_VERSION]; + unsigned char fingerprint[6]; +} sporasub_sp2_device_t; + +static dc_status_t sporasub_sp2_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); +static dc_status_t sporasub_sp2_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size); +static dc_status_t sporasub_sp2_device_dump (dc_device_t *abstract, dc_buffer_t *buffer); +static dc_status_t sporasub_sp2_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +static dc_status_t sporasub_sp2_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime); + +static const dc_device_vtable_t sporasub_sp2_device_vtable = { + sizeof(sporasub_sp2_device_t), + DC_FAMILY_SPORASUB_SP2, + sporasub_sp2_device_set_fingerprint, /* set_fingerprint */ + sporasub_sp2_device_read, /* read */ + NULL, /* write */ + sporasub_sp2_device_dump, /* dump */ + sporasub_sp2_device_foreach, /* foreach */ + sporasub_sp2_device_timesync, /* timesync */ + NULL /* close */ +}; + +static unsigned int +iceil (unsigned int x, unsigned int n) +{ + // Round up to next higher multiple. + return ((x + n - 1) / n) * n; +} + +static dc_status_t +sporasub_sp2_send (sporasub_sp2_device_t *device, unsigned char command, const unsigned char data[], unsigned int size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + + if (size > MAXPACKET) { + return DC_STATUS_INVALIDARGS; + } + + unsigned int len = size + 1; + unsigned int csum = checksum_add_uint16 (data, size, command); + + unsigned char packet[MAXPACKET + 9] = {0}; + packet[0] = HEADER_HI; + packet[1] = HEADER_LO; + packet[2] = (len >> 8) & 0xFF; + packet[3] = (len ) & 0xFF; + packet[4] = command; + if (size) { + memcpy(packet + 5, data, size); + } + packet[size + 5] = (csum >> 8) & 0xFF; + packet[size + 6] = (csum ) & 0xFF; + packet[size + 7] = TRAILER_HI; + packet[size + 8] = TRAILER_LO; + + // Send the command to the device. + status = dc_iostream_write (device->iostream, packet, size + 9, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the command."); + return status; + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +sporasub_sp2_receive (sporasub_sp2_device_t *device, unsigned char command, unsigned char data[], unsigned int size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + + if (size > MAXPACKET) { + return DC_STATUS_INVALIDARGS; + } + + // Receive the answer of the device. + unsigned char packet[MAXPACKET + 9] = {0}; + status = dc_iostream_read (device->iostream, packet, size + 9, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the answer."); + return status; + } + + // Verify the header and trailer of the packet. + if (packet[0] != HEADER_HI || packet[1] != HEADER_LO || + packet[size + 7] != TRAILER_HI || packet[size + 8] != TRAILER_LO) { + ERROR (abstract->context, "Unexpected answer header/trailer byte."); + return DC_STATUS_PROTOCOL; + } + + // Verify the packet length. + unsigned int len = array_uint16_be (packet + 2); + if (len != size + 1) { + ERROR (abstract->context, "Unexpected packet length."); + return DC_STATUS_PROTOCOL; + } + + // Verify the command byte. + if (packet[4] != command) { + ERROR (abstract->context, "Unexpected answer header/trailer byte."); + return DC_STATUS_PROTOCOL; + } + + // Verify the checksum of the packet. + unsigned short crc = array_uint16_be (packet + size + 5); + unsigned short ccrc = checksum_add_uint16 (packet + 4, size + 1, 0); + if (crc != ccrc) { + ERROR (abstract->context, "Unexpected answer checksum."); + return DC_STATUS_PROTOCOL; + } + + if (size) { + memcpy (data, packet + 5, size); + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +sporasub_sp2_packet (sporasub_sp2_device_t *device, unsigned char cmd, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + + if (device_is_cancelled (abstract)) + return DC_STATUS_CANCELLED; + + // Send the command to the device. + status = sporasub_sp2_send (device, cmd, command, csize); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the command."); + return status; + } + + // Receive the answer of the device. + status = sporasub_sp2_receive (device, cmd + 1, answer, asize); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the answer."); + return status; + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +sporasub_sp2_transfer (sporasub_sp2_device_t *device, unsigned char cmd, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize) +{ + unsigned int nretries = 0; + dc_status_t rc = DC_STATUS_SUCCESS; + while ((rc = sporasub_sp2_packet (device, cmd, command, csize, answer, asize)) != DC_STATUS_SUCCESS) { + // Automatically discard a corrupted packet, + // and request a new one. + if (rc != DC_STATUS_PROTOCOL && rc != DC_STATUS_TIMEOUT) + return rc; + + // Abort if the maximum number of retries is reached. + if (nretries++ >= MAXRETRIES) + return rc; + + // Discard any garbage bytes. + dc_iostream_sleep (device->iostream, 100); + dc_iostream_purge (device->iostream, DC_DIRECTION_INPUT); + } + + return rc; +} + +dc_status_t +sporasub_sp2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) +{ + dc_status_t status = DC_STATUS_SUCCESS; + sporasub_sp2_device_t *device = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + device = (sporasub_sp2_device_t *) dc_device_allocate (context, &sporasub_sp2_device_vtable); + if (device == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Set the default values. + device->iostream = iostream; + memset (device->fingerprint, 0, sizeof (device->fingerprint)); + + // Set the serial communication protocol (460800 8N1). + status = dc_iostream_configure (device->iostream, 460800, 8, DC_PARITY_NONE, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the terminal attributes."); + goto error_free; + } + + // Set the timeout for receiving data (1000 ms). + status = dc_iostream_set_timeout (device->iostream, 1000); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the timeout."); + goto error_free; + } + + // Clear the RTS line. + status = dc_iostream_set_rts (device->iostream, 0); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to clear the RTS line."); + goto error_free; + } + + // Set the DTR line. + status = dc_iostream_set_dtr (device->iostream, 1); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the DTR line."); + goto error_free; + } + + dc_iostream_sleep (device->iostream, 100); + dc_iostream_purge (device->iostream, DC_DIRECTION_ALL); + + // Read the version packet. + status = sporasub_sp2_packet(device, CMD_VERSION, NULL, 0, device->version, sizeof(device->version)); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to read the version packet."); + goto error_free; + } + + *out = (dc_device_t *) device; + + return DC_STATUS_SUCCESS; + +error_free: + dc_device_deallocate ((dc_device_t *) device); + return status; +} + +static dc_status_t +sporasub_sp2_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size) +{ + sporasub_sp2_device_t *device = (sporasub_sp2_device_t *) abstract; + + if (size && size != sizeof (device->fingerprint)) + return DC_STATUS_INVALIDARGS; + + if (size) + memcpy (device->fingerprint, data, sizeof (device->fingerprint)); + else + memset (device->fingerprint, 0, sizeof (device->fingerprint)); + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +sporasub_sp2_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + sporasub_sp2_device_t *device = (sporasub_sp2_device_t *) abstract; + + unsigned int nbytes = 0; + while (nbytes < size) { + // Calculate the packet size. + unsigned int len = size - nbytes; + if (len > SZ_READ) + len = SZ_READ; + + // Build the raw command. + unsigned char command[] = { + (address ) & 0xFF, + (address >> 8) & 0xFF, + len}; + + // Send the command and receive the answer. + status = sporasub_sp2_transfer (device, CMD_READ, command, sizeof(command), data + nbytes, len); + if (status != DC_STATUS_SUCCESS) + return status; + + nbytes += len; + address += len; + data += len; + } + + return status; +} + +static dc_status_t +sporasub_sp2_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) +{ + // Allocate the required amount of memory. + if (!dc_buffer_resize (buffer, SZ_MEMORY)) { + ERROR (abstract->context, "Insufficient buffer space available."); + return DC_STATUS_NOMEMORY; + } + + return device_dump_read (abstract, dc_buffer_get_data (buffer), + dc_buffer_get_size (buffer), SZ_READ); +} + +static dc_status_t +sporasub_sp2_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) +{ + dc_status_t status = DC_STATUS_SUCCESS; + sporasub_sp2_device_t *device = (sporasub_sp2_device_t *) abstract; + + // Emit a device info event. + dc_event_devinfo_t devinfo; + devinfo.model = 0; + devinfo.firmware = 0; + devinfo.serial = array_uint16_be (device->version + 1); + device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); + + // Emit a vendor event. + dc_event_vendor_t vendor; + vendor.data = device->version; + vendor.size = sizeof (device->version); + device_event_emit (abstract, DC_EVENT_VENDOR, &vendor); + + dc_buffer_t *buffer = dc_buffer_new (SZ_MEMORY); + if (buffer == NULL) { + status = DC_STATUS_NOMEMORY; + goto error_exit; + } + + status = sporasub_sp2_device_dump (abstract, buffer); + if (status != DC_STATUS_SUCCESS) { + goto error_free_buffer; + } + + unsigned char *data = dc_buffer_get_data (buffer); + + // Get the number of dives. + unsigned int ndives = array_uint16_le (data + 0x02); + + // Get the profile pointer. + unsigned int eop = array_uint16_le (data + 0x04); + if (eop < RB_PROFILE_BEGIN || eop > RB_PROFILE_END) { + ERROR (abstract->context, "Invalid profile pointer (0x%04x).", eop); + status = DC_STATUS_DATAFORMAT; + goto error_free_buffer; + } + + unsigned short *logbook = (unsigned short *) malloc(ndives * sizeof (unsigned short)); + if (logbook == NULL) { + ERROR (abstract->context, "Out of memory."); + status = DC_STATUS_NOMEMORY; + goto error_free_buffer; + } + + // Find all dives. + unsigned int count = 0; + unsigned int address = RB_PROFILE_BEGIN; + while (address + SZ_HEADER <= RB_PROFILE_END && count < ndives) { + if (address == eop) { + WARNING (abstract->context, "Reached end of profile pointer."); + break; + } + + // Get the dive length. + unsigned int nsamples = array_uint16_le (data + address); + unsigned int length = SZ_HEADER + nsamples * SZ_SAMPLE; + if (address + length > RB_PROFILE_END) { + WARNING (abstract->context, "Reached end of memory."); + break; + } + + // Store the address. + logbook[count] = address; + count++; + + // The start of the next dive is always aligned to 32 bytes. + address += iceil (length, SZ_HEADER); + } + + // Process the dives in reverse order (newest first). + for (unsigned int i = 0; i < count; ++i) { + unsigned int idx = count - 1 - i; + unsigned int offset = logbook[idx]; + + // Get the dive length. + unsigned int nsamples = array_uint16_le (data + offset); + unsigned int length = SZ_HEADER + nsamples * SZ_SAMPLE; + + // Check the fingerprint data. + if (memcmp (data + offset + 2, device->fingerprint, sizeof (device->fingerprint)) == 0) + break; + + if (callback && !callback (data + offset, length, data + offset + 2, sizeof (device->fingerprint), userdata)) { + break; + } + } + + free (logbook); +error_free_buffer: + dc_buffer_free (buffer); +error_exit: + return status; +} + + +static dc_status_t +sporasub_sp2_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) +{ + dc_status_t status = DC_STATUS_SUCCESS; + sporasub_sp2_device_t *device = (sporasub_sp2_device_t *) abstract; + + if (datetime == NULL || datetime->year < 2000) { + ERROR (abstract->context, "Invalid parameter specified."); + return DC_STATUS_INVALIDARGS; + } + + // Build the raw command. + unsigned char command[] = { + datetime->year - 2000, + datetime->month, + datetime->day, + datetime->hour, + datetime->minute, + datetime->second}; + + // Send the command and receive the answer. + unsigned char answer[1] = {0}; + status = sporasub_sp2_transfer (device, CMD_TIMESYNC, command, sizeof(command), answer, sizeof(answer)); + if (status != DC_STATUS_SUCCESS) + return status; + + // Verify the response code. + if (answer[0] != 0) { + ERROR (abstract->context, "Invalid response code 0x%02x returned.", answer[0]); + return DC_STATUS_PROTOCOL; + } + + return DC_STATUS_SUCCESS; +} diff --git a/src/sporasub_sp2.h b/src/sporasub_sp2.h new file mode 100644 index 0000000..de2ca42 --- /dev/null +++ b/src/sporasub_sp2.h @@ -0,0 +1,43 @@ +/* + * libdivecomputer + * + * Copyright (C) 2021 Jef Driesen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#ifndef SPORASUB_SP2_H +#define SPORASUB_SP2_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +dc_status_t +sporasub_sp2_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); + +dc_status_t +sporasub_sp2_parser_create (dc_parser_t **parser, dc_context_t *context); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* SPORASUB_SP2_H */ diff --git a/src/sporasub_sp2_parser.c b/src/sporasub_sp2_parser.c new file mode 100644 index 0000000..e036dd2 --- /dev/null +++ b/src/sporasub_sp2_parser.c @@ -0,0 +1,200 @@ +/* + * libdivecomputer + * + * Copyright (C) 2021 Jef Driesen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include + +#include "sporasub_sp2.h" +#include "context-private.h" +#include "parser-private.h" +#include "array.h" + +#define ISINSTANCE(parser) dc_device_isinstance((parser), &sporasub_sp2_parser_vtable) + +#define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array)) + +#define SZ_HEADER 0x20 +#define SZ_SAMPLE 0x04 + +typedef struct sporasub_sp2_parser_t sporasub_sp2_parser_t; + +struct sporasub_sp2_parser_t { + dc_parser_t base; +}; + +static dc_status_t sporasub_sp2_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t sporasub_sp2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); +static dc_status_t sporasub_sp2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); +static dc_status_t sporasub_sp2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); + +static const dc_parser_vtable_t sporasub_sp2_parser_vtable = { + sizeof(sporasub_sp2_parser_t), + DC_FAMILY_SPORASUB_SP2, + sporasub_sp2_parser_set_data, /* set_data */ + sporasub_sp2_parser_get_datetime, /* datetime */ + sporasub_sp2_parser_get_field, /* fields */ + sporasub_sp2_parser_samples_foreach, /* samples_foreach */ + NULL /* destroy */ +}; + + +dc_status_t +sporasub_sp2_parser_create (dc_parser_t **out, dc_context_t *context) +{ + sporasub_sp2_parser_t *parser = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + parser = (sporasub_sp2_parser_t *) dc_parser_allocate (context, &sporasub_sp2_parser_vtable); + if (parser == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + *out = (dc_parser_t *) parser; + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +sporasub_sp2_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) +{ + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +sporasub_sp2_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; + + if (datetime) { + datetime->year = data[4] + 2000; + datetime->month = data[3]; + datetime->day = data[2]; + datetime->hour = data[7]; + datetime->minute = data[6]; + datetime->second = data[5]; + datetime->timezone = DC_TIMEZONE_NONE; + } + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +sporasub_sp2_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; + + if (value) { + switch (type) { + case DC_FIELD_DIVETIME: + *((unsigned int *) value) = data[0x08] + data[0x09] * 60; + break; + case DC_FIELD_MAXDEPTH: + *((double *) value) = array_uint16_le (data + 0x14) / 100.0; + break; + case DC_FIELD_DIVEMODE: + *((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE; + break; + case DC_FIELD_TEMPERATURE_MINIMUM: + *((double *) value) = array_uint16_le (data + 0x18) / 10.0; + break; + case DC_FIELD_TEMPERATURE_MAXIMUM: + *((double *) value) = array_uint16_le (data + 0x16) / 10.0; + break; + default: + return DC_STATUS_UNSUPPORTED; + } + } + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +sporasub_sp2_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 nsamples = array_uint16_le(data); + + // Get the sample interval. + unsigned int interval_idx = data[0x1A]; + const unsigned int intervals[] = {1, 2, 5, 10}; + if (interval_idx >= C_ARRAY_SIZE(intervals)) { + ERROR (abstract->context, "Invalid sample interval index %u", interval_idx); + return DC_STATUS_DATAFORMAT; + } + unsigned int interval = intervals[interval_idx]; + + unsigned int time = 0; + unsigned int count = 0; + unsigned int offset = SZ_HEADER; + while (offset + SZ_SAMPLE <= size && count < nsamples) { + dc_sample_value_t sample = {0}; + + unsigned int value = array_uint32_le (data + offset); + unsigned int heartrate = (value & 0xFF000000) >> 24; + unsigned int temperature = (value & 0x00FFC000) >> 14; + unsigned int unknown = (value & 0x00003000) >> 12; + unsigned int depth = (value & 0x00000FFF) >> 0; + + // Time (seconds) + time += interval; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + + // Depth (1/100 m) + sample.depth = depth / 100.0; + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + + // Temperature (1/10 °C) + sample.temperature = temperature / 10.0 - 20.0; + if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); + + // Heartrate + if (heartrate) { + sample.heartbeat = heartrate; + if (callback) callback (DC_SAMPLE_HEARTBEAT, sample, userdata); + } + + offset += SZ_SAMPLE; + count++; + } + + return DC_STATUS_SUCCESS; +} From 76d225dcfc52554acd2bbbf518670ab5ee646c9e Mon Sep 17 00:00:00 2001 From: Nick Shore Date: Tue, 20 Apr 2021 09:30:01 +0200 Subject: [PATCH 21/22] Add support for the EON Steel Black --- src/descriptor.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index 5a6d1fe..5937bca 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -127,9 +127,10 @@ static const dc_descriptor_t g_descriptors[] = { {"Suunto", "Zoop Novo", DC_FAMILY_SUUNTO_D9, 0x1E, DC_TRANSPORT_SERIAL, NULL}, {"Suunto", "D4f", DC_FAMILY_SUUNTO_D9, 0x20, DC_TRANSPORT_SERIAL, NULL}, /* Suunto EON Steel */ - {"Suunto", "EON Steel", DC_FAMILY_SUUNTO_EONSTEEL, 0, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_suunto}, - {"Suunto", "EON Core", DC_FAMILY_SUUNTO_EONSTEEL, 1, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_suunto}, - {"Suunto", "D5", DC_FAMILY_SUUNTO_EONSTEEL, 2, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_suunto}, + {"Suunto", "EON Steel", DC_FAMILY_SUUNTO_EONSTEEL, 0, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_suunto}, + {"Suunto", "EON Core", DC_FAMILY_SUUNTO_EONSTEEL, 1, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_suunto}, + {"Suunto", "D5", DC_FAMILY_SUUNTO_EONSTEEL, 2, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_suunto}, + {"Suunto", "EON Steel Black", DC_FAMILY_SUUNTO_EONSTEEL, 3, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_suunto}, /* Uwatec Aladin */ {"Uwatec", "Aladin Air Twin", DC_FAMILY_UWATEC_ALADIN, 0x1C, DC_TRANSPORT_SERIAL, NULL}, {"Uwatec", "Aladin Sport Plus", DC_FAMILY_UWATEC_ALADIN, 0x3E, DC_TRANSPORT_SERIAL, NULL}, @@ -561,11 +562,13 @@ static int dc_filter_suunto (dc_transport_t transport, const void *userdata, voi {0x1493, 0x0030}, // Eon Steel {0x1493, 0x0033}, // Eon Core {0x1493, 0x0035}, // D5 + {0x1493, 0x0036}, // EON Steel Black }; static const char * const bluetooth[] = { "EON Steel", "EON Core", "Suunto D5", + "EON Steel Black", }; if (transport == DC_TRANSPORT_USBHID) { From d85d8811f0298fa83fb6e39365c690b12cfbbb04 Mon Sep 17 00:00:00 2001 From: Nick Shore Date: Tue, 20 Apr 2021 09:32:25 +0200 Subject: [PATCH 22/22] Add support for a new Suunto Zoop Novo variant --- src/descriptor.c | 1 + src/suunto_d9.c | 19 ++++++------ src/suunto_d9_parser.c | 67 +++++++++++++++++++++++------------------- 3 files changed, 47 insertions(+), 40 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index 5937bca..5a4f35a 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -125,6 +125,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Suunto", "DX", DC_FAMILY_SUUNTO_D9, 0x1C, DC_TRANSPORT_SERIAL, NULL}, {"Suunto", "Vyper Novo", DC_FAMILY_SUUNTO_D9, 0x1D, DC_TRANSPORT_SERIAL, NULL}, {"Suunto", "Zoop Novo", DC_FAMILY_SUUNTO_D9, 0x1E, DC_TRANSPORT_SERIAL, NULL}, + {"Suunto", "Zoop Novo", DC_FAMILY_SUUNTO_D9, 0x1F, DC_TRANSPORT_SERIAL, NULL}, {"Suunto", "D4f", DC_FAMILY_SUUNTO_D9, 0x20, DC_TRANSPORT_SERIAL, NULL}, /* Suunto EON Steel */ {"Suunto", "EON Steel", DC_FAMILY_SUUNTO_EONSTEEL, 0, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_suunto}, diff --git a/src/suunto_d9.c b/src/suunto_d9.c index 8b8320e..17f7e7f 100644 --- a/src/suunto_d9.c +++ b/src/suunto_d9.c @@ -33,13 +33,14 @@ #define C_ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a))) -#define D4i 0x19 -#define D6i 0x1A -#define D9tx 0x1B -#define DX 0x1C -#define VYPERNOVO 0x1D -#define ZOOPNOVO 0x1E -#define D4F 0x20 +#define D4i 0x19 +#define D6i 0x1A +#define D9tx 0x1B +#define DX 0x1C +#define VYPERNOVO 0x1D +#define ZOOPNOVO_A 0x1E +#define ZOOPNOVO_B 0x1F +#define D4F 0x20 typedef struct suunto_d9_device_t { suunto_common2_device_t base; @@ -100,7 +101,7 @@ suunto_d9_device_autodetect (suunto_d9_device_t *device, unsigned int model) // Use the model number as a hint to speedup the detection. unsigned int hint = 0; if (model == D4i || model == D6i || model == D9tx || - model == DX || model == VYPERNOVO || model == ZOOPNOVO || + model == DX || model == VYPERNOVO || model == ZOOPNOVO_A || model == ZOOPNOVO_B || model == D4F) hint = 1; @@ -184,7 +185,7 @@ suunto_d9_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t * // Override the base class values. model = device->base.version[0]; if (model == D4i || model == D6i || model == D9tx || - model == VYPERNOVO || model == ZOOPNOVO || + model == VYPERNOVO || model == ZOOPNOVO_A || model == ZOOPNOVO_B || model == D4F) device->base.layout = &suunto_d9tx_layout; else if (model == DX) diff --git a/src/suunto_d9_parser.c b/src/suunto_d9_parser.c index bd91743..7fc05fd 100644 --- a/src/suunto_d9_parser.c +++ b/src/suunto_d9_parser.c @@ -32,21 +32,22 @@ #define MAXPARAMS 3 #define NGASMIXES 11 -#define D9 0x0E -#define D6 0x0F -#define VYPER2 0x10 -#define COBRA2 0x11 -#define D4 0x12 -#define VYPERAIR 0x13 -#define COBRA3 0x14 -#define HELO2 0x15 -#define D4i 0x19 -#define D6i 0x1A -#define D9tx 0x1B -#define DX 0x1C -#define VYPERNOVO 0x1D -#define ZOOPNOVO 0x1E -#define D4F 0x20 +#define D9 0x0E +#define D6 0x0F +#define VYPER2 0x10 +#define COBRA2 0x11 +#define D4 0x12 +#define VYPERAIR 0x13 +#define COBRA3 0x14 +#define HELO2 0x15 +#define D4i 0x19 +#define D6i 0x1A +#define D9tx 0x1B +#define DX 0x1C +#define VYPERNOVO 0x1D +#define ZOOPNOVO_A 0x1E +#define ZOOPNOVO_B 0x1F +#define D4F 0x20 #define ID_D6I_V1_MIX2 0x1871C062 #define ID_D6I_V1_MIX3 0x1871C063 @@ -142,8 +143,8 @@ suunto_d9_parser_cache (suunto_d9_parser_t *parser) gasmode_offset = 0x1F; gasmix_offset = 0x54; gasmix_count = 8; - } else if (parser->model == D4i || parser->model == ZOOPNOVO || - parser->model == D4F) { + } else if (parser->model == D4i || parser->model == ZOOPNOVO_A || + parser->model == ZOOPNOVO_B || parser->model == D4F) { gasmode_offset = 0x1D; if (id == ID_D4I_V2) gasmix_offset = 0x67; @@ -180,8 +181,9 @@ suunto_d9_parser_cache (suunto_d9_parser_t *parser) config += 1; } else if (parser->model == HELO2 || parser->model == D4i || parser->model == D6i || parser->model == D9tx || - parser->model == DX || parser->model == ZOOPNOVO || - parser->model == VYPERNOVO || parser->model == D4F) { + parser->model == DX || parser->model == ZOOPNOVO_A || + parser->model == ZOOPNOVO_B || parser->model == VYPERNOVO || + parser->model == D4F) { config = gasmix_offset + gasmix_count * 6; } if (config + 1 > size) @@ -204,8 +206,9 @@ suunto_d9_parser_cache (suunto_d9_parser_t *parser) for (unsigned int i = 0; i < gasmix_count; ++i) { if (parser->model == HELO2 || parser->model == D4i || parser->model == D6i || parser->model == D9tx || - parser->model == DX || parser->model == ZOOPNOVO || - parser->model == VYPERNOVO || parser->model == D4F) { + parser->model == DX || parser->model == ZOOPNOVO_A || + parser->model == ZOOPNOVO_B || parser->model == VYPERNOVO || + parser->model == D4F) { parser->oxygen[i] = data[gasmix_offset + 6 * i + 1]; parser->helium[i] = data[gasmix_offset + 6 * i + 2]; } else { @@ -222,8 +225,9 @@ suunto_d9_parser_cache (suunto_d9_parser_t *parser) if (parser->model == HELO2) { parser->gasmix = data[0x26]; } else if (parser->model == D4i || parser->model == D6i || - parser->model == D9tx || parser->model == ZOOPNOVO || - parser->model == VYPERNOVO || parser->model == D4F) { + parser->model == D9tx || parser->model == ZOOPNOVO_A || + parser->model == ZOOPNOVO_B || parser->model == VYPERNOVO || + parser->model == D4F) { if (id == ID_D4I_V2 || id == ID_D6I_V2) { parser->gasmix = data[0x2D]; } else { @@ -309,8 +313,9 @@ suunto_d9_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) if (parser->model == HELO2 || parser->model == DX) offset = 0x17; else if (parser->model == D4i || parser->model == D6i || - parser->model == D9tx || parser->model == ZOOPNOVO || - parser->model == VYPERNOVO || parser->model == D4F) + parser->model == D9tx || parser->model == ZOOPNOVO_A || + parser->model == ZOOPNOVO_B || parser->model == VYPERNOVO || + parser->model == D4F) offset = 0x13; if (abstract->size < offset + 7) @@ -321,8 +326,8 @@ suunto_d9_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) if (datetime) { if (parser->model == D4i || parser->model == D6i || parser->model == D9tx || parser->model == DX || - parser->model == ZOOPNOVO || parser->model == VYPERNOVO || - parser->model == D4F) { + parser->model == ZOOPNOVO_A || parser->model == ZOOPNOVO_B || + parser->model == VYPERNOVO || parser->model == D4F) { datetime->year = p[0] + (p[1] << 8); datetime->month = p[2]; datetime->day = p[3]; @@ -364,8 +369,8 @@ suunto_d9_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigne *((unsigned int *) value) = array_uint16_le (data + 0x0B); else if (parser->model == D4i || parser->model == D6i || parser->model == D9tx || parser->model == DX || - parser->model == ZOOPNOVO || parser->model == VYPERNOVO || - parser->model == D4F) + parser->model == ZOOPNOVO_A || parser->model == ZOOPNOVO_B || + parser->model == VYPERNOVO || parser->model == D4F) *((unsigned int *) value) = array_uint16_le (data + 0x0D); else if (parser->model == HELO2) *((unsigned int *) value) = array_uint16_le (data + 0x0D) * 60; @@ -476,8 +481,8 @@ suunto_d9_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t ca unsigned int interval_sample_offset = 0x18; if (parser->model == HELO2 || parser->model == D4i || parser->model == D6i || parser->model == D9tx || - parser->model == ZOOPNOVO || parser->model == VYPERNOVO || - parser->model == D4F) + parser->model == ZOOPNOVO_A || parser->model == ZOOPNOVO_B || + parser->model == VYPERNOVO || parser->model == D4F) interval_sample_offset = 0x1E; else if (parser->model == DX) interval_sample_offset = 0x22;