[PATCH 6/6] Cochran: Added full support for Commander TM
John Van Ostrand
john at vanostrand.com
Sat Jul 15 13:39:41 PDT 2017
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);
+}
--
2.4.11
More information about the devel
mailing list