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:
parent
45f0605678
commit
216e393f43
@ -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)
|
||||
{
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user