From 535d612a93cf1812cee0ab7a7ea5f032d3f8fcab Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 1 Aug 2017 21:15:36 +0200 Subject: [PATCH 01/25] Use the correct type in the leonardo vtable --- src/cressi_leonardo_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cressi_leonardo_parser.c b/src/cressi_leonardo_parser.c index 6032376..b8c47e0 100644 --- a/src/cressi_leonardo_parser.c +++ b/src/cressi_leonardo_parser.c @@ -46,7 +46,7 @@ static dc_status_t cressi_leonardo_parser_samples_foreach (dc_parser_t *abstract static const dc_parser_vtable_t cressi_leonardo_parser_vtable = { sizeof(cressi_leonardo_parser_t), - DC_FAMILY_CRESSI_EDY, + DC_FAMILY_CRESSI_LEONARDO, cressi_leonardo_parser_set_data, /* set_data */ cressi_leonardo_parser_get_datetime, /* datetime */ cressi_leonardo_parser_get_field, /* fields */ From 4e0c2e46501e16648f5ea74b57d76b6f4d593c3f Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 1 Aug 2017 13:12:18 +0200 Subject: [PATCH 02/25] Remove non-existing function from header The dc_device_version() function has been removed from the public api long time ago. --- include/libdivecomputer/device.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/include/libdivecomputer/device.h b/include/libdivecomputer/device.h index 7ba4bd6..1990926 100644 --- a/include/libdivecomputer/device.h +++ b/include/libdivecomputer/device.h @@ -84,9 +84,6 @@ dc_device_set_events (dc_device_t *device, unsigned int events, dc_event_callbac dc_status_t dc_device_set_fingerprint (dc_device_t *device, const unsigned char data[], unsigned int size); -dc_status_t -dc_device_version (dc_device_t *device, unsigned char data[], unsigned int size); - dc_status_t dc_device_read (dc_device_t *device, unsigned int address, unsigned char data[], unsigned int size); From 55e8f83eb5d934e65fbf587d427de267f174c651 Mon Sep 17 00:00:00 2001 From: John Van Ostrand Date: Sun, 2 Jul 2017 22:05:12 -0400 Subject: [PATCH 03/25] Use rbstream for reading profile data Per-dive reading of the DC takes too long because of latency on read commands. The rbstream solves this by reading ahead in large blocks. --- src/cochran_commander.c | 92 ++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 57 deletions(-) diff --git a/src/cochran_commander.c b/src/cochran_commander.c index 1ad38f0..1e4bfb3 100644 --- a/src/cochran_commander.c +++ b/src/cochran_commander.c @@ -29,6 +29,7 @@ #include "serial.h" #include "array.h" #include "ringbuffer.h" +#include "rbstream.h" #define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array)) @@ -473,31 +474,6 @@ cochran_commander_read (cochran_commander_device_t *device, dc_event_progress_t return DC_STATUS_SUCCESS; } -static unsigned int -cochran_commander_read_retry (cochran_commander_device_t *device, dc_event_progress_t *progress, unsigned int address, unsigned char data[], unsigned int size) -{ - // Save the state of the progress events. - unsigned int saved = progress->current; - - unsigned int nretries = 0; - dc_status_t rc = DC_STATUS_SUCCESS; - while ((rc = cochran_commander_read (device, progress, address, data, size)) != 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; - - // Restore the state of the progress events. - progress->current = saved; - } - - return rc; -} - /* * For corrupt dives the end-of-samples pointer is 0xFFFFFFFF @@ -607,11 +583,8 @@ cochran_commander_find_fingerprint(cochran_commander_device_t *device, cochran_d } unsigned int profile_pre = array_uint32_le(log_entry + device->layout->pt_profile_pre); - unsigned int profile_begin = array_uint32_le(log_entry + device->layout->pt_profile_begin); - unsigned int profile_end = array_uint32_le(log_entry + device->layout->pt_profile_end); unsigned int sample_size = cochran_commander_profile_size(device, data, idx, profile_pre, last_profile_pre); - unsigned int read_size = cochran_commander_profile_size(device, data, idx, profile_begin, profile_end); last_profile_pre = profile_pre; // Determine if sample exists @@ -623,7 +596,7 @@ cochran_commander_find_fingerprint(cochran_commander_device_t *device, cochran_d data->invalid_profile_dive_num = idx; } // Accumulate read size for progress bar - sample_read_size += read_size; + sample_read_size += sample_size; } } @@ -860,6 +833,7 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call cochran_commander_device_t *device = (cochran_commander_device_t *) abstract; const cochran_device_layout_t *layout = device->layout; dc_status_t status = DC_STATUS_SUCCESS; + dc_rbstream_t *rbstream = NULL; cochran_data_t data; data.logbook = NULL; @@ -957,6 +931,14 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call // Number of dives to read dive_count = (layout->rb_logbook_entry_count + head_dive - tail_dive) % layout->rb_logbook_entry_count; + // Create the ringbuffer stream. + unsigned int last_start_address = array_uint32_le (data.logbook + ((head_dive - 1) * layout->rb_logbook_entry_size) + layout->pt_profile_end); + status = dc_rbstream_new (&rbstream, abstract, 1, 131072, layout->rb_profile_begin, layout->rb_profile_end, last_start_address); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to create the ringbuffer stream."); + goto error; + } + int invalid_profile_flag = 0; // Loop through each dive @@ -968,14 +950,17 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call unsigned int sample_start_address = array_uint32_le (log_entry + layout->pt_profile_begin); unsigned int sample_end_address = array_uint32_le (log_entry + layout->pt_profile_end); - int sample_size = 0; + int sample_size = 0, pre_size = 0; // Determine if profile exists if (idx == data.invalid_profile_dive_num) invalid_profile_flag = 1; - if (!invalid_profile_flag) + if (!invalid_profile_flag) { sample_size = cochran_commander_profile_size(device, &data, idx, sample_start_address, sample_end_address); + pre_size = cochran_commander_profile_size(device, &data, idx, sample_end_address, last_start_address); + last_start_address = sample_start_address; + } // Build dive blob unsigned int dive_size = layout->rb_logbook_entry_size + sample_size; @@ -989,38 +974,30 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call // Read profile data if (sample_size) { - if (sample_end_address == 0xFFFFFFFF) - // Corrupt dive, guess the end address - sample_end_address = cochran_commander_guess_sample_end_address(device, &data, idx); - - if (sample_start_address <= sample_end_address) { - rc = cochran_commander_read_retry (device, &progress, sample_start_address, dive + layout->rb_logbook_entry_size, sample_size); + if (pre_size) { + // Read throwaway sample data, pre-dive events and post-dive surface sample + unsigned char *pre = (unsigned char *) malloc (pre_size); + if (pre == NULL) { + status = DC_STATUS_NOMEMORY; + goto error; + } + rc = dc_rbstream_read(rbstream, &progress, pre, pre_size); + free(pre); if (rc != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the sample data."); - status = rc; - free(dive); - goto error; - } - } else { - // It wrapped the buffer, copy two sections - unsigned int size = layout->rb_profile_end - sample_start_address; - - rc = cochran_commander_read_retry (device, &progress, sample_start_address, dive + layout->rb_logbook_entry_size, size); - if (rc != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the sample data."); - status = rc; - free(dive); - goto error; - } - - rc = cochran_commander_read_retry (device, &progress, layout->rb_profile_begin, dive + layout->rb_logbook_entry_size + size, sample_end_address - layout->rb_profile_begin); - if (rc != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the sample data."); + ERROR (abstract->context, "Failed to read the pre-dive event data."); status = rc; free(dive); goto error; } } + // read sample data + rc = dc_rbstream_read(rbstream, &progress, dive + layout->rb_logbook_entry_size, sample_size); + if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the sample data."); + status = rc; + free(dive); + goto error; + } } if (callback && !callback (dive, dive_size, dive + layout->pt_fingerprint, layout->fingerprint_size, userdata)) { @@ -1032,6 +1009,7 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call } error: + dc_rbstream_free(rbstream); free(data.logbook); return status; } From cd5eb61e904eefb97d180959b201a7b9589b1fae Mon Sep 17 00:00:00 2001 From: John Van Ostrand Date: Fri, 14 Jul 2017 19:50:57 -0400 Subject: [PATCH 04/25] Make rbstream pagesize smaller and device specific The progress bar was taking 18 seconds between updates on a Commander II when using a 128K pagesize. Since devices differ in their baud rates, it makes sense to use smaller pages on slower devices. This change reduces it to 32K on a Commander II and to 64K on EMC devices. --- src/cochran_commander.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/cochran_commander.c b/src/cochran_commander.c index 1e4bfb3..5ceb046 100644 --- a/src/cochran_commander.c +++ b/src/cochran_commander.c @@ -71,6 +71,7 @@ typedef struct cochran_device_layout_t { unsigned int address_bits; cochran_endian_t endian; unsigned int baudrate; + unsigned int rbstream_size; // Config data. unsigned int cf_dive_count; unsigned int cf_last_log; @@ -123,6 +124,7 @@ static const cochran_device_layout_t cochran_cmdr_1_device_layout = { 24, // address_bits ENDIAN_WORD_BE, // endian 115200, // baudrate + 32768, // rbstream_size 0x046, // cf_dive_count 0x6c, // cf_last_log 0x70, // cf_last_interdive @@ -147,6 +149,7 @@ static const cochran_device_layout_t cochran_cmdr_device_layout = { 24, // address_bits ENDIAN_WORD_BE, // endian 115200, // baudrate + 32768, // rbstream_size 0x046, // cf_dive_count 0x06C, // cf_last_log 0x070, // cf_last_interdive @@ -170,6 +173,7 @@ static const cochran_device_layout_t cochran_emc14_device_layout = { 32, // address_bits ENDIAN_LE, // endian 806400, // baudrate + 65536, // rbstream_size 0x0D2, // cf_dive_count 0x13E, // cf_last_log 0x142, // cf_last_interdive @@ -193,6 +197,7 @@ static const cochran_device_layout_t cochran_emc16_device_layout = { 32, // address_bits ENDIAN_LE, // endian 806400, // baudrate + 65536, // rbstream_size 0x0D2, // cf_dive_count 0x13E, // cf_last_log 0x142, // cf_last_interdive @@ -216,6 +221,7 @@ static const cochran_device_layout_t cochran_emc20_device_layout = { 32, // address_bits ENDIAN_LE, // endian 806400, // baudrate + 65536, // rbstream_size 0x0D2, // cf_dive_count 0x13E, // cf_last_log 0x142, // cf_last_interdive @@ -933,7 +939,7 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call // Create the ringbuffer stream. unsigned int last_start_address = array_uint32_le (data.logbook + ((head_dive - 1) * layout->rb_logbook_entry_size) + layout->pt_profile_end); - status = dc_rbstream_new (&rbstream, abstract, 1, 131072, layout->rb_profile_begin, layout->rb_profile_end, last_start_address); + status = dc_rbstream_new (&rbstream, abstract, 1, layout->rbstream_size, layout->rb_profile_begin, layout->rb_profile_end, last_start_address); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to create the ringbuffer stream."); goto error; From 88b6e36e04c5fb0fcc527615bd6a5336ad343b0f Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 31 Jul 2017 16:50:21 +0200 Subject: [PATCH 05/25] Use only a single read operation The profile and throw-away data can be read all at once. This avoids the need for an extra memory allocation and fixes a memory leak. --- src/cochran_commander.c | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/src/cochran_commander.c b/src/cochran_commander.c index 5ceb046..27065f6 100644 --- a/src/cochran_commander.c +++ b/src/cochran_commander.c @@ -970,7 +970,7 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call // Build dive blob unsigned int dive_size = layout->rb_logbook_entry_size + sample_size; - unsigned char *dive = (unsigned char *) malloc(dive_size); + unsigned char *dive = (unsigned char *) malloc(dive_size + pre_size); if (dive == NULL) { status = DC_STATUS_NOMEMORY; goto error; @@ -980,24 +980,7 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call // Read profile data if (sample_size) { - if (pre_size) { - // Read throwaway sample data, pre-dive events and post-dive surface sample - unsigned char *pre = (unsigned char *) malloc (pre_size); - if (pre == NULL) { - status = DC_STATUS_NOMEMORY; - goto error; - } - rc = dc_rbstream_read(rbstream, &progress, pre, pre_size); - free(pre); - if (rc != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the pre-dive event data."); - status = rc; - free(dive); - goto error; - } - } - // read sample data - rc = dc_rbstream_read(rbstream, &progress, dive + layout->rb_logbook_entry_size, sample_size); + rc = dc_rbstream_read(rbstream, &progress, dive + layout->rb_logbook_entry_size, sample_size + pre_size); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the sample data."); status = rc; From 8b41e72cf721bfeb592288357c27460196ddf358 Mon Sep 17 00:00:00 2001 From: John Van Ostrand Date: Sat, 15 Jul 2017 16:39:36 -0400 Subject: [PATCH 06/25] Remove unneeded function Since moving to per-dive download of profile data (and now rbstream download) the data->sample_data_offset and data->sample_size variables aren't used so calculating them doesn't make sense. --- src/cochran_commander.c | 68 ----------------------------------------- 1 file changed, 68 deletions(-) diff --git a/src/cochran_commander.c b/src/cochran_commander.c index 27065f6..c782fb9 100644 --- a/src/cochran_commander.c +++ b/src/cochran_commander.c @@ -61,9 +61,6 @@ typedef struct cochran_data_t { int invalid_profile_dive_num; unsigned int logbook_size; - - unsigned int sample_data_offset; - unsigned int sample_size; } cochran_data_t; typedef struct cochran_device_layout_t { @@ -610,69 +607,6 @@ cochran_commander_find_fingerprint(cochran_commander_device_t *device, cochran_d } - -static void -cochran_commander_get_sample_parms(cochran_commander_device_t *device, cochran_data_t *data) -{ - dc_device_t *abstract = (dc_device_t *) device; - unsigned int pre_dive_offset = 0, end_dive_offset = 0; - - unsigned int dive_count = 0; - if (data->dive_count < device->layout->rb_logbook_entry_count) - dive_count = data->dive_count; - else - dive_count = device->layout->rb_logbook_entry_count; - - // Find lowest and highest offsets into sample data - unsigned int low_offset = 0xFFFFFFFF; - unsigned int high_offset = 0; - - for (int i = data->fp_dive_num + 1; i < dive_count; i++) { - pre_dive_offset = array_uint32_le (data->logbook + i * device->layout->rb_logbook_entry_size - + device->layout->pt_profile_pre); - end_dive_offset = array_uint32_le (data->logbook + i * device->layout->rb_logbook_entry_size - + device->layout->pt_profile_end); - - // Validate offsets, allow 0xFFFFFFF for end_dive_offset - // because we handle that as a special case. - if (pre_dive_offset < device->layout->rb_profile_begin || - pre_dive_offset > device->layout->rb_profile_end) { - ERROR(abstract->context, "Invalid pre-dive offset (%08x) on dive %d.", pre_dive_offset, i); - continue; - } - - if (end_dive_offset < device->layout->rb_profile_begin || - (end_dive_offset > device->layout->rb_profile_end && - end_dive_offset != 0xFFFFFFFF)) { - ERROR(abstract->context, "Invalid end-dive offset (%08x) on dive %d.", end_dive_offset, i); - continue; - } - - // Check for ring buffer wrap-around. - if (pre_dive_offset > end_dive_offset) - break; - - if (pre_dive_offset < low_offset) - low_offset = pre_dive_offset; - if (end_dive_offset > high_offset && end_dive_offset != 0xFFFFFFFF ) - high_offset = end_dive_offset; - } - - if (pre_dive_offset > end_dive_offset) { - high_offset = device->layout->rb_profile_end; - low_offset = device->layout->rb_profile_begin; - data->sample_data_offset = low_offset; - data->sample_size = high_offset - low_offset; - } else if (low_offset < 0xFFFFFFFF && high_offset > 0) { - data->sample_data_offset = low_offset; - data->sample_size = high_offset - data->sample_data_offset; - } else { - data->sample_data_offset = 0; - data->sample_size = 0; - } -} - - dc_status_t cochran_commander_device_open (dc_device_t **out, dc_context_t *context, const char *name) { @@ -906,8 +840,6 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call progress.maximum -= (max_sample - profile_read_size); device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - cochran_commander_get_sample_parms(device, &data); - // Emit a device info event. dc_event_devinfo_t devinfo; devinfo.model = layout->model; From 1d8f25ba9b73e094286df4b098ab5859cf61e881 Mon Sep 17 00:00:00 2001 From: John Van Ostrand Date: Sat, 15 Jul 2017 16:39:38 -0400 Subject: [PATCH 07/25] Dump function no longer assumes reads begin at byte 0 For previously supported Cochran computers high-speed read of log and profile data started at byte 0. Older models that lack the high-speed transfer function use the standard speed read commands and so the log and profile data are read at higher addresses. --- src/cochran_commander.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/cochran_commander.c b/src/cochran_commander.c index c782fb9..7728b4f 100644 --- a/src/cochran_commander.c +++ b/src/cochran_commander.c @@ -728,6 +728,7 @@ cochran_commander_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) cochran_commander_device_t *device = (cochran_commander_device_t *) abstract; dc_status_t rc = DC_STATUS_SUCCESS; unsigned char config[1024]; + unsigned int size = device->layout->rb_profile_end - device->layout->rb_logbook_begin; // Make sure buffer is good. if (!dc_buffer_clear(buffer)) { @@ -736,14 +737,14 @@ cochran_commander_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) } // Reserve space - if (!dc_buffer_resize(buffer, device->layout->rb_profile_end)) { + if (!dc_buffer_resize(buffer, size)) { ERROR(abstract->context, "Insufficient buffer space available."); return DC_STATUS_NOMEMORY; } // Determine size for progress dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; - progress.maximum = sizeof(config) + device->layout->rb_profile_end; + progress.maximum = sizeof(config) + size; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); // Emit ID block @@ -757,7 +758,7 @@ cochran_commander_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) return rc; // Read the sample data, from 0 to sample end will include logbook - rc = cochran_commander_read (device, &progress, 0, dc_buffer_get_data(buffer), device->layout->rb_profile_end); + rc = cochran_commander_read (device, &progress, device->layout->rb_logbook_begin, dc_buffer_get_data(buffer), device->layout->rb_profile_end); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the sample data."); return rc; From fb65726a55df3509b746ddd0cc8e97ce3ace04b4 Mon Sep 17 00:00:00 2001 From: John Van Ostrand Date: Sat, 15 Jul 2017 16:39:37 -0400 Subject: [PATCH 08/25] Add support for the Commander TM The Cochran Commander TM appears to be a first generation Commander with limited storage and function compared to later models. The main differences are: - The TM doesn't support high-speed transfer so use the 0x05 read command and don't change to a higher baud rate. Still reset to 9600 to wait for the heartbeat. - The TM has a different config command (one byte). - The TM has only one config page. --- src/cochran_commander.c | 175 +++++++++++++++++++++++++------- src/cochran_commander_parser.c | 179 +++++++++++++++++++++++++++++++-- src/descriptor.c | 11 +- 3 files changed, 313 insertions(+), 52 deletions(-) diff --git a/src/cochran_commander.c b/src/cochran_commander.c index 7728b4f..50422fb 100644 --- a/src/cochran_commander.c +++ b/src/cochran_commander.c @@ -35,11 +35,12 @@ #define MAXRETRIES 2 -#define COCHRAN_MODEL_COMMANDER_PRE21000 0 -#define COCHRAN_MODEL_COMMANDER_AIR_NITROX 1 -#define COCHRAN_MODEL_EMC_14 2 -#define COCHRAN_MODEL_EMC_16 3 -#define COCHRAN_MODEL_EMC_20 4 +#define COCHRAN_MODEL_COMMANDER_TM 0 +#define COCHRAN_MODEL_COMMANDER_PRE21000 1 +#define COCHRAN_MODEL_COMMANDER_AIR_NITROX 2 +#define COCHRAN_MODEL_EMC_14 3 +#define COCHRAN_MODEL_EMC_16 4 +#define COCHRAN_MODEL_EMC_20 5 typedef enum cochran_endian_t { ENDIAN_LE, @@ -88,6 +89,7 @@ typedef struct cochran_device_layout_t { unsigned int pt_profile_pre; unsigned int pt_profile_begin; unsigned int pt_profile_end; + unsigned int pt_dive_number; } cochran_device_layout_t; typedef struct cochran_commander_device_t { @@ -115,6 +117,31 @@ static const dc_device_vtable_t cochran_commander_device_vtable = { cochran_commander_device_close /* close */ }; +// Cochran Commander TM, pre-dates pre-21000 s/n +static const cochran_device_layout_t cochran_cmdr_tm_device_layout = { + COCHRAN_MODEL_COMMANDER_TM, // model + 24, // address_bits + ENDIAN_WORD_BE, // endian + 9600, // baudrate + 4096, // rbstream_size + 0x146, // cf_dive_count + 0x158, // cf_last_log + 0xffffff, // cf_last_interdive + 0x15c, // cf_serial_number + 0x010000, // rb_logbook_begin + 0x012328, // rb_logbook_end + 90, // rb_logbook_entry_size + 100, // rb_logbook_entry_count + 0x012328, // rb_profile_begin + 0x020000, // rb_profile_end + 15, // pt_fingerprint + 4, // fingerprint_size + 0, // pt_profile_pre + 0, // pt_profile_begin + 90, // pt_profile_end (Next begin pointer is the end) + 20, // pt_dive_number +}; + // Cochran Commander pre-21000 s/n static const cochran_device_layout_t cochran_cmdr_1_device_layout = { COCHRAN_MODEL_COMMANDER_PRE21000, // model @@ -137,6 +164,7 @@ static const cochran_device_layout_t cochran_cmdr_1_device_layout = { 28, // pt_profile_pre 0, // pt_profile_begin 128, // pt_profile_end + 68, // pt_dive_number }; @@ -162,6 +190,7 @@ static const cochran_device_layout_t cochran_cmdr_device_layout = { 30, // pt_profile_pre 6, // pt_profile_begin 128, // pt_profile_end + 70, // pt_dive_number }; // Cochran EMC-14 @@ -186,6 +215,7 @@ static const cochran_device_layout_t cochran_emc14_device_layout = { 30, // pt_profile_pre 6, // pt_profile_begin 256, // pt_profile_end + 86, // pt_dive_number }; // Cochran EMC-16 @@ -210,6 +240,7 @@ static const cochran_device_layout_t cochran_emc16_device_layout = { 30, // pt_profile_pre 6, // pt_profile_begin 256, // pt_profile_end + 86, // pt_dive_number }; // Cochran EMC-20 @@ -234,6 +265,7 @@ static const cochran_device_layout_t cochran_emc20_device_layout = { 30, // pt_profile_pre 6, // pt_profile_begin 256, // pt_profile_end + 86, // pt_dive_number }; @@ -242,6 +274,7 @@ static unsigned int cochran_commander_get_model (cochran_commander_device_t *device) { const cochran_commander_model_t models[] = { + {"\x0a""12", COCHRAN_MODEL_COMMANDER_TM}, {"\x11""21", COCHRAN_MODEL_COMMANDER_PRE21000}, {"\x11""22", COCHRAN_MODEL_COMMANDER_AIR_NITROX}, {"730", COCHRAN_MODEL_EMC_14}, @@ -334,7 +367,7 @@ cochran_commander_packet (cochran_commander_device_t *device, dc_event_progress_ } } - if (high_speed) { + if (high_speed && device->layout->baudrate != 9600) { // Give the DC time to process the command. dc_serial_sleep(device->port, 45); @@ -403,18 +436,24 @@ cochran_commander_read_config (cochran_commander_device_t *device, dc_event_prog dc_device_t *abstract = (dc_device_t *) device; dc_status_t rc = DC_STATUS_SUCCESS; - // Read two 512 byte blocks into one 1024 byte buffer - for (unsigned int i = 0; i < 2; i++) { - const unsigned int len = size / 2; + if ((size % 512) != 0) + return DC_STATUS_INVALIDARGS; + // Read two 512 byte blocks into one 1024 byte buffer + unsigned int pages = size / 512; + for (unsigned int i = 0; i < pages; i++) { unsigned char command[2] = {0x96, i}; - rc = cochran_commander_packet(device, progress, command, sizeof(command), data + i * len, len, 0); + unsigned int command_size = sizeof(command); + if (device->layout->model == COCHRAN_MODEL_COMMANDER_TM) + command_size = 1; + + rc = cochran_commander_packet(device, progress, command, command_size, data + i * 512, 512, 0); if (rc != DC_STATUS_SUCCESS) return rc; dc_event_vendor_t vendor; - vendor.data = data + i * len; - vendor.size = len; + vendor.data = data + i * 512; + vendor.size = 512; device_event_emit (abstract, DC_EVENT_VENDOR, &vendor); } @@ -448,15 +487,36 @@ cochran_commander_read (cochran_commander_device_t *device, dc_event_progress_t break; case 24: // Commander uses 24 byte addressing - command[0] = 0x15; - command[1] = (address ) & 0xff; - command[2] = (address >> 8) & 0xff; - command[3] = (address >> 16) & 0xff; - command[4] = (size ) & 0xff; - command[5] = (size >> 8 ) & 0xff; - command[6] = (size >> 16 ) & 0xff; - command[7] = 0x04; - command_size = 8; + if (device->layout->baudrate == 9600) { + // This read command will return 32K bytes if asked to read + // 0 bytes. So we can allow a size of up to 0x10000 but if + // the user asks for 0 bytes we should just return success + // otherwise we'll end end up running past the buffer. + if (size > 0x10000) + return DC_STATUS_INVALIDARGS; + if (size == 0) + return DC_STATUS_SUCCESS; + + // Older commander, use low-speed read command + command[0] = 0x05; + command[1] = (address ) & 0xff; + command[2] = (address >> 8) & 0xff; + command[3] = (address >> 16) & 0xff; + command[4] = (size ) & 0xff; + command[5] = (size >> 8 ) & 0xff; + command_size = 6; + } else { + // Newer commander with high-speed read command + command[0] = 0x15; + command[1] = (address ) & 0xff; + command[2] = (address >> 8) & 0xff; + command[3] = (address >> 16) & 0xff; + command[4] = (size ) & 0xff; + command[5] = (size >> 8 ) & 0xff; + command[6] = (size >> 16 ) & 0xff; + command[7] = 0x04; + command_size = 8; + } break; default: return DC_STATUS_UNSUPPORTED; @@ -526,6 +586,8 @@ cochran_commander_profile_size(cochran_commander_device_t *device, cochran_data_ static unsigned int cochran_commander_find_fingerprint(cochran_commander_device_t *device, cochran_data_t *data) { + unsigned int base = device->layout->rb_logbook_begin; + // We track profile ringbuffer usage to determine which dives have profile data int profile_capacity_remaining = device->layout->rb_profile_end - device->layout->rb_profile_begin; @@ -544,10 +606,13 @@ cochran_commander_find_fingerprint(cochran_commander_device_t *device, cochran_d // Remove the pre-dive events that occur after the last dive unsigned int rb_head_ptr = 0; - if (device->layout->endian == ENDIAN_WORD_BE) - rb_head_ptr = (array_uint32_word_be(data->config + device->layout->cf_last_log) & 0xfffff000) + 0x2000; + if (device->layout->model == COCHRAN_MODEL_COMMANDER_TM) + // TM uses SRAM and does not need to erase pages + rb_head_ptr = base + array_uint32_word_be(data->config + device->layout->cf_last_log); + else if (device->layout->endian == ENDIAN_WORD_BE) + rb_head_ptr = base + (array_uint32_word_be(data->config + device->layout->cf_last_log) & 0xfffff000) + 0x2000; else - rb_head_ptr = (array_uint32_le(data->config + device->layout->cf_last_log) & 0xfffff000) + 0x2000; + rb_head_ptr = base + (array_uint32_le(data->config + device->layout->cf_last_log) & 0xfffff000) + 0x2000; unsigned int head_dive = 0, tail_dive = 0; @@ -561,13 +626,13 @@ cochran_commander_find_fingerprint(cochran_commander_device_t *device, cochran_d } unsigned int last_profile_idx = (device->layout->rb_logbook_entry_count + head_dive - 1) % device->layout->rb_logbook_entry_count; - unsigned int last_profile_end = array_uint32_le(data->logbook + last_profile_idx * device->layout->rb_logbook_entry_size + device->layout->pt_profile_end); + unsigned int last_profile_end = base + array_uint32_le(data->logbook + last_profile_idx * device->layout->rb_logbook_entry_size + device->layout->pt_profile_end); unsigned int last_profile_pre = 0xFFFFFFFF; if (device->layout->endian == ENDIAN_WORD_BE) - last_profile_pre = array_uint32_word_be(data->config + device->layout->cf_last_log); + last_profile_pre = base + array_uint32_word_be(data->config + device->layout->cf_last_log); else - last_profile_pre = array_uint32_le(data->config + device->layout->cf_last_log); + last_profile_pre = base + array_uint32_le(data->config + device->layout->cf_last_log); if (rb_head_ptr > last_profile_end) profile_capacity_remaining -= rb_head_ptr - last_profile_end; @@ -585,7 +650,7 @@ cochran_commander_find_fingerprint(cochran_commander_device_t *device, cochran_d break; } - unsigned int profile_pre = array_uint32_le(log_entry + device->layout->pt_profile_pre); + unsigned int profile_pre = base + array_uint32_le(log_entry + device->layout->pt_profile_pre); unsigned int sample_size = cochran_commander_profile_size(device, data, idx, profile_pre, last_profile_pre); last_profile_pre = profile_pre; @@ -648,6 +713,9 @@ cochran_commander_device_open (dc_device_t **out, dc_context_t *context, const c unsigned int model = cochran_commander_get_model(device); switch (model) { + case COCHRAN_MODEL_COMMANDER_TM: + device->layout = &cochran_cmdr_tm_device_layout; + break; case COCHRAN_MODEL_COMMANDER_PRE21000: device->layout = &cochran_cmdr_1_device_layout; break; @@ -728,6 +796,7 @@ cochran_commander_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) cochran_commander_device_t *device = (cochran_commander_device_t *) abstract; dc_status_t rc = DC_STATUS_SUCCESS; unsigned char config[1024]; + unsigned int config_size = sizeof(config); unsigned int size = device->layout->rb_profile_end - device->layout->rb_logbook_begin; // Make sure buffer is good. @@ -742,9 +811,12 @@ cochran_commander_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) return DC_STATUS_NOMEMORY; } + if (device->layout->model == COCHRAN_MODEL_COMMANDER_TM) + config_size = 512; + // Determine size for progress dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; - progress.maximum = sizeof(config) + size; + progress.maximum = config_size + size; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); // Emit ID block @@ -753,12 +825,12 @@ cochran_commander_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) vendor.size = sizeof (device->id); device_event_emit (abstract, DC_EVENT_VENDOR, &vendor); - rc = cochran_commander_read_config (device, &progress, config, sizeof(config)); + rc = cochran_commander_read_config (device, &progress, config, config_size); if (rc != DC_STATUS_SUCCESS) return rc; - // Read the sample data, from 0 to sample end will include logbook - rc = cochran_commander_read (device, &progress, device->layout->rb_logbook_begin, dc_buffer_get_data(buffer), device->layout->rb_profile_end); + // Read the sample data, logbook and sample data are contiguous + rc = cochran_commander_read (device, &progress, device->layout->rb_logbook_begin, dc_buffer_get_data(buffer), size); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the sample data."); return rc; @@ -783,6 +855,10 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call unsigned int max_config = sizeof(data.config); unsigned int max_logbook = layout->rb_logbook_end - layout->rb_logbook_begin; unsigned int max_sample = layout->rb_profile_end - layout->rb_profile_begin; + unsigned int base = device->layout->rb_logbook_begin; + + if (device->layout->model == COCHRAN_MODEL_COMMANDER_TM) + max_config = 512; // setup progress indication dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; @@ -797,7 +873,7 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call // Read config dc_status_t rc = DC_STATUS_SUCCESS; - rc = cochran_commander_read_config(device, &progress, data.config, sizeof(data.config)); + rc = cochran_commander_read_config(device, &progress, data.config, max_config); if (rc != DC_STATUS_SUCCESS) return rc; @@ -807,9 +883,11 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call else data.dive_count = array_uint16_be (data.config + layout->cf_dive_count); - if (data.dive_count == 0) + if (data.dive_count == 0) { // No dives to read + WARNING(abstract->context, "This dive computer has no recorded dives."); return DC_STATUS_SUCCESS; + } if (data.dive_count > layout->rb_logbook_entry_count) { data.logbook_size = layout->rb_logbook_entry_count * layout->rb_logbook_entry_size; @@ -829,7 +907,7 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call } // Request log book - rc = cochran_commander_read(device, &progress, 0, data.logbook, data.logbook_size); + rc = cochran_commander_read(device, &progress, layout->rb_logbook_begin, data.logbook, data.logbook_size); if (rc != DC_STATUS_SUCCESS) { status = rc; goto error; @@ -837,6 +915,7 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call // Locate fingerprint, recent dive with invalid profile and calc read size unsigned int profile_read_size = cochran_commander_find_fingerprint(device, &data); + // Update progress indicator with new maximum progress.maximum -= (max_sample - profile_read_size); device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); @@ -870,8 +949,13 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call // Number of dives to read dive_count = (layout->rb_logbook_entry_count + head_dive - tail_dive) % layout->rb_logbook_entry_count; + unsigned int last_start_address = 0; + if (layout->endian == ENDIAN_WORD_BE) + last_start_address = base + array_uint32_word_be(data.config + layout->cf_last_log ); + else + last_start_address = base + array_uint32_le(data.config + layout->cf_last_log ); + // Create the ringbuffer stream. - unsigned int last_start_address = array_uint32_le (data.logbook + ((head_dive - 1) * layout->rb_logbook_entry_size) + layout->pt_profile_end); status = dc_rbstream_new (&rbstream, abstract, 1, layout->rbstream_size, layout->rb_profile_begin, layout->rb_profile_end, last_start_address); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to create the ringbuffer stream."); @@ -886,8 +970,23 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call unsigned char *log_entry = data.logbook + idx * layout->rb_logbook_entry_size; - unsigned int sample_start_address = array_uint32_le (log_entry + layout->pt_profile_begin); - unsigned int sample_end_address = array_uint32_le (log_entry + layout->pt_profile_end); + unsigned int sample_start_address = 0; + unsigned int sample_end_address = 0; + if (layout->model == COCHRAN_MODEL_COMMANDER_TM) { + sample_start_address = base + array_uint24_le (log_entry + layout->pt_profile_begin); + sample_end_address = last_start_address; + // Commander TM has SRAM which seems to randomize when they lose power for too long + // Check for bad entries. + if (sample_start_address < layout->rb_profile_begin || sample_start_address > layout->rb_profile_end || + sample_end_address < layout->rb_profile_begin || sample_end_address > layout->rb_profile_end || + array_uint16_le(log_entry + layout->pt_dive_number) % layout->rb_logbook_entry_count != idx) { + ERROR(abstract->context, "Corrupt dive (%d).", idx); + continue; + } + } else { + sample_start_address = base + array_uint32_le (log_entry + layout->pt_profile_begin); + sample_end_address = base + array_uint32_le (log_entry + layout->pt_profile_end); + } int sample_size = 0, pre_size = 0; diff --git a/src/cochran_commander_parser.c b/src/cochran_commander_parser.c index 5a0ad55..7c76a43 100644 --- a/src/cochran_commander_parser.c +++ b/src/cochran_commander_parser.c @@ -31,11 +31,12 @@ #define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array)) -#define COCHRAN_MODEL_COMMANDER_PRE21000 0 -#define COCHRAN_MODEL_COMMANDER_AIR_NITROX 1 -#define COCHRAN_MODEL_EMC_14 2 -#define COCHRAN_MODEL_EMC_16 3 -#define COCHRAN_MODEL_EMC_20 4 +#define COCHRAN_MODEL_COMMANDER_TM 0 +#define COCHRAN_MODEL_COMMANDER_PRE21000 1 +#define COCHRAN_MODEL_COMMANDER_AIR_NITROX 2 +#define COCHRAN_MODEL_EMC_14 3 +#define COCHRAN_MODEL_EMC_16 4 +#define COCHRAN_MODEL_EMC_20 5 // Cochran time stamps start at Jan 1, 1992 #define COCHRAN_EPOCH 694242000 @@ -43,6 +44,7 @@ #define UNSUPPORTED 0xFFFFFFFF typedef enum cochran_sample_format_t { + SAMPLE_TM, SAMPLE_CMDR, SAMPLE_EMC, } cochran_sample_format_t; @@ -58,6 +60,7 @@ typedef struct cochran_parser_layout_t { cochran_sample_format_t format; unsigned int headersize; unsigned int samplesize; + unsigned int pt_sample_interval; cochran_date_encoding_t date_encoding; unsigned int datetime; unsigned int pt_profile_begin; @@ -113,10 +116,36 @@ static const dc_parser_vtable_t cochran_commander_parser_vtable = { NULL /* destroy */ }; +static const cochran_parser_layout_t cochran_cmdr_tm_parser_layout = { + SAMPLE_TM, // format + 90, // headersize + 1, // samplesize + 72, // pt_sample_interval + DATE_ENCODING_TICKS, // date_encoding + 15, // datetime, 4 bytes + 0, // pt_profile_begin, 4 bytes + UNSUPPORTED, // water_conductivity, 1 byte, 0=low(fresh), 2=high(sea) + 0, // pt_profile_pre, 4 bytes + 83, // start_temp, 1 byte, F + UNSUPPORTED, // start_depth, 2 bytes, /4=ft + 20, // dive_number, 2 bytes + UNSUPPORTED, // altitude, 1 byte, /4=kilofeet + UNSUPPORTED, // pt_profile_end, 4 bytes + UNSUPPORTED, // end_temp, 1 byte F + 57, // divetime, 2 bytes, minutes + 49, // max_depth, 2 bytes, /4=ft + 51, // avg_depth, 2 bytes, /4=ft + 74, // oxygen, 4 bytes (2 of) 2 bytes, /256=% + UNSUPPORTED, // helium, 4 bytes (2 of) 2 bytes, /256=% + 82, // min_temp, 1 byte, /2+20=F + UNSUPPORTED, // max_temp, 1 byte, /2+20=F +}; + static const cochran_parser_layout_t cochran_cmdr_1_parser_layout = { - SAMPLE_CMDR, // type + SAMPLE_CMDR, // format 256, // headersize 2, // samplesize + UNSUPPORTED, // pt_sample_interval DATE_ENCODING_TICKS, // date_encoding 8, // datetime, 4 bytes 0, // pt_profile_begin, 4 bytes @@ -138,9 +167,10 @@ static const cochran_parser_layout_t cochran_cmdr_1_parser_layout = { }; static const cochran_parser_layout_t cochran_cmdr_parser_layout = { - SAMPLE_CMDR, // type + SAMPLE_CMDR, // format 256, // headersize 2, // samplesize + UNSUPPORTED, // pt_sample_interval DATE_ENCODING_MSDHYM, // date_encoding 0, // datetime, 6 bytes 6, // pt_profile_begin, 4 bytes @@ -162,9 +192,10 @@ static const cochran_parser_layout_t cochran_cmdr_parser_layout = { }; static const cochran_parser_layout_t cochran_emc_parser_layout = { - SAMPLE_EMC, // type + SAMPLE_EMC, // format 512, // headersize 3, // samplesize + UNSUPPORTED, // pt_sample_interval DATE_ENCODING_SMHDMY, // date_encoding 0, // datetime, 6 bytes 6, // pt_profile_begin, 4 bytes @@ -197,6 +228,7 @@ static const cochran_events_t cochran_events[] = { {0xC3, 1, SAMPLE_EVENT_OLF, SAMPLE_FLAGS_NONE}, // CNS Oxygen toxicity warning {0xC4, 1, SAMPLE_EVENT_MAXDEPTH, SAMPLE_FLAGS_NONE}, // Depth exceeds user set point {0xC5, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_BEGIN}, // Entered decompression mode + {0xC7, 1, SAMPLE_EVENT_VIOLATION,SAMPLE_FLAGS_BEGIN}, // Entered Gauge mode (e.g. locked out) {0xC8, 1, SAMPLE_EVENT_PO2, SAMPLE_FLAGS_BEGIN}, // PO2 too high {0xCC, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_BEGIN}, // Low Cylinder 1 pressure {0xCE, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_BEGIN}, // Non-decompression warning @@ -334,6 +366,11 @@ cochran_commander_parser_create (dc_parser_t **out, dc_context_t *context, unsig parser->model = model; switch (model) { + case COCHRAN_MODEL_COMMANDER_TM: + parser->layout = &cochran_cmdr_tm_parser_layout; + parser->events = NULL; // No inter-dive events on this model + parser->nevents = 0; + break; case COCHRAN_MODEL_COMMANDER_PRE21000: parser->layout = &cochran_cmdr_1_parser_layout; parser->events = cochran_cmdr_event_bytes; @@ -440,6 +477,8 @@ cochran_commander_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, *((unsigned int*) value) = (data[layout->min_temp] / 2.0 + 20 - 32) / 1.8; break; case DC_FIELD_TEMPERATURE_MAXIMUM: + if (layout->max_temp == UNSUPPORTED) + return DC_STATUS_UNSUPPORTED; if (data[layout->max_temp] == 0xFF) return DC_STATUS_UNSUPPORTED; *((unsigned int*) value) = (data[layout->max_temp] / 2.0 + 20 - 32) / 1.8; @@ -484,6 +523,8 @@ cochran_commander_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, // for density assume // 0 = 1000kg/m³, 2 = 1025kg/m³ // and other values are linear + if (layout->water_conductivity == UNSUPPORTED) + return DC_STATUS_UNSUPPORTED; if ((data[layout->water_conductivity] & 0x3) == 0) water->type = DC_WATER_FRESH; else @@ -493,6 +534,8 @@ cochran_commander_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, case DC_FIELD_ATMOSPHERIC: // Cochran measures air pressure and stores it as altitude. // Convert altitude (measured in 1/4 kilofeet) back to pressure. + if (layout->altitude == UNSUPPORTED) + return DC_STATUS_UNSUPPORTED; *(double *) value = ATM / BAR * pow(1 - 0.0000225577 * data[layout->altitude] * 250.0 * FEET, 5.25588); break; default: @@ -504,8 +547,114 @@ cochran_commander_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, } +/* + * Parse early Commander computers + */ static dc_status_t -cochran_commander_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +cochran_commander_parser_samples_foreach_tm (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +{ + cochran_commander_parser_t *parser = (cochran_commander_parser_t *) abstract; + const cochran_parser_layout_t *layout = parser->layout; + const unsigned char *data = abstract->data; + const unsigned char *samples = data + layout->headersize; + + if (abstract->size < layout->headersize) + return DC_STATUS_DATAFORMAT; + + unsigned int size = abstract->size - layout->headersize; + unsigned int sample_interval = data[layout->pt_sample_interval]; + + dc_sample_value_t sample = {0}; + unsigned int time = 0, last_sample_time = 0; + unsigned int offset = 2; + unsigned int deco_ceiling = 0; + + unsigned int temp = samples[0]; // Half degrees F + unsigned int depth = samples[1]; // Half feet + + last_sample_time = sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + + sample.depth = (depth / 2.0) * FEET; + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + + sample.temperature = (temp / 2.0 - 32.0) / 1.8; + if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); + + sample.gasmix = 0; + if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata); + + while (offset < size) { + const unsigned char *s = samples + offset; + + sample.time = time; + if (last_sample_time != sample.time) { + // We haven't issued this time yet. + last_sample_time = sample.time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + } + + if (*s & 0x80) { + // Event or temperate change byte + if (*s & 0x60) { + // Event byte + switch (*s) { + case 0xC5: // Deco obligation begins + break; + case 0xD8: // Deco obligation ends + break; + case 0xAB: // Decrement ceiling (deeper) + deco_ceiling += 10; // feet + + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.time = 60; // We don't know the duration + sample.deco.depth = deco_ceiling * FEET; + if (callback) callback(DC_SAMPLE_DECO, sample, userdata); + break; + case 0xAD: // Increment ceiling (shallower) + deco_ceiling -= 10; // feet + + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.depth = deco_ceiling * FEET; + sample.deco.time = 60; // We don't know the duration + if (callback) callback(DC_SAMPLE_DECO, sample, userdata); + break; + } + } else { + // Temp change + if (*s & 0x10) + temp -= (*s & 0x0f); + else + temp += (*s & 0x0f); + sample.temperature = (temp / 2.0 - 32.0) / 1.8; + if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); + } + + offset++; + continue; + } + + // Depth sample + if (s[0] & 0x40) + depth -= s[0] & 0x3f; + else + depth += s[0] & 0x3f; + + sample.depth = (depth / 2.0) * FEET; + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + + offset++; + time += sample_interval; + } + return DC_STATUS_SUCCESS; +} + + +/* + * Parse Commander I (Pre-21000 s/n), II and EMC computers + */ +static dc_status_t +cochran_commander_parser_samples_foreach_emc (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) { cochran_commander_parser_t *parser = (cochran_commander_parser_t *) abstract; const cochran_parser_layout_t *layout = parser->layout; @@ -720,3 +869,15 @@ cochran_commander_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb return DC_STATUS_SUCCESS; } + + +static dc_status_t +cochran_commander_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +{ + cochran_commander_parser_t *parser = (cochran_commander_parser_t *) abstract; + + if (parser->model == COCHRAN_MODEL_COMMANDER_TM) + return cochran_commander_parser_samples_foreach_tm (abstract, callback, userdata); + else + return cochran_commander_parser_samples_foreach_emc (abstract, callback, userdata); +} diff --git a/src/descriptor.c b/src/descriptor.c index 633debd..8ae0842 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -314,11 +314,12 @@ static const dc_descriptor_t g_descriptors[] = { {"DiveSystem", "iDive2 Deep", DC_FAMILY_DIVESYSTEM_IDIVE, 0x44}, {"DiveSystem", "iDive2 Tech+", DC_FAMILY_DIVESYSTEM_IDIVE, 0x45}, /* Cochran Commander */ - {"Cochran", "Commander I", DC_FAMILY_COCHRAN_COMMANDER, 0}, - {"Cochran", "Commander II", DC_FAMILY_COCHRAN_COMMANDER, 1}, - {"Cochran", "EMC-14", DC_FAMILY_COCHRAN_COMMANDER, 2}, - {"Cochran", "EMC-16", DC_FAMILY_COCHRAN_COMMANDER, 3}, - {"Cochran", "EMC-20H", DC_FAMILY_COCHRAN_COMMANDER, 4}, + {"Cochran", "Commander TM", DC_FAMILY_COCHRAN_COMMANDER, 0}, + {"Cochran", "Commander I", DC_FAMILY_COCHRAN_COMMANDER, 1}, + {"Cochran", "Commander II", DC_FAMILY_COCHRAN_COMMANDER, 2}, + {"Cochran", "EMC-14", DC_FAMILY_COCHRAN_COMMANDER, 3}, + {"Cochran", "EMC-16", DC_FAMILY_COCHRAN_COMMANDER, 4}, + {"Cochran", "EMC-20H", DC_FAMILY_COCHRAN_COMMANDER, 5}, }; typedef struct dc_descriptor_iterator_t { From adef3f67fce8a14febc9f608f22a929cf65311ac Mon Sep 17 00:00:00 2001 From: John Van Ostrand Date: Sun, 13 Aug 2017 11:24:45 -0400 Subject: [PATCH 09/25] Add three event codes I found three previously undiscovered event codes. --- src/cochran_commander_parser.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cochran_commander_parser.c b/src/cochran_commander_parser.c index 7c76a43..9d42a44 100644 --- a/src/cochran_commander_parser.c +++ b/src/cochran_commander_parser.c @@ -221,7 +221,9 @@ static const cochran_events_t cochran_events[] = { {0xA9, 1, SAMPLE_EVENT_SURFACE, SAMPLE_FLAGS_END}, // Exited PDI mode {0xAB, 5, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE}, // Ceiling decrease {0xAD, 5, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE}, // Ceiling increase + {0xB5, 1, SAMPLE_EVENT_AIRTIME, SAMPLE_FLAGS_BEGIN}, // Air < 5 mins deco {0xBD, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE}, // Switched to nomal PO2 setting + {0xBE, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE}, // Ceiling > 60 ft {0xC0, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE}, // Switched to FO2 21% mode {0xC1, 1, SAMPLE_EVENT_ASCENT, SAMPLE_FLAGS_BEGIN}, // Ascent rate greater than limit {0xC2, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE}, // Low battery warning @@ -232,6 +234,7 @@ static const cochran_events_t cochran_events[] = { {0xC8, 1, SAMPLE_EVENT_PO2, SAMPLE_FLAGS_BEGIN}, // PO2 too high {0xCC, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_BEGIN}, // Low Cylinder 1 pressure {0xCE, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_BEGIN}, // Non-decompression warning + {0xCF, 1, SAMPLE_EVENT_OLF, SAMPLE_FLAGS_BEGIN}, // O2 Toxicity {0xCD, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE}, // Switched to deco blend {0xD0, 1, SAMPLE_EVENT_WORKLOAD, SAMPLE_FLAGS_BEGIN}, // Breathing rate alarm {0xD3, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE}, // Low gas 1 flow rate From 3f1131f80fe67f849dd22f35104c536675a5e7c4 Mon Sep 17 00:00:00 2001 From: John Van Ostrand Date: Mon, 14 Aug 2017 16:37:39 -0400 Subject: [PATCH 10/25] Fix profile buffer size and address size I dived the model enough to wrap the profile buffer and I was wrong about where the end was. Also, the buffer starts 3 bytes after where it could. We were treating profile pointers as 4 bytes when they are two bytes. This worked most of the time when short tissues were clear (tissue load follows the pointer). --- src/cochran_commander.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/cochran_commander.c b/src/cochran_commander.c index 50422fb..16707bf 100644 --- a/src/cochran_commander.c +++ b/src/cochran_commander.c @@ -129,11 +129,11 @@ static const cochran_device_layout_t cochran_cmdr_tm_device_layout = { 0xffffff, // cf_last_interdive 0x15c, // cf_serial_number 0x010000, // rb_logbook_begin - 0x012328, // rb_logbook_end + 0x01232b, // rb_logbook_end 90, // rb_logbook_entry_size 100, // rb_logbook_entry_count - 0x012328, // rb_profile_begin - 0x020000, // rb_profile_end + 0x01232b, // rb_profile_begin + 0x018000, // rb_profile_end 15, // pt_fingerprint 4, // fingerprint_size 0, // pt_profile_pre @@ -608,7 +608,7 @@ cochran_commander_find_fingerprint(cochran_commander_device_t *device, cochran_d unsigned int rb_head_ptr = 0; if (device->layout->model == COCHRAN_MODEL_COMMANDER_TM) // TM uses SRAM and does not need to erase pages - rb_head_ptr = base + array_uint32_word_be(data->config + device->layout->cf_last_log); + rb_head_ptr = base + array_uint16_be(data->config + device->layout->cf_last_log); else if (device->layout->endian == ENDIAN_WORD_BE) rb_head_ptr = base + (array_uint32_word_be(data->config + device->layout->cf_last_log) & 0xfffff000) + 0x2000; else @@ -626,9 +626,16 @@ cochran_commander_find_fingerprint(cochran_commander_device_t *device, cochran_d } unsigned int last_profile_idx = (device->layout->rb_logbook_entry_count + head_dive - 1) % device->layout->rb_logbook_entry_count; - unsigned int last_profile_end = base + array_uint32_le(data->logbook + last_profile_idx * device->layout->rb_logbook_entry_size + device->layout->pt_profile_end); - unsigned int last_profile_pre = 0xFFFFFFFF; + unsigned int last_profile_end = 0; + if (device->layout->model == COCHRAN_MODEL_COMMANDER_TM) + // There is no end pointer in this model and no inter-dive + // events. We could use profile_begin from the next dive but + // since this is the last dive, we'll use rb_head_ptr + last_profile_end = rb_head_ptr; + else + last_profile_end = base + array_uint32_le(data->logbook + last_profile_idx * device->layout->rb_logbook_entry_size + device->layout->pt_profile_end); + unsigned int last_profile_pre = 0xFFFFFFFF; if (device->layout->endian == ENDIAN_WORD_BE) last_profile_pre = base + array_uint32_word_be(data->config + device->layout->cf_last_log); else @@ -650,7 +657,11 @@ cochran_commander_find_fingerprint(cochran_commander_device_t *device, cochran_d break; } - unsigned int profile_pre = base + array_uint32_le(log_entry + device->layout->pt_profile_pre); + unsigned int profile_pre = 0; + if (device->layout->model == COCHRAN_MODEL_COMMANDER_TM) + profile_pre = base + array_uint16_le(log_entry + device->layout->pt_profile_pre); + else + profile_pre = base + array_uint32_le(log_entry + device->layout->pt_profile_pre); unsigned int sample_size = cochran_commander_profile_size(device, data, idx, profile_pre, last_profile_pre); last_profile_pre = profile_pre; @@ -973,7 +984,7 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call unsigned int sample_start_address = 0; unsigned int sample_end_address = 0; if (layout->model == COCHRAN_MODEL_COMMANDER_TM) { - sample_start_address = base + array_uint24_le (log_entry + layout->pt_profile_begin); + sample_start_address = base + array_uint16_le (log_entry + layout->pt_profile_begin); sample_end_address = last_start_address; // Commander TM has SRAM which seems to randomize when they lose power for too long // Check for bad entries. From cdf7e8e635bc31db4684776cf49c6d07cfb1bc70 Mon Sep 17 00:00:00 2001 From: John Van Ostrand Date: Mon, 14 Aug 2017 16:37:40 -0400 Subject: [PATCH 11/25] Add event handling to TM model For some reason I had never added event processing to this model. --- src/cochran_commander_parser.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cochran_commander_parser.c b/src/cochran_commander_parser.c index 9d42a44..33f3b28 100644 --- a/src/cochran_commander_parser.c +++ b/src/cochran_commander_parser.c @@ -622,6 +622,9 @@ cochran_commander_parser_samples_foreach_tm (dc_parser_t *abstract, dc_sample_ca sample.deco.time = 60; // We don't know the duration if (callback) callback(DC_SAMPLE_DECO, sample, userdata); break; + default: + cochran_commander_handle_event(parser, s[0], callback, userdata); + break; } } else { // Temp change From f7bcbb5828646f319487c084749cbc63f6b89ba9 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 11 Aug 2017 21:04:46 +0200 Subject: [PATCH 12/25] Add support for the Aqualung i200 --- src/descriptor.c | 1 + src/oceanic_atom2.c | 1 + src/oceanic_atom2_parser.c | 18 ++++++++++++++---- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index 8ae0842..54959b0 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -218,6 +218,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Aqualung", "i750TC", DC_FAMILY_OCEANIC_ATOM2, 0x455A}, {"Aqualung", "i450T", DC_FAMILY_OCEANIC_ATOM2, 0x4641}, {"Aqualung", "i550", DC_FAMILY_OCEANIC_ATOM2, 0x4642}, + {"Aqualung", "i200", DC_FAMILY_OCEANIC_ATOM2, 0x4646}, /* Mares Nemo */ {"Mares", "Nemo", DC_FAMILY_MARES_NEMO, 0}, {"Mares", "Nemo Steel", DC_FAMILY_MARES_NEMO, 0}, diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index da19e7f..d9e8d5a 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -107,6 +107,7 @@ static const oceanic_common_version_t oceanic_atom2a_version[] = { {"PROPLUS2 \0\0 512K"}, {"OCEGEO20 \0\0 512K"}, {"OCE GEO R\0\0 512K"}, + {"AQUAI200 \0\0 512K"}, }; static const oceanic_common_version_t oceanic_atom2b_version[] = { diff --git a/src/oceanic_atom2_parser.c b/src/oceanic_atom2_parser.c index d63b568..9133984 100644 --- a/src/oceanic_atom2_parser.c +++ b/src/oceanic_atom2_parser.c @@ -84,6 +84,7 @@ #define I750TC 0x455A #define I450T 0x4641 #define I550 0x4642 +#define I200 0x4646 #define NORMAL 0 #define GAUGE 1 @@ -154,7 +155,8 @@ oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, unsigned model == OCS || model == PROPLUS3 || model == A300 || model == MANTA || model == INSIGHT2 || model == ZEN || - model == I300 || model == I550) { + model == I300 || model == I550 || + model == I200) { parser->headersize -= PAGESIZE; } else if (model == VT4 || model == VT41) { parser->headersize += PAGESIZE; @@ -266,6 +268,7 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim case COMPUMASK: case INSIGHT2: case I300: + case I200: datetime->year = ((p[3] & 0xE0) >> 1) + (p[4] & 0x0F) + 2000; datetime->month = (p[4] & 0xF0) >> 4; datetime->day = p[3] & 0x1F; @@ -685,7 +688,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == ELEMENT2 || parser->model == VEO20 || parser->model == A300 || parser->model == ZEN || parser->model == GEO || parser->model == GEO20 || - parser->model == MANTA || parser->model == I300) { + parser->model == MANTA || parser->model == I300 || + parser->model == I200) { have_pressure = 0; } @@ -839,7 +843,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == VEO30 || parser->model == OC1A || parser->model == OC1B || parser->model == OC1C || parser->model == OCI || parser->model == A300 || - parser->model == I450T || parser->model == I300) { + parser->model == I450T || parser->model == I300 || + parser->model == I200) { temperature = data[offset + 3]; } else if (parser->model == OCS || parser->model == TX1) { temperature = data[offset + 1]; @@ -906,7 +911,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == VEO30 || parser->model == OC1A || parser->model == OC1B || parser->model == OC1C || parser->model == OCI || parser->model == A300 || - parser->model == I450T || parser->model == I300) + parser->model == I450T || parser->model == I300 || + parser->model == I200) depth = (data[offset + 4] + (data[offset + 5] << 8)) & 0x0FFF; else if (parser->model == ATOM1) depth = data[offset + 3] * 16; @@ -953,6 +959,10 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ decostop = (data[offset + 5] & 0xF0) >> 4; decotime = array_uint16_le(data + offset + 4) & 0x03FF; have_deco = 1; + } else if (parser->model == I200) { + decostop = (data[offset + 7] & 0xF0) >> 4; + decotime = array_uint16_le(data + offset + 6) & 0x0FFF; + have_deco = 1; } if (have_deco) { if (decostop) { From 2813d61f65561ac72cb336aab7bffd085ba5dc9c Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 9 Aug 2017 10:33:21 +0200 Subject: [PATCH 13/25] Locate the most recent dive using the logbook pointers I received a bug report from a device which failed to download new dives after a reset (probably caused by an empty battery). This reset appears to reset the internal dive counter back to zero, and also resets the write pointer back to the begin of the logbook ringbuffer, but leaves the existing logbook entries in place. The result is that the logic to find the most recent dive based on the highest internal dive counter, will be wrong because it finds those old entries. The discovery of the logbook (and profile) write pointers eliminates the need to search for the most recent logbook entry. --- src/cressi_leonardo.c | 47 ++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/src/cressi_leonardo.c b/src/cressi_leonardo.c index 5c1dd29..c354b22 100644 --- a/src/cressi_leonardo.c +++ b/src/cressi_leonardo.c @@ -448,32 +448,25 @@ cressi_leonardo_extract_dives (dc_device_t *abstract, const unsigned char data[] if (size < SZ_MEMORY) return DC_STATUS_DATAFORMAT; - // Locate the most recent dive. - // 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) { - unsigned int offset = RB_LOGBOOK_BEGIN + i * RB_LOGBOOK_SIZE; + // Get the number of dives. + //unsigned int ndives = array_uint16_le(data + 0x62); - // Ignore uninitialized header entries. - if (array_isequal (data + offset, RB_LOGBOOK_SIZE, 0xFF)) - break; + // Get the logbook pointer. + unsigned int last = array_uint16_le(data + 0x64); + if (last < RB_LOGBOOK_BEGIN || last > RB_LOGBOOK_END || + ((last - RB_LOGBOOK_BEGIN) % RB_LOGBOOK_SIZE) != 0) { + ERROR (context, "Invalid logbook pointer (0x%04x).", last); + return DC_STATUS_DATAFORMAT; + } - // Get the internal dive number. - unsigned int current = array_uint16_le (data + offset); - if (current == 0xFFFF) { - WARNING (context, "Unexpected internal dive number found."); - break; - } - if (current > maximum) { - maximum = current; - latest = i; - } + // Convert to an index. + unsigned int latest = (last - RB_LOGBOOK_BEGIN) / RB_LOGBOOK_SIZE; - count++; + // Get the profile pointer. + unsigned int eop = array_uint16_le(data + 0x66); + if (eop < RB_PROFILE_BEGIN || last > RB_PROFILE_END) { + ERROR (context, "Invalid profile pointer (0x%04x).", eop); + return DC_STATUS_DATAFORMAT; } unsigned char *buffer = (unsigned char *) malloc (RB_LOGBOOK_SIZE + RB_PROFILE_END - RB_PROFILE_BEGIN); @@ -482,12 +475,16 @@ cressi_leonardo_extract_dives (dc_device_t *abstract, const unsigned char data[] return DC_STATUS_NOMEMORY; } - unsigned int previous = 0; + unsigned int previous = eop; unsigned int remaining = RB_PROFILE_END - RB_PROFILE_BEGIN; - for (unsigned int i = 0; i < count; ++i) { + for (unsigned int i = 0; i < RB_LOGBOOK_COUNT; ++i) { unsigned int idx = (latest + RB_LOGBOOK_COUNT - i) % RB_LOGBOOK_COUNT; unsigned int offset = RB_LOGBOOK_BEGIN + idx * RB_LOGBOOK_SIZE; + // Ignore uninitialized header entries. + if (array_isequal (data + offset, RB_LOGBOOK_SIZE, 0xFF)) + break; + // Get the ringbuffer pointers. unsigned int header = array_uint16_le (data + offset + 2); unsigned int footer = array_uint16_le (data + offset + 4); From 133b7394c29f3093d3761c33a065f556428f66fa Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 15 Aug 2017 21:34:51 +0200 Subject: [PATCH 14/25] Rename the OSTC 3+ to OSTC Plus The correct name for the OSTC 3+ is OSTC Plus nowadays. Allthough the exact name doesn't really matter because all OSTC3 based models are compatible, using the correct name should reduce confusing for end-users. --- src/descriptor.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index 54959b0..5a0f92e 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -256,8 +256,8 @@ static const dc_descriptor_t g_descriptors[] = { {"Heinrichs Weikamp", "OSTC 2", DC_FAMILY_HW_OSTC3, 0x13}, {"Heinrichs Weikamp", "OSTC 2", DC_FAMILY_HW_OSTC3, 0x1B}, {"Heinrichs Weikamp", "OSTC 3", DC_FAMILY_HW_OSTC3, 0x0A}, - {"Heinrichs Weikamp", "OSTC 3+", DC_FAMILY_HW_OSTC3, 0x13}, - {"Heinrichs Weikamp", "OSTC 3+", DC_FAMILY_HW_OSTC3, 0x1A}, + {"Heinrichs Weikamp", "OSTC Plus", DC_FAMILY_HW_OSTC3, 0x13}, + {"Heinrichs Weikamp", "OSTC Plus", DC_FAMILY_HW_OSTC3, 0x1A}, {"Heinrichs Weikamp", "OSTC 4", DC_FAMILY_HW_OSTC3, 0x3B}, {"Heinrichs Weikamp", "OSTC cR", DC_FAMILY_HW_OSTC3, 0x05}, {"Heinrichs Weikamp", "OSTC cR", DC_FAMILY_HW_OSTC3, 0x07}, From 722160cd48df56ac37ac26366b6a3ab31faa272b Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 16 Aug 2017 20:14:47 +0200 Subject: [PATCH 15/25] Fix the atmospheric pressure for the iX3M The iX3M appears to store the atmosperic pressure in units of 1/10 millibar instead of 1 millibar. --- src/divesystem_idive_parser.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/divesystem_idive_parser.c b/src/divesystem_idive_parser.c index 473dc57..a27aa7f 100644 --- a/src/divesystem_idive_parser.c +++ b/src/divesystem_idive_parser.c @@ -191,7 +191,11 @@ divesystem_idive_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; break; case DC_FIELD_ATMOSPHERIC: - *((double *) value) = array_uint16_le (data + 11) / 1000.0; + if (parser->model >= IX3M_EASY) { + *((double *) value) = array_uint16_le (data + 11) / 10000.0; + } else { + *((double *) value) = array_uint16_le (data + 11) / 1000.0; + } break; case DC_FIELD_DIVEMODE: if (parser->divemode == 0xFFFFFFFF) From 854ad13f1613a0923ba374ddadae22811926221b Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 16 Aug 2017 20:38:47 +0200 Subject: [PATCH 16/25] Implement the salinity field --- src/divesystem_idive_parser.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/divesystem_idive_parser.c b/src/divesystem_idive_parser.c index a27aa7f..ff9f480 100644 --- a/src/divesystem_idive_parser.c +++ b/src/divesystem_idive_parser.c @@ -173,6 +173,7 @@ divesystem_idive_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, } dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + dc_salinity_t *water = (dc_salinity_t *) value; if (value) { switch (type) { @@ -197,6 +198,10 @@ divesystem_idive_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, *((double *) value) = array_uint16_le (data + 11) / 1000.0; } break; + case DC_FIELD_SALINITY: + water->type = data[34] == 0 ? DC_WATER_SALT : DC_WATER_FRESH; + water->density = 0.0; + break; case DC_FIELD_DIVEMODE: if (parser->divemode == 0xFFFFFFFF) return DC_STATUS_UNSUPPORTED; From 05c858bf96028508037afdabf53303f7d2571d62 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 11 Jul 2017 00:15:39 +0200 Subject: [PATCH 17/25] Fix compatibility issue with hidapi The hidapi library requires that the first byte contains the report ID. For devices which support only a single report, the report ID byte should be zero. The remaining bytes contain the actual report data. Now, when hidapi uses libusb internally, it strips the zero report ID byte again before passing the data to libusb. Thus in order to remain compatible with the hidapi based implementation, our libusb based implementation should do the same. --- src/usbhid.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/usbhid.c b/src/usbhid.c index 0a93824..b99cf21 100644 --- a/src/usbhid.c +++ b/src/usbhid.c @@ -391,14 +391,32 @@ dc_usbhid_write (dc_usbhid_t *usbhid, const void *data, size_t size, size_t *act goto out_invalidargs; } + if (size == 0) { + goto out; + } + #if defined(HAVE_LIBUSB) && !defined(__APPLE__) - int rc = libusb_interrupt_transfer (usbhid->handle, usbhid->endpoint_out, (void *) data, size, &nbytes, 0); + const unsigned char *buffer = (const unsigned char *) data; + size_t length = size; + + // Skip a report id of zero. + unsigned char report = buffer[0]; + if (report == 0) { + buffer++; + length--; + } + + int rc = libusb_interrupt_transfer (usbhid->handle, usbhid->endpoint_out, (void *) buffer, length, &nbytes, 0); if (rc != LIBUSB_SUCCESS) { ERROR (usbhid->context, "Usb write interrupt transfer failed (%s).", libusb_error_name (rc)); status = syserror (rc); goto out; } + + if (report == 0) { + nbytes++; + } #elif defined(HAVE_HIDAPI) nbytes = hid_write(usbhid->handle, data, size); if (nbytes < 0) { From d251b373beccfe01eac6ba71a7f22a0ee13b09b3 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 11 Jul 2017 00:17:36 +0200 Subject: [PATCH 18/25] Add a zero report ID to the commands The zero report ID byte is required when using the hidapi library. We just never noticed this problem before, because we use libusb by default, and libusb doesn't need the extra zero byte. --- src/uwatec_g2.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/uwatec_g2.c b/src/uwatec_g2.c index 3328b4c..fc32aff 100644 --- a/src/uwatec_g2.c +++ b/src/uwatec_g2.c @@ -110,16 +110,17 @@ uwatec_g2_transfer (uwatec_g2_device_t *device, const unsigned char command[], u dc_status_t status = DC_STATUS_SUCCESS; size_t transferred = 0; - if (csize >= PACKET_SIZE) { + if (csize + 2 > PACKET_SIZE) { ERROR (device->base.context, "command too big (%d)", csize); return DC_STATUS_INVALIDARGS; } HEXDUMP (device->base.context, DC_LOGLEVEL_DEBUG, "cmd", command, csize); - buf[0] = csize; - memcpy(buf + 1, command, csize); - status = dc_usbhid_write (device->usbhid, buf, csize + 1, &transferred); + buf[0] = 0; + buf[1] = csize; + memcpy(buf + 2, command, csize); + status = dc_usbhid_write (device->usbhid, buf, csize + 2, &transferred); if (status != DC_STATUS_SUCCESS) { ERROR (device->base.context, "Failed to send the command."); return status; From b82d5fcfff88c16e3057bfd48cf2c312e1460944 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 11 Jul 2017 09:52:40 +0200 Subject: [PATCH 19/25] Reset the number of bytes to zero on error The hidapi read and write functions return a negative value if an error occurs. Those negative values should not be returned to the caller as the actual number of bytes (or used in the logging). The value is reset to zero instead. --- src/usbhid.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/usbhid.c b/src/usbhid.c index b99cf21..99df918 100644 --- a/src/usbhid.c +++ b/src/usbhid.c @@ -362,6 +362,7 @@ dc_usbhid_read (dc_usbhid_t *usbhid, void *data, size_t size, size_t *actual) if (nbytes < 0) { ERROR (usbhid->context, "Usb read interrupt transfer failed."); status = DC_STATUS_IO; + nbytes = 0; goto out; } #endif @@ -422,6 +423,7 @@ dc_usbhid_write (dc_usbhid_t *usbhid, const void *data, size_t size, size_t *act if (nbytes < 0) { ERROR (usbhid->context, "Usb write interrupt transfer failed."); status = DC_STATUS_IO; + nbytes = 0; goto out; } #endif From c9ed92d3f55c259931527a27d018eb5791a176dd Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 11 Jul 2017 11:16:15 +0200 Subject: [PATCH 20/25] Workaround for a Windows hidapi issue The Windows HID api always expects to receive a fixed size buffer (corresponding to the largest report supported by the device). Therefore the hidapi library internally pads the buffer with zeros to the expected size, but apparently it also returns the size of the padded buffer! As a workaround the number of bytes is limited to the actual size. --- src/usbhid.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/usbhid.c b/src/usbhid.c index 99df918..7894f06 100644 --- a/src/usbhid.c +++ b/src/usbhid.c @@ -426,6 +426,12 @@ dc_usbhid_write (dc_usbhid_t *usbhid, const void *data, size_t size, size_t *act nbytes = 0; goto out; } + +#ifdef _WIN32 + if (nbytes > size) { + nbytes = size; + } +#endif #endif out: From eea02126a41edc42324c5dcaa628ea4d1394441a Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 21 Jul 2017 00:48:04 +0200 Subject: [PATCH 21/25] Use hidapi as the default USB HID library On Windows, the hidapi library uses the standard Microsoft USB HID driver, while libusb requires the installation of a different driver (WinUSB or libusbK). But installing one of the libusb drivers breaks compatibility with other applications using hidapi (Scubapro LogTRAK and Suunto DM5) because only one driver can be active. Switching libdivecomputer to hidapi avoids this problem. On Linux, the hidapi library doesn't seem to offer any advantages over libusb. Most distributions don't even have the hidapi library installed by default. Because there are usually two variants of the hidapi library available on Linux (hidapi-libusb and hidapi-hidraw), the autotools build system won't be able to detect it out-of-the-box, and will automatically fallback to the libusb implementation. On Mac OS X, hidapi is already the default (and also the only option). --- src/descriptor.c | 4 ++-- src/usbhid.c | 42 ++++++++++++++++++++++++------------------ 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index 5a0f92e..87311e2 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -23,9 +23,9 @@ #include "config.h" #endif -#if defined(HAVE_LIBUSB) && !defined(__APPLE__) +#if defined(HAVE_HIDAPI) #define USBHID -#elif defined(HAVE_HIDAPI) +#elif defined(HAVE_LIBUSB) && !defined(__APPLE__) #define USBHID #endif diff --git a/src/usbhid.c b/src/usbhid.c index 7894f06..b697f47 100644 --- a/src/usbhid.c +++ b/src/usbhid.c @@ -25,14 +25,20 @@ #include -#if defined(HAVE_LIBUSB) && !defined(__APPLE__) +#if defined(HAVE_HIDAPI) +#define USE_HIDAPI #define USBHID +#elif defined(HAVE_LIBUSB) && !defined(__APPLE__) +#define USE_LIBUSB +#define USBHID +#endif + +#if defined(USE_LIBUSB) #ifdef _WIN32 #define NOGDI #endif #include -#elif defined(HAVE_HIDAPI) -#define USBHID +#elif defined(USE_HIDAPI) #include #endif @@ -44,20 +50,20 @@ struct dc_usbhid_t { /* Library context. */ dc_context_t *context; /* Internal state. */ -#if defined(HAVE_LIBUSB) && !defined(__APPLE__) +#if defined(USE_LIBUSB) libusb_context *ctx; libusb_device_handle *handle; int interface; unsigned char endpoint_in; unsigned char endpoint_out; unsigned int timeout; -#elif defined(HAVE_HIDAPI) +#elif defined(USE_HIDAPI) hid_device *handle; int timeout; #endif }; -#if defined(HAVE_LIBUSB) && !defined(__APPLE__) +#if defined(USE_LIBUSB) static dc_status_t syserror(int errcode) { @@ -103,7 +109,7 @@ dc_usbhid_open (dc_usbhid_t **out, dc_context_t *context, unsigned int vid, unsi // Library context. usbhid->context = context; -#if defined(HAVE_LIBUSB) && !defined(__APPLE__) +#if defined(USE_LIBUSB) struct libusb_device **devices = NULL; struct libusb_config_descriptor *config = NULL; @@ -235,7 +241,7 @@ dc_usbhid_open (dc_usbhid_t **out, dc_context_t *context, unsigned int vid, unsi libusb_free_config_descriptor (config); libusb_free_device_list (devices, 1); -#elif defined(HAVE_HIDAPI) +#elif defined(USE_HIDAPI) // Initialize the hidapi library. rc = hid_init(); @@ -260,7 +266,7 @@ dc_usbhid_open (dc_usbhid_t **out, dc_context_t *context, unsigned int vid, unsi return DC_STATUS_SUCCESS; -#if defined(HAVE_LIBUSB) && !defined(__APPLE__) +#if defined(USE_LIBUSB) error_usb_close: libusb_close (usbhid->handle); error_usb_free_config: @@ -269,7 +275,7 @@ error_usb_free_list: libusb_free_device_list (devices, 1); error_usb_exit: libusb_exit (usbhid->ctx); -#elif defined(HAVE_HIDAPI) +#elif defined(USE_HIDAPI) error_hid_exit: hid_exit (); #endif @@ -290,11 +296,11 @@ dc_usbhid_close (dc_usbhid_t *usbhid) if (usbhid == NULL) return DC_STATUS_SUCCESS; -#if defined(HAVE_LIBUSB) && !defined(__APPLE__) +#if defined(USE_LIBUSB) libusb_release_interface (usbhid->handle, usbhid->interface); libusb_close (usbhid->handle); libusb_exit (usbhid->ctx); -#elif defined(HAVE_HIDAPI) +#elif defined(USE_HIDAPI) hid_close(usbhid->handle); hid_exit(); #endif @@ -315,7 +321,7 @@ dc_usbhid_set_timeout (dc_usbhid_t *usbhid, int timeout) INFO (usbhid->context, "Timeout: value=%i", timeout); -#if defined(HAVE_LIBUSB) && !defined(__APPLE__) +#if defined(USE_LIBUSB) if (timeout < 0) { usbhid->timeout = 0; } else if (timeout == 0) { @@ -323,7 +329,7 @@ dc_usbhid_set_timeout (dc_usbhid_t *usbhid, int timeout) } else { usbhid->timeout = timeout; } -#elif defined(HAVE_HIDAPI) +#elif defined(USE_HIDAPI) if (timeout < 0) { usbhid->timeout = -1; } else { @@ -349,7 +355,7 @@ dc_usbhid_read (dc_usbhid_t *usbhid, void *data, size_t size, size_t *actual) goto out_invalidargs; } -#if defined(HAVE_LIBUSB) && !defined(__APPLE__) +#if defined(USE_LIBUSB) int rc = libusb_interrupt_transfer (usbhid->handle, usbhid->endpoint_in, data, size, &nbytes, usbhid->timeout); if (rc != LIBUSB_SUCCESS) { ERROR (usbhid->context, "Usb read interrupt transfer failed (%s).", @@ -357,7 +363,7 @@ dc_usbhid_read (dc_usbhid_t *usbhid, void *data, size_t size, size_t *actual) status = syserror (rc); goto out; } -#elif defined(HAVE_HIDAPI) +#elif defined(USE_HIDAPI) nbytes = hid_read_timeout(usbhid->handle, data, size, usbhid->timeout); if (nbytes < 0) { ERROR (usbhid->context, "Usb read interrupt transfer failed."); @@ -396,7 +402,7 @@ dc_usbhid_write (dc_usbhid_t *usbhid, const void *data, size_t size, size_t *act goto out; } -#if defined(HAVE_LIBUSB) && !defined(__APPLE__) +#if defined(USE_LIBUSB) const unsigned char *buffer = (const unsigned char *) data; size_t length = size; @@ -418,7 +424,7 @@ dc_usbhid_write (dc_usbhid_t *usbhid, const void *data, size_t size, size_t *act if (report == 0) { nbytes++; } -#elif defined(HAVE_HIDAPI) +#elif defined(USE_HIDAPI) nbytes = hid_write(usbhid->handle, data, size); if (nbytes < 0) { ERROR (usbhid->context, "Usb write interrupt transfer failed."); From acb4a187fb0984d55271b8d9f8c80a70721cde33 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 1 Aug 2017 13:28:03 +0200 Subject: [PATCH 22/25] Add support for synchronizing the device clock Being able to synchronize the dive computer clock with the host system is a very useful feature. Add the infrastructure to support this feature through the public api. --- include/libdivecomputer/device.h | 3 +++ src/atomics_cobalt.c | 1 + src/citizen_aqualand.c | 1 + src/cochran_commander.c | 1 + src/cressi_edy.c | 1 + src/cressi_leonardo.c | 1 + src/device-private.h | 2 ++ src/device.c | 13 +++++++++++++ src/diverite_nitekq.c | 1 + src/divesystem_idive.c | 1 + src/hw_frog.c | 1 + src/hw_ostc.c | 1 + src/hw_ostc3.c | 1 + src/libdivecomputer.symbols | 1 + src/mares_darwin.c | 1 + src/mares_iconhd.c | 1 + src/mares_nemo.c | 1 + src/mares_puck.c | 1 + src/oceanic_atom2.c | 1 + src/oceanic_veo250.c | 1 + src/oceanic_vtpro.c | 1 + src/reefnet_sensus.c | 1 + src/reefnet_sensuspro.c | 1 + src/reefnet_sensusultra.c | 1 + src/shearwater_petrel.c | 1 + src/shearwater_predator.c | 1 + src/suunto_d9.c | 1 + src/suunto_eon.c | 1 + src/suunto_eonsteel.c | 1 + src/suunto_solution.c | 1 + src/suunto_vyper.c | 1 + src/suunto_vyper2.c | 1 + src/uwatec_aladin.c | 1 + src/uwatec_g2.c | 1 + src/uwatec_memomouse.c | 1 + src/uwatec_meridian.c | 1 + src/uwatec_smart.c | 1 + src/zeagle_n2ition3.c | 1 + 38 files changed, 53 insertions(+) diff --git a/include/libdivecomputer/device.h b/include/libdivecomputer/device.h index 1990926..f6d415c 100644 --- a/include/libdivecomputer/device.h +++ b/include/libdivecomputer/device.h @@ -96,6 +96,9 @@ dc_device_dump (dc_device_t *device, dc_buffer_t *buffer); dc_status_t dc_device_foreach (dc_device_t *device, dc_dive_callback_t callback, void *userdata); +dc_status_t +dc_device_timesync (dc_device_t *device, const dc_datetime_t *datetime); + dc_status_t dc_device_close (dc_device_t *device); diff --git a/src/atomics_cobalt.c b/src/atomics_cobalt.c index 91a6da6..7c7dccb 100644 --- a/src/atomics_cobalt.c +++ b/src/atomics_cobalt.c @@ -75,6 +75,7 @@ static const dc_device_vtable_t atomics_cobalt_device_vtable = { NULL, /* write */ NULL, /* dump */ atomics_cobalt_device_foreach, /* foreach */ + NULL, /* timesync */ atomics_cobalt_device_close /* close */ }; diff --git a/src/citizen_aqualand.c b/src/citizen_aqualand.c index 5e111da..47ba87f 100644 --- a/src/citizen_aqualand.c +++ b/src/citizen_aqualand.c @@ -51,6 +51,7 @@ static const dc_device_vtable_t citizen_aqualand_device_vtable = { NULL, /* write */ citizen_aqualand_device_dump, /* dump */ citizen_aqualand_device_foreach, /* foreach */ + NULL, /* timesync */ citizen_aqualand_device_close /* close */ }; diff --git a/src/cochran_commander.c b/src/cochran_commander.c index 16707bf..b515909 100644 --- a/src/cochran_commander.c +++ b/src/cochran_commander.c @@ -114,6 +114,7 @@ static const dc_device_vtable_t cochran_commander_device_vtable = { NULL, /* write */ cochran_commander_device_dump, /* dump */ cochran_commander_device_foreach, /* foreach */ + NULL, /* timesync */ cochran_commander_device_close /* close */ }; diff --git a/src/cressi_edy.c b/src/cressi_edy.c index 226f32f..68e13fc 100644 --- a/src/cressi_edy.c +++ b/src/cressi_edy.c @@ -75,6 +75,7 @@ static const dc_device_vtable_t cressi_edy_device_vtable = { NULL, /* write */ cressi_edy_device_dump, /* dump */ cressi_edy_device_foreach, /* foreach */ + NULL, /* timesync */ cressi_edy_device_close /* close */ }; diff --git a/src/cressi_leonardo.c b/src/cressi_leonardo.c index c354b22..f21a223 100644 --- a/src/cressi_leonardo.c +++ b/src/cressi_leonardo.c @@ -67,6 +67,7 @@ static const dc_device_vtable_t cressi_leonardo_device_vtable = { NULL, /* write */ cressi_leonardo_device_dump, /* dump */ cressi_leonardo_device_foreach, /* foreach */ + NULL, /* timesync */ cressi_leonardo_device_close /* close */ }; diff --git a/src/device-private.h b/src/device-private.h index dcf0b96..7ca6610 100644 --- a/src/device-private.h +++ b/src/device-private.h @@ -71,6 +71,8 @@ struct dc_device_vtable_t { dc_status_t (*foreach) (dc_device_t *device, dc_dive_callback_t callback, void *userdata); + dc_status_t (*timesync) (dc_device_t *device, const dc_datetime_t *datetime); + dc_status_t (*close) (dc_device_t *device); }; diff --git a/src/device.c b/src/device.c index 9dcc528..6277c90 100644 --- a/src/device.c +++ b/src/device.c @@ -370,6 +370,19 @@ dc_device_foreach (dc_device_t *device, dc_dive_callback_t callback, void *userd } +dc_status_t +dc_device_timesync (dc_device_t *device, const dc_datetime_t *datetime) +{ + if (device == NULL) + return DC_STATUS_UNSUPPORTED; + + if (device->vtable->timesync == NULL) + return DC_STATUS_UNSUPPORTED; + + return device->vtable->timesync (device, datetime); +} + + dc_status_t dc_device_close (dc_device_t *device) { diff --git a/src/diverite_nitekq.c b/src/diverite_nitekq.c index 205115a..8c8c442 100644 --- a/src/diverite_nitekq.c +++ b/src/diverite_nitekq.c @@ -69,6 +69,7 @@ static const dc_device_vtable_t diverite_nitekq_device_vtable = { NULL, /* write */ diverite_nitekq_device_dump, /* dump */ diverite_nitekq_device_foreach, /* foreach */ + NULL, /* timesync */ diverite_nitekq_device_close /* close */ }; diff --git a/src/divesystem_idive.c b/src/divesystem_idive.c index d26e2b4..8a8f1ae 100644 --- a/src/divesystem_idive.c +++ b/src/divesystem_idive.c @@ -86,6 +86,7 @@ static const dc_device_vtable_t divesystem_idive_device_vtable = { NULL, /* write */ NULL, /* dump */ divesystem_idive_device_foreach, /* foreach */ + NULL, /* timesync */ divesystem_idive_device_close /* close */ }; diff --git a/src/hw_frog.c b/src/hw_frog.c index d8b89ca..31caa83 100644 --- a/src/hw_frog.c +++ b/src/hw_frog.c @@ -71,6 +71,7 @@ static const dc_device_vtable_t hw_frog_device_vtable = { NULL, /* write */ NULL, /* dump */ hw_frog_device_foreach, /* foreach */ + NULL, /* timesync */ hw_frog_device_close /* close */ }; diff --git a/src/hw_ostc.c b/src/hw_ostc.c index 3f46603..b866f68 100644 --- a/src/hw_ostc.c +++ b/src/hw_ostc.c @@ -80,6 +80,7 @@ static const dc_device_vtable_t hw_ostc_device_vtable = { NULL, /* write */ hw_ostc_device_dump, /* dump */ hw_ostc_device_foreach, /* foreach */ + NULL, /* timesync */ hw_ostc_device_close /* close */ }; diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index e7b7d9e..9081b1a 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -139,6 +139,7 @@ static const dc_device_vtable_t hw_ostc3_device_vtable = { hw_ostc3_device_write, /* write */ hw_ostc3_device_dump, /* dump */ hw_ostc3_device_foreach, /* foreach */ + NULL, /* timesync */ hw_ostc3_device_close /* close */ }; diff --git a/src/libdivecomputer.symbols b/src/libdivecomputer.symbols index c3d372b..237a9c1 100644 --- a/src/libdivecomputer.symbols +++ b/src/libdivecomputer.symbols @@ -56,6 +56,7 @@ dc_device_read dc_device_set_cancel dc_device_set_events dc_device_set_fingerprint +dc_device_timesync dc_device_write oceanic_atom2_device_version diff --git a/src/mares_darwin.c b/src/mares_darwin.c index 9ac56ba..4b1bc24 100644 --- a/src/mares_darwin.c +++ b/src/mares_darwin.c @@ -70,6 +70,7 @@ static const dc_device_vtable_t mares_darwin_device_vtable = { NULL, /* write */ mares_darwin_device_dump, /* dump */ mares_darwin_device_foreach, /* foreach */ + NULL, /* timesync */ mares_darwin_device_close /* close */ }; diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index 3a73968..e945e9f 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -87,6 +87,7 @@ static const dc_device_vtable_t mares_iconhd_device_vtable = { NULL, /* write */ mares_iconhd_device_dump, /* dump */ mares_iconhd_device_foreach, /* foreach */ + NULL, /* timesync */ mares_iconhd_device_close /* close */ }; diff --git a/src/mares_nemo.c b/src/mares_nemo.c index 2ec3588..c73c24b 100644 --- a/src/mares_nemo.c +++ b/src/mares_nemo.c @@ -62,6 +62,7 @@ static const dc_device_vtable_t mares_nemo_device_vtable = { NULL, /* write */ mares_nemo_device_dump, /* dump */ mares_nemo_device_foreach, /* foreach */ + NULL, /* timesync */ mares_nemo_device_close /* close */ }; diff --git a/src/mares_puck.c b/src/mares_puck.c index fc8e00d..b220f54 100644 --- a/src/mares_puck.c +++ b/src/mares_puck.c @@ -57,6 +57,7 @@ static const dc_device_vtable_t mares_puck_device_vtable = { NULL, /* write */ mares_puck_device_dump, /* dump */ mares_puck_device_foreach, /* foreach */ + NULL, /* timesync */ mares_puck_device_close /* close */ }; diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index d9e8d5a..bae6537 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -74,6 +74,7 @@ static const oceanic_common_device_vtable_t oceanic_atom2_device_vtable = { oceanic_atom2_device_write, /* write */ oceanic_common_device_dump, /* dump */ oceanic_common_device_foreach, /* foreach */ + NULL, /* timesync */ oceanic_atom2_device_close /* close */ }, oceanic_common_device_logbook, diff --git a/src/oceanic_veo250.c b/src/oceanic_veo250.c index d47c532..b6021dd 100644 --- a/src/oceanic_veo250.c +++ b/src/oceanic_veo250.c @@ -56,6 +56,7 @@ static const oceanic_common_device_vtable_t oceanic_veo250_device_vtable = { NULL, /* write */ oceanic_common_device_dump, /* dump */ oceanic_common_device_foreach, /* foreach */ + NULL, /* timesync */ oceanic_veo250_device_close /* close */ }, oceanic_common_device_logbook, diff --git a/src/oceanic_vtpro.c b/src/oceanic_vtpro.c index 2e86f5f..cc09892 100644 --- a/src/oceanic_vtpro.c +++ b/src/oceanic_vtpro.c @@ -68,6 +68,7 @@ static const oceanic_common_device_vtable_t oceanic_vtpro_device_vtable = { NULL, /* write */ oceanic_common_device_dump, /* dump */ oceanic_common_device_foreach, /* foreach */ + NULL, /* timesync */ oceanic_vtpro_device_close /* close */ }, oceanic_vtpro_device_logbook, diff --git a/src/reefnet_sensus.c b/src/reefnet_sensus.c index 94d7a7e..37c343a 100644 --- a/src/reefnet_sensus.c +++ b/src/reefnet_sensus.c @@ -57,6 +57,7 @@ static const dc_device_vtable_t reefnet_sensus_device_vtable = { NULL, /* write */ reefnet_sensus_device_dump, /* dump */ reefnet_sensus_device_foreach, /* foreach */ + NULL, /* timesync */ reefnet_sensus_device_close /* close */ }; diff --git a/src/reefnet_sensuspro.c b/src/reefnet_sensuspro.c index bf88801..4df3c07 100644 --- a/src/reefnet_sensuspro.c +++ b/src/reefnet_sensuspro.c @@ -56,6 +56,7 @@ static const dc_device_vtable_t reefnet_sensuspro_device_vtable = { NULL, /* write */ reefnet_sensuspro_device_dump, /* dump */ reefnet_sensuspro_device_foreach, /* foreach */ + NULL, /* timesync */ reefnet_sensuspro_device_close /* close */ }; diff --git a/src/reefnet_sensusultra.c b/src/reefnet_sensusultra.c index f28dbb2..fba7511 100644 --- a/src/reefnet_sensusultra.c +++ b/src/reefnet_sensusultra.c @@ -65,6 +65,7 @@ static const dc_device_vtable_t reefnet_sensusultra_device_vtable = { NULL, /* write */ reefnet_sensusultra_device_dump, /* dump */ reefnet_sensusultra_device_foreach, /* foreach */ + NULL, /* timesync */ reefnet_sensusultra_device_close /* close */ }; diff --git a/src/shearwater_petrel.c b/src/shearwater_petrel.c index 4966d14..71f507e 100644 --- a/src/shearwater_petrel.c +++ b/src/shearwater_petrel.c @@ -56,6 +56,7 @@ static const dc_device_vtable_t shearwater_petrel_device_vtable = { NULL, /* write */ NULL, /* dump */ shearwater_petrel_device_foreach, /* foreach */ + NULL, /* timesync */ shearwater_petrel_device_close /* close */ }; diff --git a/src/shearwater_predator.c b/src/shearwater_predator.c index 0e7a0d3..42a579b 100644 --- a/src/shearwater_predator.c +++ b/src/shearwater_predator.c @@ -57,6 +57,7 @@ static const dc_device_vtable_t shearwater_predator_device_vtable = { NULL, /* write */ shearwater_predator_device_dump, /* dump */ shearwater_predator_device_foreach, /* foreach */ + NULL, /* timesync */ shearwater_predator_device_close /* close */ }; diff --git a/src/suunto_d9.c b/src/suunto_d9.c index 2908792..56f8c40 100644 --- a/src/suunto_d9.c +++ b/src/suunto_d9.c @@ -58,6 +58,7 @@ static const suunto_common2_device_vtable_t suunto_d9_device_vtable = { suunto_common2_device_write, /* write */ suunto_common2_device_dump, /* dump */ suunto_common2_device_foreach, /* foreach */ + NULL, /* timesync */ suunto_d9_device_close /* close */ }, suunto_d9_device_packet diff --git a/src/suunto_eon.c b/src/suunto_eon.c index ce9ce83..2875654 100644 --- a/src/suunto_eon.c +++ b/src/suunto_eon.c @@ -51,6 +51,7 @@ static const dc_device_vtable_t suunto_eon_device_vtable = { NULL, /* write */ suunto_eon_device_dump, /* dump */ suunto_eon_device_foreach, /* foreach */ + NULL, /* timesync */ suunto_eon_device_close /* close */ }; diff --git a/src/suunto_eonsteel.c b/src/suunto_eonsteel.c index 615f5c7..2e6d4c4 100644 --- a/src/suunto_eonsteel.c +++ b/src/suunto_eonsteel.c @@ -81,6 +81,7 @@ static const dc_device_vtable_t suunto_eonsteel_device_vtable = { NULL, /* write */ NULL, /* dump */ suunto_eonsteel_device_foreach, /* foreach */ + NULL, /* timesync */ suunto_eonsteel_device_close /* close */ }; diff --git a/src/suunto_solution.c b/src/suunto_solution.c index c708114..cb2481d 100644 --- a/src/suunto_solution.c +++ b/src/suunto_solution.c @@ -54,6 +54,7 @@ static const dc_device_vtable_t suunto_solution_device_vtable = { NULL, /* write */ suunto_solution_device_dump, /* dump */ suunto_solution_device_foreach, /* foreach */ + NULL, /* timesync */ suunto_solution_device_close /* close */ }; diff --git a/src/suunto_vyper.c b/src/suunto_vyper.c index ce44e05..33657dc 100644 --- a/src/suunto_vyper.c +++ b/src/suunto_vyper.c @@ -63,6 +63,7 @@ static const dc_device_vtable_t suunto_vyper_device_vtable = { suunto_vyper_device_write, /* write */ suunto_vyper_device_dump, /* dump */ suunto_vyper_device_foreach, /* foreach */ + NULL, /* timesync */ suunto_vyper_device_close /* close */ }; diff --git a/src/suunto_vyper2.c b/src/suunto_vyper2.c index 8ab521e..618ee4a 100644 --- a/src/suunto_vyper2.c +++ b/src/suunto_vyper2.c @@ -50,6 +50,7 @@ static const suunto_common2_device_vtable_t suunto_vyper2_device_vtable = { suunto_common2_device_write, /* write */ suunto_common2_device_dump, /* dump */ suunto_common2_device_foreach, /* foreach */ + NULL, /* timesync */ suunto_vyper2_device_close /* close */ }, suunto_vyper2_device_packet diff --git a/src/uwatec_aladin.c b/src/uwatec_aladin.c index d70369a..25c9208 100644 --- a/src/uwatec_aladin.c +++ b/src/uwatec_aladin.c @@ -62,6 +62,7 @@ static const dc_device_vtable_t uwatec_aladin_device_vtable = { NULL, /* write */ uwatec_aladin_device_dump, /* dump */ uwatec_aladin_device_foreach, /* foreach */ + NULL, /* timesync */ uwatec_aladin_device_close /* close */ }; diff --git a/src/uwatec_g2.c b/src/uwatec_g2.c index fc32aff..6e35104 100644 --- a/src/uwatec_g2.c +++ b/src/uwatec_g2.c @@ -54,6 +54,7 @@ static const dc_device_vtable_t uwatec_g2_device_vtable = { NULL, /* write */ uwatec_g2_device_dump, /* dump */ uwatec_g2_device_foreach, /* foreach */ + NULL, /* timesync */ uwatec_g2_device_close /* close */ }; diff --git a/src/uwatec_memomouse.c b/src/uwatec_memomouse.c index b23c6b2..2f93067 100644 --- a/src/uwatec_memomouse.c +++ b/src/uwatec_memomouse.c @@ -58,6 +58,7 @@ static const dc_device_vtable_t uwatec_memomouse_device_vtable = { NULL, /* write */ uwatec_memomouse_device_dump, /* dump */ uwatec_memomouse_device_foreach, /* foreach */ + NULL, /* timesync */ uwatec_memomouse_device_close /* close */ }; diff --git a/src/uwatec_meridian.c b/src/uwatec_meridian.c index d59b855..c32f3bc 100644 --- a/src/uwatec_meridian.c +++ b/src/uwatec_meridian.c @@ -56,6 +56,7 @@ static const dc_device_vtable_t uwatec_meridian_device_vtable = { NULL, /* write */ uwatec_meridian_device_dump, /* dump */ uwatec_meridian_device_foreach, /* foreach */ + NULL, /* timesync */ uwatec_meridian_device_close /* close */ }; diff --git a/src/uwatec_smart.c b/src/uwatec_smart.c index 425e76e..4fd2e2d 100644 --- a/src/uwatec_smart.c +++ b/src/uwatec_smart.c @@ -52,6 +52,7 @@ static const dc_device_vtable_t uwatec_smart_device_vtable = { NULL, /* write */ uwatec_smart_device_dump, /* dump */ uwatec_smart_device_foreach, /* foreach */ + NULL, /* timesync */ uwatec_smart_device_close /* close */ }; diff --git a/src/zeagle_n2ition3.c b/src/zeagle_n2ition3.c index fed6ae2..3d2bb24 100644 --- a/src/zeagle_n2ition3.c +++ b/src/zeagle_n2ition3.c @@ -64,6 +64,7 @@ static const dc_device_vtable_t zeagle_n2ition3_device_vtable = { NULL, /* write */ zeagle_n2ition3_device_dump, /* dump */ zeagle_n2ition3_device_foreach, /* foreach */ + NULL, /* timesync */ zeagle_n2ition3_device_close /* close */ }; From 22e0ab3d2b03ef462611b7a8a193ae59876c9ba4 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 1 Aug 2017 13:28:03 +0200 Subject: [PATCH 23/25] Implement the new api for the HW devices The Heinrichs-Weikamp devices already supported clock synchronization by means of a device specific function. This is now replaced with the new api. --- include/libdivecomputer/hw_frog.h | 3 --- include/libdivecomputer/hw_ostc.h | 3 --- include/libdivecomputer/hw_ostc3.h | 3 --- src/hw_frog.c | 10 ++++------ src/hw_ostc.c | 10 ++++------ src/hw_ostc3.c | 10 ++++------ src/libdivecomputer.symbols | 3 --- 7 files changed, 12 insertions(+), 30 deletions(-) diff --git a/include/libdivecomputer/hw_frog.h b/include/libdivecomputer/hw_frog.h index 67f624f..0af10ca 100644 --- a/include/libdivecomputer/hw_frog.h +++ b/include/libdivecomputer/hw_frog.h @@ -36,9 +36,6 @@ extern "C" { dc_status_t hw_frog_device_version (dc_device_t *device, unsigned char data[], unsigned int size); -dc_status_t -hw_frog_device_clock (dc_device_t *device, const dc_datetime_t *datetime); - dc_status_t hw_frog_device_display (dc_device_t *device, const char *text); diff --git a/include/libdivecomputer/hw_ostc.h b/include/libdivecomputer/hw_ostc.h index b68e083..b7daca7 100644 --- a/include/libdivecomputer/hw_ostc.h +++ b/include/libdivecomputer/hw_ostc.h @@ -43,9 +43,6 @@ typedef enum hw_ostc_format_t { dc_status_t hw_ostc_device_md2hash (dc_device_t *device, unsigned char data[], unsigned int size); -dc_status_t -hw_ostc_device_clock (dc_device_t *device, const dc_datetime_t *datetime); - dc_status_t hw_ostc_device_eeprom_read (dc_device_t *device, unsigned int bank, unsigned char data[], unsigned int size); diff --git a/include/libdivecomputer/hw_ostc3.h b/include/libdivecomputer/hw_ostc3.h index bd0d2ed..c69dd93 100644 --- a/include/libdivecomputer/hw_ostc3.h +++ b/include/libdivecomputer/hw_ostc3.h @@ -39,9 +39,6 @@ hw_ostc3_device_version (dc_device_t *device, unsigned char data[], unsigned int dc_status_t hw_ostc3_device_hardware (dc_device_t *device, unsigned char data[], unsigned int size); -dc_status_t -hw_ostc3_device_clock (dc_device_t *device, const dc_datetime_t *datetime); - dc_status_t hw_ostc3_device_display (dc_device_t *device, const char *text); diff --git a/src/hw_frog.c b/src/hw_frog.c index 31caa83..83ee998 100644 --- a/src/hw_frog.c +++ b/src/hw_frog.c @@ -61,6 +61,7 @@ typedef struct hw_frog_device_t { static dc_status_t hw_frog_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); static dc_status_t hw_frog_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +static dc_status_t hw_frog_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime); static dc_status_t hw_frog_device_close (dc_device_t *abstract); static const dc_device_vtable_t hw_frog_device_vtable = { @@ -71,7 +72,7 @@ static const dc_device_vtable_t hw_frog_device_vtable = { NULL, /* write */ NULL, /* dump */ hw_frog_device_foreach, /* foreach */ - NULL, /* timesync */ + hw_frog_device_timesync, /* timesync */ hw_frog_device_close /* close */ }; @@ -483,14 +484,11 @@ hw_frog_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void } -dc_status_t -hw_frog_device_clock (dc_device_t *abstract, const dc_datetime_t *datetime) +static dc_status_t +hw_frog_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) { hw_frog_device_t *device = (hw_frog_device_t *) abstract; - if (!ISINSTANCE (abstract)) - return DC_STATUS_INVALIDARGS; - if (datetime == NULL) { ERROR (abstract->context, "Invalid parameter specified."); return DC_STATUS_INVALIDARGS; diff --git a/src/hw_ostc.c b/src/hw_ostc.c index b866f68..87c2b1d 100644 --- a/src/hw_ostc.c +++ b/src/hw_ostc.c @@ -70,6 +70,7 @@ typedef struct hw_ostc_firmware_t { static dc_status_t hw_ostc_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); static dc_status_t hw_ostc_device_dump (dc_device_t *abstract, dc_buffer_t *buffer); static dc_status_t hw_ostc_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +static dc_status_t hw_ostc_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime); static dc_status_t hw_ostc_device_close (dc_device_t *abstract); static const dc_device_vtable_t hw_ostc_device_vtable = { @@ -80,7 +81,7 @@ static const dc_device_vtable_t hw_ostc_device_vtable = { NULL, /* write */ hw_ostc_device_dump, /* dump */ hw_ostc_device_foreach, /* foreach */ - NULL, /* timesync */ + hw_ostc_device_timesync, /* timesync */ hw_ostc_device_close /* close */ }; @@ -378,15 +379,12 @@ hw_ostc_device_md2hash (dc_device_t *abstract, unsigned char data[], unsigned in } -dc_status_t -hw_ostc_device_clock (dc_device_t *abstract, const dc_datetime_t *datetime) +static dc_status_t +hw_ostc_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) { dc_status_t status = DC_STATUS_SUCCESS; hw_ostc_device_t *device = (hw_ostc_device_t *) abstract; - if (!ISINSTANCE (abstract)) - return DC_STATUS_INVALIDARGS; - if (datetime == NULL) { ERROR (abstract->context, "Invalid parameter specified."); return DC_STATUS_INVALIDARGS; diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 9081b1a..a1a5efd 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -129,6 +129,7 @@ static dc_status_t hw_ostc3_device_read (dc_device_t *abstract, unsigned int add static dc_status_t hw_ostc3_device_write (dc_device_t *abstract, unsigned int address, const unsigned char data[], unsigned int size); static dc_status_t hw_ostc3_device_dump (dc_device_t *abstract, dc_buffer_t *buffer); static dc_status_t hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +static dc_status_t hw_ostc3_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime); static dc_status_t hw_ostc3_device_close (dc_device_t *abstract); static const dc_device_vtable_t hw_ostc3_device_vtable = { @@ -139,7 +140,7 @@ static const dc_device_vtable_t hw_ostc3_device_vtable = { hw_ostc3_device_write, /* write */ hw_ostc3_device_dump, /* dump */ hw_ostc3_device_foreach, /* foreach */ - NULL, /* timesync */ + hw_ostc3_device_timesync, /* timesync */ hw_ostc3_device_close /* close */ }; @@ -802,14 +803,11 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi } -dc_status_t -hw_ostc3_device_clock (dc_device_t *abstract, const dc_datetime_t *datetime) +static dc_status_t +hw_ostc3_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) { hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; - if (!ISINSTANCE (abstract)) - return DC_STATUS_INVALIDARGS; - if (datetime == NULL) { ERROR (abstract->context, "Invalid parameter specified."); return DC_STATUS_INVALIDARGS; diff --git a/src/libdivecomputer.symbols b/src/libdivecomputer.symbols index 237a9c1..3b16700 100644 --- a/src/libdivecomputer.symbols +++ b/src/libdivecomputer.symbols @@ -80,19 +80,16 @@ suunto_eon_device_write_name suunto_vyper2_device_version suunto_vyper2_device_reset_maxdepth hw_ostc_device_md2hash -hw_ostc_device_clock hw_ostc_device_eeprom_read hw_ostc_device_eeprom_write hw_ostc_device_reset hw_ostc_device_screenshot hw_ostc_device_fwupdate hw_frog_device_version -hw_frog_device_clock hw_frog_device_display hw_frog_device_customtext hw_ostc3_device_version hw_ostc3_device_hardware -hw_ostc3_device_clock hw_ostc3_device_display hw_ostc3_device_customtext hw_ostc3_device_config_read From 215d1155f87c0857a6a0c4d852db3bc202aae607 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 1 Aug 2017 13:57:52 +0200 Subject: [PATCH 24/25] Add time synchronization to the example application --- examples/Makefile.am | 1 + examples/dctool.c | 1 + examples/dctool.h | 1 + examples/dctool_timesync.c | 162 +++++++++++++++++++++++++++++++++++++ 4 files changed, 165 insertions(+) create mode 100644 examples/dctool_timesync.c diff --git a/examples/Makefile.am b/examples/Makefile.am index e830769..6b70f99 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -17,6 +17,7 @@ dctool_SOURCES = \ dctool_parse.c \ dctool_read.c \ dctool_write.c \ + dctool_timesync.c \ dctool_fwupdate.c \ output.h \ output-private.h \ diff --git a/examples/dctool.c b/examples/dctool.c index df7fe01..f62b13c 100644 --- a/examples/dctool.c +++ b/examples/dctool.c @@ -63,6 +63,7 @@ static const dctool_command_t *g_commands[] = { &dctool_parse, &dctool_read, &dctool_write, + &dctool_timesync, &dctool_fwupdate, NULL }; diff --git a/examples/dctool.h b/examples/dctool.h index cc6a393..06e58a8 100644 --- a/examples/dctool.h +++ b/examples/dctool.h @@ -50,6 +50,7 @@ extern const dctool_command_t dctool_dump; extern const dctool_command_t dctool_parse; extern const dctool_command_t dctool_read; extern const dctool_command_t dctool_write; +extern const dctool_command_t dctool_timesync; extern const dctool_command_t dctool_fwupdate; const dctool_command_t * diff --git a/examples/dctool_timesync.c b/examples/dctool_timesync.c new file mode 100644 index 0000000..3a59af5 --- /dev/null +++ b/examples/dctool_timesync.c @@ -0,0 +1,162 @@ +/* + * libdivecomputer + * + * Copyright (C) 2017 Jef Driesen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#ifdef HAVE_GETOPT_H +#include +#endif + +#include +#include +#include + +#include "dctool.h" +#include "common.h" +#include "utils.h" + +static dc_status_t +do_timesync (dc_context_t *context, dc_descriptor_t *descriptor, const char *devname, const dc_datetime_t *datetime) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + dc_device_t *device = NULL; + + // Open the device. + message ("Opening the device (%s %s, %s).\n", + dc_descriptor_get_vendor (descriptor), + dc_descriptor_get_product (descriptor), + devname ? devname : "null"); + rc = dc_device_open (&device, context, descriptor, devname); + if (rc != DC_STATUS_SUCCESS) { + ERROR ("Error opening the device."); + goto cleanup; + } + + // Register the event handler. + message ("Registering the event handler.\n"); + int events = DC_EVENT_WAITING | DC_EVENT_PROGRESS | DC_EVENT_DEVINFO | DC_EVENT_CLOCK | DC_EVENT_VENDOR; + rc = dc_device_set_events (device, events, dctool_event_cb, NULL); + if (rc != DC_STATUS_SUCCESS) { + ERROR ("Error registering the event handler."); + goto cleanup; + } + + // Register the cancellation handler. + message ("Registering the cancellation handler.\n"); + rc = dc_device_set_cancel (device, dctool_cancel_cb, NULL); + if (rc != DC_STATUS_SUCCESS) { + ERROR ("Error registering the cancellation handler."); + goto cleanup; + } + + // Syncronize the device clock. + message ("Syncronize the device clock.\n"); + rc = dc_device_timesync (device, datetime); + if (rc != DC_STATUS_SUCCESS) { + ERROR ("Error syncronizing the device clock."); + goto cleanup; + } + +cleanup: + dc_device_close (device); + return rc; +} + +static int +dctool_timesync_run (int argc, char *argv[], dc_context_t *context, dc_descriptor_t *descriptor) +{ + int exitcode = EXIT_SUCCESS; + dc_status_t status = DC_STATUS_SUCCESS; + + // Default option values. + unsigned int help = 0; + + // Parse the command-line options. + int opt = 0; + const char *optstring = "h"; +#ifdef HAVE_GETOPT_LONG + struct option options[] = { + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0 } + }; + while ((opt = getopt_long (argc, argv, optstring, options, NULL)) != -1) { +#else + while ((opt = getopt (argc, argv, optstring)) != -1) { +#endif + switch (opt) { + case 'h': + help = 1; + break; + default: + return EXIT_FAILURE; + } + } + + argc -= optind; + argv += optind; + + // Show help message. + if (help) { + dctool_command_showhelp (&dctool_timesync); + return EXIT_SUCCESS; + } + + // Get the system time. + dc_datetime_t datetime = {0}; + dc_ticks_t now = dc_datetime_now (); + if (!dc_datetime_localtime(&datetime, now)) { + message ("ERROR: Failed to get the system time.\n"); + exitcode = EXIT_FAILURE; + goto cleanup; + } + + // Synchronize the device clock. + status = do_timesync (context, descriptor, argv[0], &datetime); + if (status != DC_STATUS_SUCCESS) { + message ("ERROR: %s\n", dctool_errmsg (status)); + exitcode = EXIT_FAILURE; + goto cleanup; + } + +cleanup: + return exitcode; +} + +const dctool_command_t dctool_timesync = { + dctool_timesync_run, + DCTOOL_CONFIG_DESCRIPTOR, + "timesync", + "Synchronize the device clock", + "Usage:\n" + " dctool timesync [options]\n" + "\n" + "Options:\n" +#ifdef HAVE_GETOPT_LONG + " -h, --help Show help message\n" +#else + " -h Show help message\n" +#endif +}; From 156f54302d603e6231d2c599bfedd8068d7abbe4 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 25 Apr 2017 20:32:42 +0200 Subject: [PATCH 25/25] Add basic timezone support Allthough most dive computers always use local time and don't support timezones at all, there are a few exceptions. There are two different sources of timezone information: - Some of the newer Uwatec/Scubapro devices use UTC internally and also support a timezone setting. This UTC offset is currently taken into account to obtain the dive date/time, but the UTC offset itself is lost. - Uwatec/Scubapro and Reefnet devices rely on the clock of the host system to synchronize the internal device clock and calculate the dive date/time. The consequence is that the resulting date/time is always in the timezone of the host system. In order to preserve this timezone information, the dc_datetime_t structure is extended with a new "timezone" field, containing the UTC offset in seconds. Devices without timezone support will set the field to the special value DC_TIMEZONE_NONE. The dc_datetime_localtime() and dc_datetime_gmtime() functions will automatically populate the new field with respectively the local timezone offset and zero. The dc_datetime_mktime() function will take into account the new timezone field for the conversion to UTC. The special value DC_TIMEZONE_NONE is interpreted as zero. --- configure.ac | 7 +++- examples/output_xml.c | 13 ++++-- include/libdivecomputer/datetime.h | 3 ++ src/atomics_cobalt_parser.c | 1 + src/citizen_aqualand_parser.c | 1 + src/cochran_commander_parser.c | 2 + src/cressi_edy_parser.c | 1 + src/cressi_leonardo_parser.c | 1 + src/datetime.c | 67 +++++++++++++++++++++++++++++- src/diverite_nitekq_parser.c | 1 + src/hw_ostc_parser.c | 5 ++- src/mares_darwin_parser.c | 1 + src/mares_iconhd_parser.c | 1 + src/mares_nemo_parser.c | 1 + src/oceanic_atom2_parser.c | 1 + src/oceanic_veo250_parser.c | 1 + src/oceanic_vtpro_parser.c | 1 + src/shearwater_predator_parser.c | 2 + src/suunto_d9_parser.c | 1 + src/suunto_eon_parser.c | 1 + src/suunto_eonsteel_parser.c | 6 ++- src/suunto_vyper_parser.c | 1 + src/uwatec_smart_parser.c | 2 + 23 files changed, 113 insertions(+), 8 deletions(-) diff --git a/configure.ac b/configure.ac index 695a86b..17bd5f9 100644 --- a/configure.ac +++ b/configure.ac @@ -158,9 +158,14 @@ AC_CHECK_HEADERS([sys/param.h]) # Checks for global variable declarations. AC_CHECK_DECLS([optreset]) +# Checks for structures. +AC_CHECK_MEMBERS([struct tm.tm_gmtoff],,,[ +#include +]) + # Checks for library functions. AC_FUNC_STRERROR_R -AC_CHECK_FUNCS([localtime_r gmtime_r]) +AC_CHECK_FUNCS([localtime_r gmtime_r timegm _mkgmtime]) AC_CHECK_FUNCS([getopt_long]) # Versioning. diff --git a/examples/output_xml.c b/examples/output_xml.c index 2baa329..86a3957 100644 --- a/examples/output_xml.c +++ b/examples/output_xml.c @@ -230,9 +230,16 @@ dctool_xml_output_write (dctool_output_t *abstract, dc_parser_t *parser, const u goto cleanup; } - fprintf (output->ostream, "%04i-%02i-%02i %02i:%02i:%02i\n", - dt.year, dt.month, dt.day, - dt.hour, dt.minute, dt.second); + if (dt.timezone == DC_TIMEZONE_NONE) { + fprintf (output->ostream, "%04i-%02i-%02i %02i:%02i:%02i\n", + dt.year, dt.month, dt.day, + dt.hour, dt.minute, dt.second); + } else { + fprintf (output->ostream, "%04i-%02i-%02i %02i:%02i:%02i %+03i:%02i\n", + dt.year, dt.month, dt.day, + dt.hour, dt.minute, dt.second, + dt.timezone / 3600, (dt.timezone % 3600) / 60); + } // Parse the divetime. message ("Parsing the divetime.\n"); diff --git a/include/libdivecomputer/datetime.h b/include/libdivecomputer/datetime.h index d8f596b..768883d 100644 --- a/include/libdivecomputer/datetime.h +++ b/include/libdivecomputer/datetime.h @@ -26,6 +26,8 @@ extern "C" { #endif /* __cplusplus */ +#define DC_TIMEZONE_NONE 0x80000000 + #if defined (_WIN32) && !defined (__GNUC__) typedef __int64 dc_ticks_t; #else @@ -39,6 +41,7 @@ typedef struct dc_datetime_t { int hour; int minute; int second; + int timezone; } dc_datetime_t; dc_ticks_t diff --git a/src/atomics_cobalt_parser.c b/src/atomics_cobalt_parser.c index e43243c..e16077b 100644 --- a/src/atomics_cobalt_parser.c +++ b/src/atomics_cobalt_parser.c @@ -122,6 +122,7 @@ atomics_cobalt_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dateti datetime->hour = p[0x18]; datetime->minute = p[0x19]; datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/citizen_aqualand_parser.c b/src/citizen_aqualand_parser.c index f89eacf..319dcf7 100644 --- a/src/citizen_aqualand_parser.c +++ b/src/citizen_aqualand_parser.c @@ -95,6 +95,7 @@ citizen_aqualand_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *date datetime->hour = bcd2dec(p[0x0A]); datetime->minute = bcd2dec(p[0x0B]); datetime->second = bcd2dec(p[0x0C]); + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/cochran_commander_parser.c b/src/cochran_commander_parser.c index 33f3b28..12e8b42 100644 --- a/src/cochran_commander_parser.c +++ b/src/cochran_commander_parser.c @@ -435,6 +435,7 @@ cochran_commander_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dat datetime->day = data[layout->datetime + 2]; datetime->month = data[layout->datetime + 5]; datetime->year = data[layout->datetime + 4] + (data[layout->datetime + 4] > 91 ? 1900 : 2000); + datetime->timezone = DC_TIMEZONE_NONE; break; case DATE_ENCODING_SMHDMY: datetime->second = data[layout->datetime + 0]; @@ -443,6 +444,7 @@ cochran_commander_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dat datetime->day = data[layout->datetime + 3]; datetime->month = data[layout->datetime + 4]; datetime->year = data[layout->datetime + 5] + (data[layout->datetime + 5] > 91 ? 1900 : 2000); + datetime->timezone = DC_TIMEZONE_NONE; break; case DATE_ENCODING_TICKS: ts = array_uint32_le(data + layout->datetime) + COCHRAN_EPOCH; diff --git a/src/cressi_edy_parser.c b/src/cressi_edy_parser.c index c9e1d91..eaf55c3 100644 --- a/src/cressi_edy_parser.c +++ b/src/cressi_edy_parser.c @@ -115,6 +115,7 @@ cressi_edy_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) datetime->hour = bcd2dec (p[14]); datetime->minute = bcd2dec (p[15]); datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/cressi_leonardo_parser.c b/src/cressi_leonardo_parser.c index b8c47e0..d7cf0de 100644 --- a/src/cressi_leonardo_parser.c +++ b/src/cressi_leonardo_parser.c @@ -100,6 +100,7 @@ cressi_leonardo_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datet datetime->hour = p[11]; datetime->minute = p[12]; datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/datetime.c b/src/datetime.c index 9d07f8c..3ceed42 100644 --- a/src/datetime.c +++ b/src/datetime.c @@ -61,6 +61,47 @@ dc_gmtime_r (const time_t *t, struct tm *tm) #endif } +static time_t +dc_timegm (struct tm *tm) +{ +#if defined(HAVE_TIMEGM) + return timegm (tm); +#elif defined (HAVE__MKGMTIME) + return _mkgmtime (tm); +#else + static const unsigned int mdays[] = { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 + }; + + if (tm == NULL || + tm->tm_mon < 0 || tm->tm_mon > 11 || + tm->tm_mday < 1 || tm->tm_mday > 31 || + tm->tm_hour < 0 || tm->tm_hour > 23 || + tm->tm_min < 0 || tm->tm_min > 59 || + tm->tm_sec < 0 || tm->tm_sec > 60) + return (time_t) -1; + + /* Number of leap days since 1970-01-01. */ + int year = tm->tm_year + 1900 - (tm->tm_mon < 2); + int leapdays = + ((year / 4) - (year / 100) + (year / 400)) - + ((1969 / 4) - (1969 / 100) + (1969 / 400)); + + time_t result = 0; + result += (tm->tm_year - 70) * 365; + result += leapdays; + result += mdays[tm->tm_mon]; + result += tm->tm_mday - 1; + result *= 24; + result += tm->tm_hour; + result *= 60; + result += tm->tm_min; + result *= 60; + result += tm->tm_sec; + return result; +#endif +} + dc_ticks_t dc_datetime_now (void) { @@ -72,11 +113,23 @@ dc_datetime_localtime (dc_datetime_t *result, dc_ticks_t ticks) { time_t t = ticks; + int offset = 0; struct tm tm; if (dc_localtime_r (&t, &tm) == NULL) return NULL; +#ifdef HAVE_STRUCT_TM_TM_GMTOFF + offset = tm.tm_gmtoff; +#else + struct tm tmp = tm; + time_t t_local = dc_timegm (&tmp); + if (t_local == (time_t) -1) + return NULL; + + offset = t_local - t; +#endif + if (result) { result->year = tm.tm_year + 1900; result->month = tm.tm_mon + 1; @@ -84,6 +137,7 @@ dc_datetime_localtime (dc_datetime_t *result, result->hour = tm.tm_hour; result->minute = tm.tm_min; result->second = tm.tm_sec; + result->timezone = offset; } return result; @@ -106,6 +160,7 @@ dc_datetime_gmtime (dc_datetime_t *result, result->hour = tm.tm_hour; result->minute = tm.tm_min; result->second = tm.tm_sec; + result->timezone = 0; } return result; @@ -124,7 +179,15 @@ dc_datetime_mktime (const dc_datetime_t *dt) tm.tm_hour = dt->hour; tm.tm_min = dt->minute; tm.tm_sec = dt->second; - tm.tm_isdst = -1; + tm.tm_isdst = 0; - return mktime (&tm); + time_t t = dc_timegm (&tm); + if (t == (time_t) -1) + return t; + + if (dt->timezone != DC_TIMEZONE_NONE) { + t -= dt->timezone; + } + + return t; } diff --git a/src/diverite_nitekq_parser.c b/src/diverite_nitekq_parser.c index 6c7a05f..dce40d8 100644 --- a/src/diverite_nitekq_parser.c +++ b/src/diverite_nitekq_parser.c @@ -118,6 +118,7 @@ diverite_nitekq_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datet datetime->hour = p[3]; datetime->minute = p[4]; datetime->second = p[5]; + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index b23e6d3..f1c656f 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -394,6 +394,7 @@ hw_ostc_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) dt.hour = p[3]; dt.minute = p[4]; dt.second = 0; + dt.timezone = DC_TIMEZONE_NONE; if (version == 0x24) { if (datetime) @@ -405,8 +406,10 @@ hw_ostc_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) ticks -= divetime; - if (!dc_datetime_localtime (datetime, ticks)) + if (!dc_datetime_gmtime (datetime, ticks)) return DC_STATUS_DATAFORMAT; + + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/mares_darwin_parser.c b/src/mares_darwin_parser.c index 1201ec6..ba0382f 100644 --- a/src/mares_darwin_parser.c +++ b/src/mares_darwin_parser.c @@ -118,6 +118,7 @@ mares_darwin_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime datetime->hour = p[4]; datetime->minute = p[5]; datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index 0c45a15..76324f3 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -329,6 +329,7 @@ mares_iconhd_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime datetime->day = array_uint16_le (p + 4); datetime->month = array_uint16_le (p + 6) + 1; datetime->year = array_uint16_le (p + 8) + 1900; + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/mares_nemo_parser.c b/src/mares_nemo_parser.c index 9a6cbfd..c0ab03b 100644 --- a/src/mares_nemo_parser.c +++ b/src/mares_nemo_parser.c @@ -200,6 +200,7 @@ mares_nemo_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) datetime->hour = p[3]; datetime->minute = p[4]; datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/oceanic_atom2_parser.c b/src/oceanic_atom2_parser.c index 9133984..19abadb 100644 --- a/src/oceanic_atom2_parser.c +++ b/src/oceanic_atom2_parser.c @@ -324,6 +324,7 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim break; } datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; // Convert to a 24-hour clock. datetime->hour %= 12; diff --git a/src/oceanic_veo250_parser.c b/src/oceanic_veo250_parser.c index ba69723..a3acf57 100644 --- a/src/oceanic_veo250_parser.c +++ b/src/oceanic_veo250_parser.c @@ -121,6 +121,7 @@ oceanic_veo250_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dateti datetime->hour = p[3]; datetime->minute = p[2]; datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; if (parser->model == VEO200 || parser->model == VEO250) datetime->year += 3; diff --git a/src/oceanic_vtpro_parser.c b/src/oceanic_vtpro_parser.c index 9c85854..894b626 100644 --- a/src/oceanic_vtpro_parser.c +++ b/src/oceanic_vtpro_parser.c @@ -135,6 +135,7 @@ oceanic_vtpro_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim } datetime->minute = bcd2dec (p[0]); datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; // Convert to a 24-hour clock. datetime->hour %= 12; diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index c923153..4217f54 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -202,6 +202,8 @@ shearwater_predator_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *d if (!dc_datetime_gmtime (datetime, ticks)) return DC_STATUS_DATAFORMAT; + datetime->timezone = DC_TIMEZONE_NONE; + return DC_STATUS_SUCCESS; } diff --git a/src/suunto_d9_parser.c b/src/suunto_d9_parser.c index 68e79f9..6c96fa0 100644 --- a/src/suunto_d9_parser.c +++ b/src/suunto_d9_parser.c @@ -317,6 +317,7 @@ suunto_d9_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) datetime->month = p[5]; datetime->day = p[6]; } + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/suunto_eon_parser.c b/src/suunto_eon_parser.c index 066c997..2106c69 100644 --- a/src/suunto_eon_parser.c +++ b/src/suunto_eon_parser.c @@ -178,6 +178,7 @@ suunto_eon_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) datetime->minute = bcd2dec (p[4]); } datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index baa0319..0ebee01 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -1114,7 +1114,11 @@ suunto_eonsteel_parser_get_datetime(dc_parser_t *parser, dc_datetime_t *datetime if (parser->size < 4) return DC_STATUS_UNSUPPORTED; - dc_datetime_gmtime(datetime, array_uint32_le(parser->data)); + if (!dc_datetime_gmtime(datetime, array_uint32_le(parser->data))) + return DC_STATUS_DATAFORMAT; + + datetime->timezone = DC_TIMEZONE_NONE; + return DC_STATUS_SUCCESS; } diff --git a/src/suunto_vyper_parser.c b/src/suunto_vyper_parser.c index 0ca8eaa..80f7d54 100644 --- a/src/suunto_vyper_parser.c +++ b/src/suunto_vyper_parser.c @@ -223,6 +223,7 @@ suunto_vyper_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime datetime->hour = p[3]; datetime->minute = p[4]; datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; } return DC_STATUS_SUCCESS; diff --git a/src/uwatec_smart_parser.c b/src/uwatec_smart_parser.c index 99bfa28..54bdee5 100644 --- a/src/uwatec_smart_parser.c +++ b/src/uwatec_smart_parser.c @@ -710,6 +710,8 @@ uwatec_smart_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime if (!dc_datetime_gmtime (datetime, ticks)) return DC_STATUS_DATAFORMAT; + + datetime->timezone = utc_offset * 900; } else { // For devices without timezone support, the current timezone of // the host system is used.