Merge branch 'cochran'

This commit is contained in:
Jef Driesen 2017-07-05 13:12:27 +02:00
commit b8b94c46fc
5 changed files with 483 additions and 252 deletions

View File

@ -198,6 +198,13 @@ array_uint32_le (const unsigned char data[])
}
unsigned int
array_uint32_word_be (const unsigned char data[])
{
return data[1] + (data[0] << 8) + (data[3] << 16) + (data[2] << 24);
}
void
array_uint32_le_set (unsigned char data[], const unsigned int input)
{

View File

@ -64,6 +64,9 @@ array_uint32_be (const unsigned char data[]);
unsigned int
array_uint32_le (const unsigned char data[]);
unsigned int
array_uint32_word_be (const unsigned char data[]);
void
array_uint32_le_set (unsigned char data[], const unsigned int input);

View File

@ -28,31 +28,36 @@
#include "device-private.h"
#include "serial.h"
#include "array.h"
#include "ringbuffer.h"
#define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array))
#define COCHRAN_MODEL_COMMANDER_AIR_NITROX 0
#define COCHRAN_MODEL_EMC_14 1
#define COCHRAN_MODEL_EMC_16 2
#define COCHRAN_MODEL_EMC_20 3
#define MAXRETRIES 2
#define COCHRAN_MODEL_COMMANDER_PRE21000 0
#define COCHRAN_MODEL_COMMANDER_AIR_NITROX 1
#define COCHRAN_MODEL_EMC_14 2
#define COCHRAN_MODEL_EMC_16 3
#define COCHRAN_MODEL_EMC_20 4
typedef enum cochran_endian_t {
ENDIAN_LE,
ENDIAN_BE,
ENDIAN_WORD_BE,
} cochran_endian_t;
typedef struct cochran_commander_model_t {
unsigned char id[2 + 1];
unsigned char id[3 + 1];
unsigned int model;
} cochran_commander_model_t;
typedef struct cochran_data_t {
unsigned char config[1024];
unsigned char *logbook;
unsigned char *sample;
unsigned short int dive_count;
int fp_dive_num;
int invalid_profile_dive_num;
unsigned int logbook_size;
@ -78,7 +83,9 @@ typedef struct cochran_device_layout_t {
// Profile ringbuffer.
unsigned int rb_profile_begin;
unsigned int rb_profile_end;
// Profile pointers.
// pointers.
unsigned int pt_fingerprint;
unsigned int fingerprint_size;
unsigned int pt_profile_pre;
unsigned int pt_profile_begin;
unsigned int pt_profile_end;
@ -109,15 +116,15 @@ static const dc_device_vtable_t cochran_commander_device_vtable = {
cochran_commander_device_close /* close */
};
// Cochran Commander Nitrox
static const cochran_device_layout_t cochran_cmdr_device_layout = {
COCHRAN_MODEL_COMMANDER_AIR_NITROX, // model
// Cochran Commander pre-21000 s/n
static const cochran_device_layout_t cochran_cmdr_1_device_layout = {
COCHRAN_MODEL_COMMANDER_PRE21000, // model
24, // address_bits
ENDIAN_BE, // endian
ENDIAN_WORD_BE, // endian
115200, // baudrate
0x046, // cf_dive_count
0x06E, // cf_last_log
0x200, // cf_last_interdive
0x6c, // cf_last_log
0x70, // cf_last_interdive
0x0AA, // cf_serial_number
0x00000000, // rb_logbook_begin
0x00020000, // rb_logbook_end
@ -125,6 +132,32 @@ static const cochran_device_layout_t cochran_cmdr_device_layout = {
512, // rb_logbook_entry_count
0x00020000, // rb_profile_begin
0x00100000, // rb_profile_end
12, // pt_fingerprint
4, // fingerprint_size
28, // pt_profile_pre
0, // pt_profile_begin
128, // pt_profile_end
};
// Cochran Commander Nitrox
static const cochran_device_layout_t cochran_cmdr_device_layout = {
COCHRAN_MODEL_COMMANDER_AIR_NITROX, // model
24, // address_bits
ENDIAN_WORD_BE, // endian
115200, // baudrate
0x046, // cf_dive_count
0x06C, // cf_last_log
0x070, // cf_last_interdive
0x0AA, // cf_serial_number
0x00000000, // rb_logbook_begin
0x00020000, // rb_logbook_end
256, // rb_logbook_entry_size
512, // rb_logbook_entry_count
0x00020000, // rb_profile_begin
0x00100000, // rb_profile_end
0, // pt_fingerprint
6, // fingerprint_size
30, // pt_profile_pre
6, // pt_profile_begin
128, // pt_profile_end
@ -146,6 +179,8 @@ static const cochran_device_layout_t cochran_emc14_device_layout = {
256, // rb_logbook_entry_count
0x00022000, // rb_profile_begin
0x00200000, // rb_profile_end
0, // pt_fingerprint
6, // fingerprint_size
30, // pt_profile_pre
6, // pt_profile_begin
256, // pt_profile_end
@ -167,6 +202,8 @@ static const cochran_device_layout_t cochran_emc16_device_layout = {
1024, // rb_logbook_entry_count
0x00094000, // rb_profile_begin
0x00800000, // rb_profile_end
0, // pt_fingerprint
6, // fingerprint_size
30, // pt_profile_pre
6, // pt_profile_begin
256, // pt_profile_end
@ -188,6 +225,8 @@ static const cochran_device_layout_t cochran_emc20_device_layout = {
1024, // rb_logbook_entry_count
0x00094000, // rb_profile_begin
0x01000000, // rb_profile_end
0, // pt_fingerprint
6, // fingerprint_size
30, // pt_profile_pre
6, // pt_profile_begin
256, // pt_profile_end
@ -199,15 +238,20 @@ static unsigned int
cochran_commander_get_model (cochran_commander_device_t *device)
{
const cochran_commander_model_t models[] = {
{"\x11""2", COCHRAN_MODEL_COMMANDER_AIR_NITROX},
{"73", COCHRAN_MODEL_EMC_14},
{"A3", COCHRAN_MODEL_EMC_16},
{"23", COCHRAN_MODEL_EMC_20},
{"\x11""21", COCHRAN_MODEL_COMMANDER_PRE21000},
{"\x11""22", COCHRAN_MODEL_COMMANDER_AIR_NITROX},
{"730", COCHRAN_MODEL_EMC_14},
{"731", COCHRAN_MODEL_EMC_14},
{"A30", COCHRAN_MODEL_EMC_16},
{"A31", COCHRAN_MODEL_EMC_16},
{"230", COCHRAN_MODEL_EMC_20},
{"231", COCHRAN_MODEL_EMC_20},
{"\x40""30", COCHRAN_MODEL_EMC_20},
};
unsigned int model = 0xFFFFFFFF;
for (unsigned int i = 0; i < C_ARRAY_SIZE(models); ++i) {
if (memcmp (device->id + 0x3B, models[i].id, sizeof(models[i].id) - 1) == 0) {
if (memcmp (device->id + 0x3D, models[i].id, sizeof(models[i].id) - 1) == 0) {
model = models[i].model;
break;
}
@ -429,24 +473,165 @@ cochran_commander_read (cochran_commander_device_t *device, dc_event_progress_t
return DC_STATUS_SUCCESS;
}
static unsigned int
cochran_commander_read_retry (cochran_commander_device_t *device, dc_event_progress_t *progress, unsigned int address, unsigned char data[], unsigned int size)
{
// Save the state of the progress events.
unsigned int saved = progress->current;
static void
unsigned int nretries = 0;
dc_status_t rc = DC_STATUS_SUCCESS;
while ((rc = cochran_commander_read (device, progress, address, data, size)) != DC_STATUS_SUCCESS) {
// Automatically discard a corrupted packet,
// and request a new one.
if (rc != DC_STATUS_PROTOCOL && rc != DC_STATUS_TIMEOUT)
return rc;
// Abort if the maximum number of retries is reached.
if (nretries++ >= MAXRETRIES)
return rc;
// Restore the state of the progress events.
progress->current = saved;
}
return rc;
}
/*
* For corrupt dives the end-of-samples pointer is 0xFFFFFFFF
* search for a reasonable size, e.g. using next dive start sample
* or end-of-samples to limit searching for recoverable samples
*/
static unsigned int
cochran_commander_guess_sample_end_address(cochran_commander_device_t *device, cochran_data_t *data, unsigned int log_num)
{
const unsigned char *log_entry = data->logbook + device->layout->rb_logbook_entry_size * log_num;
if (log_num == data->dive_count)
// Return next usable address from config page
return array_uint32_le(data->config + device->layout->rb_profile_end);
// Next log's start address
return array_uint32_le(log_entry + device->layout->rb_logbook_entry_size + device->layout->pt_profile_begin);
}
static unsigned int
cochran_commander_profile_size(cochran_commander_device_t *device, cochran_data_t *data, int dive_num, unsigned int sample_start_address, unsigned int sample_end_address)
{
// Validate addresses
if (sample_start_address < device->layout->rb_profile_begin ||
sample_start_address > device->layout->rb_profile_end ||
sample_end_address < device->layout->rb_profile_begin ||
(sample_end_address > device->layout->rb_profile_end &&
sample_end_address != 0xFFFFFFFF)) {
return 0;
}
if (sample_end_address == 0xFFFFFFFF)
// Corrupt dive, guess the end address
sample_end_address = cochran_commander_guess_sample_end_address(device, data, dive_num);
return ringbuffer_distance(sample_start_address, sample_end_address, 0, device->layout->rb_profile_begin, device->layout->rb_profile_end);
}
/*
* Do several things. Find the log that matches the fingerprint,
* calculate the total read size for progress indicator,
* Determine the most recent dive without profile data.
*/
static unsigned int
cochran_commander_find_fingerprint(cochran_commander_device_t *device, cochran_data_t *data)
{
// Skip to fingerprint to reduce time
if (data->dive_count < device->layout->rb_logbook_entry_count)
data->fp_dive_num = data->dive_count;
else
data->fp_dive_num = device->layout->rb_logbook_entry_count;
data->fp_dive_num--;
// We track profile ringbuffer usage to determine which dives have profile data
int profile_capacity_remaining = device->layout->rb_profile_end - device->layout->rb_profile_begin;
while (data->fp_dive_num >= 0 && memcmp(device->fingerprint,
data->logbook + data->fp_dive_num * device->layout->rb_logbook_entry_size,
sizeof(device->fingerprint)))
data->fp_dive_num--;
int dive_count = -1;
data->fp_dive_num = -1;
// Start at end of log
if (data->dive_count < device->layout->rb_logbook_entry_count)
dive_count = data->dive_count;
else
dive_count = device->layout->rb_logbook_entry_count;
dive_count--;
unsigned int sample_read_size = 0;
data->invalid_profile_dive_num = -1;
// Remove the pre-dive events that occur after the last dive
unsigned int rb_head_ptr = 0;
if (device->layout->endian == ENDIAN_WORD_BE)
rb_head_ptr = (array_uint32_word_be(data->config + device->layout->cf_last_log) & 0xfffff000) + 0x2000;
else
rb_head_ptr = (array_uint32_le(data->config + device->layout->cf_last_log) & 0xfffff000) + 0x2000;
unsigned int head_dive = 0, tail_dive = 0;
if (data->dive_count <= device->layout->rb_logbook_entry_count) {
head_dive = data->dive_count;
tail_dive = 0;
} else {
// Log wrapped
tail_dive = data->dive_count % device->layout->rb_logbook_entry_count;
head_dive = tail_dive;
}
unsigned int last_profile_idx = (device->layout->rb_logbook_entry_count + head_dive - 1) % device->layout->rb_logbook_entry_count;
unsigned int last_profile_end = array_uint32_le(data->logbook + last_profile_idx * device->layout->rb_logbook_entry_size + device->layout->pt_profile_end);
unsigned int last_profile_pre = 0xFFFFFFFF;
if (device->layout->endian == ENDIAN_WORD_BE)
last_profile_pre = array_uint32_word_be(data->config + device->layout->cf_last_log);
else
last_profile_pre = array_uint32_le(data->config + device->layout->cf_last_log);
if (rb_head_ptr > last_profile_end)
profile_capacity_remaining -= rb_head_ptr - last_profile_end;
// Loop through dives to find FP, Accumulate profile data size,
// and find the last dive with invalid profile
for (unsigned int i = 0; i <= dive_count; ++i) {
unsigned int idx = (device->layout->rb_logbook_entry_count + head_dive - (i + 1)) % device->layout->rb_logbook_entry_count;
unsigned char *log_entry = data->logbook + idx * device->layout->rb_logbook_entry_size;
// We're done if we find the fingerprint
if (!memcmp(device->fingerprint, log_entry + device->layout->pt_fingerprint, device->layout->fingerprint_size)) {
data->fp_dive_num = idx;
break;
}
unsigned int profile_pre = array_uint32_le(log_entry + device->layout->pt_profile_pre);
unsigned int profile_begin = array_uint32_le(log_entry + device->layout->pt_profile_begin);
unsigned int profile_end = array_uint32_le(log_entry + device->layout->pt_profile_end);
unsigned int sample_size = cochran_commander_profile_size(device, data, idx, profile_pre, last_profile_pre);
unsigned int read_size = cochran_commander_profile_size(device, data, idx, profile_begin, profile_end);
last_profile_pre = profile_pre;
// Determine if sample exists
if (profile_capacity_remaining > 0) {
// Subtract this dive's profile size including post-dive events
profile_capacity_remaining -= sample_size;
if (profile_capacity_remaining < 0) {
// Save the last dive that is missing profile data
data->invalid_profile_dive_num = idx;
}
// Accumulate read size for progress bar
sample_read_size += read_size;
}
}
return sample_read_size;
}
static void
cochran_commander_get_sample_parms(cochran_commander_device_t *device, cochran_data_t *data)
{
@ -509,103 +694,6 @@ cochran_commander_get_sample_parms(cochran_commander_device_t *device, cochran_d
}
/*
* For corrupt dives the end-of-samples pointer is 0xFFFFFFFF
* search for a reasonable size, e.g. using next dive start sample
* or end-of-samples to limit searching for recoverable samples
*/
static unsigned int
cochran_commander_guess_sample_end_address(cochran_commander_device_t *device, cochran_data_t *data, unsigned int log_num)
{
const unsigned char *log_entry = data->logbook + device->layout->rb_logbook_entry_size * log_num;
if (log_num == data->dive_count)
// Return next usable address from config page
return array_uint32_le(data->config + device->layout->rb_profile_end);
// Next log's start address
return array_uint32_le(log_entry + device->layout->rb_logbook_entry_size + device->layout->pt_profile_begin);
}
static dc_status_t
cochran_commander_read_all (cochran_commander_device_t *device, cochran_data_t *data)
{
dc_device_t *abstract = (dc_device_t *) device;
dc_status_t rc = DC_STATUS_SUCCESS;
// Calculate max data sizes
unsigned int max_config = sizeof(data->config);
unsigned int max_logbook = device->layout->rb_logbook_end - device->layout->rb_logbook_begin;
unsigned int max_sample = device->layout->rb_profile_end - device->layout->rb_profile_begin;
dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
progress.maximum = max_config + max_logbook + max_sample;
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
// Emit ID block
dc_event_vendor_t vendor;
vendor.data = device->id;
vendor.size = sizeof (device->id);
device_event_emit (abstract, DC_EVENT_VENDOR, &vendor);
// Read config
rc = cochran_commander_read_config(device, &progress, data->config, sizeof(data->config));
if (rc != DC_STATUS_SUCCESS)
return rc;
// Determine size of dive list to read.
if (device->layout->endian == ENDIAN_LE)
data->dive_count = array_uint16_le (data->config + device->layout->cf_dive_count);
else
data->dive_count = array_uint16_be (data->config + device->layout->cf_dive_count);
if (data->dive_count > device->layout->rb_logbook_entry_count) {
data->logbook_size = device->layout->rb_logbook_entry_count * device->layout->rb_logbook_entry_size;
} else {
data->logbook_size = data->dive_count * device->layout->rb_logbook_entry_size;
}
progress.maximum -= max_logbook - data->logbook_size;
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
// Allocate space for log book.
data->logbook = (unsigned char *) malloc(data->logbook_size);
if (data->logbook == NULL) {
ERROR (abstract->context, "Failed to allocate memory.");
return DC_STATUS_NOMEMORY;
}
// Request log book
rc = cochran_commander_read(device, &progress, 0, data->logbook, data->logbook_size);
if (rc != DC_STATUS_SUCCESS)
return rc;
// Determine sample memory to read
cochran_commander_find_fingerprint(device, data);
cochran_commander_get_sample_parms(device, data);
progress.maximum -= max_sample - data->sample_size;
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
if (data->sample_size > 0) {
data->sample = (unsigned char *) malloc(data->sample_size);
if (data->sample == NULL) {
ERROR (abstract->context, "Failed to allocate memory.");
return DC_STATUS_NOMEMORY;
}
// Read the sample data
rc = cochran_commander_read (device, &progress, data->sample_data_offset, data->sample, data->sample_size);
if (rc != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to read the sample data.");
return rc;
}
}
return DC_STATUS_SUCCESS;
}
dc_status_t
cochran_commander_device_open (dc_device_t **out, dc_context_t *context, const char *name)
{
@ -647,6 +735,9 @@ cochran_commander_device_open (dc_device_t **out, dc_context_t *context, const c
unsigned int model = cochran_commander_get_model(device);
switch (model) {
case COCHRAN_MODEL_COMMANDER_PRE21000:
device->layout = &cochran_cmdr_1_device_layout;
break;
case COCHRAN_MODEL_COMMANDER_AIR_NITROX:
device->layout = &cochran_cmdr_device_layout;
break;
@ -697,13 +788,13 @@ cochran_commander_device_set_fingerprint (dc_device_t *abstract, const unsigned
{
cochran_commander_device_t *device = (cochran_commander_device_t *) abstract;
if (size && size != sizeof (device->fingerprint))
if (size && size != device->layout->fingerprint_size)
return DC_STATUS_INVALIDARGS;
if (size)
memcpy (device->fingerprint, data, sizeof (device->fingerprint));
memcpy (device->fingerprint, data, device->layout->fingerprint_size);
else
memset (device->fingerprint, 0xFF, sizeof (device->fingerprint));
memset (device->fingerprint, 0xFF, sizeof(device->fingerprint));
return DC_STATUS_SUCCESS;
}
@ -767,111 +858,172 @@ static dc_status_t
cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
{
cochran_commander_device_t *device = (cochran_commander_device_t *) abstract;
const cochran_device_layout_t *layout = device->layout;
dc_status_t status = DC_STATUS_SUCCESS;
cochran_data_t data;
data.logbook = NULL;
data.sample = NULL;
status = cochran_commander_read_all (device, &data);
if (status != DC_STATUS_SUCCESS)
goto error;
// Emit a device info event.
dc_event_devinfo_t devinfo;
devinfo.model = device->layout->model;
devinfo.firmware = 0; // unknown
devinfo.serial = array_uint32_le(data.config + device->layout->cf_serial_number);
device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo);
// Calculate max data sizes
unsigned int max_config = sizeof(data.config);
unsigned int max_logbook = layout->rb_logbook_end - layout->rb_logbook_begin;
unsigned int max_sample = layout->rb_profile_end - layout->rb_profile_begin;
// Calculate profile RB effective head pointer
// Cochran seems to erase 8K chunks so round up.
unsigned int last_start_address = (array_uint32_le(data.config + device->layout->cf_last_interdive) & 0xfffff000) + 0x2000;
if (last_start_address < device->layout->rb_profile_begin || last_start_address > device->layout->rb_profile_end) {
ERROR(abstract->context, "Invalid profile ringbuffer head pointer in Cochran config block.");
status = DC_STATUS_DATAFORMAT;
// setup progress indication
dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
progress.maximum = max_config + max_logbook + max_sample;
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
// Emit ID block
dc_event_vendor_t vendor;
vendor.data = device->id;
vendor.size = sizeof (device->id);
device_event_emit (abstract, DC_EVENT_VENDOR, &vendor);
// Read config
dc_status_t rc = DC_STATUS_SUCCESS;
rc = cochran_commander_read_config(device, &progress, data.config, sizeof(data.config));
if (rc != DC_STATUS_SUCCESS)
return rc;
// Determine size of dive list to read.
if (layout->endian == ENDIAN_LE)
data.dive_count = array_uint16_le (data.config + layout->cf_dive_count);
else
data.dive_count = array_uint16_be (data.config + layout->cf_dive_count);
if (data.dive_count == 0)
// No dives to read
return DC_STATUS_SUCCESS;
if (data.dive_count > layout->rb_logbook_entry_count) {
data.logbook_size = layout->rb_logbook_entry_count * layout->rb_logbook_entry_size;
} else {
data.logbook_size = data.dive_count * layout->rb_logbook_entry_size;
}
// Update progress indicator with new maximum
progress.maximum -= max_logbook - data.logbook_size;
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
// Allocate space for log book.
data.logbook = (unsigned char *) malloc(data.logbook_size);
if (data.logbook == NULL) {
ERROR (abstract->context, "Failed to allocate memory.");
return DC_STATUS_NOMEMORY;
}
// Request log book
rc = cochran_commander_read(device, &progress, 0, data.logbook, data.logbook_size);
if (rc != DC_STATUS_SUCCESS) {
status = rc;
goto error;
}
// We track profile ringbuffer usage to determine which dives have profile data
int profile_capacity_remaining = device->layout->rb_profile_end - device->layout->rb_profile_begin;
// Locate fingerprint, recent dive with invalid profile and calc read size
unsigned int profile_read_size = cochran_commander_find_fingerprint(device, &data);
// Update progress indicator with new maximum
progress.maximum -= (max_sample - profile_read_size);
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
unsigned int dive_count = 0;
if (data.dive_count < device->layout->rb_logbook_entry_count)
dive_count = data.dive_count;
cochran_commander_get_sample_parms(device, &data);
// Emit a device info event.
dc_event_devinfo_t devinfo;
devinfo.model = layout->model;
devinfo.firmware = 0; // unknown
if (layout->endian == ENDIAN_WORD_BE)
devinfo.serial = array_uint32_word_be(data.config + layout->cf_serial_number);
else
dive_count = device->layout->rb_logbook_entry_count;
devinfo.serial = array_uint32_le(data.config + layout->cf_serial_number);
device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo);
unsigned int head_dive = 0, tail_dive = 0, dive_count = 0;
if (data.dive_count <= layout->rb_logbook_entry_count) {
head_dive = data.dive_count;
tail_dive = 0;
} else {
// Log wrapped
tail_dive = data.dive_count % layout->rb_logbook_entry_count;
head_dive = tail_dive;
}
// Change tail to dive following the fingerprint dive.
if (data.fp_dive_num > -1)
tail_dive = (data.fp_dive_num + 1) % layout->rb_logbook_entry_count;
// Number of dives to read
dive_count = (layout->rb_logbook_entry_count + head_dive - tail_dive) % layout->rb_logbook_entry_count;
int invalid_profile_flag = 0;
// Loop through each dive
for (int i = dive_count - 1; i > data.fp_dive_num; i--) {
unsigned char *log_entry = data.logbook + i * device->layout->rb_logbook_entry_size;
for (unsigned int i = 0; i < dive_count; ++i) {
unsigned int idx = (layout->rb_logbook_entry_count + head_dive - (i + 1)) % layout->rb_logbook_entry_count;
unsigned int sample_start_address = array_uint32_le (log_entry + device->layout->pt_profile_begin);
unsigned int sample_end_address = array_uint32_le (log_entry + device->layout->pt_profile_end);
unsigned char *log_entry = data.logbook + idx * layout->rb_logbook_entry_size;
// Validate
if (sample_start_address < device->layout->rb_profile_begin ||
sample_start_address > device->layout->rb_profile_end ||
sample_end_address < device->layout->rb_profile_begin ||
(sample_end_address > device->layout->rb_profile_end &&
sample_end_address != 0xFFFFFFFF)) {
continue;
}
unsigned int sample_start_address = array_uint32_le (log_entry + layout->pt_profile_begin);
unsigned int sample_end_address = array_uint32_le (log_entry + layout->pt_profile_end);
if (sample_end_address == 0xFFFFFFFF)
// Corrupt dive, guess the end address
sample_end_address = cochran_commander_guess_sample_end_address(device, &data, i);
// Determine if sample exists
if (profile_capacity_remaining > 0) {
// Subtract this dive's profile size including post-dive events
profile_capacity_remaining -= (last_start_address - sample_start_address);
// Adjust for a dive that wraps the buffer
if (sample_start_address > last_start_address)
profile_capacity_remaining -= device->layout->rb_profile_end - device->layout->rb_profile_begin;
}
last_start_address = sample_start_address;
unsigned char *sample = NULL;
int sample_size = 0;
if (profile_capacity_remaining < 0) {
// There is no profile for this dive
sample = NULL;
sample_size = 0;
} else {
// Calculate the size of the profile only
sample = data.sample + sample_start_address - data.sample_data_offset;
sample_size = sample_end_address - sample_start_address;
if (sample_size < 0)
// Adjust for ring buffer wrap-around
sample_size += device->layout->rb_profile_end - device->layout->rb_profile_begin;
}
// Determine if profile exists
if (idx == data.invalid_profile_dive_num)
invalid_profile_flag = 1;
if (!invalid_profile_flag)
sample_size = cochran_commander_profile_size(device, &data, idx, sample_start_address, sample_end_address);
// Build dive blob
unsigned int dive_size = device->layout->rb_logbook_entry_size + sample_size;
unsigned int dive_size = layout->rb_logbook_entry_size + sample_size;
unsigned char *dive = (unsigned char *) malloc(dive_size);
if (dive == NULL) {
status = DC_STATUS_NOMEMORY;
goto error;
}
memcpy(dive, log_entry, device->layout->rb_logbook_entry_size); // log
memcpy(dive, log_entry, layout->rb_logbook_entry_size); // log
// Copy profile data
// Read profile data
if (sample_size) {
if (sample_end_address == 0xFFFFFFFF)
// Corrupt dive, guess the end address
sample_end_address = cochran_commander_guess_sample_end_address(device, &data, idx);
if (sample_start_address <= sample_end_address) {
memcpy(dive + device->layout->rb_logbook_entry_size, sample, sample_size);
rc = cochran_commander_read_retry (device, &progress, sample_start_address, dive + layout->rb_logbook_entry_size, sample_size);
if (rc != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to read the sample data.");
status = rc;
free(dive);
goto error;
}
} else {
// It wrapped the buffer, copy two sections
unsigned int size = device->layout->rb_profile_end - sample_start_address;
unsigned int size = layout->rb_profile_end - sample_start_address;
memcpy(dive + device->layout->rb_logbook_entry_size, sample, size);
memcpy(dive + device->layout->rb_logbook_entry_size + size,
data.sample, sample_end_address - device->layout->rb_profile_begin);
rc = cochran_commander_read_retry (device, &progress, sample_start_address, dive + layout->rb_logbook_entry_size, size);
if (rc != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to read the sample data.");
status = rc;
free(dive);
goto error;
}
rc = cochran_commander_read_retry (device, &progress, layout->rb_profile_begin, dive + layout->rb_logbook_entry_size + size, sample_end_address - layout->rb_profile_begin);
if (rc != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to read the sample data.");
status = rc;
free(dive);
goto error;
}
}
}
if (callback && !callback (dive, dive_size, dive, sizeof(device->fingerprint), userdata)) {
if (callback && !callback (dive, dive_size, dive + layout->pt_fingerprint, layout->fingerprint_size, userdata)) {
free(dive);
break;
}
@ -881,6 +1033,5 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call
error:
free(data.logbook);
free(data.sample);
return status;
}

View File

@ -31,10 +31,14 @@
#define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array))
#define COCHRAN_MODEL_COMMANDER_AIR_NITROX 0
#define COCHRAN_MODEL_EMC_14 1
#define COCHRAN_MODEL_EMC_16 2
#define COCHRAN_MODEL_EMC_20 3
#define COCHRAN_MODEL_COMMANDER_PRE21000 0
#define COCHRAN_MODEL_COMMANDER_AIR_NITROX 1
#define COCHRAN_MODEL_EMC_14 2
#define COCHRAN_MODEL_EMC_16 3
#define COCHRAN_MODEL_EMC_20 4
// Cochran time stamps start at Jan 1, 1992
#define COCHRAN_EPOCH 694242000
#define UNSUPPORTED 0xFFFFFFFF
@ -43,11 +47,19 @@ typedef enum cochran_sample_format_t {
SAMPLE_EMC,
} cochran_sample_format_t;
typedef enum cochran_date_encoding_t {
DATE_ENCODING_MSDHYM,
DATE_ENCODING_SMHDMY,
DATE_ENCODING_TICKS,
} cochran_date_encoding_t;
typedef struct cochran_parser_layout_t {
cochran_sample_format_t format;
unsigned int headersize;
unsigned int samplesize;
unsigned int second, minute, hour, day, month, year;
cochran_date_encoding_t date_encoding;
unsigned int datetime;
unsigned int pt_profile_begin;
unsigned int water_conductivity;
unsigned int pt_profile_pre;
@ -101,11 +113,36 @@ static const dc_parser_vtable_t cochran_commander_parser_vtable = {
NULL /* destroy */
};
static const cochran_parser_layout_t cochran_cmdr_1_parser_layout = {
SAMPLE_CMDR, // type
256, // headersize
2, // samplesize
DATE_ENCODING_TICKS, // date_encoding
8, // datetime, 4 bytes
0, // pt_profile_begin, 4 bytes
24, // water_conductivity, 1 byte, 0=low(fresh), 2=high(sea)
28, // pt_profile_pre, 4 bytes
43, // start_temp, 1 byte, F
54, // start_depth, 2 bytes, /4=ft
68, // dive_number, 2 bytes
73, // altitude, 1 byte, /4=kilofeet
128, // pt_profile_end, 4 bytes
153, // end_temp, 1 byte F
166, // divetime, 2 bytes, minutes
168, // max_depth, 2 bytes, /4=ft
170, // avg_depth, 2 bytes, /4=ft
210, // oxygen, 4 bytes (2 of) 2 bytes, /256=%
UNSUPPORTED, // helium, 4 bytes (2 of) 2 bytes, /256=%
232, // min_temp, 1 byte, /2+20=F
233, // max_temp, 1 byte, /2+20=F
};
static const cochran_parser_layout_t cochran_cmdr_parser_layout = {
SAMPLE_CMDR, // type
256, // headersize
2, // samplesize
1, 0, 3, 2, 5, 4, // second, minute, hour, day, month, year, 1 byte each
DATE_ENCODING_MSDHYM, // date_encoding
0, // datetime, 6 bytes
6, // pt_profile_begin, 4 bytes
24, // water_conductivity, 1 byte, 0=low(fresh), 2=high(sea)
30, // pt_profile_pre, 4 bytes
@ -128,7 +165,8 @@ static const cochran_parser_layout_t cochran_emc_parser_layout = {
SAMPLE_EMC, // type
512, // headersize
3, // samplesize
0, 1, 2, 3, 4, 5, // second, minute, hour, day, month, year, 1 byte each
DATE_ENCODING_SMHDMY, // date_encoding
0, // datetime, 6 bytes
6, // pt_profile_begin, 4 bytes
24, // water_conductivity, 1 byte 0=low(fresh), 2=high(sea)
30, // pt_profile_pre, 4 bytes
@ -296,6 +334,11 @@ cochran_commander_parser_create (dc_parser_t **out, dc_context_t *context, unsig
parser->model = model;
switch (model) {
case COCHRAN_MODEL_COMMANDER_PRE21000:
parser->layout = &cochran_cmdr_1_parser_layout;
parser->events = cochran_cmdr_event_bytes;
parser->nevents = C_ARRAY_SIZE(cochran_cmdr_event_bytes);
break;
case COCHRAN_MODEL_COMMANDER_AIR_NITROX:
parser->layout = &cochran_cmdr_parser_layout;
parser->events = cochran_cmdr_event_bytes;
@ -340,13 +383,32 @@ cochran_commander_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dat
if (abstract->size < layout->headersize)
return DC_STATUS_DATAFORMAT;
dc_ticks_t ts = 0;
if (datetime) {
datetime->second = data[layout->second];
datetime->minute = data[layout->minute];
datetime->hour = data[layout->hour];
datetime->day = data[layout->day];
datetime->month = data[layout->month];
datetime->year = data[layout->year] + (data[layout->year] > 91 ? 1900 : 2000);
switch (layout->date_encoding)
{
case DATE_ENCODING_MSDHYM:
datetime->second = data[layout->datetime + 1];
datetime->minute = data[layout->datetime + 0];
datetime->hour = data[layout->datetime + 3];
datetime->day = data[layout->datetime + 2];
datetime->month = data[layout->datetime + 5];
datetime->year = data[layout->datetime + 4] + (data[layout->datetime + 4] > 91 ? 1900 : 2000);
break;
case DATE_ENCODING_SMHDMY:
datetime->second = data[layout->datetime + 0];
datetime->minute = data[layout->datetime + 1];
datetime->hour = data[layout->datetime + 2];
datetime->day = data[layout->datetime + 3];
datetime->month = data[layout->datetime + 4];
datetime->year = data[layout->datetime + 5] + (data[layout->datetime + 5] > 91 ? 1900 : 2000);
break;
case DATE_ENCODING_TICKS:
ts = array_uint32_le(data + layout->datetime) + COCHRAN_EPOCH;
dc_datetime_localtime(datetime, ts);
break;
}
}
return DC_STATUS_SUCCESS;
@ -471,10 +533,11 @@ cochran_commander_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb
// know what the dive summary values are (i.e. max depth, min temp)
if (array_uint32_le(data + layout->pt_profile_end) == 0xFFFFFFFF) {
corrupt_dive = 1;
dc_datetime_t d;
cochran_commander_parser_get_datetime(abstract, &d);
WARNING(abstract->context, "Incomplete dive on %02d/%02d/%02d at %02d:%02d:%02d, trying to parse samples",
data[layout->year], data[layout->month], data[layout->day],
data[layout->hour], data[layout->minute], data[layout->second]);
d.year, d.month, d.day, d.hour, d.minute, d.second);
// Eliminate inter-dive events
size = cochran_commander_backparse(parser, samples, size);
@ -484,7 +547,8 @@ cochran_commander_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb
// and temp every other second.
// Prime values from the dive log section
if (parser->model == COCHRAN_MODEL_COMMANDER_AIR_NITROX) {
if (parser->model == COCHRAN_MODEL_COMMANDER_AIR_NITROX ||
parser->model == COCHRAN_MODEL_COMMANDER_PRE21000) {
// Commander stores start depth in quarter-feet
start_depth = array_uint16_le (data + layout->start_depth) / 4.0;
} else {
@ -503,6 +567,7 @@ cochran_commander_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb
sample.gasmix = 0;
if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata);
unsigned int last_gasmix = sample.gasmix;
while (offset < size) {
const unsigned char *s = samples + offset;
@ -537,45 +602,48 @@ cochran_commander_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb
if (s[0] & 0x80) {
offset += cochran_commander_handle_event(parser, s[0], callback, userdata);
if (layout->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
// Events indicating change in deco status
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 + layout->samplesize) + 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.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 + layout->samplesize) + 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.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
if (last_gasmix != 1) {
sample.gasmix = 1;
if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata);
break;
case 0xF3: // Switched to gas blend 1
last_gasmix = sample.gasmix;
}
break;
case 0xF3: // Switched to gas blend 1
if (last_gasmix != 0) {
sample.gasmix = 0;
if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata);
break;
last_gasmix = sample.gasmix;
}
break;
}
continue;

View File

@ -313,10 +313,12 @@ static const dc_descriptor_t g_descriptors[] = {
{"DiveSystem", "iDive2 Easy", DC_FAMILY_DIVESYSTEM_IDIVE, 0x42},
{"DiveSystem", "iDive2 Deep", DC_FAMILY_DIVESYSTEM_IDIVE, 0x44},
{"DiveSystem", "iDive2 Tech+", DC_FAMILY_DIVESYSTEM_IDIVE, 0x45},
{"Cochran", "Commander", DC_FAMILY_COCHRAN_COMMANDER, 0},
{"Cochran", "EMC-14", DC_FAMILY_COCHRAN_COMMANDER, 1},
{"Cochran", "EMC-16", DC_FAMILY_COCHRAN_COMMANDER, 2},
{"Cochran", "EMC-20H", DC_FAMILY_COCHRAN_COMMANDER, 3},
/* Cochran Commander */
{"Cochran", "Commander I", DC_FAMILY_COCHRAN_COMMANDER, 0},
{"Cochran", "Commander II", DC_FAMILY_COCHRAN_COMMANDER, 1},
{"Cochran", "EMC-14", DC_FAMILY_COCHRAN_COMMANDER, 2},
{"Cochran", "EMC-16", DC_FAMILY_COCHRAN_COMMANDER, 3},
{"Cochran", "EMC-20H", DC_FAMILY_COCHRAN_COMMANDER, 4},
};
typedef struct dc_descriptor_iterator_t {