[PATCH 7/8] Renamed Cochran backend

John Van Ostrand john at vanostrand.com
Sun Nov 2 18:52:33 PST 2014


Family is not COCHRAN_COMMANDER and functions are cochran_commander_*.
---
 examples/cochran_download.c                 |   2 +-
 include/libdivecomputer/cochran.h           |  19 +-
 include/libdivecomputer/cochran_commander.h |  44 ++
 include/libdivecomputer/common.h            |   2 +-
 src/Makefile.am                             |   4 +-
 src/cochran_cmdr_parser.c                   |   6 +-
 src/cochran_commander.c                     | 890 ++++++++++++++++++++++++++++
 src/cochran_commander.h                     | 157 +++++
 src/cochran_commander_parser.c              | 218 +++++++
 src/cochran_commander_parser.h              | 115 ++++
 src/cochran_common.c                        | 890 ----------------------------
 src/cochran_common.h                        | 157 -----
 src/cochran_common_parser.c                 | 218 -------
 src/cochran_common_parser.h                 | 115 ----
 src/cochran_emc_parser.c                    |   6 +-
 src/descriptor.c                            |   6 +-
 src/device.c                                |   4 +-
 src/parser.c                                |   4 +-
 18 files changed, 1442 insertions(+), 1415 deletions(-)
 create mode 100644 include/libdivecomputer/cochran_commander.h
 create mode 100644 src/cochran_commander.c
 create mode 100644 src/cochran_commander.h
 create mode 100644 src/cochran_commander_parser.c
 create mode 100644 src/cochran_commander_parser.h
 delete mode 100644 src/cochran_common.c
 delete mode 100644 src/cochran_common.h
 delete mode 100644 src/cochran_common_parser.c
 delete mode 100644 src/cochran_common_parser.h

diff --git a/examples/cochran_download.c b/examples/cochran_download.c
index a9253af..01df25d 100644
--- a/examples/cochran_download.c
+++ b/examples/cochran_download.c
@@ -309,7 +309,7 @@ main (int argc, char *argv[])
 	extern int optind, opterr, optopt;
 
 	// Default values.
-	dc_family_t backend = DC_FAMILY_COCHRAN;
+	dc_family_t backend = DC_FAMILY_COCHRAN_COMMANDER;
 
 	// Command line arguments
 	const char *devname = NULL, *dirname;
diff --git a/include/libdivecomputer/cochran.h b/include/libdivecomputer/cochran.h
index 90b1bb5..a2a0962 100644
--- a/include/libdivecomputer/cochran.h
+++ b/include/libdivecomputer/cochran.h
@@ -22,23 +22,6 @@
 #ifndef COCHRAN_H
 #define COCHRAN_H
 
-#include "context.h"
-#include "device.h"
-#include "parser.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-
-dc_status_t
-cochran_common_device_open (dc_device_t **device, dc_context_t *context,
-		const char *name);
-
-dc_status_t
-cochran_common_parser_create (dc_parser_t **parser, dc_context_t *context);
-
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
+#include "cochran_commander.h"
 
 #endif /* COCHRAN_H */
diff --git a/include/libdivecomputer/cochran_commander.h b/include/libdivecomputer/cochran_commander.h
new file mode 100644
index 0000000..143c451
--- /dev/null
+++ b/include/libdivecomputer/cochran_commander.h
@@ -0,0 +1,44 @@
+/*
+ * 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
+ */
+
+#ifndef COCHRAN_COMMANDER_H
+#define COCHRAN_COMMANDER_H
+
+#include "context.h"
+#include "device.h"
+#include "parser.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+dc_status_t
+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);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* COCHRAN_COMMANDER_H */
diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h
index 218548f..a2ff47f 100644
--- a/include/libdivecomputer/common.h
+++ b/include/libdivecomputer/common.h
@@ -84,7 +84,7 @@ typedef enum dc_family_t {
 	/* Dive Rite */
 	DC_FAMILY_DIVERITE_NITEKQ = (11 << 16),
 	/* Cochran */
-	DC_FAMILY_COCHRAN = (12 << 16),
+	DC_FAMILY_COCHRAN_COMMANDER = (12 << 16),
 } dc_family_t;
 
 #ifdef __cplusplus
diff --git a/src/Makefile.am b/src/Makefile.am
index 33d4713..33bd5e2 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -56,8 +56,8 @@ libdivecomputer_la_SOURCES = \
 	checksum.h checksum.c \
 	array.h array.c \
 	buffer.c \
-	cochran_common.h cochran_common.c \
-	cochran_common_parser.h cochran_common_parser.c \
+	cochran_commander.h cochran_commander.c \
+	cochran_commander_parser.h cochran_commander_parser.c \
 	cochran_cmdr_parser.c cochran_emc_parser.c
 
 if OS_WIN32
diff --git a/src/cochran_cmdr_parser.c b/src/cochran_cmdr_parser.c
index 8b94fcf..7cfd650 100644
--- a/src/cochran_cmdr_parser.c
+++ b/src/cochran_cmdr_parser.c
@@ -31,8 +31,8 @@
 #include "serial.h"
 #include "array.h"
 
-#include "cochran_common.h"
-#include "cochran_common_parser.h"
+#include "cochran_commander.h"
+#include "cochran_commander_parser.h"
 
 
 #define SZ_SAMPLE	2
