Consolidated model differences in structures to facilitate common code. Merged EMC and Commander parser_foreach and get_field into common functions. Removed model string prefix from dive data in favour of passing model number to the parser_create function. --- include/libdivecomputer/cochran_commander.h | 2 +- src/Makefile.am | 3 +- src/cochran_cmdr_parser.c | 248 -------------- src/cochran_commander.c | 410 +++++++++++++---------- src/cochran_commander.h | 160 ++++----- src/cochran_commander_parser.c | 484 ++++++++++++++++++++++------ src/cochran_commander_parser.h | 69 +--- src/cochran_emc_parser.c | 345 -------------------- src/parser.c | 2 +- 9 files changed, 701 insertions(+), 1022 deletions(-) delete mode 100644 src/cochran_cmdr_parser.c delete mode 100644 src/cochran_emc_parser.c
diff --git a/include/libdivecomputer/cochran_commander.h b/include/libdivecomputer/cochran_commander.h index 143c451..9fff2d1 100644 --- a/include/libdivecomputer/cochran_commander.h +++ b/include/libdivecomputer/cochran_commander.h @@ -35,7 +35,7 @@ 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); +cochran_commander_parser_create (dc_parser_t **parser, dc_context_t *context, int model);
#ifdef __cplusplus } diff --git a/src/Makefile.am b/src/Makefile.am index 91721b9..0c46a32 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -66,8 +66,7 @@ libdivecomputer_la_SOURCES = \ array.h array.c \ buffer.c \ cochran_commander.h cochran_commander.c \ - cochran_commander_parser.h cochran_commander_parser.c \ - cochran_cmdr_parser.c cochran_emc_parser.c + cochran_commander_parser.h cochran_commander_parser.c
if OS_WIN32 libdivecomputer_la_SOURCES += serial.h serial_win32.c diff --git a/src/cochran_cmdr_parser.c b/src/cochran_cmdr_parser.c deleted file mode 100644 index 20d4fd2..0000000 --- a/src/cochran_cmdr_parser.c +++ /dev/null @@ -1,248 +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 <math.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_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_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; - - if (value) { - switch (type) { - case DC_FIELD_TEMPERATURE_SURFACE: - *((unsigned int*) value) = (log_entry[CMD_START_TEMP] - 32.0) / 1.8; - break; - case DC_FIELD_TEMPERATURE_MINIMUM: - if (log_entry[CMD_MIN_TEMP] == 0xFF) - return DC_STATUS_UNSUPPORTED; - else - *((unsigned int*) value) = (log_entry[CMD_MIN_TEMP] / 2.0 - + 20 - 32) / 1.8; - break; - case DC_FIELD_TEMPERATURE_MAXIMUM: - if (log_entry[CMD_MAX_TEMP] == 0xFF) - return DC_STATUS_UNSUPPORTED; - else - *((unsigned int*) value) = (log_entry[CMD_MAX_TEMP] / 2.0 - + 20 - 32) / 1.8; - break; - case DC_FIELD_DIVETIME: - minutes = array_uint16_le(log_entry + CMD_BT); - - if (minutes == 0xFFFF) - return DC_STATUS_UNSUPPORTED; - else - *((unsigned int *) value) = minutes * 60; - break; - case DC_FIELD_MAXDEPTH: - qfeet = array_uint16_le(log_entry + CMD_MAX_DEPTH); - if (qfeet == 0xFFFF) - return DC_STATUS_UNSUPPORTED; - else - *((double *) value) = qfeet / 4.0 * FEET; - break; - case DC_FIELD_AVGDEPTH: - qfeet = array_uint16_le(log_entry + CMD_AVG_DEPTH); - if (qfeet == 0xFFFF) - return DC_STATUS_UNSUPPORTED; - else - *((double *) value) = qfeet / 4.0 * FEET; - break; - case DC_FIELD_GASMIX_COUNT: - *((unsigned int *) value) = 2; - break; - case DC_FIELD_GASMIX: - // 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: - // 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_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 - * log_entry[CMD_ALTITUDE] * 250.0 * FEET, 5.25588); - break; - default: - return DC_STATUS_UNSUPPORTED; - } - } - - return DC_STATUS_SUCCESS; -} - - -dc_status_t -cochran_cmdr_parser_samples_foreach (dc_parser_t *abstract, - dc_sample_callback_t callback, void *userdata) -{ - 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; - - dc_sample_value_t sample = {0}; - unsigned int time = 0, last_sample_time = 0; - unsigned int offset = 0; - double temperature; - int depth_qfeet; - double ascent_rate; - unsigned char corrupt_dive = 0; - 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_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); - } - - // Cochran samples depth every second and varies between ascent rate - // and temp ever other second. - - // Prime values from the dive log section - 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 = (double) depth_qfeet * FEET; - if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); - - sample.temperature = (log_entry[CMD_START_TEMP] - 32.0) / 1.8; - if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); - - while (offset < size) { - const unsigned char *s = NULL; - - sample.time = time; - if (callback && last_sample_time != sample.time) { - // We haven't issued this time yet. - last_sample_time = sample.time; - callback (DC_SAMPLE_TIME, sample, userdata); - } - - s = samples + offset; - - // If corrupt_dive end before offset - if (corrupt_dive) { - // 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; - } - - // Check for event - if (s[0] & 0x80) { - offset += cochran_commander_handle_event(abstract, callback, - userdata, s[0], time); - continue; - } - - - // Depth is logged as change in feet, bit 0x40 means negative depth - 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 - 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 = s[1] / 2.0 + 20; - sample.temperature = (temperature - 32.0) / 1.8; - - if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); - } - - time ++; - offset += COCHRAN_CMDR_SAMPLE_SIZE; - } - - return DC_STATUS_SUCCESS; -} diff --git a/src/cochran_commander.c b/src/cochran_commander.c index d11df92..c457b3a 100644 --- a/src/cochran_commander.c +++ b/src/cochran_commander.c @@ -42,107 +42,147 @@
/* -* Hardware and data layout information for various Cochran models. +* Device, protocol and data layout information for various Cochran models. * * EMC models are identical except for the size of logbook and profile * memory. */ -static cochran_layout_t cochran_layout_cmdr = { - COCHRAN_MODEL_COMMANDER_AIR_NITROX, /* model */ - ENDIAN_BE, /* endian */ - DATE_MSDHYM, /* date_format */ - ADDRESS_24_BIT, /* address_size */ - 115200, /* high_baud_rate */ - - 0x00000000, /* rb_logbook_begin */ - 0x00020000, /* rb_logbook_end */ - 256, /* rb_log_size */ - 512, /* rb_max_log */ - 0x00020000, /* rb_profile_begin */ - 0x00100000, /* rb_profile_end */ - - 0x006, /* pt_log_profile_begin */ - 0x01e, /* pt_log_profile_pre */ - 0x046, /* pt_log_dive_number */ - 0x080, /* pt_log_profile_end */ - - 0x046, /* pt_conf_dive_count */ - 0x06e, /* pt_conf_last_log */ - 0x071, /* pt_conf_last_interdive */ + +// Cochran Commander Nitrox +cochran_device_info_t cochran_cmdr_device_info = { + COCHRAN_MODEL_COMMANDER_AIR_NITROX, // model + ADDRESS_24_BIT, // address_size + ENDIAN_BE, // endian + 115200, // high_baud + 0x00000000, // rb_logbook_begin + 0x00020000, // rb_lobook_end + 0x00020000, // rb_profile_begin + 0x00100000, // rb_profile_end + 256, // log_size + 512, // max_log + 2, // max_gas + SAMPLE_CMDR, // sample_format +}; + +cochran_conf_offsets_t cochran_cmdr_conf_offsets = { + 0x046, // dive_count + 0x06e, // last_log + 0x071, // last_interdive +}; + +cochran_log_offsets_t cochran_cmdr_log_offsets = { + 1, 0, 3, 2, 5, 4, // sec, min, hour, day, mon, year, 1 byte each + 6, // profile_begin_offset, 4 bytes + 24, // water_conductivity, 1 byte, 0=low(fresh), 2=high(sea) + 30, // profile_pre_offset, 4 bytes + 45, // start_temp, 1 byte, F + 56, // start_depth, 2 bytes, /4=ft + 70, // dive_number, 2 bytes + 73, // altitude, 1 byte, /4=kilofeet + 128, // profile_end_offset, 4 bytes + 153, // end_temp, 1 byte F + 166, // bt, 2 bytes, minutes + 168, // max_depth, 2 bytes, /4=ft + 170, // avg_depth, 2 bytes, /4=ft + 210, // o2_percent, 4 bytes (2 of) 2 bytes, /256=% + UNSUPPORTED, // he_percent, 4 bytes (2 of) 2 bytes, /256=% + 232, // min_temp, 1 byte, /2+20=F + 233, // max_temp, 1 byte, /2+20=F +}; + +// inter-dive event lengths +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} }; + +// Cochran EMC-14 +cochran_device_info_t cochran_emc14_device_info = { + COCHRAN_MODEL_EMC_14, // model + ADDRESS_32_BIT, // address_size + ENDIAN_LE, // endian + 806400, // high_baud + 0x00000000, // rb_logbook_begin + 0x00020000, // rb_lobook_end + 0x00022000, // rb_profile_begin + 0x00200000, // rb_profile_end + 512, // log_size + 256, // max_log + 2, // max_gas + SAMPLE_EMC, // sample_format +}; + +// Cochran EMC-16 +cochran_device_info_t cochran_emc16_device_info = { + COCHRAN_MODEL_EMC_16, // model + ADDRESS_32_BIT, // address_size + ENDIAN_LE, // endian + 806400, // high_baud + 0x00000000, // rb_logbook_begin + 0x00080000, // rb_lobook_end + 0x00094000, // rb_profile_begin + 0x00800000, // rb_profile_end + 512, // log_size + 1024, // max_log + 2, // max_gas + SAMPLE_EMC, // sample_format };
-static cochran_layout_t cochran_layout_emc20 = { - COCHRAN_MODEL_EMC_20, /* model */ - ENDIAN_LE, /* endian */ - DATE_SMHDMY, /* date_format */ - ADDRESS_32_BIT, /* address_size */ - 806400, /* high_baud_rate was 825600 */ - - 0x00000000, /* rb_logbook_begin */ - 0x00080000, /* rb_logbook_end */ - 512, /* rb_log_size */ - 1024, /* rb_max_log */ - 0x00094000, /* rb_profile_begin */ - 0x01000000, /* rb_profile_end */ - - 0x006, /* pt_log_profile_begin */ - 0x01e, /* pt_log_profile_pre */ - 0x056, /* pt_log_dive_number */ - 0x100, /* pt_log_profile_end */ - - 0x0d2, /* pt_conf_dive_count */ - 0x13e, /* pt_conf_last_log */ - 0x142, /* pt_conf_last_interdive */ + +// Cochran EMC-20 +cochran_device_info_t cochran_emc20_device_info = { + COCHRAN_MODEL_EMC_20, // model + ADDRESS_32_BIT, // address_size + ENDIAN_LE, // endian + 806400, // high_baud + 0x00000000, // rb_logbook_begin + 0x00080000, // rb_lobook_end + 0x00094000, // rb_profile_begin + 0x01000000, // rb_profile_end + 512, // log_size + 1024, // max_log + 2, // max_gas + SAMPLE_EMC, // sample_format };
-static cochran_layout_t cochran_layout_emc16 = { - COCHRAN_MODEL_EMC_16, /* model */ - ENDIAN_LE, /* endian */ - DATE_SMHDMY, /* date_format */ - ADDRESS_32_BIT, /* address_size */ - 806400, /* high_baud_rate */ - - 0x00000000, /* rb_logbook_begin */ - 0x00080000, /* rb_logbook_end */ - 512, /* rb_log_size */ - 1024, /* rb_max_log */ - 0x00094000, /* rb_profile_begin */ - 0x00800000, /* rb_profile_end */ - - 0x006, /* pt_log_profile_begin */ - 0x01e, /* pt_log_profile_pre */ - 0x056, /* pt_log_dive_number */ - 0x100, /* pt_log_profile_end */ - - 0x0d2, /* pt_conf_dive_count */ - 0x13e, /* pt_conf_last_log */ - 0x142, /* pt_conf_last_interdive */ +// Common EMC offsets +cochran_conf_offsets_t cochran_emc_conf_offsets = { + 0x0d2, // dive_count + 0x13e, // last_log + 0x142, // last_interdive };
-static cochran_layout_t cochran_layout_emc14 = { - COCHRAN_MODEL_EMC_14, /* model */ - ENDIAN_LE, /* endian */ - DATE_SMHDMY, /* date_format */ - ADDRESS_32_BIT, /* address_size */ - 806400, /* high_baud_rate */ - - 0x00000000, /* rb_logbook_begin */ - 0x00020000, /* rb_logbook_end */ - 512, /* rb_log_size */ - 256, /* rb_max_log */ - 0x00022000, /* rb_profile_begin */ - 0x00200000, /* rb_profile_end */ - - 0x006, /* pt_log_profile_begin */ - 0x01e, /* pt_log_profile_pre */ - 0x056, /* pt_log_dive_number */ - 0x100, /* pt_log_profile_end */ - - 0x0d2, /* pt_conf_dive_count */ - 0x13e, /* pt_conf_last_log */ - 0x142, /* pt_conf_last_interdive */ + +cochran_log_offsets_t cochran_emc_log_offsets = { + 0, 1, 2, 3, 4, 5, // sec, min, hour, day, mon, year, 1 byte each + 6, // profile_begin_offset, 4 bytes + 24, // water_conductivity, 1 byte 0=low(fresh), 2=high(sea) + 30, // profile_pre_offset, 4 bytes + 55, // start_temp, 1 byte, F + 42, // start_depth, 2 bytes, /256=ft + 86, // dive_number, 2 bytes, + 89, // altitude, 1 byte /4=kilofeet + 256, // profile_end_offset, 4 bytes + 293, // end_temp, 1 byte, F + 304, // bt, 2 bytes, minutes + 306, // max_depth, 2 bytes, /4=ft + 310, // avg_depth, 2 bytes, /4=ft + 144, // o2_percent, 6 bytes (3 of) 2 bytes, /256=% + 164, // he_percent, 6 bytes (3 of) 2 bytes, /256=% + 403, // min_temp, 1 byte, /2+20=F + 407, // max_temp, 1 byte, /2+20=F };
+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 const dc_device_vtable_t cochran_commander_device_vtable = { sizeof (cochran_device_t), DC_FAMILY_COCHRAN_COMMANDER, @@ -161,7 +201,7 @@ static dc_status_t cochran_commander_read (dc_device_t *abstract, dc_event_progress_t *progress, unsigned int address, unsigned char data[], unsigned int size); static void -cochran_check_profiles(dc_device_t *abstract, cochran_layout_t *layout); +cochran_check_profiles(dc_device_t *abstract);
static dc_status_t @@ -191,8 +231,7 @@ cochran_packet (cochran_device_t *device, dc_event_progress_t *progress, serial_sleep(device->port, 45);
// Rates are odd, like 806400 for the EMC, 115200 for commander - cochran_layout_t *layout = cochran_commander_get_layout(device->model_string); - rc = serial_configure(device->port, layout->high_baud_rate, 8, + rc = serial_configure(device->port, device->info->high_baud, 8, SERIAL_PARITY_NONE, 2, SERIAL_FLOWCONTROL_NONE); if (rc == -1) { // Assume we are talking to a pty, a simulator @@ -295,7 +334,6 @@ cochran_commander_device_open (dc_device_t **out, dc_context_t *context, const char *name) { cochran_device_t *device = NULL; - cochran_layout_t *layout; dc_status_t rc;
if (out == NULL) @@ -334,10 +372,11 @@ cochran_commander_device_open (dc_device_t **out, dc_context_t *context,
memcpy(device->model_string, device->data.id + 0x3b, 8); device->model_string[8] = 0; - layout = cochran_commander_get_layout(device->model_string);
- // Check ID - if (!layout || (layout->model & 0xFF0000) == COCHRAN_MODEL_UNKNOWN) { + device->base.devinfo.model = cochran_commander_model_id(device->model_string); + + if (device->base.devinfo.model == -1) { + // Unknown device ERROR (context, "Unknown Cochran model %02x %02x %02x %02x %02x %02x %02x %02x", device->model_string[0], device->model_string[1], @@ -349,6 +388,10 @@ cochran_commander_device_open (dc_device_t **out, dc_context_t *context, return DC_STATUS_UNSUPPORTED; }
+ // Get model capability, protocol and data format + cochran_commander_info(device->base.devinfo.model, &device->info, + &device->conf_ptr, &device->log_ptr, &device->event_bytes); + *out = (dc_device_t *) device;
return DC_STATUS_SUCCESS; @@ -405,13 +448,12 @@ cochran_commander_read (dc_device_t *abstract, dc_event_progress_t *progress, unsigned int address, unsigned char data[], unsigned int size) { cochran_device_t *device = (cochran_device_t*) abstract; - cochran_layout_t *layout = cochran_commander_get_layout(device->model_string);
// Build the command unsigned char command[10]; unsigned char command_size;
- switch (layout->address_size) + switch (device->info->address_size) { case ADDRESS_32_BIT: // EMC uses 32 bit addressing @@ -438,7 +480,8 @@ cochran_commander_read (dc_device_t *abstract, dc_event_progress_t *progress, command[6] = (size >> 16 ) & 0xff; command[7] = 0x04; command_size = 8; - break; default: + break; + default: return DC_STATUS_UNSUPPORTED; }
@@ -452,31 +495,74 @@ cochran_commander_read (dc_device_t *abstract, dc_event_progress_t *progress, }
-cochran_layout_t * -cochran_commander_get_layout (const unsigned char *model) +// Determine model descriptor number from model string +int +cochran_commander_model_id(const char *model) +{ + struct model_map_t { + char *model; + int id; + }; + + // Model strings are COCHRAN_MODEL_SIZE long + struct model_map_t model_map[5] = { + { "AM\x11""2212\x02", COCHRAN_MODEL_COMMANDER_AIR_NITROX }, + { "AM7303\x8b\x43", COCHRAN_MODEL_EMC_14 }, + { "AMA315\xC3\xC5", COCHRAN_MODEL_EMC_16 }, + { "AM2315\xA3\x71", COCHRAN_MODEL_EMC_20 }, + { "", -1 }, + }; + + int x = 0; + while (model_map[x].id != -1 && strncmp(model, model_map[x].model, COCHRAN_MODEL_SIZE)) + x++; + + return model_map[x].id; +} + + +// Get device capability, protocol info and data format for a device +dc_status_t +cochran_commander_info (const int model, cochran_device_info_t **info, cochran_conf_offsets_t **conf_ptr, cochran_log_offsets_t **log_ptr, struct event_size (**event_bytes)[15]) { // Determine model - if (memcmp(model, "AM2315\xA3\x71", 8) == 0) + if (model == COCHRAN_MODEL_EMC_20) { - return &cochran_layout_emc20; + if (info) *info = &cochran_emc20_device_info; + if (conf_ptr) *conf_ptr = &cochran_emc_conf_offsets; + if (log_ptr) *log_ptr = &cochran_emc_log_offsets; + if (event_bytes) *event_bytes = &emc_event_bytes; } - else if (memcmp(model, "AMA315\xC3\xC5", 8) == 0) + else if (model == COCHRAN_MODEL_EMC_16) { - return &cochran_layout_emc16; + if (info) *info = &cochran_emc16_device_info; + if (conf_ptr) *conf_ptr = &cochran_emc_conf_offsets; + if (log_ptr) *log_ptr = &cochran_emc_log_offsets; + if (event_bytes) *event_bytes = &emc_event_bytes; } - else if (memcmp(model, "AM7303\x8b\x43", 8) == 0) + else if (model == COCHRAN_MODEL_EMC_14) { - return &cochran_layout_emc14; + if (info) *info = &cochran_emc14_device_info; + if (conf_ptr) *conf_ptr = &cochran_emc_conf_offsets; + if (log_ptr) *log_ptr = &cochran_emc_log_offsets; + if (event_bytes) *event_bytes = &emc_event_bytes; } - else if (memcmp(model, "AM\x11""2212\x02", 8) == 0) + else if (model == COCHRAN_MODEL_COMMANDER_AIR_NITROX) { - return &cochran_layout_cmdr; + if (info) *info = &cochran_cmdr_device_info; + if (conf_ptr) *conf_ptr = &cochran_cmdr_conf_offsets; + if (log_ptr) *log_ptr = &cochran_cmdr_log_offsets; + if (event_bytes) *event_bytes = &cmdr_event_bytes; + } + else { + return DC_STATUS_UNSUPPORTED; }
- return NULL; + return DC_STATUS_SUCCESS; }
+ static dc_status_t cochran_read_id (dc_device_t *abstract) { @@ -537,11 +623,10 @@ cochran_read_misc (dc_device_t *abstract, dc_event_progress_t *progress) cochran_device_t *device = (cochran_device_t *) abstract; dc_status_t rc; dc_event_vendor_t vendor; - cochran_layout_t *layout = cochran_commander_get_layout(device->model_string);
unsigned char command[7] = { 0x89, 0x05, 0x00, 0x00, 0x00, 0xDC, 0x05 };
- switch (layout->model & 0xFF0000) + switch (device->info->model & 0xFF0000) { case COCHRAN_MODEL_COMMANDER_FAMILY: command[2] = 0xCA; @@ -613,14 +698,13 @@ cochran_find_fingerprint(dc_device_t *abstract) { cochran_device_t *device = (cochran_device_t *) abstract; cochran_data_t *d = (cochran_data_t *) &device->data; - cochran_layout_t *layout = cochran_commander_get_layout(device->model_string);
// 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 * layout->rb_log_size - + layout->pt_log_dive_number, + d->logbook + d->fp_dive_num * device->info->log_size + + device->log_ptr->dive_number, sizeof(d->fingerprint))) d->fp_dive_num--; } @@ -633,7 +717,6 @@ cochran_get_sample_parms(dc_device_t *abstract) cochran_data_t *d = (cochran_data_t *) &device->data; unsigned int pre_dive_offset = 0, end_dive_offset = 0; unsigned int low_offset, high_offset; - cochran_layout_t *layout = cochran_commander_get_layout(device->model_string);
// Find lowest and highest offsets into sample data low_offset = 0xFFFFFFFF; @@ -641,10 +724,10 @@ cochran_get_sample_parms(dc_device_t *abstract)
int i; for (i = d->fp_dive_num + 1; i < d->dive_count; i++) { - pre_dive_offset = array_uint32_le (&(d->logbook[i * layout->rb_log_size - + layout->pt_log_profile_pre])); - end_dive_offset = array_uint32_le (&(d->logbook[i * layout->rb_log_size - + layout->pt_log_profile_end])); + pre_dive_offset = array_uint32_le (&(d->logbook[i * device->info->log_size + + device->log_ptr->profile_pre_offset])); + end_dive_offset = array_uint32_le (&(d->logbook[i * device->info->log_size + + device->log_ptr->profile_end_offset]));
// Check for ring buffer wrap-around. if (pre_dive_offset > end_dive_offset) @@ -661,8 +744,7 @@ cochran_get_sample_parms(dc_device_t *abstract) // 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; - layout->rb_profile_end = high_offset; - low_offset = layout->rb_profile_begin; + low_offset = device->info->rb_profile_begin; d->sample_data_offset = low_offset; d->sample_size = high_offset - low_offset; } else if (low_offset < 0xFFFFFFFF && high_offset > 0) { @@ -719,15 +801,14 @@ 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_event_progress_t progress = {0}; - cochran_layout_t *layout = cochran_commander_get_layout(device->model_string);
dc_status_t rc; int max_config, max_logbook, max_sample;
// Calculate max data sizes max_config = 512 * 2; - max_logbook =layout->rb_logbook_end - layout->rb_logbook_begin; - max_sample = layout->rb_profile_end - layout->rb_profile_begin; + max_logbook =device->info->rb_logbook_end - device->info->rb_logbook_begin; + max_sample = device->info->rb_profile_end - device->info->rb_profile_begin;
progress.current = 0; progress.maximum = max_config + max_logbook + max_sample; @@ -739,12 +820,12 @@ cochran_commander_device_read_all (dc_device_t *abstract) return rc;
// Determine size of dive list to read. Round up to nearest 16K - if (layout->endian == ENDIAN_LE) - d->dive_count = array_uint16_le (d->config[0] + layout->pt_conf_dive_count); + if (device->info->endian == ENDIAN_LE) + d->dive_count = array_uint16_le (d->config[0] + device->conf_ptr->dive_count); else - d->dive_count = array_uint16_be (d->config[0] + layout->pt_conf_dive_count); + d->dive_count = array_uint16_be (d->config[0] + device->conf_ptr->dive_count);
- d->logbook_size = ((d->dive_count * layout->rb_log_size) & 0xFFFFC000) + d->logbook_size = ((d->dive_count * device->info->log_size) & 0xFFFFC000) + 0x4000;
progress.maximum -= max_logbook - d->logbook_size; @@ -766,7 +847,7 @@ cochran_commander_device_read_all (dc_device_t *abstract) return rc;
// Determine logs that haven't had profile data overwritten - cochran_check_profiles(abstract, layout); + cochran_check_profiles(abstract);
return DC_STATUS_SUCCESS; } @@ -784,7 +865,6 @@ cochran_commander_device_dump (dc_device_t *abstract, dc_buffer_t *data) cochran_data_t *d = (cochran_data_t *) &device->data; dc_event_progress_t progress = {0}; dc_event_vendor_t vendor; - cochran_layout_t *layout = cochran_commander_get_layout(device->model_string);
dc_status_t rc; int size; @@ -796,7 +876,7 @@ cochran_commander_device_dump (dc_device_t *abstract, dc_buffer_t *data) }
// Determine size for progress - size = 512 + 512 + 1500 + layout->rb_profile_end; + size = 512 + 512 + 1500 + device->info->rb_profile_end;
progress.current = 0; progress.maximum = size; @@ -824,7 +904,7 @@ cochran_commander_device_dump (dc_device_t *abstract, dc_buffer_t *data) // Read logbook and sample memory
// Reserve space - if (!dc_buffer_resize(data, layout->rb_profile_end)) { + if (!dc_buffer_resize(data, device->info->rb_profile_end)) { ERROR(abstract->context, "Insufficient buffer space available."); return DC_STATUS_NOMEMORY; } @@ -836,7 +916,7 @@ cochran_commander_device_dump (dc_device_t *abstract, dc_buffer_t *data)
// Read the sample data, from 0 to sample end will include logbook rc = cochran_commander_read (abstract, &progress, 0, - dc_buffer_get_data(data), layout->rb_profile_end); + dc_buffer_get_data(data), device->info->rb_profile_end); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the sample data."); return rc; @@ -853,17 +933,17 @@ 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) +cochran_guess_sample_end_address(cochran_device_t *device, cochran_data_t *data, unsigned int log_num) { - const unsigned char *log_entry = data->logbook + layout->rb_log_size * log_num; + const unsigned char *log_entry = data->logbook + device->info->log_size * log_num;
if (log_num == data->dive_count) // Return next usable address from config0 page return array_uint32_le(data->config[0] - + layout->rb_profile_end); + + device->info->rb_profile_end);
// Next log's start address - return array_uint32_le(log_entry + layout->rb_log_size + 6); + return array_uint32_le(log_entry + device->info->log_size + 6); }
@@ -873,7 +953,6 @@ cochran_commander_device_foreach (dc_device_t *abstract, { cochran_device_t *device = (cochran_device_t *) abstract; cochran_data_t *data = &device->data; - cochran_layout_t *layout = cochran_commander_get_layout(device->model_string);
unsigned int sample_start_address, sample_end_address; dc_status_t rc; @@ -889,19 +968,19 @@ 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_entry = data->logbook + i * layout->rb_log_size; + log_entry = data->logbook + i * device->info->log_size;
sample_start_address = array_uint32_le (log_entry + 6); - sample_end_address = array_uint32_le (log_entry + layout->rb_log_size / 2); + sample_end_address = array_uint32_le (log_entry + device->info->log_size / 2);
if (sample_end_address == 0xFFFFFFFF) // Corrupt dive, guess the end address - sample_end_address = cochran_guess_sample_end_address(layout, data, i); + sample_end_address = cochran_guess_sample_end_address(device, data, i);
sample = data->sample + sample_start_address - data->sample_data_offset;
// Determine size of sample - unsigned int dive_num =array_uint16_le(log_entry + layout->pt_log_dive_number); + unsigned int dive_num =array_uint16_le(log_entry + device->log_ptr->dive_number); if (dive_num >= data->profile_tail) sample_size = sample_end_address - sample_start_address; else @@ -909,32 +988,31 @@ cochran_commander_device_foreach (dc_device_t *abstract,
if (sample_size < 0) // Adjust for ring buffer wrap-around - sample_size += layout->rb_profile_end - - layout->rb_profile_begin; + sample_size += device->info->rb_profile_end + - device->info->rb_profile_begin;
- fingerprint = log_entry + layout->pt_log_dive_number; + fingerprint = log_entry + device->log_ptr->dive_number;
// Build dive blob - dive_size = COCHRAN_MODEL_SIZE + layout->rb_log_size + sample_size; + dive_size = device->info->log_size + sample_size; dive = (unsigned char *) malloc(dive_size); if (dive == NULL) return DC_STATUS_NOMEMORY;
- memcpy(dive, data->id + 0x3B, 8); // model string - memcpy(dive + COCHRAN_MODEL_SIZE, log_entry, layout->rb_log_size); // log + memcpy(dive, log_entry, device->info->log_size); // log
// Copy profile data if (sample_size) { if (sample_start_address <= sample_end_address) { - memcpy(dive + COCHRAN_MODEL_SIZE + layout->rb_log_size, sample, + memcpy(dive + device->info->log_size, sample, sample_size); } else { // It wrapped the buffer, copy two sections - unsigned int size = layout->rb_profile_end - sample_start_address; + unsigned int size = device->info->rb_profile_end - sample_start_address;
- memcpy(dive + COCHRAN_MODEL_SIZE + layout->rb_log_size, sample, size); - memcpy(dive + COCHRAN_MODEL_SIZE + layout->rb_log_size + size, - data->sample, sample_end_address - layout->rb_profile_begin); + memcpy(dive + device->info->log_size, sample, size); + memcpy(dive + device->info->log_size + size, + data->sample, sample_end_address - device->info->rb_profile_begin); } }
@@ -960,16 +1038,16 @@ cochran_commander_device_foreach (dc_device_t *abstract, */
static void -cochran_check_profiles(dc_device_t *abstract, cochran_layout_t *layout) { +cochran_check_profiles(dc_device_t *abstract) { cochran_device_t *device = (cochran_device_t *) abstract;
// Find head log entry // Set handy point to first dive's dive number - unsigned char *l = device->data.logbook + layout->pt_log_dive_number; + unsigned char *l = device->data.logbook + device->log_ptr->dive_number; int head; - for (head = 0; head < layout->rb_max_log - 1; head ++) { + for (head = 0; head < device->info->max_log - 1; head ++) { int this_dive = array_uint16_le(l); - l += layout->rb_log_size; + l += device->info->log_size; int next_dive = array_uint16_le(l);
if (next_dive < this_dive || next_dive == 0xFFFF) @@ -978,10 +1056,10 @@ cochran_check_profiles(dc_device_t *abstract, cochran_layout_t *layout) {
// This is the last piece of data unsigned int head_ptr; - if (layout->endian == ENDIAN_LE) - head_ptr = array_uint32_le(device->data.config[0] + layout->pt_conf_last_log); + if (device->info->endian == ENDIAN_LE) + head_ptr = array_uint32_le(device->data.config[0] + device->conf_ptr->last_log); else - head_ptr = array_uint32_be(device->data.config[0] + layout->pt_conf_last_log); + head_ptr = array_uint32_be(device->data.config[0] + device->conf_ptr->last_log);
// Cochran (commander at least) erases 8k blocks, so round up head_ptr = (head_ptr & 0xfffff000) + 0x2000; @@ -993,16 +1071,16 @@ cochran_check_profiles(dc_device_t *abstract, cochran_layout_t *layout) { int profile_wrap = 0; int n = head - 1; if (n < 0) - n = MIN(layout->rb_max_log - 1, head); + n = MIN(device->info->max_log - 1, head);
while (n != head) { - unsigned int start_dive_ptr = array_uint32_le(device->data.logbook + layout->rb_log_size * n + layout->pt_log_profile_begin); - unsigned int end_dive_ptr = array_uint32_le(device->data.logbook + layout->rb_log_size * n + layout->pt_log_profile_end); + unsigned int start_dive_ptr = array_uint32_le(device->data.logbook + device->info->log_size * n + device->log_ptr->profile_begin_offset); + unsigned int end_dive_ptr = array_uint32_le(device->data.logbook + device->info->log_size * n + device->log_ptr->profile_end_offset);
if (start_dive_ptr == 0xFFFFFFFF || start_dive_ptr == 0) { n--; if (n < 0) - n = MIN(layout->rb_max_log - 1, head); + n = MIN(device->info->max_log - 1, head); continue; }
@@ -1020,7 +1098,7 @@ cochran_check_profiles(dc_device_t *abstract, cochran_layout_t *layout) {
n--; if (n < 0) - n = MIN(layout->rb_max_log - 1, head); + n = MIN(device->info->max_log - 1, head); }
device->data.log_head = head; diff --git a/src/cochran_commander.h b/src/cochran_commander.h index 981750c..22f17a3 100644 --- a/src/cochran_commander.h +++ b/src/cochran_commander.h @@ -26,11 +26,7 @@ #define COCHRAN_MODEL_SIZE 8 #define COCHRAN_FINGERPRINT_SIZE 2
-#define COCHRAN_EMC_LOG_SIZE 512 -#define COCHRAN_EMC_SAMPLE_SIZE 3 - -#define COCHRAN_CMDR_LOG_SIZE 256 -#define COCHRAN_CMDR_SAMPLE_SIZE 2 +#define UNSUPPORTED 0xFFFFFFFF
typedef enum cochran_model_t { COCHRAN_MODEL_UNKNOWN = 0, @@ -47,60 +43,65 @@ typedef enum cochran_endian_t { ENDIAN_BE, } cochran_endian_t;
-typedef enum cochran_dateformat_t { - DATE_SMHDMY, - DATE_MSDHYM, -} cochran_dateformat_t; - typedef enum cochran_addresssize_t { ADDRESS_24_BIT, ADDRESS_32_BIT, } cochran_addresssize_t;
-typedef struct cochran_layout_t { +typedef enum cochran_sample_format_t { + SAMPLE_UNKNOWN, + SAMPLE_CMDR, + SAMPLE_EMC, +} cochran_sample_format_t; + +// Information about the capabilities of a device +// Used in reading the information from the device +// and (in the case of max_*) used when decoding. +typedef struct cochran_device_info_t { cochran_model_t model; - cochran_endian_t endian; - cochran_dateformat_t date_format; cochran_addresssize_t address_size; - unsigned int high_baud_rate; - - unsigned int rb_logbook_begin; - unsigned int rb_logbook_end; - unsigned int rb_log_size; - unsigned int rb_max_log; - unsigned int rb_profile_begin; - unsigned int rb_profile_end; - - unsigned int pt_log_profile_begin; - unsigned int pt_log_profile_pre; - unsigned int pt_log_dive_number; - unsigned int pt_log_profile_end; - - unsigned int pt_conf_dive_count; - unsigned int pt_conf_last_log; - unsigned int pt_conf_last_interdive; -} cochran_layout_t; - -// Device configuration items -/* -typedef struct cochran_config_t { - cochran_model_t model; + cochran_endian_t endian; + int high_baud; + + int rb_logbook_begin; + int rb_logbook_end; + int rb_profile_begin; + int rb_profile_end; + 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 -} cochran_config_t; -*/ + int max_log; + int max_gas; + + cochran_sample_format_t sample_format; +} cochran_device_info_t; + +// Offsets into the conf block +typedef struct cochran_conf_offsets_t { + int dive_count; + int last_log; + int last_interdive; +} cochran_conf_offsets_t; + +// Offsets into each log block +typedef struct cochran_log_offsets_t { + int sec, min, hour, day, mon, year; + int profile_begin_offset; + int water_conductivity; + int profile_pre_offset; + int start_temp; + int start_depth; + int dive_number; + int altitude; + int profile_end_offset; + int end_temp; + int bt; + int max_depth; + int avg_depth; + int o2_percent; + int he_percent; + int min_temp; + int max_temp; +} cochran_log_offsets_t;
typedef struct cochran_data_t { unsigned char id0[67]; @@ -125,59 +126,24 @@ typedef struct cochran_data_t { unsigned int sample_size; } cochran_data_t;
+struct event_size { + int code; + int size; +}; + typedef struct cochran_device_t { dc_device_t base; const char *name; // serial port name serial_t *port; unsigned char model_string[COCHRAN_MODEL_SIZE + 1]; + cochran_device_info_t *info; + cochran_conf_offsets_t *conf_ptr; + cochran_log_offsets_t *log_ptr; + struct event_size (*event_bytes)[15]; cochran_data_t data; // dive data used in parsing } 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 CMD_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_END_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=% -#define CMD_MIN_TEMP 232 // 1 byte, /2+20=F -#define CMD_MAX_TEMP 233 // 1 byte, /2+20=F - -// 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 24 // 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_END_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 -#define EMC_MIN_TEMP 403 // 1 byte, /2+20=F -#define EMC_MAX_TEMP 407 // 1 byte, /2+20=F - - 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); @@ -189,4 +155,6 @@ 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); -cochran_layout_t *cochran_commander_get_layout(const unsigned char *model); +int cochran_commander_model_id(const char *model); +dc_status_t cochran_commander_info(const int model, + cochran_device_info_t **info, cochran_conf_offsets_t **conf_ptr, cochran_log_offsets_t **log_ptr, struct event_size (**event_bytes)[15]); diff --git a/src/cochran_commander_parser.c b/src/cochran_commander_parser.c index a6380e4..45184fe 100644 --- a/src/cochran_commander_parser.c +++ b/src/cochran_commander_parser.c @@ -20,6 +20,7 @@ */
#include <stdlib.h> +#include <math.h>
#include <libdivecomputer/units.h> #include <libdivecomputer/cochran.h> @@ -34,8 +35,39 @@ #include "cochran_commander_parser.h"
-dc_status_t cochran_commander_parser_set_data (dc_parser_t *abstract, - const unsigned char *data, unsigned int size); +static cochran_events_t cochran_events[] = { + { 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 } +}; + + 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, @@ -48,7 +80,10 @@ typedef struct cochran_commander_parser_t cochran_commander_parser_t;
struct cochran_commander_parser_t { dc_parser_t base; - cochran_layout_t *layout; + unsigned int model; + cochran_device_info_t *info; + cochran_log_offsets_t *log_ptr; + struct event_size (*event_bytes)[15]; };
static dc_parser_vtable_t cochran_commander_parser_vtable = { @@ -64,7 +99,7 @@ static dc_parser_vtable_t cochran_commander_parser_vtable = {
dc_status_t -cochran_commander_parser_create (dc_parser_t **out, dc_context_t *context) +cochran_commander_parser_create (dc_parser_t **out, dc_context_t *context, int model) { cochran_commander_parser_t *parser = NULL;
@@ -78,7 +113,9 @@ cochran_commander_parser_create (dc_parser_t **out, dc_context_t *context) return DC_STATUS_NOMEMORY; }
- parser->layout = NULL; + parser->model = model; + + cochran_commander_info(model, &parser->info, NULL, &parser->log_ptr, &parser->event_bytes);
*out = (dc_parser_t *) parser;
@@ -90,99 +127,28 @@ dc_status_t 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; - - // Determine cochran data format - // abstract->data is prefixed by the model string - // 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; }
-// 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_commander_parser_t *parser = (cochran_commander_parser_t *) abstract; - const unsigned char *data = abstract->data; - 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_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_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); - } + cochran_log_offsets_t *l = parser->log_ptr; + const unsigned char *log_entry = abstract->data;
- return DC_STATUS_SUCCESS; -} + datetime->second = log_entry[l->sec]; + datetime->minute = log_entry[l->min]; + datetime->hour = log_entry[l->hour]; + datetime->day = log_entry[l->day]; + datetime->month = log_entry[l->mon]; + datetime->year = log_entry[l->year] + (log_entry[l->year] > 91 ? 1900 : 2000);
-static dc_status_t -cochran_commander_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, - unsigned int flags, void *value) -{ - 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); - case COCHRAN_MODEL_EMC_FAMILY: - return cochran_emc_parser_get_field(abstract, type, flags, value); - } - - return DC_STATUS_UNSUPPORTED; + return DC_STATUS_SUCCESS; }
-static dc_status_t -cochran_commander_parser_samples_foreach (dc_parser_t *abstract, - dc_sample_callback_t callback, void *userdata) -{ - 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: - 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; - } - - return DC_STATUS_UNSUPPORTED; -}
void cochran_commander_get_event_info(const unsigned char code, @@ -197,6 +163,7 @@ cochran_commander_get_event_info(const unsigned char code, *event = events[i]; }
+ int cochran_commander_handle_event (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata, unsigned char code, @@ -256,20 +223,347 @@ cochran_commander_handle_event (dc_parser_t *abstract, 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].code != -1; x++) { - int ptr = size - (*event_bytes)[x].size; - if (ptr > 0 && samples[ptr] == (*event_bytes)[x].code) { + 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); - } + result = cochran_backparse(abstract, samples, ptr, event_bytes); + } + + if (result < best_result) { + best_result = result; + } + } + + return best_result; +} + + +dc_status_t +cochran_commander_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, + unsigned int flags, void *value) +{ + const cochran_commander_parser_t *parser = (cochran_commander_parser_t *) abstract; + const cochran_log_offsets_t *l = parser->log_ptr; + const unsigned char *log_entry = abstract->data; + unsigned int minutes = 0, qfeet = 0; + + dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + dc_salinity_t *water = (dc_salinity_t *) value; + + if (value) { + switch (type) { + case DC_FIELD_TEMPERATURE_SURFACE: + *((unsigned int*) value) = (log_entry[l->start_temp] - 32.0) / 1.8; + break; + case DC_FIELD_TEMPERATURE_MINIMUM: + if (log_entry[l->min_temp] == 0xFF) + return DC_STATUS_UNSUPPORTED; + else + *((unsigned int*) value) = (log_entry[l->min_temp] / 2.0 + + 20 - 32) / 1.8; + break; + case DC_FIELD_TEMPERATURE_MAXIMUM: + if (log_entry[l->max_temp] == 0xFF) + return DC_STATUS_UNSUPPORTED; + else + *((unsigned int*) value) = (log_entry[l->max_temp] / 2.0 + + 20 - 32) / 1.8; + break; + case DC_FIELD_DIVETIME: + minutes = array_uint16_le(log_entry + l->bt); + + if (minutes == 0xFFFF) + return DC_STATUS_UNSUPPORTED; + else + *((unsigned int *) value) = minutes * 60; + break; + case DC_FIELD_MAXDEPTH: + qfeet = array_uint16_le(log_entry + l->max_depth); + if (qfeet == 0xFFFF) + return DC_STATUS_UNSUPPORTED; + else + *((double *) value) = qfeet / 4.0 * FEET; + break; + case DC_FIELD_AVGDEPTH: + qfeet = array_uint16_le(log_entry + l->avg_depth); + if (qfeet == 0xFFFF) + return DC_STATUS_UNSUPPORTED; + else + *((double *) value) = qfeet / 4.0 * FEET; + break; + case DC_FIELD_GASMIX_COUNT: + *((unsigned int *) value) = 2; + break; + case DC_FIELD_GASMIX: + if (flags < 0 || flags >= parser->info->max_gas) { + return DC_STATUS_UNSUPPORTED; + } + + // 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 + + l->o2_percent + 2 * flags) / 256.0 / 100; + if (l->he_percent == UNSUPPORTED) { + gasmix->helium = 0; + } else { + gasmix->helium = array_uint16_le (log_entry + + l->he_percent + 2 * flags) / 256.0 / 100; + } + gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; + break; + case DC_FIELD_SALINITY: + // 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 + if ((log_entry[l->water_conductivity] & 0x3) == 0) + water->type = DC_WATER_FRESH; + else + water->type = DC_WATER_SALT; + water->density = 1000 + 12.5 * (log_entry[l->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 + * log_entry[l->altitude] * 250.0 * FEET, 5.25588); + break; + default: + return DC_STATUS_UNSUPPORTED; + } + } + + return DC_STATUS_SUCCESS; +} + + +dc_status_t +cochran_commander_parser_samples_foreach (dc_parser_t *abstract, + dc_sample_callback_t callback, void *userdata) +{ + const cochran_commander_parser_t *parser = (cochran_commander_parser_t *) abstract; + const cochran_log_offsets_t *l = parser->log_ptr; + const unsigned char *log_entry = abstract->data; + const unsigned char *samples = log_entry + parser->info->log_size; + + unsigned int size = abstract->size - parser->info->log_size; + + int sampling_size; + switch (parser->info->sample_format) { + case SAMPLE_CMDR: + sampling_size = 2; + break; + case SAMPLE_EMC: + sampling_size = 3; + break; + } + + dc_sample_value_t sample = {0}; + unsigned int time = 0, last_sample_time = 0; + unsigned int offset = 0; + double start_depth = 0; + int depth_qfeet = 0; // 3 inch increments + unsigned char deco_obligation = 0; + unsigned int deco_ceiling = 0; + unsigned char corrupt_dive = 0; + + // 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 + l->profile_end_offset) == 0xFFFFFFFF) { + corrupt_dive = 1; + + WARNING(abstract->context, "Incomplete dive on %02d/%02d/%02d at %02d:%02d:%02d, trying to parse samples", + log_entry[l->year], log_entry[l->mon], log_entry[l->day], + log_entry[l->hour], log_entry[l->min], log_entry[l->sec]); + + // Eliminate inter-dive events + size = cochran_backparse(abstract, samples, size, parser->event_bytes); + } + + // Cochran samples depth every second and varies between ascent rate + // and temp every other second. + + // Prime values from the dive log section + if (parser->model == COCHRAN_MODEL_COMMANDER_AIR_NITROX) { + // Commander stores start depth in quarter-feet + start_depth = array_uint16_le (log_entry + l->start_depth) / 4; + } else { + // EMC stores start depth in 256ths of a foot. + start_depth = array_uint16_le (log_entry + l->start_depth) / 256; + } + + last_sample_time = sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + + sample.depth = start_depth * FEET; + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
- if (result < best_result) { - best_result = result; - } - } + sample.temperature = (log_entry[l->start_temp] - 32.0) / 1.8; + if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); + + while (offset < size) { + const unsigned char *s = samples + offset; + + sample.time = time; + if (callback && last_sample_time != sample.time) { + // We haven't issued this time yet. + last_sample_time = sample.time; + callback (DC_SAMPLE_TIME, sample, userdata); + } + + // If corrupt_dive end before offset + if (corrupt_dive) { + // 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) { + INFO(abstract->context, "Used corrupt dive breakout 1 on event %02x", s[0]); + break; + } + if (time > 1 && (s[0] == 0xE3 || s[0] == 0xF3)) { + INFO(abstract->context, "Used corrupt dive breakout 2 on event %02x", s[0]); + break; + } + } + + // Check for event + if (s[0] & 0x80) { + offset += cochran_commander_handle_event(abstract, callback, + userdata, s[0], time); + + if (parser->info->sample_format == SAMPLE_EMC) { + // EMC models have events indicating change in deco status + // Commander may have them but I don't have example data + switch (s[0]) + { + case 0xC5: // Deco obligation begins + deco_obligation = 1; + break; + case 0xD8: // Deco obligation ends + deco_obligation = 0; + break; + case 0xAB: // Decrement ceiling (deeper) + deco_ceiling += 10; // feet + + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.time = (array_uint16_le(s + sampling_size) + 1) * 60; + sample.deco.depth = deco_ceiling * FEET; + if (callback) callback(DC_SAMPLE_DECO, sample, userdata); + break; + case 0xAD: // Increment ceiling (shallower) + deco_ceiling -= 10; // feet + + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.depth = deco_ceiling * FEET; + sample.deco.time = (array_uint16_le(s + sampling_size) + 1) * 60; + if (callback) callback(DC_SAMPLE_DECO, sample, userdata); + break; + case 0xC0: // Switched to FO2 21% mode (surface) + // Event seen upon surfacing + break; + case 0xCD: // Switched to deco blend + case 0xEF: // Switched to gas blend 2 + sample.gasmix = 1; + if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata); + break; + case 0xF3: // Switched to gas blend 1 + sample.gasmix = 0; + if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata); + break; + } + }
- return best_result; + continue; + } + + // Depth is logged as change in feet, bit 0x40 means negative depth + 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) { + double ascent_rate; + // Ascent rate + 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 + double temperature = s[1] / 2.0 + 20; + sample.temperature = (temperature - 32.0) / 1.8; + + if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); + } + + // Cochran EMC models store NDL and deco stop time + // int the 20th to 23rd sample + if (parser->info->sample_format == SAMPLE_EMC) { + // Find the next sample by skipping over any event bytes. + // This is only temporary so we can get NDL and deco stop + // times which span two samples. + const unsigned char *n = s + sampling_size; + cochran_events_t event; + + while ((*n & 0x80) && n < samples + size) { + cochran_commander_get_event_info(*n, &event); + n += event.data_bytes; + } + + // Tissue load is recorded across 20 samples, we ignore them + // NDL and deco stop time is recorded across the next 4 samples + // The first 2 are either NDL or stop time at deepest stop (if in deco) + // The next 2 are total deco stop time. + switch (time % 24) + { + case 20: + if (deco_obligation) { + /* Deco time for deepest stop, unused */ + int deco_time = (s[2] + n[2] * 256 + 1) * 60; + } else { + /* Send deco NDL sample */ + sample.deco.type = DC_DECO_NDL; + sample.deco.time = (s[2] + n[2] * 256 + 1) * 60; // seconds + sample.deco.depth = 0; + if (callback) callback (DC_SAMPLE_DECO, sample, userdata); + } + break; + case 22: + /* Deco time, total obligation */ + if (deco_obligation) { + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.depth = deco_ceiling * FEET; + sample.deco.time = (s[2] + n[2] * 256 + 1) * 60; // minutes + if (callback) callback (DC_SAMPLE_DECO, sample, userdata); + } + break; + } + } + + time ++; + offset += sampling_size; + } + + return DC_STATUS_SUCCESS; } diff --git a/src/cochran_commander_parser.h b/src/cochran_commander_parser.h index 0a125e5..3176a61 100644 --- a/src/cochran_commander_parser.h +++ b/src/cochran_commander_parser.h @@ -26,76 +26,9 @@ typedef struct cochran_events_t { 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, 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 } -}; - - -// Common dc_status_t cochran_commander_parser_create (dc_parser_t **out, - dc_context_t *context); + dc_context_t *context, int model); 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); -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 time); -int cochran_backparse(dc_parser_t *abstract, unsigned const char *samples, int size, struct event_size (*event_bytes)[15]); - - -// 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 deleted file mode 100644 index aae484f..0000000 --- a/src/cochran_emc_parser.c +++ /dev/null @@ -1,345 +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 <math.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" - - -// Size of inter-dive events -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} }; - - -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_entry = abstract->data + COCHRAN_MODEL_SIZE; - unsigned char corrupt_dive = 0; - - // 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; - - 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) = (log_entry[EMC_START_TEMP] - 32.0) / 1.8; - break; - case DC_FIELD_TEMPERATURE_MINIMUM: - if (corrupt_dive) { - return DC_STATUS_UNSUPPORTED; - } else - *((unsigned int*) value) = (log_entry[EMC_MIN_TEMP] / 2.0 - + 20 - 32) / 1.8; - break; - case DC_FIELD_TEMPERATURE_MAXIMUM: - if (corrupt_dive) { - return DC_STATUS_UNSUPPORTED; - } else - *((unsigned int*) value) = (log_entry[EMC_MAX_TEMP] / 2.0 - + 20 - 32) / 1.8; - break; - case DC_FIELD_DIVETIME: - if (corrupt_dive) { - *((unsigned int*) value) = parsed_stats.divetime; - } else - *((unsigned int *) value) = array_uint16_le (log_entry + EMC_BT) * 60; - break; - case DC_FIELD_MAXDEPTH: - if (corrupt_dive) { - *((unsigned int*) value) = parsed_stats.maxdepth; - } else - *((double *) value) = array_uint16_le (log_entry + EMC_MAX_DEPTH) / 4.0 - * FEET; - break; - case DC_FIELD_AVGDEPTH: - if (corrupt_dive) { - return DC_STATUS_UNSUPPORTED; - } else - *((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: - // 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: - // 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 ); - // 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 - * log_entry[EMC_ALTITUDE] * 250.0 * FEET, 5.25588); - break; - default: - return DC_STATUS_UNSUPPORTED; - } - } - - 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_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; - - dc_sample_value_t sample = {0}, empty_sample = {0}; - unsigned int time = 0, last_sample_time = 0; - unsigned int offset = 0; - int depth_qfeet = 0; - double temperature; - double start_depth = 0; - double ascent_rate; - unsigned char deco_obligation = 0; - unsigned int deco_ceiling = 0; - unsigned int deco_time = 0; - unsigned char corrupt_dive = 0; - - // 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_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); - } - - /* - * Cochran samples depth every second and varies between ascent rate - * and temp ever other second. - * In the 21st, 22nd, 23rd, 24th samples are NDL remaining, - * deco time and ceiling - */ - - // Prime with values from the dive log section - 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 = start_depth * FEET; - if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); - - 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; - if (callback && last_sample_time != sample.time) { - // We haven't issued this time yet. - last_sample_time = sample.time; - callback (DC_SAMPLE_TIME, sample, userdata); - } - - s = samples + offset; - - // If corrupt_dive end before offset - if (corrupt_dive) { - // 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; - } - - // Check for event - if (s[0] & 0x80) { - offset += cochran_commander_handle_event(abstract, callback, - userdata, s[0], time); - switch (s[0]) - { - case 0xC5: // Deco obligation begins - deco_obligation = 1; - break; - case 0xD8: // Deco obligation ends - deco_obligation = 0; - break; - case 0xAB: // Decrement ceiling (deeper) - deco_ceiling += 10; // feet - - sample.deco.type = DC_DECO_DECOSTOP; - 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; - case 0xAD: // Increment ceiling (shallower) - deco_ceiling -= 10; // feet - - sample.deco.type = DC_DECO_DECOSTOP; - sample.deco.depth = deco_ceiling * FEET; - sample.deco.time = (array_uint16_le(s + 3) + 1) * 60; - if (callback) callback(DC_SAMPLE_DECO, sample, userdata); - break; - case 0xC0: // Switched to FO2 21% mode (surface) - // Event seen upon surfacing - break; - case 0xCD: // Switched to deco blend - case 0xEF: // Switched to gas blend 2 - sample.gasmix = 1; - if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata); - break; - case 0xF3: // Switched to gas blend 1 - sample.gasmix = 0; - if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata); - break; - } - - continue; - } - - - // Depth is logged as change in feet, bit 0x40 means negative depth - 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 - 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 = 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; - cochran_events_t event; - - while ((*n & 0x80) && n < s + size) { - cochran_commander_get_event_info(*n, &event); - n += event.data_bytes; - } - - // Tissue load is recorded across 20 samples, we ignore them - // NDL and deco stop time is recorded across the next 4 samples - // The first 2 are either NDL or stop time at deepest stop (if in deco) - // The next 2 are total deco stop time. - switch (time % 24) - { - case 20: - if (deco_obligation) { - /* Deco time for deepest stop, unused */ - deco_time = (s[2] + n[2] * 256 + 1) * 60; - } else { - /* Send deco NDL sample */ - sample.deco.type = DC_DECO_NDL; - sample.deco.time = (s[2] + n[2] * 256 + 1) * 60; // seconds - sample.deco.depth = 0; - if (callback) callback (DC_SAMPLE_DECO, sample, userdata); - } - break; - case 22: - /* Deco time, total obligation */ - if (deco_obligation) { - sample.deco.type = DC_DECO_DECOSTOP; - sample.deco.depth = deco_ceiling * FEET; - sample.deco.time = (s[2] + n[2] * 256 + 1) * 60; // minutes - if (callback) callback (DC_SAMPLE_DECO, sample, userdata); - } - break; - } - - time ++; - offset += COCHRAN_EMC_SAMPLE_SIZE; - } - - return DC_STATUS_SUCCESS; -} diff --git a/src/parser.c b/src/parser.c index 40773c7..2498a8e 100644 --- a/src/parser.c +++ b/src/parser.c @@ -146,7 +146,7 @@ dc_parser_new (dc_parser_t **out, dc_device_t *device) rc = divesystem_idive_parser_create2 (&parser, context, device->devinfo.model); break; case DC_FAMILY_COCHRAN_COMMANDER: - rc = cochran_commander_parser_create (&parser, context); + rc = cochran_commander_parser_create (&parser, context, device->devinfo.model); break; default: return DC_STATUS_INVALIDARGS;