[PATCH 2/2] Cochran: Made significant changes to address review issues.
John Van Ostrand
john at vanostrand.com
Sat Feb 27 09:56:25 PST 2016
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;
--
2.4.3
More information about the devel
mailing list