[PATCH 2/2] Cochran: Made significant changes to address review issues.

John Van Ostrand john at vanostrand.com
Sat Feb 27 09:56:25 PST 2016


Consolidated model differences in structures to facilitate
common code.
Merged EMC and Commander parser_foreach and get_field into
common functions.
Removed model string prefix from dive data in favour of passing
model number to the parser_create function.
---
 include/libdivecomputer/cochran_commander.h |   2 +-
 src/Makefile.am                             |   3 +-
 src/cochran_cmdr_parser.c                   | 248 --------------
 src/cochran_commander.c                     | 410 +++++++++++++----------
 src/cochran_commander.h                     | 160 ++++-----
 src/cochran_commander_parser.c              | 484 ++++++++++++++++++++++------
 src/cochran_commander_parser.h              |  69 +---
 src/cochran_emc_parser.c                    | 345 --------------------
 src/parser.c                                |   2 +-
 9 files changed, 701 insertions(+), 1022 deletions(-)
 delete mode 100644 src/cochran_cmdr_parser.c
 delete mode 100644 src/cochran_emc_parser.c

diff --git a/include/libdivecomputer/cochran_commander.h b/include/libdivecomputer/cochran_commander.h
index 143c451..9fff2d1 100644
--- a/include/libdivecomputer/cochran_commander.h
+++ b/include/libdivecomputer/cochran_commander.h
@@ -35,7 +35,7 @@ cochran_commander_device_open (dc_device_t **device, dc_context_t *context,
 		const char *name);
 
 dc_status_t
-cochran_commander_parser_create (dc_parser_t **parser, dc_context_t *context);
+cochran_commander_parser_create (dc_parser_t **parser, dc_context_t *context, int model);
 
 #ifdef __cplusplus
 }
diff --git a/src/Makefile.am b/src/Makefile.am
index 91721b9..0c46a32 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -66,8 +66,7 @@ libdivecomputer_la_SOURCES = \
 	array.h array.c \
 	buffer.c \
 	cochran_commander.h cochran_commander.c \
-	cochran_commander_parser.h cochran_commander_parser.c \
-	cochran_cmdr_parser.c cochran_emc_parser.c
+	cochran_commander_parser.h cochran_commander_parser.c
 
 if OS_WIN32
 libdivecomputer_la_SOURCES += serial.h serial_win32.c
diff --git a/src/cochran_cmdr_parser.c b/src/cochran_cmdr_parser.c
deleted file mode 100644
index 20d4fd2..0000000
--- a/src/cochran_cmdr_parser.c
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * libdivecomputer
- *
- * Copyright (C) 2014 John Van Ostrand
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301 USA
- */
-
-#include <stdlib.h>
-#include <math.h>
-
-#include <libdivecomputer/units.h>
-#include <libdivecomputer/cochran.h>
-
-#include "context-private.h"
-#include "device-private.h"
-#include "parser-private.h"
-#include "serial.h"
-#include "array.h"
-
-#include "cochran_commander.h"
-#include "cochran_commander_parser.h"
-
-
-dc_status_t
-cochran_cmdr_parser_get_field (dc_parser_t *abstract, dc_field_type_t type,
-		unsigned int flags, void *value)
-{
-	const unsigned char *data = abstract->data;
-	const unsigned char *log_entry = data + COCHRAN_MODEL_SIZE;
-	unsigned int minutes = 0, qfeet = 0;
-
-	dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
-	dc_salinity_t *water = (dc_salinity_t *) value;
-
-	if (value) {
-		switch (type) {
-		case DC_FIELD_TEMPERATURE_SURFACE:
-			*((unsigned int*) value) = (log_entry[CMD_START_TEMP] - 32.0) / 1.8;
-			break;
-		case DC_FIELD_TEMPERATURE_MINIMUM:
-			if (log_entry[CMD_MIN_TEMP] == 0xFF)
-				return DC_STATUS_UNSUPPORTED;
-			else
-				*((unsigned int*) value) = (log_entry[CMD_MIN_TEMP] / 2.0
-					+ 20 - 32) / 1.8;
-			break;
-		case DC_FIELD_TEMPERATURE_MAXIMUM:
-			if (log_entry[CMD_MAX_TEMP] == 0xFF)
-				return DC_STATUS_UNSUPPORTED;
-			else
-				*((unsigned int*) value) = (log_entry[CMD_MAX_TEMP] / 2.0
-					+ 20 - 32) / 1.8;
-			break;
-		case DC_FIELD_DIVETIME:
-			minutes = array_uint16_le(log_entry + CMD_BT);
-
-			if (minutes == 0xFFFF)
-				return DC_STATUS_UNSUPPORTED;
-			else
-				*((unsigned int *) value) = minutes * 60;
-			break;
-		case DC_FIELD_MAXDEPTH:
-			qfeet = array_uint16_le(log_entry + CMD_MAX_DEPTH);
-			if (qfeet == 0xFFFF)
-				return DC_STATUS_UNSUPPORTED;
-			else
-				*((double *) value) = qfeet / 4.0 * FEET;
-			break;
-		case DC_FIELD_AVGDEPTH:
-			qfeet = array_uint16_le(log_entry + CMD_AVG_DEPTH);
-			if (qfeet == 0xFFFF)
-				return DC_STATUS_UNSUPPORTED;
-			else
-				*((double *) value) = qfeet / 4.0 * FEET;
-			break;
-		case DC_FIELD_GASMIX_COUNT:
-			*((unsigned int *) value) = 2;
-			break;
-		case DC_FIELD_GASMIX:
-			// Gas percentages are decimal and encoded as
-			// highbyte = integer portion
-			// lowbyte = decimal portion, divide by 256 to get decimal value
-			gasmix->oxygen = array_uint16_le (log_entry
-					+ CMD_O2_PERCENT + 2 * flags) / 256.0 / 100;
-			gasmix->helium = 0;
-			gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium;
-			break;
-		case DC_FIELD_SALINITY:
-			// 0x00 = low conductivity, 0x10 = high, maybe there's a 0x01 and 0x11?
-			// Assume Cochran's conductivity ranges from 0 to 3 
-			// 0 is fresh water, anything else is sea water
-			// for density assume
-			//  	0 = 1000kg/m³, 2 = 1025kg/m³
-			// and other values are linear
-			water->type = ( (log_entry[CMD_WATER_CONDUCTIVITY] & 0x3) == 0
-				? DC_WATER_FRESH : DC_WATER_SALT );
-			water->density = 1000 + 12.5 * (log_entry[CMD_WATER_CONDUCTIVITY] & 0x3);
-			break;
-		case DC_FIELD_ATMOSPHERIC:
-			// Cochran measures air pressure and stores it as altitude.
-			// Convert altitude (measured in 1/4 kilofeet) back to pressure.
-			*(double *) value = ATM / BAR * pow(1 - 0.0000225577
-					* log_entry[CMD_ALTITUDE] * 250.0 * FEET, 5.25588);
-			break;
-		default:
-			return DC_STATUS_UNSUPPORTED;
-		}
-	}
-
-	return DC_STATUS_SUCCESS;
-}
-
-
-dc_status_t
-cochran_cmdr_parser_samples_foreach (dc_parser_t *abstract,
-		dc_sample_callback_t callback, void *userdata)
-{
-	const unsigned char *log_entry = abstract->data + COCHRAN_MODEL_SIZE;
-	const unsigned char *samples = log_entry + COCHRAN_CMDR_LOG_SIZE;
-
-	unsigned int size = abstract->size - COCHRAN_MODEL_SIZE
-		- COCHRAN_CMDR_LOG_SIZE;
-
-	dc_sample_value_t sample = {0};
-	unsigned int time = 0, last_sample_time = 0;
-	unsigned int offset = 0;
-	double temperature;
-	int depth_qfeet;
-	double ascent_rate;
-	unsigned char corrupt_dive = 0;
-	struct event_size cmdr_event_bytes[15] = { {0x00, 17}, {0x01, 21}, {0x02, 18},
-											   {0x03, 17}, {0x06, 19}, {0x07, 19},
-											   {0x08, 19}, {0x09, 19}, {0x0a, 19},
-											   {0x0b, 21}, {0x0c, 19}, {0x0d, 19},
-											   {0x0e, 19}, {0x10, 21},
-											   {  -1,  1} };
-
-	// In rare circumstances Cochran computers won't record the end-of-dive
-	// log entry block. When the end-sample pointer is 0xFFFFFFFF it's corrupt.
-	// That means we don't really know where the dive samples end and we don't
-	// know what the dive summary values are (i.e. max depth, min temp)
-	if (array_uint32_le(log_entry + COCHRAN_CMDR_LOG_SIZE / 2) == 0xFFFFFFFF) {
-		corrupt_dive = 1;
-
-		WARNING(abstract->context, "Incomplete dive on %02d/%02d/%02d at %02d:%02d:%02d, trying to parse samples", log_entry[CMD_YEAR], log_entry[CMD_MON], log_entry[CMD_DAY], log_entry[CMD_HOUR], log_entry[CMD_MIN], log_entry[CMD_SEC]);
-
-		// Eliminate inter-dive events
-		size = cochran_backparse(abstract, samples, size, &cmdr_event_bytes);
-	}
-
-	// Cochran samples depth every second and varies between ascent rate
-	// and temp ever other second.
-
-	// Prime values from the dive log section
-	depth_qfeet = array_uint16_le (log_entry + CMD_START_DEPTH);
-
-	last_sample_time = sample.time = time;
-	if (callback) callback (DC_SAMPLE_TIME, sample, userdata);
-
-	sample.depth = (double) depth_qfeet * FEET;
-	if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
-
-	sample.temperature = (log_entry[CMD_START_TEMP] - 32.0) / 1.8;
-	if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
-
-	while (offset < size) {
-		const unsigned char *s = NULL;
-
-		sample.time = time;
-		if (callback && last_sample_time != sample.time) {
-			// We haven't issued this time yet.
-			last_sample_time = sample.time;
-			callback (DC_SAMPLE_TIME, sample, userdata);
-		}
-
-		s = samples + offset;
-
-		// If corrupt_dive end before offset
-		if (corrupt_dive) {
-			// When we aren't sure where the sample data ends we can
-			// look for events that shouldn't be in the sample data.
-			// 0xFF is unwritten memory
-			// 0xA8 indicates start of post-dive interval
-			// 0xE3 (switch to FO2 mode) and 0xF3 (switch to blend 1) occur
-			// at dive start so when we see them after the first second we
-			// found the beginning of the next dive.
-			if (s[0] == 0xFF || s[0] == 0xA8) {
-				break;
-			}
-			if (time > 1 && (s[0] == 0xE3 || s[0] == 0xF3))
-				break;
-		}
-
-		// Check for event
-		if (s[0] & 0x80) {
-			offset += cochran_commander_handle_event(abstract, callback,
-				userdata, s[0], time);
-			continue;
-		}
-
-
-		// Depth is logged as change in feet, bit 0x40 means negative depth
-		if (s[0] & 0x40)
-			depth_qfeet -= (s[0] & 0x3f);
-		else
-			depth_qfeet += (s[0] & 0x3f);
-
-		sample.depth = depth_qfeet / 4.0 * FEET;
-		if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
-
-		// Ascent rate is logged in the 0th sample, temp in the 1st, repeat.
-		if (time % 2 == 0) {
-			// Ascent rate
-			if (s[1] & 0x80)
-				ascent_rate = (s[1] & 0x7f) / 4.0;
-			else
-				ascent_rate =  - (s[1] & 0x7f) / 4.0;
-
-			sample.ascent_rate = ascent_rate * FEET;
-			if (callback) callback (DC_SAMPLE_ASCENT_RATE, sample, userdata);
-		} else {
-			// Temperature logged in half degrees F above 20
-			temperature = s[1] / 2.0 + 20;
-			sample.temperature = (temperature - 32.0) / 1.8;
-
-			if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
-		}
-
-		time ++;
-		offset += COCHRAN_CMDR_SAMPLE_SIZE;
-	}
-
-	return DC_STATUS_SUCCESS;
-}
diff --git a/src/cochran_commander.c b/src/cochran_commander.c
index d11df92..c457b3a 100644
--- a/src/cochran_commander.c
+++ b/src/cochran_commander.c
@@ -42,107 +42,147 @@
 
 
 /*
-*  Hardware and data layout information for various Cochran models.
+*  Device, protocol and data layout information for various Cochran models.
 *
 *  EMC models are identical except for the size of logbook and profile
 *  memory.
 */