@@ -151,7 +151,7 @@ cochran_cmdr_parser_samples_foreach (dc_parser_t *abstract,
 
 		// Check for event
 		if (s[0] & 0x80) {
-			offset += cochran_common_handle_event(abstract, callback, userdata,
+			offset += cochran_commander_handle_event(abstract, callback, userdata,
 					s[0], offset, time);
 			if (s[0] == 0xC5)
 				deco_obligation = 1;	// Deco obligation begins
diff --git a/src/cochran_commander.c b/src/cochran_commander.c
new file mode 100644
index 0000000..fc25932
--- /dev/null
+++ b/src/cochran_commander.c
@@ -0,0 +1,890 @@
+/*
+ * 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 <string.h> // memcpy, memcmp
+#include <stdlib.h> // malloc, free
+#include <assert.h> // assert
+#include <time.h>
+
+#include <libdivecomputer/cochran.h>
+
+#include "context-private.h"
+#include "device-private.h"
+#include "serial.h"
+#include "array.h"
+
+#include "cochran_commander.h"
+
+
+#define EXITCODE(rc) \
+( \
+	rc == -1 ? DC_STATUS_IO : DC_STATUS_TIMEOUT \
+)
+
+static const dc_device_vtable_t cochran_commander_device_vtable = {
+	DC_FAMILY_COCHRAN_COMMANDER,
+	cochran_commander_device_set_fingerprint,	/* set_fingerprint */
+	cochran_commander_device_read,				/* read */
+	NULL,									/* write */
+	cochran_commander_device_dump,				/* dump */
+	cochran_commander_device_foreach,			/* foreach */
+	cochran_commander_device_close				/* close */
+};
+
+
+dc_status_t
+cochran_packet (cochran_device_t *device, const unsigned char command[],
+		unsigned int csize, unsigned char answer[], unsigned int asize,
+		int high_speed)
+{
+	dc_device_t *abstract = (dc_device_t *) device;
+	unsigned int bytes_read = 0, n, read_size;
+	unsigned int ptr;
+
+	if (device_is_cancelled (abstract))
+		return DC_STATUS_CANCELLED;
+
+	// Send the command to the device, one byte at a time
+	for (ptr = 0; ptr < csize; ptr++) {
+		if (ptr) serial_sleep(device->port, 16);	// 16 ms
+		n = serial_write(device->port, command + ptr, 1);
+		if (n != 1) {
+			ERROR (abstract->context, "Failed to send the command.");
+			return EXITCODE (n);
+		}
+	}
+
+	if (high_speed) {
+		serial_sleep(device->port, 45);
+
+		// Weird but I only get the right result when I do it twice
+		// Rates are odd, like 825600 for the EMC, 115200 for commander
+		serial_configure(device->port, device->data.high_baud, 8,
+						SERIAL_PARITY_NONE, 2, SERIAL_FLOWCONTROL_NONE);
+		serial_configure(device->port, device->data.high_baud, 8,
+						SERIAL_PARITY_NONE, 2, SERIAL_FLOWCONTROL_NONE);
+	}
+
+	// Receive the answer from the device.
+	// Use 1024 byte "packets" so we can display progress.
+	while (bytes_read < asize) {
+		if (asize - bytes_read > 1024)
+			read_size = 1024;
+		else
+			read_size = asize - bytes_read;
+
+		n = serial_read (device->port, answer + bytes_read, read_size);
+		if (n != read_size) {
+			ERROR (abstract->context, "Failed to receive data, expected %u,"
+					"read %u.", read_size, n);
+			return EXITCODE (n);
+		}
+
+		bytes_read += n;
+
+		if (device->progress) {
+			device->progress->current = bytes_read;
+			device_event_emit (abstract, DC_EVENT_PROGRESS, device->progress);
+		}
+	}
+
+	return DC_STATUS_SUCCESS;
+}
+
+
+static dc_status_t
+cochran_commander_serial_setup (cochran_device_t *device, dc_context_t *context)
+{
+	int rc;
+
+	// Set the serial communication protocol (9600 8N2, no FC).
+	rc = serial_configure (device->port, 9600, 8, SERIAL_PARITY_NONE,
+							2, SERIAL_FLOWCONTROL_NONE);
+	if (rc == -1) {
+		ERROR (context, "Failed to set the terminal attributes.");
+		serial_close (device->port);
+		free (device);
+		return DC_STATUS_IO;
+	}
+
+	serial_set_queue_size(device->port, 4096, 4096);
+
+	// Make sure everything is in a sane state.
+	// Mimicing Analyst software with excessive flushes
+	serial_flush (device->port, SERIAL_QUEUE_OUTPUT);
+	serial_flush (device->port, SERIAL_QUEUE_INPUT);
+	serial_flush (device->port, SERIAL_QUEUE_INPUT);
+	serial_flush (device->port, SERIAL_QUEUE_INPUT);
+	serial_flush (device->port, SERIAL_QUEUE_INPUT);
+	serial_flush (device->port, SERIAL_QUEUE_INPUT);
+	serial_flush (device->port, SERIAL_QUEUE_INPUT);
+
+	serial_set_break(device->port, 1);
+	serial_sleep(device->port, 16);
+	
+	serial_set_break(device->port, 0);
+
+	// Set the timeout for receiving data (5000 ms).
+	if (serial_set_timeout (device->port, 5000) == -1) {
+		ERROR (context, "Failed to set the timeout.");
+		serial_close (device->port);
+		free (device);
+		return DC_STATUS_IO;
+	}
+
+	// Wait for heartbeat byte before send
+	int n;
+	char answer[1];
+	if ((n = serial_read(device->port, answer, 1)) != 1) {
+		ERROR (context, "Failed to receive device heartbeat.");
+		return EXITCODE (n);
+	}
+
+	return DC_STATUS_SUCCESS;
+}
+
+
+dc_status_t
+cochran_commander_serial_open(cochran_device_t *device, dc_context_t *context)
+{
+	// Open the device.
+	int rc = serial_open (&device->port, context, device->name);
+	if (rc == -1) {
+		ERROR (context, "Failed to open the serial port.");
+		free (device);
+		return DC_STATUS_IO;
+	}
+
+	return cochran_commander_serial_setup(device, context);
+}
+
+
+dc_status_t
+cochran_commander_device_open (dc_device_t **out, dc_context_t *context,
+		const char *name)
+{
+	dc_status_t rc;
+
+	if (out == NULL)
+		return DC_STATUS_INVALIDARGS;
+
+	// Allocate memory.
+	cochran_device_t *device = (cochran_device_t *) malloc (
+			sizeof (cochran_device_t));
+	if (device == NULL) {
+		ERROR (context, "Failed to allocate memory.");
+		return DC_STATUS_NOMEMORY;
+	}
+
+	// Initialize the base class.
+	device_init (&device->base, context, &cochran_commander_device_vtable);
+
+	// Set the default values.
+	device->port = NULL;
+	device->name = name;
+	device->progress = NULL;
+	device->data.logbook = NULL;
+	device->data.sample = NULL;
+	cochran_commander_device_set_fingerprint((dc_device_t *) device, "", 0);
+
+	if ((rc = cochran_commander_serial_open(device, context))
+			!= DC_STATUS_SUCCESS)
+		return rc;
+
+	// Read ID from the device
+	rc = cochran_read_id((dc_device_t *) device);
+
+	if (rc != DC_STATUS_SUCCESS) {
+		ERROR (context, "Device not responding.");
+		serial_close (device->port);
+		free (device);
+		return rc;
+	}
+
+	// Check ID
+	if ((device->data.model & 0xFF0000) == COCHRAN_MODEL_UNKNOWN) {
+		ERROR (context, "Device not recognized.");
+		serial_close (device->port);
+		free (device);
+		return DC_STATUS_UNSUPPORTED;
+	}
+
+	*out = (dc_device_t *) device;
+
+	return DC_STATUS_SUCCESS;
+}
+
+
+dc_status_t
+cochran_commander_device_close (dc_device_t *abstract)
+{
+	cochran_device_t *device = (cochran_device_t*) abstract;
+
+	// Close the device.
+	if (serial_close (device->port) == -1) {
+		free (device);
+		return DC_STATUS_IO;
+	}
+
+	// Free memory.
+	free (device->data.logbook);
+	free (device->data.sample);
+	free (device);
+
+	return DC_STATUS_SUCCESS;
+}
+
+
+dc_status_t
+cochran_commander_device_set_fingerprint (dc_device_t *abstract,
+		const unsigned char data[], unsigned int size)
+{
+	cochran_device_t *device = (cochran_device_t *) abstract;
+	cochran_data_t *d = &(device->data);
+
+	if (size && size != sizeof (d->fingerprint))
+		return DC_STATUS_INVALIDARGS;
+
+	if (size)
+		memcpy (&(d->fingerprint), data, sizeof (d->fingerprint));
+	else
+		memset (&(d->fingerprint), 0xFF, sizeof (d->fingerprint));
+
+	return DC_STATUS_SUCCESS;
+}
+
+
+dc_status_t
+cochran_commander_device_read (dc_device_t *abstract, unsigned int address,
+		unsigned char data[], unsigned int size)
+{
+	cochran_device_t *device = (cochran_device_t*) abstract;
+
+	// Build the command
+	unsigned char command[10];
+	unsigned char command_size;
+
+	switch (device->data.address_length)
+	{
+	case COCHRAN_ADDRESS_LENGTH_32:
+		// EMC uses 32 bit addressing
+		command[0] = 0x15;
+		command[1] = (address      ) & 0xff;
+		command[2] = (address >>  8) & 0xff;
+		command[3] = (address >> 16) & 0xff;
+		command[4] = (address >> 24) & 0xff;
+		command[5] = (size         ) & 0xff;
+		command[6] = (size >>  8   ) & 0xff;
+		command[7] = (size >> 16   ) & 0xff;
+		command[8] = (size >> 24   ) & 0xff;
+		command[9] = 0x05;
+		command_size = 10;
+		break;
+	case COCHRAN_ADDRESS_LENGTH_24:
+		// Commander uses 24 byte addressing
+		command[0] = 0x15;
+		command[1] = (address      ) & 0xff;
+		command[2] = (address >>  8) & 0xff;
+		command[3] = (address >> 16) & 0xff;
+		command[4] = (size         ) & 0xff;
+		command[5] = (size >>  8   ) & 0xff;
+		command[6] = (size >> 16   ) & 0xff;
+		command[7] = 0x04;
+		command_size = 8;
+		break; default:
+		return DC_STATUS_UNSUPPORTED;
+	}
+
+	// Read data at high speed
+	dc_status_t rc = cochran_packet (device, command, command_size, data,
+			size, 1);
+	if (rc != DC_STATUS_SUCCESS)
+		return rc;
+
+	return DC_STATUS_SUCCESS;
+}
+
+
+static void
+cochran_set_device_config (cochran_device_t *device)
+{
+	dc_device_t *abstract = (dc_device_t *) device;
+	// Determine model
+	if (memcmp(device->data.id + 0x3B, "AM2315\xA3\x71", 8) == 0)
+	{
+		device->data.model = COCHRAN_MODEL_EMC_20;
+		device->data.log_size = 512;
+		device->data.sample_memory_start_address = 0x94000;
+		device->data.dive_num_ptr = 0x56;
+		device->data.dive_count_ptr = 0xD2;
+		device->data.dive_count_endian = COCHRAN_LE_TYPE;
+		device->data.sample_end_ptr = 256;
+		device->data.log_pre_dive_ptr = 30;
+		device->data.log_end_dive_ptr = 256;
+		device->data.last_interdive_ptr = 233;
+		device->data.last_entry_ptr = 194;
+		device->data.date_format = COCHRAN_DATE_FORMAT_SMHDMY;
+		device->data.address_length = COCHRAN_ADDRESS_LENGTH_32;
+		device->data.high_baud = 825600;
+	}
+	else if (memcmp(device->data.id + 0x3B, "AMA315\xC3\xC5", 8) == 0)
+	{
+		device->data.model = COCHRAN_MODEL_EMC_16;
+		device->data.log_size = 512;
+		device->data.sample_memory_start_address = 0x94000;
+		device->data.dive_num_ptr = 0x56;
+		device->data.dive_count_ptr = 0xD2;
+		device->data.dive_count_endian = COCHRAN_LE_TYPE;
+		device->data.sample_end_ptr = 256;
+		device->data.log_pre_dive_ptr = 30;
+		device->data.log_end_dive_ptr = 256;
+		device->data.last_interdive_ptr = 233;
+		device->data.last_entry_ptr = 194;
+		device->data.date_format = COCHRAN_DATE_FORMAT_SMHDMY;
+		device->data.address_length = COCHRAN_ADDRESS_LENGTH_32;
+		device->data.high_baud = 825600;
+	}
+	else if (memcmp(device->data.id + 0x3B, "AM\x11""2212\x02", 8) == 0)
+	{
+		device->data.model = COCHRAN_MODEL_COMMANDER_AIR_NITROX;
+		device->data.log_size = 256;
+		device->data.sample_memory_start_address = 0x20000;
+		device->data.dive_num_ptr = 0x46;
+		device->data.dive_count_ptr = 0x46;
+		device->data.dive_count_endian = COCHRAN_BE_TYPE;
+		device->data.sample_end_ptr = 256;
+		device->data.log_pre_dive_ptr = 30;
+		device->data.log_end_dive_ptr = 128;
+		device->data.last_interdive_ptr = 167;
+		device->data.last_entry_ptr = -1;
+		device->data.date_format = COCHRAN_DATE_FORMAT_MSDHYM;
+		device->data.address_length = COCHRAN_ADDRESS_LENGTH_24;
+		device->data.high_baud = 115200;
+	}
+	else
+	{
+		device->data.model = 0;
+		ERROR (abstract->context,
+			"Unknown Cochran model %02x %02x %02x %02x %02x %02x %02x %02x",
+			*(device->data.id + 0x3B),     *(device->data.id + 0x3B + 1),
+			*(device->data.id + 0x3B + 2), *(device->data.id + 0x3B + 3),
+			*(device->data.id + 0x3B + 4), *(device->data.id + 0x3B + 5),
+			*(device->data.id + 0x3B + 6), *(device->data.id + 0x3B + 7));
+	}
+
+	return;
+}
+
+
+static dc_status_t
+cochran_read_id (dc_device_t *abstract)
+{
+	cochran_device_t *device = (cochran_device_t *) abstract;
+	dc_status_t rc;
+	unsigned char command[6] = {0x05, 0x9D, 0xFF, 0x00, 0x43, 0x00};
+
+	rc = cochran_packet(device, command, 6, device->data.id, 67, 0);
+	if (rc != DC_STATUS_SUCCESS)
+		return rc;
+
+	if (strncmp(device->data.id, "(C)", 3) != 0) {
+		// It's a Commander, read again
+		memcpy(device->data.id0, device->data.id, 67);
+
+		command[1] = 0xBD;
+		command[2] = 0x7F;
+
+		rc = cochran_packet(device, command, 6, device->data.id, 67, 0);
+		if (rc != DC_STATUS_SUCCESS)
+			return rc;
+	}
+
+	cochran_set_device_config(device);
+
+	return DC_STATUS_SUCCESS;
+}
+
+
+static dc_status_t
+cochran_read_config (dc_device_t *abstract)
+{
+	cochran_device_t *device = (cochran_device_t *) abstract;
+	cochran_data_t *data = &device->data;
+	dc_status_t rc;
+	unsigned char command[2] = { 0x96, 0x00 };
+
+	if ((data->model & 0xFF0000) == COCHRAN_MODEL_EMC_FAMILY)
+		data->config_count = 2;
+	else
+		data->config_count = 4;
+
+	int n;
+	for (n = 0; n < data->config_count; n++) {
+		command[1] = n;
+		rc = cochran_packet(device, command, 2, data->config[n], 512, 0);
+		if (rc != DC_STATUS_SUCCESS)
+			return rc;
+	}
+
+	return DC_STATUS_SUCCESS;
+}
+
+
+static dc_status_t
+cochran_read_misc (dc_device_t *abstract)
+{
+	cochran_device_t *device = (cochran_device_t *) abstract;
+
+	unsigned char command[7] = { 0x89, 0x05, 0x00, 0x00, 0x00, 0xDC, 0x05 };
+
+	switch (device->data.model & 0xFF0000)
+	{
+	case COCHRAN_MODEL_COMMANDER_FAMILY:
+		command[2] = 0xCA;
+		command[3] = 0xFD;
+		break;
+	case COCHRAN_MODEL_EMC_FAMILY:
+		command[2] = 0xE0;
+		command[3] = 0x03;
+		break;
+	default:
+		return DC_STATUS_UNSUPPORTED;
+	}
+
+	// Send first byte then wait for heartbeat before sending the rest
+	serial_write(device->port, command, 1);
+
+	int n;
+	char answer[1];
+	if ((n = serial_read(device->port, answer, 1)) != 1) {
+		ERROR (abstract->context, "Failed to receive device heartbeat.");
+		return EXITCODE (n);
+	}
+
+	return cochran_packet(device, command + 1, 6, device->data.misc, 1500, 0);
+}
+
+
+static dc_status_t
+cochran_read_logbook (dc_device_t *abstract)
+{
+	cochran_device_t *device = (cochran_device_t *) abstract;
+	cochran_data_t *d = &(device->data);
+	dc_status_t rc;
+
+	if (d->logbook)
+		free(d->logbook);
+
+	// Allocate space for log book.
+	d->logbook = (unsigned char *) malloc(d->logbook_size);
+	if (device == NULL) {
+		ERROR (abstract->context, "Failed to allocate memory.");
+		return DC_STATUS_NOMEMORY;
+	}
+
+	// Enable progress notifications.
+	device->progress = malloc(sizeof(dc_event_progress_t));
+	if (device->progress == NULL) {
+		ERROR (abstract->context, "Failed to allocate memory.");
+		return DC_STATUS_NOMEMORY;
+	}
+
+	device->progress->current = 0;
+	device->progress->maximum = d->logbook_size;
+	device_event_emit (abstract, DC_EVENT_PROGRESS, device->progress);
+
+	serial_sleep(device->port, 800);
+
+	// set back to 9600 baud
+	cochran_commander_serial_setup(device, abstract->context);
+
+	// Request log book
+	rc = cochran_commander_device_read(abstract, 0, d->logbook, d->logbook_size);
+
+	device->progress->current = d->logbook_size;
+	device_event_emit (abstract, DC_EVENT_PROGRESS, device->progress);
+	free (device->progress);
+
+	return rc;
+}
+
+
+static void
+cochran_find_fingerprint(dc_device_t *abstract)
+{
+	cochran_device_t *device = (cochran_device_t *) abstract;
+	cochran_data_t *d = (cochran_data_t *) &(device->data);
+
+	// 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 * d->log_size
+						+ d->dive_num_ptr,
+						 sizeof(d->fingerprint)))
+		d->fp_dive_num--;
+}
+
+
+static void
+cochran_get_sample_parms(dc_device_t *abstract)
+{
+	cochran_device_t *device = (cochran_device_t *) abstract;
+	cochran_data_t *d = (cochran_data_t *) &(device->data);
+	unsigned int pre_dive_offset, end_dive_offset;
+	unsigned int low_offset, high_offset;
+
+	// Find lowest and highest offsets into sample data
+	low_offset = 0xFFFFFFFF;
+	high_offset = 0;
+
+	int i;
+	for (i = d->fp_dive_num + 1; i < d->dive_count; i++) {
+		pre_dive_offset = array_uint32_le (&(d->logbook[i * d->log_size
+				+ d->log_pre_dive_ptr]));
+		end_dive_offset = array_uint32_le (&(d->logbook[i * d->log_size
+				+ d->log_end_dive_ptr]));
+
+		// Check for ring buffer wrap-around.
+		if (pre_dive_offset > end_dive_offset)
+			break;
+	
+		if (pre_dive_offset < low_offset)
+			low_offset = pre_dive_offset;
+		if (end_dive_offset > high_offset && end_dive_offset != 0xFFFFFFFF )
+			high_offset = end_dive_offset;
+	}
+
+	if (pre_dive_offset > end_dive_offset) {
+		// Since I can't tell how much memory it has, I'll round.
+		// 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;
+		d->sample_memory_end_address = high_offset;
+		low_offset = d->sample_memory_start_address;
+		d->sample_data_offset = low_offset;
+		d->sample_size = high_offset - low_offset;
+	} else if (low_offset < 0xFFFFFFFF && high_offset > 0) {
+		// Round offset and size to 16K boundary
+		d->sample_data_offset = low_offset & 0xFFFFC000;
+		high_offset = ((high_offset  - 1) & 0xFFFFC000) + 0x4000;
+		d->sample_size = high_offset - d->sample_data_offset;
+	} else {
+		d->sample_data_offset = 0;
+		d->sample_size = 0;
+	}
+}
+
+
+static dc_status_t
+cochran_read_samples(dc_device_t *abstract)
+{
+	cochran_device_t *device = (cochran_device_t *) abstract;
+	cochran_data_t *d = (cochran_data_t *) &(device->data);
+	dc_status_t rc;
+
+
+	if (d->sample_size > 0) {
+		if (d->sample)
+			free(d->sample);
+
+		d->sample = (unsigned char *) malloc(d->sample_size);
+		if (device == NULL) {
+			ERROR (abstract->context, "Failed to allocate memory.");
+			return DC_STATUS_NOMEMORY;
+		}
+
+		// Enable progress notifications.
+		device->progress = malloc(sizeof(dc_event_progress_t));
+		if (device->progress == NULL) {
+			ERROR (abstract->context, "Failed to allocate memory.");
+			return DC_STATUS_NOMEMORY;
+		}
+
+		device->progress->current = 0;
+		device->progress->maximum = d->sample_size;
+		device_event_emit (abstract, DC_EVENT_PROGRESS, device->progress);
+
+		serial_sleep(device->port, 800);
+
+		// set back to 9600 baud
+		cochran_commander_serial_setup(device, abstract->context);
+
+		// Read the sample data
+		rc = cochran_commander_device_read (abstract, d->sample_data_offset,
+				d->sample, d->sample_size);
+		if (rc != DC_STATUS_SUCCESS) {
+			free (device->progress);
+			ERROR (abstract->context, "Failed to read the sample data.");
+			return rc;
+		}
+
+		device->progress->current = d->sample_size;
+		device_event_emit (abstract, DC_EVENT_PROGRESS, device->progress);
+		free (device->progress);
+	}
+
+	return DC_STATUS_SUCCESS;
+}
+
+
+static dc_status_t
+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_status_t rc;
+
+	// Read config
+	rc = cochran_read_config(abstract);
+	if (rc != DC_STATUS_SUCCESS)
+		return rc;
+
+	rc = cochran_read_misc(abstract);
+	if (rc != DC_STATUS_SUCCESS)
+		return rc;
+
+	// Determine size of dive list to read. Round up to nearest 16K
+	if (d->dive_count_endian == COCHRAN_LE_TYPE)
+		d->dive_count = array_uint16_le (d->config[0] + d->dive_count_ptr);
+	else 
+		d->dive_count = array_uint16_be (d->config[0] + d->dive_count_ptr);
+	
+	d->logbook_size = ((d->dive_count * d->log_size) & 0xFFFFC000)
+		 + 0x4000;
+
+	rc = cochran_read_logbook(abstract);
+	if (rc != DC_STATUS_SUCCESS)
+		return rc;
+
+	// Determine sample memory to read
+	cochran_find_fingerprint(abstract);	
+	cochran_get_sample_parms(abstract);
+
+	rc = cochran_read_samples(abstract);
+	if (rc != DC_STATUS_SUCCESS)
+		return rc;
+
+	return DC_STATUS_SUCCESS;
+}
+
+
+#define pack_uint32_array_le(d, i)	((d)[0] = (i) & 0xff, \
+									(d)[1] = ((i) >> 8) & 0xff, \
+									(d)[2] = ((i) >> 16) & 0xff, \
+									(d)[3] = ((i) >> 24) & 0xff)
+
+dc_status_t
+cochran_commander_device_dump (dc_device_t *abstract, dc_buffer_t *data)
+{
+	cochran_device_t *device = (cochran_device_t *) abstract;
+	cochran_data_t *d = (cochran_data_t *) &(device->data);
+	int ptr = 0;
+
+	dc_status_t rc;
+	char *b;
+	int size;
+
+	rc = cochran_commander_device_read_all (abstract);
+
+	if (rc != DC_STATUS_SUCCESS)
+		return rc;
+
+	// Reserve space for block pointers
+	// Structure is:
+	// 		int ptr;
+	//		char data_valid_flag;
+	dc_buffer_resize(data, 10 * 5);
+	
+	// Set pointer to first block
+	size = dc_buffer_get_size(data);
+	pack_uint32_array_le(dc_buffer_get_data(data) + ptr, size);
+
+	if (d->extra_id_flag) {
+		dc_buffer_append(data, d->id0, 67);
+		*(dc_buffer_get_data(data) + ptr + 4) = 1;
+	} else {
+		dc_buffer_resize(data, size + 67);
+		b = dc_buffer_get_data(data);
+
+		memset(b + size, 0, 67);
+	}
+	
+	// Set pointer to next block
+	ptr += 5;
+	size = dc_buffer_get_size(data);
+	pack_uint32_array_le(dc_buffer_get_data(data) + ptr, size);
+
+	dc_buffer_append (data, d->id, 67);
+	*(dc_buffer_get_data(data) + ptr + 4) = 1;
+	
+	// Add config blocks
+	int n;
+	for (n = 0; n < d->config_count; n++) {
+		// Set pointer to next block
+		ptr += 5;
+		size = dc_buffer_get_size(data);
+		pack_uint32_array_le(dc_buffer_get_data(data) + ptr, size);
+
+		dc_buffer_append (data, d->config[n], 512);
+
+		*(dc_buffer_get_data(data) + ptr + 4) = 1;
+	}
+
+	// Add blank config blocks
+	for (n = d->config_count; n < 4; n++) {
+		// Set pointer to next block
+		ptr += 5;
+		size = dc_buffer_get_size(data);
+		pack_uint32_array_le(dc_buffer_get_data(data) + ptr, size);
+
+		dc_buffer_resize(data, size + 512);
+
+		b = dc_buffer_get_data(data);
+		memset(b + size, 0, 512);
+	}
+	
+	// Set pointer to next block
+	ptr += 5;
+	size = dc_buffer_get_size(data);
+	pack_uint32_array_le(dc_buffer_get_data(data) + ptr, size);
+
+	if (d->misc) {
+		dc_buffer_append (data, d->misc, 1500);
+		*(dc_buffer_get_data(data) + ptr + 4) = 1;
+	} else {
+		dc_buffer_resize(data, size + 1500);
+
+		b = dc_buffer_get_data(data);
+		memset(b + size, 0, 1500);
+	}
+	
+	// Set pointer to next block
+	ptr += 5;
+	size = dc_buffer_get_size(data);
+	pack_uint32_array_le(dc_buffer_get_data(data) + ptr, size);
+
+	if (d->logbook) {
+		dc_buffer_append (data, d->logbook, d->logbook_size);
+		*(dc_buffer_get_data(data) + ptr + 4) = 1;
+	} else {
+		dc_buffer_resize(data, size + d->logbook_size);
+
+		b = dc_buffer_get_data(data);
+		memset(b + size, 0, d->logbook_size);
+	}
+	
+	// Set pointer to next block
+	ptr += 5;
+	size = dc_buffer_get_size(data);
+	pack_uint32_array_le(dc_buffer_get_data(data) + ptr, size);
+
+	if (d->sample) {
+		dc_buffer_append (data, d->sample, d->sample_size);
+		*(dc_buffer_get_data(data) + ptr + 4) = 1;
+	} else {
+		dc_buffer_resize(data, dc_buffer_get_size(data) + d->sample_size);
+
+		b = dc_buffer_get_data(data);
+		memset(b + size, 0, d->logbook_size);
+	}
+
+	// Set pointer to end
+	ptr += 5;
+	size = dc_buffer_get_size(data);
+	pack_uint32_array_le(dc_buffer_get_data(data) + ptr, size);
+
+	return DC_STATUS_SUCCESS;
+}
+
+
+
+dc_status_t
+cochran_commander_device_foreach (dc_device_t *abstract,
+		dc_dive_callback_t callback, void *userdata)
+{
+	cochran_device_t *device = (cochran_device_t *) abstract;
+	cochran_data_t *d = &(device->data);
+	unsigned int sample_start_offset, sample_end_offset;
+	struct tm t;
+	dc_status_t rc;
+
+	rc = cochran_commander_device_read_all (abstract);
+
+	if (rc != DC_STATUS_SUCCESS)
+		return rc;
+
+	// Loop through each dive
+	int i;
+	for (i = d->dive_count - 1; i > d->fp_dive_num; i--) {
+
+		d->current_log = d->logbook + i * d->log_size;
+
+		sample_start_offset = array_uint32_le (d->current_log + 6);
+		sample_end_offset = array_uint32_le (d->current_log
+				+ d->log_size/2);
+
+		d->current_sample = d->sample + sample_start_offset
+				- d->sample_data_offset;
+
+		// Check for corrupt post-dive section
+		if (array_uint32_le(d->current_log + d->log_size/2) == 0xFFFFFFFF)
+			d->corrupt_dive = 1;
+		else
+			d->corrupt_dive = 0;
+
+		// Check for ring buffer wrap
+		if (sample_start_offset > sample_end_offset)
+			d->current_sample_size = d->sample_memory_end_address
+				- sample_start_offset + 1 + sample_end_offset
+				- d->sample_memory_start_address + 1;
+		else
+			d->current_sample_size = sample_end_offset - sample_start_offset;
+
+		d->current_fingerprint = d->current_log + d->dive_num_ptr;
+
+		if (d->date_format == COCHRAN_DATE_FORMAT_SMHDMY) {
+			t.tm_sec = d->current_log[0];
+			t.tm_min = d->current_log[1];
+			t.tm_hour = d->current_log[2];
+			t.tm_mday = d->current_log[3];
+			t.tm_mon = d->current_log[4];
+			t.tm_year = d->current_log[5];
+			t.tm_wday = t.tm_yday = t.tm_isdst = 0;
+		} else {
+			t.tm_sec = d->current_log[1];
+			t.tm_min = d->current_log[0];
+			t.tm_hour = d->current_log[3];
+			t.tm_mday = d->current_log[2];
+			t.tm_mon = d->current_log[5];
+			t.tm_year = d->current_log[4];
+			t.tm_wday = t.tm_yday = t.tm_isdst = 0;
+		}
+		d->current_dive_start_time = mktime(&t);
+
+		if (callback && !callback ((unsigned char *) d, sizeof(device->data),
+				d->current_fingerprint, sizeof (d->fingerprint), userdata))
+			return DC_STATUS_SUCCESS;
+	}
+
+	return DC_STATUS_SUCCESS;
+}
diff --git a/src/cochran_commander.h b/src/cochran_commander.h
new file mode 100644
index 0000000..d16de42
--- /dev/null
+++ b/src/cochran_commander.h
@@ -0,0 +1,157 @@
+/*
+ * 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
+ */
+
+// seconds to add to Cochran time stamps to get unix time
+// Equivalent to Jan 1, 1992 00:00:00
+#define COCHRAN_TIMESTAMP_OFFSET 694224000
+
+#define COCHRAN_LE_TYPE 0
+#define COCHRAN_BE_TYPE 1
+
+#define COCHRAN_DATE_FORMAT_SMHDMY 0
+#define COCHRAN_DATE_FORMAT_MSDHYM 1
+
+#define COCHRAN_ADDRESS_LENGTH_32 0
+#define COCHRAN_ADDRESS_LENGTH_24 1
+
+typedef enum cochran_model_t {
+	COCHRAN_MODEL_UNKNOWN = 0,
+	COCHRAN_MODEL_EMC_FAMILY = 1 << 16,
+	COCHRAN_MODEL_EMC_14,
+	COCHRAN_MODEL_EMC_16,
+	COCHRAN_MODEL_EMC_20,
+	COCHRAN_MODEL_COMMANDER_FAMILY = 2 << 16,
+	COCHRAN_MODEL_COMMANDER_AIR_NITROX,
+} cochran_model_t;
+
+typedef struct cochran_data_t {
+	cochran_model_t model;
+
+	unsigned char id0[67];
+	unsigned char id[67];
+	unsigned char config[4][512];
+	unsigned char misc[1500];
+	unsigned char *logbook;
+	unsigned char *sample;
+
+	unsigned int extra_id_flag;
+	unsigned int config_count;
+
+	unsigned short int dive_count;
+	unsigned char fingerprint[2];
+	int fp_dive_num;
+
+	unsigned int logbook_size;
+	unsigned int current_sample_size;
+
+	unsigned int sample_data_offset;
+	unsigned int sample_size;
+	unsigned int last_interdive_offset;
+	unsigned int last_entry_offset;
+
+	unsigned char *current_fingerprint;
+	unsigned char *current_log;
+	unsigned char *current_sample;
+	time_t current_dive_start_time;
+
+	// Config items
+	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
+
+	unsigned char corrupt_dive;
+} cochran_data_t;
+
+typedef struct cochran_device_t {
+	dc_device_t base;
+	const char *name;				// serial port name
+	serial_t *port;
+	cochran_data_t data;			// dive data used in parsing
+	dc_event_progress_t *progress;	// for progress in the _read function
+} 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 CME_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_MIN_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=%
+
+// 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	 25		// 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_MIN_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
+
+
+dc_status_t cochran_packet (cochran_device_t *device,
+		const unsigned char command[], unsigned int csize,
+		unsigned char answer[], unsigned int asize, int high_speed);
+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);
+dc_status_t cochran_commander_device_set_fingerprint (dc_device_t *abstract,
+		const unsigned char data[], unsigned int size);
+dc_status_t cochran_commander_device_read (dc_device_t *abstract,
+		unsigned int address, unsigned char data[], unsigned int size);
+static dc_status_t cochran_read_id (dc_device_t *abstract);
+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);
diff --git a/src/cochran_commander_parser.c b/src/cochran_commander_parser.c
new file mode 100644
index 0000000..004ab7e
--- /dev/null
+++ b/src/cochran_commander_parser.c
@@ -0,0 +1,218 @@
+/*
+ * 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 <time.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_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);
+static dc_status_t cochran_commander_parser_get_field (dc_parser_t *abstract,
+		dc_field_type_t type, unsigned int flags, void *value);
+dc_status_t cochran_commander_parser_destroy (dc_parser_t *abstract);
+static dc_status_t cochran_commander_parser_samples_foreach
+		(dc_parser_t *abstract, dc_sample_callback_t callback,
+		void *userdata);
+int cochran_commander_handle_event (dc_parser_t *abstract,
+		dc_sample_callback_t callback, void *userdata, unsigned char code,
+		unsigned int offset, unsigned int time);
+
+
+static dc_parser_vtable_t cochran_commander_parser_vtable = {
+	DC_FAMILY_COCHRAN_COMMANDER,
+	cochran_commander_parser_set_data,			/* set_data */
+	cochran_commander_parser_get_datetime,		/* datetime */
+	cochran_commander_parser_get_field,		/* fields */
+	cochran_commander_parser_samples_foreach,	/* samples_foreach */
+	cochran_commander_parser_destroy			/* destroy */
+};
+
+
+
+dc_status_t
+cochran_commander_parser_create (dc_parser_t **out, dc_context_t *context)
+{
+	if (out == NULL)
+		return DC_STATUS_INVALIDARGS;
+
+	// Allocate memory.
+	dc_parser_t *parser = (dc_parser_t *) malloc (sizeof (dc_parser_t));
+	if (parser == NULL) {
+		ERROR (context, "Failed to allocate memory.");
+		return DC_STATUS_NOMEMORY;
+	}
+
+	parser_init (parser, context, &cochran_commander_parser_vtable);
+
+	*out = parser;
+
+	return DC_STATUS_SUCCESS;
+}
+
+
+dc_status_t
+cochran_commander_parser_destroy (dc_parser_t *abstract)
+{
+	// Free memory.
+	free (abstract);
+
+	return DC_STATUS_SUCCESS;
+}
+
+
+dc_status_t
+cochran_commander_parser_set_data (dc_parser_t *abstract,
+		const unsigned char *data, unsigned int size)
+{
+	abstract->data = data;
+	abstract->size = size;
+
+	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_data_t *data = (cochran_data_t *) abstract->data;
+	const unsigned char *log = data->current_log;
+
+	if (data->date_format == COCHRAN_DATE_FORMAT_SMHDMY) {
+		datetime->second = log[0];
+		datetime->minute = log[1];
+		datetime->hour = log[2];
+		datetime->day = log[3];
+		datetime->month = log[4];
+		datetime->year = log[5] + (log[5] > 91 ? 1900 : 2000);
+	} else {
+		datetime->second = log[1];
+		datetime->minute = log[0];
+		datetime->hour = log[3];
+		datetime->day = log[2];
+		datetime->month = log[5];
+		datetime->year = log[4] + (log[5] > 91 ? 1900 : 2000);
+	}
+
+	return DC_STATUS_SUCCESS;
+}
+
+static dc_status_t
+cochran_commander_parser_get_field (dc_parser_t *abstract, dc_field_type_t type,
+			unsigned int flags, void *value)
+{
+	cochran_data_t *data = (cochran_data_t *) abstract->data;
+
+	switch (data->model & 0xFF0000)
+	{
+	case COCHRAN_MODEL_COMMANDER_FAMILY:
+		return cochran_cmdr_parser_get_field(abstract, type, flags, value);
+		break;
+	case COCHRAN_MODEL_EMC_FAMILY:
+		return cochran_emc_parser_get_field(abstract, type, flags, value);
+		break;
+	}
+}
+
+static dc_status_t
+cochran_commander_parser_samples_foreach (dc_parser_t *abstract,
+			dc_sample_callback_t callback, void *userdata)
+{
+	cochran_data_t *data = (cochran_data_t *) abstract->data;
+
+	switch (data->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;
+	}
+}
+
+
+
+int
+cochran_commander_handle_event (dc_parser_t *abstract,
+		dc_sample_callback_t callback, void *userdata, unsigned char code,
+		unsigned int offset, unsigned int time)
+{
+	cochran_data_t *data = (cochran_data_t *) abstract->data;
+	cochran_events_t *e = cochran_events;
+
+	dc_sample_value_t sample = {0};
+
+	unsigned char event_ptr = 0;
+
+	while (e[event_ptr].code && e[event_ptr].code != code)
+		event_ptr++;
+
+	sample.event.time = 0;
+
+	if (e[event_ptr].code) {
+		switch (e[event_ptr].code)
+		{
+		case 0xAB:	// Ceiling decrease
+			// Indicated to lower ceiling by 10 ft (deeper)
+			// Bytes 1-2: first stop duration (min)
+			// Bytes 3-4: total stop duration (min)
+			// Handled in calling function
+			break;
+		case 0xAD:	// Ceiling increase
+			// Indicates to raise ceiling by 10 ft (shallower)
+			// Handled in calling function
+			break;
+		default:
+			// Don't send known events of type NONE
+			if (! e[event_ptr].type == SAMPLE_EVENT_NONE) {
+				sample.event.type = e[event_ptr].type;
+				sample.event.flags = e[event_ptr].flag;
+				if (callback) callback (DC_SAMPLE_EVENT, sample, userdata);
+			}
+		}
+	} else {
+		// Unknown event, send it so we know we missed something
+		sample.event.type = SAMPLE_EVENT_NONE;
+		sample.event.flags = SAMPLE_FLAGS_NONE;
+		sample.event.value = code;
+		if (callback) callback (DC_SAMPLE_EVENT, sample, userdata);
+	}
+
+	return e[event_ptr].data_bytes;
+}
diff --git a/src/cochran_commander_parser.h b/src/cochran_commander_parser.h
new file mode 100644
index 0000000..314d538
--- /dev/null
+++ b/src/cochran_commander_parser.h
@@ -0,0 +1,115 @@
+/*
+ * 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
+ */
+
+typedef struct cochran_events_t {
+	unsigned char code;
+	unsigned char data_bytes;
+	char *name;
+	parser_sample_event_t type;
+	parser_sample_flags_t flag;
+} cochran_events_t;
+
+static cochran_events_t cochran_events[] = {
+	{ 0xA8, 1, "Entered PDI mode",
+					SAMPLE_EVENT_SURFACE,			SAMPLE_FLAGS_BEGIN },
+	{ 0xA9, 1, "Exited PDI mode",
+					SAMPLE_EVENT_SURFACE,			SAMPLE_FLAGS_END },
+	{ 0xAB, 5, "Ceiling decrease",
+					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_NONE },
+	{ 0xAD, 5, "Ceiling increase",
+					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_NONE },
+	{ 0xBD, 1, "Switched to nomal PO2 setting",
+					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_NONE },
+	{ 0xC0, 1, "Switched to FO2 21% mode",
+					SAMPLE_EVENT_GASCHANGE,			SAMPLE_FLAGS_NONE },
+	{ 0xC1, 1, "Ascent rate greater than limit",
+					SAMPLE_EVENT_ASCENT,			SAMPLE_FLAGS_BEGIN },
+	{ 0xC2, 1, "Low battery warning",
+					SAMPLE_EVENT_BATTERY,			SAMPLE_FLAGS_NONE },
+	{ 0xC3, 1, "CNS Oxygen toxicity warning",
+					SAMPLE_EVENT_OLF,				SAMPLE_FLAGS_NONE },
+	{ 0xC4, 1, "Depth exceeds user set point",
+					SAMPLE_EVENT_MAXDEPTH,			SAMPLE_FLAGS_NONE },
+	{ 0xC5, 1, "Entered decompression mode",
+					SAMPLE_EVENT_DEEPSTOP,			SAMPLE_FLAGS_BEGIN },
+	{ 0xC8, 1, "PO2 too high",
+					SAMPLE_EVENT_FLOOR, 			SAMPLE_FLAGS_BEGIN },
+	{ 0xCC, 1, "Low Cylinder 1 pressure",
+					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_BEGIN },
+	{ 0xCE, 1, "Non-decompression warning",
+					SAMPLE_EVENT_RBT,				SAMPLE_FLAGS_BEGIN },
+	{ 0xCD, 1, "Switched to deco blend",
+					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_BEGIN },
+	{ 0xD0, 1, "Breathing rate alarm",
+					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_BEGIN },
+	{ 0xD3, 1, "Low gas 1 flow rate",
+					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_NONE },
+	{ 0xD6, 1, "Depth is less than ceiling",
+					SAMPLE_EVENT_CEILING, 			SAMPLE_FLAGS_BEGIN },
+	{ 0xD8, 1, "End decompression mode",
+					SAMPLE_EVENT_DEEPSTOP,			SAMPLE_FLAGS_END },
+	{ 0xE1, 1, "End ascent rate warning",
+					SAMPLE_EVENT_ASCENT,			SAMPLE_FLAGS_END },
+	{ 0xE2, 1, "Low SBAT battery warning",
+					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_NONE },
+	{ 0xE3, 1, "Switched to FO2 mode",
+					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_NONE },
+	{ 0xE5, 1, "Switched to PO2 mode",
+					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_NONE },
+	{ 0xEE, 1, "End non-decompresison warning",
+					SAMPLE_EVENT_RBT,				SAMPLE_FLAGS_END },
+	{ 0xEF, 1, "Switch to blend 2",
+					SAMPLE_EVENT_GASCHANGE2,		SAMPLE_FLAGS_NONE },
+	{ 0xF0, 1, "Breathing rate alarm",
+					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_END },
+	{ 0xF3, 1, "Switch to blend 1",
+					SAMPLE_EVENT_GASCHANGE2,		SAMPLE_FLAGS_NONE },
+	{ 0xF6, 1, "End Depth is less than ceiling",
+					SAMPLE_EVENT_CEILING,			SAMPLE_FLAGS_END },
+	{ 0x00, 1, NULL,
+					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_NONE }
+};
+
+
+// Common
+dc_status_t cochran_commander_parser_create (dc_parser_t **out,
+		dc_context_t *context);
+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);
+int cochran_commander_handle_event (dc_parser_t *abstract,
+		dc_sample_callback_t callback, void *userdata, unsigned char code,
+		unsigned int offset, unsigned int time);
+
+// 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_common.c b/src/cochran_common.c
deleted file mode 100644
index 4fcd751..0000000
--- a/src/cochran_common.c
+++ /dev/null
@@ -1,890 +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 <string.h> // memcpy, memcmp
-#include <stdlib.h> // malloc, free
-#include <assert.h> // assert
-#include <time.h>
-
-#include <libdivecomputer/cochran.h>
-
-#include "context-private.h"
-#include "device-private.h"
-#include "serial.h"
-#include "array.h"
-
-#include "cochran_common.h"
-
-
-#define EXITCODE(rc) \
-( \
-	rc == -1 ? DC_STATUS_IO : DC_STATUS_TIMEOUT \
-)
-
-static const dc_device_vtable_t cochran_common_device_vtable = {
-	DC_FAMILY_COCHRAN,
-	cochran_common_device_set_fingerprint,	/* set_fingerprint */
-	cochran_common_device_read,				/* read */
-	NULL,									/* write */
-	cochran_common_device_dump,				/* dump */
-	cochran_common_device_foreach,			/* foreach */
-	cochran_common_device_close				/* close */
-};
-
-
-dc_status_t
-cochran_packet (cochran_device_t *device, const unsigned char command[],
-		unsigned int csize, unsigned char answer[], unsigned int asize,
-		int high_speed)
-{
-	dc_device_t *abstract = (dc_device_t *) device;
-	unsigned int bytes_read = 0, n, read_size;
-	unsigned int ptr;
-
-	if (device_is_cancelled (abstract))
-		return DC_STATUS_CANCELLED;
-
-	// Send the command to the device, one byte at a time
-	for (ptr = 0; ptr < csize; ptr++) {
-		if (ptr) serial_sleep(device->port, 16);	// 16 ms
-		n = serial_write(device->port, command + ptr, 1);
-		if (n != 1) {
-			ERROR (abstract->context, "Failed to send the command.");
-			return EXITCODE (n);
-		}
-	}
-
-	if (high_speed) {
-		serial_sleep(device->port, 45);
-
-		// Weird but I only get the right result when I do it twice
-		// Rates are odd, like 825600 for the EMC, 115200 for commander
-		serial_configure(device->port, device->data.high_baud, 8,
-						SERIAL_PARITY_NONE, 2, SERIAL_FLOWCONTROL_NONE);
-		serial_configure(device->port, device->data.high_baud, 8,
-						SERIAL_PARITY_NONE, 2, SERIAL_FLOWCONTROL_NONE);
-	}
-
-	// Receive the answer from the device.
-	// Use 1024 byte "packets" so we can display progress.
-	while (bytes_read < asize) {
-		if (asize - bytes_read > 1024)
-			read_size = 1024;
-		else
-			read_size = asize - bytes_read;
-
-		n = serial_read (device->port, answer + bytes_read, read_size);
-		if (n != read_size) {
-			ERROR (abstract->context, "Failed to receive data, expected %u,"
-					"read %u.", read_size, n);
-			return EXITCODE (n);
-		}
-
-		bytes_read += n;
-
-		if (device->progress) {
-			device->progress->current = bytes_read;
-			device_event_emit (abstract, DC_EVENT_PROGRESS, device->progress);
-		}
-	}
-
-	return DC_STATUS_SUCCESS;
-}
-
-
-static dc_status_t
-cochran_common_serial_setup (cochran_device_t *device, dc_context_t *context)
-{
-	int rc;
-
-	// Set the serial communication protocol (9600 8N2, no FC).
-	rc = serial_configure (device->port, 9600, 8, SERIAL_PARITY_NONE,
-							2, SERIAL_FLOWCONTROL_NONE);
-	if (rc == -1) {
-		ERROR (context, "Failed to set the terminal attributes.");
-		serial_close (device->port);
-		free (device);
-		return DC_STATUS_IO;
-	}
-
-	serial_set_queue_size(device->port, 4096, 4096);
-
-	// Make sure everything is in a sane state.
-	// Mimicing Analyst software with excessive flushes
-	serial_flush (device->port, SERIAL_QUEUE_OUTPUT);
-	serial_flush (device->port, SERIAL_QUEUE_INPUT);
-	serial_flush (device->port, SERIAL_QUEUE_INPUT);
-	serial_flush (device->port, SERIAL_QUEUE_INPUT);
-	serial_flush (device->port, SERIAL_QUEUE_INPUT);
-	serial_flush (device->port, SERIAL_QUEUE_INPUT);
-	serial_flush (device->port, SERIAL_QUEUE_INPUT);
-
-	serial_set_break(device->port, 1);
-	serial_sleep(device->port, 16);
-	
-	serial_set_break(device->port, 0);
-
-	// Set the timeout for receiving data (5000 ms).
-	if (serial_set_timeout (device->port, 5000) == -1) {
-		ERROR (context, "Failed to set the timeout.");
-		serial_close (device->port);
-		free (device);
-		return DC_STATUS_IO;
-	}
-
-	// Wait for heartbeat byte before send
-	int n;
-	char answer[1];
-	if ((n = serial_read(device->port, answer, 1)) != 1) {
-		ERROR (context, "Failed to receive device heartbeat.");
-		return EXITCODE (n);
-	}
-
-	return DC_STATUS_SUCCESS;
-}
-
-
-dc_status_t
-cochran_common_serial_open(cochran_device_t *device, dc_context_t *context)
-{
-	// Open the device.
-	int rc = serial_open (&device->port, context, device->name);
-	if (rc == -1) {
-		ERROR (context, "Failed to open the serial port.");
-		free (device);
-		return DC_STATUS_IO;
-	}
-
-	return cochran_common_serial_setup(device, context);
-}
-
-
-dc_status_t
-cochran_common_device_open (dc_device_t **out, dc_context_t *context,
-		const char *name)
-{
-	dc_status_t rc;
-
-	if (out == NULL)
-		return DC_STATUS_INVALIDARGS;
-
-	// Allocate memory.
-	cochran_device_t *device = (cochran_device_t *) malloc (
-			sizeof (cochran_device_t));
-	if (device == NULL) {
-		ERROR (context, "Failed to allocate memory.");
-		return DC_STATUS_NOMEMORY;
-	}
-
-	// Initialize the base class.
-	device_init (&device->base, context, &cochran_common_device_vtable);
-
-	// Set the default values.
-	device->port = NULL;
-	device->name = name;
-	device->progress = NULL;
-	device->data.logbook = NULL;
-	device->data.sample = NULL;
-	cochran_common_device_set_fingerprint((dc_device_t *) device, "", 0);
-
-	if ((rc = cochran_common_serial_open(device, context))
-			!= DC_STATUS_SUCCESS)
-		return rc;
-
-	// Read ID from the device
-	rc = cochran_read_id((dc_device_t *) device);
-
-	if (rc != DC_STATUS_SUCCESS) {
-		ERROR (context, "Device not responding.");
-		serial_close (device->port);
-		free (device);
-		return rc;
-	}
-
-	// Check ID
-	if ((device->data.model & 0xFF0000) == COCHRAN_MODEL_UNKNOWN) {
-		ERROR (context, "Device not recognized.");
-		serial_close (device->port);
-		free (device);
-		return DC_STATUS_UNSUPPORTED;
-	}
-
-	*out = (dc_device_t *) device;
-
-	return DC_STATUS_SUCCESS;
-}
-
-
-dc_status_t
-cochran_common_device_close (dc_device_t *abstract)
-{
-	cochran_device_t *device = (cochran_device_t*) abstract;
-
-	// Close the device.
-	if (serial_close (device->port) == -1) {
-		free (device);
-		return DC_STATUS_IO;
-	}
-
-	// Free memory.
-	free (device->data.logbook);
-	free (device->data.sample);
-	free (device);
-
-	return DC_STATUS_SUCCESS;
-}
-
-
-dc_status_t
-cochran_common_device_set_fingerprint (dc_device_t *abstract,
-		const unsigned char data[], unsigned int size)
-{
-	cochran_device_t *device = (cochran_device_t *) abstract;
-	cochran_data_t *d = &(device->data);
-
-	if (size && size != sizeof (d->fingerprint))
-		return DC_STATUS_INVALIDARGS;
-
-	if (size)
-		memcpy (&(d->fingerprint), data, sizeof (d->fingerprint));
-	else
-		memset (&(d->fingerprint), 0xFF, sizeof (d->fingerprint));
-
-	return DC_STATUS_SUCCESS;
-}
-
-
-dc_status_t
-cochran_common_device_read (dc_device_t *abstract, unsigned int address,
-		unsigned char data[], unsigned int size)
-{
-	cochran_device_t *device = (cochran_device_t*) abstract;
-
-	// Build the command
-	unsigned char command[10];
-	unsigned char command_size;
-
-	switch (device->data.address_length)
-	{
-	case COCHRAN_ADDRESS_LENGTH_32:
-		// EMC uses 32 bit addressing
-		command[0] = 0x15;
-		command[1] = (address      ) & 0xff;
-		command[2] = (address >>  8) & 0xff;
-		command[3] = (address >> 16) & 0xff;
-		command[4] = (address >> 24) & 0xff;
-		command[5] = (size         ) & 0xff;
-		command[6] = (size >>  8   ) & 0xff;
-		command[7] = (size >> 16   ) & 0xff;
-		command[8] = (size >> 24   ) & 0xff;
-		command[9] = 0x05;
-		command_size = 10;
-		break;
-	case COCHRAN_ADDRESS_LENGTH_24:
-		// Commander uses 24 byte addressing
-		command[0] = 0x15;
-		command[1] = (address      ) & 0xff;
-		command[2] = (address >>  8) & 0xff;
-		command[3] = (address >> 16) & 0xff;
-		command[4] = (size         ) & 0xff;
-		command[5] = (size >>  8   ) & 0xff;
-		command[6] = (size >> 16   ) & 0xff;
-		command[7] = 0x04;
-		command_size = 8;
-		break; default:
-		return DC_STATUS_UNSUPPORTED;
-	}
-
-	// Read data at high speed
-	dc_status_t rc = cochran_packet (device, command, command_size, data,
-			size, 1);
-	if (rc != DC_STATUS_SUCCESS)
-		return rc;
-
-	return DC_STATUS_SUCCESS;
-}
-
-
-static void
-cochran_set_device_config (cochran_device_t *device)
-{
-	dc_device_t *abstract = (dc_device_t *) device;
-	// Determine model
-	if (memcmp(device->data.id + 0x3B, "AM2315\xA3\x71", 8) == 0)
-	{
-		device->data.model = COCHRAN_MODEL_EMC_20;
-		device->data.log_size = 512;
-		device->data.sample_memory_start_address = 0x94000;
-		device->data.dive_num_ptr = 0x56;
-		device->data.dive_count_ptr = 0xD2;
-		device->data.dive_count_endian = COCHRAN_LE_TYPE;
-		device->data.sample_end_ptr = 256;
-		device->data.log_pre_dive_ptr = 30;
-		device->data.log_end_dive_ptr = 256;
-		device->data.last_interdive_ptr = 233;
-		device->data.last_entry_ptr = 194;
-		device->data.date_format = COCHRAN_DATE_FORMAT_SMHDMY;
-		device->data.address_length = COCHRAN_ADDRESS_LENGTH_32;
-		device->data.high_baud = 825600;
-	}
-	else if (memcmp(device->data.id + 0x3B, "AMA315\xC3\xC5", 8) == 0)
-	{
-		device->data.model = COCHRAN_MODEL_EMC_16;
-		device->data.log_size = 512;
-		device->data.sample_memory_start_address = 0x94000;
-		device->data.dive_num_ptr = 0x56;
-		device->data.dive_count_ptr = 0xD2;
-		device->data.dive_count_endian = COCHRAN_LE_TYPE;
-		device->data.sample_end_ptr = 256;
-		device->data.log_pre_dive_ptr = 30;
-		device->data.log_end_dive_ptr = 256;
-		device->data.last_interdive_ptr = 233;
-		device->data.last_entry_ptr = 194;
-		device->data.date_format = COCHRAN_DATE_FORMAT_SMHDMY;
-		device->data.address_length = COCHRAN_ADDRESS_LENGTH_32;
-		device->data.high_baud = 825600;
-	}
-	else if (memcmp(device->data.id + 0x3B, "AM\x11""2212\x02", 8) == 0)
-	{
-		device->data.model = COCHRAN_MODEL_COMMANDER_AIR_NITROX;
-		device->data.log_size = 256;
-		device->data.sample_memory_start_address = 0x20000;
-		device->data.dive_num_ptr = 0x46;
-		device->data.dive_count_ptr = 0x46;
-		device->data.dive_count_endian = COCHRAN_BE_TYPE;
-		device->data.sample_end_ptr = 256;
-		device->data.log_pre_dive_ptr = 30;
-		device->data.log_end_dive_ptr = 128;
-		device->data.last_interdive_ptr = 167;
-		device->data.last_entry_ptr = -1;
-		device->data.date_format = COCHRAN_DATE_FORMAT_MSDHYM;
-		device->data.address_length = COCHRAN_ADDRESS_LENGTH_24;
-		device->data.high_baud = 115200;
-	}
-	else
-	{
-		device->data.model = 0;
-		ERROR (abstract->context,
-			"Unknown Cochran model %02x %02x %02x %02x %02x %02x %02x %02x",
-			*(device->data.id + 0x3B),     *(device->data.id + 0x3B + 1),
-			*(device->data.id + 0x3B + 2), *(device->data.id + 0x3B + 3),
-			*(device->data.id + 0x3B + 4), *(device->data.id + 0x3B + 5),
-			*(device->data.id + 0x3B + 6), *(device->data.id + 0x3B + 7));
-	}
-
-	return;
-}
-
-
-static dc_status_t
-cochran_read_id (dc_device_t *abstract)
-{
-	cochran_device_t *device = (cochran_device_t *) abstract;
-	dc_status_t rc;
-	unsigned char command[6] = {0x05, 0x9D, 0xFF, 0x00, 0x43, 0x00};
-
-	rc = cochran_packet(device, command, 6, device->data.id, 67, 0);
-	if (rc != DC_STATUS_SUCCESS)
-		return rc;
-
-	if (strncmp(device->data.id, "(C)", 3) != 0) {
-		// It's a Commander, read again
-		memcpy(device->data.id0, device->data.id, 67);
-
-		command[1] = 0xBD;
-		command[2] = 0x7F;
-
-		rc = cochran_packet(device, command, 6, device->data.id, 67, 0);
-		if (rc != DC_STATUS_SUCCESS)
-			return rc;
-	}
-
-	cochran_set_device_config(device);
-
-	return DC_STATUS_SUCCESS;
-}
-
-
-static dc_status_t
-cochran_read_config (dc_device_t *abstract)
-{
-	cochran_device_t *device = (cochran_device_t *) abstract;
-	cochran_data_t *data = &device->data;
-	dc_status_t rc;
-	unsigned char command[2] = { 0x96, 0x00 };
-
-	if ((data->model & 0xFF0000) == COCHRAN_MODEL_EMC_FAMILY)
-		data->config_count = 2;
-	else
-		data->config_count = 4;
-
-	int n;
-	for (n = 0; n < data->config_count; n++) {
-		command[1] = n;
-		rc = cochran_packet(device, command, 2, data->config[n], 512, 0);
-		if (rc != DC_STATUS_SUCCESS)
-			return rc;
-	}
-
-	return DC_STATUS_SUCCESS;
-}
-
-
-static dc_status_t
-cochran_read_misc (dc_device_t *abstract)
-{
-	cochran_device_t *device = (cochran_device_t *) abstract;
-
-	unsigned char command[7] = { 0x89, 0x05, 0x00, 0x00, 0x00, 0xDC, 0x05 };
-
-	switch (device->data.model & 0xFF0000)
-	{
-	case COCHRAN_MODEL_COMMANDER_FAMILY:
-		command[2] = 0xCA;
-		command[3] = 0xFD;
-		break;
-	case COCHRAN_MODEL_EMC_FAMILY:
-		command[2] = 0xE0;
-		command[3] = 0x03;
-		break;
-	default:
-		return DC_STATUS_UNSUPPORTED;
-	}
-
-	// Send first byte then wait for heartbeat before sending the rest
-	serial_write(device->port, command, 1);
-
-	int n;
-	char answer[1];
-	if ((n = serial_read(device->port, answer, 1)) != 1) {
-		ERROR (abstract->context, "Failed to receive device heartbeat.");
-		return EXITCODE (n);
-	}
-
-	return cochran_packet(device, command + 1, 6, device->data.misc, 1500, 0);
-}
-
-
-static dc_status_t
-cochran_read_logbook (dc_device_t *abstract)
-{
-	cochran_device_t *device = (cochran_device_t *) abstract;
-	cochran_data_t *d = &(device->data);
-	dc_status_t rc;
-
-	if (d->logbook)
-		free(d->logbook);
-
-	// Allocate space for log book.
-	d->logbook = (unsigned char *) malloc(d->logbook_size);
-	if (device == NULL) {
-		ERROR (abstract->context, "Failed to allocate memory.");
-		return DC_STATUS_NOMEMORY;
-	}
-
-	// Enable progress notifications.
-	device->progress = malloc(sizeof(dc_event_progress_t));
-	if (device->progress == NULL) {
-		ERROR (abstract->context, "Failed to allocate memory.");
-		return DC_STATUS_NOMEMORY;
-	}
-
-	device->progress->current = 0;
-	device->progress->maximum = d->logbook_size;
-	device_event_emit (abstract, DC_EVENT_PROGRESS, device->progress);
-
-	serial_sleep(device->port, 800);
-
-	// set back to 9600 baud
-	cochran_common_serial_setup(device, abstract->context);
-
-	// Request log book
-	rc = cochran_common_device_read(abstract, 0, d->logbook, d->logbook_size);
-
-	device->progress->current = d->logbook_size;
-	device_event_emit (abstract, DC_EVENT_PROGRESS, device->progress);
-	free (device->progress);
-
-	return rc;
-}
-
-
-static void
-cochran_find_fingerprint(dc_device_t *abstract)
-{
-	cochran_device_t *device = (cochran_device_t *) abstract;
-	cochran_data_t *d = (cochran_data_t *) &(device->data);
-
-	// 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 * d->log_size
-						+ d->dive_num_ptr,
-						 sizeof(d->fingerprint)))
-		d->fp_dive_num--;
-}
-
-
-static void
-cochran_get_sample_parms(dc_device_t *abstract)
-{
-	cochran_device_t *device = (cochran_device_t *) abstract;
-	cochran_data_t *d = (cochran_data_t *) &(device->data);
-	unsigned int pre_dive_offset, end_dive_offset;
-	unsigned int low_offset, high_offset;
-
-	// Find lowest and highest offsets into sample data
-	low_offset = 0xFFFFFFFF;
-	high_offset = 0;
-
-	int i;
-	for (i = d->fp_dive_num + 1; i < d->dive_count; i++) {
-		pre_dive_offset = array_uint32_le (&(d->logbook[i * d->log_size
-				+ d->log_pre_dive_ptr]));
-		end_dive_offset = array_uint32_le (&(d->logbook[i * d->log_size
-				+ d->log_end_dive_ptr]));
-
-		// Check for ring buffer wrap-around.
-		if (pre_dive_offset > end_dive_offset)
-			break;
-	
-		if (pre_dive_offset < low_offset)
-			low_offset = pre_dive_offset;
-		if (end_dive_offset > high_offset && end_dive_offset != 0xFFFFFFFF )
-			high_offset = end_dive_offset;
-	}
-
-	if (pre_dive_offset > end_dive_offset) {
-		// Since I can't tell how much memory it has, I'll round.
-		// 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;
-		d->sample_memory_end_address = high_offset;
-		low_offset = d->sample_memory_start_address;
-		d->sample_data_offset = low_offset;
-		d->sample_size = high_offset - low_offset;
-	} else if (low_offset < 0xFFFFFFFF && high_offset > 0) {
-		// Round offset and size to 16K boundary
-		d->sample_data_offset = low_offset & 0xFFFFC000;
-		high_offset = ((high_offset  - 1) & 0xFFFFC000) + 0x4000;
-		d->sample_size = high_offset - d->sample_data_offset;
-	} else {
-		d->sample_data_offset = 0;
-		d->sample_size = 0;
-	}
-}
-
-
-static dc_status_t
-cochran_read_samples(dc_device_t *abstract)
-{
-	cochran_device_t *device = (cochran_device_t *) abstract;
-	cochran_data_t *d = (cochran_data_t *) &(device->data);
-	dc_status_t rc;
-
-
-	if (d->sample_size > 0) {
-		if (d->sample)
-			free(d->sample);
-
-		d->sample = (unsigned char *) malloc(d->sample_size);
-		if (device == NULL) {
-			ERROR (abstract->context, "Failed to allocate memory.");
-			return DC_STATUS_NOMEMORY;
-		}
-
-		// Enable progress notifications.
-		device->progress = malloc(sizeof(dc_event_progress_t));
-		if (device->progress == NULL) {
-			ERROR (abstract->context, "Failed to allocate memory.");
-			return DC_STATUS_NOMEMORY;
-		}
-
-		device->progress->current = 0;
-		device->progress->maximum = d->sample_size;
-		device_event_emit (abstract, DC_EVENT_PROGRESS, device->progress);
-
-		serial_sleep(device->port, 800);
-
-		// set back to 9600 baud
-		cochran_common_serial_setup(device, abstract->context);
-
-		// Read the sample data
-		rc = cochran_common_device_read (abstract, d->sample_data_offset,
-				d->sample, d->sample_size);
-		if (rc != DC_STATUS_SUCCESS) {
-			free (device->progress);
-			ERROR (abstract->context, "Failed to read the sample data.");
-			return rc;
-		}
-
-		device->progress->current = d->sample_size;
-		device_event_emit (abstract, DC_EVENT_PROGRESS, device->progress);
-		free (device->progress);
-	}
-
-	return DC_STATUS_SUCCESS;
-}
-
-
-static dc_status_t
-cochran_common_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_status_t rc;
-
-	// Read config
-	rc = cochran_read_config(abstract);
-	if (rc != DC_STATUS_SUCCESS)
-		return rc;
-
-	rc = cochran_read_misc(abstract);
-	if (rc != DC_STATUS_SUCCESS)
-		return rc;
-
-	// Determine size of dive list to read. Round up to nearest 16K
-	if (d->dive_count_endian == COCHRAN_LE_TYPE)
-		d->dive_count = array_uint16_le (d->config[0] + d->dive_count_ptr);
-	else 
-		d->dive_count = array_uint16_be (d->config[0] + d->dive_count_ptr);
-	
-	d->logbook_size = ((d->dive_count * d->log_size) & 0xFFFFC000)
-		 + 0x4000;
-
-	rc = cochran_read_logbook(abstract);
-	if (rc != DC_STATUS_SUCCESS)
-		return rc;
-
-	// Determine sample memory to read
-	cochran_find_fingerprint(abstract);	
-	cochran_get_sample_parms(abstract);
-
-	rc = cochran_read_samples(abstract);
-	if (rc != DC_STATUS_SUCCESS)
-		return rc;
-
-	return DC_STATUS_SUCCESS;
-}
-
-
-#define pack_uint32_array_le(d, i)	((d)[0] = (i) & 0xff, \
-									(d)[1] = ((i) >> 8) & 0xff, \
-									(d)[2] = ((i) >> 16) & 0xff, \
-									(d)[3] = ((i) >> 24) & 0xff)
-
-dc_status_t
-cochran_common_device_dump (dc_device_t *abstract, dc_buffer_t *data)
-{
-	cochran_device_t *device = (cochran_device_t *) abstract;
-	cochran_data_t *d = (cochran_data_t *) &(device->data);
-	int ptr = 0;
-
-	dc_status_t rc;
-	char *b;
-	int size;
-
-	rc = cochran_common_device_read_all (abstract);
-
-	if (rc != DC_STATUS_SUCCESS)
-		return rc;
-
-	// Reserve space for block pointers
-	// Structure is:
-	// 		int ptr;
-	//		char data_valid_flag;
-	dc_buffer_resize(data, 10 * 5);
-	
-	// Set pointer to first block
-	size = dc_buffer_get_size(data);
-	pack_uint32_array_le(dc_buffer_get_data(data) + ptr, size);
-
-	if (d->extra_id_flag) {
-		dc_buffer_append(data, d->id0, 67);
-		*(dc_buffer_get_data(data) + ptr + 4) = 1;
-	} else {
-		dc_buffer_resize(data, size + 67);
-		b = dc_buffer_get_data(data);
-
-		memset(b + size, 0, 67);
-	}
-	
-	// Set pointer to next block
-	ptr += 5;
-	size = dc_buffer_get_size(data);
-	pack_uint32_array_le(dc_buffer_get_data(data) + ptr, size);
-
-	dc_buffer_append (data, d->id, 67);
-	*(dc_buffer_get_data(data) + ptr + 4) = 1;
-	
-	// Add config blocks
-	int n;
-	for (n = 0; n < d->config_count; n++) {
-		// Set pointer to next block
-		ptr += 5;
-		size = dc_buffer_get_size(data);
-		pack_uint32_array_le(dc_buffer_get_data(data) + ptr, size);
-
-		dc_buffer_append (data, d->config[n], 512);
-
-		*(dc_buffer_get_data(data) + ptr + 4) = 1;
-	}
-
-	// Add blank config blocks
-	for (n = d->config_count; n < 4; n++) {
-		// Set pointer to next block
-		ptr += 5;
-		size = dc_buffer_get_size(data);
-		pack_uint32_array_le(dc_buffer_get_data(data) + ptr, size);
-
-		dc_buffer_resize(data, size + 512);
-
-		b = dc_buffer_get_data(data);
-		memset(b + size, 0, 512);
-	}
-	
-	// Set pointer to next block
-	ptr += 5;
-	size = dc_buffer_get_size(data);
-	pack_uint32_array_le(dc_buffer_get_data(data) + ptr, size);
-
-	if (d->misc) {
-		dc_buffer_append (data, d->misc, 1500);
-		*(dc_buffer_get_data(data) + ptr + 4) = 1;
-	} else {
-		dc_buffer_resize(data, size + 1500);
-
-		b = dc_buffer_get_data(data);
-		memset(b + size, 0, 1500);
-	}
-	
-	// Set pointer to next block
-	ptr += 5;
-	size = dc_buffer_get_size(data);
-	pack_uint32_array_le(dc_buffer_get_data(data) + ptr, size);
-
-	if (d->logbook) {
-		dc_buffer_append (data, d->logbook, d->logbook_size);
-		*(dc_buffer_get_data(data) + ptr + 4) = 1;
-	} else {
-		dc_buffer_resize(data, size + d->logbook_size);
-
-		b = dc_buffer_get_data(data);
-		memset(b + size, 0, d->logbook_size);
-	}
-	
-	// Set pointer to next block
-	ptr += 5;
-	size = dc_buffer_get_size(data);
-	pack_uint32_array_le(dc_buffer_get_data(data) + ptr, size);
-
-	if (d->sample) {
-		dc_buffer_append (data, d->sample, d->sample_size);
-		*(dc_buffer_get_data(data) + ptr + 4) = 1;
-	} else {
-		dc_buffer_resize(data, dc_buffer_get_size(data) + d->sample_size);
-
-		b = dc_buffer_get_data(data);
-		memset(b + size, 0, d->logbook_size);
-	}
-
-	// Set pointer to end
-	ptr += 5;
-	size = dc_buffer_get_size(data);
-	pack_uint32_array_le(dc_buffer_get_data(data) + ptr, size);
-
-	return DC_STATUS_SUCCESS;
-}
-
-
-
-dc_status_t
-cochran_common_device_foreach (dc_device_t *abstract,
-		dc_dive_callback_t callback, void *userdata)
-{
-	cochran_device_t *device = (cochran_device_t *) abstract;
-	cochran_data_t *d = &(device->data);
-	unsigned int sample_start_offset, sample_end_offset;
-	struct tm t;
-	dc_status_t rc;
-
-	rc = cochran_common_device_read_all (abstract);
-
-	if (rc != DC_STATUS_SUCCESS)
-		return rc;
-
-	// Loop through each dive
-	int i;
-	for (i = d->dive_count - 1; i > d->fp_dive_num; i--) {
-
-		d->current_log = d->logbook + i * d->log_size;
-
-		sample_start_offset = array_uint32_le (d->current_log + 6);
-		sample_end_offset = array_uint32_le (d->current_log
-				+ d->log_size/2);
-
-		d->current_sample = d->sample + sample_start_offset
-				- d->sample_data_offset;
-
-		// Check for corrupt post-dive section
-		if (array_uint32_le(d->current_log + d->log_size/2) == 0xFFFFFFFF)
-			d->corrupt_dive = 1;
-		else
-			d->corrupt_dive = 0;
-
-		// Check for ring buffer wrap
-		if (sample_start_offset > sample_end_offset)
-			d->current_sample_size = d->sample_memory_end_address
-				- sample_start_offset + 1 + sample_end_offset
-				- d->sample_memory_start_address + 1;
-		else
-			d->current_sample_size = sample_end_offset - sample_start_offset;
-
-		d->current_fingerprint = d->current_log + d->dive_num_ptr;
-
-		if (d->date_format == COCHRAN_DATE_FORMAT_SMHDMY) {
-			t.tm_sec = d->current_log[0];
-			t.tm_min = d->current_log[1];
-			t.tm_hour = d->current_log[2];
-			t.tm_mday = d->current_log[3];
-			t.tm_mon = d->current_log[4];
-			t.tm_year = d->current_log[5];
-			t.tm_wday = t.tm_yday = t.tm_isdst = 0;
-		} else {
-			t.tm_sec = d->current_log[1];
-			t.tm_min = d->current_log[0];
-			t.tm_hour = d->current_log[3];
-			t.tm_mday = d->current_log[2];
-			t.tm_mon = d->current_log[5];
-			t.tm_year = d->current_log[4];
-			t.tm_wday = t.tm_yday = t.tm_isdst = 0;
-		}
-		d->current_dive_start_time = mktime(&t);
-
-		if (callback && !callback ((unsigned char *) d, sizeof(device->data),
-				d->current_fingerprint, sizeof (d->fingerprint), userdata))
-			return DC_STATUS_SUCCESS;
-	}
-
-	return DC_STATUS_SUCCESS;
-}
diff --git a/src/cochran_common.h b/src/cochran_common.h
deleted file mode 100644
index 3498696..0000000
--- a/src/cochran_common.h
+++ /dev/null
@@ -1,157 +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
- */
-
-// seconds to add to Cochran time stamps to get unix time
-// Equivalent to Jan 1, 1992 00:00:00
-#define COCHRAN_TIMESTAMP_OFFSET 694224000
-
-#define COCHRAN_LE_TYPE 0
-#define COCHRAN_BE_TYPE 1
-
-#define COCHRAN_DATE_FORMAT_SMHDMY 0
-#define COCHRAN_DATE_FORMAT_MSDHYM 1
-
-#define COCHRAN_ADDRESS_LENGTH_32 0
-#define COCHRAN_ADDRESS_LENGTH_24 1
-
-typedef enum cochran_model_t {
-	COCHRAN_MODEL_UNKNOWN = 0,
-	COCHRAN_MODEL_EMC_FAMILY = 1 << 16,
-	COCHRAN_MODEL_EMC_14,
-	COCHRAN_MODEL_EMC_16,
-	COCHRAN_MODEL_EMC_20,
-	COCHRAN_MODEL_COMMANDER_FAMILY = 2 << 16,
-	COCHRAN_MODEL_COMMANDER_AIR_NITROX,
-} cochran_model_t;
-
-typedef struct cochran_data_t {
-	cochran_model_t model;
-
-	unsigned char id0[67];
-	unsigned char id[67];
-	unsigned char config[4][512];
-	unsigned char misc[1500];
-	unsigned char *logbook;
-	unsigned char *sample;
-
-	unsigned int extra_id_flag;
-	unsigned int config_count;
-
-	unsigned short int dive_count;
-	unsigned char fingerprint[2];
-	int fp_dive_num;
-
-	unsigned int logbook_size;
-	unsigned int current_sample_size;
-
-	unsigned int sample_data_offset;
-	unsigned int sample_size;
-	unsigned int last_interdive_offset;
-	unsigned int last_entry_offset;
-
-	unsigned char *current_fingerprint;
-	unsigned char *current_log;
-	unsigned char *current_sample;
-	time_t current_dive_start_time;
-
-	// Config items
-	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
-
-	unsigned char corrupt_dive;
-} cochran_data_t;
-
-typedef struct cochran_device_t {
-	dc_device_t base;
-	const char *name;				// serial port name
-	serial_t *port;
-	cochran_data_t data;			// dive data used in parsing
-	dc_event_progress_t *progress;	// for progress in the _read function
-} 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 CME_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_MIN_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=%
-
-// 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	 25		// 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_MIN_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
-
-
-dc_status_t cochran_packet (cochran_device_t *device,
-		const unsigned char command[], unsigned int csize,
-		unsigned char answer[], unsigned int asize, int high_speed);
-dc_status_t cochran_common_device_open (dc_device_t **out,
-		dc_context_t *context, const char *name);
-dc_status_t cochran_common_device_close (dc_device_t *abstract);
-dc_status_t cochran_common_device_set_fingerprint (dc_device_t *abstract,
-		const unsigned char data[], unsigned int size);
-dc_status_t cochran_common_device_read (dc_device_t *abstract,
-		unsigned int address, unsigned char data[], unsigned int size);
-static dc_status_t cochran_read_id (dc_device_t *abstract);
-dc_status_t cochran_common_device_foreach (dc_device_t *abstract,
-		dc_dive_callback_t callback, void *userdata);
-dc_status_t cochran_common_device_dump (dc_device_t *abstract,
-		dc_buffer_t *data);
diff --git a/src/cochran_common_parser.c b/src/cochran_common_parser.c
deleted file mode 100644
index fefbc98..0000000
--- a/src/cochran_common_parser.c
+++ /dev/null
@@ -1,218 +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 <time.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_common.h"
-#include "cochran_common_parser.h"
-
-
-dc_status_t cochran_common_parser_set_data (dc_parser_t *abstract,
-		const unsigned char *data, unsigned int size);
-dc_status_t cochran_common_parser_get_datetime (dc_parser_t *abstract,
-		dc_datetime_t *datetime);
-static dc_status_t cochran_common_parser_get_field (dc_parser_t *abstract,
-		dc_field_type_t type, unsigned int flags, void *value);
-dc_status_t cochran_common_parser_destroy (dc_parser_t *abstract);
-static dc_status_t cochran_common_parser_samples_foreach
-		(dc_parser_t *abstract, dc_sample_callback_t callback,
-		void *userdata);
-int cochran_common_handle_event (dc_parser_t *abstract,
-		dc_sample_callback_t callback, void *userdata, unsigned char code,
-		unsigned int offset, unsigned int time);
-
-
-static dc_parser_vtable_t cochran_common_parser_vtable = {
-	DC_FAMILY_COCHRAN,
-	cochran_common_parser_set_data,			/* set_data */
-	cochran_common_parser_get_datetime,		/* datetime */
-	cochran_common_parser_get_field,		/* fields */
-	cochran_common_parser_samples_foreach,	/* samples_foreach */
-	cochran_common_parser_destroy			/* destroy */
-};
-
-
-
-dc_status_t
-cochran_common_parser_create (dc_parser_t **out, dc_context_t *context)
-{
-	if (out == NULL)
-		return DC_STATUS_INVALIDARGS;
-
-	// Allocate memory.
-	dc_parser_t *parser = (dc_parser_t *) malloc (sizeof (dc_parser_t));
-	if (parser == NULL) {
-		ERROR (context, "Failed to allocate memory.");
-		return DC_STATUS_NOMEMORY;
-	}
-
-	parser_init (parser, context, &cochran_common_parser_vtable);
-
-	*out = parser;
-
-	return DC_STATUS_SUCCESS;
-}
-
-
-dc_status_t
-cochran_common_parser_destroy (dc_parser_t *abstract)
-{
-	// Free memory.
-	free (abstract);
-
-	return DC_STATUS_SUCCESS;
-}
-
-
-dc_status_t
-cochran_common_parser_set_data (dc_parser_t *abstract,
-		const unsigned char *data, unsigned int size)
-{
-	abstract->data = data;
-	abstract->size = size;
-
-	return DC_STATUS_SUCCESS;
-}
-
-
-// There are two date formats used by Cochran
-dc_status_t
-cochran_common_parser_get_datetime (dc_parser_t *abstract,
-		dc_datetime_t *datetime)
-{
-	cochran_data_t *data = (cochran_data_t *) abstract->data;
-	const unsigned char *log = data->current_log;
-
-	if (data->date_format == COCHRAN_DATE_FORMAT_SMHDMY) {
-		datetime->second = log[0];
-		datetime->minute = log[1];
-		datetime->hour = log[2];
-		datetime->day = log[3];
-		datetime->month = log[4];
-		datetime->year = log[5] + (log[5] > 91 ? 1900 : 2000);
-	} else {
-		datetime->second = log[1];
-		datetime->minute = log[0];
-		datetime->hour = log[3];
-		datetime->day = log[2];
-		datetime->month = log[5];
-		datetime->year = log[4] + (log[5] > 91 ? 1900 : 2000);
-	}
-
-	return DC_STATUS_SUCCESS;
-}
-
-static dc_status_t
-cochran_common_parser_get_field (dc_parser_t *abstract, dc_field_type_t type,
-			unsigned int flags, void *value)
-{
-	cochran_data_t *data = (cochran_data_t *) abstract->data;
-
-	switch (data->model & 0xFF0000)
-	{
-	case COCHRAN_MODEL_COMMANDER_FAMILY:
-		return cochran_cmdr_parser_get_field(abstract, type, flags, value);
-		break;
-	case COCHRAN_MODEL_EMC_FAMILY:
-		return cochran_emc_parser_get_field(abstract, type, flags, value);
-		break;
-	}
-}
-
-static dc_status_t
-cochran_common_parser_samples_foreach (dc_parser_t *abstract,
-			dc_sample_callback_t callback, void *userdata)
-{
-	cochran_data_t *data = (cochran_data_t *) abstract->data;
-
-	switch (data->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;
-	}
-}
-
-
-
-int
-cochran_common_handle_event (dc_parser_t *abstract,
-		dc_sample_callback_t callback, void *userdata, unsigned char code,
-		unsigned int offset, unsigned int time)
-{
-	cochran_data_t *data = (cochran_data_t *) abstract->data;
-	cochran_events_t *e = cochran_events;
-
-	dc_sample_value_t sample = {0};
-
-	unsigned char event_ptr = 0;
-
-	while (e[event_ptr].code && e[event_ptr].code != code)
-		event_ptr++;
-
-	sample.event.time = 0;
-
-	if (e[event_ptr].code) {
-		switch (e[event_ptr].code)
-		{
-		case 0xAB:	// Ceiling decrease
-			// Indicated to lower ceiling by 10 ft (deeper)
-			// Bytes 1-2: first stop duration (min)
-			// Bytes 3-4: total stop duration (min)
-			// Handled in calling function
-			break;
-		case 0xAD:	// Ceiling increase
-			// Indicates to raise ceiling by 10 ft (shallower)
-			// Handled in calling function
-			break;
-		default:
-			// Don't send known events of type NONE
-			if (! e[event_ptr].type == SAMPLE_EVENT_NONE) {
-				sample.event.type = e[event_ptr].type;
-				sample.event.flags = e[event_ptr].flag;
-				if (callback) callback (DC_SAMPLE_EVENT, sample, userdata);
-			}
-		}
-	} else {
-		// Unknown event, send it so we know we missed something
-		sample.event.type = SAMPLE_EVENT_NONE;
-		sample.event.flags = SAMPLE_FLAGS_NONE;
-		sample.event.value = code;
-		if (callback) callback (DC_SAMPLE_EVENT, sample, userdata);
-	}
-
-	return e[event_ptr].data_bytes;
-}
diff --git a/src/cochran_common_parser.h b/src/cochran_common_parser.h
deleted file mode 100644
index 9c4a67d..0000000
--- a/src/cochran_common_parser.h
+++ /dev/null
@@ -1,115 +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
- */
-
-typedef struct cochran_events_t {
-	unsigned char code;
-	unsigned char data_bytes;
-	char *name;
-	parser_sample_event_t type;
-	parser_sample_flags_t flag;
-} cochran_events_t;
-
-static cochran_events_t cochran_events[] = {
-	{ 0xA8, 1, "Entered PDI mode",
-					SAMPLE_EVENT_SURFACE,			SAMPLE_FLAGS_BEGIN },
-	{ 0xA9, 1, "Exited PDI mode",
-					SAMPLE_EVENT_SURFACE,			SAMPLE_FLAGS_END },
-	{ 0xAB, 5, "Ceiling decrease",
-					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_NONE },
-	{ 0xAD, 5, "Ceiling increase",
-					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_NONE },
-	{ 0xBD, 1, "Switched to nomal PO2 setting",
-					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_NONE },
-	{ 0xC0, 1, "Switched to FO2 21% mode",
-					SAMPLE_EVENT_GASCHANGE,			SAMPLE_FLAGS_NONE },
-	{ 0xC1, 1, "Ascent rate greater than limit",
-					SAMPLE_EVENT_ASCENT,			SAMPLE_FLAGS_BEGIN },
-	{ 0xC2, 1, "Low battery warning",
-					SAMPLE_EVENT_BATTERY,			SAMPLE_FLAGS_NONE },
-	{ 0xC3, 1, "CNS Oxygen toxicity warning",
-					SAMPLE_EVENT_OLF,				SAMPLE_FLAGS_NONE },
-	{ 0xC4, 1, "Depth exceeds user set point",
-					SAMPLE_EVENT_MAXDEPTH,			SAMPLE_FLAGS_NONE },
-	{ 0xC5, 1, "Entered decompression mode",
-					SAMPLE_EVENT_DEEPSTOP,			SAMPLE_FLAGS_BEGIN },
-	{ 0xC8, 1, "PO2 too high",
-					SAMPLE_EVENT_FLOOR, 			SAMPLE_FLAGS_BEGIN },
-	{ 0xCC, 1, "Low Cylinder 1 pressure",
-					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_BEGIN },
-	{ 0xCE, 1, "Non-decompression warning",
-					SAMPLE_EVENT_RBT,				SAMPLE_FLAGS_BEGIN },
-	{ 0xCD, 1, "Switched to deco blend",
-					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_BEGIN },
-	{ 0xD0, 1, "Breathing rate alarm",
-					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_BEGIN },
-	{ 0xD3, 1, "Low gas 1 flow rate",
-					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_NONE },
-	{ 0xD6, 1, "Depth is less than ceiling",
-					SAMPLE_EVENT_CEILING, 			SAMPLE_FLAGS_BEGIN },
-	{ 0xD8, 1, "End decompression mode",
-					SAMPLE_EVENT_DEEPSTOP,			SAMPLE_FLAGS_END },
-	{ 0xE1, 1, "End ascent rate warning",
-					SAMPLE_EVENT_ASCENT,			SAMPLE_FLAGS_END },
-	{ 0xE2, 1, "Low SBAT battery warning",
-					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_NONE },
-	{ 0xE3, 1, "Switched to FO2 mode",
-					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_NONE },
-	{ 0xE5, 1, "Switched to PO2 mode",
-					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_NONE },
-	{ 0xEE, 1, "End non-decompresison warning",
-					SAMPLE_EVENT_RBT,				SAMPLE_FLAGS_END },
-	{ 0xEF, 1, "Switch to blend 2",
-					SAMPLE_EVENT_GASCHANGE2,		SAMPLE_FLAGS_NONE },
-	{ 0xF0, 1, "Breathing rate alarm",
-					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_END },
-	{ 0xF3, 1, "Switch to blend 1",
-					SAMPLE_EVENT_GASCHANGE2,		SAMPLE_FLAGS_NONE },
-	{ 0xF6, 1, "End Depth is less than ceiling",
-					SAMPLE_EVENT_CEILING,			SAMPLE_FLAGS_END },
-	{ 0x00, 1, NULL,
-					SAMPLE_EVENT_NONE,				SAMPLE_FLAGS_NONE }
-};
-
-
-// Common
-dc_status_t cochran_common_parser_create (dc_parser_t **out,
-		dc_context_t *context);
-dc_status_t cochran_common_parser_destroy (dc_parser_t *abstract);
-dc_status_t cochran_common_parser_set_data (dc_parser_t *abstract,
-		const unsigned char *data, unsigned int size);
-dc_status_t cochran_common_parser_get_datetime (dc_parser_t *abstract,
-		dc_datetime_t *datetime);
-int cochran_common_handle_event (dc_parser_t *abstract,
-		dc_sample_callback_t callback, void *userdata, unsigned char code,
-		unsigned int offset, unsigned int time);
-
-// 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
index e1f28ac..37089a7 100644
--- a/src/cochran_emc_parser.c
+++ b/src/cochran_emc_parser.c
@@ -32,8 +32,8 @@
 #include "serial.h"
 #include "array.h"
 
