Add support for the Cochran Commander and EMC.
This commit is contained in:
parent
b13d8da426
commit
af30fbb3f8
@ -76,6 +76,7 @@ static const backend_table_t g_backends[] = {
|
||||
{"nitekq", DC_FAMILY_DIVERITE_NITEKQ},
|
||||
{"aqualand", DC_FAMILY_CITIZEN_AQUALAND},
|
||||
{"idive", DC_FAMILY_DIVESYSTEM_IDIVE},
|
||||
{"cochran", DC_FAMILY_COCHRAN_COMMANDER},
|
||||
};
|
||||
|
||||
const char *
|
||||
|
||||
@ -54,4 +54,6 @@ libdivecomputer_HEADERS = \
|
||||
citizen_aqualand.h \
|
||||
divesystem.h \
|
||||
divesystem_idive.h \
|
||||
cochran.h \
|
||||
cochran_commander.h \
|
||||
custom_serial.h
|
||||
|
||||
27
include/libdivecomputer/cochran.h
Normal file
27
include/libdivecomputer/cochran.h
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef COCHRAN_H
|
||||
#define COCHRAN_H
|
||||
|
||||
#include "cochran_commander.h"
|
||||
|
||||
#endif /* COCHRAN_H */
|
||||
43
include/libdivecomputer/cochran_commander.h
Normal file
43
include/libdivecomputer/cochran_commander.h
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef COCHRAN_COMMANDER_H
|
||||
#define COCHRAN_COMMANDER_H
|
||||
|
||||
#include "context.h"
|
||||
#include "device.h"
|
||||
#include "parser.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
dc_status_t
|
||||
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, unsigned int model);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* COCHRAN_COMMANDER_H */
|
||||
@ -88,6 +88,8 @@ typedef enum dc_family_t {
|
||||
DC_FAMILY_CITIZEN_AQUALAND = (12 << 16),
|
||||
/* DiveSystem */
|
||||
DC_FAMILY_DIVESYSTEM_IDIVE = (13 << 16),
|
||||
/* Cochran */
|
||||
DC_FAMILY_COCHRAN_COMMANDER = (14 << 16),
|
||||
} dc_family_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@ -8,4 +8,5 @@ Description: A library for communication with various dive computers.
|
||||
Version: @VERSION@
|
||||
Requires.private: @DEPENDENCIES@
|
||||
Libs: -L${libdir} -ldivecomputer
|
||||
Libs.private: -lm
|
||||
Cflags: -I${includedir}
|
||||
|
||||
@ -210,6 +210,14 @@
|
||||
RelativePath="..\src\citizen_aqualand_parser.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\src\cochran_commander.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\src\cochran_commander_parser.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\src\common.c"
|
||||
>
|
||||
@ -524,6 +532,14 @@
|
||||
RelativePath="..\include\libdivecomputer\citizen_aqualand.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\include\libdivecomputer\cochran.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\include\libdivecomputer\cochran_commander.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\src\common-private.h"
|
||||
>
|
||||
|
||||
@ -3,7 +3,7 @@ AM_CFLAGS = $(LIBUSB_CFLAGS) $(HIDAPI_CFLAGS) -DENABLE_DEPRECATED
|
||||
|
||||
lib_LTLIBRARIES = libdivecomputer.la
|
||||
|
||||
libdivecomputer_la_LIBADD = $(LIBUSB_LIBS) $(HIDAPI_LIBS)
|
||||
libdivecomputer_la_LIBADD = $(LIBUSB_LIBS) $(HIDAPI_LIBS) -lm
|
||||
libdivecomputer_la_LDFLAGS = \
|
||||
-version-info $(DC_VERSION_LIBTOOL) \
|
||||
-no-undefined \
|
||||
@ -65,6 +65,8 @@ libdivecomputer_la_SOURCES = \
|
||||
checksum.h checksum.c \
|
||||
array.h array.c \
|
||||
buffer.c \
|
||||
cochran_commander.h cochran_commander.c \
|
||||
cochran_commander_parser.c \
|
||||
custom_serial.c
|
||||
|
||||
if OS_WIN32
|
||||
|
||||
888
src/cochran_commander.c
Normal file
888
src/cochran_commander.c
Normal file
@ -0,0 +1,888 @@
|
||||
/*
|
||||
* 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 <string.h> // memcpy, memcmp
|
||||
#include <stdlib.h> // malloc, free
|
||||
#include <assert.h> // assert
|
||||
|
||||
#include <libdivecomputer/cochran.h>
|
||||
|
||||
#include "context-private.h"
|
||||
#include "device-private.h"
|
||||
#include "serial.h"
|
||||
#include "array.h"
|
||||
|
||||
#define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array))
|
||||
|
||||
#define EXITCODE(rc) \
|
||||
( \
|
||||
rc == -1 ? DC_STATUS_IO : DC_STATUS_TIMEOUT \
|
||||
)
|
||||
|
||||
#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
|
||||
|
||||
typedef enum cochran_endian_t {
|
||||
ENDIAN_LE,
|
||||
ENDIAN_BE,
|
||||
} cochran_endian_t;
|
||||
|
||||
typedef struct cochran_commander_model_t {
|
||||
unsigned char id[8 + 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;
|
||||
|
||||
unsigned int logbook_size;
|
||||
|
||||
unsigned int sample_data_offset;
|
||||
unsigned int sample_size;
|
||||
} cochran_data_t;
|
||||
|
||||
typedef struct cochran_device_layout_t {
|
||||
unsigned int model;
|
||||
unsigned int address_bits;
|
||||
cochran_endian_t endian;
|
||||
unsigned int baudrate;
|
||||
// Config data.
|
||||
unsigned int cf_dive_count;
|
||||
unsigned int cf_last_log;
|
||||
unsigned int cf_last_interdive;
|
||||
unsigned int cf_serial_number;
|
||||
// Logbook ringbuffer.
|
||||
unsigned int rb_logbook_begin;
|
||||
unsigned int rb_logbook_end;
|
||||
unsigned int rb_logbook_entry_size;
|
||||
unsigned int rb_logbook_entry_count;
|
||||
// Profile ringbuffer.
|
||||
unsigned int rb_profile_begin;
|
||||
unsigned int rb_profile_end;
|
||||
// Profile pointers.
|
||||
unsigned int pt_profile_pre;
|
||||
unsigned int pt_profile_begin;
|
||||
unsigned int pt_profile_end;
|
||||
} cochran_device_layout_t;
|
||||
|
||||
typedef struct cochran_commander_device_t {
|
||||
dc_device_t base;
|
||||
serial_t *port;
|
||||
const cochran_device_layout_t *layout;
|
||||
unsigned char id[67];
|
||||
unsigned char fingerprint[6];
|
||||
} cochran_commander_device_t;
|
||||
|
||||
static dc_status_t cochran_commander_device_set_fingerprint (dc_device_t *device, const unsigned char data[], unsigned int size);
|
||||
static dc_status_t cochran_commander_device_read (dc_device_t *device, unsigned int address, unsigned char data[], unsigned int size);
|
||||
static dc_status_t cochran_commander_device_dump (dc_device_t *device, dc_buffer_t *data);
|
||||
static dc_status_t cochran_commander_device_foreach (dc_device_t *device, dc_dive_callback_t callback, void *userdata);
|
||||
static dc_status_t cochran_commander_device_close (dc_device_t *device);
|
||||
|
||||
static const dc_device_vtable_t cochran_commander_device_vtable = {
|
||||
sizeof (cochran_commander_device_t),
|
||||
DC_FAMILY_COCHRAN_COMMANDER,
|
||||
cochran_commander_device_set_fingerprint,/* set_fingerprint */
|
||||
cochran_commander_device_read, /* read */
|
||||
NULL, /* write */
|
||||
cochran_commander_device_dump, /* dump */
|
||||
cochran_commander_device_foreach, /* foreach */
|
||||
cochran_commander_device_close /* close */
|
||||
};
|
||||
|
||||
// Cochran Commander Nitrox
|
||||
static const cochran_device_layout_t cochran_cmdr_device_layout = {
|
||||
COCHRAN_MODEL_COMMANDER_AIR_NITROX, // model
|
||||
24, // address_bits
|
||||
ENDIAN_BE, // endian
|
||||
115200, // baudrate
|
||||
0x046, // cf_dive_count
|
||||
0x06E, // cf_last_log
|
||||
0x200, // 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
|
||||
30, // pt_profile_pre
|
||||
6, // pt_profile_begin
|
||||
128, // pt_profile_end
|
||||
};
|
||||
|
||||
// Cochran EMC-14
|
||||
static const cochran_device_layout_t cochran_emc14_device_layout = {
|
||||
COCHRAN_MODEL_EMC_14, // model
|
||||
32, // address_bits
|
||||
ENDIAN_LE, // endian
|
||||
806400, // baudrate
|
||||
0x0D2, // cf_dive_count
|
||||
0x13E, // cf_last_log
|
||||
0x142, // cf_last_interdive
|
||||
0x1E6, // cf_serial_number
|
||||
0x00000000, // rb_logbook_begin
|
||||
0x00020000, // rb_logbook_end
|
||||
512, // rb_logbook_entry_size
|
||||
256, // rb_logbook_entry_count
|
||||
0x00022000, // rb_profile_begin
|
||||
0x00200000, // rb_profile_end
|
||||
30, // pt_profile_pre
|
||||
6, // pt_profile_begin
|
||||
256, // pt_profile_end
|
||||
};
|
||||
|
||||
// Cochran EMC-16
|
||||
static const cochran_device_layout_t cochran_emc16_device_layout = {
|
||||
COCHRAN_MODEL_EMC_16, // model
|
||||
32, // address_bits
|
||||
ENDIAN_LE, // endian
|
||||
806400, // baudrate
|
||||
0x0D2, // cf_dive_count
|
||||
0x13E, // cf_last_log
|
||||
0x142, // cf_last_interdive
|
||||
0x1E6, // cf_serial_number
|
||||
0x00000000, // rb_logbook_begin
|
||||
0x00080000, // rb_logbook_end
|
||||
512, // rb_logbook_entry_size
|
||||
1024, // rb_logbook_entry_count
|
||||
0x00094000, // rb_profile_begin
|
||||
0x00800000, // rb_profile_end
|
||||
30, // pt_profile_pre
|
||||
6, // pt_profile_begin
|
||||
256, // pt_profile_end
|
||||
};
|
||||
|
||||
// Cochran EMC-20
|
||||
static const cochran_device_layout_t cochran_emc20_device_layout = {
|
||||
COCHRAN_MODEL_EMC_20, // model
|
||||
32, // address_bits
|
||||
ENDIAN_LE, // endian
|
||||
806400, // baudrate
|
||||
0x0D2, // cf_dive_count
|
||||
0x13E, // cf_last_log
|
||||
0x142, // cf_last_interdive
|
||||
0x1E6, // cf_serial_number
|
||||
0x00000000, // rb_logbook_begin
|
||||
0x00080000, // rb_logbook_end
|
||||
512, // rb_logbook_entry_size
|
||||
1024, // rb_logbook_entry_count
|
||||
0x00094000, // rb_profile_begin
|
||||
0x01000000, // rb_profile_end
|
||||
30, // pt_profile_pre
|
||||
6, // pt_profile_begin
|
||||
256, // pt_profile_end
|
||||
};
|
||||
|
||||
|
||||
// Determine model descriptor number from model string
|
||||
static unsigned int
|
||||
cochran_commander_get_model (cochran_commander_device_t *device)
|
||||
{
|
||||
const cochran_commander_model_t models[] = {
|
||||
{"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},
|
||||
};
|
||||
|
||||
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) {
|
||||
model = models[i].model;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
|
||||
static dc_status_t
|
||||
cochran_commander_serial_setup (cochran_commander_device_t *device)
|
||||
{
|
||||
// Set the serial communication protocol (9600 8N2, no FC).
|
||||
int rc = serial_configure (device->port, 9600, 8, SERIAL_PARITY_NONE, 2, SERIAL_FLOWCONTROL_NONE);
|
||||
if (rc == -1) {
|
||||
ERROR (device->base.context, "Failed to set the terminal attributes.");
|
||||
return DC_STATUS_IO;
|
||||
}
|
||||
|
||||
// Set the timeout for receiving data (5000 ms).
|
||||
if (serial_set_timeout (device->port, 5000) == -1) {
|
||||
ERROR (device->base.context, "Failed to set the timeout.");
|
||||
return DC_STATUS_IO;
|
||||
}
|
||||
|
||||
// Wake up DC and trigger heartbeat
|
||||
serial_set_break(device->port, 1);
|
||||
serial_sleep(device->port, 16);
|
||||
serial_set_break(device->port, 0);
|
||||
|
||||
// Clear old heartbeats
|
||||
serial_flush (device->port, SERIAL_QUEUE_BOTH);
|
||||
|
||||
// Wait for heartbeat byte before send
|
||||
unsigned char answer = 0;
|
||||
int n = serial_read(device->port, &answer, 1);
|
||||
if (n != 1) {
|
||||
ERROR (device->base.context, "Failed to receive device heartbeat.");
|
||||
return EXITCODE (n);
|
||||
}
|
||||
|
||||
if (answer != 0xAA) {
|
||||
ERROR (device->base.context, "Received bad hearbeat byte (%02x).", answer);
|
||||
return DC_STATUS_PROTOCOL;
|
||||
}
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static dc_status_t
|
||||
cochran_commander_packet (cochran_commander_device_t *device, dc_event_progress_t *progress,
|
||||
const unsigned char command[], unsigned int csize,
|
||||
unsigned char answer[], unsigned int asize, int high_speed)
|
||||
{
|
||||
dc_device_t *abstract = (dc_device_t *) device;
|
||||
|
||||
if (device_is_cancelled (abstract))
|
||||
return DC_STATUS_CANCELLED;
|
||||
|
||||
// Send the command to the device, one byte at a time
|
||||
// If sent all at once the command is ignored. It's like the DC
|
||||
// has no buffering.
|
||||
for (unsigned int i = 0; i < csize; i++) {
|
||||
// Give the DC time to read the character.
|
||||
if (i) serial_sleep(device->port, 16); // 16 ms
|
||||
|
||||
unsigned int n = serial_write(device->port, command + i, 1);
|
||||
if (n != 1) {
|
||||
ERROR (abstract->context, "Failed to send the command.");
|
||||
return EXITCODE (n);
|
||||
}
|
||||
}
|
||||
|
||||
if (high_speed) {
|
||||
// Give the DC time to process the command.
|
||||
serial_sleep(device->port, 45);
|
||||
|
||||
// Rates are odd, like 806400 for the EMC, 115200 for commander
|
||||
int rc = serial_configure(device->port, device->layout->baudrate, 8, SERIAL_PARITY_NONE, 2, SERIAL_FLOWCONTROL_NONE);
|
||||
if (rc == -1) {
|
||||
ERROR (abstract->context, "Failed to set the high baud rate.");
|
||||
return DC_STATUS_IO;
|
||||
}
|
||||
}
|
||||
|
||||
// Receive the answer from the device.
|
||||
// Use 1024 byte "packets" so we can display progress.
|
||||
unsigned int nbytes = 0;
|
||||
while (nbytes < asize) {
|
||||
unsigned int len = asize - nbytes;
|
||||
if (len > 1024)
|
||||
len = 1024;
|
||||
|
||||
int n = serial_read (device->port, answer + nbytes, len);
|
||||
if (n != len) {
|
||||
ERROR (abstract->context, "Failed to receive data, expected %u,"
|
||||
"read %u.",len, n);
|
||||
return EXITCODE (n);
|
||||
}
|
||||
|
||||
nbytes += n;
|
||||
|
||||
if (progress) {
|
||||
progress->current += n;
|
||||
device_event_emit (abstract, DC_EVENT_PROGRESS, progress);
|
||||
}
|
||||
}
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static dc_status_t
|
||||
cochran_commander_read_id (cochran_commander_device_t *device, unsigned char id[], unsigned int size)
|
||||
{
|
||||
dc_status_t rc = DC_STATUS_SUCCESS;
|
||||
|
||||
unsigned char command[6] = {0x05, 0x9D, 0xFF, 0x00, 0x43, 0x00};
|
||||
|
||||
rc = cochran_commander_packet(device, NULL, command, sizeof(command), id, size, 0);
|
||||
if (rc != DC_STATUS_SUCCESS)
|
||||
return rc;
|
||||
|
||||
if (memcmp(id, "(C)", 3) != 0) {
|
||||
// It's a Commander, read a different location
|
||||
command[1] = 0xBD;
|
||||
command[2] = 0x7F;
|
||||
|
||||
rc = cochran_commander_packet(device, NULL, command, sizeof(command), id, size, 0);
|
||||
if (rc != DC_STATUS_SUCCESS)
|
||||
return rc;
|
||||
}
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static dc_status_t
|
||||
cochran_commander_read_config (cochran_commander_device_t *device, dc_event_progress_t *progress, unsigned char data[], unsigned int size)
|
||||
{
|
||||
dc_device_t *abstract = (dc_device_t *) device;
|
||||
dc_status_t rc = DC_STATUS_SUCCESS;
|
||||
|
||||
// Read two 512 byte blocks into one 1024 byte buffer
|
||||
for (unsigned int i = 0; i < 2; i++) {
|
||||
const unsigned int len = size / 2;
|
||||
|
||||
unsigned char command[2] = {0x96, i};
|
||||
rc = cochran_commander_packet(device, progress, command, sizeof(command), data + i * len, len, 0);
|
||||
if (rc != DC_STATUS_SUCCESS)
|
||||
return rc;
|
||||
|
||||
dc_event_vendor_t vendor;
|
||||
vendor.data = data + i * len;
|
||||
vendor.size = len;
|
||||
device_event_emit (abstract, DC_EVENT_VENDOR, &vendor);
|
||||
}
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static dc_status_t
|
||||
cochran_commander_read (cochran_commander_device_t *device, dc_event_progress_t *progress, unsigned int address, unsigned char data[], unsigned int size)
|
||||
{
|
||||
dc_status_t rc = DC_STATUS_SUCCESS;
|
||||
|
||||
// Build the command
|
||||
unsigned char command[10];
|
||||
unsigned char command_size;
|
||||
|
||||
switch (device->layout->address_bits) {
|
||||
case 32:
|
||||
// EMC uses 32 bit addressing
|
||||
command[0] = 0x15;
|
||||
command[1] = (address ) & 0xff;
|
||||
command[2] = (address >> 8) & 0xff;
|
||||
command[3] = (address >> 16) & 0xff;
|
||||
command[4] = (address >> 24) & 0xff;
|
||||
command[5] = (size ) & 0xff;
|
||||
command[6] = (size >> 8 ) & 0xff;
|
||||
command[7] = (size >> 16 ) & 0xff;
|
||||
command[8] = (size >> 24 ) & 0xff;
|
||||
command[9] = 0x05;
|
||||
command_size = 10;
|
||||
break;
|
||||
case 24:
|
||||
// Commander uses 24 byte addressing
|
||||
command[0] = 0x15;
|
||||
command[1] = (address ) & 0xff;
|
||||
command[2] = (address >> 8) & 0xff;
|
||||
command[3] = (address >> 16) & 0xff;
|
||||
command[4] = (size ) & 0xff;
|
||||
command[5] = (size >> 8 ) & 0xff;
|
||||
command[6] = (size >> 16 ) & 0xff;
|
||||
command[7] = 0x04;
|
||||
command_size = 8;
|
||||
break;
|
||||
default:
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
}
|
||||
|
||||
serial_sleep(device->port, 800);
|
||||
|
||||
// set back to 9600 baud
|
||||
rc = cochran_commander_serial_setup(device);
|
||||
if (rc != DC_STATUS_SUCCESS)
|
||||
return rc;
|
||||
|
||||
// Read data at high speed
|
||||
rc = cochran_commander_packet (device, progress, command, command_size, data, size, 1);
|
||||
if (rc != DC_STATUS_SUCCESS)
|
||||
return rc;
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
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--;
|
||||
|
||||
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--;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
cochran_commander_get_sample_parms(cochran_commander_device_t *device, cochran_data_t *data)
|
||||
{
|
||||
dc_device_t *abstract = (dc_device_t *) device;
|
||||
unsigned int pre_dive_offset = 0, end_dive_offset = 0;
|
||||
|
||||
unsigned int dive_count = 0;
|
||||
if (data->dive_count < device->layout->rb_logbook_entry_count)
|
||||
dive_count = data->dive_count;
|
||||
else
|
||||
dive_count = device->layout->rb_logbook_entry_count;
|
||||
|
||||
// Find lowest and highest offsets into sample data
|
||||
unsigned int low_offset = 0xFFFFFFFF;
|
||||
unsigned int high_offset = 0;
|
||||
|
||||
for (int i = data->fp_dive_num + 1; i < dive_count; i++) {
|
||||
pre_dive_offset = array_uint32_le (data->logbook + i * device->layout->rb_logbook_entry_size
|
||||
+ device->layout->pt_profile_pre);
|
||||
end_dive_offset = array_uint32_le (data->logbook + i * device->layout->rb_logbook_entry_size
|
||||
+ device->layout->pt_profile_end);
|
||||
|
||||
// Validate offsets, allow 0xFFFFFFF for end_dive_offset
|
||||
// because we handle that as a special case.
|
||||
if (pre_dive_offset < device->layout->rb_profile_begin ||
|
||||
pre_dive_offset > device->layout->rb_profile_end) {
|
||||
ERROR(abstract->context, "Invalid pre-dive offset (%08x) on dive %d.", pre_dive_offset, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (end_dive_offset < device->layout->rb_profile_begin ||
|
||||
(end_dive_offset > device->layout->rb_profile_end &&
|
||||
end_dive_offset != 0xFFFFFFFF)) {
|
||||
ERROR(abstract->context, "Invalid end-dive offset (%08x) on dive %d.", end_dive_offset, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for ring buffer wrap-around.
|
||||
if (pre_dive_offset > end_dive_offset)
|
||||
break;
|
||||
|
||||
if (pre_dive_offset < low_offset)
|
||||
low_offset = pre_dive_offset;
|
||||
if (end_dive_offset > high_offset && end_dive_offset != 0xFFFFFFFF )
|
||||
high_offset = end_dive_offset;
|
||||
}
|
||||
|
||||
if (pre_dive_offset > end_dive_offset) {
|
||||
high_offset = device->layout->rb_profile_end;
|
||||
low_offset = device->layout->rb_profile_begin;
|
||||
data->sample_data_offset = low_offset;
|
||||
data->sample_size = high_offset - low_offset;
|
||||
} else if (low_offset < 0xFFFFFFFF && high_offset > 0) {
|
||||
data->sample_data_offset = low_offset;
|
||||
data->sample_size = high_offset - data->sample_data_offset;
|
||||
} else {
|
||||
data->sample_data_offset = 0;
|
||||
data->sample_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
cochran_commander_device_t *device = NULL;
|
||||
|
||||
if (out == NULL)
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
|
||||
// Allocate memory.
|
||||
device = (cochran_commander_device_t *) dc_device_allocate (context, &cochran_commander_device_vtable);
|
||||
if (device == NULL) {
|
||||
ERROR (context, "Failed to allocate memory.");
|
||||
return DC_STATUS_NOMEMORY;
|
||||
}
|
||||
|
||||
// Set the default values.
|
||||
device->port = NULL;
|
||||
cochran_commander_device_set_fingerprint((dc_device_t *) device, NULL, 0);
|
||||
|
||||
// Open the device.
|
||||
int rc = serial_open (&device->port, device->base.context, name);
|
||||
if (rc == -1) {
|
||||
ERROR (device->base.context, "Failed to open the serial port.");
|
||||
status = DC_STATUS_IO;
|
||||
goto error_free;
|
||||
}
|
||||
|
||||
status = cochran_commander_serial_setup(device);
|
||||
if (status != DC_STATUS_SUCCESS) {
|
||||
goto error_close;
|
||||
}
|
||||
|
||||
// Read ID from the device
|
||||
status = cochran_commander_read_id (device, device->id, sizeof(device->id));
|
||||
if (status != DC_STATUS_SUCCESS) {
|
||||
ERROR (context, "Device not responding.");
|
||||
goto error_close;
|
||||
}
|
||||
|
||||
unsigned int model = cochran_commander_get_model(device);
|
||||
switch (model) {
|
||||
case COCHRAN_MODEL_COMMANDER_AIR_NITROX:
|
||||
device->layout = &cochran_cmdr_device_layout;
|
||||
break;
|
||||
case COCHRAN_MODEL_EMC_14:
|
||||
device->layout = &cochran_emc14_device_layout;
|
||||
break;
|
||||
case COCHRAN_MODEL_EMC_16:
|
||||
device->layout = &cochran_emc16_device_layout;
|
||||
break;
|
||||
case COCHRAN_MODEL_EMC_20:
|
||||
device->layout = &cochran_emc20_device_layout;
|
||||
break;
|
||||
default:
|
||||
ERROR (context, "Unknown model");
|
||||
status = DC_STATUS_UNSUPPORTED;
|
||||
goto error_close;
|
||||
}
|
||||
|
||||
*out = (dc_device_t *) device;
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
|
||||
error_close:
|
||||
serial_close (device->port);
|
||||
error_free:
|
||||
dc_device_deallocate ((dc_device_t *) device);
|
||||
return status;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
cochran_commander_device_close (dc_device_t *abstract)
|
||||
{
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
cochran_commander_device_t *device = (cochran_commander_device_t *) abstract;
|
||||
|
||||
// Close the device.
|
||||
if (serial_close (device->port) == -1) {
|
||||
dc_status_set_error(&status, DC_STATUS_IO);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
cochran_commander_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size)
|
||||
{
|
||||
cochran_commander_device_t *device = (cochran_commander_device_t *) abstract;
|
||||
|
||||
if (size && size != sizeof (device->fingerprint))
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
|
||||
if (size)
|
||||
memcpy (device->fingerprint, data, sizeof (device->fingerprint));
|
||||
else
|
||||
memset (device->fingerprint, 0xFF, sizeof (device->fingerprint));
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static dc_status_t
|
||||
cochran_commander_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size)
|
||||
{
|
||||
cochran_commander_device_t *device = (cochran_commander_device_t *) abstract;
|
||||
|
||||
return cochran_commander_read(device, NULL, address, data, size);
|
||||
}
|
||||
|
||||
|
||||
static dc_status_t
|
||||
cochran_commander_device_dump (dc_device_t *abstract, dc_buffer_t *buffer)
|
||||
{
|
||||
cochran_commander_device_t *device = (cochran_commander_device_t *) abstract;
|
||||
dc_status_t rc = DC_STATUS_SUCCESS;
|
||||
unsigned char config[1024];
|
||||
|
||||
// Make sure buffer is good.
|
||||
if (!dc_buffer_clear(buffer)) {
|
||||
ERROR (abstract->context, "Uninitialized buffer.");
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
}
|
||||
|
||||
// Reserve space
|
||||
if (!dc_buffer_resize(buffer, device->layout->rb_profile_end)) {
|
||||
ERROR(abstract->context, "Insufficient buffer space available.");
|
||||
return DC_STATUS_NOMEMORY;
|
||||
}
|
||||
|
||||
// Determine size for progress
|
||||
dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
|
||||
progress.maximum = sizeof(config) + device->layout->rb_profile_end;
|
||||
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);
|
||||
|
||||
rc = cochran_commander_read_config (device, &progress, config, sizeof(config));
|
||||
if (rc != DC_STATUS_SUCCESS)
|
||||
return rc;
|
||||
|
||||
// Read the sample data, from 0 to sample end will include logbook
|
||||
rc = cochran_commander_read (device, &progress, 0, dc_buffer_get_data(buffer), device->layout->rb_profile_end);
|
||||
if (rc != DC_STATUS_SUCCESS) {
|
||||
ERROR (abstract->context, "Failed to read the sample data.");
|
||||
return rc;
|
||||
}
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
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 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;
|
||||
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;
|
||||
|
||||
unsigned int dive_count = 0;
|
||||
if (data.dive_count < device->layout->rb_logbook_entry_count)
|
||||
dive_count = data.dive_count;
|
||||
else
|
||||
dive_count = device->layout->rb_logbook_entry_count;
|
||||
|
||||
// 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;
|
||||
|
||||
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);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Build dive blob
|
||||
unsigned int dive_size = device->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
|
||||
|
||||
// Copy profile data
|
||||
if (sample_size) {
|
||||
if (sample_start_address <= sample_end_address) {
|
||||
memcpy(dive + device->layout->rb_logbook_entry_size, sample, sample_size);
|
||||
} else {
|
||||
// It wrapped the buffer, copy two sections
|
||||
unsigned int size = device->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);
|
||||
}
|
||||
}
|
||||
|
||||
if (callback && !callback (dive, dive_size, dive, sizeof(device->fingerprint), userdata)) {
|
||||
free(dive);
|
||||
break;
|
||||
}
|
||||
|
||||
free(dive);
|
||||
}
|
||||
|
||||
error:
|
||||
free(data.logbook);
|
||||
free(data.sample);
|
||||
return status;
|
||||
}
|
||||
651
src/cochran_commander_parser.c
Normal file
651
src/cochran_commander_parser.c
Normal file
@ -0,0 +1,651 @@
|
||||
/*
|
||||
* 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 "parser-private.h"
|
||||
#include "array.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 UNSUPPORTED 0xFFFFFFFF
|
||||
|
||||
typedef enum cochran_sample_format_t {
|
||||
SAMPLE_CMDR,
|
||||
SAMPLE_EMC,
|
||||
} cochran_sample_format_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;
|
||||
unsigned int pt_profile_begin;
|
||||
unsigned int water_conductivity;
|
||||
unsigned int pt_profile_pre;
|
||||
unsigned int start_temp;
|
||||
unsigned int start_depth;
|
||||
unsigned int dive_number;
|
||||
unsigned int altitude;
|
||||
unsigned int pt_profile_end;
|
||||
unsigned int end_temp;
|
||||
unsigned int divetime;
|
||||
unsigned int max_depth;
|
||||
unsigned int avg_depth;
|
||||
unsigned int oxygen;
|
||||
unsigned int helium;
|
||||
unsigned int min_temp;
|
||||
unsigned int max_temp;
|
||||
} cochran_parser_layout_t;
|
||||
|
||||
typedef struct cochran_events_t {
|
||||
unsigned char code;
|
||||
unsigned int data_bytes;
|
||||
parser_sample_event_t type;
|
||||
parser_sample_flags_t flag;
|
||||
} cochran_events_t;
|
||||
|
||||
typedef struct event_size_t {
|
||||
unsigned int code;
|
||||
unsigned int size;
|
||||
} event_size_t;
|
||||
|
||||
typedef struct cochran_commander_parser_t {
|
||||
dc_parser_t base;
|
||||
unsigned int model;
|
||||
const cochran_parser_layout_t *layout;
|
||||
const event_size_t *events;
|
||||
unsigned int nevents;
|
||||
} cochran_commander_parser_t ;
|
||||
|
||||
static dc_status_t cochran_commander_parser_set_data (dc_parser_t *parser, const unsigned char *data, unsigned int size);
|
||||
static dc_status_t cochran_commander_parser_get_datetime (dc_parser_t *parser, dc_datetime_t *datetime);
|
||||
static dc_status_t cochran_commander_parser_get_field (dc_parser_t *parser, dc_field_type_t type, unsigned int flags, void *value);
|
||||
static dc_status_t cochran_commander_parser_samples_foreach (dc_parser_t *parser, dc_sample_callback_t callback, void *userdata);
|
||||
|
||||
static const dc_parser_vtable_t cochran_commander_parser_vtable = {
|
||||
sizeof(cochran_commander_parser_t),
|
||||
DC_FAMILY_COCHRAN_COMMANDER,
|
||||
cochran_commander_parser_set_data, /* set_data */
|
||||
cochran_commander_parser_get_datetime, /* datetime */
|
||||
cochran_commander_parser_get_field, /* fields */
|
||||
cochran_commander_parser_samples_foreach, /* samples_foreach */
|
||||
NULL /* destroy */
|
||||
};
|
||||
|
||||
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
|
||||
6, // pt_profile_begin, 4 bytes
|
||||
24, // water_conductivity, 1 byte, 0=low(fresh), 2=high(sea)
|
||||
30, // pt_profile_pre, 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, // 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_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
|
||||
6, // pt_profile_begin, 4 bytes
|
||||
24, // water_conductivity, 1 byte 0=low(fresh), 2=high(sea)
|
||||
30, // pt_profile_pre, 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, // pt_profile_end, 4 bytes
|
||||
293, // end_temp, 1 byte, F
|
||||
304, // divetime, 2 bytes, minutes
|
||||
306, // max_depth, 2 bytes, /4=ft
|
||||
310, // avg_depth, 2 bytes, /4=ft
|
||||
144, // oxygen, 6 bytes (3 of) 2 bytes, /256=%
|
||||
164, // helium, 6 bytes (3 of) 2 bytes, /256=%
|
||||
403, // min_temp, 1 byte, /2+20=F
|
||||
407, // max_temp, 1 byte, /2+20=F
|
||||
};
|
||||
|
||||
static const 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_NONE, 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_NONE, SAMPLE_FLAGS_BEGIN}, // Entered decompression mode
|
||||
{0xC8, 1, SAMPLE_EVENT_PO2, SAMPLE_FLAGS_BEGIN}, // PO2 too high
|
||||
{0xCC, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_BEGIN}, // Low Cylinder 1 pressure
|
||||
{0xCE, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_BEGIN}, // Non-decompression warning
|
||||
{0xCD, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE}, // Switched to deco blend
|
||||
{0xD0, 1, SAMPLE_EVENT_WORKLOAD, 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_NONE, 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_NONE, SAMPLE_FLAGS_END}, // End non-decompresison warning
|
||||
{0xEF, 1, SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE}, // Switch to blend 2
|
||||
{0xF0, 1, SAMPLE_EVENT_WORKLOAD, 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
|
||||
};
|
||||
|
||||
static const event_size_t cochran_cmdr_event_bytes[] = {
|
||||
{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},
|
||||
};
|
||||
|
||||
static const event_size_t cochran_emc_event_bytes[] = {
|
||||
{0x00, 19}, {0x01, 23}, {0x02, 20},
|
||||
{0x03, 19}, {0x06, 21}, {0x07, 21},
|
||||
{0x0a, 21}, {0x0b, 21}, {0x0f, 19},
|
||||
{0x10, 21},
|
||||
};
|
||||
|
||||
|
||||
static unsigned int
|
||||
cochran_commander_handle_event (cochran_commander_parser_t *parser, unsigned char code, dc_sample_callback_t callback, void *userdata)
|
||||
{
|
||||
dc_parser_t *abstract = (dc_parser_t *) parser;
|
||||
|
||||
const cochran_events_t *event = NULL;
|
||||
for (unsigned int i = 0; i < C_ARRAY_SIZE(cochran_events); ++i) {
|
||||
if (cochran_events[i].code == code) {
|
||||
event = cochran_events + i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (event == NULL) {
|
||||
// Unknown event, send warning so we know we missed something
|
||||
WARNING(abstract->context, "Unknown event 0x%02x", code);
|
||||
return 1;
|
||||
}
|
||||
|
||||
switch (code) {
|
||||
case 0xAB: // Ceiling decrease
|
||||
// Indicated to lower ceiling by 10 ft (deeper)
|
||||
// Bytes 1-2: first stop duration (min)
|
||||
// Bytes 3-4: total stop duration (min)
|
||||
// Handled in calling function
|
||||
break;
|
||||
case 0xAD: // Ceiling increase
|
||||
// Indicates to raise ceiling by 10 ft (shallower)
|
||||
// Handled in calling function
|
||||
break;
|
||||
case 0xC0: // Switched to FO2 21% mode (surface)
|
||||
// Event seen upon surfacing
|
||||
// handled in calling function
|
||||
break;
|
||||
case 0xCD: // Switched to deco blend
|
||||
case 0xEF: // Switched to gas blend 2
|
||||
case 0xF3: // Switched to gas blend 1
|
||||
// handled in calling function
|
||||
break;
|
||||
default:
|
||||
// Don't send known events of type NONE
|
||||
if (event->type != SAMPLE_EVENT_NONE) {
|
||||
dc_sample_value_t sample = {0};
|
||||
sample.event.type = event->type;
|
||||
sample.event.time = 0;
|
||||
sample.event.value = 0;
|
||||
sample.event.flags = event->flag;
|
||||
if (callback) callback (DC_SAMPLE_EVENT, sample, userdata);
|
||||
}
|
||||
}
|
||||
|
||||
return event->data_bytes;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Used to find the end of a dive that has an incomplete dive-end
|
||||
* block. It parses backwards past inter-dive events.
|
||||
*/
|
||||
static int
|
||||
cochran_commander_backparse(cochran_commander_parser_t *parser, const unsigned char *samples, int size)
|
||||
{
|
||||
int result = size, best_result = size;
|
||||
|
||||
for (unsigned int i = 0; i < parser->nevents; i++) {
|
||||
int ptr = size - parser->events[i].size;
|
||||
if (ptr > 0 && samples[ptr] == parser->events[i].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_commander_backparse(parser, samples, ptr);
|
||||
}
|
||||
|
||||
if (result < best_result) {
|
||||
best_result = result;
|
||||
}
|
||||
}
|
||||
|
||||
return best_result;
|
||||
}
|
||||
|
||||
|
||||
dc_status_t
|
||||
cochran_commander_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model)
|
||||
{
|
||||
cochran_commander_parser_t *parser = NULL;
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
|
||||
if (out == NULL)
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
|
||||
// Allocate memory.
|
||||
parser = (cochran_commander_parser_t *) dc_parser_allocate (context, &cochran_commander_parser_vtable);
|
||||
if (parser == NULL) {
|
||||
ERROR (context, "Failed to allocate memory.");
|
||||
return DC_STATUS_NOMEMORY;
|
||||
}
|
||||
|
||||
parser->model = model;
|
||||
|
||||
switch (model) {
|
||||
case COCHRAN_MODEL_COMMANDER_AIR_NITROX:
|
||||
parser->layout = &cochran_cmdr_parser_layout;
|
||||
parser->events = cochran_cmdr_event_bytes;
|
||||
parser->nevents = C_ARRAY_SIZE(cochran_cmdr_event_bytes);
|
||||
break;
|
||||
case COCHRAN_MODEL_EMC_14:
|
||||
case COCHRAN_MODEL_EMC_16:
|
||||
case COCHRAN_MODEL_EMC_20:
|
||||
parser->layout = &cochran_emc_parser_layout;
|
||||
parser->events = cochran_emc_event_bytes;
|
||||
parser->nevents = C_ARRAY_SIZE(cochran_emc_event_bytes);
|
||||
break;
|
||||
default:
|
||||
status = DC_STATUS_UNSUPPORTED;
|
||||
goto error_free;
|
||||
}
|
||||
|
||||
*out = (dc_parser_t *) parser;
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
|
||||
error_free:
|
||||
dc_parser_deallocate ((dc_parser_t *) parser);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
static dc_status_t
|
||||
cochran_commander_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size)
|
||||
{
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static 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 cochran_parser_layout_t *layout = parser->layout;
|
||||
const unsigned char *data = abstract->data;
|
||||
|
||||
if (abstract->size < layout->headersize)
|
||||
return DC_STATUS_DATAFORMAT;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static 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_parser_layout_t *layout = parser->layout;
|
||||
const unsigned char *data = 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 (abstract->size < layout->headersize)
|
||||
return DC_STATUS_DATAFORMAT;
|
||||
|
||||
if (value) {
|
||||
switch (type) {
|
||||
case DC_FIELD_TEMPERATURE_SURFACE:
|
||||
*((unsigned int*) value) = (data[layout->start_temp] - 32.0) / 1.8;
|
||||
break;
|
||||
case DC_FIELD_TEMPERATURE_MINIMUM:
|
||||
if (data[layout->min_temp] == 0xFF)
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
*((unsigned int*) value) = (data[layout->min_temp] / 2.0 + 20 - 32) / 1.8;
|
||||
break;
|
||||
case DC_FIELD_TEMPERATURE_MAXIMUM:
|
||||
if (data[layout->max_temp] == 0xFF)
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
*((unsigned int*) value) = (data[layout->max_temp] / 2.0 + 20 - 32) / 1.8;
|
||||
break;
|
||||
case DC_FIELD_DIVETIME:
|
||||
minutes = array_uint16_le(data + layout->divetime);
|
||||
if (minutes == 0xFFFF)
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
*((unsigned int *) value) = minutes * 60;
|
||||
break;
|
||||
case DC_FIELD_MAXDEPTH:
|
||||
qfeet = array_uint16_le(data + layout->max_depth);
|
||||
if (qfeet == 0xFFFF)
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
*((double *) value) = qfeet / 4.0 * FEET;
|
||||
break;
|
||||
case DC_FIELD_AVGDEPTH:
|
||||
qfeet = array_uint16_le(data + layout->avg_depth);
|
||||
if (qfeet == 0xFFFF)
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
*((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 (data + layout->oxygen + 2 * flags) / 256.0 / 100;
|
||||
if (layout->helium == UNSUPPORTED) {
|
||||
gasmix->helium = 0;
|
||||
} else {
|
||||
gasmix->helium = array_uint16_le (data + layout->helium + 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 ((data[layout->water_conductivity] & 0x3) == 0)
|
||||
water->type = DC_WATER_FRESH;
|
||||
else
|
||||
water->type = DC_WATER_SALT;
|
||||
water->density = 1000.0 + 12.5 * (data[layout->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 * data[layout->altitude] * 250.0 * FEET, 5.25588);
|
||||
break;
|
||||
default:
|
||||
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;
|
||||
const cochran_parser_layout_t *layout = parser->layout;
|
||||
const unsigned char *data = abstract->data;
|
||||
const unsigned char *samples = data + layout->headersize;
|
||||
const unsigned char *last_sample = NULL;
|
||||
|
||||
if (abstract->size < layout->headersize)
|
||||
return DC_STATUS_DATAFORMAT;
|
||||
|
||||
unsigned int size = abstract->size - layout->headersize;
|
||||
|
||||
dc_sample_value_t sample = {0};
|
||||
unsigned int time = 0, last_sample_time = 0;
|
||||
unsigned int offset = 0;
|
||||
double start_depth = 0;
|
||||
int depth = 0;
|
||||
unsigned int deco_obligation = 0;
|
||||
unsigned int deco_ceiling = 0;
|
||||
unsigned int 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(data + layout->pt_profile_end) == 0xFFFFFFFF) {
|
||||
corrupt_dive = 1;
|
||||
|
||||
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]);
|
||||
|
||||
// Eliminate inter-dive events
|
||||
size = cochran_commander_backparse(parser, samples, size);
|
||||
}
|
||||
|
||||
// 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 (data + layout->start_depth) / 4.0;
|
||||
} else {
|
||||
// EMC stores start depth in 256ths of a foot.
|
||||
start_depth = array_uint16_le (data + layout->start_depth) / 256.0;
|
||||
}
|
||||
|
||||
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 = (data[layout->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 (last_sample_time != sample.time) {
|
||||
// We haven't issued this time yet.
|
||||
last_sample_time = sample.time;
|
||||
if (callback) 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) {
|
||||
DEBUG(abstract->context, "Used corrupt dive breakout 1 on event %02x", s[0]);
|
||||
break;
|
||||
}
|
||||
if (time > 1 && (s[0] == 0xE3 || s[0] == 0xF3)) {
|
||||
DEBUG(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(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
|
||||
|
||||
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.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.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;
|
||||
}
|
||||
|
||||
// Make sure we have a full sample
|
||||
if (offset + layout->samplesize > size)
|
||||
break;
|
||||
|
||||
// Depth is logged as change in feet, bit 0x40 means negative depth
|
||||
if (s[0] & 0x40)
|
||||
depth -= (s[0] & 0x3f);
|
||||
else
|
||||
depth += (s[0] & 0x3f);
|
||||
|
||||
sample.depth = (start_depth + depth / 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
|
||||
double ascent_rate = 0.0;
|
||||
if (s[1] & 0x80)
|
||||
ascent_rate = (s[1] & 0x7f);
|
||||
else
|
||||
ascent_rate = -(s[1] & 0x7f);
|
||||
ascent_rate *= FEET / 4.0;
|
||||
} else {
|
||||
// Temperature logged in half degrees F above 20
|
||||
double temperature = s[1] / 2.0 + 20.0;
|
||||
sample.temperature = (temperature - 32.0) / 1.8;
|
||||
|
||||
if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
|
||||
}
|
||||
|
||||
// Cochran EMC models store NDL and deco stop time
|
||||
// in the 20th to 23rd sample
|
||||
if (layout->format == SAMPLE_EMC) {
|
||||
// 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.
|
||||
unsigned int deco_time = 0;
|
||||
switch (time % 24) {
|
||||
case 21:
|
||||
deco_time = last_sample[2] + s[2] * 256 + 1;
|
||||
if (deco_obligation) {
|
||||
/* Deco time for deepest stop, unused */
|
||||
} else {
|
||||
/* Send deco NDL sample */
|
||||
sample.deco.type = DC_DECO_NDL;
|
||||
sample.deco.time = deco_time * 60;
|
||||
sample.deco.depth = 0;
|
||||
if (callback) callback (DC_SAMPLE_DECO, sample, userdata);
|
||||
}
|
||||
break;
|
||||
case 23:
|
||||
/* Deco time, total obligation */
|
||||
deco_time = last_sample[2] + s[2] * 256 + 1;
|
||||
if (deco_obligation) {
|
||||
sample.deco.type = DC_DECO_DECOSTOP;
|
||||
sample.deco.depth = deco_ceiling * FEET;
|
||||
sample.deco.time = deco_time * 60;
|
||||
if (callback) callback (DC_SAMPLE_DECO, sample, userdata);
|
||||
}
|
||||
break;
|
||||
}
|
||||
last_sample = s;
|
||||
}
|
||||
|
||||
time++;
|
||||
offset += layout->samplesize;
|
||||
}
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
@ -263,6 +263,10 @@ static const dc_descriptor_t g_descriptors[] = {
|
||||
{"DiveSystem", "iX3M Deep", DC_FAMILY_DIVESYSTEM_IDIVE, 0x23},
|
||||
{"DiveSystem", "iX3M Tec", DC_FAMILY_DIVESYSTEM_IDIVE, 0x24},
|
||||
{"DiveSystem", "iX3M Reb", DC_FAMILY_DIVESYSTEM_IDIVE, 0x25},
|
||||
{"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},
|
||||
};
|
||||
|
||||
typedef struct dc_descriptor_iterator_t {
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
#include <libdivecomputer/diverite.h>
|
||||
#include <libdivecomputer/citizen.h>
|
||||
#include <libdivecomputer/divesystem.h>
|
||||
#include <libdivecomputer/cochran.h>
|
||||
|
||||
#include "device-private.h"
|
||||
#include "context-private.h"
|
||||
@ -185,6 +186,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr
|
||||
case DC_FAMILY_DIVESYSTEM_IDIVE:
|
||||
rc = divesystem_idive_device_open2 (&device, context, name, dc_descriptor_get_model (descriptor));
|
||||
break;
|
||||
case DC_FAMILY_COCHRAN_COMMANDER:
|
||||
rc = cochran_commander_device_open (&device, context, name);
|
||||
break;
|
||||
default:
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
}
|
||||
|
||||
@ -71,6 +71,7 @@ diverite_nitekq_parser_create
|
||||
citizen_aqualand_parser_create
|
||||
divesystem_idive_parser_create
|
||||
divesystem_idive_parser_create2
|
||||
cochran_commander_parser_create
|
||||
|
||||
dc_device_open
|
||||
dc_device_close
|
||||
@ -180,3 +181,4 @@ diverite_nitekq_extract_dives
|
||||
citizen_aqualand_device_open
|
||||
divesystem_idive_device_open
|
||||
divesystem_idive_device_open2
|
||||
cochran_commander_device_open
|
||||
|
||||
@ -35,6 +35,7 @@
|
||||
#include <libdivecomputer/diverite.h>
|
||||
#include <libdivecomputer/citizen.h>
|
||||
#include <libdivecomputer/divesystem.h>
|
||||
#include <libdivecomputer/cochran.h>
|
||||
|
||||
#include "context-private.h"
|
||||
#include "parser-private.h"
|
||||
@ -144,6 +145,9 @@ dc_parser_new (dc_parser_t **out, dc_device_t *device)
|
||||
case DC_FAMILY_DIVESYSTEM_IDIVE:
|
||||
rc = divesystem_idive_parser_create2 (&parser, context, device->devinfo.model);
|
||||
break;
|
||||
case DC_FAMILY_COCHRAN_COMMANDER:
|
||||
rc = cochran_commander_parser_create (&parser, context, device->devinfo.model);
|
||||
break;
|
||||
default:
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user