-static cochran_layout_t cochran_layout_cmdr = {
-	 COCHRAN_MODEL_COMMANDER_AIR_NITROX,	/* model */
-	 ENDIAN_BE,			/* endian */
-	 DATE_MSDHYM,		/* date_format */
-	 ADDRESS_24_BIT,	/* address_size */
-	 115200,			/* high_baud_rate */
-
-	 0x00000000,		/* rb_logbook_begin */
-	 0x00020000,		/* rb_logbook_end */
-	 256,				/* rb_log_size */
-	 512,				/* rb_max_log */
-	 0x00020000,		/* rb_profile_begin */
-	 0x00100000,		/* rb_profile_end */
-
-	 0x006,				/* pt_log_profile_begin */
-	 0x01e,				/* pt_log_profile_pre */
-	 0x046,				/* pt_log_dive_number */
-	 0x080,				/* pt_log_profile_end */
-
-	 0x046,				/* pt_conf_dive_count */
-	 0x06e,				/* pt_conf_last_log */
-	 0x071,				/* pt_conf_last_interdive */
+
+// Cochran Commander Nitrox
+cochran_device_info_t cochran_cmdr_device_info = {
+	COCHRAN_MODEL_COMMANDER_AIR_NITROX,		// model
+	ADDRESS_24_BIT,							// address_size
+	ENDIAN_BE,								// endian
+	115200,									// high_baud
+	0x00000000,								// rb_logbook_begin
+	0x00020000,								// rb_lobook_end
+	0x00020000,								// rb_profile_begin
+	0x00100000,								// rb_profile_end
+	256,									// log_size
+	512,									// max_log
+	2,										// max_gas
+	SAMPLE_CMDR,							// sample_format
+}; 
+
+cochran_conf_offsets_t cochran_cmdr_conf_offsets = {
+	0x046,		// dive_count
+	0x06e,		// last_log
+	0x071,		// last_interdive
+};
+
+cochran_log_offsets_t cochran_cmdr_log_offsets = {
+	1, 0, 3, 2, 5, 4,	// sec, min, hour, day, mon, year, 1 byte each
+	6,					// profile_begin_offset, 4 bytes
+	24,					// water_conductivity, 1 byte, 0=low(fresh), 2=high(sea)
+	30,					// profile_pre_offset, 4 bytes
+	45,					// start_temp, 1 byte, F
+	56,					// start_depth, 2 bytes, /4=ft
+	70,					// dive_number, 2 bytes
+	73,					// altitude, 1 byte, /4=kilofeet
+	128,				// profile_end_offset, 4 bytes
+	153,				// end_temp, 1 byte F
+	166,				// bt, 2 bytes, minutes
+	168,				// max_depth, 2 bytes, /4=ft
+	170,				// avg_depth, 2 bytes, /4=ft
+	210,				// o2_percent, 4 bytes (2 of) 2 bytes, /256=%
+	UNSUPPORTED,		// he_percent, 4 bytes (2 of) 2 bytes, /256=%
+	232,				// min_temp, 1 byte, /2+20=F
+	233,				// max_temp, 1 byte, /2+20=F
+};
+
+// inter-dive event lengths
+struct event_size cmdr_event_bytes[15] = {
+		{0x00, 17}, {0x01, 21}, {0x02, 18},
+		{0x03, 17}, {0x06, 19}, {0x07, 19},
+		{0x08, 19}, {0x09, 19}, {0x0a, 19},
+		{0x0b, 21}, {0x0c, 19}, {0x0d, 19},
+		{0x0e, 19}, {0x10, 21},
+		{  -1,  1} };
+
+// Cochran EMC-14
+cochran_device_info_t cochran_emc14_device_info = {
+	COCHRAN_MODEL_EMC_14,	// model
+	ADDRESS_32_BIT,			// address_size
+	ENDIAN_LE,				// endian
+	806400,					// high_baud
+	0x00000000,				// rb_logbook_begin
+	0x00020000,				// rb_lobook_end
+	0x00022000,				// rb_profile_begin
+	0x00200000,				// rb_profile_end
+	512,					// log_size
+	256,					// max_log
+	2,						// max_gas
+	SAMPLE_EMC,				// sample_format
+};
+
+// Cochran EMC-16
+cochran_device_info_t cochran_emc16_device_info = {
+	COCHRAN_MODEL_EMC_16,	// model
+	ADDRESS_32_BIT,			// address_size
+	ENDIAN_LE,				// endian
+	806400,					// high_baud
+	0x00000000,				// rb_logbook_begin
+	0x00080000,				// rb_lobook_end
+	0x00094000,				// rb_profile_begin
+	0x00800000,				// rb_profile_end
+	512,					// log_size
+	1024,					// max_log
+	2,						// max_gas
+	SAMPLE_EMC,				// sample_format
 };
 
-static cochran_layout_t cochran_layout_emc20 = {
-	 COCHRAN_MODEL_EMC_20,	/* model */
-	 ENDIAN_LE,			/* endian */
-	 DATE_SMHDMY,		/* date_format */
-	 ADDRESS_32_BIT,	/* address_size */
-	 806400,			/* high_baud_rate was 825600 */
-
-	 0x00000000,		/* rb_logbook_begin */
-	 0x00080000,		/* rb_logbook_end */
-	 512,				/* rb_log_size */
-	 1024,				/* rb_max_log */
-	 0x00094000,		/* rb_profile_begin */
-	 0x01000000,		/* rb_profile_end */
-
-	 0x006,				/* pt_log_profile_begin */
-	 0x01e,				/* pt_log_profile_pre */
-	 0x056,				/* pt_log_dive_number */
-	 0x100,				/* pt_log_profile_end */
-
-	 0x0d2,				/* pt_conf_dive_count */
-	 0x13e,				/* pt_conf_last_log */
-	 0x142,				/* pt_conf_last_interdive */
+
+// Cochran EMC-20
+cochran_device_info_t cochran_emc20_device_info = {
+	COCHRAN_MODEL_EMC_20,	// model
+	ADDRESS_32_BIT,			// address_size
+	ENDIAN_LE,				// endian
+	806400,					// high_baud
+	0x00000000,				// rb_logbook_begin
+	0x00080000,				// rb_lobook_end
+	0x00094000,				// rb_profile_begin
+	0x01000000,				// rb_profile_end
+	512,					// log_size
+	1024,					// max_log
+	2,						// max_gas
+	SAMPLE_EMC,				// sample_format
 };
 
-static cochran_layout_t cochran_layout_emc16 = {
-	 COCHRAN_MODEL_EMC_16,	/* model */
-	 ENDIAN_LE,			/* endian */
-	 DATE_SMHDMY,		/* date_format */
-	 ADDRESS_32_BIT,	/* address_size */
-	 806400,			/* high_baud_rate */
-
-	 0x00000000,		/* rb_logbook_begin */
-	 0x00080000,		/* rb_logbook_end */
-	 512,				/* rb_log_size */
-	 1024,				/* rb_max_log */
-	 0x00094000,		/* rb_profile_begin */
-	 0x00800000,		/* rb_profile_end */
-
-	 0x006,				/* pt_log_profile_begin */
-	 0x01e,				/* pt_log_profile_pre */
-	 0x056,				/* pt_log_dive_number */
-	 0x100,				/* pt_log_profile_end */
-
-	 0x0d2,				/* pt_conf_dive_count */
-	 0x13e,				/* pt_conf_last_log */
-	 0x142,				/* pt_conf_last_interdive */
+// Common EMC offsets
+cochran_conf_offsets_t cochran_emc_conf_offsets = {
+	0x0d2,		// dive_count
+	0x13e,		// last_log
+	0x142,		// last_interdive
 };
 
-static cochran_layout_t cochran_layout_emc14 = {
-	 COCHRAN_MODEL_EMC_14,	/* model */
-	 ENDIAN_LE,			/* endian */
-	 DATE_SMHDMY,		/* date_format */
-	 ADDRESS_32_BIT,	/* address_size */
-	 806400,			/* high_baud_rate */
-
-	 0x00000000,		/* rb_logbook_begin */
-	 0x00020000,		/* rb_logbook_end */
-	 512,				/* rb_log_size */
-	 256,				/* rb_max_log */
-	 0x00022000,		/* rb_profile_begin */
-	 0x00200000,		/* rb_profile_end */
-
-	 0x006,				/* pt_log_profile_begin */
-	 0x01e,				/* pt_log_profile_pre */
-	 0x056,				/* pt_log_dive_number */
-	 0x100,				/* pt_log_profile_end */
-
-	 0x0d2,				/* pt_conf_dive_count */
-	 0x13e,				/* pt_conf_last_log */
-	 0x142,				/* pt_conf_last_interdive */
+
+cochran_log_offsets_t cochran_emc_log_offsets = {
+	0, 1, 2, 3, 4, 5,	// sec, min, hour, day, mon, year, 1 byte each
+	6,					// profile_begin_offset, 4 bytes
+	24,					// water_conductivity, 1 byte 0=low(fresh), 2=high(sea)
+	30,					// profile_pre_offset, 4 bytes
+	55,					// start_temp, 1 byte, F
+	42,					// start_depth, 2 bytes, /256=ft
+	86,					// dive_number, 2 bytes,
+	89,					// altitude, 1 byte /4=kilofeet
+	256,				// profile_end_offset, 4 bytes
+	293,				// end_temp, 1 byte, F
+	304,				// bt, 2 bytes, minutes
+	306,				// max_depth, 2 bytes, /4=ft
+	310,				// avg_depth, 2 bytes, /4=ft
+	144,				// o2_percent, 6 bytes (3 of) 2 bytes, /256=%
+	164,				// he_percent, 6 bytes (3 of) 2 bytes, /256=%
+	403,				// min_temp, 1 byte, /2+20=F
+	407,				// max_temp, 1 byte, /2+20=F
 };
 
+struct event_size emc_event_bytes[15] = {
+		{0x00, 19}, {0x01, 23}, {0x02, 20},
+		{0x03, 19}, {0x06, 21}, {0x07, 21},
+		{0x0a, 21}, {0x0b, 21}, {0x0f, 19},
+		{0x10, 21},
+		{  -1,  1} };
+
 static const dc_device_vtable_t cochran_commander_device_vtable = {
 	sizeof (cochran_device_t),
 	DC_FAMILY_COCHRAN_COMMANDER,
@@ -161,7 +201,7 @@ static dc_status_t
 cochran_commander_read (dc_device_t *abstract, dc_event_progress_t *progress,
 		unsigned int address, unsigned char data[], unsigned int size);
 static void
-cochran_check_profiles(dc_device_t *abstract, cochran_layout_t *layout);
+cochran_check_profiles(dc_device_t *abstract);
 
 
 static dc_status_t
@@ -191,8 +231,7 @@ cochran_packet (cochran_device_t *device, dc_event_progress_t *progress,
 		serial_sleep(device->port, 45);
 
 		// Rates are odd, like 806400 for the EMC, 115200 for commander
-		cochran_layout_t *layout = cochran_commander_get_layout(device->model_string);
-		rc = serial_configure(device->port, layout->high_baud_rate, 8,
+		rc = serial_configure(device->port, device->info->high_baud, 8,
 						SERIAL_PARITY_NONE, 2, SERIAL_FLOWCONTROL_NONE);
 		if (rc == -1) {
 			// Assume we are talking to a pty, a simulator
@@ -295,7 +334,6 @@ cochran_commander_device_open (dc_device_t **out, dc_context_t *context,
 		const char *name)
 {
 	cochran_device_t *device = NULL;
-	cochran_layout_t *layout;
 	dc_status_t rc;
 
 	if (out == NULL)
@@ -334,10 +372,11 @@ cochran_commander_device_open (dc_device_t **out, dc_context_t *context,
 
 	memcpy(device->model_string, device->data.id + 0x3b, 8);
 	device->model_string[8] = 0;
-	layout = cochran_commander_get_layout(device->model_string);
 
-	// Check ID
-	if (!layout || (layout->model & 0xFF0000) == COCHRAN_MODEL_UNKNOWN) {
+	device->base.devinfo.model = cochran_commander_model_id(device->model_string);
+
+	if (device->base.devinfo.model == -1) {
+		// Unknown device
 		ERROR (context,
 			"Unknown Cochran model %02x %02x %02x %02x %02x %02x %02x %02x",
 			device->model_string[0], device->model_string[1],
@@ -349,6 +388,10 @@ cochran_commander_device_open (dc_device_t **out, dc_context_t *context,
 		return DC_STATUS_UNSUPPORTED;
 	}
 
+	// Get model capability, protocol and data format
+	cochran_commander_info(device->base.devinfo.model, &device->info,
+			&device->conf_ptr, &device->log_ptr, &device->event_bytes);
+
 	*out = (dc_device_t *) device;
 
 	return DC_STATUS_SUCCESS;
@@ -405,13 +448,12 @@ cochran_commander_read (dc_device_t *abstract, dc_event_progress_t *progress,
 		unsigned int address, unsigned char data[], unsigned int size)
 {
 	cochran_device_t *device = (cochran_device_t*) abstract;
-	cochran_layout_t *layout = cochran_commander_get_layout(device->model_string);
 
 	// Build the command
 	unsigned char command[10];
 	unsigned char command_size;
 
-	switch (layout->address_size)
+	switch (device->info->address_size)
 	{
 	case ADDRESS_32_BIT:
 		// EMC uses 32 bit addressing
@@ -438,7 +480,8 @@ cochran_commander_read (dc_device_t *abstract, dc_event_progress_t *progress,
 		command[6] = (size >> 16   ) & 0xff;
 		command[7] = 0x04;
 		command_size = 8;
-		break; default:
+		break;
+	default:
 		return DC_STATUS_UNSUPPORTED;
 	}
 
@@ -452,31 +495,74 @@ cochran_commander_read (dc_device_t *abstract, dc_event_progress_t *progress,
 }
 
 
-cochran_layout_t *
-cochran_commander_get_layout (const unsigned char *model)
+// Determine model descriptor number from model string
+int
+cochran_commander_model_id(const char *model)
+{
+	struct model_map_t {
+		char *model;
+		int id;
+	};
+
+	// Model strings are COCHRAN_MODEL_SIZE long
+	struct model_map_t model_map[5] = {
+		{ "AM\x11""2212\x02", COCHRAN_MODEL_COMMANDER_AIR_NITROX },
+		{ "AM7303\x8b\x43",   COCHRAN_MODEL_EMC_14 },
+		{ "AMA315\xC3\xC5",   COCHRAN_MODEL_EMC_16 },
+		{ "AM2315\xA3\x71",   COCHRAN_MODEL_EMC_20 },
+		{ "", -1 },
+	};
+
+	int x = 0;
+	while (model_map[x].id != -1 && strncmp(model, model_map[x].model, COCHRAN_MODEL_SIZE))
+		x++;
+
+	return model_map[x].id;
+}
+
+
+// Get device capability, protocol info and data format for a device
+dc_status_t
+cochran_commander_info (const int model, cochran_device_info_t **info, cochran_conf_offsets_t **conf_ptr, cochran_log_offsets_t **log_ptr, struct event_size (**event_bytes)[15])
 {
 	// Determine model
-	if (memcmp(model, "AM2315\xA3\x71", 8) == 0)
+	if (model == COCHRAN_MODEL_EMC_20)
 	{
-		return &cochran_layout_emc20;
+		if (info) *info = &cochran_emc20_device_info;
+		if (conf_ptr) *conf_ptr = &cochran_emc_conf_offsets;
+		if (log_ptr) *log_ptr = &cochran_emc_log_offsets;
+		if (event_bytes) *event_bytes = &emc_event_bytes;
 	}
-	else if (memcmp(model, "AMA315\xC3\xC5", 8) == 0)
+	else if (model == COCHRAN_MODEL_EMC_16)
 	{
-		return &cochran_layout_emc16;
+		if (info) *info = &cochran_emc16_device_info;
+		if (conf_ptr) *conf_ptr = &cochran_emc_conf_offsets;
+		if (log_ptr) *log_ptr = &cochran_emc_log_offsets;
+		if (event_bytes) *event_bytes = &emc_event_bytes;
 	}
-	else if (memcmp(model, "AM7303\x8b\x43", 8) == 0)
+	else if (model == COCHRAN_MODEL_EMC_14)
 	{
-		return &cochran_layout_emc14;
+		if (info) *info = &cochran_emc14_device_info;
+		if (conf_ptr) *conf_ptr = &cochran_emc_conf_offsets;
+		if (log_ptr) *log_ptr = &cochran_emc_log_offsets;
+		if (event_bytes) *event_bytes = &emc_event_bytes;
 	}
-	else if (memcmp(model, "AM\x11""2212\x02", 8) == 0)
+	else if (model == COCHRAN_MODEL_COMMANDER_AIR_NITROX)
 	{
-		return &cochran_layout_cmdr;
+		if (info) *info = &cochran_cmdr_device_info;
+		if (conf_ptr) *conf_ptr = &cochran_cmdr_conf_offsets;
+		if (log_ptr) *log_ptr = &cochran_cmdr_log_offsets;
+		if (event_bytes) *event_bytes = &cmdr_event_bytes;
+	}
+	else {
+		return DC_STATUS_UNSUPPORTED;
 	}
 
-	return NULL;
+	return DC_STATUS_SUCCESS;
 }
 
 
+
 static dc_status_t
 cochran_read_id (dc_device_t *abstract)
 {
@@ -537,11 +623,10 @@ cochran_read_misc (dc_device_t *abstract, dc_event_progress_t *progress)
 	cochran_device_t *device = (cochran_device_t *) abstract;
 	dc_status_t rc;
 	dc_event_vendor_t vendor;
-	cochran_layout_t *layout = cochran_commander_get_layout(device->model_string);
 
 	unsigned char command[7] = { 0x89, 0x05, 0x00, 0x00, 0x00, 0xDC, 0x05 };
 
-	switch (layout->model & 0xFF0000)
+	switch (device->info->model & 0xFF0000)
 	{
 	case COCHRAN_MODEL_COMMANDER_FAMILY:
 		command[2] = 0xCA;
@@ -613,14 +698,13 @@ cochran_find_fingerprint(dc_device_t *abstract)
 {
 	cochran_device_t *device = (cochran_device_t *) abstract;
 	cochran_data_t *d = (cochran_data_t *) &device->data;
-	cochran_layout_t *layout = cochran_commander_get_layout(device->model_string);
 
 	// Skip to fingerprint to reduce time
 	d->fp_dive_num = d->dive_count - 1;
 
 	while (d->fp_dive_num >= 0 && memcmp(&d->fingerprint,
-						 d->logbook + d->fp_dive_num * layout->rb_log_size
-						+ layout->pt_log_dive_number,
+						 d->logbook + d->fp_dive_num * device->info->log_size
+						+ device->log_ptr->dive_number,
 						 sizeof(d->fingerprint)))
 		d->fp_dive_num--;
 }
@@ -633,7 +717,6 @@ cochran_get_sample_parms(dc_device_t *abstract)
 	cochran_data_t *d = (cochran_data_t *) &device->data;
 	unsigned int pre_dive_offset = 0, end_dive_offset = 0;
 	unsigned int low_offset, high_offset;
-	cochran_layout_t *layout = cochran_commander_get_layout(device->model_string);
 
 	// Find lowest and highest offsets into sample data
 	low_offset = 0xFFFFFFFF;
@@ -641,10 +724,10 @@ cochran_get_sample_parms(dc_device_t *abstract)
 
 	int i;
 	for (i = d->fp_dive_num + 1; i < d->dive_count; i++) {
-		pre_dive_offset = array_uint32_le (&(d->logbook[i * layout->rb_log_size
-				+ layout->pt_log_profile_pre]));
-		end_dive_offset = array_uint32_le (&(d->logbook[i * layout->rb_log_size
-				+ layout->pt_log_profile_end]));
+		pre_dive_offset = array_uint32_le (&(d->logbook[i * device->info->log_size
+				+ device->log_ptr->profile_pre_offset]));
+		end_dive_offset = array_uint32_le (&(d->logbook[i * device->info->log_size
+				+ device->log_ptr->profile_end_offset]));
 
 		// Check for ring buffer wrap-around.
 		if (pre_dive_offset > end_dive_offset)
@@ -661,8 +744,7 @@ cochran_get_sample_parms(dc_device_t *abstract)
 		// I'll round to 128K, dives longer than 12 hrs aren't likely
 		// and memory in sizes not rounded to 128K might be odd.
 		high_offset = ((pre_dive_offset - 1) & 0xE0000) + 0x20000;
-		layout->rb_profile_end = high_offset;
-		low_offset = layout->rb_profile_begin;
+		low_offset = device->info->rb_profile_begin;
 		d->sample_data_offset = low_offset;
 		d->sample_size = high_offset - low_offset;
 	} else if (low_offset < 0xFFFFFFFF && high_offset > 0) {
@@ -719,15 +801,14 @@ cochran_commander_device_read_all (dc_device_t *abstract)
 	cochran_device_t *device = (cochran_device_t *) abstract;
 	cochran_data_t *d = (cochran_data_t *) &device->data;
 	dc_event_progress_t progress = {0};
-	cochran_layout_t *layout = cochran_commander_get_layout(device->model_string);
 
 	dc_status_t rc;
 	int max_config, max_logbook, max_sample;
 
 	// Calculate max data sizes
 	max_config = 512 * 2;
-	max_logbook =layout->rb_logbook_end - layout->rb_logbook_begin;
-	max_sample = layout->rb_profile_end - layout->rb_profile_begin;
+	max_logbook =device->info->rb_logbook_end - device->info->rb_logbook_begin;
+	max_sample = device->info->rb_profile_end - device->info->rb_profile_begin;
 
 	progress.current = 0;
 	progress.maximum = max_config + max_logbook + max_sample;
@@ -739,12 +820,12 @@ cochran_commander_device_read_all (dc_device_t *abstract)
 		return rc;
 
 	// Determine size of dive list to read. Round up to nearest 16K
-	if (layout->endian == ENDIAN_LE)
-		d->dive_count = array_uint16_le (d->config[0] + layout->pt_conf_dive_count);
+	if (device->info->endian == ENDIAN_LE)
+		d->dive_count = array_uint16_le (d->config[0] + device->conf_ptr->dive_count);
 	else
-		d->dive_count = array_uint16_be (d->config[0] + layout->pt_conf_dive_count);
+		d->dive_count = array_uint16_be (d->config[0] + device->conf_ptr->dive_count);
 
-	d->logbook_size = ((d->dive_count * layout->rb_log_size) & 0xFFFFC000)
+	d->logbook_size = ((d->dive_count * device->info->log_size) & 0xFFFFC000)
 		 + 0x4000;
 
 	progress.maximum -= max_logbook - d->logbook_size;
@@ -766,7 +847,7 @@ cochran_commander_device_read_all (dc_device_t *abstract)
 		return rc;
 
 	// Determine logs that haven't had profile data overwritten
-	cochran_check_profiles(abstract, layout);
+	cochran_check_profiles(abstract);
 
 	return DC_STATUS_SUCCESS;
 }
@@ -784,7 +865,6 @@ cochran_commander_device_dump (dc_device_t *abstract, dc_buffer_t *data)
 	cochran_data_t *d = (cochran_data_t *) &device->data;
 	dc_event_progress_t progress = {0};
 	dc_event_vendor_t vendor;
-	cochran_layout_t *layout = cochran_commander_get_layout(device->model_string);
 
 	dc_status_t rc;
 	int size;
@@ -796,7 +876,7 @@ cochran_commander_device_dump (dc_device_t *abstract, dc_buffer_t *data)
 	}
 
 	// Determine size for progress
-	size = 512 + 512 + 1500 + layout->rb_profile_end;
+	size = 512 + 512 + 1500 + device->info->rb_profile_end;
 
 	progress.current = 0;
 	progress.maximum = size;
@@ -824,7 +904,7 @@ cochran_commander_device_dump (dc_device_t *abstract, dc_buffer_t *data)
 	// Read logbook and sample memory
 
 	// Reserve space
-	if (!dc_buffer_resize(data, layout->rb_profile_end)) {
+	if (!dc_buffer_resize(data, device->info->rb_profile_end)) {
 		ERROR(abstract->context, "Insufficient buffer space available.");
 		return DC_STATUS_NOMEMORY;
 	}
@@ -836,7 +916,7 @@ cochran_commander_device_dump (dc_device_t *abstract, dc_buffer_t *data)
 
 	// Read the sample data, from 0 to sample end will include logbook
 	rc = cochran_commander_read (abstract, &progress, 0,
-			dc_buffer_get_data(data), layout->rb_profile_end);
+			dc_buffer_get_data(data), device->info->rb_profile_end);
 	if (rc != DC_STATUS_SUCCESS) {
 		ERROR (abstract->context, "Failed to read the sample data.");
 		return rc;
@@ -853,17 +933,17 @@ cochran_commander_device_dump (dc_device_t *abstract, dc_buffer_t *data)
 */
 
 static unsigned int
-cochran_guess_sample_end_address(cochran_layout_t *layout, cochran_data_t *data, unsigned int log_num)
+cochran_guess_sample_end_address(cochran_device_t *device, cochran_data_t *data, unsigned int log_num)
 {
-	const unsigned char *log_entry = data->logbook + layout->rb_log_size * log_num;
+	const unsigned char *log_entry = data->logbook + device->info->log_size * log_num;
 
 	if (log_num == data->dive_count)
 		// Return next usable address from config0 page
 		return array_uint32_le(data->config[0]
-			+ layout->rb_profile_end);
+			+ device->info->rb_profile_end);
 
 	// Next log's start address
-	return array_uint32_le(log_entry + layout->rb_log_size + 6);
+	return array_uint32_le(log_entry + device->info->log_size + 6);
 }
 
 
@@ -873,7 +953,6 @@ cochran_commander_device_foreach (dc_device_t *abstract,
 {
 	cochran_device_t *device = (cochran_device_t *) abstract;
 	cochran_data_t *data = &device->data;
-	cochran_layout_t *layout = cochran_commander_get_layout(device->model_string);
 
 	unsigned int sample_start_address, sample_end_address;
 	dc_status_t rc;
@@ -889,19 +968,19 @@ cochran_commander_device_foreach (dc_device_t *abstract,
 	// Loop through each dive
 	int i;
 	for (i = data->dive_count - 1; i > data->fp_dive_num; i--) {
-		log_entry = data->logbook + i * layout->rb_log_size;
+		log_entry = data->logbook + i * device->info->log_size;
 
 		sample_start_address = array_uint32_le (log_entry + 6);
-		sample_end_address = array_uint32_le (log_entry + layout->rb_log_size / 2);
+		sample_end_address = array_uint32_le (log_entry + device->info->log_size / 2);
 
 		if (sample_end_address == 0xFFFFFFFF)
 			// Corrupt dive, guess the end address
-			sample_end_address = cochran_guess_sample_end_address(layout, data, i);
+			sample_end_address = cochran_guess_sample_end_address(device, data, i);
 
 		sample = data->sample + sample_start_address - data->sample_data_offset;
 
 		// Determine size of sample
-		unsigned int dive_num =array_uint16_le(log_entry + layout->pt_log_dive_number);
+		unsigned int dive_num =array_uint16_le(log_entry + device->log_ptr->dive_number);
 		if (dive_num >= data->profile_tail)
 			sample_size = sample_end_address - sample_start_address;
 		else
@@ -909,32 +988,31 @@ cochran_commander_device_foreach (dc_device_t *abstract,
 
 		if (sample_size < 0)
 			// Adjust for ring buffer wrap-around
-			sample_size += layout->rb_profile_end
-				- layout->rb_profile_begin;
+			sample_size += device->info->rb_profile_end
+				- device->info->rb_profile_begin;
 
-		fingerprint = log_entry + layout->pt_log_dive_number;
+		fingerprint = log_entry + device->log_ptr->dive_number;
 
 		// Build dive blob
-		dive_size = COCHRAN_MODEL_SIZE + layout->rb_log_size + sample_size;
+		dive_size = device->info->log_size + sample_size;
 		dive = (unsigned char *) malloc(dive_size);
 		if (dive == NULL)
 			return DC_STATUS_NOMEMORY;
 
-		memcpy(dive, data->id + 0x3B, 8);       // model string
-		memcpy(dive + COCHRAN_MODEL_SIZE, log_entry, layout->rb_log_size); // log
+		memcpy(dive, log_entry, device->info->log_size); // log
 
 		// Copy profile data
 		if (sample_size) {
 			if (sample_start_address <= sample_end_address) {
-				memcpy(dive + COCHRAN_MODEL_SIZE + layout->rb_log_size, sample,
+				memcpy(dive + device->info->log_size, sample,
 					sample_size);
 			} else {
 				// It wrapped the buffer, copy two sections
-				unsigned int size = layout->rb_profile_end - sample_start_address;
+				unsigned int size = device->info->rb_profile_end - sample_start_address;
 
-				memcpy(dive + COCHRAN_MODEL_SIZE + layout->rb_log_size, sample, size);
-				memcpy(dive + COCHRAN_MODEL_SIZE + layout->rb_log_size + size,
-					data->sample, sample_end_address - layout->rb_profile_begin);
+				memcpy(dive + device->info->log_size, sample, size);
+				memcpy(dive + device->info->log_size + size,
+					data->sample, sample_end_address - device->info->rb_profile_begin);
 			}
 		}
 
@@ -960,16 +1038,16 @@ cochran_commander_device_foreach (dc_device_t *abstract,
 */
 
 static void
-cochran_check_profiles(dc_device_t *abstract, cochran_layout_t *layout) {
+cochran_check_profiles(dc_device_t *abstract) {
 	cochran_device_t *device = (cochran_device_t *) abstract;
 
 	// Find head log entry
 	// Set handy point to first dive's dive number
-	unsigned char *l = device->data.logbook + layout->pt_log_dive_number;
+	unsigned char *l = device->data.logbook + device->log_ptr->dive_number;
 	int head;
-	for (head = 0; head < layout->rb_max_log - 1; head ++) {
+	for (head = 0; head < device->info->max_log - 1; head ++) {
 		int this_dive = array_uint16_le(l);
-		l += layout->rb_log_size;
+		l += device->info->log_size;
 		int next_dive = array_uint16_le(l);
 
 		if (next_dive < this_dive || next_dive == 0xFFFF)
@@ -978,10 +1056,10 @@ cochran_check_profiles(dc_device_t *abstract, cochran_layout_t *layout) {
 
 	// This is the last piece of data
 	unsigned int head_ptr;
-	if (layout->endian == ENDIAN_LE)
-		head_ptr = array_uint32_le(device->data.config[0] + layout->pt_conf_last_log);
+	if (device->info->endian == ENDIAN_LE)
+		head_ptr = array_uint32_le(device->data.config[0] + device->conf_ptr->last_log);
 	else
-		head_ptr = array_uint32_be(device->data.config[0] + layout->pt_conf_last_log);
+		head_ptr = array_uint32_be(device->data.config[0] + device->conf_ptr->last_log);
 
 	// Cochran (commander at least) erases 8k blocks, so round up
 	head_ptr = (head_ptr & 0xfffff000) + 0x2000;
@@ -993,16 +1071,16 @@ cochran_check_profiles(dc_device_t *abstract, cochran_layout_t *layout) {
 	int profile_wrap = 0;
 	int n = head - 1;
 	if (n < 0)
-		n = MIN(layout->rb_max_log - 1, head);
+		n = MIN(device->info->max_log - 1, head);
 
 	while (n != head) {
-		unsigned int start_dive_ptr = array_uint32_le(device->data.logbook + layout->rb_log_size * n + layout->pt_log_profile_begin);
-		unsigned int end_dive_ptr = array_uint32_le(device->data.logbook + layout->rb_log_size * n + layout->pt_log_profile_end);
+		unsigned int start_dive_ptr = array_uint32_le(device->data.logbook + device->info->log_size * n + device->log_ptr->profile_begin_offset);
+		unsigned int end_dive_ptr = array_uint32_le(device->data.logbook + device->info->log_size * n + device->log_ptr->profile_end_offset);
 
 		if (start_dive_ptr == 0xFFFFFFFF || start_dive_ptr == 0) {
 			n--;
 			if (n < 0)
-				n = MIN(layout->rb_max_log - 1, head);
+				n = MIN(device->info->max_log - 1, head);
 			continue;
 		}
 
@@ -1020,7 +1098,7 @@ cochran_check_profiles(dc_device_t *abstract, cochran_layout_t *layout) {
 
 		n--;
 		if (n < 0)
-			n = MIN(layout->rb_max_log - 1, head);
+			n = MIN(device->info->max_log - 1, head);
 	}
 
 	device->data.log_head = head;
diff --git a/src/cochran_commander.h b/src/cochran_commander.h
index 981750c..22f17a3 100644
--- a/src/cochran_commander.h
+++ b/src/cochran_commander.h
@@ -26,11 +26,7 @@
 #define COCHRAN_MODEL_SIZE			8
 #define COCHRAN_FINGERPRINT_SIZE	2
 
-#define COCHRAN_EMC_LOG_SIZE		512
-#define COCHRAN_EMC_SAMPLE_SIZE		3
-
-#define COCHRAN_CMDR_LOG_SIZE		256
-#define COCHRAN_CMDR_SAMPLE_SIZE	2
+#define UNSUPPORTED 0xFFFFFFFF
 
 typedef enum cochran_model_t {
 	COCHRAN_MODEL_UNKNOWN = 0,
@@ -47,60 +43,65 @@ typedef enum cochran_endian_t {
 	ENDIAN_BE,
 } cochran_endian_t;
 
-typedef enum cochran_dateformat_t {
-	DATE_SMHDMY,
-	DATE_MSDHYM,
-} cochran_dateformat_t;
-
 typedef enum cochran_addresssize_t {
 	ADDRESS_24_BIT,
 	ADDRESS_32_BIT,
 } cochran_addresssize_t;
 
-typedef struct cochran_layout_t {
+typedef enum cochran_sample_format_t {
+	SAMPLE_UNKNOWN,
+	SAMPLE_CMDR,
+	SAMPLE_EMC,
+} cochran_sample_format_t;
+
+// Information about the capabilities of a device
+// Used in reading the information from the device
+// and (in the case of max_*) used when decoding.
+typedef struct cochran_device_info_t {
 	cochran_model_t model;
-	cochran_endian_t endian;
-	cochran_dateformat_t date_format;
 	cochran_addresssize_t address_size;
-	unsigned int high_baud_rate;
-
-	unsigned int rb_logbook_begin;
-	unsigned int rb_logbook_end;
-	unsigned int rb_log_size;
-	unsigned int rb_max_log;
-	unsigned int rb_profile_begin;
-	unsigned int rb_profile_end;
-
-	unsigned int pt_log_profile_begin;
-	unsigned int pt_log_profile_pre;
-	unsigned int pt_log_dive_number;
-	unsigned int pt_log_profile_end;
-
-	unsigned int pt_conf_dive_count;
-	unsigned int pt_conf_last_log;
-	unsigned int pt_conf_last_interdive;
-} cochran_layout_t;
-
-// Device configuration items
-/*
-typedef struct cochran_config_t {
-	cochran_model_t model;
+	cochran_endian_t endian;
+	int high_baud;
+
+	int rb_logbook_begin;
+	int rb_logbook_end;
+	int rb_profile_begin;
+	int rb_profile_end;
+
 	int log_size;
-	int sample_memory_start_address;
-	int sample_memory_end_address;
-	int dive_num_ptr;
-	int dive_count_ptr;
-	int dive_count_endian;
-	int sample_end_ptr;
-	int log_pre_dive_ptr;
-	int log_end_dive_ptr;
-	int last_interdive_ptr;
-	int last_entry_ptr;
-	int date_format;
-	int address_length;
-	int high_baud; 			// baud rate to switch to for log/sample download
-} cochran_config_t;
-*/
+	int max_log;
+	int max_gas;
+
+	cochran_sample_format_t sample_format;
+} cochran_device_info_t;
+
+// Offsets into the conf block
+typedef struct cochran_conf_offsets_t {
+	int dive_count;
+	int last_log;
+	int last_interdive;
+} cochran_conf_offsets_t;
+
+// Offsets into each log block
+typedef struct cochran_log_offsets_t {
+	int sec, min, hour, day, mon, year;
+	int profile_begin_offset;
+	int water_conductivity;
+	int profile_pre_offset;
+	int start_temp;
+	int start_depth;
+	int dive_number;
+	int altitude;
+	int profile_end_offset;
+	int end_temp;
+	int bt;
+	int max_depth;
+	int avg_depth;
+	int o2_percent;
+	int he_percent;
+	int min_temp;
+	int max_temp;
+} cochran_log_offsets_t;
 
 typedef struct cochran_data_t {
 	unsigned char id0[67];
@@ -125,59 +126,24 @@ typedef struct cochran_data_t {
 	unsigned int sample_size;
 } cochran_data_t;
 
+struct event_size {
+    int code;
+    int size;
+};
+
 typedef struct cochran_device_t {
 	dc_device_t base;
 	const char *name;				// serial port name
 	serial_t *port;
 	unsigned char model_string[COCHRAN_MODEL_SIZE + 1];
+	cochran_device_info_t *info;
+	cochran_conf_offsets_t *conf_ptr;
+	cochran_log_offsets_t *log_ptr;
+	struct event_size (*event_bytes)[15];
 	cochran_data_t data;			// dive data used in parsing
 } cochran_device_t;
 
 
-// Commander log fields
-#define CMD_SEC					  1
-#define CMD_MIN					  0
-#define CMD_HOUR				  3
-#define CMD_DAY					  2
-#define CMD_MON					  5
-#define CMD_YEAR				  4
-#define CMD_START_OFFSET		  6		// 4 bytes
-#define CMD_WATER_CONDUCTIVITY	 24		// 1 byte, 0=low, 2=high
-#define CMD_START_TEMP			 45		// 1 byte, F
-#define CMD_START_DEPTH			 56		// 2 byte, /4=ft
-#define CMD_ALTITUDE			 73		// 1 byte, /4=Kilofeet
-#define CMD_END_OFFSET			128		// 4 bytes
-#define CMD_END_TEMP			153		// 1 byte, F
-#define CMD_BT					166		// 2 bytes, minutes
-#define CMD_MAX_DEPTH			168		// 2 bytes, /4=ft
-#define CMD_AVG_DEPTH			170		// 2 bytes, /4=ft
-#define CMD_O2_PERCENT			210		// 8 bytes, 4 x 2 byte, /256=%
-#define CMD_MIN_TEMP			232		// 1 byte, /2+20=F
-#define CMD_MAX_TEMP			233		// 1 byte, /2+20=F
-
-// EMC log fields
-#define EMC_SEC					  0
-#define EMC_MIN					  1
-#define EMC_HOUR				  2
-#define EMC_DAY					  3
-#define EMC_MON					  4
-#define EMC_YEAR				  5
-#define EMC_START_OFFSET		  6		// 4 bytes
-#define EMC_WATER_CONDUCTIVITY	 24		// 1 byte, 0=low, 2=high
-#define EMC_START_DEPTH			 42		// 2 byte, /256=ft
-#define EMC_START_TEMP			 55		// 1 byte, F
-#define EMC_ALTITUDE			 89		// 1 byte, /4=Kilofeet
-#define EMC_O2_PERCENT			144		// 20 bytes, 10 x 2 bytes, /256=%
-#define EMC_HE_PERCENT			164		// 20 bytes, 10 x 2 bytes, /256=%
-#define EMC_END_OFFSET			256		// 4 bytes
-#define EMC_END_TEMP			293		// 1 byte, F
-#define EMC_BT					304		// 2 bytes, minutes
-#define EMC_MAX_DEPTH			306		// 2 bytes, /4=ft
-#define EMC_AVG_DEPTH			310		// 2 bytes, /4=ft
-#define EMC_MIN_TEMP			403		// 1 byte, /2+20=F
-#define EMC_MAX_TEMP			407		// 1 byte, /2+20=F
-
-
 dc_status_t cochran_commander_device_open (dc_device_t **out,
 		dc_context_t *context, const char *name);
 dc_status_t cochran_commander_device_close (dc_device_t *abstract);
@@ -189,4 +155,6 @@ dc_status_t cochran_commander_device_foreach (dc_device_t *abstract,
 		dc_dive_callback_t callback, void *userdata);
 dc_status_t cochran_commander_device_dump (dc_device_t *abstract,
 		dc_buffer_t *data);
-cochran_layout_t *cochran_commander_get_layout(const unsigned char *model);
+int cochran_commander_model_id(const char *model);
+dc_status_t cochran_commander_info(const int model,
+		cochran_device_info_t **info, cochran_conf_offsets_t **conf_ptr, cochran_log_offsets_t **log_ptr, struct event_size (**event_bytes)[15]);
diff --git a/src/cochran_commander_parser.c b/src/cochran_commander_parser.c
index a6380e4..45184fe 100644
--- a/src/cochran_commander_parser.c
+++ b/src/cochran_commander_parser.c
@@ -20,6 +20,7 @@
  */
 
 #include <stdlib.h>
+#include <math.h>
 
 #include <libdivecomputer/units.h>
 #include <libdivecomputer/cochran.h>
@@ -34,8 +35,39 @@
 #include "cochran_commander_parser.h"
 
 
-dc_status_t cochran_commander_parser_set_data (dc_parser_t *abstract,
-		const unsigned char *data, unsigned int size);
+static cochran_events_t cochran_events[] = {
+	{ 0xA8, 1, SAMPLE_EVENT_SURFACE, SAMPLE_FLAGS_BEGIN },	// Entered PDI mode
+	{ 0xA9, 1, SAMPLE_EVENT_SURFACE, SAMPLE_FLAGS_END },	// Exited PDI mode
+	{ 0xAB, 5, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Ceiling decrease
+	{ 0xAD, 5, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Ceiling increase
+	{ 0xBD, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Switched to nomal PO2 setting
+	{ 0xC0, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Switched to FO2 21% mode
+	{ 0xC1, 1, SAMPLE_EVENT_ASCENT,	SAMPLE_FLAGS_BEGIN },	// Ascent rate greater than limit
+	{ 0xC2, 1, SAMPLE_EVENT_BATTERY, SAMPLE_FLAGS_NONE },	// Low battery warning
+	{ 0xC3, 1, SAMPLE_EVENT_OLF,	SAMPLE_FLAGS_NONE },	// CNS Oxygen toxicity warning
+	{ 0xC4, 1, SAMPLE_EVENT_MAXDEPTH, SAMPLE_FLAGS_NONE },	// Depth exceeds user set point
+	{ 0xC5, 1, SAMPLE_EVENT_DEEPSTOP, SAMPLE_FLAGS_BEGIN },	// Entered decompression mode
+	{ 0xC8, 1, SAMPLE_EVENT_FLOOR,	SAMPLE_FLAGS_BEGIN },	// PO2 too high
+	{ 0xCC, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_BEGIN },	// Low Cylinder 1 pressure
+	{ 0xCE, 1, SAMPLE_EVENT_RBT,	SAMPLE_FLAGS_BEGIN },	// Non-decompression warning
+	{ 0xCD, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Switched to deco blend
+	{ 0xD0, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_BEGIN },	// Breathing rate alarm
+	{ 0xD3, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Low gas 1 flow rate
+	{ 0xD6, 1, SAMPLE_EVENT_CEILING, SAMPLE_FLAGS_BEGIN },	// Depth is less than ceiling
+	{ 0xD8, 1, SAMPLE_EVENT_DEEPSTOP, SAMPLE_FLAGS_END },	// End decompression mode
+	{ 0xE1, 1, SAMPLE_EVENT_ASCENT,	SAMPLE_FLAGS_END },		// End ascent rate warning
+	{ 0xE2, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Low SBAT battery warning
+	{ 0xE3, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Switched to FO2 mode
+	{ 0xE5, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Switched to PO2 mode
+	{ 0xEE, 1, SAMPLE_EVENT_RBT,	SAMPLE_FLAGS_END },		// End non-decompresison warning
+	{ 0xEF, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Switch to blend 2
+	{ 0xF0, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_END },		// Breathing rate alarm
+	{ 0xF3, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Switch to blend 1
+	{ 0xF6, 1, SAMPLE_EVENT_CEILING, SAMPLE_FLAGS_END },	// End Depth is less than ceiling
+	{ 0x00, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE }
+};
+
+
 dc_status_t cochran_commander_parser_get_datetime (dc_parser_t *abstract,
 		dc_datetime_t *datetime);
 static dc_status_t cochran_commander_parser_get_field (dc_parser_t *abstract,
@@ -48,7 +80,10 @@ typedef struct cochran_commander_parser_t cochran_commander_parser_t;
 
 struct cochran_commander_parser_t {
 	dc_parser_t base;
-	cochran_layout_t *layout;
+	unsigned int model;
+	cochran_device_info_t *info;
+	cochran_log_offsets_t *log_ptr;
+	struct event_size (*event_bytes)[15];
 };
 
 static dc_parser_vtable_t cochran_commander_parser_vtable = {
@@ -64,7 +99,7 @@ static dc_parser_vtable_t cochran_commander_parser_vtable = {
 
 
 dc_status_t
-cochran_commander_parser_create (dc_parser_t **out, dc_context_t *context)
+cochran_commander_parser_create (dc_parser_t **out, dc_context_t *context, int model)
 {
 	cochran_commander_parser_t *parser = NULL;
 
@@ -78,7 +113,9 @@ cochran_commander_parser_create (dc_parser_t **out, dc_context_t *context)
 		return DC_STATUS_NOMEMORY;
 	}
 
-	parser->layout = NULL;
+	parser->model = model;
+
+	cochran_commander_info(model, &parser->info, NULL, &parser->log_ptr, &parser->event_bytes);
 
 	*out = (dc_parser_t *) parser;
 
@@ -90,99 +127,28 @@ dc_status_t
 cochran_commander_parser_set_data (dc_parser_t *abstract,
 		const unsigned char *data, unsigned int size)
 {
-	cochran_commander_parser_t *parser = (cochran_commander_parser_t *) abstract;
-
-	// Determine cochran data format
-	// abstract->data is prefixed by the model string
-	// FIXME: Instead of prefixing the model string, pass the model
-	// number to the cochran_commander_parser_create() function. That's
-	// how other backends do this.
-	if (size >= COCHRAN_MODEL_SIZE)
-		parser->layout = cochran_commander_get_layout(data);
-
 	return DC_STATUS_SUCCESS;
 }
 
 
-// There are two date formats used by Cochran
 dc_status_t
 cochran_commander_parser_get_datetime (dc_parser_t *abstract,
 		dc_datetime_t *datetime)
 {
 	cochran_commander_parser_t *parser = (cochran_commander_parser_t *) abstract;
-	const unsigned char *data = abstract->data;
-	const unsigned char *log_entry = data + COCHRAN_MODEL_SIZE;
-
-	// FIXME: See my comment regarding a layout structure below.
-
-	if (parser->layout->date_format == DATE_SMHDMY) {
-		datetime->second = log_entry[0];
-		datetime->minute = log_entry[1];
-		datetime->hour = log_entry[2];
-		datetime->day = log_entry[3];
-		datetime->month = log_entry[4];
-		datetime->year = log_entry[5] + (log_entry[5] > 91 ? 1900 : 2000);
-	} else {
-		datetime->second = log_entry[1];
-		datetime->minute = log_entry[0];
-		datetime->hour = log_entry[3];
-		datetime->day = log_entry[2];
-		datetime->month = log_entry[5];
-		datetime->year = log_entry[4] + (log_entry[4] > 91 ? 1900 : 2000);
-	}
+	cochran_log_offsets_t *l = parser->log_ptr;
+	const unsigned char *log_entry = abstract->data;
 
-	return DC_STATUS_SUCCESS;
-}
+	datetime->second = log_entry[l->sec];
+	datetime->minute = log_entry[l->min];
+	datetime->hour = log_entry[l->hour];
+	datetime->day = log_entry[l->day];
+	datetime->month = log_entry[l->mon];
+	datetime->year = log_entry[l->year] + (log_entry[l->year] > 91 ? 1900 : 2000);
 
-static dc_status_t
-cochran_commander_parser_get_field (dc_parser_t *abstract, dc_field_type_t type,
-			unsigned int flags, void *value)
-{
-	cochran_commander_parser_t *parser = (cochran_commander_parser_t *) abstract;
-
-	// FIXME: These two get_field functions appear very similar. I think
-	// you can easily merge them if you introduce a layout structure
-	// where you have the offset of each field. Check the uwatec parser
-	// with the uwatec_smart_header_info_t struct for an example. You
-	// already have the EMC_xxx and CMD_xxx macros.
-
-	// FIXME: I suggest using the same model numbers as in descriptor.c
-	// and just list all models explictly with extra case statements.
-	switch (parser->layout->model & 0xFF0000)
-	{
-	case COCHRAN_MODEL_COMMANDER_FAMILY:
-		return cochran_cmdr_parser_get_field(abstract, type, flags, value);
-	case COCHRAN_MODEL_EMC_FAMILY:
-		return cochran_emc_parser_get_field(abstract, type, flags, value);
-	}
-
-	return DC_STATUS_UNSUPPORTED;
+	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;
-
-	// FIXME: Same remarks here. At first sight these two functions look
-	// again very similar. I wonder if we can merge them and deal with
-	// the difference by means of the info in a layout structure?
-
-	switch (parser->layout->model & 0xFF0000)
-	{
-	case COCHRAN_MODEL_COMMANDER_FAMILY:
-		return cochran_cmdr_parser_samples_foreach(abstract, callback,
-				userdata);
-		break;
-	case COCHRAN_MODEL_EMC_FAMILY:
-		return cochran_emc_parser_samples_foreach(abstract, callback,
-				userdata);
-		break;
-	}
-
-	return DC_STATUS_UNSUPPORTED;
-}
 
 void
 cochran_commander_get_event_info(const unsigned char code,
@@ -197,6 +163,7 @@ cochran_commander_get_event_info(const unsigned char code,
 	*event = events[i];
 }
 
+
 int
 cochran_commander_handle_event (dc_parser_t *abstract,
 		dc_sample_callback_t callback, void *userdata, unsigned char code,
@@ -256,20 +223,347 @@ cochran_commander_handle_event (dc_parser_t *abstract,
 int cochran_backparse(dc_parser_t *abstract, unsigned const char *samples, int size, struct event_size (*event_bytes)[15]) {
 	int result = size, best_result = size;
 
-    for (int x = 0; (*event_bytes)[x].code != -1; x++) {
-        int ptr = size - (*event_bytes)[x].size;
-        if (ptr > 0 && samples[ptr] == (*event_bytes)[x].code) {
+	for (int x = 0; (*event_bytes)[x].code != -1; x++) {
+		int ptr = size - (*event_bytes)[x].size;
+		if (ptr > 0 && samples[ptr] == (*event_bytes)[x].code) {
 			// Recurse to find the largest match. Because we are parsing backwards
 			// and the events vary in size we can't be sure the byte that matches
 			// the event code is an event code or data from inside a longer or shorter
 			// event.
-            result = cochran_backparse(abstract, samples, ptr, event_bytes);
-        }
+			result = cochran_backparse(abstract, samples, ptr, event_bytes);
+		}
+
+		if (result < best_result) {
+			best_result = result;
+		}
+	}
+
+	return best_result;
+}
+
+
+dc_status_t
+cochran_commander_parser_get_field (dc_parser_t *abstract, dc_field_type_t type,
+		unsigned int flags, void *value)
+{
+	const cochran_commander_parser_t *parser = (cochran_commander_parser_t *) abstract;
+	const cochran_log_offsets_t *l = parser->log_ptr;
+	const unsigned char *log_entry = abstract->data;
+	unsigned int minutes = 0, qfeet = 0;
+
+	dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
+	dc_salinity_t *water = (dc_salinity_t *) value;
+
+	if (value) {
+		switch (type) {
+		case DC_FIELD_TEMPERATURE_SURFACE:
+			*((unsigned int*) value) = (log_entry[l->start_temp] - 32.0) / 1.8;
+			break;
+		case DC_FIELD_TEMPERATURE_MINIMUM:
+			if (log_entry[l->min_temp] == 0xFF)
+				return DC_STATUS_UNSUPPORTED;
+			else
+				*((unsigned int*) value) = (log_entry[l->min_temp] / 2.0
+					+ 20 - 32) / 1.8;
+			break;
+		case DC_FIELD_TEMPERATURE_MAXIMUM:
+			if (log_entry[l->max_temp] == 0xFF)
+				return DC_STATUS_UNSUPPORTED;
+			else
+				*((unsigned int*) value) = (log_entry[l->max_temp] / 2.0
+					+ 20 - 32) / 1.8;
+			break;
+		case DC_FIELD_DIVETIME:
+			minutes = array_uint16_le(log_entry + l->bt);
+
+			if (minutes == 0xFFFF)
+				return DC_STATUS_UNSUPPORTED;
+			else
+				*((unsigned int *) value) = minutes * 60;
+			break;
+		case DC_FIELD_MAXDEPTH:
+			qfeet = array_uint16_le(log_entry + l->max_depth);
+			if (qfeet == 0xFFFF)
+				return DC_STATUS_UNSUPPORTED;
+			else
+				*((double *) value) = qfeet / 4.0 * FEET;
+			break;
+		case DC_FIELD_AVGDEPTH:
+			qfeet = array_uint16_le(log_entry + l->avg_depth);
+			if (qfeet == 0xFFFF)
+				return DC_STATUS_UNSUPPORTED;
+			else
+				*((double *) value) = qfeet / 4.0 * FEET;
+			break;
+		case DC_FIELD_GASMIX_COUNT:
+			*((unsigned int *) value) = 2;
+			break;
+		case DC_FIELD_GASMIX:
+			if (flags < 0 || flags >= parser->info->max_gas) {
+				return DC_STATUS_UNSUPPORTED;
+			}
+
+			// Gas percentages are decimal and encoded as
+			// highbyte = integer portion
+			// lowbyte = decimal portion, divide by 256 to get decimal value
+			gasmix->oxygen = array_uint16_le (log_entry
+					+ l->o2_percent + 2 * flags) / 256.0 / 100;
+			if (l->he_percent == UNSUPPORTED) {
+				gasmix->helium = 0;
+			} else {
+				gasmix->helium = array_uint16_le (log_entry
+					+ l->he_percent + 2 * flags) / 256.0 / 100;
+			}
+			gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium;
+			break;
+		case DC_FIELD_SALINITY:
+			// 0x00 = low conductivity, 0x10 = high, maybe there's a 0x01 and 0x11?
+			// Assume Cochran's conductivity ranges from 0 to 3
+			// 0 is fresh water, anything else is sea water
+			// for density assume
+			//  	0 = 1000kg/m³, 2 = 1025kg/m³
+			// and other values are linear
+			if ((log_entry[l->water_conductivity] & 0x3) == 0)
+				water->type = DC_WATER_FRESH;
+			else
+				water->type = DC_WATER_SALT;
+			water->density = 1000 + 12.5 * (log_entry[l->water_conductivity] & 0x3);
+			break;
+		case DC_FIELD_ATMOSPHERIC:
+			// Cochran measures air pressure and stores it as altitude.
+			// Convert altitude (measured in 1/4 kilofeet) back to pressure.
+			*(double *) value = ATM / BAR * pow(1 - 0.0000225577
+					* log_entry[l->altitude] * 250.0 * FEET, 5.25588);
+			break;
+		default:
+			return DC_STATUS_UNSUPPORTED;
+		}
+	}
+
+	return DC_STATUS_SUCCESS;
+}
+
+
+dc_status_t
+cochran_commander_parser_samples_foreach (dc_parser_t *abstract,
+		dc_sample_callback_t callback, void *userdata)
+{
+	const cochran_commander_parser_t *parser = (cochran_commander_parser_t *) abstract;
+	const cochran_log_offsets_t *l = parser->log_ptr;
+	const unsigned char *log_entry = abstract->data;
+	const unsigned char *samples = log_entry + parser->info->log_size;
+
+	unsigned int size = abstract->size - parser->info->log_size;
+
+	int sampling_size;
+	switch (parser->info->sample_format) {
+	case SAMPLE_CMDR:
+		sampling_size = 2;
+		break;
+	case SAMPLE_EMC:
+		sampling_size = 3;
+		break;
+	}
+
+	dc_sample_value_t sample = {0};
+	unsigned int time = 0, last_sample_time = 0;
+	unsigned int offset = 0;
+	double start_depth = 0;
+	int depth_qfeet = 0;	// 3 inch increments
+	unsigned char deco_obligation = 0;
+	unsigned int deco_ceiling = 0;
+	unsigned char corrupt_dive = 0;
+
+	// In rare circumstances Cochran computers won't record the end-of-dive
+	// log entry block. When the end-sample pointer is 0xFFFFFFFF it's corrupt.
+	// That means we don't really know where the dive samples end and we don't
+	// know what the dive summary values are (i.e. max depth, min temp)
+	if (array_uint32_le(log_entry + l->profile_end_offset) == 0xFFFFFFFF) {
+		corrupt_dive = 1;
+
+		WARNING(abstract->context, "Incomplete dive on %02d/%02d/%02d at %02d:%02d:%02d, trying to parse samples",
+				log_entry[l->year], log_entry[l->mon], log_entry[l->day],
+				log_entry[l->hour], log_entry[l->min], log_entry[l->sec]);
+
+		// Eliminate inter-dive events
+		size = cochran_backparse(abstract, samples, size, parser->event_bytes);
+	}
+
+	// Cochran samples depth every second and varies between ascent rate
+	// and temp every other second.
+
+	// Prime values from the dive log section
+	if (parser->model == COCHRAN_MODEL_COMMANDER_AIR_NITROX) {
+		// Commander stores start depth in quarter-feet
+		start_depth = array_uint16_le (log_entry + l->start_depth) / 4;
+	} else {
+		// EMC stores start depth in 256ths of a foot.
+		start_depth = array_uint16_le (log_entry + l->start_depth) / 256;
+	}
+
+	last_sample_time = sample.time = time;
+	if (callback) callback (DC_SAMPLE_TIME, sample, userdata);
+
+	sample.depth = start_depth * FEET;
+	if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
 
-        if (result < best_result) {
-            best_result = result;
-        }
-    }
+	sample.temperature = (log_entry[l->start_temp] - 32.0) / 1.8;
+	if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
+
+	while (offset < size) {
+		const unsigned char *s = samples + offset;
+
+		sample.time = time;
+		if (callback && last_sample_time != sample.time) {
+			// We haven't issued this time yet.
+			last_sample_time = sample.time;
+			callback (DC_SAMPLE_TIME, sample, userdata);
+		}
+
+		// If corrupt_dive end before offset
+		if (corrupt_dive) {
+			// When we aren't sure where the sample data ends we can
+			// look for events that shouldn't be in the sample data.
+			// 0xFF is unwritten memory
+			// 0xA8 indicates start of post-dive interval
+			// 0xE3 (switch to FO2 mode) and 0xF3 (switch to blend 1) occur
+			// at dive start so when we see them after the first second we
+			// found the beginning of the next dive.
+			if (s[0] == 0xFF || s[0] == 0xA8) {
+				INFO(abstract->context, "Used corrupt dive breakout 1 on event %02x", s[0]);
+				break;
+			}
+			if (time > 1 && (s[0] == 0xE3 || s[0] == 0xF3)) {
+				INFO(abstract->context, "Used corrupt dive breakout 2 on event %02x", s[0]);
+				break;
+			}
+		}
+
+		// Check for event
+		if (s[0] & 0x80) {
+			offset += cochran_commander_handle_event(abstract, callback,
+				userdata, s[0], time);
+
+			if (parser->info->sample_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
+
+					sample.deco.type = DC_DECO_DECOSTOP;
+					sample.deco.time = (array_uint16_le(s + sampling_size) + 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 + sampling_size) + 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.gasmix = 1;
+					if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata);
+					break;
+				case 0xF3:  // Switched to gas blend 1
+					sample.gasmix = 0;
+					if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata);
+					break;
+				}
+			}
 
-    return best_result;
+			continue;
+		}
+
+		// Depth is logged as change in feet, bit 0x40 means negative depth
+		if (s[0] & 0x40)
+			depth_qfeet -= (s[0] & 0x3f);
+		else
+			depth_qfeet += (s[0] & 0x3f);
+
+		sample.depth = (start_depth + depth_qfeet / 4.0) * FEET;
+		if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
+
+		// Ascent rate is logged in the 0th sample, temp in the 1st, repeat.
+		if (time % 2 == 0) {
+			double ascent_rate;
+			// Ascent rate
+			if (s[1] & 0x80)
+				ascent_rate = (s[1] & 0x7f) / 4.0;
+			else
+				ascent_rate =  - (s[1] & 0x7f) / 4.0;
+
+			sample.ascent_rate = ascent_rate * FEET;
+			if (callback) callback (DC_SAMPLE_ASCENT_RATE, sample, userdata);
+		} else {
+			// Temperature logged in half degrees F above 20
+			double temperature = s[1] / 2.0 + 20;
+			sample.temperature = (temperature - 32.0) / 1.8;
+
+			if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
+		}
+
+		// Cochran EMC models store NDL and deco stop time
+		// int the 20th to 23rd sample
+		if (parser->info->sample_format == SAMPLE_EMC) {
+			// Find the next sample by skipping over any event bytes.
+			// This is only temporary so we can get NDL and deco stop
+			// times which span two samples.
+			const unsigned char *n = s + sampling_size;
+			cochran_events_t event;
+
+			while ((*n & 0x80) && n < samples + size) {
+				cochran_commander_get_event_info(*n, &event);
+				n += event.data_bytes;
+			}
+
+			// Tissue load is recorded across 20 samples, we ignore them
+			// NDL and deco stop time is recorded across the next 4 samples
+			// The first 2 are either NDL or stop time at deepest stop (if in deco)
+			// The next 2 are total deco stop time.
+			switch (time % 24)
+			{
+			case 20:
+				if (deco_obligation) {
+					/* Deco time for deepest stop, unused */
+					int deco_time = (s[2] + n[2] * 256 + 1) * 60;
+				} else {
+					/* Send deco NDL sample */
+					sample.deco.type = DC_DECO_NDL;
+					sample.deco.time = (s[2] + n[2] * 256 + 1) * 60; // seconds
+					sample.deco.depth = 0;
+					if (callback) callback (DC_SAMPLE_DECO, sample, userdata);
+				}
+				break;
+			case 22:
+				/* Deco time, total obligation */
+				if (deco_obligation) {
+					sample.deco.type = DC_DECO_DECOSTOP;
+					sample.deco.depth = deco_ceiling * FEET;
+					sample.deco.time = (s[2] + n[2] * 256 + 1) * 60; // minutes
+					if (callback) callback (DC_SAMPLE_DECO, sample, userdata);
+				}
+				break;
+			}
+		}
+
+		time ++;
+		offset += sampling_size;
+	}
+
+	return DC_STATUS_SUCCESS;
 }
diff --git a/src/cochran_commander_parser.h b/src/cochran_commander_parser.h
index 0a125e5..3176a61 100644
--- a/src/cochran_commander_parser.h
+++ b/src/cochran_commander_parser.h
@@ -26,76 +26,9 @@ typedef struct cochran_events_t {
 	parser_sample_flags_t flag;
 } cochran_events_t;
 
-struct event_size {
-	int code;
-	int size;
-};
 
-// FIXME: Mark this variable as const!
-// FIXME: This does not belong in a header file! Now you end up with
-// multiple instances, one in each source file.
-// FIXME: I actually prefer to see the emc and cmdr files merged into
-// the commander file anyway. If you read my comments there, I think we
-// can share most of the code there, so using separate files becomes
-// pointless. And then the previous remark is no longer an issue.
-static cochran_events_t cochran_events[] = {
-	{ 0xA8, 1, SAMPLE_EVENT_SURFACE, SAMPLE_FLAGS_BEGIN },	// Entered PDI mode
-	{ 0xA9, 1, SAMPLE_EVENT_SURFACE, SAMPLE_FLAGS_END },	// Exited PDI mode
-	{ 0xAB, 5, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Ceiling decrease
-	{ 0xAD, 5, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Ceiling increase
-	{ 0xBD, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Switched to nomal PO2 setting
-	{ 0xC0, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Switched to FO2 21% mode
-	{ 0xC1, 1, SAMPLE_EVENT_ASCENT,	SAMPLE_FLAGS_BEGIN },	// Ascent rate greater than limit
-	{ 0xC2, 1, SAMPLE_EVENT_BATTERY, SAMPLE_FLAGS_NONE },	// Low battery warning
-	{ 0xC3, 1, SAMPLE_EVENT_OLF,	SAMPLE_FLAGS_NONE },	// CNS Oxygen toxicity warning
-	{ 0xC4, 1, SAMPLE_EVENT_MAXDEPTH, SAMPLE_FLAGS_NONE },	// Depth exceeds user set point
-	{ 0xC5, 1, SAMPLE_EVENT_DEEPSTOP, SAMPLE_FLAGS_BEGIN },	// Entered decompression mode
-	{ 0xC8, 1, SAMPLE_EVENT_FLOOR,	SAMPLE_FLAGS_BEGIN },	// PO2 too high
-	{ 0xCC, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_BEGIN },	// Low Cylinder 1 pressure
-	{ 0xCE, 1, SAMPLE_EVENT_RBT,	SAMPLE_FLAGS_BEGIN },	// Non-decompression warning
-	{ 0xCD, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Switched to deco blend
-	{ 0xD0, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_BEGIN },	// Breathing rate alarm
-	{ 0xD3, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Low gas 1 flow rate
-	{ 0xD6, 1, SAMPLE_EVENT_CEILING, SAMPLE_FLAGS_BEGIN },	// Depth is less than ceiling
-	{ 0xD8, 1, SAMPLE_EVENT_DEEPSTOP, SAMPLE_FLAGS_END },	// End decompression mode
-	{ 0xE1, 1, SAMPLE_EVENT_ASCENT,	SAMPLE_FLAGS_END },		// End ascent rate warning
-	{ 0xE2, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Low SBAT battery warning
-	{ 0xE3, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Switched to FO2 mode
-	{ 0xE5, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Switched to PO2 mode
-	{ 0xEE, 1, SAMPLE_EVENT_RBT,	SAMPLE_FLAGS_END },		// End non-decompresison warning
-	{ 0xEF, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Switch to blend 2
-	{ 0xF0, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_END },		// Breathing rate alarm
-	{ 0xF3, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE },	// Switch to blend 1
-	{ 0xF6, 1, SAMPLE_EVENT_CEILING, SAMPLE_FLAGS_END },	// End Depth is less than ceiling
-	{ 0x00, 1, SAMPLE_EVENT_NONE,	SAMPLE_FLAGS_NONE }
-};
-
-
-// Common
 dc_status_t cochran_commander_parser_create (dc_parser_t **out,
-		dc_context_t *context);
+		dc_context_t *context, int model);
 dc_status_t cochran_commander_parser_destroy (dc_parser_t *abstract);
 dc_status_t cochran_commander_parser_set_data (dc_parser_t *abstract,
 		const unsigned char *data, unsigned int size);
-dc_status_t cochran_commander_parser_get_datetime (dc_parser_t *abstract,
-		dc_datetime_t *datetime);
-void cochran_commander_get_event_info(const unsigned char code,
-		cochran_events_t *event);
-int cochran_commander_handle_event (dc_parser_t *abstract,
-		dc_sample_callback_t callback, void *userdata, unsigned char code,
-		unsigned int time);
-int cochran_backparse(dc_parser_t *abstract, unsigned const char *samples, int size, struct event_size (*event_bytes)[15]);
-
-
-// EMC FAMILY
-dc_status_t cochran_emc_parser_get_field (dc_parser_t *abstract,
-		dc_field_type_t type, unsigned int flags, void *value);
-dc_status_t cochran_emc_parser_samples_foreach (dc_parser_t *abstract,
-		dc_sample_callback_t callback, void *userdata);
-
-// COMMANDER FAMILY
-dc_status_t cochran_cmdr_parser_get_field(dc_parser_t *abstract,
-		dc_field_type_t type, unsigned int flags, void *value);
-dc_status_t cochran_cmdr_parser_samples_foreach (dc_parser_t *abstract,
-		dc_sample_callback_t callback, void *userdata);
-
diff --git a/src/cochran_emc_parser.c b/src/cochran_emc_parser.c
deleted file mode 100644
index aae484f..0000000
--- a/src/cochran_emc_parser.c
+++ /dev/null
@@ -1,345 +0,0 @@
-/*
- * libdivecomputer
- *
- * Copyright (C) 2014 John Van Ostrand
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301 USA
- */
-
-#include <stdlib.h>
-#include <math.h>
-
-#include <libdivecomputer/units.h>
-#include <libdivecomputer/cochran.h>
-
-#include "context-private.h"
-#include "device-private.h"
-#include "parser-private.h"
-#include "serial.h"
-#include "array.h"
-
-#include "cochran_commander.h"
-#include "cochran_commander_parser.h"
-
-
-// Size of inter-dive events
-struct event_size emc_event_bytes[15] = {  {0x00, 19}, {0x01, 23}, {0x02, 20},
-								{0x03, 19}, {0x06, 21}, {0x07, 21},
-								{0x0a, 21}, {0x0b, 21}, {0x0f, 19},
-								{0x10, 21},
-								{  -1,  1} };
-
-
-dc_status_t
-cochran_emc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type,
-		unsigned int flags, void *value)
-{
-	const unsigned char *log_entry = abstract->data + COCHRAN_MODEL_SIZE;
-	unsigned char corrupt_dive = 0;
-
-	// FIXME: Is there a reason why you have this global check instead
-	// of checking the actual value for 0xFFFF, like you do in the
-	// commander function?
-	if (array_uint32_le(log_entry + COCHRAN_EMC_LOG_SIZE / 2) == 0xFFFFFFFF)
-		corrupt_dive = 1;
-
-	dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
-	dc_salinity_t *water = (dc_salinity_t *) value;
-
-	if (value) {
-
-		// Parse data to recreate dive summary information
-		sample_statistics_t parsed_stats = {0};
-		if (corrupt_dive)
-			cochran_emc_parser_samples_foreach(abstract, sample_statistics_cb, (void *) &parsed_stats);
-
-		switch (type) {
-		case DC_FIELD_TEMPERATURE_SURFACE:
-			*((unsigned int*) value) = (log_entry[EMC_START_TEMP] - 32.0) / 1.8;
-			break;
-		case DC_FIELD_TEMPERATURE_MINIMUM:
-			if (corrupt_dive) {
-				return DC_STATUS_UNSUPPORTED;
-			} else
-				*((unsigned int*) value) = (log_entry[EMC_MIN_TEMP] / 2.0
-					+ 20 - 32) / 1.8;
-			break;
-		case DC_FIELD_TEMPERATURE_MAXIMUM:
-			if (corrupt_dive) {
-				return DC_STATUS_UNSUPPORTED;
-			} else
-				*((unsigned int*) value) = (log_entry[EMC_MAX_TEMP] / 2.0
-					+ 20 - 32) / 1.8;
-			break;
-		case DC_FIELD_DIVETIME:
-			if (corrupt_dive) {
-				*((unsigned int*) value) = parsed_stats.divetime;
-			} else
-				*((unsigned int *) value) = array_uint16_le (log_entry + EMC_BT) * 60;
-			break;
-		case DC_FIELD_MAXDEPTH:
-			if (corrupt_dive) {
-				*((unsigned int*) value) = parsed_stats.maxdepth;
-			} else
-				*((double *) value) = array_uint16_le (log_entry + EMC_MAX_DEPTH) / 4.0
-						* FEET;
-			break;
-		case DC_FIELD_AVGDEPTH:
-			if (corrupt_dive) {
-				return DC_STATUS_UNSUPPORTED;
-			} else
-				*((double *) value) = array_uint16_le (log_entry + EMC_AVG_DEPTH) / 4.0
-						* FEET;
-			break;
-		case DC_FIELD_GASMIX_COUNT:
-			*((unsigned int *) value) = 10;
-			break;
-		case DC_FIELD_GASMIX:
-			// Gas percentages are decimal and encoded as
-			// highbyte = integer value
-			// lowbyte = decimal portion, divide by 256 to get decimal value
-			gasmix->oxygen = array_uint16_le (log_entry
-					+ EMC_O2_PERCENT + 2 * flags) / 256.0 / 100;
-			gasmix->helium = array_uint16_le (log_entry
-					+ EMC_HE_PERCENT + 2 * flags) / 256.0 / 100;
-			gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium;
-			break;
-		case DC_FIELD_SALINITY:
-			// 0x00 = low conductivity, 0x10 = high, maybe there's a 0x01 and 0x11?
-			water->type = ( (log_entry[EMC_WATER_CONDUCTIVITY] & 0x3) == 0
-					? DC_WATER_FRESH : DC_WATER_SALT );
-			// Assume Cochran's conductivity ranges from 0 to 3
-			// so 0 = 1000kg/m³, 2 = 1025kg/m³
-			water->density = 1000 + 12.5 * (log_entry[EMC_WATER_CONDUCTIVITY] & 0x3);
-			break;
-		case DC_FIELD_ATMOSPHERIC:
-			// Cochran measures atm pressure and stores it as altitude.
-			// Convert altitude (measured in 1/4 kilofeet) back to pressure.
-			*(double *) value = ATM / BAR * pow(1 - 0.0000225577
-					* log_entry[EMC_ALTITUDE] * 250.0 * FEET, 5.25588);
-			break;
-		default:
-			return DC_STATUS_UNSUPPORTED;
-		}
-	}
-
-	return DC_STATUS_SUCCESS;
-}
-
-// TODO: Not reviewed in detail yet
-dc_status_t
-cochran_emc_parser_samples_foreach (dc_parser_t *abstract,
-		dc_sample_callback_t callback, void *userdata)
-{
-	const unsigned char *data = abstract->data;
-	const unsigned char *log_entry = data + COCHRAN_MODEL_SIZE;
-	const unsigned char *samples = log_entry + COCHRAN_EMC_LOG_SIZE;
-	unsigned int size = abstract->size - COCHRAN_MODEL_SIZE
-		- COCHRAN_EMC_LOG_SIZE;
-
-	dc_sample_value_t sample = {0}, empty_sample = {0};
-	unsigned int time = 0, last_sample_time = 0;
-	unsigned int offset = 0;
-	int depth_qfeet = 0;
-	double temperature;
-	double start_depth = 0;
-	double ascent_rate;
-	unsigned char deco_obligation = 0;
-	unsigned int deco_ceiling = 0;
-	unsigned int deco_time = 0;
-	unsigned char corrupt_dive = 0;
-
-	// In rare circumstances Cochran computers won't record the end-of-dive
-	// log entry block. When the end-sample pointer is 0xFFFFFFFF it's corrupt.
-	// That means we don't really know where the dive samples end and we don't
-	// know what the dive summary values are (i.e. max depth, min temp)
-	if (array_uint32_le(log_entry + COCHRAN_EMC_LOG_SIZE / 2) == 0xFFFFFFFF) {
-		corrupt_dive = 1;
-
-		WARNING(abstract->context, "Incomplete dive on %02d/%02d/%02d at %02d:%02d:%02d, trying to parse samples.", log_entry[EMC_YEAR], log_entry[EMC_MON], log_entry[EMC_DAY], log_entry[EMC_HOUR], log_entry[EMC_MIN], log_entry[EMC_SEC]);
-
-		// Eliminate inter-dive events
-		size = cochran_backparse(abstract, samples, size, &emc_event_bytes);
-	}
-
-	/*
-	 * Cochran samples depth every second and varies between ascent rate
-	 * and temp ever other second.
-	 * In the 21st, 22nd, 23rd, 24th samples are NDL remaining,
-	 * deco time and ceiling
-	 */
-
-	// Prime with values from the dive log section
-	start_depth = array_uint16_le (log_entry + EMC_START_DEPTH) / 256;
-
-	last_sample_time = sample.time = time;
-	if (callback) callback (DC_SAMPLE_TIME, sample, userdata);
-
-	sample.depth = start_depth * FEET;
-	if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
-
-	sample.temperature = (log_entry[EMC_START_TEMP] - 32) / 1.8;
-	if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
-
-	while (offset < size) {
-		const unsigned char *s = NULL;
-
-		sample = empty_sample;
-
-		sample.time = time;
-		if (callback && last_sample_time != sample.time) {
-			// We haven't issued this time yet.
-			last_sample_time = sample.time;
-			callback (DC_SAMPLE_TIME, sample, userdata);
-		}
-
-		s = samples + offset;
-
-		// If corrupt_dive end before offset
-		if (corrupt_dive) {
-			// When we aren't sure where the sample data ends we can
-			// look for events that shouldn't be in the sample data.
-			// 0xFF is unwritten memory
-			// 0xA8 indicates start of post-dive interval
-			// 0xE3 (switch to FO2 mode) and 0xF3 (switch to blend 1) occur
-			// at dive start so when we see them after the first second we
-			// found the beginning of the next dive.
-			if (s[0] == 0xFF || s[0] == 0xA8) {
-					break;
-			}
-			if (time > 1 && (s[0] == 0xE3 || s[0] == 0xF3))
-				break;
-		}
-
-		// Check for event
-		if (s[0] & 0x80) {
-			offset += cochran_commander_handle_event(abstract, callback,
-				userdata, s[0], time);
-			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 + 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 + 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
-				sample.gasmix = 1;
-				if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata);
-				break;
-			case 0xF3:  // Switched to gas blend 1
-				sample.gasmix = 0;
-				if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata);
-				break;
-			}
-
-			continue;
-		}
-
-
-		// Depth is logged as change in feet, bit 0x40 means negative depth
-		if (s[0] & 0x40)
-			depth_qfeet -= (s[0] & 0x3f);
-		else
-			depth_qfeet += (s[0] & 0x3f);
-
-		sample.depth = (start_depth + depth_qfeet / 4.0) * FEET;
-		if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
-
-		// Ascent rate is logged in the 0th sample, temp in the 1st, repeat.
-		if (time % 2 == 0) {
-			// Ascent rate
-			if (s[1] & 0x80)
-				ascent_rate = (s[1] & 0x7f) / 4.0;
-			else
-				ascent_rate = - (s[1] & 0x7f) / 4.0;
-
-			sample.ascent_rate = ascent_rate * FEET;
-			if (callback) callback (DC_SAMPLE_ASCENT_RATE, sample, userdata);
-		} else {
-			// Temperature logged in half degrees F above 20
-			temperature = s[1] / 2.0 + 20;
-			sample.temperature = (temperature - 32.0) / 1.8;
-
-			if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
-		}
-
-
-		// Some samples are split over two seconds
-		// Skip over event bytes to find the next sample
-		const unsigned char *n = s + COCHRAN_EMC_SAMPLE_SIZE;
-		cochran_events_t event;
-
-		while ((*n & 0x80) && n < s + size) {
-			cochran_commander_get_event_info(*n, &event);
-			n += event.data_bytes;
-		}
-
-		// Tissue load is recorded across 20 samples, we ignore them
-		// NDL and deco stop time is recorded across the next 4 samples
-		// The first 2 are either NDL or stop time at deepest stop (if in deco)
-		// The next 2 are total deco stop time.
-		switch (time % 24)
-		{
-		case 20:
-			if (deco_obligation) {
-				/* Deco time for deepest stop, unused */
-				deco_time = (s[2] + n[2] * 256 + 1) * 60;
-			} else {
-				/* Send deco NDL sample */
-				sample.deco.type = DC_DECO_NDL;
-				sample.deco.time = (s[2] + n[2] * 256 + 1) * 60; // seconds
-				sample.deco.depth = 0;
-				if (callback) callback (DC_SAMPLE_DECO, sample, userdata);
-			}
-			break;
-		case 22:
-			/* Deco time, total obligation */
-			if (deco_obligation) {
-				sample.deco.type = DC_DECO_DECOSTOP;
-				sample.deco.depth = deco_ceiling * FEET;
-				sample.deco.time = (s[2] + n[2] * 256 + 1) * 60; // minutes
-				if (callback) callback (DC_SAMPLE_DECO, sample, userdata);
-			}
-			break;
-		}
-
-		time ++;
-		offset += COCHRAN_EMC_SAMPLE_SIZE;
-	}
-
-	return DC_STATUS_SUCCESS;
-}
diff --git a/src/parser.c b/src/parser.c
index 40773c7..2498a8e 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -146,7 +146,7 @@ dc_parser_new (dc_parser_t **out, dc_device_t *device)
 		rc = divesystem_idive_parser_create2 (&parser, context, device->devinfo.model);
 		break;
 	case DC_FAMILY_COCHRAN_COMMANDER:
-		rc = cochran_commander_parser_create (&parser, context);
+		rc = cochran_commander_parser_create (&parser, context, device->devinfo.model);
 		break;
 	default:
 		return DC_STATUS_INVALIDARGS;
-- 
2.4.3



More information about the devel mailing list