From 4c91309c56bc984d952b3bd4a10d1788320681a9 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 11 Jul 2019 21:25:18 +0200 Subject: [PATCH 01/11] Add firmware upgrade support for the Ratio computers --- examples/dctool_fwupdate.c | 4 + include/libdivecomputer/Makefile.am | 3 +- include/libdivecomputer/divesystem_idive.h | 38 +++ src/divesystem_idive.c | 281 +++++++++++++++++++++ src/divesystem_idive.h | 1 + src/libdivecomputer.symbols | 1 + 6 files changed, 327 insertions(+), 1 deletion(-) create mode 100644 include/libdivecomputer/divesystem_idive.h diff --git a/examples/dctool_fwupdate.c b/examples/dctool_fwupdate.c index e5daf35..596ccb1 100644 --- a/examples/dctool_fwupdate.c +++ b/examples/dctool_fwupdate.c @@ -35,6 +35,7 @@ #include #include #include +#include #include "dctool.h" #include "common.h" @@ -93,6 +94,9 @@ fwupdate (dc_context_t *context, dc_descriptor_t *descriptor, dc_transport_t tra case DC_FAMILY_HW_OSTC3: rc = hw_ostc3_device_fwupdate (device, hexfile); break; + case DC_FAMILY_DIVESYSTEM_IDIVE: + rc = divesystem_idive_device_fwupdate (device, hexfile); + break; default: rc = DC_STATUS_UNSUPPORTED; break; diff --git a/include/libdivecomputer/Makefile.am b/include/libdivecomputer/Makefile.am index 8e12f2d..e972153 100644 --- a/include/libdivecomputer/Makefile.am +++ b/include/libdivecomputer/Makefile.am @@ -28,4 +28,5 @@ libdivecomputer_HEADERS = \ hw_ostc.h \ hw_frog.h \ hw_ostc3.h \ - atomics_cobalt.h + atomics_cobalt.h \ + divesystem_idive.h diff --git a/include/libdivecomputer/divesystem_idive.h b/include/libdivecomputer/divesystem_idive.h new file mode 100644 index 0000000..07065d1 --- /dev/null +++ b/include/libdivecomputer/divesystem_idive.h @@ -0,0 +1,38 @@ +/* + * libdivecomputer + * + * Copyright (C) 2019 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 DC_DIVESYSTEM_IDIVE_H +#define DC_DIVESYSTEM_IDIVE_H + +#include "common.h" +#include "device.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +dc_status_t +divesystem_idive_device_fwupdate (dc_device_t *abstract, const char *filename); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* DC_DIVESYSTEM_IDIVE_H */ diff --git a/src/divesystem_idive.c b/src/divesystem_idive.c index 1c71de9..0659d06 100644 --- a/src/divesystem_idive.c +++ b/src/divesystem_idive.c @@ -21,10 +21,12 @@ #include // memcmp, memcpy #include // malloc, free +#include #include "divesystem_idive.h" #include "context-private.h" #include "device-private.h" +#include "platform.h" #include "checksum.h" #include "array.h" @@ -39,6 +41,7 @@ #define MAXPACKET 0xFF #define START 0x55 #define ACK 0x06 +#define WAIT 0x13 #define NAK 0x15 #define CMD_IDIVE_ID 0x10 @@ -51,6 +54,12 @@ #define CMD_IX3M_HEADER 0x79 #define CMD_IX3M_SAMPLE 0x7A #define CMD_IX3M_TIMESYNC 0x13 +#define CMD_IX3M_BOOTLOADER 0x0A + +#define BOOTLOADER_PROBE 0x78 +#define BOOTLOADER_UPLOAD_A 0x40 +#define BOOTLOADER_UPLOAD_B 0x23 +#define BOOTLOADER_ACK 0x46 #define ERR_INVALID_CMD 0x10 #define ERR_INVALID_LENGTH 0x20 @@ -80,6 +89,11 @@ typedef struct divesystem_idive_commands_t { unsigned int nsamples; } divesystem_idive_commands_t; +typedef struct divesystem_idive_signature_t { + const char *name; + unsigned int delay; +} divesystem_idive_signature_t; + typedef struct divesystem_idive_device_t { dc_device_t base; dc_iostream_t *iostream; @@ -127,6 +141,14 @@ static const divesystem_idive_commands_t ix3m_apos4 = { 3, }; +static const divesystem_idive_signature_t signatures[] = { + {"dsh01", 50}, // IX3M GPS + {"dsh30", 50}, // IX3M Pro + {"dsh20", 5}, // iDive Sport + {"dsh23", 5}, // iDive Color + {"acx", 5}, // WPT +}; + dc_status_t divesystem_idive_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream, unsigned int model) { @@ -652,3 +674,262 @@ divesystem_idive_device_timesync (dc_device_t *abstract, const dc_datetime_t *da return DC_STATUS_SUCCESS; } + +static dc_status_t +divesystem_idive_firmware_readfile (dc_buffer_t *buffer, dc_context_t *context, const char *filename) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_buffer_t *tmp = NULL; + FILE *fp = NULL; + + if (!dc_buffer_clear (buffer)) { + ERROR (context, "Invalid arguments."); + return DC_STATUS_INVALIDARGS; + } + + // Allocate a temporary buffer. + tmp = dc_buffer_new (0x20000); + if (tmp == NULL) { + ERROR (context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_exit; + } + + // Open the file. + fp = fopen (filename, "rb"); + if (fp == NULL) { + ERROR (context, "Failed to open the file."); + status = DC_STATUS_IO; + goto error_free; + } + + // Read the entire file into the buffer. + size_t n = 0; + unsigned char block[4096] = {0}; + while ((n = fread (block, 1, sizeof (block), fp)) > 0) { + if (!dc_buffer_append (tmp, block, n)) { + ERROR (context, "Insufficient buffer space available."); + status = DC_STATUS_NOMEMORY; + goto error_close; + } + } + + // Resize the output buffer. + size_t nbytes = dc_buffer_get_size (tmp); + if (!dc_buffer_resize (buffer, nbytes / 2)) { + ERROR (context, "Insufficient buffer space available."); + status = DC_STATUS_NOMEMORY; + goto error_close; + } + + // Convert to binary data. + int rc = array_convert_hex2bin ( + dc_buffer_get_data (tmp), dc_buffer_get_size (tmp), + dc_buffer_get_data (buffer), dc_buffer_get_size (buffer)); + if (rc != 0) { + ERROR (context, "Unexpected data format."); + status = DC_STATUS_DATAFORMAT; + goto error_close; + } + +error_close: + fclose (fp); +error_free: + dc_buffer_free (tmp); +error_exit: + return status; +} + +static dc_status_t +divesystem_idive_firmware_send (divesystem_idive_device_t *device, const divesystem_idive_signature_t *signature, const unsigned char data[], size_t size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + + unsigned int nretries = 0; + while (1) { + // Send the frame. + status = dc_iostream_write (device->iostream, data, size, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the frame."); + return status; + } + + // Read the response until an ACK or NAK byte is received. + unsigned int state = 0; + while (state == 0) { + // Receive the response. + unsigned char response = 0; + status = dc_iostream_read (device->iostream, &response, 1, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the response."); + return status; + } + + // Process the response. + switch (response) { + case ACK: + case NAK: + state = response; + break; + case WAIT: + dc_iostream_sleep (device->iostream, signature->delay); + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'K': + case 'X': + break; + default: + WARNING (abstract->context, "Unexpected response byte received (%02x)", response); + break; + } + } + + // Exit if ACK received. + if (state == ACK) + break; + + // Abort if the maximum number of retries is reached. + if (nretries++ >= MAXRETRIES) { + ERROR (abstract->context, "Maximum number of retries reached."); + return DC_STATUS_PROTOCOL; + } + } + + return DC_STATUS_SUCCESS; +} + +dc_status_t +divesystem_idive_device_fwupdate (dc_device_t *abstract, const char *filename) +{ + dc_status_t status = DC_STATUS_SUCCESS; + divesystem_idive_device_t *device = (divesystem_idive_device_t *) abstract; + unsigned int errcode = 0; + + // Allocate memory for the firmware data. + dc_buffer_t *buffer = dc_buffer_new (0); + if (buffer == NULL) { + ERROR (abstract->context, "Failed to allocate memory for the firmware data."); + status = DC_STATUS_NOMEMORY; + goto error_exit; + } + + // Read the firmware file. + status = divesystem_idive_firmware_readfile (buffer, abstract->context, filename); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the firmware file."); + goto error_free; + } + + // Cache the data and size. + const unsigned char *data = dc_buffer_get_data (buffer); + size_t size = dc_buffer_get_size (buffer); + + // Enable progress notifications. + dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; + progress.maximum = size; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Activate the bootloader. + const unsigned char bootloader[] = {CMD_IX3M_BOOTLOADER, 0xC9, 0x4B}; + status = divesystem_idive_transfer (device, bootloader, sizeof (bootloader), NULL, 0, &errcode); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to activate the bootloader."); + goto error_free; + } + + // Give the device some time to enter the bootloader. + dc_iostream_sleep (device->iostream, 2000); + + // Wait for the bootloader. + const divesystem_idive_signature_t *signature = NULL; + while (signature == NULL) { + // Discard garbage data. + dc_iostream_purge (device->iostream, DC_DIRECTION_INPUT); + + // Probe for the bootloader. + const unsigned char probe[] = {BOOTLOADER_PROBE}; + status = dc_iostream_write (device->iostream, probe, sizeof (probe), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to activate the bootloader."); + goto error_free; + } + + // Read the signature string. + size_t n = 0; + unsigned char name[5] = {0}; + status = dc_iostream_read (device->iostream, name, sizeof (name), &n); + if (status != DC_STATUS_SUCCESS && status != DC_STATUS_TIMEOUT) { + ERROR (abstract->context, "Failed to read the signature string."); + goto error_free; + } + + // Verify the signature string. + for (size_t i = 0; i < C_ARRAY_SIZE (signatures); ++i) { + if (n == strlen (signatures[i].name) && memcmp (name, signatures[i].name, n) == 0) { + signature = signatures + i; + break; + } + } + } + + // Send the start upload command. + const unsigned char upload[] = {BOOTLOADER_UPLOAD_A, BOOTLOADER_UPLOAD_B}; + status = dc_iostream_write (device->iostream, upload, sizeof(upload), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the start upload command."); + goto error_free; + } + + // Receive the ack. + unsigned char ack[1] = {0}; + status = dc_iostream_read (device->iostream, ack, sizeof(ack), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the ack byte."); + goto error_free; + } + + // Verify the ack. + if (ack[0] != BOOTLOADER_ACK) { + ERROR (abstract->context, "Invalid ack byte (%02x).", ack[0]); + status = DC_STATUS_PROTOCOL; + goto error_free; + } + + // Upload the firmware. + unsigned int offset = 0; + while (offset + 2 <= size) { + // Get the number of bytes in the current frame. + unsigned int len = array_uint16_be (data + offset) + 2; + if (offset + len > size) { + ERROR (abstract->context, "Invalid frame size (%u %u " DC_PRINTF_SIZE ")", offset, len, size); + status = DC_STATUS_DATAFORMAT; + goto error_free; + } + + // Send the frame. + status = divesystem_idive_firmware_send (device, signature, data + offset, len); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the frame."); + goto error_free; + } + + // Update and emit a progress event. + progress.current += len; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + offset += len; + } + +error_free: + dc_buffer_free (buffer); +error_exit: + return status; +} diff --git a/src/divesystem_idive.h b/src/divesystem_idive.h index 8c79866..1da80a0 100644 --- a/src/divesystem_idive.h +++ b/src/divesystem_idive.h @@ -26,6 +26,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { diff --git a/src/libdivecomputer.symbols b/src/libdivecomputer.symbols index 2d7e7de..04e4196 100644 --- a/src/libdivecomputer.symbols +++ b/src/libdivecomputer.symbols @@ -142,3 +142,4 @@ hw_ostc3_device_config_reset hw_ostc3_device_fwupdate atomics_cobalt_device_version atomics_cobalt_device_set_simulation +divesystem_idive_device_fwupdate From 4bbcb6a8a1bb859f3596f98b2a240832cdfdf789 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 25 Oct 2019 21:29:43 +0200 Subject: [PATCH 02/11] Add the Mares Genius to the bluetooth filter Use the prefix matcher because the bluetooth device name of the Mares Genius appears to contain spaces at the end. --- src/descriptor.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/descriptor.c b/src/descriptor.c index 824e496..2f8a2e0 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -565,10 +565,11 @@ static int dc_filter_mares (dc_transport_t transport, const void *userdata) { static const char * const bluetooth[] = { "Mares bluelink pro", + "Mares Genius", }; if (transport == DC_TRANSPORT_BLE) { - return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_name); + return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_prefix); } return 1; From 21a9fa6879714a2023e48799c327f6ae56d6ef2f Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 3 Nov 2019 19:50:40 +0100 Subject: [PATCH 03/11] Fix the Scubapro G2 HUD udev rule The USB VID/PID numbers must not include the hexadecimal 0x prefix. --- contrib/udev/libdivecomputer.rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/udev/libdivecomputer.rules b/contrib/udev/libdivecomputer.rules index 14d0ce6..e4e6fdc 100644 --- a/contrib/udev/libdivecomputer.rules +++ b/contrib/udev/libdivecomputer.rules @@ -17,7 +17,7 @@ SUBSYSTEM=="usb", ATTR{idVendor}=="2e6c", ATTR{idProduct}=="3201", GROUP="plugde SUBSYSTEM=="usb", ATTR{idVendor}=="2e6c", ATTR{idProduct}=="3211", GROUP="plugdev" # Scubapro G2 HUD -SUBSYSTEM=="usb", ATTR{idVendor}=="2e6c", ATTR{idProduct}=="0x4201", GROUP="plugdev" +SUBSYSTEM=="usb", ATTR{idVendor}=="2e6c", ATTR{idProduct}=="4201", GROUP="plugdev" # Scubapro Aladin Square SUBSYSTEM=="usb", ATTR{idVendor}=="c251", ATTR{idProduct}=="2006", GROUP="plugdev" From ab230fd4e0f92c8d3a60c14d70e0153a49cc0feb Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 12 Nov 2019 21:12:06 +0100 Subject: [PATCH 04/11] Fix the OSTC tank pressure decoding The tank pressure is stored with a resolution of 1 bar instead of 0.1 bar. There is however one exception. The hwOS Sport firmware used a resolution of 0.1 bar between versions 10.40 and 10.50. Unfortunately the only way to distinguish the Sport from the Tech variant is the different range of the version number (10.x vs 3.x). The consequence is that this workaround will start to produce wrong results once the firmware version number of the hwOS tech variant reaches the 10.x range. If that ever happens, this workaround should be removed again! --- src/hw_ostc_parser.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index f84e611..4d81f0c 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -918,7 +918,12 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call value = array_uint16_le (data + offset); if (value != 0) { sample.pressure.tank = tank; - sample.pressure.value = value / 10.0; + sample.pressure.value = value; + // The hwOS Sport firmware used a resolution of + // 0.1 bar between versions 10.40 and 10.50. + if (parser->model != OSTC4 && (firmware >= 0x0A28 && firmware <= 0x0A32)) { + sample.pressure.value /= 10.0; + } if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata); } break; From 7f21998ad5c73528651054251297c57ef5fb2c9d Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 19 Nov 2019 20:47:53 +0100 Subject: [PATCH 05/11] Limit the tank pressure workaround to hwOS devices The workaround for the tank pressure in the previous commit is only relevant for the newer hwOS based devices, and not for the original OSTC devices. In practice this doesn't cause any problems because the original OSTC doesn't support a tank pressure sensor, but nevertheless it's better to use the correct condition. --- src/hw_ostc_parser.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index 4d81f0c..8c80588 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -921,7 +921,8 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call sample.pressure.value = value; // The hwOS Sport firmware used a resolution of // 0.1 bar between versions 10.40 and 10.50. - if (parser->model != OSTC4 && (firmware >= 0x0A28 && firmware <= 0x0A32)) { + if (parser->hwos && parser->model != OSTC4 && + (firmware >= 0x0A28 && firmware <= 0x0A32)) { sample.pressure.value /= 10.0; } if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata); From b92cf6de6992a01698c9753d67517a9a99403a47 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 19 Nov 2019 21:07:15 +0100 Subject: [PATCH 06/11] Remove the obsolete hwos parameter In commit 2829f7ebf9902170bf653d67dbe412a0a4f140cf, the hwos parameter of the hw_ostc_parser_create() function was kept to preserve backwards compatibility. Since the function has been removed from the public api, the parameter can be removed now. --- src/hw_ostc.h | 2 +- src/hw_ostc_parser.c | 4 ++-- src/parser.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hw_ostc.h b/src/hw_ostc.h index a0ce743..fc45182 100644 --- a/src/hw_ostc.h +++ b/src/hw_ostc.h @@ -36,7 +36,7 @@ dc_status_t hw_ostc_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); dc_status_t -hw_ostc_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int hwos); +hw_ostc_parser_create (dc_parser_t **parser, dc_context_t *context); #ifdef __cplusplus } diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index 8c80588..5130d63 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -332,9 +332,9 @@ hw_ostc_parser_create_internal (dc_parser_t **out, dc_context_t *context, unsign dc_status_t -hw_ostc_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int hwos) +hw_ostc_parser_create (dc_parser_t **out, dc_context_t *context) { - return hw_ostc_parser_create_internal (out, context, hwos, 0); + return hw_ostc_parser_create_internal (out, context, 0, 0); } dc_status_t diff --git a/src/parser.c b/src/parser.c index 6a236e6..9203490 100644 --- a/src/parser.c +++ b/src/parser.c @@ -132,7 +132,7 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa rc = mares_iconhd_parser_create (&parser, context, model); break; case DC_FAMILY_HW_OSTC: - rc = hw_ostc_parser_create (&parser, context, 0); + rc = hw_ostc_parser_create (&parser, context); break; case DC_FAMILY_HW_FROG: case DC_FAMILY_HW_OSTC3: From 612011249d29e3a4835d8aded2ea5a6f9a6dabc1 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 19 Nov 2019 21:33:56 +0100 Subject: [PATCH 07/11] Use symbolic constants for the sample types --- src/hw_ostc_parser.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index 5130d63..5ad91bc 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -43,6 +43,14 @@ #define HEADER 1 #define PROFILE 2 +#define TEMPERATURE 0 +#define DECO 1 +#define GF 2 +#define PPO2 3 +#define DECOPLAN 4 +#define CNS 5 +#define TANK 6 + #define OSTC_ZHL16_OC 0 #define OSTC_GAUGE 1 #define OSTC_ZHL16_CC 2 @@ -641,21 +649,21 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call if (info[i].divisor) { switch (info[i].type) { - case 0: // Temperature - case 1: // Deco / NDL - case 6: // Tank pressure + case TEMPERATURE: + case DECO: + case TANK: if (info[i].size != 2) { ERROR(abstract->context, "Unexpected sample size."); return DC_STATUS_DATAFORMAT; } break; - case 3: // ppO2 + case PPO2: if (info[i].size != 3 && info[i].size != 9) { ERROR(abstract->context, "Unexpected sample size."); return DC_STATUS_DATAFORMAT; } break; - case 5: // CNS + case CNS: if (info[i].size != 1 && info[i].size != 2) { ERROR(abstract->context, "Unexpected sample size."); return DC_STATUS_DATAFORMAT; @@ -870,12 +878,12 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call unsigned int count = 0; unsigned int value = 0; switch (info[i].type) { - case 0: // Temperature (0.1 °C). + case TEMPERATURE: value = array_uint16_le (data + offset); sample.temperature = value / 10.0; if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); break; - case 1: // Deco / NDL + case DECO: // Due to a firmware bug, the deco/ndl info is incorrect for // all OSTC4 dives with a firmware older than version 1.0.8. if (parser->model == OSTC4 && firmware < 0x0810) @@ -890,7 +898,7 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call sample.deco.time = data[offset + 1] * 60; if (callback) callback (DC_SAMPLE_DECO, sample, userdata); break; - case 3: // ppO2 (0.01 bar). + case PPO2: for (unsigned int j = 0; j < 3; ++j) { if (info[i].size == 3) { ppo2[j] = data[offset + j]; @@ -907,14 +915,14 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call } } break; - case 5: // CNS + case CNS: if (info[i].size == 2) sample.cns = array_uint16_le (data + offset) / 100.0; else sample.cns = data[offset] / 100.0; if (callback) callback (DC_SAMPLE_CNS, sample, userdata); break; - case 6: // Tank pressure + case TANK: value = array_uint16_le (data + offset); if (value != 0) { sample.pressure.tank = tank; From a5ba2f4e41e914ba1e93bc66dcb9b2db8a2cecb9 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 19 Nov 2019 21:36:26 +0100 Subject: [PATCH 08/11] Use macros to encode the firmware version --- src/hw_ostc_parser.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index 5ad91bc..4a348b8 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -71,6 +71,16 @@ #define OSTC4 0x3B +#define OSTC3FW(major,minor) ( \ + (((major) & 0xFF) << 8) | \ + ((minor) & 0xFF)) + +#define OSTC4FW(major,minor,micro,beta) ( \ + (((major) & 0x1F) << 11) | \ + (((minor) & 0x1F) >> 6) | \ + (((micro) & 0x1F) << 1) | \ + ((beta) & 0x01)) + typedef struct hw_ostc_sample_info_t { unsigned int type; unsigned int divisor; @@ -886,7 +896,7 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call case DECO: // Due to a firmware bug, the deco/ndl info is incorrect for // all OSTC4 dives with a firmware older than version 1.0.8. - if (parser->model == OSTC4 && firmware < 0x0810) + if (parser->model == OSTC4 && firmware < OSTC4FW(1,0,8,0)) break; if (data[offset]) { sample.deco.type = DC_DECO_DECOSTOP; @@ -930,7 +940,7 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call // The hwOS Sport firmware used a resolution of // 0.1 bar between versions 10.40 and 10.50. if (parser->hwos && parser->model != OSTC4 && - (firmware >= 0x0A28 && firmware <= 0x0A32)) { + (firmware >= OSTC3FW(10,40) && firmware <= OSTC3FW(10,50))) { sample.pressure.value /= 10.0; } if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata); From b9a3606f379c3c9f57bbf561bdcb52fb76b711db Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 19 Nov 2019 22:16:09 +0100 Subject: [PATCH 09/11] Add a workaround for the hwOS ppO2 firmware bug Due to a bug in the hwOS Tech firmware v3.03 to v3.07, and the hwOS Sport firmware v10.57 to v10.63, the ppO2 divisor is sometimes not correctly reset to zero when no ppO2 samples are being recorded. Usually this condition can be detected by the fact that the length of the extended sample will not have enough space left for the ppO2 sample (9 bytes). As a workaround, reset the divisor back to zero to manually disable the ppO2 samples. In theory this detection method is not 100% reliable. There can still be other sample types present in the extended sample. If their total size is larger than 9 bytes, the bug will not be detected at all. Instead, those bytes will get interpreted as the ppO2 sample, resulting in bogus ppO2 values. Additionally, one of the other sample types will now run out of space and cause the parsing to fail with an error. However, in practice this risk is relative low. Most of the other samples are relative small (1 or 2 bytes), so you would need many of them. That's rather unlikely in most configurations. The only exception is the large deco plan sample (15 bytes). --- src/hw_ostc_parser.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index 4a348b8..0f96b90 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -880,6 +880,17 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call for (unsigned int i = 0; i < nconfig; ++i) { if (info[i].divisor && (nsamples % info[i].divisor) == 0) { if (length < info[i].size) { + // Due to a bug in the hwOS Tech firmware v3.03 to v3.07, and + // the hwOS Sport firmware v10.57 to v10.63, the ppO2 divisor + // is sometimes not correctly reset to zero when no ppO2 + // samples are being recorded. + if (info[i].type == PPO2 && parser->hwos && parser->model != OSTC4 && + ((firmware >= OSTC3FW(3,3) && firmware <= OSTC3FW(3,7)) || + (firmware >= OSTC3FW(10,57) && firmware <= OSTC3FW(10,63)))) { + WARNING (abstract->context, "Reset invalid ppO2 divisor to zero."); + info[i].divisor = 0; + continue; + } ERROR (abstract->context, "Buffer overflow detected!"); return DC_STATUS_DATAFORMAT; } From ae733fd8a82f2c6f331a869325ac7428dad30851 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 13 Nov 2019 21:27:18 +0100 Subject: [PATCH 10/11] Ignore all empty logbook entries The logbook ringbuffer is always updated sequentially. Therefore, emtpy entries can only be present after the oldest dive. However it appears that under some special conditions (for example an empty battery during the dive), the logbook entry is not always stored correctly, which can result in an empty entry after all. I suspect that at the start of each dive, the OSTC erases the next available entry in the logbook ringbuffer and updates the internal write pointer. Once the dive is finished, the actual content of the erased logbook is written. Thus, when the OSTC runs out of battery power during the dive, that last step never happens, and the erased entry remains in place. As a workaround, ignore all empty logbook entries instead of assuming we reached the last dive. --- src/hw_ostc3.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 187a9e4..0521e2e 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -713,7 +713,6 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi // The device maintains an internal counter which is incremented for every // dive, and the current value at the time of the dive is stored in the // dive header. Thus the most recent dive will have the highest value. - unsigned int count = 0; unsigned int latest = 0; unsigned int maximum = 0; for (unsigned int i = 0; i < RB_LOGBOOK_COUNT; ++i) { @@ -729,24 +728,21 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi maximum = current; latest = i; } - - count++; } // Calculate the total and maximum size. unsigned int ndives = 0; unsigned int size = 0; unsigned int maxsize = 0; - for (unsigned int i = 0; i < count; ++i) { + unsigned char dive[RB_LOGBOOK_COUNT] = {0}; + for (unsigned int i = 0; i < RB_LOGBOOK_COUNT; ++i) { unsigned int idx = (latest + RB_LOGBOOK_COUNT - i) % RB_LOGBOOK_COUNT; unsigned int offset = idx * logbook->size; - // Uninitialized header entries should no longer be present at this - // stage, unless the dives are interleaved with empty entries. But - // that's something we don't support at all. + // Ignore uninitialized header entries. if (array_isequal (header + offset, logbook->size, 0xFF)) { WARNING (abstract->context, "Unexpected empty header found."); - break; + continue; } // Calculate the profile length. @@ -770,6 +766,7 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi if (length > maxsize) maxsize = length; size += length; + dive[ndives] = idx; ndives++; } @@ -793,7 +790,7 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi // Download the dives. for (unsigned int i = 0; i < ndives; ++i) { - unsigned int idx = (latest + RB_LOGBOOK_COUNT - i) % RB_LOGBOOK_COUNT; + unsigned int idx = dive[i]; unsigned int offset = idx * logbook->size; // Calculate the profile length. From 03c8e350dd990c9e9adc4a61d1256c21a7efb1e5 Mon Sep 17 00:00:00 2001 From: Nick Shore Date: Fri, 20 Dec 2019 10:47:48 +0100 Subject: [PATCH 11/11] Fix the Oceanic Geo 4.0 memory layout --- src/oceanic_atom2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index b93de28..1bc18c8 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -124,6 +124,7 @@ static const oceanic_common_version_t oceanic_atom2b_version[] = { {"HOLLDG03 \0\0 512K"}, {"AQUAI100 \0\0 512K"}, {"AQUA300C \0\0 512K"}, + {"OCEGEO40 \0\0 512K"}, }; static const oceanic_common_version_t oceanic_atom2c_version[] = { @@ -138,7 +139,6 @@ static const oceanic_common_version_t oceanic_default_version[] = { {"ELITET31 \0\0 512K"}, {"DATAMASK \0\0 512K"}, {"COMPMASK \0\0 512K"}, - {"OCEGEO40 \0\0 512K"}, }; static const oceanic_common_version_t sherwood_wisdom_version[] = {