Mostly style, best practice issues and a few bugs. --- src/cochran_cmdr_parser.c | 135 +++++++++++++++---------- src/cochran_commander.c | 18 ++-- src/cochran_commander_parser.c | 79 +++++++++------ src/cochran_commander_parser.h | 104 ++++++++----------- src/cochran_emc_parser.c | 222 +++++++++++++++-------------------------- 5 files changed, 259 insertions(+), 299 deletions(-)
diff --git a/src/cochran_cmdr_parser.c b/src/cochran_cmdr_parser.c index 4e2915b..20d4fd2 100644 --- a/src/cochran_cmdr_parser.c +++ b/src/cochran_cmdr_parser.c @@ -40,7 +40,8 @@ cochran_cmdr_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) { const unsigned char *data = abstract->data; - const unsigned char *log = data + COCHRAN_MODEL_SIZE; + const unsigned char *log_entry = data + COCHRAN_MODEL_SIZE; + unsigned int minutes = 0, qfeet = 0;
dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_salinity_t *water = (dc_salinity_t *) value; @@ -48,60 +49,72 @@ cochran_cmdr_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, if (value) { switch (type) { case DC_FIELD_TEMPERATURE_SURFACE: - *((unsigned int*) value) = ((float) log[CMD_START_TEMP] - 32) / 1.8; + *((unsigned int*) value) = (log_entry[CMD_START_TEMP] - 32.0) / 1.8; break; case DC_FIELD_TEMPERATURE_MINIMUM: - if (array_uint16_le(log + CMD_MIN_TEMP) == 0xFFFF) - *((double *) value) = 0; + if (log_entry[CMD_MIN_TEMP] == 0xFF) + return DC_STATUS_UNSUPPORTED; else - *((unsigned int*) value) = ((float) log[CMD_MIN_TEMP] / 2 + *((unsigned int*) value) = (log_entry[CMD_MIN_TEMP] / 2.0 + 20 - 32) / 1.8; break; case DC_FIELD_TEMPERATURE_MAXIMUM: - if (array_uint16_le(log + CMD_MAX_TEMP) == 0xFFFF) - *((double *) value) = 0; + if (log_entry[CMD_MAX_TEMP] == 0xFF) + return DC_STATUS_UNSUPPORTED; else - *((unsigned int*) value) = ((float) log[CMD_MAX_TEMP] / 2 + *((unsigned int*) value) = (log_entry[CMD_MAX_TEMP] / 2.0 + 20 - 32) / 1.8; break; case DC_FIELD_DIVETIME: - if (array_uint16_le(log + CMD_BT) == 0xFFFF) - *((double *) value) = 0; + minutes = array_uint16_le(log_entry + CMD_BT); + + if (minutes == 0xFFFF) + return DC_STATUS_UNSUPPORTED; else - *((unsigned int *) value) = array_uint16_le (log + CMD_BT) * 60; + *((unsigned int *) value) = minutes * 60; break; case DC_FIELD_MAXDEPTH: - if (array_uint16_le(log + CMD_MAX_DEPTH) == 0xFFFF) - *((double *) value) = 0; + qfeet = array_uint16_le(log_entry + CMD_MAX_DEPTH); + if (qfeet == 0xFFFF) + return DC_STATUS_UNSUPPORTED; else - *((double *) value) = (float) array_uint16_le (log - + CMD_MAX_DEPTH) / 4 * FEET; + *((double *) value) = qfeet / 4.0 * FEET; break; case DC_FIELD_AVGDEPTH: - if (array_uint16_le(log + CMD_AVG_DEPTH) == 0xFFFF) - *((double *) value) = 0; + qfeet = array_uint16_le(log_entry + CMD_AVG_DEPTH); + if (qfeet == 0xFFFF) + return DC_STATUS_UNSUPPORTED; else - *((double *) value) = (float) array_uint16_le (log - + CMD_AVG_DEPTH) / 4 * FEET; + *((double *) value) = qfeet / 4.0 * FEET; break; case DC_FIELD_GASMIX_COUNT: *((unsigned int *) value) = 2; break; case DC_FIELD_GASMIX: - gasmix->oxygen = (double) array_uint16_le (log - + CMD_O2_PERCENT + 2 * flags) / 256 / 100; + // Gas percentages are decimal and encoded as + // highbyte = integer portion + // lowbyte = decimal portion, divide by 256 to get decimal value + gasmix->oxygen = array_uint16_le (log_entry + + CMD_O2_PERCENT + 2 * flags) / 256.0 / 100; gasmix->helium = 0; gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; break; case DC_FIELD_SALINITY: - // 0 = low conductivity, 1 = high, maybe there's a 2? - water->type = ( (log[CMD_WATER_CONDUCTIVITY] & 0x3) == 0 + // 0x00 = low conductivity, 0x10 = high, maybe there's a 0x01 and 0x11? + // Assume Cochran's conductivity ranges from 0 to 3 + // 0 is fresh water, anything else is sea water + // for density assume + // 0 = 1000kg/m³, 2 = 1025kg/m³ + // and other values are linear + water->type = ( (log_entry[CMD_WATER_CONDUCTIVITY] & 0x3) == 0 ? DC_WATER_FRESH : DC_WATER_SALT ); - water->density = 1000 + 12.5 * (log[CMD_WATER_CONDUCTIVITY] & 0x3); + water->density = 1000 + 12.5 * (log_entry[CMD_WATER_CONDUCTIVITY] & 0x3); break; case DC_FIELD_ATMOSPHERIC: + // Cochran measures air pressure and stores it as altitude. + // Convert altitude (measured in 1/4 kilofeet) back to pressure. *(double *) value = ATM / BAR * pow(1 - 0.0000225577 - * (double) log[CMD_ALTITUDE] * 250 * FEET, 5.25588); + * log_entry[CMD_ALTITUDE] * 250.0 * FEET, 5.25588); break; default: return DC_STATUS_UNSUPPORTED; @@ -116,31 +129,34 @@ dc_status_t cochran_cmdr_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) { - const unsigned char *log = abstract->data + COCHRAN_MODEL_SIZE; - const unsigned char *samples = log + COCHRAN_CMDR_LOG_SIZE; + const unsigned char *log_entry = abstract->data + COCHRAN_MODEL_SIZE; + const unsigned char *samples = log_entry + COCHRAN_CMDR_LOG_SIZE;
unsigned int size = abstract->size - COCHRAN_MODEL_SIZE - COCHRAN_CMDR_LOG_SIZE; - const unsigned char *s;
- dc_sample_value_t sample = {0}, empty_sample = {0}; - unsigned int time = 0, last_sample_time; + dc_sample_value_t sample = {0}; + unsigned int time = 0, last_sample_time = 0; unsigned int offset = 0; double temperature; - double depth; + int depth_qfeet; double ascent_rate; unsigned char corrupt_dive = 0; - int cmdr_event_bytes[15][2] = { {0x00, 17}, {0x01, 21}, {0x02, 18}, - {0x03, 17}, {0x06, 19}, {0x07, 19}, - {0x08, 19}, {0x09, 19}, {0x0a, 19}, - {0x0b, 21}, {0x0c, 19}, {0x0d, 19}, - {0x0e, 19}, {0x10, 21}, - { -1, 1} }; - - if (array_uint32_le(log + COCHRAN_CMDR_LOG_SIZE / 2) == 0xFFFFFFFF) { + struct event_size cmdr_event_bytes[15] = { {0x00, 17}, {0x01, 21}, {0x02, 18}, + {0x03, 17}, {0x06, 19}, {0x07, 19}, + {0x08, 19}, {0x09, 19}, {0x0a, 19}, + {0x0b, 21}, {0x0c, 19}, {0x0d, 19}, + {0x0e, 19}, {0x10, 21}, + { -1, 1} }; + + // In rare circumstances Cochran computers won't record the end-of-dive + // log entry block. When the end-sample pointer is 0xFFFFFFFF it's corrupt. + // That means we don't really know where the dive samples end and we don't + // know what the dive summary values are (i.e. max depth, min temp) + if (array_uint32_le(log_entry + COCHRAN_CMDR_LOG_SIZE / 2) == 0xFFFFFFFF) { corrupt_dive = 1;
- WARNING(abstract->context, "Incomplete dive on %02d/%02d/%02d at %02d:%02d:%02d, trying to parse samples", log[CMD_YEAR], log[CMD_MON], log[CMD_DAY], log[CMD_HOUR], log[CMD_MIN], log[CMD_SEC]); + WARNING(abstract->context, "Incomplete dive on %02d/%02d/%02d at %02d:%02d:%02d, trying to parse samples", log_entry[CMD_YEAR], log_entry[CMD_MON], log_entry[CMD_DAY], log_entry[CMD_HOUR], log_entry[CMD_MIN], log_entry[CMD_SEC]);
// Eliminate inter-dive events size = cochran_backparse(abstract, samples, size, &cmdr_event_bytes); @@ -150,19 +166,19 @@ cochran_cmdr_parser_samples_foreach (dc_parser_t *abstract, // and temp ever other second.
// Prime values from the dive log section - depth = array_uint16_le (log + CMD_START_DEPTH) / 4; + depth_qfeet = array_uint16_le (log_entry + CMD_START_DEPTH);
last_sample_time = sample.time = time; if (callback) callback (DC_SAMPLE_TIME, sample, userdata);
- sample.depth = depth * FEET; + sample.depth = (double) depth_qfeet * FEET; if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
- sample.temperature = (float) *(log + CMD_START_TEMP) - 32 / 1.8; + sample.temperature = (log_entry[CMD_START_TEMP] - 32.0) / 1.8; if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
while (offset < size) { - sample = empty_sample; + const unsigned char *s = NULL;
sample.time = time; if (callback && last_sample_time != sample.time) { @@ -175,10 +191,15 @@ cochran_cmdr_parser_samples_foreach (dc_parser_t *abstract,
// If corrupt_dive end before offset if (corrupt_dive) { - if ( s[0] == 0x10 - || s[0] == 0xFF - || s[0] == 0xA8) { - break; + // When we aren't sure where the sample data ends we can + // look for events that shouldn't be in the sample data. + // 0xFF is unwritten memory + // 0xA8 indicates start of post-dive interval + // 0xE3 (switch to FO2 mode) and 0xF3 (switch to blend 1) occur + // at dive start so when we see them after the first second we + // found the beginning of the next dive. + if (s[0] == 0xFF || s[0] == 0xA8) { + break; } if (time > 1 && (s[0] == 0xE3 || s[0] == 0xF3)) break; @@ -187,25 +208,33 @@ cochran_cmdr_parser_samples_foreach (dc_parser_t *abstract, // Check for event if (s[0] & 0x80) { offset += cochran_commander_handle_event(abstract, callback, - userdata, s[0], offset, time); + userdata, s[0], time); continue; }
// Depth is logged as change in feet, bit 0x40 means negative depth - depth += (float) (s[0] & 0x3F) / 4 * (s[0] & 0x40 ? -1 : 1); - sample.depth = depth * FEET; + if (s[0] & 0x40) + depth_qfeet -= (s[0] & 0x3f); + else + depth_qfeet += (s[0] & 0x3f); + + sample.depth = depth_qfeet / 4.0 * FEET; if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
// Ascent rate is logged in the 0th sample, temp in the 1st, repeat. if (time % 2 == 0) { // Ascent rate - ascent_rate = (float) (s[1] & 0x7f) / 4 * (s[1] & 0x80 ? 1 : -1); + if (s[1] & 0x80) + ascent_rate = (s[1] & 0x7f) / 4.0; + else + ascent_rate = - (s[1] & 0x7f) / 4.0; + sample.ascent_rate = ascent_rate * FEET; if (callback) callback (DC_SAMPLE_ASCENT_RATE, sample, userdata); } else { // Temperature logged in half degrees F above 20 - temperature = (float) s[1] / 2 + 20; + temperature = s[1] / 2.0 + 20; sample.temperature = (temperature - 32.0) / 1.8;
if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); diff --git a/src/cochran_commander.c b/src/cochran_commander.c index ae2dc45..d11df92 100644 --- a/src/cochran_commander.c +++ b/src/cochran_commander.c @@ -855,7 +855,7 @@ cochran_commander_device_dump (dc_device_t *abstract, dc_buffer_t *data) static unsigned int cochran_guess_sample_end_address(cochran_layout_t *layout, cochran_data_t *data, unsigned int log_num) { - const unsigned char *log = data->logbook + layout->rb_log_size * log_num; + const unsigned char *log_entry = data->logbook + layout->rb_log_size * log_num;
if (log_num == data->dive_count) // Return next usable address from config0 page @@ -863,7 +863,7 @@ cochran_guess_sample_end_address(cochran_layout_t *layout, cochran_data_t *data, + layout->rb_profile_end);
// Next log's start address - return array_uint32_le(log + layout->rb_log_size + 6); + return array_uint32_le(log_entry + layout->rb_log_size + 6); }
@@ -878,7 +878,7 @@ cochran_commander_device_foreach (dc_device_t *abstract, unsigned int sample_start_address, sample_end_address; dc_status_t rc;
- unsigned char *log, *fingerprint, *sample, *dive; + unsigned char *log_entry, *fingerprint, *sample, *dive; int sample_size, dive_size;
rc = cochran_commander_device_read_all (abstract); @@ -889,10 +889,10 @@ cochran_commander_device_foreach (dc_device_t *abstract, // Loop through each dive int i; for (i = data->dive_count - 1; i > data->fp_dive_num; i--) { - log = data->logbook + i * layout->rb_log_size; + log_entry = data->logbook + i * layout->rb_log_size;
- sample_start_address = array_uint32_le (log + 6); - sample_end_address = array_uint32_le (log + layout->rb_log_size / 2); + sample_start_address = array_uint32_le (log_entry + 6); + sample_end_address = array_uint32_le (log_entry + layout->rb_log_size / 2);
if (sample_end_address == 0xFFFFFFFF) // Corrupt dive, guess the end address @@ -901,7 +901,7 @@ cochran_commander_device_foreach (dc_device_t *abstract, sample = data->sample + sample_start_address - data->sample_data_offset;
// Determine size of sample - unsigned int dive_num =array_uint16_le(log + layout->pt_log_dive_number); + unsigned int dive_num =array_uint16_le(log_entry + layout->pt_log_dive_number); if (dive_num >= data->profile_tail) sample_size = sample_end_address - sample_start_address; else @@ -912,7 +912,7 @@ cochran_commander_device_foreach (dc_device_t *abstract, sample_size += layout->rb_profile_end - layout->rb_profile_begin;
- fingerprint = log + layout->pt_log_dive_number; + fingerprint = log_entry + layout->pt_log_dive_number;
// Build dive blob dive_size = COCHRAN_MODEL_SIZE + layout->rb_log_size + sample_size; @@ -921,7 +921,7 @@ cochran_commander_device_foreach (dc_device_t *abstract, return DC_STATUS_NOMEMORY;
memcpy(dive, data->id + 0x3B, 8); // model string - memcpy(dive + COCHRAN_MODEL_SIZE, log, layout->rb_log_size); // log + memcpy(dive + COCHRAN_MODEL_SIZE, log_entry, layout->rb_log_size); // log
// Copy profile data if (sample_size) { diff --git a/src/cochran_commander_parser.c b/src/cochran_commander_parser.c index e2b854c..a6380e4 100644 --- a/src/cochran_commander_parser.c +++ b/src/cochran_commander_parser.c @@ -43,9 +43,6 @@ static dc_status_t cochran_commander_parser_get_field (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);
typedef struct cochran_commander_parser_t cochran_commander_parser_t;
@@ -81,6 +78,8 @@ cochran_commander_parser_create (dc_parser_t **out, dc_context_t *context) return DC_STATUS_NOMEMORY; }
+ parser->layout = NULL; + *out = (dc_parser_t *) parser;
return DC_STATUS_SUCCESS; @@ -92,12 +91,14 @@ cochran_commander_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) { cochran_commander_parser_t *parser = (cochran_commander_parser_t *) abstract; - abstract->data = data; - abstract->size = size;
// Determine cochran data format // abstract->data is prefixed by the model string - parser->layout = cochran_commander_get_layout(data); + // FIXME: Instead of prefixing the model string, pass the model + // number to the cochran_commander_parser_create() function. That's + // how other backends do this. + if (size >= COCHRAN_MODEL_SIZE) + parser->layout = cochran_commander_get_layout(data);
return DC_STATUS_SUCCESS; } @@ -110,22 +111,24 @@ cochran_commander_parser_get_datetime (dc_parser_t *abstract, { cochran_commander_parser_t *parser = (cochran_commander_parser_t *) abstract; const unsigned char *data = abstract->data; - const unsigned char *log = data + COCHRAN_MODEL_SIZE; + const unsigned char *log_entry = data + COCHRAN_MODEL_SIZE; + + // FIXME: See my comment regarding a layout structure below.
if (parser->layout->date_format == DATE_SMHDMY) { - datetime->second = log[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); + datetime->second = log_entry[0]; + datetime->minute = log_entry[1]; + datetime->hour = log_entry[2]; + datetime->day = log_entry[3]; + datetime->month = log_entry[4]; + datetime->year = log_entry[5] + (log_entry[5] > 91 ? 1900 : 2000); } else { - datetime->second = log[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); + datetime->second = log_entry[1]; + datetime->minute = log_entry[0]; + datetime->hour = log_entry[3]; + datetime->day = log_entry[2]; + datetime->month = log_entry[5]; + datetime->year = log_entry[4] + (log_entry[4] > 91 ? 1900 : 2000); }
return DC_STATUS_SUCCESS; @@ -137,14 +140,20 @@ cochran_commander_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, { cochran_commander_parser_t *parser = (cochran_commander_parser_t *) abstract;
+ // FIXME: These two get_field functions appear very similar. I think + // you can easily merge them if you introduce a layout structure + // where you have the offset of each field. Check the uwatec parser + // with the uwatec_smart_header_info_t struct for an example. You + // already have the EMC_xxx and CMD_xxx macros. + + // FIXME: I suggest using the same model numbers as in descriptor.c + // and just list all models explictly with extra case statements. switch (parser->layout->model & 0xFF0000) { case COCHRAN_MODEL_COMMANDER_FAMILY: return cochran_cmdr_parser_get_field(abstract, type, flags, value); - break; case COCHRAN_MODEL_EMC_FAMILY: return cochran_emc_parser_get_field(abstract, type, flags, value); - break; }
return DC_STATUS_UNSUPPORTED; @@ -156,6 +165,10 @@ cochran_commander_parser_samples_foreach (dc_parser_t *abstract, { cochran_commander_parser_t *parser = (cochran_commander_parser_t *) abstract;
+ // FIXME: Same remarks here. At first sight these two functions look + // again very similar. I wonder if we can merge them and deal with + // the difference by means of the info in a layout structure? + switch (parser->layout->model & 0xFF0000) { case COCHRAN_MODEL_COMMANDER_FAMILY: @@ -187,7 +200,7 @@ cochran_commander_get_event_info(const unsigned char code, 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) + unsigned int time) { dc_sample_value_t sample = {0}; cochran_events_t event; @@ -220,18 +233,15 @@ cochran_commander_handle_event (dc_parser_t *abstract, break; default: // Don't send known events of type NONE - if (! event.type == SAMPLE_EVENT_NONE) { + if (event.type != SAMPLE_EVENT_NONE) { sample.event.type = event.type; sample.event.flags = event.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); + // Unknown event, send warning so we know we missed something + WARNING(abstract->context, "Unkown event 0x%02x", code); }
return event.data_bytes; @@ -243,13 +253,16 @@ cochran_commander_handle_event (dc_parser_t *abstract, * Used to find the end of a dive that has an incomplete dive-end * block. It parses backwards past inter-dive events. */ - -int cochran_backparse(dc_parser_t *abstract, const char *samples, int size, int (*event_bytes)[15][2]) { +int cochran_backparse(dc_parser_t *abstract, unsigned const char *samples, int size, struct event_size (*event_bytes)[15]) { int result = size, best_result = size;
- for (int x = 0; (*event_bytes)[x][0] != -1; x++) { - int ptr = size - (*event_bytes)[x][1]; - if (ptr > 0 && samples[ptr] == (*event_bytes)[x][0]) { + for (int x = 0; (*event_bytes)[x].code != -1; x++) { + int ptr = size - (*event_bytes)[x].size; + if (ptr > 0 && samples[ptr] == (*event_bytes)[x].code) { + // Recurse to find the largest match. Because we are parsing backwards + // and the events vary in size we can't be sure the byte that matches + // the event code is an event code or data from inside a longer or shorter + // event. result = cochran_backparse(abstract, samples, ptr, event_bytes); }
diff --git a/src/cochran_commander_parser.h b/src/cochran_commander_parser.h index 730edf7..0a125e5 100644 --- a/src/cochran_commander_parser.h +++ b/src/cochran_commander_parser.h @@ -22,70 +22,52 @@ typedef struct cochran_events_t { unsigned char code; unsigned char data_bytes; - const char *name; parser_sample_event_t type; parser_sample_flags_t flag; } cochran_events_t;
+struct event_size { + int code; + int size; +}; + +// FIXME: Mark this variable as const! +// FIXME: This does not belong in a header file! Now you end up with +// multiple instances, one in each source file. +// FIXME: I actually prefer to see the emc and cmdr files merged into +// the commander file anyway. If you read my comments there, I think we +// can share most of the code there, so using separate files becomes +// pointless. And then the previous remark is no longer an issue. static cochran_events_t cochran_events[] = { - { 0xA8, 1, "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_NONE, 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_NONE }, - { 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_NONE, SAMPLE_FLAGS_NONE }, - { 0xF0, 1, "Breathing rate alarm", - SAMPLE_EVENT_NONE, SAMPLE_FLAGS_END }, - { 0xF3, 1, "Switch to blend 1", - SAMPLE_EVENT_NONE, 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 } + { 0xA8, 1, SAMPLE_EVENT_SURFACE, SAMPLE_FLAGS_BEGIN }, // Entered PDI mode + { 0xA9, 1, SAMPLE_EVENT_SURFACE, SAMPLE_FLAGS_END }, // Exited PDI mode + { 0xAB, 5, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE }, // Ceiling decrease + { 0xAD, 5, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE }, // Ceiling increase + { 0xBD, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE }, // Switched to nomal PO2 setting + { 0xC0, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE }, // Switched to FO2 21% mode + { 0xC1, 1, SAMPLE_EVENT_ASCENT, SAMPLE_FLAGS_BEGIN }, // Ascent rate greater than limit + { 0xC2, 1, SAMPLE_EVENT_BATTERY, SAMPLE_FLAGS_NONE }, // Low battery warning + { 0xC3, 1, SAMPLE_EVENT_OLF, SAMPLE_FLAGS_NONE }, // CNS Oxygen toxicity warning + { 0xC4, 1, SAMPLE_EVENT_MAXDEPTH, SAMPLE_FLAGS_NONE }, // Depth exceeds user set point + { 0xC5, 1, SAMPLE_EVENT_DEEPSTOP, SAMPLE_FLAGS_BEGIN }, // Entered decompression mode + { 0xC8, 1, SAMPLE_EVENT_FLOOR, SAMPLE_FLAGS_BEGIN }, // PO2 too high + { 0xCC, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_BEGIN }, // Low Cylinder 1 pressure + { 0xCE, 1, SAMPLE_EVENT_RBT, SAMPLE_FLAGS_BEGIN }, // Non-decompression warning + { 0xCD, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE }, // Switched to deco blend + { 0xD0, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_BEGIN }, // Breathing rate alarm + { 0xD3, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE }, // Low gas 1 flow rate + { 0xD6, 1, SAMPLE_EVENT_CEILING, SAMPLE_FLAGS_BEGIN }, // Depth is less than ceiling + { 0xD8, 1, SAMPLE_EVENT_DEEPSTOP, SAMPLE_FLAGS_END }, // End decompression mode + { 0xE1, 1, SAMPLE_EVENT_ASCENT, SAMPLE_FLAGS_END }, // End ascent rate warning + { 0xE2, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE }, // Low SBAT battery warning + { 0xE3, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE }, // Switched to FO2 mode + { 0xE5, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE }, // Switched to PO2 mode + { 0xEE, 1, SAMPLE_EVENT_RBT, SAMPLE_FLAGS_END }, // End non-decompresison warning + { 0xEF, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE }, // Switch to blend 2 + { 0xF0, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_END }, // Breathing rate alarm + { 0xF3, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE }, // Switch to blend 1 + { 0xF6, 1, SAMPLE_EVENT_CEILING, SAMPLE_FLAGS_END }, // End Depth is less than ceiling + { 0x00, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE } };
@@ -101,8 +83,8 @@ void cochran_commander_get_event_info(const unsigned char code, cochran_events_t *event); int cochran_commander_handle_event (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata, unsigned char code, - unsigned int offset, unsigned int time); -int cochran_backparse(dc_parser_t *abstract, const char *samples, int size, int (*event_bytes)[15][2]); + unsigned int time); +int cochran_backparse(dc_parser_t *abstract, unsigned const char *samples, int size, struct event_size (*event_bytes)[15]);
// EMC FAMILY diff --git a/src/cochran_emc_parser.c b/src/cochran_emc_parser.c index c4a4d56..aae484f 100644 --- a/src/cochran_emc_parser.c +++ b/src/cochran_emc_parser.c @@ -35,103 +35,101 @@ #include "cochran_commander_parser.h"
-struct dive_stats { - unsigned int dive_time; - float max_depth; - float avg_depth; - float min_temp; - float max_temp; -}; - // Size of inter-dive events -int emc_event_bytes[15][2] = { {0x00, 19}, {0x01, 23}, {0x02, 20}, +struct event_size emc_event_bytes[15] = { {0x00, 19}, {0x01, 23}, {0x02, 20}, {0x03, 19}, {0x06, 21}, {0x07, 21}, {0x0a, 21}, {0x0b, 21}, {0x0f, 19}, {0x10, 21}, { -1, 1} };
-static void cochran_emc_parse_dive_stats (dc_parser_t *abstract, - struct dive_stats *stats); -
dc_status_t cochran_emc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) { - const unsigned char *log = abstract->data + COCHRAN_MODEL_SIZE; + const unsigned char *log_entry = abstract->data + COCHRAN_MODEL_SIZE; unsigned char corrupt_dive = 0;
- if (array_uint32_le(log + COCHRAN_EMC_LOG_SIZE / 2) == 0xFFFFFFFF) + // FIXME: Is there a reason why you have this global check instead + // of checking the actual value for 0xFFFF, like you do in the + // commander function? + if (array_uint32_le(log_entry + COCHRAN_EMC_LOG_SIZE / 2) == 0xFFFFFFFF) corrupt_dive = 1;
dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_salinity_t *water = (dc_salinity_t *) value;
- struct dive_stats stats; - if (value) { + + // Parse data to recreate dive summary information + sample_statistics_t parsed_stats = {0}; + if (corrupt_dive) + cochran_emc_parser_samples_foreach(abstract, sample_statistics_cb, (void *) &parsed_stats); + switch (type) { case DC_FIELD_TEMPERATURE_SURFACE: - *((unsigned int*) value) = ((float) log[EMC_START_TEMP] - 32) / 1.8; + *((unsigned int*) value) = (log_entry[EMC_START_TEMP] - 32.0) / 1.8; break; case DC_FIELD_TEMPERATURE_MINIMUM: if (corrupt_dive) { - cochran_emc_parse_dive_stats(abstract, &stats); - *((unsigned int*) value) = stats.min_temp; + return DC_STATUS_UNSUPPORTED; } else - *((unsigned int*) value) = ((float) log[EMC_MIN_TEMP] / 2 + *((unsigned int*) value) = (log_entry[EMC_MIN_TEMP] / 2.0 + 20 - 32) / 1.8; break; case DC_FIELD_TEMPERATURE_MAXIMUM: if (corrupt_dive) { - cochran_emc_parse_dive_stats(abstract, &stats); - *((unsigned int*) value) = stats.max_temp; + return DC_STATUS_UNSUPPORTED; } else - *((unsigned int*) value) = ((float) log[EMC_MAX_TEMP] / 2 + *((unsigned int*) value) = (log_entry[EMC_MAX_TEMP] / 2.0 + 20 - 32) / 1.8; break; case DC_FIELD_DIVETIME: if (corrupt_dive) { - cochran_emc_parse_dive_stats(abstract, &stats); - *((unsigned int*) value) = stats.dive_time; + *((unsigned int*) value) = parsed_stats.divetime; } else - *((unsigned int *) value) = array_uint16_le (log + EMC_BT) * 60; + *((unsigned int *) value) = array_uint16_le (log_entry + EMC_BT) * 60; break; case DC_FIELD_MAXDEPTH: if (corrupt_dive) { - cochran_emc_parse_dive_stats(abstract, &stats); - *((unsigned int*) value) = stats.max_depth; + *((unsigned int*) value) = parsed_stats.maxdepth; } else - *((double *) value) = array_uint16_le (log + EMC_MAX_DEPTH) / 4 + *((double *) value) = array_uint16_le (log_entry + EMC_MAX_DEPTH) / 4.0 * FEET; break; case DC_FIELD_AVGDEPTH: if (corrupt_dive) { - cochran_emc_parse_dive_stats(abstract, &stats); - *((unsigned int*) value) = stats.avg_depth; + return DC_STATUS_UNSUPPORTED; } else - *((double *) value) = array_uint16_le (log + EMC_AVG_DEPTH) / 4 + *((double *) value) = array_uint16_le (log_entry + EMC_AVG_DEPTH) / 4.0 * FEET; break; case DC_FIELD_GASMIX_COUNT: *((unsigned int *) value) = 10; break; case DC_FIELD_GASMIX: - gasmix->oxygen = (double) array_uint16_le (log - + EMC_O2_PERCENT + 2 * flags) / 256 / 100; - gasmix->helium = (double) array_uint16_le (log - + EMC_HE_PERCENT + 2 * flags) / 256 / 100; + // Gas percentages are decimal and encoded as + // highbyte = integer value + // lowbyte = decimal portion, divide by 256 to get decimal value + gasmix->oxygen = array_uint16_le (log_entry + + EMC_O2_PERCENT + 2 * flags) / 256.0 / 100; + gasmix->helium = array_uint16_le (log_entry + + EMC_HE_PERCENT + 2 * flags) / 256.0 / 100; gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; break; case DC_FIELD_SALINITY: - // 0 = low conductivity, 2 = high, maybe there's a 1? - water->type = ( (log[EMC_WATER_CONDUCTIVITY] & 0x3) == 0 + // 0x00 = low conductivity, 0x10 = high, maybe there's a 0x01 and 0x11? + water->type = ( (log_entry[EMC_WATER_CONDUCTIVITY] & 0x3) == 0 ? DC_WATER_FRESH : DC_WATER_SALT ); - water->density = 1000 + 12.5 * (log[EMC_WATER_CONDUCTIVITY] & 0x3); + // Assume Cochran's conductivity ranges from 0 to 3 + // so 0 = 1000kg/m³, 2 = 1025kg/m³ + water->density = 1000 + 12.5 * (log_entry[EMC_WATER_CONDUCTIVITY] & 0x3); break; case DC_FIELD_ATMOSPHERIC: + // Cochran measures atm pressure and stores it as altitude. + // Convert altitude (measured in 1/4 kilofeet) back to pressure. *(double *) value = ATM / BAR * pow(1 - 0.0000225577 - * (double) log[EMC_ALTITUDE] * 250 * FEET, 5.25588); + * log_entry[EMC_ALTITUDE] * 250.0 * FEET, 5.25588); break; default: return DC_STATUS_UNSUPPORTED; @@ -141,33 +139,37 @@ cochran_emc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, return DC_STATUS_SUCCESS; }
- +// TODO: Not reviewed in detail yet dc_status_t cochran_emc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) { const unsigned char *data = abstract->data; - const unsigned char *log = data + COCHRAN_MODEL_SIZE; - const unsigned char *samples = log + COCHRAN_EMC_LOG_SIZE; + const unsigned char *log_entry = data + COCHRAN_MODEL_SIZE; + const unsigned char *samples = log_entry + COCHRAN_EMC_LOG_SIZE; unsigned int size = abstract->size - COCHRAN_MODEL_SIZE - COCHRAN_EMC_LOG_SIZE; - const unsigned char *s;
dc_sample_value_t sample = {0}, empty_sample = {0}; - unsigned int time = 0, last_sample_time; + unsigned int time = 0, last_sample_time = 0; unsigned int offset = 0; + int depth_qfeet = 0; double temperature; - double depth; + double start_depth = 0; double ascent_rate; unsigned char deco_obligation = 0; unsigned int deco_ceiling = 0; - unsigned int deco_time; + unsigned int deco_time = 0; unsigned char corrupt_dive = 0;
- if (array_uint32_le(log + COCHRAN_EMC_LOG_SIZE / 2) == 0xFFFFFFFF) { + // In rare circumstances Cochran computers won't record the end-of-dive + // log entry block. When the end-sample pointer is 0xFFFFFFFF it's corrupt. + // That means we don't really know where the dive samples end and we don't + // know what the dive summary values are (i.e. max depth, min temp) + if (array_uint32_le(log_entry + COCHRAN_EMC_LOG_SIZE / 2) == 0xFFFFFFFF) { corrupt_dive = 1;
- WARNING(abstract->context, "Incomplete dive on %02d/%02d/%02d at %02d:%02d:%02d, trying to parse samples.", log[EMC_YEAR], log[EMC_MON], log[EMC_DAY], log[EMC_HOUR], log[EMC_MIN], log[EMC_SEC]); + WARNING(abstract->context, "Incomplete dive on %02d/%02d/%02d at %02d:%02d:%02d, trying to parse samples.", log_entry[EMC_YEAR], log_entry[EMC_MON], log_entry[EMC_DAY], log_entry[EMC_HOUR], log_entry[EMC_MIN], log_entry[EMC_SEC]);
// Eliminate inter-dive events size = cochran_backparse(abstract, samples, size, &emc_event_bytes); @@ -181,18 +183,20 @@ cochran_emc_parser_samples_foreach (dc_parser_t *abstract, */
// Prime with values from the dive log section - depth = array_uint16_le (log + EMC_START_DEPTH) / 256; + start_depth = array_uint16_le (log_entry + EMC_START_DEPTH) / 256;
last_sample_time = sample.time = time; if (callback) callback (DC_SAMPLE_TIME, sample, userdata);
- sample.depth = depth * FEET; + sample.depth = start_depth * FEET; if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
- sample.temperature = (log[EMC_START_TEMP] - 32) / 1.8; + sample.temperature = (log_entry[EMC_START_TEMP] - 32) / 1.8; if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
while (offset < size) { + const unsigned char *s = NULL; + sample = empty_sample;
sample.time = time; @@ -206,9 +210,14 @@ cochran_emc_parser_samples_foreach (dc_parser_t *abstract,
// If corrupt_dive end before offset if (corrupt_dive) { - if ( s[0] == 0x10 - || s[0] == 0xFF - || s[0] == 0xA8) { + // When we aren't sure where the sample data ends we can + // look for events that shouldn't be in the sample data. + // 0xFF is unwritten memory + // 0xA8 indicates start of post-dive interval + // 0xE3 (switch to FO2 mode) and 0xF3 (switch to blend 1) occur + // at dive start so when we see them after the first second we + // found the beginning of the next dive. + if (s[0] == 0xFF || s[0] == 0xA8) { break; } if (time > 1 && (s[0] == 0xE3 || s[0] == 0xF3)) @@ -218,7 +227,7 @@ cochran_emc_parser_samples_foreach (dc_parser_t *abstract, // Check for event if (s[0] & 0x80) { offset += cochran_commander_handle_event(abstract, callback, - userdata, s[0], offset, time); + userdata, s[0], time); switch (s[0]) { case 0xC5: // Deco obligation begins @@ -231,7 +240,7 @@ cochran_emc_parser_samples_foreach (dc_parser_t *abstract, deco_ceiling += 10; // feet
sample.deco.type = DC_DECO_DECOSTOP; - sample.deco.time = (array_uint16_le(s + 3) + 1) * 60; + sample.deco.time = (array_uint16_le(s + 3) + 1) * 60; sample.deco.depth = deco_ceiling * FEET; if (callback) callback(DC_SAMPLE_DECO, sample, userdata); break; @@ -262,25 +271,33 @@ cochran_emc_parser_samples_foreach (dc_parser_t *abstract,
// Depth is logged as change in feet, bit 0x40 means negative depth - depth += (float) (s[0] & 0x3F) / 4 * (s[0] & 0x40 ? -1 : 1); - sample.depth = depth * FEET; + if (s[0] & 0x40) + depth_qfeet -= (s[0] & 0x3f); + else + depth_qfeet += (s[0] & 0x3f); + + sample.depth = (start_depth + depth_qfeet / 4.0) * FEET; if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
// Ascent rate is logged in the 0th sample, temp in the 1st, repeat. if (time % 2 == 0) { // Ascent rate - ascent_rate = (float) (s[1] & 0x7f) / 4 * (s[1] & 0x80 ? 1 : -1); + if (s[1] & 0x80) + ascent_rate = (s[1] & 0x7f) / 4.0; + else + ascent_rate = - (s[1] & 0x7f) / 4.0; + sample.ascent_rate = ascent_rate * FEET; if (callback) callback (DC_SAMPLE_ASCENT_RATE, sample, userdata); } else { // Temperature logged in half degrees F above 20 - temperature = (float) s[1] / 2 + 20; + temperature = s[1] / 2.0 + 20; sample.temperature = (temperature - 32.0) / 1.8;
if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); }
- + // Some samples are split over two seconds // Skip over event bytes to find the next sample const unsigned char *n = s + COCHRAN_EMC_SAMPLE_SIZE; @@ -326,84 +343,3 @@ cochran_emc_parser_samples_foreach (dc_parser_t *abstract,
return DC_STATUS_SUCCESS; } - -void cochran_emc_parse_dive_stats (dc_parser_t *abstract, - struct dive_stats *stats) -{ - const unsigned char *log = abstract->data + COCHRAN_MODEL_SIZE; - const unsigned char *samples = log + COCHRAN_EMC_LOG_SIZE; - unsigned int size = abstract->size - COCHRAN_MODEL_SIZE - - COCHRAN_EMC_LOG_SIZE; - const unsigned char *s; - - unsigned int offset = 0; - float depth_m = 0, depth, temp; - unsigned int time = 0; - unsigned char corrupt_dive = 0; - - if (array_uint32_le(log + COCHRAN_EMC_LOG_SIZE / 2) == 0xFFFFFFFF) { - corrupt_dive = 1; - - // Eliminate inter-dive events - size = cochran_backparse(abstract, samples, size, &emc_event_bytes); - } - - // Prime with values from the dive log section - depth = array_uint16_le (log + EMC_START_DEPTH) / 256; - - stats->max_depth = depth; - stats->min_temp = 999; - stats->avg_depth = 0; - stats->max_temp = 0; - - while (offset < size) { - s = samples + offset; - - if (corrupt_dive) { - if (s[0] == 0x10 - || s[0] == 0xFF - || s[0] == 0xA8) { - // End corrupted dive - break; - } - if (time > 1 && (s[0] == 0xE3 || s[0] == 0xF3)) - break; - } - - // Check for event - if (s[0] & 0x80) { - cochran_events_t event; - cochran_commander_get_event_info(s[0], &event); - - // Advance some bytes - if (event.code) - offset += event.data_bytes; - else - offset ++; - - continue; - } - - // Depth is logged as change in feet, bit 0x40 means negative depth - depth += (float) (s[0] & 0x3F) / 4 * (s[0] & 0x40 ? -1 : 1); - depth_m = depth * FEET; - - if (depth_m > stats->max_depth) stats->max_depth = depth_m; - - stats->avg_depth = (stats->avg_depth * (time - 1) + depth_m) / time; - - if (time % 2 != 0) { - // Temperature logged in half degrees F above 20 - temp = (float) s[1] / 2 + 20; - temp = (temp - 32.0) / 1.8; - - if (temp < stats->min_temp) stats->min_temp = temp; - if (temp > stats->max_temp) stats->max_temp = temp; - } - - time ++; - offset += COCHRAN_EMC_SAMPLE_SIZE; - } - - stats->dive_time = time - 1; -}