diff --git a/src/array.c b/src/array.c index 13a73e5..5574083 100644 --- a/src/array.c +++ b/src/array.c @@ -198,6 +198,13 @@ array_uint32_le (const unsigned char data[]) } +unsigned int +array_uint32_word_be (const unsigned char data[]) +{ + return data[1] + (data[0] << 8) + (data[3] << 16) + (data[2] << 24); +} + + void array_uint32_le_set (unsigned char data[], const unsigned int input) { diff --git a/src/array.h b/src/array.h index cd0a3a1..da70efa 100644 --- a/src/array.h +++ b/src/array.h @@ -64,6 +64,9 @@ array_uint32_be (const unsigned char data[]); unsigned int array_uint32_le (const unsigned char data[]); +unsigned int +array_uint32_word_be (const unsigned char data[]); + void array_uint32_le_set (unsigned char data[], const unsigned int input); diff --git a/src/cochran_commander.c b/src/cochran_commander.c index d2c47b0..1ad38f0 100644 --- a/src/cochran_commander.c +++ b/src/cochran_commander.c @@ -28,31 +28,36 @@ #include "device-private.h" #include "serial.h" #include "array.h" +#include "ringbuffer.h" #define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array)) -#define COCHRAN_MODEL_COMMANDER_AIR_NITROX 0 -#define COCHRAN_MODEL_EMC_14 1 -#define COCHRAN_MODEL_EMC_16 2 -#define COCHRAN_MODEL_EMC_20 3 +#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 typedef enum cochran_endian_t { ENDIAN_LE, ENDIAN_BE, + ENDIAN_WORD_BE, } cochran_endian_t; typedef struct cochran_commander_model_t { - unsigned char id[2 + 1]; + unsigned char id[3 + 1]; unsigned int model; } cochran_commander_model_t; typedef struct cochran_data_t { unsigned char config[1024]; unsigned char *logbook; - unsigned char *sample; unsigned short int dive_count; int fp_dive_num; + int invalid_profile_dive_num; unsigned int logbook_size; @@ -78,7 +83,9 @@ typedef struct cochran_device_layout_t { // Profile ringbuffer. unsigned int rb_profile_begin; unsigned int rb_profile_end; - // Profile pointers. + // pointers. + unsigned int pt_fingerprint; + unsigned int fingerprint_size; unsigned int pt_profile_pre; unsigned int pt_profile_begin; unsigned int pt_profile_end; @@ -109,15 +116,15 @@ static const dc_device_vtable_t cochran_commander_device_vtable = { cochran_commander_device_close /* close */ }; -// Cochran Commander Nitrox -static const cochran_device_layout_t cochran_cmdr_device_layout = { - COCHRAN_MODEL_COMMANDER_AIR_NITROX, // model +// Cochran Commander pre-21000 s/n +static const cochran_device_layout_t cochran_cmdr_1_device_layout = { + COCHRAN_MODEL_COMMANDER_PRE21000, // model 24, // address_bits - ENDIAN_BE, // endian + ENDIAN_WORD_BE, // endian 115200, // baudrate 0x046, // cf_dive_count - 0x06E, // cf_last_log - 0x200, // cf_last_interdive + 0x6c, // cf_last_log + 0x70, // cf_last_interdive 0x0AA, // cf_serial_number 0x00000000, // rb_logbook_begin 0x00020000, // rb_logbook_end @@ -125,6 +132,32 @@ static const cochran_device_layout_t cochran_cmdr_device_layout = { 512, // rb_logbook_entry_count 0x00020000, // rb_profile_begin 0x00100000, // rb_profile_end + 12, // pt_fingerprint + 4, // fingerprint_size + 28, // pt_profile_pre + 0, // pt_profile_begin + 128, // pt_profile_end +}; + + +// Cochran Commander Nitrox +static const cochran_device_layout_t cochran_cmdr_device_layout = { + COCHRAN_MODEL_COMMANDER_AIR_NITROX, // model + 24, // address_bits + ENDIAN_WORD_BE, // endian + 115200, // baudrate + 0x046, // cf_dive_count + 0x06C, // cf_last_log + 0x070, // cf_last_interdive + 0x0AA, // cf_serial_number + 0x00000000, // rb_logbook_begin + 0x00020000, // rb_logbook_end + 256, // rb_logbook_entry_size + 512, // rb_logbook_entry_count + 0x00020000, // rb_profile_begin + 0x00100000, // rb_profile_end + 0, // pt_fingerprint + 6, // fingerprint_size 30, // pt_profile_pre 6, // pt_profile_begin 128, // pt_profile_end @@ -146,6 +179,8 @@ static const cochran_device_layout_t cochran_emc14_device_layout = { 256, // rb_logbook_entry_count 0x00022000, // rb_profile_begin 0x00200000, // rb_profile_end + 0, // pt_fingerprint + 6, // fingerprint_size 30, // pt_profile_pre 6, // pt_profile_begin 256, // pt_profile_end @@ -167,6 +202,8 @@ static const cochran_device_layout_t cochran_emc16_device_layout = { 1024, // rb_logbook_entry_count 0x00094000, // rb_profile_begin 0x00800000, // rb_profile_end + 0, // pt_fingerprint + 6, // fingerprint_size 30, // pt_profile_pre 6, // pt_profile_begin 256, // pt_profile_end @@ -188,6 +225,8 @@ static const cochran_device_layout_t cochran_emc20_device_layout = { 1024, // rb_logbook_entry_count 0x00094000, // rb_profile_begin 0x01000000, // rb_profile_end + 0, // pt_fingerprint + 6, // fingerprint_size 30, // pt_profile_pre 6, // pt_profile_begin 256, // pt_profile_end @@ -199,15 +238,20 @@ static unsigned int cochran_commander_get_model (cochran_commander_device_t *device) { const cochran_commander_model_t models[] = { - {"\x11""2", COCHRAN_MODEL_COMMANDER_AIR_NITROX}, - {"73", COCHRAN_MODEL_EMC_14}, - {"A3", COCHRAN_MODEL_EMC_16}, - {"23", COCHRAN_MODEL_EMC_20}, + {"\x11""21", COCHRAN_MODEL_COMMANDER_PRE21000}, + {"\x11""22", COCHRAN_MODEL_COMMANDER_AIR_NITROX}, + {"730", COCHRAN_MODEL_EMC_14}, + {"731", COCHRAN_MODEL_EMC_14}, + {"A30", COCHRAN_MODEL_EMC_16}, + {"A31", COCHRAN_MODEL_EMC_16}, + {"230", COCHRAN_MODEL_EMC_20}, + {"231", COCHRAN_MODEL_EMC_20}, + {"\x40""30", COCHRAN_MODEL_EMC_20}, }; unsigned int model = 0xFFFFFFFF; for (unsigned int i = 0; i < C_ARRAY_SIZE(models); ++i) { - if (memcmp (device->id + 0x3B, models[i].id, sizeof(models[i].id) - 1) == 0) { + if (memcmp (device->id + 0x3D, models[i].id, sizeof(models[i].id) - 1) == 0) { model = models[i].model; break; } @@ -429,24 +473,165 @@ 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; -static void + 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 + * search for a reasonable size, e.g. using next dive start sample + * or end-of-samples to limit searching for recoverable samples + */ +static unsigned int +cochran_commander_guess_sample_end_address(cochran_commander_device_t *device, cochran_data_t *data, unsigned int log_num) +{ + const unsigned char *log_entry = data->logbook + device->layout->rb_logbook_entry_size * log_num; + + if (log_num == data->dive_count) + // Return next usable address from config page + return array_uint32_le(data->config + device->layout->rb_profile_end); + + // Next log's start address + return array_uint32_le(log_entry + device->layout->rb_logbook_entry_size + device->layout->pt_profile_begin); +} + + +static unsigned int +cochran_commander_profile_size(cochran_commander_device_t *device, cochran_data_t *data, int dive_num, unsigned int sample_start_address, unsigned int sample_end_address) +{ + // Validate addresses + if (sample_start_address < device->layout->rb_profile_begin || + sample_start_address > device->layout->rb_profile_end || + sample_end_address < device->layout->rb_profile_begin || + (sample_end_address > device->layout->rb_profile_end && + sample_end_address != 0xFFFFFFFF)) { + return 0; + } + + if (sample_end_address == 0xFFFFFFFF) + // Corrupt dive, guess the end address + sample_end_address = cochran_commander_guess_sample_end_address(device, data, dive_num); + + return ringbuffer_distance(sample_start_address, sample_end_address, 0, device->layout->rb_profile_begin, device->layout->rb_profile_end); +} + + +/* + * Do several things. Find the log that matches the fingerprint, + * calculate the total read size for progress indicator, + * Determine the most recent dive without profile data. + */ + +static unsigned int cochran_commander_find_fingerprint(cochran_commander_device_t *device, cochran_data_t *data) { - // Skip to fingerprint to reduce time - if (data->dive_count < device->layout->rb_logbook_entry_count) - data->fp_dive_num = data->dive_count; - else - data->fp_dive_num = device->layout->rb_logbook_entry_count; - data->fp_dive_num--; + // 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; - while (data->fp_dive_num >= 0 && memcmp(device->fingerprint, - data->logbook + data->fp_dive_num * device->layout->rb_logbook_entry_size, - sizeof(device->fingerprint))) - data->fp_dive_num--; + int dive_count = -1; + data->fp_dive_num = -1; + + // Start at end of log + if (data->dive_count < device->layout->rb_logbook_entry_count) + dive_count = data->dive_count; + else + dive_count = device->layout->rb_logbook_entry_count; + dive_count--; + + unsigned int sample_read_size = 0; + data->invalid_profile_dive_num = -1; + + // 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; + else + rb_head_ptr = (array_uint32_le(data->config + device->layout->cf_last_log) & 0xfffff000) + 0x2000; + + unsigned int head_dive = 0, tail_dive = 0; + + if (data->dive_count <= device->layout->rb_logbook_entry_count) { + head_dive = data->dive_count; + tail_dive = 0; + } else { + // Log wrapped + tail_dive = data->dive_count % device->layout->rb_logbook_entry_count; + head_dive = tail_dive; + } + + 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_pre = 0xFFFFFFFF; + + if (device->layout->endian == ENDIAN_WORD_BE) + last_profile_pre = 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); + + if (rb_head_ptr > last_profile_end) + profile_capacity_remaining -= rb_head_ptr - last_profile_end; + + // Loop through dives to find FP, Accumulate profile data size, + // and find the last dive with invalid profile + for (unsigned int i = 0; i <= dive_count; ++i) { + unsigned int idx = (device->layout->rb_logbook_entry_count + head_dive - (i + 1)) % device->layout->rb_logbook_entry_count; + + unsigned char *log_entry = data->logbook + idx * device->layout->rb_logbook_entry_size; + + // We're done if we find the fingerprint + if (!memcmp(device->fingerprint, log_entry + device->layout->pt_fingerprint, device->layout->fingerprint_size)) { + data->fp_dive_num = idx; + break; + } + + 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 + if (profile_capacity_remaining > 0) { + // Subtract this dive's profile size including post-dive events + profile_capacity_remaining -= sample_size; + if (profile_capacity_remaining < 0) { + // Save the last dive that is missing profile data + data->invalid_profile_dive_num = idx; + } + // Accumulate read size for progress bar + sample_read_size += read_size; + } + } + + return sample_read_size; } + static void cochran_commander_get_sample_parms(cochran_commander_device_t *device, cochran_data_t *data) { @@ -509,103 +694,6 @@ cochran_commander_get_sample_parms(cochran_commander_device_t *device, cochran_d } -/* - * For corrupt dives the end-of-samples pointer is 0xFFFFFFFF - * search for a reasonable size, e.g. using next dive start sample - * or end-of-samples to limit searching for recoverable samples - */ -static unsigned int -cochran_commander_guess_sample_end_address(cochran_commander_device_t *device, cochran_data_t *data, unsigned int log_num) -{ - const unsigned char *log_entry = data->logbook + device->layout->rb_logbook_entry_size * log_num; - - if (log_num == data->dive_count) - // Return next usable address from config page - return array_uint32_le(data->config + device->layout->rb_profile_end); - - // Next log's start address - return array_uint32_le(log_entry + device->layout->rb_logbook_entry_size + device->layout->pt_profile_begin); -} - - -static dc_status_t -cochran_commander_read_all (cochran_commander_device_t *device, cochran_data_t *data) -{ - dc_device_t *abstract = (dc_device_t *) device; - dc_status_t rc = DC_STATUS_SUCCESS; - - // Calculate max data sizes - unsigned int max_config = sizeof(data->config); - unsigned int max_logbook = device->layout->rb_logbook_end - device->layout->rb_logbook_begin; - unsigned int max_sample = device->layout->rb_profile_end - device->layout->rb_profile_begin; - - dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; - progress.maximum = max_config + max_logbook + max_sample; - device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - - // Emit ID block - dc_event_vendor_t vendor; - vendor.data = device->id; - vendor.size = sizeof (device->id); - device_event_emit (abstract, DC_EVENT_VENDOR, &vendor); - - // Read config - rc = cochran_commander_read_config(device, &progress, data->config, sizeof(data->config)); - if (rc != DC_STATUS_SUCCESS) - return rc; - - // Determine size of dive list to read. - if (device->layout->endian == ENDIAN_LE) - data->dive_count = array_uint16_le (data->config + device->layout->cf_dive_count); - else - data->dive_count = array_uint16_be (data->config + device->layout->cf_dive_count); - - if (data->dive_count > device->layout->rb_logbook_entry_count) { - data->logbook_size = device->layout->rb_logbook_entry_count * device->layout->rb_logbook_entry_size; - } else { - data->logbook_size = data->dive_count * device->layout->rb_logbook_entry_size; - } - - progress.maximum -= max_logbook - data->logbook_size; - device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - - // Allocate space for log book. - data->logbook = (unsigned char *) malloc(data->logbook_size); - if (data->logbook == NULL) { - ERROR (abstract->context, "Failed to allocate memory."); - return DC_STATUS_NOMEMORY; - } - - // Request log book - rc = cochran_commander_read(device, &progress, 0, data->logbook, data->logbook_size); - if (rc != DC_STATUS_SUCCESS) - return rc; - - // Determine sample memory to read - cochran_commander_find_fingerprint(device, data); - cochran_commander_get_sample_parms(device, data); - - progress.maximum -= max_sample - data->sample_size; - device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - - if (data->sample_size > 0) { - data->sample = (unsigned char *) malloc(data->sample_size); - if (data->sample == NULL) { - ERROR (abstract->context, "Failed to allocate memory."); - return DC_STATUS_NOMEMORY; - } - - // Read the sample data - rc = cochran_commander_read (device, &progress, data->sample_data_offset, data->sample, data->sample_size); - if (rc != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the sample data."); - return rc; - } - } - - return DC_STATUS_SUCCESS; -} - dc_status_t cochran_commander_device_open (dc_device_t **out, dc_context_t *context, const char *name) { @@ -647,6 +735,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_PRE21000: + device->layout = &cochran_cmdr_1_device_layout; + break; case COCHRAN_MODEL_COMMANDER_AIR_NITROX: device->layout = &cochran_cmdr_device_layout; break; @@ -697,13 +788,13 @@ cochran_commander_device_set_fingerprint (dc_device_t *abstract, const unsigned { cochran_commander_device_t *device = (cochran_commander_device_t *) abstract; - if (size && size != sizeof (device->fingerprint)) + if (size && size != device->layout->fingerprint_size) return DC_STATUS_INVALIDARGS; if (size) - memcpy (device->fingerprint, data, sizeof (device->fingerprint)); + memcpy (device->fingerprint, data, device->layout->fingerprint_size); else - memset (device->fingerprint, 0xFF, sizeof (device->fingerprint)); + memset (device->fingerprint, 0xFF, sizeof(device->fingerprint)); return DC_STATUS_SUCCESS; } @@ -767,111 +858,172 @@ static dc_status_t cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) { 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; cochran_data_t data; data.logbook = NULL; - data.sample = NULL; - status = cochran_commander_read_all (device, &data); - if (status != DC_STATUS_SUCCESS) - goto error; - // Emit a device info event. - dc_event_devinfo_t devinfo; - devinfo.model = device->layout->model; - devinfo.firmware = 0; // unknown - devinfo.serial = array_uint32_le(data.config + device->layout->cf_serial_number); - device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); + // Calculate max data sizes + 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; - // Calculate profile RB effective head pointer - // Cochran seems to erase 8K chunks so round up. - unsigned int last_start_address = (array_uint32_le(data.config + device->layout->cf_last_interdive) & 0xfffff000) + 0x2000; - if (last_start_address < device->layout->rb_profile_begin || last_start_address > device->layout->rb_profile_end) { - ERROR(abstract->context, "Invalid profile ringbuffer head pointer in Cochran config block."); - status = DC_STATUS_DATAFORMAT; + // setup progress indication + dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; + progress.maximum = max_config + max_logbook + max_sample; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Emit ID block + dc_event_vendor_t vendor; + vendor.data = device->id; + vendor.size = sizeof (device->id); + device_event_emit (abstract, DC_EVENT_VENDOR, &vendor); + + // Read config + dc_status_t rc = DC_STATUS_SUCCESS; + rc = cochran_commander_read_config(device, &progress, data.config, sizeof(data.config)); + if (rc != DC_STATUS_SUCCESS) + return rc; + + // Determine size of dive list to read. + if (layout->endian == ENDIAN_LE) + data.dive_count = array_uint16_le (data.config + layout->cf_dive_count); + else + data.dive_count = array_uint16_be (data.config + layout->cf_dive_count); + + if (data.dive_count == 0) + // No dives to read + 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; + } else { + data.logbook_size = data.dive_count * layout->rb_logbook_entry_size; + } + + // Update progress indicator with new maximum + progress.maximum -= max_logbook - data.logbook_size; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Allocate space for log book. + data.logbook = (unsigned char *) malloc(data.logbook_size); + if (data.logbook == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Request log book + rc = cochran_commander_read(device, &progress, 0, data.logbook, data.logbook_size); + if (rc != DC_STATUS_SUCCESS) { + status = rc; goto error; } - // 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; + // 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); - unsigned int dive_count = 0; - if (data.dive_count < device->layout->rb_logbook_entry_count) - dive_count = data.dive_count; + cochran_commander_get_sample_parms(device, &data); + + // Emit a device info event. + dc_event_devinfo_t devinfo; + devinfo.model = layout->model; + devinfo.firmware = 0; // unknown + if (layout->endian == ENDIAN_WORD_BE) + devinfo.serial = array_uint32_word_be(data.config + layout->cf_serial_number); else - dive_count = device->layout->rb_logbook_entry_count; + devinfo.serial = array_uint32_le(data.config + layout->cf_serial_number); + + device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); + + unsigned int head_dive = 0, tail_dive = 0, dive_count = 0; + + if (data.dive_count <= layout->rb_logbook_entry_count) { + head_dive = data.dive_count; + tail_dive = 0; + } else { + // Log wrapped + tail_dive = data.dive_count % layout->rb_logbook_entry_count; + head_dive = tail_dive; + } + + // Change tail to dive following the fingerprint dive. + if (data.fp_dive_num > -1) + tail_dive = (data.fp_dive_num + 1) % layout->rb_logbook_entry_count; + + // Number of dives to read + dive_count = (layout->rb_logbook_entry_count + head_dive - tail_dive) % layout->rb_logbook_entry_count; + + int invalid_profile_flag = 0; // Loop through each dive - for (int i = dive_count - 1; i > data.fp_dive_num; i--) { - unsigned char *log_entry = data.logbook + i * device->layout->rb_logbook_entry_size; + for (unsigned int i = 0; i < dive_count; ++i) { + unsigned int idx = (layout->rb_logbook_entry_count + head_dive - (i + 1)) % layout->rb_logbook_entry_count; - unsigned int sample_start_address = array_uint32_le (log_entry + device->layout->pt_profile_begin); - unsigned int sample_end_address = array_uint32_le (log_entry + device->layout->pt_profile_end); + unsigned char *log_entry = data.logbook + idx * layout->rb_logbook_entry_size; - // Validate - if (sample_start_address < device->layout->rb_profile_begin || - sample_start_address > device->layout->rb_profile_end || - sample_end_address < device->layout->rb_profile_begin || - (sample_end_address > device->layout->rb_profile_end && - sample_end_address != 0xFFFFFFFF)) { - continue; - } + 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); - if (sample_end_address == 0xFFFFFFFF) - // Corrupt dive, guess the end address - sample_end_address = cochran_commander_guess_sample_end_address(device, &data, i); - - // Determine if sample exists - if (profile_capacity_remaining > 0) { - // Subtract this dive's profile size including post-dive events - profile_capacity_remaining -= (last_start_address - sample_start_address); - // Adjust for a dive that wraps the buffer - if (sample_start_address > last_start_address) - profile_capacity_remaining -= device->layout->rb_profile_end - device->layout->rb_profile_begin; - } - last_start_address = sample_start_address; - - unsigned char *sample = NULL; int sample_size = 0; - if (profile_capacity_remaining < 0) { - // There is no profile for this dive - sample = NULL; - sample_size = 0; - } else { - // Calculate the size of the profile only - sample = data.sample + sample_start_address - data.sample_data_offset; - sample_size = sample_end_address - sample_start_address; - if (sample_size < 0) - // Adjust for ring buffer wrap-around - sample_size += device->layout->rb_profile_end - device->layout->rb_profile_begin; - } + // Determine if profile exists + if (idx == data.invalid_profile_dive_num) + invalid_profile_flag = 1; + + if (!invalid_profile_flag) + sample_size = cochran_commander_profile_size(device, &data, idx, sample_start_address, sample_end_address); // Build dive blob - unsigned int dive_size = device->layout->rb_logbook_entry_size + sample_size; + unsigned int dive_size = layout->rb_logbook_entry_size + sample_size; unsigned char *dive = (unsigned char *) malloc(dive_size); if (dive == NULL) { status = DC_STATUS_NOMEMORY; goto error; } - memcpy(dive, log_entry, device->layout->rb_logbook_entry_size); // log + memcpy(dive, log_entry, layout->rb_logbook_entry_size); // log - // Copy profile data + // 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) { - memcpy(dive + device->layout->rb_logbook_entry_size, sample, sample_size); + rc = cochran_commander_read_retry (device, &progress, sample_start_address, 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; + } } else { // It wrapped the buffer, copy two sections - unsigned int size = device->layout->rb_profile_end - sample_start_address; + unsigned int size = layout->rb_profile_end - sample_start_address; - memcpy(dive + device->layout->rb_logbook_entry_size, sample, size); - memcpy(dive + device->layout->rb_logbook_entry_size + size, - data.sample, sample_end_address - device->layout->rb_profile_begin); + 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."); + status = rc; + free(dive); + goto error; + } } } - if (callback && !callback (dive, dive_size, dive, sizeof(device->fingerprint), userdata)) { + if (callback && !callback (dive, dive_size, dive + layout->pt_fingerprint, layout->fingerprint_size, userdata)) { free(dive); break; } @@ -881,6 +1033,5 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call error: free(data.logbook); - free(data.sample); return status; } diff --git a/src/cochran_commander_parser.c b/src/cochran_commander_parser.c index ab854ad..5a0ad55 100644 --- a/src/cochran_commander_parser.c +++ b/src/cochran_commander_parser.c @@ -31,10 +31,14 @@ #define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array)) -#define COCHRAN_MODEL_COMMANDER_AIR_NITROX 0 -#define COCHRAN_MODEL_EMC_14 1 -#define COCHRAN_MODEL_EMC_16 2 -#define COCHRAN_MODEL_EMC_20 3 +#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 + +// Cochran time stamps start at Jan 1, 1992 +#define COCHRAN_EPOCH 694242000 #define UNSUPPORTED 0xFFFFFFFF @@ -43,11 +47,19 @@ typedef enum cochran_sample_format_t { SAMPLE_EMC, } cochran_sample_format_t; + +typedef enum cochran_date_encoding_t { + DATE_ENCODING_MSDHYM, + DATE_ENCODING_SMHDMY, + DATE_ENCODING_TICKS, +} cochran_date_encoding_t; + typedef struct cochran_parser_layout_t { cochran_sample_format_t format; unsigned int headersize; unsigned int samplesize; - unsigned int second, minute, hour, day, month, year; + cochran_date_encoding_t date_encoding; + unsigned int datetime; unsigned int pt_profile_begin; unsigned int water_conductivity; unsigned int pt_profile_pre; @@ -101,11 +113,36 @@ static const dc_parser_vtable_t cochran_commander_parser_vtable = { NULL /* destroy */ }; +static const cochran_parser_layout_t cochran_cmdr_1_parser_layout = { + SAMPLE_CMDR, // type + 256, // headersize + 2, // samplesize + DATE_ENCODING_TICKS, // date_encoding + 8, // datetime, 4 bytes + 0, // pt_profile_begin, 4 bytes + 24, // water_conductivity, 1 byte, 0=low(fresh), 2=high(sea) + 28, // pt_profile_pre, 4 bytes + 43, // start_temp, 1 byte, F + 54, // start_depth, 2 bytes, /4=ft + 68, // dive_number, 2 bytes + 73, // altitude, 1 byte, /4=kilofeet + 128, // pt_profile_end, 4 bytes + 153, // end_temp, 1 byte F + 166, // divetime, 2 bytes, minutes + 168, // max_depth, 2 bytes, /4=ft + 170, // avg_depth, 2 bytes, /4=ft + 210, // oxygen, 4 bytes (2 of) 2 bytes, /256=% + UNSUPPORTED, // helium, 4 bytes (2 of) 2 bytes, /256=% + 232, // min_temp, 1 byte, /2+20=F + 233, // max_temp, 1 byte, /2+20=F +}; + static const cochran_parser_layout_t cochran_cmdr_parser_layout = { SAMPLE_CMDR, // type 256, // headersize 2, // samplesize - 1, 0, 3, 2, 5, 4, // second, minute, hour, day, month, year, 1 byte each + DATE_ENCODING_MSDHYM, // date_encoding + 0, // datetime, 6 bytes 6, // pt_profile_begin, 4 bytes 24, // water_conductivity, 1 byte, 0=low(fresh), 2=high(sea) 30, // pt_profile_pre, 4 bytes @@ -128,7 +165,8 @@ static const cochran_parser_layout_t cochran_emc_parser_layout = { SAMPLE_EMC, // type 512, // headersize 3, // samplesize - 0, 1, 2, 3, 4, 5, // second, minute, hour, day, month, year, 1 byte each + DATE_ENCODING_SMHDMY, // date_encoding + 0, // datetime, 6 bytes 6, // pt_profile_begin, 4 bytes 24, // water_conductivity, 1 byte 0=low(fresh), 2=high(sea) 30, // pt_profile_pre, 4 bytes @@ -296,6 +334,11 @@ cochran_commander_parser_create (dc_parser_t **out, dc_context_t *context, unsig parser->model = model; switch (model) { + case COCHRAN_MODEL_COMMANDER_PRE21000: + parser->layout = &cochran_cmdr_1_parser_layout; + parser->events = cochran_cmdr_event_bytes; + parser->nevents = C_ARRAY_SIZE(cochran_cmdr_event_bytes); + break; case COCHRAN_MODEL_COMMANDER_AIR_NITROX: parser->layout = &cochran_cmdr_parser_layout; parser->events = cochran_cmdr_event_bytes; @@ -340,13 +383,32 @@ cochran_commander_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dat if (abstract->size < layout->headersize) return DC_STATUS_DATAFORMAT; + dc_ticks_t ts = 0; + if (datetime) { - datetime->second = data[layout->second]; - datetime->minute = data[layout->minute]; - datetime->hour = data[layout->hour]; - datetime->day = data[layout->day]; - datetime->month = data[layout->month]; - datetime->year = data[layout->year] + (data[layout->year] > 91 ? 1900 : 2000); + switch (layout->date_encoding) + { + case DATE_ENCODING_MSDHYM: + datetime->second = data[layout->datetime + 1]; + datetime->minute = data[layout->datetime + 0]; + datetime->hour = data[layout->datetime + 3]; + 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); + break; + case DATE_ENCODING_SMHDMY: + datetime->second = data[layout->datetime + 0]; + datetime->minute = data[layout->datetime + 1]; + datetime->hour = data[layout->datetime + 2]; + 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); + break; + case DATE_ENCODING_TICKS: + ts = array_uint32_le(data + layout->datetime) + COCHRAN_EPOCH; + dc_datetime_localtime(datetime, ts); + break; + } } return DC_STATUS_SUCCESS; @@ -471,10 +533,11 @@ cochran_commander_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb // know what the dive summary values are (i.e. max depth, min temp) if (array_uint32_le(data + layout->pt_profile_end) == 0xFFFFFFFF) { corrupt_dive = 1; + dc_datetime_t d; + cochran_commander_parser_get_datetime(abstract, &d); WARNING(abstract->context, "Incomplete dive on %02d/%02d/%02d at %02d:%02d:%02d, trying to parse samples", - data[layout->year], data[layout->month], data[layout->day], - data[layout->hour], data[layout->minute], data[layout->second]); + d.year, d.month, d.day, d.hour, d.minute, d.second); // Eliminate inter-dive events size = cochran_commander_backparse(parser, samples, size); @@ -484,7 +547,8 @@ cochran_commander_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb // and temp every other second. // Prime values from the dive log section - if (parser->model == COCHRAN_MODEL_COMMANDER_AIR_NITROX) { + if (parser->model == COCHRAN_MODEL_COMMANDER_AIR_NITROX || + parser->model == COCHRAN_MODEL_COMMANDER_PRE21000) { // Commander stores start depth in quarter-feet start_depth = array_uint16_le (data + layout->start_depth) / 4.0; } else { @@ -503,6 +567,7 @@ cochran_commander_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb sample.gasmix = 0; if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata); + unsigned int last_gasmix = sample.gasmix; while (offset < size) { const unsigned char *s = samples + offset; @@ -537,45 +602,48 @@ cochran_commander_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb if (s[0] & 0x80) { offset += cochran_commander_handle_event(parser, s[0], callback, userdata); - if (layout->format == SAMPLE_EMC) { - // EMC models have events indicating change in deco status - // Commander may have them but I don't have example data - switch (s[0]) { - case 0xC5: // Deco obligation begins - deco_obligation = 1; - break; - case 0xD8: // Deco obligation ends - deco_obligation = 0; - break; - case 0xAB: // Decrement ceiling (deeper) - deco_ceiling += 10; // feet + // Events indicating change in deco status + switch (s[0]) { + case 0xC5: // Deco obligation begins + deco_obligation = 1; + break; + case 0xD8: // Deco obligation ends + deco_obligation = 0; + break; + case 0xAB: // Decrement ceiling (deeper) + deco_ceiling += 10; // feet - sample.deco.type = DC_DECO_DECOSTOP; - sample.deco.time = (array_uint16_le(s + layout->samplesize) + 1) * 60; - 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.time = (array_uint16_le(s + 3) + 1) * 60; + 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 = (array_uint16_le(s + layout->samplesize) + 1) * 60; - if (callback) callback(DC_SAMPLE_DECO, sample, userdata); - break; - case 0xC0: // Switched to FO2 21% mode (surface) - // Event seen upon surfacing - break; - case 0xCD: // Switched to deco blend - case 0xEF: // Switched to gas blend 2 + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.depth = deco_ceiling * FEET; + sample.deco.time = (array_uint16_le(s + 3) + 1) * 60; + if (callback) callback(DC_SAMPLE_DECO, sample, userdata); + break; + case 0xC0: // Switched to FO2 21% mode (surface) + // Event seen upon surfacing + break; + case 0xCD: // Switched to deco blend + case 0xEF: // Switched to gas blend 2 + if (last_gasmix != 1) { sample.gasmix = 1; if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata); - break; - case 0xF3: // Switched to gas blend 1 + last_gasmix = sample.gasmix; + } + break; + case 0xF3: // Switched to gas blend 1 + if (last_gasmix != 0) { sample.gasmix = 0; if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata); - break; + last_gasmix = sample.gasmix; } + break; } continue; diff --git a/src/descriptor.c b/src/descriptor.c index 9b537e1..633debd 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -313,10 +313,12 @@ static const dc_descriptor_t g_descriptors[] = { {"DiveSystem", "iDive2 Easy", DC_FAMILY_DIVESYSTEM_IDIVE, 0x42}, {"DiveSystem", "iDive2 Deep", DC_FAMILY_DIVESYSTEM_IDIVE, 0x44}, {"DiveSystem", "iDive2 Tech+", DC_FAMILY_DIVESYSTEM_IDIVE, 0x45}, - {"Cochran", "Commander", DC_FAMILY_COCHRAN_COMMANDER, 0}, - {"Cochran", "EMC-14", DC_FAMILY_COCHRAN_COMMANDER, 1}, - {"Cochran", "EMC-16", DC_FAMILY_COCHRAN_COMMANDER, 2}, - {"Cochran", "EMC-20H", DC_FAMILY_COCHRAN_COMMANDER, 3}, + /* 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}, }; typedef struct dc_descriptor_iterator_t {