summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar John Van Ostrand <john@vanostrand.com>2014-10-17 21:03:37 -0400
committerGravatar Dirk Hohndel <dirk@hohndel.org>2014-10-27 15:21:19 -0700
commit49401eec0bc83bc852114eac0a6ec828016e9ae8 (patch)
tree8f3a47568692791ce2e1bd987c6ffea9d1a907db
parentb5dedcfba41cf5f3d4d62de37aa83470ce8ccba3 (diff)
downloadsubsurface-49401eec0bc83bc852114eac0a6ec828016e9ae8.tar.gz
Finished Cochran dive log import
I fixed up the decode and finished the parse for Cochran EMC, Commander and Gemini computers. I suspect that this code may only work with files from certain versions of Cochran Analyst. It works with my own CAN files and with the samples that came with Analyst v4.01v. A seemingly arbitrary offset of 0x4914 is needed to access data. The previous code uses 0x4a14 and 0x4b14. I suspect these are from different version of Analyst. [Dirk Hohndel: whitespace cleanup, add files to subsurface.pro, made sure this compiles without the corresponding patch to libdivecomputer (that isn't upstream, yet), cleaned up the usage of structs, removed a few unused variables] Signed-off-by: John Van Ostrand <john@vanostrand.com> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
-rw-r--r--CMakeLists.txt1
-rw-r--r--cochran.c793
-rw-r--r--cochran_cmdr.h82
-rw-r--r--cochran_emc.h171
-rw-r--r--file.c4
-rw-r--r--file.h4
-rw-r--r--qt-ui/mainwindow.cpp2
-rw-r--r--subsurface.pro3
8 files changed, 932 insertions, 128 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3298981e5..a04794a26 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -77,6 +77,7 @@ ADD_CUSTOM_TARGET(version ALL COMMAND
# compile the core library, in C.
SET(SUBSURFACE_CORE_LIB_SRCS
+ cochran.c
deco.c
device.c
dive.c
diff --git a/cochran.c b/cochran.c
index 018ca673a..b309ecb10 100644
--- a/cochran.c
+++ b/cochran.c
@@ -1,3 +1,4 @@
+#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
@@ -7,8 +8,48 @@
#include "dive.h"
#include "file.h"
-
-#define DON
+#include "units.h"
+#include "gettext.h"
+#include "cochran_emc.h"
+#include "cochran_cmdr.h"
+#include "divelist.h"
+
+#include <libdivecomputer/parser.h>
+
+#define POUND 0.45359237
+#define FEET 0.3048
+#define INCH 0.0254
+#define GRAVITY 9.80665
+#define ATM 101325.0
+#define BAR 100000.0
+#define FSW (ATM / 33.0)
+#define MSW (BAR / 10.0)
+#define PSI ((POUND * GRAVITY) / (INCH * INCH))
+
+// Some say 0x4a14 and 0x4b14 are the right number for this offset
+// This works with CAN files from Analyst 4.01v and computers
+// such as Commander, Gemini, EMC-16, and EMC-20H
+#define LOG_ENTRY_OFFSET 0x4914
+
+enum cochran_type {
+ TYPE_GEMINI,
+ TYPE_COMMANDER,
+ TYPE_EMC
+};
+
+struct config {
+ enum cochran_type type;
+ unsigned int logbook_size;
+ unsigned int sample_size;
+} config;
+
+
+// Convert 4 bytes into an INT
+#define array_uint16_le(p) ( (unsigned int) (p)[0] \
+ + ((p)[1]<<8) )
+#define array_uint32_le(p) ( (unsigned int) (p)[0] \
+ + ((p)[1]<<8) + ((p)[2]<<16) \
+ + ((p)[3]<<24) )
/*
* The Cochran file format is designed to be annoying to read. It's roughly:
@@ -20,18 +61,26 @@
* 0x3ffff: 0 (high 32 bits of filesize? Bogus: the offsets into the file
* are 32-bit, so it can't be a large file anyway)
*
- * 0x40000: "block 0": the decoding block. The first byte is some random
- * value (0x46 in the files I have access to), the next 200+ bytes or so
- * are the "scrambling array" that needs to be added into the file
- * contents to make sense of them.
+ * 0x40000: byte 0x46
+ * 0x40001: "block 0": 256 byte encryption key
+ * 0x40101: the random modulus, or length of the key to use
+ * 0x40102: block 1: Version and date of Analyst and a feature string identifying
+ * the computer features and the features of the file
+ * 0x40138: Computer configuration page 1, 512 bytes
+ * 0x40338: Computer configuration page 2, 512 bytes
+ * 0x40538: Misc data (tissues) 1500 bytes
+ * 0x40b14: Ownership data 512 bytes ???
+ *
+ * 0x4171c: Ownership data 512 bytes ??? <copy>
+ *
+ * 0x45415: Time stamp 17 bytes
+ * 0x45426: Computer configuration page 1, 512 bytes <copy>
+ * 0x45626: Computer configuration page 2, 512 bytes <copy>
*
- * The descrambling array seems to be of some random size which is likely
- * determinable from the array somehow, the two test files I have it as
- * 230 bytes and 234 bytes respectively.
*/
static unsigned int partial_decode(unsigned int start, unsigned int end,
- const unsigned char *decode, unsigned offset, unsigned mod,
- const unsigned char *buf, unsigned int size, unsigned char *dst)
+ const unsigned char *decode, unsigned offset, unsigned mod,
+ const unsigned char *buf, unsigned int size, unsigned char *dst)
{
unsigned i, sum = 0;
@@ -49,39 +98,12 @@ static unsigned int partial_decode(unsigned int start, unsigned int end,
return sum;
}
-/*
- * The decode buffer size can be figured out by simply trying our the
- * decode: we expect that the scrambled contents are largely random, and
- * thus tend to have half the bits set. Summing over the bytes is going
- * to give an average of 0x80 per byte.
- *
- * The decoded array is mostly full of zeroes, so the sum is lower.
- *
- * Works for me.
- */
-static int figure_out_modulus(const unsigned char *decode, const unsigned char *dive, unsigned int size)
-{
- int mod, best = -1;
- unsigned int min = ~0u;
-
- if (size < 0x1000)
- return best;
-
- for (mod = 50; mod < 300; mod++) {
- unsigned int sum;
-
- sum = partial_decode(0, 0x0fff, decode, 1, mod, dive, size, NULL);
- if (sum < min) {
- min = sum;
- best = mod;
- }
- }
- return best;
-}
+#ifdef COCHRAN_DEBUG
#define hexchar(n) ("0123456789abcdef"[(n) & 15])
-static int show_line(unsigned offset, const unsigned char *data, unsigned size, int show_empty)
+static int show_line(unsigned offset, const unsigned char *data,
+ unsigned size, int show_empty)
{
unsigned char bits;
int i, off;
@@ -116,108 +138,503 @@ static int show_line(unsigned offset, const unsigned char *data, unsigned size,
return 0;
}
-static void cochran_debug_write(const char *filename, const unsigned char *data, unsigned size)
+static void cochran_debug_write(const unsigned char *data, unsigned size)
{
- int i, show = 1;
+ return;
+
+ int show = 1, i;
+
for (i = 0; i < size; i += 16)
show = show_line(i, data + i, size - i, show);
}
-static void parse_cochran_header(const char *filename,
+static void cochran_debug_sample(const char * s, unsigned int seconds)
+{
+ switch (config.type)
+ {
+ case TYPE_GEMINI:
+ switch (seconds % 4)
+ {
+ case 0:
+ printf("Hex: %02x %02x ", s[0], s[1]);
+ break;
+ case 1:
+ printf("Hex: %02x %02x ", s[0], s[1]);
+ break;
+ case 2:
+ printf("Hex: %02x %02x ", s[0], s[1]);
+ break;
+ case 3:
+ printf("Hex: %02x %02x ", s[0], s[1]);
+ break;
+ }
+ break;
+ case TYPE_COMMANDER:
+ switch (seconds % 2)
+ {
+ case 0:
+ printf("Hex: %02x %02x ", s[0], s[1]);
+ break;
+ case 1:
+ printf("Hex: %02x %02x ", s[0], s[1]);
+ break;
+ }
+ break;
+ case TYPE_EMC:
+ switch (seconds % 2)
+ {
+ case 0:
+ printf("Hex: %02x %02x %02x ", s[0], s[1], s[2]);
+ break;
+ case 1:
+ printf("Hex: %02x %02x %02x ", s[0], s[1], s[2]);
+ break;
+ }
+ break;
+ }
+
+ printf ("%02dh %02dm %02ds: Depth: %-5.2f, ", seconds / 3660,
+ (seconds % 3660) / 60, seconds % 60, depth);
+}
+
+#endif // COCHRAN_DEBUG
+
+static void cochran_parse_header(
const unsigned char *decode, unsigned mod,
const unsigned char *in, unsigned size)
{
- char *buf = malloc(size);
+ unsigned char *buf = malloc(size);
/* Do the "null decode" using a one-byte decode array of '\0' */
- partial_decode(0, 0x0b14, "", 0, 1, in, size, buf);
+ /* Copies in plaintext, will be overwritten later */
+ partial_decode(0, 0x0102, "", 0, 1, in, size, buf);
/*
* The header scrambling is different form the dive
* scrambling. Oh yay!
*/
+ partial_decode(0x0102, 0x010e, decode, 0, mod, in, size, buf);
partial_decode(0x010e, 0x0b14, decode, 0, mod, in, size, buf);
partial_decode(0x0b14, 0x1b14, decode, 0, mod, in, size, buf);
partial_decode(0x1b14, 0x2b14, decode, 0, mod, in, size, buf);
partial_decode(0x2b14, 0x3b14, decode, 0, mod, in, size, buf);
partial_decode(0x3b14, 0x5414, decode, 0, mod, in, size, buf);
- partial_decode(0x5414, size, decode, 0, mod, in, size, buf);
+ partial_decode(0x5414, size, decode, 0, mod, in, size, buf);
+
+ // Detect log type
+ switch (buf[0x133])
+ {
+ case '2': // Cochran Commander, version II log format
+ config.logbook_size = 256;
+ if (buf[0x132] == 0x10) {
+ config.type = TYPE_GEMINI;
+ config.sample_size = 2; // Gemini with tank PSI samples
+ } else {
+ config.type = TYPE_COMMANDER;
+ config.sample_size = 2; // Commander
+ }
+ break;
+ case '3': // Cochran EMC, version III log format
+ config.type = TYPE_EMC;
+ config.logbook_size = 512;
+ config.sample_size = 3;
+ break;
+ default:
+ printf ("Unknown log format v%c\n", buf[0x137]);
+ exit(1);
+ break;
+ }
- printf("\n%s, header\n\n", filename);
- cochran_debug_write(filename, buf, size);
+#ifdef COCHRAN_DEBUG
+ puts("Header\n======\n\n");
+ cochran_debug_write(buf, size);
+#endif
free(buf);
}
+
/*
- * Cochran export files show that depths seem to be in
- * quarter feet (rounded up to tenths).
- *
- * Temperature seems to be exported in Fahrenheit.
- *
- * Cylinder pressure seems to be in multiples of 4 psi.
- *
- * The data seems to be some byte-stream where the pattern
- * appears to be that the two high bits indicate type of
- * data.
- *
- * For '00', the low six bits seem to be positive
- * values with a distribution towards zero, probably depth
- * deltas. '0 0' exists, but is very rare ("surface"?). 63
- * exists, but is rare.
- *
- * For '01', the low six bits seem to be a signed binary value,
- * with the most common being 0, and 1 and -1 (63) being the
- * next most common values.
- *
- * NOTE! Don's CAN data is different. It shows the reverse pattern
- * for 00 and 01 above: 00 looks like signed data, with 01 looking
- * like unsigned data.
- *
- * For '10', there seems to be another positive value distribution,
- * but unlike '00' the value 0 is common, and I see examples of 63
- * too ("overflow"?) and a spike at '7'.
- *
- * Again, Don's data is different.
- *
- * The values for '11' seem to be some exception case. Possibly
- * overflow handling, possibly warning events. It doesn't have
- * any clear distribution: values 0, 1, 16, 33, 35, 48, 51, 55
- * and 63 are common.
- *
- * For David and Don's data, '01' is the most common, with '00'
- * and '10' not uncommon. '11' is two orders of magnitude less
- * common.
- *
- * For Alex, '00' is the most common, with 01 about a third as
- * common, and 02 a third of that. 11 is least common.
- *
- * There clearly are variations in the format here. And Alex has
- * a different data offset than Don/David too (see the #ifdef DON).
- * Christ. Maybe I've misread the patterns entirely.
- */
-static void cochran_profile_write(const unsigned char *buf, int size)
+* Bytes expected after a pre-dive event code
+*/
+
+static int cochran_predive_event_bytes (unsigned char code)
{
- int i;
- for (i = 0; i < size; i++) {
- unsigned char c = buf[i];
- printf("%d %d\n",
- c >> 6, c & 0x3f);
+ int x = 0;
+
+ int gem_event_bytes[15][2] = { {0x00, 10}, {0x02, 17}, {0x08, 18},
+ {0x09, 18}, {0x0c, 18}, {0x0d, 18},
+ {0x0e, 18},
+ { -1, 0} };
+ int cmdr_event_bytes[15][2] = { {0x00, 16}, {0x01, 20}, {0x02, 17},
+ {0x03, 16}, {0x06, 18}, {0x07, 18},
+ {0x08, 18}, {0x09, 18}, {0x0a, 18},
+ {0x0b, 20}, {0x0c, 18}, {0x0d, 18},
+ {0x0e, 18}, {0x10, 20},
+ { -1, 0} };
+ int emc_event_bytes[15][2] = { {0x00, 18}, {0x01, 22}, {0x02, 19},
+ {0x03, 18}, {0x06, 20}, {0x07, 20},
+ {0x0a, 20}, {0x0b, 20}, {0x0f, 18},
+ {0x10, 20},
+ { -1, 0} };
+
+ switch (config.type)
+ {
+ case TYPE_GEMINI:
+ while (gem_event_bytes[x][0] != code && gem_event_bytes[x][0] != -1)
+ x++;
+
+ return gem_event_bytes[x][1];
+ break;
+ case TYPE_COMMANDER:
+ while (cmdr_event_bytes[x][0] != code && cmdr_event_bytes[x][0] != -1)
+ x++;
+
+ return cmdr_event_bytes[x][1];
+ break;
+ case TYPE_EMC:
+ while (emc_event_bytes[x][0] != code && emc_event_bytes[x][0] != -1)
+ x++;
+
+ return emc_event_bytes[x][1];
+ break;
}
+
}
-static void parse_cochran_dive(const char *filename, int dive,
- const unsigned char *decode, unsigned mod,
- const unsigned char *in, unsigned size)
+int cochran_dive_event_bytes(unsigned char event)
{
- char *buf = malloc(size);
-#ifdef DON
- unsigned int offset = 0x4a14;
-#else
- unsigned int offset = 0x4b14;
+ if (event == 0xAD || event == 0xAB)
+ return 4;
+ else
+ return 0;
+}
+
+static void cochran_dive_event(struct divecomputer *dc, const unsigned char *s,
+ unsigned int seconds, unsigned int *in_deco,
+ unsigned int *deco_ceiling, unsigned int *deco_time)
+{
+ switch (s[0])
+ {
+ case 0xC5: // Deco obligation begins
+ *in_deco = 1;
+ add_event(dc, seconds, SAMPLE_EVENT_DECOSTOP,
+ SAMPLE_FLAGS_BEGIN, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "deco stop"));
+ break;
+ case 0xDB: // Deco obligation ends
+ *in_deco = 0;
+ add_event(dc, seconds, SAMPLE_EVENT_DECOSTOP,
+ SAMPLE_FLAGS_END, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "deco stop"));
+ break;
+ case 0xAD: // Raise deco ceiling 10 ft
+ *deco_ceiling -= 10; // ft
+ *deco_time = (array_uint16_le(s + 3) + 1) * 60;
+ break;
+ case 0xAB: // Lower deco ceiling 10 ft
+ *deco_ceiling += 10; // ft
+ *deco_time = (array_uint16_le(s + 3) + 1) * 60;
+ break;
+ case 0xA8: // Entered Post Dive interval mode (surfaced)
+ break;
+ case 0xA9: // Exited PDI mode (re-submierged)
+ break;
+ case 0xBD: // Switched to normal PO2 setting
+ break;
+ case 0xC0: // Switched to FO2 21% mode (generally upon surface)
+ break;
+ case 0xC1: // "Ascent rate alarm
+ add_event(dc, seconds, SAMPLE_EVENT_ASCENT,
+ SAMPLE_FLAGS_BEGIN, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "ascent"));
+ break;
+ case 0xC2: // Low battery warning
+#ifdef SAMPLE_EVENT_BATTERY
+ add_event(dc, seconds, SAMPLE_EVENT_BATTERY,
+ SAMPLE_FLAGS_NONE, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "battery"));
#endif
+ break;
+ case 0xC3: // CNS warning
+ add_event(dc, seconds, SAMPLE_EVENT_OLF,
+ SAMPLE_FLAGS_BEGIN, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "OLF"));
+ break;
+ case 0xC4: // Depth alarm begin
+ add_event(dc, seconds, SAMPLE_EVENT_MAXDEPTH,
+ SAMPLE_FLAGS_BEGIN, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "maxdepth"));
+ break;
+ case 0xC8: // PPO2 alarm begin
+ add_event(dc, seconds, SAMPLE_EVENT_PO2,
+ SAMPLE_FLAGS_BEGIN, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "PO2"));
+ break;
+ case 0xCC: // Low cylinder 1 pressure";
+ break;
+ case 0xCD: // Switch to deco blend setting
+ add_event(dc, seconds, SAMPLE_EVENT_GASCHANGE,
+ SAMPLE_FLAGS_NONE, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "gaschange"));
+ break;
+ case 0xCE: // NDL alarm begin
+ add_event(dc, seconds, SAMPLE_EVENT_RBT,
+ SAMPLE_FLAGS_BEGIN, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "rbt"));
+ break;
+ case 0xD0: // Breathing rate alarm begin
+ break;
+ case 0xD3: // Low gas 1 flow rate alarm begin";
+ break;
+ case 0xD6: // Ceiling alarm begin
+ add_event(dc, seconds, SAMPLE_EVENT_CEILING,
+ SAMPLE_FLAGS_BEGIN, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "ceiling"));
+ break;
+ case 0xD8: // End decompression mode
+ *in_deco = 0;
+ add_event(dc, seconds, SAMPLE_EVENT_DECOSTOP,
+ SAMPLE_FLAGS_END, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "deco stop"));
+ break;
+ case 0xE1: // Ascent alarm end
+ add_event(dc, seconds, SAMPLE_EVENT_ASCENT,
+ SAMPLE_FLAGS_END, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "ascent"));
+ break;
+ case 0xE2: // Low transmitter battery alarm
+ add_event(dc, seconds, SAMPLE_EVENT_TRANSMITTER,
+ SAMPLE_FLAGS_BEGIN, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "transmitter"));
+ break;
+ case 0xE3: // Switch to FO2 mode
+ break;
+ case 0xE5: // Switched to PO2 mode
+ break;
+ case 0xE8: // PO2 too low alarm
+ add_event(dc, seconds, SAMPLE_EVENT_PO2,
+ SAMPLE_FLAGS_BEGIN, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "PO2"));
+ break;
+ case 0xEE: // NDL alarm end
+ add_event(dc, seconds, SAMPLE_EVENT_RBT,
+ SAMPLE_FLAGS_END, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "rbt"));
+ break;
+ case 0xEF: // Switch to blend 2
+ add_event(dc, seconds, SAMPLE_EVENT_GASCHANGE,
+ SAMPLE_FLAGS_NONE, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "gaschange"));
+ break;
+ case 0xF0: // Breathing rate alarm end
+ break;
+ case 0xF3: // Switch to blend 1 (often at dive start)
+ add_event(dc, seconds, SAMPLE_EVENT_GASCHANGE,
+ SAMPLE_FLAGS_NONE, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "gaschange"));
+ break;
+ case 0xF6: // Ceiling alarm end
+ add_event(dc, seconds, SAMPLE_EVENT_CEILING,
+ SAMPLE_FLAGS_END, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "ceiling"));
+ break;
+ default:
+ break;
+ }
+}
+
+
+/*
+* Parse sample data, extract events and build a dive
+*/
+
+static void cochran_parse_samples(struct dive *dive, const unsigned char *log,
+ const unsigned char *samples, int size,
+ unsigned int *duration, double *max_depth,
+ double *avg_depth, double *min_temp)
+{
+ const unsigned char *s;
+ unsigned int offset = 0, seconds = 0;
+ double depth = 0, temp = 0, depth_sample = 0, psi = 0, sgc_rate = 0;
+ int ascent_rate = 0;
+ unsigned int ndl = 0;
+ unsigned int in_deco = 0, deco_ceiling = 0, deco_time = 0;
+
+ struct divecomputer *dc = &dive->dc;
+ struct sample *sample;
+
+ const struct cochran_cmdr_log_t *log_cmdr = (struct cochran_cmdr_log_t *) log;
+ const struct cochran_emc_log_t *log_emc = (struct cochran_emc_log_t *) log;
+
+ // Initialize stat variables
+ *max_depth = 0, *avg_depth = 0, *min_temp = 0xFF;
+
+ // Get starting depth and temp (tank PSI???)
+ switch (config.type)
+ {
+ case TYPE_GEMINI:
+ depth = (float) (log_cmdr->start_depth[0]
+ + log_cmdr->start_depth[1] * 256) / 4;
+ psi = log_cmdr->start_psi[0] + log_cmdr->start_psi[1] * 256;
+ sgc_rate = (float) (log_cmdr->start_sgc[0]
+ + log_cmdr->start_sgc[1] * 256) / 2;
+ break;
+
+ case TYPE_COMMANDER:
+ depth = (float) (log_cmdr->start_depth[0]
+ + log_cmdr->start_depth[1] * 256) / 4;
+ break;
+
+ case TYPE_EMC:
+ depth = (float) log_emc->start_depth[0] / 256
+ + log_emc->start_depth[1];
+ temp = log_emc->start_temperature;
+ break;
+ }
+
+ // Skip past pre-dive events
+ unsigned int x = 0;
+ if (samples[x] != 0x40) {
+ unsigned int c;
+ while ( (samples[x] & 0x80) == 0 && samples[x] != 0x40 && x < size) {
+ c = cochran_predive_event_bytes(samples[x]) + 1;
+#ifdef COCHRAN_DEBUG
+ printf ("Predive event: ", samples[x]);
+ for (int y = 0; y < c; y++) printf ("%02x ", samples[x + y]);
+ putchar('\n');
+#endif
+ x += c;
+ }
+ }
+
+ // Now process samples
+ offset = x;
+ while (offset < size) {
+ s = samples + offset;
+
+ // Start with an empty sample
+ sample = prepare_sample(dc);
+ sample->time.seconds = seconds;
+
+ // Check for event
+ if (s[0] & 0x80) {
+ cochran_dive_event(dc, s, seconds, &in_deco, &deco_ceiling, &deco_time);
+ offset += cochran_dive_event_bytes(s[0]) + 1;
+ continue;
+ }
+
+ // Depth is in every sample
+ depth_sample = (float) (s[0] & 0x3F) / 4 * (s[0] & 0x40 ? -1 : 1);
+ depth += depth_sample;
+
+#ifdef COCHRAN_DEBUG
+ cochran_debug_sample(s, seconds);
+#endif
+
+ switch (config.type)
+ {
+ case TYPE_COMMANDER:
+ switch (seconds % 2)
+ {
+ case 0: // Ascent rate
+ ascent_rate = (s[1] & 0x7f) * (s[1] & 0x80 ? 1: -1);
+ break;
+ case 1: // Temperature
+ temp = s[1] / 2 + 20;
+ break;
+ }
+ break;
+ case TYPE_GEMINI:
+ // Gemini with tank pressure and SAC rate.
+ switch (seconds % 4)
+ {
+ case 0: // Ascent rate
+ ascent_rate = (s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1);
+ break;
+ case 2: // PSI change
+ psi -= (float) (s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1) / 4;
+ break;
+ case 1: // SGC rate
+ sgc_rate -= (float) (s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1) / 2;
+ break;
+ case 3: // Temperature
+ temp = (float) s[1] / 2 + 20;
+ break;
+ }
+ break;
+ case TYPE_EMC:
+ switch (seconds % 2)
+ {
+ case 0: // Ascent rate
+ ascent_rate = (s[1] & 0x7f) * (s[1] & 0x80 ? 1: -1);
+ break;
+ case 1: // Temperature
+ temp = (float) s[1] / 2 + 20;
+ break;
+ }
+ // Get NDL and deco information
+ switch (seconds % 24)
+ {
+ case 20:
+ if (in_deco) {
+ // Fist stop time
+ //first_deco_time = (s[2] + s[5] * 256 + 1) * 60; // seconds
+ ndl = 0;
+ } else {
+ // NDL
+ ndl = (s[2] + s[5] * 256 + 1) * 60; // seconds
+ deco_time = 0;
+ }
+ break;
+ case 22:
+ if (in_deco) {
+ // Total stop time
+ deco_time = (s[2] + s[5] * 256 + 1) * 60; // seconds
+ ndl = 0;
+ }
+ break;
+ }
+ }
+
+ // Track dive stats
+ if (depth > *max_depth) *max_depth = depth;
+ if (temp < *min_temp) *min_temp = temp;
+ *avg_depth = (*avg_depth * seconds + depth) / (seconds + 1);
+
+ sample->depth.mm = depth * FEET * 1000;
+ sample->ndl.seconds = ndl;
+ sample->in_deco = in_deco;
+ sample->stoptime.seconds = deco_time;
+ sample->stopdepth.mm = deco_ceiling * FEET * 1000;
+ sample->temperature.mkelvin = C_to_mkelvin((temp - 32) / 1.8);
+ sample->sensor = 0;
+ sample->cylinderpressure.mbar = psi * PSI / 100;
+
+ finish_sample(dc);
+
+ offset += config.sample_size;
+ seconds++;
+ }
+
+ if (seconds > 0)
+ *duration = seconds - 1;
+}
+
+
+static void cochran_parse_dive(const unsigned char *decode, unsigned mod,
+ const unsigned char *in, unsigned size)
+{
+ unsigned char *buf = malloc(size);
+ struct dive *dive;
+ struct divecomputer *dc;
+ struct tm tm = {0};
+ uint32_t csum[5];
+
+ double max_depth, avg_depth, min_temp;
+ unsigned int duration = 0, corrupt_dive = 0;
/*
* The scrambling has odd boundaries. I think the boundaries
@@ -240,17 +657,148 @@ static void parse_cochran_dive(const char *filename, int dive,
* scrambled, but there seems to be size differences in the data,
* so this just descrambles part of it:
*/
- partial_decode(0x48ff, offset, decode, 0, mod, in, size, buf);
- partial_decode(offset, size, decode, 0, mod, in, size, buf);
+ // Decode log entry (512 bytes + random prefix)
+ partial_decode(0x48ff, 0x4914 + config.logbook_size, decode,
+ 0, mod, in, size, buf);
+
+ unsigned int sample_size = size - 0x4914 - config.logbook_size;
+ int g;
- printf("\n%s, dive %d\n\n", filename, dive);
- cochran_debug_write(filename, buf, size);
- cochran_profile_write(buf + offset, size - offset);
+ // Decode sample data
+ partial_decode(0x4914 + config.logbook_size, size, decode,
+ 0, mod, in, size, buf);
+
+#ifdef COCHRAN_DEBUG
+ // Display pre-logbook data
+ puts("\nPre Logbook Data\n");
+ cochran_debug_write(buf, 0x4914);
+
+ // Display log book
+ puts("\nLogbook Data\n");
+ cochran_debug_write(buf + 0x4914, config.logbook_size + 0x400);
+
+ // Display sample data
+ puts("\nSample Data\n");
+#endif
+
+ dive = alloc_dive();
+ dc = &dive->dc;
+
+ struct cochran_cmdr_log_t *cmdr_log = (struct cochran_cmdr_log_t *) (buf + 0x4914);
+ struct cochran_emc_log_t *emc_log = (struct cochran_emc_log_t *) (buf + 0x4914);
+
+ switch (config.type)
+ {
+ case TYPE_GEMINI:
+ case TYPE_COMMANDER:
+ if (config.type == TYPE_GEMINI) {
+ dc->model = "Gemini";
+ dc->deviceid = buf[0x18c] * 256 + buf[0x18d]; // serial no
+ fill_default_cylinder(&dive->cylinder[0]);
+ dive->cylinder[0].gasmix.o2.permille = (cmdr_log->o2_percent[0][0] / 256
+ + cmdr_log->o2_percent[0][1]) * 10;
+ dive->cylinder[0].gasmix.he.permille = 0;
+ } else {
+ dc->model = "Commander";
+ dc->deviceid = array_uint32_le(buf + 0x31e); // serial no
+ for (g = 0; g < 2; g++) {
+ fill_default_cylinder(&dive->cylinder[g]);
+ dive->cylinder[g].gasmix.o2.permille = (cmdr_log->o2_percent[g][0] / 256
+ + cmdr_log->o2_percent[g][1]) * 10;
+ dive->cylinder[g].gasmix.he.permille = 0;
+ }
+ }
+
+ tm.tm_year = cmdr_log->year;
+ tm.tm_mon = cmdr_log->month - 1;
+ tm.tm_mday = cmdr_log->day;
+ tm.tm_hour = cmdr_log->hour;
+ tm.tm_min = cmdr_log->minutes;
+ tm.tm_sec = cmdr_log->seconds;
+ tm.tm_isdst = -1;
+
+ dive->when = dc->when = utc_mktime(&tm);
+ dive->number = cmdr_log->number[0] + cmdr_log->number[1] * 256 + 1;
+ dc->duration.seconds = (cmdr_log->bt[0] + cmdr_log->bt[1] * 256) * 60;
+ dc->surfacetime.seconds = (cmdr_log->sit[0] + cmdr_log->sit[1] * 256) * 60;
+ dc->maxdepth.mm = (cmdr_log->max_depth[0] +
+ cmdr_log->max_depth[1] * 256) / 4 * FEET * 1000;
+ dc->meandepth.mm = (cmdr_log->avg_depth[0] +
+ cmdr_log->avg_depth[1] * 256) / 4 * FEET * 1000;
+ dc->watertemp.mkelvin = C_to_mkelvin((cmdr_log->temp / 32) - 1.8);
+ dc->surface_pressure.mbar = ATM / BAR * pow(1 - 0.0000225577
+ * (double) cmdr_log->altitude * 250 * FEET, 5.25588) * 1000;
+ dc->salinity = 10000 + 150 * emc_log->water_conductivity;
+
+ SHA1(cmdr_log->number, 2, (unsigned char *)csum);
+ dc->diveid = csum[0];
+
+ if (cmdr_log->max_depth[0] == 0xff && cmdr_log->max_depth[1] == 0xff)
+ corrupt_dive = 1;
+
+ break;
+ case TYPE_EMC:
+ dc->model = "EMC";
+ dc->deviceid = array_uint32_le(buf + 0x31e); // serial no
+ for (g = 0; g < 4; g++) {
+ fill_default_cylinder(&dive->cylinder[g]);
+ dive->cylinder[g].gasmix.o2.permille = (emc_log->o2_percent[g][0] / 256
+ + emc_log->o2_percent[g][1]) * 10;
+ dive->cylinder[g].gasmix.he.permille = (emc_log->he_percent[g][0] / 256
+ + emc_log->he_percent[g][1]) * 10;
+ }
+
+ tm.tm_year = emc_log->year;
+ tm.tm_mon = emc_log->month - 1;
+ tm.tm_mday = emc_log->day;
+ tm.tm_hour = emc_log->hour;
+ tm.tm_min = emc_log->minutes;
+ tm.tm_sec = emc_log->seconds;
+ tm.tm_isdst = -1;
+
+
+ dive->when = dc->when = utc_mktime(&tm);
+ dive->number = emc_log->number[0] + emc_log->number[1] * 256 + 1;
+ dc->duration.seconds = (emc_log->bt[0] + emc_log->bt[1] * 256) * 60;
+ dc->surfacetime.seconds = (emc_log->sit[0] + emc_log->sit[1] * 256) * 60;
+ dc->maxdepth.mm = (emc_log->max_depth[0] +
+ emc_log->max_depth[1] * 256) / 4 * FEET * 1000;
+ dc->meandepth.mm = (emc_log->avg_depth[0] +
+ emc_log->avg_depth[1] * 256) / 4 * FEET * 1000;
+ dc->watertemp.mkelvin = C_to_mkelvin((emc_log->temp - 32) / 1.8);
+ dc->surface_pressure.mbar = ATM / BAR * pow(1 - 0.0000225577
+ * (double) emc_log->altitude * 250 * FEET, 5.25588) * 1000;
+ dc->salinity = 10000 + 150 * emc_log->water_conductivity;
+
+ SHA1(emc_log->number, 2, (unsigned char *)csum);
+ dc->diveid = csum[0];
+
+ if (emc_log->max_depth[0] == 0xff && emc_log->max_depth[1] == 0xff)
+ corrupt_dive = 1;
+
+ break;
+ }
+
+ cochran_parse_samples(dive, buf + 0x4914, buf + 0x4914
+ + config.logbook_size, sample_size,
+ &duration, &max_depth, &avg_depth, &min_temp);
+
+ // Check for corrupt dive
+ if (corrupt_dive) {
+ dc->maxdepth.mm = max_depth * FEET * 1000;
+ dc->meandepth.mm = avg_depth * FEET * 1000;
+ dc->watertemp.mkelvin = C_to_mkelvin((min_temp - 32) / 1.8);
+ dc->duration.seconds = duration;
+ }
+
+ dive->downloaded = true;
+ record_dive(dive);
+ mark_divelist_changed(true);
free(buf);
}
-int try_to_open_cochran(const char *filename, struct memblock *mem, GError **error)
+int try_to_open_cochran(const char *filename, struct memblock *mem)
{
unsigned int i;
unsigned int mod;
@@ -259,16 +807,19 @@ int try_to_open_cochran(const char *filename, struct memblock *mem, GError **err
if (mem->size < 0x40000)
return 0;
- offsets = mem->buffer;
+
+ offsets = (int *) mem->buffer;
dive1 = offsets[0];
dive2 = offsets[1];
+
if (dive1 < 0x40000 || dive2 < dive1 || dive2 > mem->size)
return 0;
- mod = figure_out_modulus(decode, mem->buffer + dive1, dive2 - dive1);
+ mod = decode[0x100] + 1;
- parse_cochran_header(filename, decode, mod, mem->buffer + 0x40000, dive1 - 0x40000);
+ cochran_parse_header(decode, mod, mem->buffer + 0x40000, dive1 - 0x40000);
+ // Decode each dive
for (i = 0; i < 65534; i++) {
dive1 = offsets[i];
dive2 = offsets[i + 1];
@@ -276,8 +827,10 @@ int try_to_open_cochran(const char *filename, struct memblock *mem, GError **err
break;
if (dive2 > mem->size)
break;
- parse_cochran_dive(filename, i + 1, decode, mod, mem->buffer + dive1, dive2 - dive1);
+
+ cochran_parse_dive(decode, mod, mem->buffer + dive1,
+ dive2 - dive1);
}
- exit(0);
+ return 1; // no further processing needed
}
diff --git a/cochran_cmdr.h b/cochran_cmdr.h
new file mode 100644
index 000000000..1c5f938d5
--- /dev/null
+++ b/cochran_cmdr.h
@@ -0,0 +1,82 @@
+/*
+ * subsurface
+ *
+ * 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
+ */
+
+
+struct cochran_cmdr_log_t {
+ // Pre-dive 128 bytes
+ unsigned char minutes, seconds; // 2 bytes
+ unsigned char day, hour, year, month; // 4 bytes
+ unsigned char sample_start_offset[4]; // 4 bytes
+ unsigned char start_timestamp[4]; // 4 bytes
+ unsigned char pre_dive_timestamp[4]; // 4 bytes
+ unsigned char unknown1[6]; // 6 bytes
+ unsigned char water_conductivity; // 1 byte [0=low, 2=high]
+ unsigned char unknown2[5]; // 5 bytes
+//30
+ unsigned char sample_pre_event_offset[4];// 4 bytes
+ unsigned char unknown3[4]; // 4 bytes
+ unsigned char start_battery_voltage[2]; // 2 bytes [/256]
+//40
+ unsigned char unknown4[2]; // 2 bytes
+ unsigned char start_sgc[2]; // 2 bytes
+ unsigned char entered_or_computed_po[2];// 2 bytes ???
+ unsigned char unknown5[10]; // 10 bytes
+//56
+ unsigned char start_depth[2]; // 2 byte [/4]
+ unsigned char unknown6[4]; // 3 bytes
+ unsigned char start_psi[2]; // 2 bytes LE
+ unsigned char unknown7[4]; // 4 bytes
+ unsigned char sit[2]; // 2 bytes
+//70
+ unsigned char number[2]; // 2 bytes
+ unsigned char unknown8[1]; // 1 byte
+ unsigned char altitude; // 1 byte [/4 = kft]
+ unsigned char unknown9[28]; // 27 bytes
+ unsigned char alarm_depth[2]; // 2 bytes
+ unsigned char unknown10[4]; // 5 bytes
+//108
+ unsigned char repetitive_dive; // 1 byte
+ unsigned char unknown11[3]; // 3 bytes
+ unsigned char start_tissue_nsat[16]; // 16 bytes [/256]
+
+ // Post-dive 128 bytes
+ unsigned char sample_end_offset[4]; // 4 bytes
+ unsigned char unknown12[21]; // 21 bytes
+ unsigned char temp; // 1 byte
+ unsigned char unknown13[12]; // 12 bytes
+ unsigned char bt[2]; // 2 bytes [minutes]
+//168
+ unsigned char max_depth[2]; // 2 bytes [/4]
+ unsigned char avg_depth[2]; // 2 bytes
+ unsigned char unknown14[38]; // 38 bytes
+//210
+ unsigned char o2_percent[4][2]; // 8 bytes
+ unsigned char unknown15[22]; // 22 bytes
+ unsigned char end_tissue_nsat[16]; // 16 bytes [/256]
+} __attribute__((packed));
+
+struct cochran_cmdr_config1_t {
+ unsigned char unknown1[209];
+ unsigned short int dive_count;
+ unsigned char unknown2[274];
+ unsigned short int serial_num; // @170
+ unsigned char unknown3[24];
+} __attribute__((packed));
diff --git a/cochran_emc.h b/cochran_emc.h
new file mode 100644
index 000000000..46a56fe68
--- /dev/null
+++ b/cochran_emc.h
@@ -0,0 +1,171 @@
+/*
+ * subsurface
+ *
+ * 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
+ */
+
+// 512 bytes for each dive in the log book
+struct cochran_emc_log_t {
+ // Pre-dive 256 bytes
+ unsigned char seconds, minutes, hour; // 3 bytes
+ unsigned char day, month, year; // 3 bytes
+ unsigned char sample_start_offset[4]; // 4 bytes
+ unsigned char start_timestamp[4]; // 4 bytes [secs from jan 1,92]
+ unsigned char pre_dive_timestamp[4]; // 4 bytes [secs from Jan 1,92]
+ unsigned char unknown1[6]; // 6 bytes
+ unsigned char water_conductivity; // 1 byte [0 =low, 2-high]
+ unsigned char unknown2[5]; // 5 bytes
+//30
+ unsigned char sample_pre_event_offset[4]; // 4 bytes
+ unsigned char config_bitfield[6]; // 6 bytes
+ unsigned char unknown3[2]; // 2 bytes
+ unsigned char start_depth[2]; // 2 bytes [/256]
+ unsigned char unknown4[2]; // 2 bytes
+ unsigned char start_battery_voltage[2]; // 2 bytes [/256]
+//48
+ unsigned char unknown5[7]; // 7 bytes
+ unsigned char start_temperature; // 1 byte [F]
+ unsigned char unknown6[28]; // 28 bytes
+ unsigned char sit[2]; // 2 bytes [minutes]
+ unsigned char number[2]; // 2 bytes
+ unsigned char unknown7[1]; // 1 bytes
+ unsigned char altitude; // 1 byte [/4 = kft]
+ unsigned char start_nofly[2]; // 2 bytes [/256 = hours]
+//92
+ unsigned char unknown8[18]; // 18 bytes
+ unsigned char post_dive_sit[2]; // 2 bytes [seconds]
+ unsigned char po2_set_point[9][2]; // 18 bytes [/256 = %]
+ unsigned char unknown9[12]; // 12 bytes
+ unsigned char po2_alarm[2]; // 2 bytes [/256 = %]
+//144
+ unsigned char o2_percent[10][2]; // 20 bytes [/256 = %]
+ unsigned char he_percent[10][2]; // 20 bytes [/256 = %]
+ unsigned char alarm_depth[2]; // 2 bytes
+ unsigned char unknown10[14]; // 14 bytes
+ unsigned char conservatism; // 1 bytes [/256 = fraction]
+ unsigned char unknown11[2]; // 2 bytes
+ unsigned char repetitive_dive; // 1 byte
+ unsigned char unknown12[12]; // 12 bytes
+ unsigned char start_tissue_nsat[20][2]; // 40 bytes [/256]
+
+ // Post-dive 256 bytes
+ unsigned char sample_end_offset[4]; // 4 bytes
+ unsigned char unknown13[33]; // 33 bytes
+ unsigned char temp; // 1 byte [F]
+ unsigned char unknown14[10]; // 10 bytes
+// 48
+ unsigned char bt[2]; // 2 bytes [minutes]
+ unsigned char max_depth[2]; // 2 bytes [/4 = ft]
+ unsigned char unknown15[2]; // 2 bytes
+ unsigned char avg_depth[2]; // 2 bytes [/4 = ft]
+ unsigned char min_ndc[2]; // 2 bytes [minutes]
+ unsigned char min_ndx_bt[2]; // 2 bytes [minutes]
+ unsigned char max_forecast_deco[2]; // 2 bytes [minutes]
+ unsigned char max_forecast_deco_bt[2]; // 2 bytes [minutes]
+//64
+ unsigned char max_ceiling[2]; // 2 bytes [*10 = ft]
+ unsigned char max_ceiling_bt[2]; // 2 bytes [minutes]
+ unsigned char unknown16[10]; // 18 bytes
+ unsigned char max_ascent_rate; // 1 byte [ft/min]
+ unsigned char unknown17[3]; // 3 bytes
+ unsigned char max_ascent_rate_bt[2]; // 2 bytes [seconds]
+//84
+ unsigned char unknown18[54]; // 54 bytes
+//138
+ unsigned char end_battery_voltage[2]; // 2 bytes [/256 = v]
+ unsigned char unknown19[8]; // 8 bytes
+ unsigned char min_temp_bt[2]; // 2 bytes [seconds]
+//150
+ unsigned char unknown20[22]; // 22 bytes
+//172
+ unsigned char end_nofly[2]; // 2 bytes [/256 = hours]
+ unsigned char alarm_count[2]; // 2 byte
+ unsigned char actual_deco_time[2]; // 2 bytes [seconds]
+//178
+ unsigned char unknown21[38]; // 38 bytes
+//216
+ unsigned char end_tissue_nsat[20][2]; // 40 bytes [/256 = fraction]
+} __attribute__((packed));
+
+typedef enum cochran_emc_bitfield_config_t {
+ BF_TEMP_DEPENDENT_N2,
+ BF_ASCENT_RATE_BAR_GRAPH,
+ BF_BLEND_2_SWITCHING,
+ BF_ALTITUDE_AS_ONE_ZONE,
+ BF_DECOMPRESSION_TIME_DISPLAY,
+ BF_BLEND_3_SWITCHING,
+ BF_VARIABLE_ASCENT_RATE_ALARM,
+ BF_ASCENT_RATE_RESPONSE,
+ BF_REPETITIVE_DIVE_DEPENDENT_N2,
+ BF_TRAINING_MODE,
+ BF_CONSTANT_MODE_COMPUTATIONS,
+ BF_DISPLAYED_UNITS,
+ BF_AUDIBLE_ALARM,
+ BF_CLOCK,
+ BF_CEILING_DISPLAY_DIV_BY_10,
+ BF_GAS_2_AS_FIRST_GAS,
+ BF_ENABLE_HELIUM_COMPUTATIONS,
+ BF_AUTOMATIC_PO2_FO2_SWITCHING,
+ BF_TOUCH_PROGRAMMING_PO2_FO2_SWITCH,
+} cochran_emc_bitfield_config_t;
+
+
+struct cochran_emc_bitfield_t {
+ cochran_emc_bitfield_config_t config;
+ unsigned char word;
+ unsigned char byte;
+ unsigned char mask;
+ unsigned char shift;
+} cochran_emc_bitfield_t;
+
+static struct cochran_emc_bitfield_t cochran_emc_bits[] = {
+// Word BD
+ { BF_TEMP_DEPENDENT_N2, 0xBD, 0, 0x40, 6 }, // 0=normal, 1=reduced
+ { BF_ASCENT_RATE_BAR_GRAPH, 0xBD, 0, 0x20, 5 }, // 0=fixed, 1=proportional
+ { BF_BLEND_2_SWITCHING, 0xBD, 0, 0x04, 2 }, // 0=dis, 1=ena
+ { BF_ALTITUDE_AS_ONE_ZONE, 0xBD, 0, 0x02, 1}, // 0=off, 1=on
+
+ { BF_DECOMPRESSION_TIME_DISPLAY, 0xBD, 1, 0xC0, 5}, // 111=both, 011=stop, 001=total
+ { BF_BLEND_3_SWITCHING, 0xBD, 1, 0x10, 4 }, // 0=dis, 1=ena
+ { BF_VARIABLE_ASCENT_RATE_ALARM, 0xBD, 1, 0x04, 3}, // 0=off, 1=on
+ { BF_ASCENT_RATE_RESPONSE, 0xBD, 1, 0x07, 0},
+
+//WORD BE
+ { BF_REPETITIVE_DIVE_DEPENDENT_N2, 0xBE, 0, 0x80, 7 }, // 0=off, 1=on
+ { BF_TRAINING_MODE, 0xBE, 0, 0x04, 2 }, // 0=off, 1=on
+ { BF_CONSTANT_MODE_COMPUTATIONS, 0xBE, 0, 0x04, 2 }, // 0=FO2, 1=PO2
+ { BF_DISPLAYED_UNITS, 0xBE, 0, 0x01, 0 }, // 1=metric, 0=imperial
+
+// WORD BF
+ { BF_AUDIBLE_ALARM, 0xBF, 0, 0x40, 6 }, // 0=on, 1=off ***
+ { BF_CLOCK, 0xBF, 0, 0x20, 5 }, // 0=off, 1=on
+ { BF_CEILING_DISPLAY_DIV_BY_10, 0xBF, 0, 0x10, 4 }, // 0=off, 1=on
+ { BF_GAS_2_AS_FIRST_GAS, 0xBF, 0, 0x02, 1 }, // 0=dis, 1=ena
+ { BF_ENABLE_HELIUM_COMPUTATIONS, 0xBF, 0, 0x01, 0 }, // 0=dis, 1=ena
+
+ { BF_AUTOMATIC_PO2_FO2_SWITCHING, 0xBF, 1, 0x04, 2 }, // 0=dis, 1=ena
+ { BF_TOUCH_PROGRAMMING_PO2_FO2_SWITCH, 0xBF, 1, 0x02, 1 }, // 0=dis, 1=ena
+};
+
+struct cochran_emc_config1_t {
+ unsigned char unknown1[209];
+ unsigned short int dive_count;
+ unsigned char unknown2[274];
+ unsigned short int serial_num;
+ unsigned char unknown3[24];
+} __attribute__((packed));
diff --git a/file.c b/file.c
index da5ea830d..6ada903fd 100644
--- a/file.c
+++ b/file.c
@@ -351,13 +351,9 @@ static int open_by_filename(const char *filename, const char *fmt, struct memblo
/* CSV files */
if (!strcasecmp(fmt, "CSV"))
return 1;
-
-#if ONCE_COCHRAN_IS_SUPPORTED
/* Truly nasty intentionally obfuscated Cochran Anal software */
if (!strcasecmp(fmt, "CAN"))
return try_to_open_cochran(filename, mem);
-#endif
-
/* Cochran export comma-separated-value files */
if (!strcasecmp(fmt, "DPT"))
return try_to_open_csv(filename, mem, CSV_DEPTH);
diff --git a/file.h b/file.h
index 2e4d3ebde..8c5b48bc9 100644
--- a/file.h
+++ b/file.h
@@ -6,9 +6,7 @@ struct memblock {
size_t size;
};
-#if 0
-extern int try_to_open_cochran(const char *filename, struct memblock *mem, GError **error);
-#endif
+extern int try_to_open_cochran(const char *filename, struct memblock *mem);
#ifdef __cplusplus
extern "C" {
diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp
index 0a6112b4f..9aadfd313 100644
--- a/qt-ui/mainwindow.cpp
+++ b/qt-ui/mainwindow.cpp
@@ -1225,7 +1225,7 @@ void MainWindow::loadFiles(const QStringList fileNames)
void MainWindow::on_actionImportDiveLog_triggered()
{
QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open dive log file"), lastUsedDir(),
- tr("Dive log files (*.xml *.uddf *.udcf *.csv *.jlb *.dld *.sde *.db);;"
+ tr("Dive log files (*.xml *.uddf *.udcf *.csv *.jlb *.dld *.sde *.db *.can);;"
"XML files (*.xml);;UDDF/UDCF files(*.uddf *.udcf);;JDiveLog files(*.jlb);;"
"Suunto Files(*.sde *.db);;CSV Files(*.csv);;MkVI Files(*.txt);;All Files(*)"));
diff --git a/subsurface.pro b/subsurface.pro
index 724e0215d..53f7f4b6b 100644
--- a/subsurface.pro
+++ b/subsurface.pro
@@ -21,6 +21,8 @@ QMAKE_CLEAN += $$TARGET
VERSION = 4.2
HEADERS = \
+ cochran_cmdr.h \
+ cochran_emc.h \
color.h \
deco.h \
device.h \
@@ -110,6 +112,7 @@ android: HEADERS -= \
qt-ui/printoptions.h
SOURCES = \
+ cochran.c \
deco.c \
device.c \
dive.c \