-#include "cochran_common.h"
-#include "cochran_common_parser.h"
+#include "cochran_commander.h"
+#include "cochran_commander_parser.h"
 
 
 struct dive_stats {
@@ -230,7 +230,7 @@ cochran_emc_parser_samples_foreach (dc_parser_t *abstract,
 
 		// Check for event
 		if (s[0] & 0x80) {
-			offset += cochran_common_handle_event(abstract, callback, userdata,
+			offset += cochran_commander_handle_event(abstract, callback, userdata,
 				s[0], offset, time);
 			switch (s[0])
 			{
diff --git a/src/descriptor.c b/src/descriptor.c
index 8836709..d4c2852 100644
--- a/src/descriptor.c
+++ b/src/descriptor.c
@@ -223,9 +223,9 @@ static const dc_descriptor_t g_descriptors[] = {
 	{"Shearwater", "Petrel",   DC_FAMILY_SHEARWATER_PETREL, 3},
 	/* Dive Rite NiTek Q */
 	{"Dive Rite", "NiTek Q",   DC_FAMILY_DIVERITE_NITEKQ, 0},
-	{"Cochran", "Commander",	DC_FAMILY_COCHRAN, 0},
-	{"Cochran", "EMC-16",		DC_FAMILY_COCHRAN, 1},
-	{"Cochran", "EMC-20H",		DC_FAMILY_COCHRAN, 2},
+	{"Cochran", "Commander",	DC_FAMILY_COCHRAN_COMMANDER, 0},
+	{"Cochran", "EMC-16",		DC_FAMILY_COCHRAN_COMMANDER, 1},
+	{"Cochran", "EMC-20H",		DC_FAMILY_COCHRAN_COMMANDER, 2},
 };
 
 typedef struct dc_descriptor_iterator_t {
diff --git a/src/device.c b/src/device.c
index cdd41ab..abbd6ec 100644
--- a/src/device.c
+++ b/src/device.c
@@ -155,8 +155,8 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr
 	case DC_FAMILY_DIVERITE_NITEKQ:
 		rc = diverite_nitekq_device_open (&device, context, name);
 		break;
-	case DC_FAMILY_COCHRAN:
-		rc = cochran_common_device_open (&device, context, name);
+	case DC_FAMILY_COCHRAN_COMMANDER:
+		rc = cochran_commander_device_open (&device, context, name);
 		break;
 	default:
 		return DC_STATUS_INVALIDARGS;
diff --git a/src/parser.c b/src/parser.c
index 07e9d2d..1759c81 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -132,8 +132,8 @@ dc_parser_new (dc_parser_t **out, dc_device_t *device)
 	case DC_FAMILY_DIVERITE_NITEKQ:
 		rc = diverite_nitekq_parser_create (&parser, context);
 		break;
-	case DC_FAMILY_COCHRAN:
-		rc = cochran_common_parser_create (&parser, context);
+	case DC_FAMILY_COCHRAN_COMMANDER:
+		rc = cochran_commander_parser_create (&parser, context);
 		break;
 	default:
 		return DC_STATUS_INVALIDARGS;
-- 
1.8.3.1



More information about the devel mailing list