The Cochran Commander TM appears to be a first generation Commander with limited storage and function compared to later models. This commit provides full support for downloading Commander TM logs and profiles. --- src/cochran_commander.c | 62 +++++++++++--- src/cochran_commander_parser.c | 178 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 219 insertions(+), 21 deletions(-)
diff --git a/src/cochran_commander.c b/src/cochran_commander.c index a77eabb..39bfe8e 100644 --- a/src/cochran_commander.c +++ b/src/cochran_commander.c @@ -87,6 +87,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 { @@ -120,6 +121,7 @@ static const cochran_device_layout_t cochran_cmdr_tm_device_layout = { 24, // address_bits ENDIAN_WORD_BE, // endian 9600, // baudrate + 4096, // rbstream_size 0x146, // cf_dive_count 0x158, // cf_last_log 0xffffff, // cf_last_interdive @@ -135,6 +137,7 @@ static const cochran_device_layout_t cochran_cmdr_tm_device_layout = { 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 @@ -159,6 +162,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 };
@@ -184,6 +188,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 @@ -208,6 +213,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 @@ -232,6 +238,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 @@ -256,6 +263,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 };
@@ -569,6 +577,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;
@@ -587,10 +597,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; - 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;
@@ -604,13 +617,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; @@ -628,7 +641,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, i, profile_pre, last_profile_pre); last_profile_pre = profile_pre; @@ -836,6 +849,7 @@ 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; @@ -863,9 +877,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 > rb_logbook_entry_count) { data.logbook_size = rb_logbook_entry_count * layout->rb_logbook_entry_size; @@ -885,7 +901,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; @@ -893,6 +909,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); @@ -927,7 +944,12 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call dive_count = (rb_logbook_entry_count + head_dive - tail_dive) % rb_logbook_entry_count;
// Create rbstream - unsigned int last_start_address = array_uint32_le( data.logbook + ((head_dive - 1) * layout->rb_logbook_entry_size) + layout->pt_profile_end ); + 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 ); + rc = dc_rbstream_new (&rbstream, abstract, 1, layout->rbstream_size, layout->rb_profile_begin, layout->rb_profile_end, last_start_address );
int invalid_profile_flag = 0; @@ -938,8 +960,24 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call
unsigned char *log_entry = data.logbook + i * 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 != i) { + ERROR(abstract->context, "Corrupt dive (%d).", i); + 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 b6ee17a..60a1b31 100644 --- a/src/cochran_commander_parser.c +++ b/src/cochran_commander_parser.c @@ -32,11 +32,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 @@ -44,6 +45,7 @@ #define UNSUPPORTED 0xFFFFFFFF
typedef enum cochran_sample_format_t { + SAMPLE_TM, SAMPLE_CMDR, SAMPLE_EMC, } cochran_sample_format_t; @@ -59,6 +61,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; @@ -114,10 +117,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 @@ -139,9 +168,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 @@ -163,9 +193,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 @@ -336,6 +367,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; @@ -442,6 +478,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; @@ -486,6 +524,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 @@ -495,6 +535,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: @@ -506,8 +548,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; @@ -722,3 +870,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); +}