Add support for Pre-21000 s/n Commander dive computers

This adds support for older Cochran Commander dive computers,
specifically Commanders with serial numbers prior to 21000.

This also renames "Commander" model to "Commander II" and
adds "Commander I" to refer to pre-21000 models.
This commit is contained in:
John Van Ostrand 2017-06-02 19:51:28 -04:00 committed by Jef Driesen
parent 45f0605678
commit 216e393f43
5 changed files with 170 additions and 38 deletions

View File

@ -198,6 +198,13 @@ array_uint32_le (const unsigned char data[])
}
unsigned int
array_uint32_word_be (const unsigned char data[])
{
return data[1] + (data[0] << 8) + (data[3] << 16) + (data[2] << 24);
}
void
array_uint32_le_set (unsigned char data[], const unsigned int input)
{

View File

@ -64,6 +64,9 @@ array_uint32_be (const unsigned char data[]);
unsigned int
array_uint32_le (const unsigned char data[]);
unsigned int
array_uint32_word_be (const unsigned char data[]);
void
array_uint32_le_set (unsigned char data[], const unsigned int input);

View File

@ -34,14 +34,16 @@
#define MAXRETRIES 2
#define COCHRAN_MODEL_COMMANDER_AIR_NITROX 0
#define COCHRAN_MODEL_EMC_14 1
#define COCHRAN_MODEL_EMC_16 2
#define COCHRAN_MODEL_EMC_20 3
#define COCHRAN_MODEL_COMMANDER_PRE21000 0
#define COCHRAN_MODEL_COMMANDER_AIR_NITROX 1
#define COCHRAN_MODEL_EMC_14 2
#define COCHRAN_MODEL_EMC_16 3
#define COCHRAN_MODEL_EMC_20 4
typedef enum cochran_endian_t {
ENDIAN_LE,
ENDIAN_BE,
ENDIAN_WORD_BE,
} cochran_endian_t;
typedef enum cochran_profile_size_t {
@ -50,7 +52,7 @@ typedef enum cochran_profile_size_t {
} cochran_profile_size_t;
typedef struct cochran_commander_model_t {
unsigned char id[2 + 1];
unsigned char id[3 + 1];
unsigned int model;
} cochran_commander_model_t;
@ -86,7 +88,9 @@ typedef struct cochran_device_layout_t {
// Profile ringbuffer.
unsigned int rb_profile_begin;
unsigned int rb_profile_end;
// Profile pointers.
// pointers.
unsigned int pt_fingerprint;
unsigned int fingerprint_size;
unsigned int pt_profile_pre;
unsigned int pt_profile_begin;
unsigned int pt_profile_end;
@ -117,6 +121,30 @@ static const dc_device_vtable_t cochran_commander_device_vtable = {
cochran_commander_device_close /* close */
};
// Cochran Commander pre-21000 s/n
static const cochran_device_layout_t cochran_cmdr_1_device_layout = {
COCHRAN_MODEL_COMMANDER_PRE21000, // model
24, // address_bits
ENDIAN_WORD_BE, // endian
115200, // baudrate
0x046, // cf_dive_count
0x6c, // cf_last_log
0x70, // cf_last_interdive
0x0AA, // cf_serial_number
0x00000000, // rb_logbook_begin
0x00020000, // rb_logbook_end
256, // rb_logbook_entry_size
512, // rb_logbook_entry_count
0x00020000, // rb_profile_begin
0x00100000, // rb_profile_end
12, // pt_fingerprint
4, // fingerprint_size
28, // pt_profile_pre
0, // pt_profile_begin
128, // pt_profile_end
};
// Cochran Commander Nitrox
static const cochran_device_layout_t cochran_cmdr_device_layout = {
COCHRAN_MODEL_COMMANDER_AIR_NITROX, // model
@ -133,6 +161,8 @@ static const cochran_device_layout_t cochran_cmdr_device_layout = {
512, // rb_logbook_entry_count
0x00020000, // rb_profile_begin
0x00100000, // rb_profile_end
0, // pt_fingerprint
6, // fingerprint_size
30, // pt_profile_pre
6, // pt_profile_begin
128, // pt_profile_end
@ -154,6 +184,8 @@ static const cochran_device_layout_t cochran_emc14_device_layout = {
256, // rb_logbook_entry_count
0x00022000, // rb_profile_begin
0x00200000, // rb_profile_end
0, // pt_fingerprint
6, // fingerprint_size
30, // pt_profile_pre
6, // pt_profile_begin
256, // pt_profile_end
@ -175,6 +207,8 @@ static const cochran_device_layout_t cochran_emc16_device_layout = {
1024, // rb_logbook_entry_count
0x00094000, // rb_profile_begin
0x00800000, // rb_profile_end
0, // pt_fingerprint
6, // fingerprint_size
30, // pt_profile_pre
6, // pt_profile_begin
256, // pt_profile_end
@ -196,6 +230,8 @@ static const cochran_device_layout_t cochran_emc20_device_layout = {
1024, // rb_logbook_entry_count
0x00094000, // rb_profile_begin
0x01000000, // rb_profile_end
0, // pt_fingerprint
6, // fingerprint_size
30, // pt_profile_pre
6, // pt_profile_begin
256, // pt_profile_end
@ -207,10 +243,14 @@ static unsigned int
cochran_commander_get_model (cochran_commander_device_t *device)
{
const cochran_commander_model_t models[] = {
{"\x11""2", COCHRAN_MODEL_COMMANDER_AIR_NITROX},
{"73", COCHRAN_MODEL_EMC_14},
{"A3", COCHRAN_MODEL_EMC_16},
{"23", COCHRAN_MODEL_EMC_20},
{"\x11""21", COCHRAN_MODEL_COMMANDER_PRE21000},
{"\x11""22", COCHRAN_MODEL_COMMANDER_AIR_NITROX},
{"730", COCHRAN_MODEL_EMC_14},
{"731", COCHRAN_MODEL_EMC_14},
{"A30", COCHRAN_MODEL_EMC_16},
{"A31", COCHRAN_MODEL_EMC_16},
{"230", COCHRAN_MODEL_EMC_20},
{"231", COCHRAN_MODEL_EMC_20},
};
unsigned int model = 0xFFFFFFFF;
@ -541,7 +581,11 @@ cochran_commander_find_fingerprint(cochran_commander_device_t *device, cochran_d
data->invalid_profile_dive_num = -1;
// Remove the pre-dive events that occur after the last dive
unsigned int rb_head_ptr = (array_uint32_le(data->config + device->layout->cf_last_interdive) & 0xfffff000) + 0x2000;
unsigned int rb_head_ptr = 0;
if (device->layout->endian == ENDIAN_WORD_BE)
rb_head_ptr = (array_uint32_word_be(data->config + device->layout->cf_last_interdive) & 0xfffff000) + 0x2000;
else
rb_head_ptr = (array_uint32_le(data->config + device->layout->cf_last_interdive) & 0xfffff000) + 0x2000;
unsigned int last_dive_end_address = array_uint32_le(data->logbook + dive_count * device->layout->rb_logbook_entry_size + device->layout->pt_profile_end);
if (rb_head_ptr > last_dive_end_address)
profile_capacity_remaining -= rb_head_ptr - last_dive_end_address;
@ -565,7 +609,7 @@ cochran_commander_find_fingerprint(cochran_commander_device_t *device, cochran_d
unsigned char *log_entry = data->logbook + idx * device->layout->rb_logbook_entry_size;
// We're done if we find the fingerprint
if (!memcmp(device->fingerprint, log_entry, sizeof(device->fingerprint))) {
if (!memcmp(device->fingerprint, log_entry + device->layout->pt_fingerprint, device->layout->fingerprint_size)) {
data->fp_dive_num = idx;
break;
}
@ -694,6 +738,9 @@ cochran_commander_device_open (dc_device_t **out, dc_context_t *context, const c
unsigned int model = cochran_commander_get_model(device);
switch (model) {
case COCHRAN_MODEL_COMMANDER_PRE21000:
device->layout = &cochran_cmdr_1_device_layout;
break;
case COCHRAN_MODEL_COMMANDER_AIR_NITROX:
device->layout = &cochran_cmdr_device_layout;
break;
@ -744,13 +791,13 @@ cochran_commander_device_set_fingerprint (dc_device_t *abstract, const unsigned
{
cochran_commander_device_t *device = (cochran_commander_device_t *) abstract;
if (size && size != sizeof (device->fingerprint))
if (size && size != device->layout->fingerprint_size)
return DC_STATUS_INVALIDARGS;
if (size)
memcpy (device->fingerprint, data, sizeof (device->fingerprint));
memcpy (device->fingerprint, data, device->layout->fingerprint_size);
else
memset (device->fingerprint, 0xFF, sizeof (device->fingerprint));
memset (device->fingerprint, 0xFF, sizeof(device->fingerprint));
return DC_STATUS_SUCCESS;
}
@ -887,12 +934,21 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call
dc_event_devinfo_t devinfo;
devinfo.model = device->layout->model;
devinfo.firmware = 0; // unknown
devinfo.serial = array_uint32_le(data.config + device->layout->cf_serial_number);
if (device->layout->endian == ENDIAN_WORD_BE)
devinfo.serial = array_uint32_word_be(data.config + device->layout->cf_serial_number);
else
devinfo.serial = array_uint32_le(data.config + device->layout->cf_serial_number);
device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo);
// Calculate profile RB effective head pointer
// Cochran seems to erase 8K chunks so round up.
unsigned int last_start_address = (array_uint32_le(data.config + device->layout->cf_last_interdive) & 0xfffff000) + 0x2000;
unsigned int last_start_address;
if (device->layout->endian == ENDIAN_WORD_BE)
last_start_address = (array_uint32_word_be(data.config + device->layout->cf_last_interdive) & 0xfffff000) + 0x2000;
else
last_start_address = (array_uint32_le(data.config + device->layout->cf_last_interdive) & 0xfffff000) + 0x2000;
if (last_start_address < device->layout->rb_profile_begin || last_start_address > device->layout->rb_profile_end) {
ERROR(abstract->context, "Invalid profile ringbuffer head pointer in Cochran config block.");
status = DC_STATUS_DATAFORMAT;
@ -983,7 +1039,7 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call
}
}
if (callback && !callback (dive, dive_size, dive, sizeof(device->fingerprint), userdata)) {
if (callback && !callback (dive, dive_size, dive + device->layout->pt_fingerprint, device->layout->fingerprint_size, userdata)) {
free(dive);
break;
}

View File

@ -31,10 +31,14 @@
#define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array))
#define COCHRAN_MODEL_COMMANDER_AIR_NITROX 0
#define COCHRAN_MODEL_EMC_14 1
#define COCHRAN_MODEL_EMC_16 2
#define COCHRAN_MODEL_EMC_20 3
#define COCHRAN_MODEL_COMMANDER_PRE21000 0
#define COCHRAN_MODEL_COMMANDER_AIR_NITROX 1
#define COCHRAN_MODEL_EMC_14 2
#define COCHRAN_MODEL_EMC_16 3
#define COCHRAN_MODEL_EMC_20 4
// Cochran time stamps start at Jan 1, 1992
#define COCHRAN_EPOCH 694242000
#define UNSUPPORTED 0xFFFFFFFF
@ -43,11 +47,19 @@ typedef enum cochran_sample_format_t {
SAMPLE_EMC,
} cochran_sample_format_t;
typedef enum cochran_date_encoding_t {
DATE_ENCODING_MSDHYM,
DATE_ENCODING_SMHDMY,
DATE_ENCODING_TICKS,
} cochran_date_encoding_t;
typedef struct cochran_parser_layout_t {
cochran_sample_format_t format;
unsigned int headersize;
unsigned int samplesize;
unsigned int second, minute, hour, day, month, year;
cochran_date_encoding_t date_encoding;
unsigned int datetime;
unsigned int pt_profile_begin;
unsigned int water_conductivity;
unsigned int pt_profile_pre;
@ -101,11 +113,36 @@ static const dc_parser_vtable_t cochran_commander_parser_vtable = {
NULL /* destroy */
};
static const cochran_parser_layout_t cochran_cmdr_1_parser_layout = {
SAMPLE_CMDR, // type
256, // headersize
2, // samplesize
DATE_ENCODING_TICKS, // date_encoding
8, // datetime, 4 bytes
0, // pt_profile_begin, 4 bytes
24, // water_conductivity, 1 byte, 0=low(fresh), 2=high(sea)
28, // pt_profile_pre, 4 bytes
43, // start_temp, 1 byte, F
54, // start_depth, 2 bytes, /4=ft
68, // dive_number, 2 bytes
73, // altitude, 1 byte, /4=kilofeet
128, // pt_profile_end, 4 bytes
153, // end_temp, 1 byte F
166, // divetime, 2 bytes, minutes
168, // max_depth, 2 bytes, /4=ft
170, // avg_depth, 2 bytes, /4=ft
210, // oxygen, 4 bytes (2 of) 2 bytes, /256=%
UNSUPPORTED, // helium, 4 bytes (2 of) 2 bytes, /256=%
232, // min_temp, 1 byte, /2+20=F
233, // max_temp, 1 byte, /2+20=F
};
static const cochran_parser_layout_t cochran_cmdr_parser_layout = {
SAMPLE_CMDR, // type
256, // headersize
2, // samplesize
1, 0, 3, 2, 5, 4, // second, minute, hour, day, month, year, 1 byte each
DATE_ENCODING_MSDHYM, // date_encoding
0, // datetime, 6 bytes
6, // pt_profile_begin, 4 bytes
24, // water_conductivity, 1 byte, 0=low(fresh), 2=high(sea)
30, // pt_profile_pre, 4 bytes
@ -128,7 +165,8 @@ static const cochran_parser_layout_t cochran_emc_parser_layout = {
SAMPLE_EMC, // type
512, // headersize
3, // samplesize
0, 1, 2, 3, 4, 5, // second, minute, hour, day, month, year, 1 byte each
DATE_ENCODING_SMHDMY, // date_encoding
0, // datetime, 6 bytes
6, // pt_profile_begin, 4 bytes
24, // water_conductivity, 1 byte 0=low(fresh), 2=high(sea)
30, // pt_profile_pre, 4 bytes
@ -296,6 +334,11 @@ cochran_commander_parser_create (dc_parser_t **out, dc_context_t *context, unsig
parser->model = model;
switch (model) {
case COCHRAN_MODEL_COMMANDER_PRE21000:
parser->layout = &cochran_cmdr_1_parser_layout;
parser->events = cochran_cmdr_event_bytes;
parser->nevents = C_ARRAY_SIZE(cochran_cmdr_event_bytes);
break;
case COCHRAN_MODEL_COMMANDER_AIR_NITROX:
parser->layout = &cochran_cmdr_parser_layout;
parser->events = cochran_cmdr_event_bytes;
@ -340,13 +383,32 @@ cochran_commander_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dat
if (abstract->size < layout->headersize)
return DC_STATUS_DATAFORMAT;
dc_ticks_t ts = 0;
if (datetime) {
datetime->second = data[layout->second];
datetime->minute = data[layout->minute];
datetime->hour = data[layout->hour];
datetime->day = data[layout->day];
datetime->month = data[layout->month];
datetime->year = data[layout->year] + (data[layout->year] > 91 ? 1900 : 2000);
switch (layout->date_encoding)
{
case DATE_ENCODING_MSDHYM:
datetime->second = data[layout->datetime + 1];
datetime->minute = data[layout->datetime + 0];
datetime->hour = data[layout->datetime + 3];
datetime->day = data[layout->datetime + 2];
datetime->month = data[layout->datetime + 5];
datetime->year = data[layout->datetime + 4] + (data[layout->datetime + 4] > 91 ? 1900 : 2000);
break;
case DATE_ENCODING_SMHDMY:
datetime->second = data[layout->datetime + 0];
datetime->minute = data[layout->datetime + 1];
datetime->hour = data[layout->datetime + 2];
datetime->day = data[layout->datetime + 3];
datetime->month = data[layout->datetime + 4];
datetime->year = data[layout->datetime + 5] + (data[layout->datetime + 5] > 91 ? 1900 : 2000);
break;
case DATE_ENCODING_TICKS:
ts = array_uint32_le(data + layout->datetime) + COCHRAN_EPOCH;
dc_datetime_localtime(datetime, ts);
break;
}
}
return DC_STATUS_SUCCESS;
@ -471,10 +533,11 @@ cochran_commander_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb
// know what the dive summary values are (i.e. max depth, min temp)
if (array_uint32_le(data + layout->pt_profile_end) == 0xFFFFFFFF) {
corrupt_dive = 1;
dc_datetime_t d;
cochran_commander_parser_get_datetime(abstract, &d);
WARNING(abstract->context, "Incomplete dive on %02d/%02d/%02d at %02d:%02d:%02d, trying to parse samples",
data[layout->year], data[layout->month], data[layout->day],
data[layout->hour], data[layout->minute], data[layout->second]);
d.year, d.month, d.day, d.hour, d.minute, d.second);
// Eliminate inter-dive events
size = cochran_commander_backparse(parser, samples, size);
@ -484,7 +547,8 @@ cochran_commander_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb
// and temp every other second.
// Prime values from the dive log section
if (parser->model == COCHRAN_MODEL_COMMANDER_AIR_NITROX) {
if (parser->model == COCHRAN_MODEL_COMMANDER_AIR_NITROX ||
parser->model == COCHRAN_MODEL_COMMANDER_PRE21000) {
// Commander stores start depth in quarter-feet
start_depth = array_uint16_le (data + layout->start_depth) / 4.0;
} else {

View File

@ -313,10 +313,12 @@ static const dc_descriptor_t g_descriptors[] = {
{"DiveSystem", "iDive2 Easy", DC_FAMILY_DIVESYSTEM_IDIVE, 0x42},
{"DiveSystem", "iDive2 Deep", DC_FAMILY_DIVESYSTEM_IDIVE, 0x44},
{"DiveSystem", "iDive2 Tech+", DC_FAMILY_DIVESYSTEM_IDIVE, 0x45},
{"Cochran", "Commander", DC_FAMILY_COCHRAN_COMMANDER, 0},
{"Cochran", "EMC-14", DC_FAMILY_COCHRAN_COMMANDER, 1},
{"Cochran", "EMC-16", DC_FAMILY_COCHRAN_COMMANDER, 2},
{"Cochran", "EMC-20H", DC_FAMILY_COCHRAN_COMMANDER, 3},
/* Cochran Commander */
{"Cochran", "Commander I", DC_FAMILY_COCHRAN_COMMANDER, 0},
{"Cochran", "Commander II", DC_FAMILY_COCHRAN_COMMANDER, 1},
{"Cochran", "EMC-14", DC_FAMILY_COCHRAN_COMMANDER, 2},
{"Cochran", "EMC-16", DC_FAMILY_COCHRAN_COMMANDER, 3},
{"Cochran", "EMC-20H", DC_FAMILY_COCHRAN_COMMANDER, 4},
};
typedef struct dc_descriptor_iterator_t {