diff --git a/src/array.c b/src/array.c index 13a73e5..5574083 100644 --- a/src/array.c +++ b/src/array.c @@ -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) { diff --git a/src/array.h b/src/array.h index cd0a3a1..da70efa 100644 --- a/src/array.h +++ b/src/array.h @@ -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); diff --git a/src/cochran_commander.c b/src/cochran_commander.c index b09e9fd..95c00fe 100644 --- a/src/cochran_commander.c +++ b/src/cochran_commander.c @@ -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; } diff --git a/src/cochran_commander_parser.c b/src/cochran_commander_parser.c index ab854ad..1c14536 100644 --- a/src/cochran_commander_parser.c +++ b/src/cochran_commander_parser.c @@ -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 { diff --git a/src/descriptor.c b/src/descriptor.c index 9b537e1..633debd 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -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 {