[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