Specifically tested with EMC-20H and Commander Air_Nitrox this commit should support the families for the most part however there are may be differences in the DC signature, memory size, and other factors that will prevent opening the device and obtaining a clean import. Try it with other EMC and Commander devices and check library ERRORs for the signature information needed to add support.
Also missing is tank pressure (for Gemini) and support for logbooks that have rolled old dives off. There is support for dive samples that have rolled off.
John Van Ostrand (1): Added support for Cochran EMC and Commander Air import
examples/Makefile.am | 6 +- examples/cochran_download.c | 299 +++++++++++++ examples/universal.c | 8 +- include/libdivecomputer/Makefile.am | 3 +- include/libdivecomputer/cochran.h | 61 +++ include/libdivecomputer/common.h | 3 + include/libdivecomputer/parser.h | 20 +- src/Makefile.am | 7 +- src/cochran_cmdr.c | 66 +++ src/cochran_cmdr.h | 83 ++++ src/cochran_cmdr_parser.c | 202 +++++++++ src/cochran_common.c | 861 ++++++++++++++++++++++++++++++++++++ src/cochran_common.h | 109 +++++ src/cochran_common_parser.c | 157 +++++++ src/cochran_common_parser.h | 80 ++++ src/cochran_emc.c | 67 +++ src/cochran_emc.h | 180 ++++++++ src/cochran_emc_parser.c | 399 +++++++++++++++++ src/descriptor.c | 3 + src/device.c | 7 + src/libdivecomputer.symbols | 2 + src/parser.c | 7 + 22 files changed, 2617 insertions(+), 13 deletions(-) create mode 100644 examples/cochran_download.c create mode 100644 include/libdivecomputer/cochran.h create mode 100644 src/cochran_cmdr.c create mode 100644 src/cochran_cmdr.h create mode 100644 src/cochran_cmdr_parser.c create mode 100644 src/cochran_common.c create mode 100644 src/cochran_common.h create mode 100644 src/cochran_common_parser.c create mode 100644 src/cochran_common_parser.h create mode 100644 src/cochran_emc.c create mode 100644 src/cochran_emc.h create mode 100644 src/cochran_emc_parser.c
Specifically tested with EMC-20H and Commander Air_Nitrox this commit should support the families for the most part however there are may be differences in the DC signature, memory size, and other factors that will prevent opening the device and obtaining a clean import. Try it with other EMC and Commander devices and check library ERRORs for the signature information needed to add support.
Also missing is tank pressure (for Gemini) and support for logbooks that have rolled old dives off. There is support for dive samples that have rolled off. --- examples/Makefile.am | 6 +- examples/cochran_download.c | 299 +++++++++++++ examples/universal.c | 8 +- include/libdivecomputer/Makefile.am | 3 +- include/libdivecomputer/cochran.h | 61 +++ include/libdivecomputer/common.h | 3 + include/libdivecomputer/parser.h | 20 +- src/Makefile.am | 7 +- src/cochran_cmdr.c | 66 +++ src/cochran_cmdr.h | 83 ++++ src/cochran_cmdr_parser.c | 202 +++++++++ src/cochran_common.c | 861 ++++++++++++++++++++++++++++++++++++ src/cochran_common.h | 109 +++++ src/cochran_common_parser.c | 157 +++++++ src/cochran_common_parser.h | 80 ++++ src/cochran_emc.c | 67 +++ src/cochran_emc.h | 180 ++++++++ src/cochran_emc_parser.c | 399 +++++++++++++++++ src/descriptor.c | 3 + src/device.c | 7 + src/libdivecomputer.symbols | 2 + src/parser.c | 7 + 22 files changed, 2617 insertions(+), 13 deletions(-) create mode 100644 examples/cochran_download.c create mode 100644 include/libdivecomputer/cochran.h create mode 100644 src/cochran_cmdr.c create mode 100644 src/cochran_cmdr.h create mode 100644 src/cochran_cmdr_parser.c create mode 100644 src/cochran_common.c create mode 100644 src/cochran_common.h create mode 100644 src/cochran_common_parser.c create mode 100644 src/cochran_common_parser.h create mode 100644 src/cochran_emc.c create mode 100644 src/cochran_emc.h create mode 100644 src/cochran_emc_parser.c
diff --git a/examples/Makefile.am b/examples/Makefile.am index d77db45..10bc2cf 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -1,9 +1,11 @@ AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include LDADD = $(top_builddir)/src/libdivecomputer.la +AM_LDFLAGS = -lm
bin_PROGRAMS = \ universal \ - ostc-fwupdate + ostc-fwupdate \ + cochran_download
COMMON = common.c common.h \ utils.c utils.h @@ -11,3 +13,5 @@ COMMON = common.c common.h \ universal_SOURCES = universal.c $(COMMON)
ostc_fwupdate_SOURCES = hw_ostc_fwupdate.c $(COMMON) + +cochran_download_SOURCES = cochran_download.c diff --git a/examples/cochran_download.c b/examples/cochran_download.c new file mode 100644 index 0000000..b42eb91 --- /dev/null +++ b/examples/cochran_download.c @@ -0,0 +1,299 @@ +/* + * cochran_download + * + * 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 <stdio.h> // fopen, fwrite, fclose +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <fcntl.h> + + +#include <libdivecomputer/context.h> +#include <libdivecomputer/device.h> +#include <libdivecomputer/parser.h> +#include <libdivecomputer/cochran.h> + + +volatile sig_atomic_t g_cancel = 0; + +void +sighandler (int signum) +{ + // Restore the default signal handler. + signal (signum, SIG_DFL); + + g_cancel = 1; +} + +static int +cancel_cb (void *userdata) +{ + return g_cancel; +} + +static void +usage (const char *filename) +{ + fprintf (stderr, "Usage:\n\n"); + fprintf (stderr, " %s [options] devname\n\n", filename); + fprintf (stderr, "Options:\n\n"); + fprintf (stderr, " -b name Set backend name (required).\n"); + fprintf (stderr, " -d dirname Dump data to dirname.\n"); + fprintf (stderr, " -f Force dump despite download errors\n"); + fprintf (stderr, " -h Show this help message.\n\n"); + + fprintf (stderr, "Supported backends:\n\n"); + fprintf (stderr, " emc, Cochran EMC family\n"); + fprintf (stderr, " commander, Cochran Commander family\n"); + fprintf (stderr, "\n\n"); +} + +static dc_status_t +search (dc_descriptor_t **out, const char *name, dc_family_t backend, unsigned int model) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + + dc_iterator_t *iterator = NULL; + rc = dc_descriptor_iterator (&iterator); + if (rc != DC_STATUS_SUCCESS) { + printf ("Error creating the device descriptor iterator.\n"); + return rc; + } + + dc_descriptor_t *descriptor = NULL, *current = NULL; + while ((rc = dc_iterator_next (iterator, &descriptor)) == DC_STATUS_SUCCESS) + { + if (backend == dc_descriptor_get_type (descriptor)) { + dc_descriptor_free (current); + current = descriptor; + break; + } + + dc_descriptor_free (descriptor); + } + + if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_DONE) { + dc_descriptor_free (current); + dc_iterator_free (iterator); + printf ("Error iterating the device descriptors.\n"); + return rc; + } + + dc_iterator_free (iterator); + + *out = current; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +write_dump(dc_buffer_t *dump, const char *fname) +{ + int fd, size = 0, written; + + if (dump) + size = dc_buffer_get_size(dump); + + if (size > 0) { + fd = open(fname, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH ); + if (fd == -1) { + printf("unable to open %s for writing\n", fname); + exit; + } + written = write(fd, dc_buffer_get_data(dump), size); + if (written != size) { + printf("Error writing %s. Wrote %d expected %d.\n", fname, written, size); + return (DC_STATUS_UNSUPPORTED); + } + } + return (DC_STATUS_SUCCESS); +} + +static dc_status_t +dowork (dc_context_t *context, dc_descriptor_t *descriptor, const char *devname, const char *dirname, unsigned int force_flag) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + + struct stat dstat; + + // Open the device. + dc_device_t *device = NULL; + rc = dc_device_open (&device, context, descriptor, devname); + if (rc != DC_STATUS_SUCCESS) { + printf ("Error opening %s.\n", devname); + return rc; + } + + // Register the cancellation handler. + rc = dc_device_set_cancel (device, cancel_cb, NULL); + if (rc != DC_STATUS_SUCCESS) { + printf ("Error registering the cancellation handler.\n"); + dc_device_close (device); + return rc; + } + + // Create directory if needed + stat(dirname, &dstat); + + if ( ! (dstat.st_mode & S_IFDIR) ) { + if (mkdir(dirname, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) { + printf("Unable to create directory\n"); + exit; + } + } + + + // TODO Run dump and write files + struct cochran_data_dump dump = {0}; + rc = dc_device_dump (device, (dc_buffer_t *) &dump); + + if (rc == DC_STATUS_SUCCESS || force_flag) { + char fname[128]; + + sprintf(fname, "%s/info-x05x9dxffx00x43x00.bin", dirname); + write_dump(dump.id0, fname); + + if (dc_buffer_get_size(dump.id0) > 0) + sprintf(fname, "%s/info-xdbx7fxffx00x43x00.bin", dirname); + else + sprintf(fname, "%s/info-x05x9dxffx00x43x00.bin", dirname); + write_dump(dump.id1, fname); + + sprintf(fname, "%s/config-x96x00.bin", dirname); + write_dump(dump.config1, fname); + + sprintf(fname, "%s/config-x96x01.bin", dirname); + write_dump(dump.config2, fname); + + sprintf(fname, "%s/config-x96x02.bin", dirname); + write_dump(dump.config3, fname); + + sprintf(fname, "%s/config-x96x03.bin", dirname); + write_dump(dump.config4, fname); + + sprintf(fname, "%s/misc-x89x05x00x00x00xdcx05.bin", dirname); + write_dump(dump.misc, fname); + + sprintf(fname, "%s/logbook.bin", dirname); + write_dump(dump.logbook, fname); + + sprintf(fname, "%s/sample.bin", dirname); + write_dump(dump.sample, fname); + + dc_buffer_free (dump.id0); + dc_buffer_free (dump.id1); + dc_buffer_free (dump.config1); + dc_buffer_free (dump.config2); + dc_buffer_free (dump.config3); + dc_buffer_free (dump.config4); + dc_buffer_free (dump.misc); + dc_buffer_free (dump.logbook); + dc_buffer_free (dump.sample); + } + + // Close the device. + rc = dc_device_close (device); + if (rc != DC_STATUS_SUCCESS) { + printf ("Error closing the device.\n"); + return rc; + } + + return DC_STATUS_SUCCESS; +} + + +int +main (int argc, char *argv[]) +{ + extern char *optarg; + extern int optind, opterr, optopt; + + // Default values. + dc_family_t backend = DC_FAMILY_NULL; + + // Command line arguments + const char *devname = NULL, *dirname; + unsigned int force_flag = 0; + + + // Parse command-line options. + int opt = 0; + while ((opt = getopt (argc, argv, "b:d:ls:h")) != -1) { + switch (opt) { + case 'b': // backend + if ( ! strcmp ("emc", optarg) ) + backend = DC_FAMILY_COCHRAN_EMC; + else if ( ! strcmp( "commander", optarg) ) + backend = DC_FAMILY_COCHRAN_COMMANDER; + break; + case 'd': // dump data to directory + dirname = optarg; + break; + case 'f': // Force dump despite download error + force_flag = 1; + break; + case '?': + case 'h': + default: + usage (argv[0]); + return EXIT_FAILURE; + } + } + + if (optind < argc) + devname = argv[optind]; + + + signal (SIGINT, sighandler); + + dc_context_t *context = NULL; + dc_status_t rc = dc_context_new (&context); + if (rc != DC_STATUS_SUCCESS) { + return EXIT_FAILURE; + } + + dc_context_set_loglevel (context, DC_LOGLEVEL_NONE); + //dc_context_set_logfunc (context, logfunc, NULL); + + /* Search for a matching device descriptor. */ + dc_descriptor_t *descriptor = NULL; + rc = search (&descriptor, NULL, backend, 0); + if (rc != DC_STATUS_SUCCESS) { + return EXIT_FAILURE; + } + + /* Fail if no device descriptor found. */ + if (descriptor == NULL) { + printf ("No matching device found.\n"); + usage (argv[0]); + return EXIT_FAILURE; + } + + rc = dowork (context, descriptor, devname, dirname, force_flag); + + dc_descriptor_free (descriptor); + dc_context_free (context); + + return rc != DC_STATUS_SUCCESS ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/examples/universal.c b/examples/universal.c index 1e0307b..56445b7 100644 --- a/examples/universal.c +++ b/examples/universal.c @@ -102,6 +102,8 @@ static const backend_table_t g_backends[] = { {"predator", DC_FAMILY_SHEARWATER_PREDATOR}, {"petrel", DC_FAMILY_SHEARWATER_PETREL}, {"nitekq", DC_FAMILY_DIVERITE_NITEKQ}, + {"commander", DC_FAMILY_COCHRAN_COMMANDER}, + {"emc", DC_FAMILY_COCHRAN_EMC}, };
static dc_family_t @@ -245,7 +247,7 @@ sample_cb (dc_sample_type_t type, dc_sample_value_t value, void *userdata) "safety stop (voluntary)", "safety stop (mandatory)", "deepstop", "ceiling (safety stop)", "floor", "divetime", "maxdepth", "OLF", "PO2", "airtime", "rgbm", "heading", "tissue level warning", - "gaschange2"}; + "gaschange2", "battery"}; static const char *decostop[] = { "ndl", "safety", "deco", "deep"};
@@ -307,6 +309,10 @@ sample_cb (dc_sample_type_t type, dc_sample_value_t value, void *userdata) fprintf (sampledata->fp, " <deco time="%u" depth="%.2f">%s</deco>\n", value.deco.time, value.deco.depth, decostop[value.deco.type]); break; + case DC_SAMPLE_ASCENT_RATE: + fprintf (sampledata->fp, " <ascent_rate>%.2f</ascent_rate>\n", + value.ascent_rate); + break; default: break; } diff --git a/include/libdivecomputer/Makefile.am b/include/libdivecomputer/Makefile.am index 891f4e0..59b30a5 100644 --- a/include/libdivecomputer/Makefile.am +++ b/include/libdivecomputer/Makefile.am @@ -49,4 +49,5 @@ libdivecomputer_HEADERS = \ shearwater_petrel.h \ shearwater_predator.h \ diverite.h \ - diverite_nitekq.h + diverite_nitekq.h \ + cochran.h diff --git a/include/libdivecomputer/cochran.h b/include/libdivecomputer/cochran.h new file mode 100644 index 0000000..27fdcc5 --- /dev/null +++ b/include/libdivecomputer/cochran.h @@ -0,0 +1,61 @@ +/* + * 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 "context.h" +#include "device.h" +#include "parser.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +struct cochran_data_dump { + dc_buffer_t *id0; + dc_buffer_t *id1; + dc_buffer_t *config1; + dc_buffer_t *config2; + dc_buffer_t *config3; + dc_buffer_t *config4; + dc_buffer_t *misc; + dc_buffer_t *logbook; + dc_buffer_t *sample; +}; + +dc_status_t +cochran_cmdr_device_open (dc_device_t **device, dc_context_t *context, const char *name); + +dc_status_t +cochran_cmdr_parser_create (dc_parser_t **parser, dc_context_t *context); + +dc_status_t +cochran_emc_device_open (dc_device_t **device, dc_context_t *context, const char *name); + +dc_status_t +cochran_emc_parser_create (dc_parser_t **parser, dc_context_t *context); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* COCHRAN_H */ diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h index 983ec07..ef86272 100644 --- a/include/libdivecomputer/common.h +++ b/include/libdivecomputer/common.h @@ -83,6 +83,9 @@ typedef enum dc_family_t { DC_FAMILY_SHEARWATER_PETREL, /* Dive Rite */ DC_FAMILY_DIVERITE_NITEKQ = (11 << 16), + /* Cochran */ + DC_FAMILY_COCHRAN_COMMANDER = (12 << 16), + DC_FAMILY_COCHRAN_EMC, } dc_family_t;
#ifdef __cplusplus diff --git a/include/libdivecomputer/parser.h b/include/libdivecomputer/parser.h index 65b18c9..0f7021b 100644 --- a/include/libdivecomputer/parser.h +++ b/include/libdivecomputer/parser.h @@ -43,7 +43,8 @@ typedef enum dc_sample_type_t { DC_SAMPLE_SETPOINT, DC_SAMPLE_PPO2, DC_SAMPLE_CNS, - DC_SAMPLE_DECO + DC_SAMPLE_DECO, + DC_SAMPLE_ASCENT_RATE, } dc_sample_type_t;
typedef enum dc_field_type_t { @@ -53,7 +54,8 @@ typedef enum dc_field_type_t { DC_FIELD_GASMIX_COUNT, DC_FIELD_GASMIX, DC_FIELD_SALINITY, - DC_FIELD_ATMOSPHERIC + DC_FIELD_ATMOSPHERIC, + DC_FIELD_TEMPERATURE } dc_field_type_t;
typedef enum parser_sample_event_t { @@ -85,6 +87,7 @@ typedef enum parser_sample_event_t { SAMPLE_EVENT_GASCHANGE2, /* The event value contains the O2 and He percentages, packed as two 16bit integers in respectively the low and high part. */ + SAMPLE_EVENT_BATTERY /* Battery low */ } parser_sample_event_t;
/* For backwards compatibility */ @@ -129,20 +132,20 @@ typedef struct dc_gasmix_t { } dc_gasmix_t;
typedef union dc_sample_value_t { - unsigned int time; - double depth; + unsigned int time; // seconds + double depth; // metres struct { unsigned int tank; double value; } pressure; - double temperature; + double temperature; // celcius struct { unsigned int type; unsigned int time; unsigned int flags; unsigned int value; } event; - unsigned int rbt; + unsigned int rbt; // minutes unsigned int heartbeat; unsigned int bearing; struct { @@ -155,9 +158,10 @@ typedef union dc_sample_value_t { double cns; struct { unsigned int type; - unsigned int time; - double depth; + unsigned int time; // minutes + double depth; // metres } deco; + double ascent_rate; // m/sec } dc_sample_value_t;
typedef struct dc_parser_t dc_parser_t; diff --git a/src/Makefile.am b/src/Makefile.am index 595f0c7..d8098f6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include -AM_CFLAGS = $(LIBUSB_CFLAGS) +AM_CFLAGS = $(LIBUSB_CFLAGS) -g
lib_LTLIBRARIES = libdivecomputer.la
@@ -55,7 +55,10 @@ libdivecomputer_la_SOURCES = \ ringbuffer.h ringbuffer.c \ checksum.h checksum.c \ array.h array.c \ - buffer.c + buffer.c \ + cochran_common.h cochran_common.c cochran_common_parser.h cochran_common_parser.c \ + cochran_cmdr.h cochran_cmdr.c cochran_cmdr_parser.c \ + cochran_emc.h cochran_emc.c cochran_emc_parser.c
if OS_WIN32 libdivecomputer_la_SOURCES += serial.h serial_win32.c diff --git a/src/cochran_cmdr.c b/src/cochran_cmdr.c new file mode 100644 index 0000000..fb6e1e0 --- /dev/null +++ b/src/cochran_cmdr.c @@ -0,0 +1,66 @@ +/* + * 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 <time.h> + +#include <libdivecomputer/cochran.h> + +#include "context-private.h" +#include "device-private.h" +#include "serial.h" + +#include "cochran_common.h" +#include "cochran_cmdr.h" + + +static const dc_device_vtable_t cochran_cmdr_device_vtable = { + DC_FAMILY_COCHRAN_COMMANDER, + cochran_common_device_set_fingerprint, /* set_fingerprint */ + cochran_common_device_read, /* read */ + NULL, /* write */ + cochran_common_device_dump, /* dump */ + cochran_common_device_foreach, /* foreach */ + cochran_common_device_close /* close */ +}; + + +dc_status_t +cochran_cmdr_device_open (dc_device_t **out, dc_context_t *context, const char *name) +{ + dc_status_t rc; + rc = cochran_common_device_open(out, context, name, &cochran_cmdr_device_vtable); + if (rc != DC_STATUS_SUCCESS) + return rc; + + cochran_device_t *device = (cochran_device_t *) *out; + + // Check family + if ((device->data.model & 0xFF0000) != COCHRAN_MODEL_COMMANDER_FAMILY) { + ERROR (context, "Device not recognized."); + serial_close (device->port); + free (device->data.id); + free (device); + return DC_STATUS_UNSUPPORTED; + } + + return DC_STATUS_SUCCESS; +} diff --git a/src/cochran_cmdr.h b/src/cochran_cmdr.h new file mode 100644 index 0000000..bac5f57 --- /dev/null +++ b/src/cochran_cmdr.h @@ -0,0 +1,83 @@ +/* + * 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 + */ + + +struct cochran_cmdr_log_t { + // Pre-dive 128 bytes + unsigned char minutes, seconds; // 3 bytes + unsigned char day, hour, year, month; // 3 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[4]; // 4 bytes + unsigned char entered_or_computed_po[2];// 2 bytes ??? + unsigned char unknown5[8]; // 8 bytes + unsigned char start_depth[2]; // 2 byte [/4] +//56 + unsigned char unknown6[12]; // 12 bytes + unsigned char sit[2]; // 2 bytes +//70 + unsigned char number[2]; // 2 bytes + unsigned char unknown7[1]; // 1 byte + unsigned char altitude; // 1 byte [/4 = kft] + unsigned char unknown8[28]; // 27 bytes + unsigned char alarm_depth[2]; // 2 bytes + unsigned char unknown9[4]; // 5 bytes +//108 + unsigned char repetitive_dive; // 1 byte + unsigned char unknown10[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 unknown11[21]; // 21 bytes + unsigned char temp; // 1 byte + unsigned char unknown12[12]; // 12 bytes + unsigned char bt[2]; // 2 bytes [minutes] + unsigned char max_depth[2]; // 2 bytes [/4] + unsigned char avg_depth[2]; // 2 bytes + unsigned char unknown13[38]; // 38 bytes + unsigned char o2_percent[4][2]; // 8 bytes + unsigned char unknown14[22]; // 22 bytes + unsigned char end_tissue_nsat[16]; // 16 bytes [/256] +} __attribute__((packed)); + + +typedef struct cochran_cmdr_log_t cochran_cmdr_log_t; + + +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)); + +typedef struct cochran_emc_config1_t cochran_emc_config1_t; diff --git a/src/cochran_cmdr_parser.c b/src/cochran_cmdr_parser.c new file mode 100644 index 0000000..8c74705 --- /dev/null +++ b/src/cochran_cmdr_parser.c @@ -0,0 +1,202 @@ +/* + * libdivecomputer + * + * Copyright (C) 2014 John Van Ostrand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include <stdlib.h> +#include <math.h> + +#include <libdivecomputer/units.h> +#include <libdivecomputer/cochran.h> + +#include "context-private.h" +#include "device-private.h" +#include "parser-private.h" +#include "serial.h" +#include "array.h" + +#include "cochran_common.h" +#include "cochran_cmdr.h" +#include "cochran_common_parser.h" + +#define SZ_SAMPLE 2 + +static dc_status_t cochran_cmdr_parser_get_field(dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); +static dc_status_t cochran_cmdr_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); + +static const dc_parser_vtable_t cochran_cmdr_parser_vtable = { + DC_FAMILY_COCHRAN_COMMANDER, + cochran_common_parser_set_data, /* set_data */ + cochran_common_parser_get_datetime, /* datetime */ + cochran_cmdr_parser_get_field, /* fields */ + cochran_cmdr_parser_samples_foreach, /* samples_foreach */ + cochran_common_parser_destroy /* destroy */ +}; + + +dc_status_t +cochran_cmdr_parser_create (dc_parser_t **out, dc_context_t *context) +{ + return cochran_common_parser_create(out, context, &cochran_cmdr_parser_vtable); +} + + +static dc_status_t +cochran_cmdr_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) +{ + cochran_data_t *data = (cochran_data_t *) abstract->data; + cochran_cmdr_log_t *log = (cochran_cmdr_log_t *) data->current_log; + + dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + dc_salinity_t *water = (dc_salinity_t *) value; + + if (value) { + switch (type) { + case DC_FIELD_TEMPERATURE: + *((unsigned int*) value) = (log->temp - 32) / 1.8; + case DC_FIELD_DIVETIME: + *((unsigned int *) value) = array_uint16_le (log->bt) * 60; + break; + case DC_FIELD_MAXDEPTH: + *((double *) value) = array_uint16_le (log->max_depth) / 4 * FEET; + break; + case DC_FIELD_AVGDEPTH: + *((double *) value) = array_uint16_le (log->avg_depth) / 4 * FEET; + break; + case DC_FIELD_GASMIX_COUNT: + *((unsigned int *) value) = 2; + break; + case DC_FIELD_GASMIX: + gasmix->oxygen = (double) array_uint16_le ((char *)log->o2_percent + 2 * flags) / 256 / 100; + gasmix->helium = 0; + gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; + break; + case DC_FIELD_SALINITY: + // 0 = low conductivity, 1 = high, maybe there's a 2? + water->type = ( log->water_conductivity == 0 ? DC_WATER_FRESH : DC_WATER_SALT ); + water->density = 1000 + 12.5 * log->water_conductivity; + break; + case DC_FIELD_ATMOSPHERIC: + *(double *) value = ATM / BAR * pow(1 - 0.0000225577 * (double) log->altitude * 250 * FEET, 5.25588); + break; + default: + return DC_STATUS_UNSUPPORTED; + } + } + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +cochran_cmdr_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +{ + cochran_data_t *data = (cochran_data_t *)abstract->data; + unsigned char *sdata = data->current_sample; + cochran_cmdr_log_t *log = (cochran_cmdr_log_t *) data->current_log; + unsigned int size = data->current_sample_size; + unsigned char *s; + + dc_sample_value_t sample = {0}, empty_sample = {0}; + unsigned int time = 0, last_sample_time; + unsigned int offset = 0; + double temperature; + double depth; + double ascent_rate; + unsigned char deco_obligation = 0; + unsigned int deco_time = 0; + + // Cochran samples depth every second and varies between ascent rate + // and temp ever other second. + + // Prime values from the dive log section + depth = array_uint16_le (log->start_depth) / 256; + + last_sample_time = sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + + sample.depth = depth * FEET; + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + + // TODO: sample.temperature = (log->start_temperature - 32) / 1.8; + // TODO: if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); + + while (offset < size) { + sample = empty_sample; + + sample.time = time; + if (callback && last_sample_time != sample.time) { + // We haven't issued this time yet. + last_sample_time = sample.time; + callback (DC_SAMPLE_TIME, sample, userdata); + } + + if (offset > data->sample_memory_end_address) + s = sdata + offset - data->sample_memory_end_address - 1; + else + s = sdata + offset; + + // If corrupt_dive end before offset + if (data->corrupt_dive) { + if ( s[0] == 0x10 + || s[0] == 0xFF + || s[0] == 0xA8) { + break; + } + if (time > 1 && (s[0] == 0xE3 || s[0] == 0xF3)) + break; + } + + // Check for event + if (s[0] & 0x80) { + offset += cochran_common_handle_event(abstract, callback, userdata, s[0], offset, time); + if (s[0] == 0xC5) + deco_obligation = 1; // Deco obligation begins + else if (s[0] == 0xC8) + deco_obligation = 0; // Deco obligation ends + + continue; + } + + + // Depth is logged as change in feet, bit 0x40 means negative depth + depth += (float) (s[0] & 0x3F) / 4 * (s[0] & 0x40 ? -1 : 1); + sample.depth = depth * 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 + ascent_rate = (s[1] & 0x7f) / 4 * (s[0] & 0x80 ? 1 : -1); + sample.ascent_rate = ascent_rate * FEET; + if (callback) callback (DC_SAMPLE_ASCENT_RATE, sample, userdata); + } else { + // Temperature logged in half degrees F above 20 + temperature = s[1] / 2 + 20; + sample.temperature = (temperature - 32.0) / 1.8; + + if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); + } + + time ++; + offset += SZ_SAMPLE; + } + + return DC_STATUS_SUCCESS; +} diff --git a/src/cochran_common.c b/src/cochran_common.c new file mode 100644 index 0000000..877491d --- /dev/null +++ b/src/cochran_common.c @@ -0,0 +1,861 @@ +/* + * 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 <time.h> + +#include <libdivecomputer/cochran.h> + +#include "context-private.h" +#include "device-private.h" +#include "serial.h" +#include "array.h" + +#include "cochran_common.h" + + +#define EXITCODE(rc) \ +( \ + rc == -1 ? DC_STATUS_IO : DC_STATUS_TIMEOUT \ +) + + +dc_status_t +cochran_packet (cochran_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, int high_speed) +{ + dc_device_t *abstract = (dc_device_t *) device; + unsigned int bytes_read = 0, n, read_size; + unsigned int ptr; + + if (device_is_cancelled (abstract)) + return DC_STATUS_CANCELLED; + + struct timespec tr, ts = {0}; + ts.tv_nsec = 16 * 1000000; // 16 ms + // Send the command to the device, one byte at a time + for (ptr = 0; ptr < csize; ptr++) { + if (ptr) nanosleep(&ts, &tr); + n = serial_write(device->port, command + ptr, 1); + if (n != 1) { + ERROR (abstract->context, "Failed to send the command."); + return EXITCODE (n); + } + } + + if (high_speed) { + struct timespec tr, ts = {0}; + ts.tv_nsec = 45 * 1000000; // 45 ms + nanosleep(&ts, &tr); + + // Weird but I only get the right result when I do it twice + // Rates are odd, like 825600 for the EMC, 115200 for commander + serial_configure(device->port, device->data.high_baud, 8, + SERIAL_PARITY_NONE, 2, SERIAL_FLOWCONTROL_NONE); + serial_configure(device->port, device->data.high_baud, 8, + SERIAL_PARITY_NONE, 2, SERIAL_FLOWCONTROL_NONE); + } + + // Receive the answer from the device. + // Use 1024 byte "packets" so we can display progress. + while (bytes_read < asize) { + if (asize - bytes_read > 1024) + read_size = 1024; + else + read_size = asize - bytes_read; + + n = serial_read (device->port, answer + bytes_read, read_size); + if (n != read_size) { + ERROR (abstract->context, "Failed to receive data, expected %u, read %u.", read_size, n); + return EXITCODE (n); + } + + bytes_read += n; + + if (device->progress) { + device->progress->current = bytes_read; + device_event_emit (abstract, DC_EVENT_PROGRESS, device->progress); + } + } + + return DC_STATUS_SUCCESS; +} + + +dc_status_t +cochran_common_serial_open(cochran_device_t *device, dc_context_t *context) +{ + // Open the device. + int rc = serial_open (&device->port, context, device->name); + if (rc == -1) { + ERROR (context, "Failed to open the serial port."); + free (device); + return DC_STATUS_IO; + } + + // Set the serial communication protocol (9600 8N2, no FC). + rc = serial_configure (device->port, 9600, 8, SERIAL_PARITY_NONE, + 2, SERIAL_FLOWCONTROL_NONE); + if (rc == -1) { + ERROR (context, "Failed to set the terminal attributes."); + serial_close (device->port); + free (device); + return DC_STATUS_IO; + } + + serial_set_queue_size(device->port, 4096, 4096); + + // Make sure everything is in a sane state. + // Mimicing Analyst software with excessive flushes + serial_flush (device->port, SERIAL_QUEUE_OUTPUT); + serial_flush (device->port, SERIAL_QUEUE_INPUT); + serial_flush (device->port, SERIAL_QUEUE_INPUT); + serial_flush (device->port, SERIAL_QUEUE_INPUT); + serial_flush (device->port, SERIAL_QUEUE_INPUT); + serial_flush (device->port, SERIAL_QUEUE_INPUT); + serial_flush (device->port, SERIAL_QUEUE_INPUT); + + serial_set_break(device->port, 1); + struct timespec tr, ts = {0}; + ts.tv_nsec = 16 * 1000000; // 16ms + nanosleep(&ts, &tr); + + serial_set_break(device->port, 0); + + // Set the timeout for receiving data (5000 ms). + if (serial_set_timeout (device->port, 5000) == -1) { + ERROR (context, "Failed to set the timeout."); + serial_close (device->port); + free (device); + return DC_STATUS_IO; + } + + // Wait for heartbeat byte before send + int n; + char answer[1]; + if ((n = serial_read(device->port, answer, 1)) != 1) { + ERROR (context, "Failed to receive device heartbeat."); + return EXITCODE (n); + } + + return DC_STATUS_SUCCESS; +} + + +dc_status_t +cochran_common_device_open (dc_device_t **out, dc_context_t *context, const char *name, const dc_device_vtable_t *vtable) +{ + dc_status_t rc; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + cochran_device_t *device = (cochran_device_t *) malloc (sizeof (cochran_device_t)); + if (device == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Initialize the base class. + device_init (&device->base, context, vtable); + + // Set the default values. + device->port = NULL; + device->name = name; + device->progress = NULL; + device->data.logbook = NULL; + device->data.sample = NULL; + cochran_common_device_set_fingerprint((dc_device_t *) device, "", 0); + + if ((rc = cochran_common_serial_open(device, context)) != DC_STATUS_SUCCESS) + return rc; + + // Read ID from the device + rc = cochran_read_id((dc_device_t *) device); + + if (rc != DC_STATUS_SUCCESS) { + ERROR (context, "Device not responding."); + serial_close (device->port); + free (device->data.id); + free (device->data.id0); + free (device); + return rc; + } + + *out = (dc_device_t *) device; + + return DC_STATUS_SUCCESS; +} + + +dc_status_t +cochran_common_device_close (dc_device_t *abstract) +{ + cochran_device_t *device = (cochran_device_t*) abstract; + + // Close the device. + if (serial_close (device->port) == -1) { + free (device); + return DC_STATUS_IO; + } + + // Free memory. + free (device->data.id); + free (device->data.id0); + free (device->data.config1); + free (device->data.config2); + free (device->data.config3); + free (device->data.config4); + free (device->data.misc); + free (device->data.logbook); + free (device->data.sample); + free (device); + + return DC_STATUS_SUCCESS; +} + + +dc_status_t +cochran_common_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size) +{ + cochran_device_t *device = (cochran_device_t *) abstract; + cochran_data_t *d = &(device->data); + + if (size && size != sizeof (d->fingerprint)) + return DC_STATUS_INVALIDARGS; + + if (size) + memcpy (&(d->fingerprint), data, sizeof (d->fingerprint)); + else + memset (&(d->fingerprint), 0xFF, sizeof (d->fingerprint)); + + return DC_STATUS_SUCCESS; +} + + +dc_status_t +cochran_common_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size) +{ + cochran_device_t *device = (cochran_device_t*) abstract; + + // Build the command + unsigned char command[10]; + unsigned char command_size; + + switch (device->data.address_length) + { + case COCHRAN_ADDRESS_LENGTH_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 COCHRAN_ADDRESS_LENGTH_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; + } + + // Read data at high speed + dc_status_t rc = cochran_packet (device, command, command_size, data, size, 1); + if (rc != DC_STATUS_SUCCESS) + return rc; + + return DC_STATUS_SUCCESS; +} + + +static void +cochran_set_device_config (cochran_device_t *device) +{ + dc_device_t *abstract = (dc_device_t *) device; + // Determine model + if (memcmp(device->data.id + 0x3B, "AM2315\xA3\x71", 8) == 0) + { + device->data.model = COCHRAN_MODEL_EMC_20; + device->data.log_size = 512; + device->data.sample_memory_start_address = 0x94000; + device->data.dive_num_ptr = 0x56; + device->data.dive_count_ptr = 0xD2; + device->data.dive_count_endian = COCHRAN_LE_TYPE; + device->data.sample_end_ptr = 256; + device->data.log_pre_dive_ptr = 30; + device->data.log_end_dive_ptr = 256; + device->data.last_interdive_ptr = 233; + device->data.last_entry_ptr = 194; + device->data.date_format = COCHRAN_DATE_FORMAT_SMHDMY; + device->data.address_length = COCHRAN_ADDRESS_LENGTH_32; + device->data.high_baud = 825600; + } + else if (memcmp(device->data.id + 0x3B, "AMA315\xC3\xC5", 8) == 0) + { + device->data.model = COCHRAN_MODEL_EMC_16; + device->data.log_size = 512; + device->data.sample_memory_start_address = 0x94000; + device->data.dive_num_ptr = 0x56; + device->data.dive_count_ptr = 0xD2; + device->data.dive_count_endian = COCHRAN_LE_TYPE; + device->data.sample_end_ptr = 256; + device->data.log_pre_dive_ptr = 30; + device->data.log_end_dive_ptr = 256; + device->data.last_interdive_ptr = 233; + device->data.last_entry_ptr = 194; + device->data.date_format = COCHRAN_DATE_FORMAT_SMHDMY; + device->data.address_length = COCHRAN_ADDRESS_LENGTH_32; + device->data.high_baud = 825600; + } + else if (memcmp(device->data.id + 0x3B, "AM\x11\x32\x32\x31\x32\x02", 8) == 0) + { + device->data.model = COCHRAN_MODEL_COMMANDER_AIR_NITROX; + device->data.log_size = 256; + device->data.sample_memory_start_address = 0x20000; + device->data.dive_num_ptr = 0x46; + device->data.dive_count_ptr = 0x46; + device->data.dive_count_endian = COCHRAN_BE_TYPE; + device->data.sample_end_ptr = 256; + device->data.log_pre_dive_ptr = 30; + device->data.log_end_dive_ptr = 128; + device->data.last_interdive_ptr = 167; + device->data.last_entry_ptr = -1; + device->data.date_format = COCHRAN_DATE_FORMAT_MSDHYM; + device->data.address_length = COCHRAN_ADDRESS_LENGTH_24; + device->data.high_baud = 115200; + } + else + { + device->data.model = 0; + ERROR (abstract->context, + "Unknown Cochran model %02x %02x %02x %02x %02x %02x %02x %02x", + *(device->data.id + 0x3B), *(device->data.id + 0x3B + 1), + *(device->data.id + 0x3B + 2), *(device->data.id + 0x3B + 3), + *(device->data.id + 0x3B + 4), *(device->data.id + 0x3B + 5), + *(device->data.id + 0x3B + 6), *(device->data.id + 0x3B + 7)); + } + + return; +} + + +static dc_status_t +cochran_read_id (dc_device_t *abstract) +{ + cochran_device_t *device = (cochran_device_t *) abstract; + dc_status_t rc; + unsigned char command[6] = {0x05, 0x9D, 0xFF, 0x00, 0x43, 0x00}; + + device->data.id = (unsigned char *) malloc(67); + if (device->data.id == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + rc = cochran_packet(device, command, 6, device->data.id, 67, 0); + if (rc != DC_STATUS_SUCCESS) + return rc; + + if (strncmp(device->data.id, "(C)", 3) != 0) { + // It's a Commander, read again + device->data.id0 = device->data.id; + + command[1] = 0xBD; + command[2] = 0x7F; + + device->data.id = (unsigned char *) malloc(67); + if (device->data.id == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + rc = cochran_packet(device, command, 6, device->data.id, 67, 0); + if (rc != DC_STATUS_SUCCESS) + return rc; + } + + cochran_set_device_config(device); + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +cochran_read_config (dc_device_t *abstract) +{ + cochran_device_t *device = (cochran_device_t *) abstract; + cochran_data_t *data = &device->data; + dc_status_t rc; + unsigned char command[2] = { 0x96, 0x00 }; + + data->config1 = (unsigned char *) malloc(512); + if (data->config1 == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + rc = cochran_packet(device, command, 2, data->config1, 512, 0); + if (rc != DC_STATUS_SUCCESS) + return rc; + + data->last_interdive_offset = + array_uint32_le (data->config1 + data->last_interdive_ptr); + data->last_entry_offset = + array_uint32_le (data->config1 + data->last_entry_ptr); + + // Read second page + command[1] = 0x01; + + data->config2 = (unsigned char *) malloc(512); + if (data->config2 == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + rc = cochran_packet(device, command, 2, data->config2, 512, 0); + if (rc != DC_STATUS_SUCCESS) + return rc; + + // EMC only seems to have two config pages + if ((data->model & 0xFF0000) == COCHRAN_MODEL_EMC_FAMILY) + return DC_STATUS_SUCCESS; + + // Go on reading the extra configs for a Commander + // Read third page + command[1] = 0x02; + + data->config3 = (unsigned char *) malloc(512); + if (data->config3 == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + rc = cochran_packet(device, command, 2, data->config3, 512, 0); + if (rc != DC_STATUS_SUCCESS) + return rc; + + // Read fourth page + command[1] = 0x03; + + data->config4 = (unsigned char *) malloc(512); + if (data->config4 == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + rc = cochran_packet(device, command, 2, data->config4, 512, 0); + + return rc; +} + + +static dc_status_t +cochran_read_misc (dc_device_t *abstract) +{ + cochran_device_t *device = (cochran_device_t *) abstract; + + unsigned char command[7] = { 0x89, 0x05, 0x00, 0x00, 0x00, 0xDC, 0x05 }; + + switch (device->data.model & 0xFF0000) + { + case COCHRAN_MODEL_COMMANDER_FAMILY: + command[2] = 0xCA; + command[3] = 0xFD; + break; + case COCHRAN_MODEL_EMC_FAMILY: + command[2] = 0xE0; + command[3] = 0x03; + break; + default: + return DC_STATUS_UNSUPPORTED; + } + + device->data.misc = (unsigned char *) malloc(1500); + if (device->data.misc == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Send first byte then wait for heartbeat before sding the rest + serial_write(device->port, command, 1); + + int n; + char answer[1]; + if ((n = serial_read(device->port, answer, 1)) != 1) { + ERROR (abstract->context, "Failed to receive device heartbeat."); + return EXITCODE (n); + } + + return cochran_packet(device, command + 1, 6, device->data.misc, 1500, 0); +} + + +static dc_status_t +cochran_read_logbook (dc_device_t *abstract) +{ + cochran_device_t *device = (cochran_device_t *) abstract; + cochran_data_t *d = &(device->data); + dc_status_t rc; + + // Determine size of dive list to read. Round up to nearest 16K + if (d->dive_count_endian == COCHRAN_LE_TYPE) + d->dive_count = array_uint16_le (d->config1 + d->dive_count_ptr); + else + d->dive_count = array_uint16_be (d->config1 + d->dive_count_ptr); + + d->logbook_size = ((d->dive_count * d->log_size) & 0xFFFFC000) + + 0x4000; + + // Allocate space for log book. + d->logbook = (unsigned char *) malloc(d->logbook_size); + if (device == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Enable progress notifications. + device->progress = malloc(sizeof(dc_event_progress_t)); + if (device->progress == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + device->progress->current = 0; + device->progress->maximum = d->logbook_size; + device_event_emit (abstract, DC_EVENT_PROGRESS, device->progress); + + // We have to close and restart to get the DC's attention + serial_close(device->port); + + struct timespec tr, ts = {0, 800 * 1000000}; // 800ms + nanosleep(&ts, &tr); + + cochran_common_serial_open(device, abstract->context); + + // Request log book + rc = cochran_common_device_read(abstract, 0, d->logbook, d->logbook_size); + + device->progress->current = d->logbook_size; + device_event_emit (abstract, DC_EVENT_PROGRESS, device->progress); + free (device->progress); + + return rc; +} + + +static void +cochran_find_fingerprint(dc_device_t *abstract) +{ + cochran_device_t *device = (cochran_device_t *) abstract; + cochran_data_t *d = (cochran_data_t *) &(device->data); + + // Skip to fingerprint to reduce time + d->fp_dive_num = d->dive_count - 1; + + while (d->fp_dive_num >= 0 && memcmp(&(d->fingerprint), + d->logbook + d->fp_dive_num * d->log_size + + d->dive_num_ptr, + sizeof(d->fingerprint))) + d->fp_dive_num--; +} + + +static void +cochran_read_parms(dc_device_t *abstract, int *low_offset, int *high_offset) +{ + cochran_device_t *device = (cochran_device_t *) abstract; + cochran_data_t *d = (cochran_data_t *) &(device->data); + unsigned int pre_dive_offset, end_dive_offset; + + // Find lowest and highest offsets into sample data + *low_offset = 0xFFFFFFFF; + *high_offset = 0; + + int i; + for (i = d->fp_dive_num + 1; i < d->dive_count; i++) { + pre_dive_offset = array_uint32_le (&(d->logbook[i * d->log_size + + d->log_pre_dive_ptr])); + end_dive_offset = array_uint32_le (&(d->logbook[i * d->log_size + + d->log_end_dive_ptr])); + + // 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) { + // Since I can't tell how much memory it has, I'll round. + // I'll round to 128K, dives longer than 12 hrs aren't likely + // and memory in sizes not rounded to 128K might be odd. + *high_offset = ((pre_dive_offset - 1) & 0xE0000) + 0x20000; + d->sample_memory_end_address = *high_offset; + *low_offset = d->sample_memory_start_address; + d->sample_data_offset = *low_offset; + d->sample_size = *high_offset - *low_offset; + } else if (*low_offset < 0xFFFFFFFF && *high_offset > 0) { + // Round offset and size to 16K boundary + d->sample_data_offset = *low_offset & 0xFFFFC000; + *high_offset = ((*high_offset - 1) & 0xFFFFC000) + 0x4000; + d->sample_size = *high_offset - d->sample_data_offset; + } else { + d->sample_data_offset = 0; + d->sample_size = 0; + } +} + + +static dc_status_t +cochran_read_samples(dc_device_t *abstract) +{ + cochran_device_t *device = (cochran_device_t *) abstract; + cochran_data_t *d = (cochran_data_t *) &(device->data); + int low_offset, high_offset; + dc_status_t rc; + + + cochran_find_fingerprint(abstract); + cochran_read_parms(abstract, &low_offset, &high_offset); + + if (d->sample_size > 0) { + d->sample = (unsigned char *) malloc(d->sample_size); + if (device == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // We have to close the serial port to get the DC's attention + serial_close(device->port); + + // Enable progress notifications. + device->progress = malloc(sizeof(dc_event_progress_t)); + if (device->progress == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + device->progress->current = 0; + device->progress->maximum = d->sample_size; + device_event_emit (abstract, DC_EVENT_PROGRESS, device->progress); + + struct timespec tr, ts = {0, 800* 1000000}; // 800 ms + nanosleep(&ts, &tr); + + cochran_common_serial_open(device, abstract->context); + + // Read the sample data + rc = cochran_common_device_read (abstract, d->sample_data_offset, d->sample, d->sample_size); + if (rc != DC_STATUS_SUCCESS) { + free (device->progress); + ERROR (abstract->context, "Failed to read the sample data."); + return rc; + } + + device->progress->current = d->sample_size; + device_event_emit (abstract, DC_EVENT_PROGRESS, device->progress); + free (device->progress); + } + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +cochran_common_device_read_all (dc_device_t *abstract) +{ + dc_status_t rc; + + // Read config + rc = cochran_read_config(abstract); + if (rc != DC_STATUS_SUCCESS) + return rc; + + rc = cochran_read_misc(abstract); + if (rc != DC_STATUS_SUCCESS) + return rc; + + rc = cochran_read_logbook(abstract); + if (rc != DC_STATUS_SUCCESS) + return rc; + + rc = cochran_read_samples(abstract); + if (rc != DC_STATUS_SUCCESS) + return rc; + + return DC_STATUS_SUCCESS; +} + + +dc_status_t +cochran_common_device_dump (dc_device_t *abstract, dc_buffer_t *data) +{ + cochran_device_t *device = (cochran_device_t *) abstract; + cochran_data_t *d = (cochran_data_t *) &(device->data); + + struct cochran_data_dump *dump = (struct cochran_data_dump *) data; + + dc_status_t rc; + + rc = cochran_common_device_read_all (abstract); + + if (rc != DC_STATUS_SUCCESS) + return rc; + + // Copy data for permanency. + + if (d->id0) { + dump->id0 = dc_buffer_new(67); + dc_buffer_append (dump->id0, d->id0, 67); + } + + if (d->id) { + dump->id1 = dc_buffer_new(67); + dc_buffer_append (dump->id1, d->id, 67); + } + + if (d->config1) { + dump->config1 = dc_buffer_new(512); + dc_buffer_append (dump->config1, d->config1, 512); + } + + if (d->config2) { + dump->config2 = dc_buffer_new(512); + dc_buffer_append (dump->config2, d->config2, 512); + } + + if (d->config3) { + dump->config3 = dc_buffer_new(512); + dc_buffer_append (dump->config3, d->config3, 512); + } + + if (d->config4) { + dump->config4 = dc_buffer_new(512); + dc_buffer_append (dump->config4, d->config4, 512); + } + + if (d->misc) { + dump->misc = dc_buffer_new(1500); + dc_buffer_append (dump->misc, d->misc, 1500); + } + + if (d->logbook) { + dump->logbook = dc_buffer_new(d->logbook_size); + dc_buffer_append (dump->logbook, d->logbook, d->logbook_size); + } + + if (d->sample) { + dump->sample = dc_buffer_new(d->sample_size); + dc_buffer_append (dump->sample, d->sample, d->sample_size); + } + + return DC_STATUS_SUCCESS; +} + + + +dc_status_t +cochran_common_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) +{ + cochran_device_t *device = (cochran_device_t *) abstract; + cochran_data_t *d = &(device->data); + unsigned int sample_start_offset, sample_end_offset; + struct tm t; + dc_status_t rc; + + rc = cochran_common_device_read_all (abstract); + + if (rc != DC_STATUS_SUCCESS) + return rc; + + // Loop through each dive + int i; + for (i = d->dive_count - 1; i > d->fp_dive_num; i--) { + + d->current_log = d->logbook + i * d->log_size; + + sample_start_offset = array_uint32_le (d->current_log + 6); + sample_end_offset = array_uint32_le (d->current_log + + d->log_size/2); + + d->current_sample = d->sample + sample_start_offset + - d->sample_data_offset; + + // Check for corrupt post-dive section + if (array_uint32_le(d->current_log + d->log_size/2) == 0xFFFFFFFF) + d->corrupt_dive = 1; + else + d->corrupt_dive = 0; + + // Check for ring buffer wrap + if (sample_start_offset > sample_end_offset) + d->current_sample_size = d->sample_memory_end_address + - sample_start_offset + 1 + sample_end_offset + - d->sample_memory_start_address + 1; + else + d->current_sample_size = sample_end_offset - sample_start_offset; + + d->current_fingerprint = d->current_log + d->dive_num_ptr; + + if (d->date_format == COCHRAN_DATE_FORMAT_SMHDMY) { + t.tm_sec = d->current_log[0]; + t.tm_min = d->current_log[1]; + t.tm_hour = d->current_log[2]; + t.tm_mday = d->current_log[3]; + t.tm_mon = d->current_log[4]; + t.tm_year = d->current_log[5]; + t.tm_wday = t.tm_yday = t.tm_isdst = 0; + } else { + t.tm_sec = d->current_log[1]; + t.tm_min = d->current_log[0]; + t.tm_hour = d->current_log[3]; + t.tm_mday = d->current_log[2]; + t.tm_mon = d->current_log[5]; + t.tm_year = d->current_log[4]; + t.tm_wday = t.tm_yday = t.tm_isdst = 0; + } + d->current_dive_start_time = mktime(&t); + + if (callback && !callback ((unsigned char *) d, sizeof(device->data), d->current_fingerprint, sizeof (d->fingerprint), userdata)) + return DC_STATUS_SUCCESS; + } + + return DC_STATUS_SUCCESS; +} diff --git a/src/cochran_common.h b/src/cochran_common.h new file mode 100644 index 0000000..136f153 --- /dev/null +++ b/src/cochran_common.h @@ -0,0 +1,109 @@ +/* + * 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 + */ + +// seconds to add to Cochran time stamps to get unix time +// Equivalent to Jan 1, 1992 00:00:00 +#define COCHRAN_TIMESTAMP_OFFSET 694224000 + +#define COCHRAN_LE_TYPE 0 +#define COCHRAN_BE_TYPE 1 + +#define COCHRAN_DATE_FORMAT_SMHDMY 0 +#define COCHRAN_DATE_FORMAT_MSDHYM 1 + +#define COCHRAN_ADDRESS_LENGTH_32 0 +#define COCHRAN_ADDRESS_LENGTH_24 1 + +typedef enum cochran_model_t { + COCHRAN_MODEL_UNKNOWN = 0, + COCHRAN_MODEL_EMC_FAMILY = 1 << 16, + COCHRAN_MODEL_EMC_14, + COCHRAN_MODEL_EMC_16, + COCHRAN_MODEL_EMC_20, + COCHRAN_MODEL_COMMANDER_FAMILY = 2 << 16, + COCHRAN_MODEL_COMMANDER_AIR_NITROX, +} cochran_model_t; + +typedef struct cochran_data_t { + cochran_model_t model; + + unsigned char *id0; + unsigned char *id; + unsigned char *config1; + unsigned char *config2; + unsigned char *config3; + unsigned char *config4; + unsigned char *misc; + unsigned char *logbook; + unsigned char *sample; + + unsigned short int dive_count; + unsigned char fingerprint[2]; + int fp_dive_num; + + unsigned int logbook_size; + unsigned int current_sample_size; + + unsigned int sample_data_offset; + unsigned int sample_size; + unsigned int last_interdive_offset; + unsigned int last_entry_offset; + + unsigned char *current_fingerprint; + unsigned char *current_log; + unsigned char *current_sample; + time_t current_dive_start_time; + + // Config items + int log_size; + int sample_memory_start_address; + int sample_memory_end_address; + int dive_num_ptr; + int dive_count_ptr; + int dive_count_endian; + int sample_end_ptr; + int log_pre_dive_ptr; + int log_end_dive_ptr; + int last_interdive_ptr; + int last_entry_ptr; + int date_format; + int address_length; + int high_baud; // baud rate to switch to for log/sample download + + unsigned char corrupt_dive; +} cochran_data_t; + +typedef struct cochran_device_t { + dc_device_t base; + const char *name; // serial port name + serial_t *port; + cochran_data_t data; // dive data used in parsing + dc_event_progress_t *progress; // We have to do progress in the _read function +} cochran_device_t; + +dc_status_t cochran_packet (cochran_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, int high_speed); +dc_status_t cochran_common_device_open (dc_device_t **out, dc_context_t *context, const char *name, const dc_device_vtable_t *vtable); +dc_status_t cochran_common_device_close (dc_device_t *abstract); +dc_status_t cochran_common_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); +dc_status_t cochran_common_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size); +static dc_status_t cochran_read_id (dc_device_t *abstract); +dc_status_t cochran_common_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +dc_status_t cochran_common_device_dump (dc_device_t *abstract, dc_buffer_t *data); diff --git a/src/cochran_common_parser.c b/src/cochran_common_parser.c new file mode 100644 index 0000000..744e5e7 --- /dev/null +++ b/src/cochran_common_parser.c @@ -0,0 +1,157 @@ +/* + * 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 <libdivecomputer/units.h> +#include <libdivecomputer/cochran.h> + +#include "context-private.h" +#include "device-private.h" +#include "parser-private.h" +#include "serial.h" +#include "array.h" + +#include "cochran_common.h" +#include "cochran_common_parser.h" + + +dc_status_t cochran_common_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +dc_status_t cochran_common_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); +dc_status_t cochran_common_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); +dc_status_t cochran_common_parser_destroy (dc_parser_t *abstract); +int cochran_common_handle_event (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata, unsigned char code, unsigned int offset, unsigned int time); + + +dc_status_t +cochran_common_parser_create (dc_parser_t **out, dc_context_t *context, const dc_parser_vtable_t *vtable) +{ + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + dc_parser_t *parser = (dc_parser_t *) malloc (sizeof (dc_parser_t)); + if (parser == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + parser_init (parser, context, vtable); + + *out = parser; + + return DC_STATUS_SUCCESS; +} + + +dc_status_t +cochran_common_parser_destroy (dc_parser_t *abstract) +{ + // Free memory. + free (abstract); + + return DC_STATUS_SUCCESS; +} + + +dc_status_t +cochran_common_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) +{ + abstract->data = data; + abstract->size = size; + + return DC_STATUS_SUCCESS; +} + + +// There are two date formats used by Cochran +dc_status_t +cochran_common_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) +{ + cochran_data_t *data = (cochran_data_t *) abstract->data; + const unsigned char *log = data->current_log; + + if (data->date_format == COCHRAN_DATE_FORMAT_SMHDMY) { + datetime->second = log[0]; + datetime->minute = log[1]; + datetime->hour = log[2]; + datetime->day = log[3]; + datetime->month = log[4]; + datetime->year = log[5] + (log[5] > 91 ? 1900 : 2000); + } else { + datetime->second = log[1]; + datetime->minute = log[0]; + datetime->hour = log[3]; + datetime->day = log[2]; + datetime->month = log[5]; + datetime->year = log[4] + (log[5] > 91 ? 1900 : 2000); + } + + return DC_STATUS_SUCCESS; +} + + +int +cochran_common_handle_event (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata, unsigned char code, unsigned int offset, unsigned int time) +{ + cochran_data_t *data = (cochran_data_t *) abstract->data; + cochran_events_t *e = cochran_events; + + dc_sample_value_t sample = {0}; + + unsigned char event_ptr = 0; + + while (e[event_ptr].code && e[event_ptr].code != code) + event_ptr++; + + sample.event.time = 0; + + if (e[event_ptr].code) { + switch (e[event_ptr].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; + default: + // Don't send known events of type NONE + if (! e[event_ptr].type == SAMPLE_EVENT_NONE) { + sample.event.type = e[event_ptr].type; + sample.event.flags = e[event_ptr].flag; + if (callback) callback (DC_SAMPLE_EVENT, sample, userdata); + } + } + } else { + // Unknown event, send it so we know we missed something + sample.event.type = SAMPLE_EVENT_NONE; + sample.event.flags = SAMPLE_FLAGS_NONE; + sample.event.value = code; + if (callback) callback (DC_SAMPLE_EVENT, sample, userdata); + } + + return e[event_ptr].data_bytes; +} diff --git a/src/cochran_common_parser.h b/src/cochran_common_parser.h new file mode 100644 index 0000000..d19a619 --- /dev/null +++ b/src/cochran_common_parser.h @@ -0,0 +1,80 @@ +/* + * 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 + */ + +typedef struct cochran_events_t { + unsigned char code; + unsigned char data_bytes; + char *name; + parser_sample_event_t type; + parser_sample_flags_t flag; +} cochran_events_t; + +static cochran_events_t cochran_events[] = { + { 0xA8, 1, "Entered PDI mode", + SAMPLE_EVENT_SURFACE, SAMPLE_FLAGS_BEGIN }, + { 0xA9, 1, "Exited PDI mode", + SAMPLE_EVENT_SURFACE, SAMPLE_FLAGS_END }, + { 0xAB, 5, "Ceiling decrease", + SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE }, + { 0xAD, 5, "Ceiling increase", + SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE }, + { 0xC0, 1, "Switched to FO2 21% mode", + SAMPLE_EVENT_GASCHANGE, SAMPLE_FLAGS_NONE }, + { 0xC1, 1, "Ascent rate greater than limit", + SAMPLE_EVENT_ASCENT, SAMPLE_FLAGS_BEGIN }, + { 0xC2, 1, "Low battery warning", + SAMPLE_EVENT_BATTERY, SAMPLE_FLAGS_NONE }, + { 0xC3, 1, "CNS Oxygen toxicity warning", + SAMPLE_EVENT_OLF, SAMPLE_FLAGS_NONE }, + { 0xC4, 1, "Depth exceeds user set point", + SAMPLE_EVENT_MAXDEPTH, SAMPLE_FLAGS_NONE }, + { 0xC5, 1, "Entered decompression mode", + SAMPLE_EVENT_DEEPSTOP, SAMPLE_FLAGS_BEGIN }, + { 0xC8, 1, "PO2 too high", + SAMPLE_EVENT_FLOOR, SAMPLE_FLAGS_BEGIN }, + { 0xCE, 1, "Non-decompression warning", + SAMPLE_EVENT_RBT, SAMPLE_FLAGS_BEGIN }, + { 0xD6, 1, "Depth is less than ceiling", + SAMPLE_EVENT_CEILING, SAMPLE_FLAGS_BEGIN }, + { 0xD8, 1, "End decompression mode", + SAMPLE_EVENT_DEEPSTOP, SAMPLE_FLAGS_END }, + { 0xE1, 1, "End ascent rate warning", + SAMPLE_EVENT_ASCENT, SAMPLE_FLAGS_END }, + { 0xE3, 1, "Switched to FO2 mode", + SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE }, + { 0xEE, 1, "End non-decompresison warning", + SAMPLE_EVENT_RBT, SAMPLE_FLAGS_END }, + { 0xEF, 1, "Switch to blend 2", + SAMPLE_EVENT_GASCHANGE2, SAMPLE_FLAGS_NONE }, + { 0xF3, 1, "Switch to blend 1", + SAMPLE_EVENT_GASCHANGE2, SAMPLE_FLAGS_NONE }, + { 0xF6, 1, "End Depth is less than ceiling", + SAMPLE_EVENT_CEILING, SAMPLE_FLAGS_END }, + { 0x00, 1, NULL, + SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE } +}; + + +dc_status_t cochran_common_parser_create (dc_parser_t **out, dc_context_t *context, const dc_parser_vtable_t *vtable); +dc_status_t cochran_common_parser_destroy (dc_parser_t *abstract); +dc_status_t cochran_common_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +dc_status_t cochran_common_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); +int cochran_common_handle_event (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata, unsigned char code, unsigned int offset, unsigned int time); diff --git a/src/cochran_emc.c b/src/cochran_emc.c new file mode 100644 index 0000000..c4b0367 --- /dev/null +++ b/src/cochran_emc.c @@ -0,0 +1,67 @@ +/* + * 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 <time.h> + +#include <libdivecomputer/cochran.h> + +#include "context-private.h" +#include "device-private.h" +#include "serial.h" + +#include "cochran_common.h" +#include "cochran_emc.h" + + +static const dc_device_vtable_t cochran_emc_device_vtable = { + DC_FAMILY_COCHRAN_EMC, + cochran_common_device_set_fingerprint, /* set_fingerprint */ + cochran_common_device_read, /* read */ + NULL, /* write */ + cochran_common_device_dump, /* dump */ + cochran_common_device_foreach, /* foreach */ + cochran_common_device_close /* close */ +}; + + +dc_status_t +cochran_emc_device_open (dc_device_t **out, dc_context_t *context, const char *name) +{ + dc_status_t rc; + + rc = cochran_common_device_open(out, context, name, &cochran_emc_device_vtable); + if (rc != DC_STATUS_SUCCESS) + return rc; + + cochran_device_t *device = (cochran_device_t *) *out; + + // Check family + if ((device->data.model & 0xFF0000) != COCHRAN_MODEL_EMC_FAMILY) { + ERROR (context, "Device not recognized."); + serial_close (device->port); + free (device->data.id); + free (device); + return DC_STATUS_UNSUPPORTED; + } + + return DC_STATUS_SUCCESS; +} diff --git a/src/cochran_emc.h b/src/cochran_emc.h new file mode 100644 index 0000000..e21036b --- /dev/null +++ b/src/cochran_emc.h @@ -0,0 +1,180 @@ +/* + * 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 + */ + +// 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 struct cochran_emc_log_t cochran_emc_log_t; + +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; + + +typedef 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 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)); + +typedef struct cochran_emc_config1_t cochran_emc_config1_t; diff --git a/src/cochran_emc_parser.c b/src/cochran_emc_parser.c new file mode 100644 index 0000000..cf6e9af --- /dev/null +++ b/src/cochran_emc_parser.c @@ -0,0 +1,399 @@ +/* + * libdivecomputer + * + * Copyright (C) 2014 John Van Ostrand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include <stdlib.h> +#include <math.h> + +#include <libdivecomputer/units.h> +#include <libdivecomputer/cochran.h> + +#include "context-private.h" +#include "device-private.h" +#include "parser-private.h" +#include "serial.h" +#include "array.h" + +#include "cochran_common.h" +#include "cochran_emc.h" +#include "cochran_common_parser.h" + + +struct dive_stats { + unsigned int dive_time; + float max_depth; + float avg_depth; + float min_temp; +}; + + +static dc_status_t cochran_emc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); +static dc_status_t cochran_emc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); +static void cochran_emc_parse_dive_stats (dc_parser_t *abstract, struct dive_stats *stats); + + +static dc_parser_vtable_t cochran_emc_parser_vtable = { + DC_FAMILY_COCHRAN_EMC, + cochran_common_parser_set_data, /* set_data */ + cochran_common_parser_get_datetime, /* datetime */ + cochran_emc_parser_get_field, /* fields */ + cochran_emc_parser_samples_foreach, /* samples_foreach */ + cochran_common_parser_destroy /* destroy */ +}; + + +dc_status_t +cochran_emc_parser_create (dc_parser_t **out, dc_context_t *context) +{ + return cochran_common_parser_create(out, context, &cochran_emc_parser_vtable); +} + + +static dc_status_t +cochran_emc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) +{ + cochran_data_t *data = (cochran_data_t *) abstract->data; + cochran_emc_log_t *log = (cochran_emc_log_t *) data->current_log; + + dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + dc_salinity_t *water = (dc_salinity_t *) value; + + unsigned int dive_time; + float max_depth, avg_depth, min_temp; + struct dive_stats stats; + + if (value) { + switch (type) { + case DC_FIELD_TEMPERATURE: + if (data->corrupt_dive) { + cochran_emc_parse_dive_stats(abstract, &stats); + *((unsigned int*) value) = min_temp; + } else + *((unsigned int*) value) = (log->start_temperature - 32) / 1.8; + break; + case DC_FIELD_DIVETIME: + if (data->corrupt_dive) { + cochran_emc_parse_dive_stats(abstract, &stats); + *((unsigned int*) value) = dive_time; + } else + *((unsigned int *) value) = array_uint16_le (log->bt) * 60; + break; + case DC_FIELD_MAXDEPTH: + if (data->corrupt_dive) { + cochran_emc_parse_dive_stats(abstract, &stats); + *((unsigned int*) value) = max_depth; + } else + *((double *) value) = array_uint16_le (log->max_depth) / 4 * FEET; + break; + case DC_FIELD_AVGDEPTH: + if (data->corrupt_dive) { + cochran_emc_parse_dive_stats(abstract, &stats); + *((unsigned int*) value) = avg_depth; + } else + *((double *) value) = array_uint16_le (log->avg_depth) / 4 * FEET; + break; + case DC_FIELD_GASMIX_COUNT: + *((unsigned int *) value) = 10; + break; + case DC_FIELD_GASMIX: + gasmix->oxygen = (double) array_uint16_le ((char *)log->o2_percent + 2 * flags) / 256 / 100; + gasmix->helium = (double) array_uint16_le ((char *)log->he_percent + 2 * flags) / 256 / 100; + gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; + break; + case DC_FIELD_SALINITY: + // 0 = low conductivity, 2 = high, maybe there's a 1? + water->type = ( log->water_conductivity == 0 ? DC_WATER_FRESH : DC_WATER_SALT ); + water->density = 1000 + 12.5 * log->water_conductivity; + break; + case DC_FIELD_ATMOSPHERIC: + *(double *) value = ATM / BAR * pow(1 - 0.0000225577 * (double) log->altitude * 250 * FEET, 5.25588); + break; + default: + return DC_STATUS_UNSUPPORTED; + } + } + + return DC_STATUS_SUCCESS; +} + +/* +* 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_emc_guess_sample_size(cochran_data_t *data) +{ + unsigned int size = 0; + unsigned int offset, next_offset, memory_start_offset, memory_end_offset; + + // Get memory boundries + memory_start_offset = array_uint32_le(data->config1 + + data->sample_memory_start_address); + memory_end_offset = array_uint32_le(data->config1 + + data->sample_memory_end_address); + + // see if there is a next sample + if (data->current_log - data->logbook + 512 >= data->logbook_size) { + // we are the last log entry, use maximum data + size = memory_end_offset - offset + 1; + } else { + offset = array_uint32_le(data->current_log + 6); + next_offset = array_uint32_le(data->current_log + 512 + 30); + + if (next_offset >= offset) { + size = next_offset - offset + 1; + } else { + // samples wrap to begin of memory + size = memory_end_offset - offset + next_offset + - memory_start_offset + 2; + } + } + + return (size); +} + + + +static dc_status_t +cochran_emc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +{ + cochran_data_t *data = (cochran_data_t *) abstract->data; + unsigned char *sdata = data->current_sample; + cochran_emc_log_t *log = (cochran_emc_log_t *) data->current_log; + unsigned int size = data->current_sample_size; + unsigned char *s; + + dc_sample_value_t sample = {0}, empty_sample = {0}; + unsigned int time = 0, last_sample_time; + unsigned int offset = 0; + double temperature; + double depth; + double ascent_rate; + unsigned char deco_obligation = 0; + unsigned int deco_ceiling = 0; + unsigned int deco_time = 0; + + if (data->corrupt_dive) { + // Size of sample data is wrong, make a guess + size = cochran_emc_guess_sample_size(data); + } + + // Cochran samples depth every second and varies between ascent rate + // and temp ever other second. + // In the 21st, 22nd, 23rd, 24th samples are NDL remaining, deco time and ceiling + + // Prime with values from the dive log section + depth = array_uint16_le (log->start_depth) / 256; + + last_sample_time = sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + + sample.depth = depth * FEET; + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + + sample.temperature = (log->start_temperature - 32) / 1.8; + if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); + + while (offset < size) { + sample = empty_sample; + + sample.time = time; + if (callback && last_sample_time != sample.time) { + // We haven't issued this time yet. + last_sample_time = sample.time; + callback (DC_SAMPLE_TIME, sample, userdata); + } + + s = sdata + offset; + + // If corrupt_dive end before offset + if (data->corrupt_dive) { + if ( s[0] == 0x10 + || s[0] == 0xFF + || s[0] == 0xA8) { + break; + } + if (time > 1 && (s[0] == 0xE3 || s[0] == 0xF3)) + break; + } + + // Check for event + if (s[0] & 0x80) { + offset += cochran_common_handle_event(abstract, callback, userdata, s[0], offset, time); + switch (s[0]) + { + case 0xC5: // Deco obligation begins + deco_obligation = 1; + break; + case 0xD8: // Deco obligation ends + deco_obligation = 0; + break; + case 0xAB: // Decrement ceiling (deeper) + deco_ceiling += 10; // feet + + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.time = (array_uint16_le(s + 3) + 1) * 60; + sample.deco.depth = deco_ceiling * FEET; + if (callback) callback(DC_SAMPLE_DECO, sample, userdata); + break; + case 0xAD: // Increment ceiling (shallower) + deco_ceiling -= 10; // feet + + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.depth = deco_ceiling * FEET; + deco_time = (array_uint16_le(s + 3) + 1) * 60; + if (callback) callback(DC_SAMPLE_DECO, sample, userdata); + break; + } + + continue; + } + + + // Depth is logged as change in feet, bit 0x40 means negative depth + depth += (float) (s[0] & 0x3F) / 4 * (s[0] & 0x40 ? -1 : 1); + sample.depth = depth * 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 + ascent_rate = (s[1] & 0x7f) / 4 * (s[0] & 0x80 ? 1 : -1); + sample.ascent_rate = ascent_rate * FEET; + if (callback) callback (DC_SAMPLE_ASCENT_RATE, sample, userdata); + } else { + // Temperature logged in half degrees F above 20 + temperature = s[1] / 2 + 20; + sample.temperature = (temperature - 32.0) / 1.8; + + if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); + } + + // Tissue load is in 20 samples, then 2 samples for RBT, + // and 2 for ceiling + switch (time % 24) + { + case 20: + if (deco_obligation) { + /* Deco time for deepest stop, unused */ + deco_time = (s[2] + s[5] * 256 + 1) * 60; + } else { + /* Get RBT */ + sample.rbt = s[2] + s[5] * 256 + 1; + if (callback) callback (DC_SAMPLE_RBT, sample, userdata); + /* Send deco NDL sample */ + sample.deco.time = sample.rbt * 60; // seconds + sample.deco.depth = 0; + if (callback) callback (DC_SAMPLE_DECO, sample, userdata); + } + break; + case 22: + /* Deco time, total obligation */ + if (deco_obligation) { + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.depth = deco_ceiling * FEET; + sample.deco.time = (s[2] + s[5] * 256 + 1) * 60; // minutes + if (callback) callback (DC_SAMPLE_DECO, sample, userdata); + } + break; + } + + time ++; + offset += 3; + } + + return DC_STATUS_SUCCESS; +} + +void cochran_emc_parse_dive_stats (dc_parser_t *abstract, struct dive_stats *stats) +{ + cochran_data_t *data = (cochran_data_t *) abstract->data; + unsigned char *sdata = data->current_sample; + cochran_emc_log_t *log = (cochran_emc_log_t *) data->current_log; + unsigned int size = data->current_sample_size; + unsigned char *s; + unsigned int offset = 0; + cochran_events_t *e = cochran_events; + unsigned char event_ptr; + float depth_m = 0, depth, temp; + unsigned int time = 0; + + // Check for corrupt dive + if (data->corrupt_dive) + { + // It's corrupt, guess at the sample size + size = cochran_emc_guess_sample_size(data); + } + + stats->max_depth = 0; + stats->min_temp = 999; + stats->avg_depth = 0; + + while (offset < size) { + s = sdata + offset; + + if ( s[0] == 0x10 + || s[0] == 0xFF + || s[0] == 0xA8) { + // End corrupted dive + break; + } + + if (time > 1 && (s[0] == 0xE3 || s[0] == 0xF3)) + break; + + // Check for event + if (s[0] & 0x80) { + event_ptr = 0; + while (e[event_ptr].code && e[event_ptr].code != s[0]) + event_ptr++; + + // Advance some bytes + if (e[event_ptr].code) + offset += e[event_ptr].data_bytes; + else + offset ++; + + continue; + } + + // Depth is logged as change in feet, bit 0x40 means negative depth + depth += (float) (s[0] & 0x3F) / 4 * (s[0] & 0x40 ? -1 : 1); + depth_m = depth * FEET; + + if (depth_m > stats->max_depth) stats->max_depth = depth_m; + + stats->avg_depth = (stats->avg_depth * (time - 1) + depth_m) / time; + + if (time % 2 != 0) { + // Temperature logged in half degrees F above 20 + temp = s[1] / 2 + 20; + temp = (temp - 32.0) / 1.8; + + if (temp < stats->min_temp) stats->min_temp = temp; + } + + time ++; + offset += 3; + } + + stats->dive_time = time - 1; +} diff --git a/src/descriptor.c b/src/descriptor.c index 403b67a..20f74e6 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -223,6 +223,9 @@ static const dc_descriptor_t g_descriptors[] = { {"Shearwater", "Petrel", DC_FAMILY_SHEARWATER_PETREL, 3}, /* Dive Rite NiTek Q */ {"Dive Rite", "NiTek Q", DC_FAMILY_DIVERITE_NITEKQ, 0}, + {"Cochran", "Commander", DC_FAMILY_COCHRAN_COMMANDER, 0}, + {"Cochran", "EMC-20H", DC_FAMILY_COCHRAN_EMC, 0}, + {"Cochran", "EMC-16", DC_FAMILY_COCHRAN_EMC, 0}, };
typedef struct dc_descriptor_iterator_t { diff --git a/src/device.c b/src/device.c index 9fad7a2..e8661b0 100644 --- a/src/device.c +++ b/src/device.c @@ -34,6 +34,7 @@ #include <libdivecomputer/atomics.h> #include <libdivecomputer/shearwater.h> #include <libdivecomputer/diverite.h> +#include <libdivecomputer/cochran.h>
#include "device-private.h" #include "context-private.h" @@ -154,6 +155,12 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr case DC_FAMILY_DIVERITE_NITEKQ: rc = diverite_nitekq_device_open (&device, context, name); break; + case DC_FAMILY_COCHRAN_COMMANDER: + rc = cochran_cmdr_device_open (&device, context, name); + break; + case DC_FAMILY_COCHRAN_EMC: + rc = cochran_emc_device_open (&device, context, name); + break; default: return DC_STATUS_INVALIDARGS; } diff --git a/src/libdivecomputer.symbols b/src/libdivecomputer.symbols index 14a96a8..681a5d6 100644 --- a/src/libdivecomputer.symbols +++ b/src/libdivecomputer.symbols @@ -67,6 +67,7 @@ atomics_cobalt_parser_set_calibration shearwater_predator_parser_create shearwater_petrel_parser_create diverite_nitekq_parser_create +cochran_emc_parser_create
dc_device_open dc_device_close @@ -166,3 +167,4 @@ shearwater_predator_extract_dives shearwater_petrel_device_open diverite_nitekq_device_open diverite_nitekq_extract_dives +cochran_emc_device_open diff --git a/src/parser.c b/src/parser.c index 7cc7f4e..dc6655b 100644 --- a/src/parser.c +++ b/src/parser.c @@ -32,6 +32,7 @@ #include <libdivecomputer/atomics.h> #include <libdivecomputer/shearwater.h> #include <libdivecomputer/diverite.h> +#include <libdivecomputer/cochran.h>
#include "parser-private.h" #include "device-private.h" @@ -131,6 +132,12 @@ dc_parser_new (dc_parser_t **out, dc_device_t *device) case DC_FAMILY_DIVERITE_NITEKQ: rc = diverite_nitekq_parser_create (&parser, context); break; + case DC_FAMILY_COCHRAN_COMMANDER: + rc = cochran_cmdr_parser_create (&parser, context); + break; + case DC_FAMILY_COCHRAN_EMC: + rc = cochran_emc_parser_create (&parser, context); + break; default: return DC_STATUS_INVALIDARGS; }
On 2014-10-07 18:09, John Van Ostrand wrote:
Specifically tested with EMC-20H and Commander Air_Nitrox this commit should support the families for the most part however there are may be differences in the DC signature, memory size, and other factors that will prevent opening the device and obtaining a clean import. Try it with other EMC and Commander devices and check library ERRORs for the signature information needed to add support.
First of all, sorry for the very late response. Please don't take this as a lack of interest! On the contrary. You're actually the first to contribute a complete new backend. Much appreciated!
Now, there are a couple of issues that needs to be addressed before this can be included in libdivecomputer:
You used a few Linux specific calls such as nanosleep() in your code. This obviously breaks the windows builds. The rule of thumb is that the dive computer backends should contain no platform specific code at all. All platform specific code should be moved to the platform specific modules. For example we already have a serial_sleep() function.
A related issue is that you used packed structures, which are a gcc extension. Although, I personally use mingw (gcc) for my Windows builds, the msvc compiler is supported too. Casting the raw data to a structure for easier parsing is non-portable anyway (e.g. little vs big endian). Therefore, in libdivecomputer we always de-serialize the data in a portable way using the array_uint{16,24,32}_{le,be} functions. During development the structures are indeed very convenient (I occasionally do that as well), but for the final version they should be replaced with something more portable.
Your dc_device_dump implementation uses a custom cochran_data_dump structure instead of the dc_buffer_t structure. You either need to get rid of this custom structure, or not implement this function. Right now if anyone calls this function with dc_buffer_t structure, bad things will happen. I guess this is also the reason why you added your own example application instead of the generic example application. If really necessary, you can implement and expose backend specific functions to support features outside the generic api.
You implemented two backends. One for the EMC and one for the Commander. But the communication protocol is roughly identical, and you already handle the differences in the common code. That means you only need a single backend, which supports multiple models. This is very common in other backends too. Just name your backend after one of them (for example DC_FAMILY_COCHRAN_EMC).
During the communication you close and re-open the serial port. Are you sure this is really necessary? What kind of interface does the Cochran use? A usb-serial chip (prolific, ftdi, etc)? Maybe you just need the right trick like toggling some serial lines to restart the communication? The reason why I'm asking is that in future versions, opening the serial port will likely be moved to the application side, and in that case re-opening the port will no longer be possible.
There are a few other issues as well, but I think it makes no sense to dig into the details before addressing the more critical issues first.
Jef
On Wed, Oct 22, 2014 at 3:23 AM, Jef Driesen jef@libdivecomputer.org wrote:
On 2014-10-07 18:09, John Van Ostrand wrote:
Specifically tested with EMC-20H and Commander Air_Nitrox this commit should support the families for the most part however there are may be differences in the DC signature, memory size, and other factors that will prevent opening the device and obtaining a clean import. Try it with other EMC and Commander devices and check library ERRORs for the signature information needed to add support.
First of all, sorry for the very late response. Please don't take this as a lack of interest! On the contrary. You're actually the first to contribute a complete new backend. Much appreciated!
It's nice to be appreciated, thanks.
Now, there are a couple of issues that needs to be addressed before this can be included in libdivecomputer:
You used a few Linux specific calls such as nanosleep() in your code. This obviously breaks the windows builds. The rule of thumb is that the dive computer backends should contain no platform specific code at all. All platform specific code should be moved to the platform specific modules. For example we already have a serial_sleep() function.
Those I should be able to change to serial_sleep.
A related issue is that you used packed structures, which are a gcc extension. Although, I personally use mingw (gcc) for my Windows builds, the msvc compiler is supported too. Casting the raw data to a structure for easier parsing is non-portable anyway (e.g. little vs big endian). Therefore, in libdivecomputer we always de-serialize the data in a portable way using the array_uint{16,24,32}_{le,be} functions. During development the structures are indeed very convenient (I occasionally do that as well), but for the final version they should be replaced with something more portable.
Those packed structures are the dive logs and I realize they have a lot more information than libdivecomputer needs but I wanted to save that research because it's been useful in debugging and decoding other model's logs and it may become useful in the future. You'll see that they are all char or char arrays that I either de-serialize in-line (sometimes for good reason) or use the array_uint* functions. I'd prefer to keep a structure in place rather than directly access select bytes. Would a union between a char array and the structure be a portable way of packing it? Would that be an option?
Your dc_device_dump implementation uses a custom cochran_data_dump
structure instead of the dc_buffer_t structure. You either need to get rid of this custom structure, or not implement this function. Right now if anyone calls this function with dc_buffer_t structure, bad things will happen. I guess this is also the reason why you added your own example application instead of the generic example application. If really necessary, you can implement and expose backend specific functions to support features outside the generic api.
I should be able to refactor that function to use dc_buffer properly. You mentioned that the dump function was more for debugging or bypassing libdivecomputer's foreach functions so I was liberal with it. I still want to export all the data so how about I use dc_buffer directly, put pointers to the data sections in first and stack the data in after, one big blob. Maybe use a union to a structure to easily decode it.
I wrote the cochran_download program because I wanted a way for a user to be able to download their data and send it to me easily. I assume there will be lots more work needed to support various DCs. The data produced can be used with a simulator I wrote.h
You implemented two backends. One for the EMC and one for the Commander.
But the communication protocol is roughly identical, and you already handle the differences in the common code. That means you only need a single backend, which supports multiple models. This is very common in other backends too. Just name your backend after one of them (for example DC_FAMILY_COCHRAN_EMC).
I thought that being very specific about model was important in not leading users to believe their computer was supported. There are significant differences between models and possibly within a model that has different features enabled that would cause the communication or decoding to fail. I have access for four different models with 3 purchased recently which means I don't have any duplicates to compare, duplicate models with different features enable to compare, and I can't tell if things change within a model over time.
A little background may help in this point and the next. I chatted with Mike Cochran who refused his company's help in decoding because he was concerned that it would leak hints about the decompression algorithm. He also warned that others have bricked their DC trying to communicate with it and even admitted their programmers have bricked DCs while developing their Windows Analyst dive log software. I chose to take that quite seriously.
The code is extremely conservative in identifying DCs. Right now it uses a 6 digit model string (e.g. AM2315) which I suspect not only varies between models but varies within a model based on what features are enabled (e.g. more memory, additional gases) and possible based on which microcontroller is used (perhaps it changes over the life of a model.)
For example the following seem to be specific between models and depending on which features are enabled. Data format (ie log, samples) Baud rate Start and End memory addresses (needed when the log "wraps" around.
I've determined that byte 3 and 4 of that model string indicate the data format but I have no way of determining the baud rate or memory range.
I'm also working on a third model except all I have is data.
Does this change your view on this?
During the communication you close and re-open the serial port. Are you
sure this is really necessary? What kind of interface does the Cochran use? A usb-serial chip (prolific, ftdi, etc)? Maybe you just need the right trick like toggling some serial lines to restart the communication? The reason why I'm asking is that in future versions, opening the serial port will likely be moved to the application side, and in that case re-opening the port will no longer be possible.
You may recall me posting about communication in the past. The cable uses an end-of-life FTDI chip to communicate with the three contacts on the DC (3-wire serial?) The cochran DCs seem tricky. They initially take commands at 9600 baud and they deliver results for small data transfers at the same rate, but for logbook and sample data they operate at what I suspect is their microcontroller's full speed, so I have unusual baud rates like 825,000 baud.
Their Analyst software always downloads the log data as a whole, something I suspect isn't necessary and sample data can be quite big (3 bytes every second) but I decided, based on Mr. Cochran's warning, that I should mimic their windows software closely. That means I open and close the connection.
I could revisit that code and see if I can remove the close/open to see if it works but I'd still need to do the baud change and there is a need for several flushes to wake the DC up. The DC also logs computer connections and if those logs looked unusual users might find Cochran contesting warranty claims. I don't know that close/open is logged, I was being careful.
Will your plans to move the open() to the application side allow for baud rate changes that are needed by the Cochran?
There are a few other issues as well, but I think it makes no sense to dig
into the details before addressing the more critical issues first.
Jef
I've complete some work on a Subsurface log file import for Cochran .CAN and .WAN files. That data is in the same format as the data streams from the DC, except it's re-arranged slightly. I've refined my understanding of the data format and some of the code used. I have the Gemeni (air integrated) data format decoded. I also have samples of older DCs that I'm dabbling with but might discard because they are too old.
That said there is a lot of duplicate function between the two programs and if I revisit the libdivecomputer code I may find additional refinements. I'm open to other ideas.
On 22-10-14 17:33, John Van Ostrand wrote:
On Wed, Oct 22, 2014 at 3:23 AM, Jef Driesen jef@libdivecomputer.org wrote:
On 2014-10-07 18:09, John Van Ostrand wrote: Now, there are a couple of issues that needs to be addressed before this can be included in libdivecomputer:
You used a few Linux specific calls such as nanosleep() in your code. This obviously breaks the windows builds. The rule of thumb is that the dive computer backends should contain no platform specific code at all. All platform specific code should be moved to the platform specific modules. For example we already have a serial_sleep() function.
Those I should be able to change to serial_sleep.
Yeah, that's pretty straightforward.
A related issue is that you used packed structures, which are a gcc extension. Although, I personally use mingw (gcc) for my Windows builds, the msvc compiler is supported too. Casting the raw data to a structure for easier parsing is non-portable anyway (e.g. little vs big endian). Therefore, in libdivecomputer we always de-serialize the data in a portable way using the array_uint{16,24,32}_{le,be} functions. During development the structures are indeed very convenient (I occasionally do that as well), but for the final version they should be replaced with something more portable.
Those packed structures are the dive logs and I realize they have a lot more information than libdivecomputer needs but I wanted to save that research because it's been useful in debugging and decoding other model's logs and it may become useful in the future. You'll see that they are all char or char arrays that I either de-serialize in-line (sometimes for good reason) or use the array_uint* functions. I'd prefer to keep a structure in place rather than directly access select bytes. Would a union between a char array and the structure be a portable way of packing it? Would that be an option?
A union doesn't change the fact that casting a byte array to a structure is not strictly portable (alignment, endianness, etc). In practice it will most likely just work because you only used unsigned char's, but since we can't guarantee that, I prefer not to rely on that. The _attribute__((packed)) is a gcc extension, which is not supported by the msvc compiler, so you can't use that.
For anything longer than an 8 bit value, you'll need the array_uint helper functions anyway, so the only benefit of using the structure is that you don't need to calculate the byte offset manually.
Your dc_device_dump implementation uses a custom cochran_data_dump structure instead of the dc_buffer_t structure. You either need to get rid of this custom structure, or not implement this function. Right now if anyone calls this function with dc_buffer_t structure, bad things will happen. I guess this is also the reason why you added your own example application instead of the generic example application. If really necessary, you can implement and expose backend specific functions to support features outside the generic api.
I should be able to refactor that function to use dc_buffer properly. You mentioned that the dump function was more for debugging or bypassing libdivecomputer's foreach functions so I was liberal with it. I still want to export all the data so how about I use dc_buffer directly, put pointers to the data sections in first and stack the data in after, one big blob. Maybe use a union to a structure to easily decode it.
I wrote the cochran_download program because I wanted a way for a user to be able to download their data and send it to me easily. I assume there will be lots more work needed to support various DCs. The data produced can be used with a simulator I wrote.h
The dump function is indeed mainly for debugging purposes. If your implementation follows the expected pattern (e.g. returning a proper dc_buffer_t) you wouldn't need a custom application.
If you pack all the pieces in one blob, that's fine. Based on your code most pieces have a fixed size, so you can just append them. For the variable length ones, you'll need a small header with at least the size. Just make sure you serialize the data in a portable way (for example 32bit big endian integers for those lengths).
You implemented two backends. One for the EMC and one for the Commander. But the communication protocol is roughly identical, and you already handle the differences in the common code. That means you only need a single backend, which supports multiple models. This is very common in other backends too. Just name your backend after one of them (for example DC_FAMILY_COCHRAN_EMC).
I thought that being very specific about model was important in not leading users to believe their computer was supported. There are significant differences between models and possibly within a model that has different features enabled that would cause the communication or decoding to fail. I have access for four different models with 3 purchased recently which means I don't have any duplicates to compare, duplicate models with different features enable to compare, and I can't tell if things change within a model over time.
A little background may help in this point and the next. I chatted with Mike Cochran who refused his company's help in decoding because he was concerned that it would leak hints about the decompression algorithm. He also warned that others have bricked their DC trying to communicate with it and even admitted their programmers have bricked DCs while developing their Windows Analyst dive log software. I chose to take that quite seriously.
The code is extremely conservative in identifying DCs. Right now it uses a 6 digit model string (e.g. AM2315) which I suspect not only varies between models but varies within a model based on what features are enabled (e.g. more memory, additional gases) and possible based on which microcontroller is used (perhaps it changes over the life of a model.)
For example the following seem to be specific between models and depending on which features are enabled. Data format (ie log, samples) Baud rate Start and End memory addresses (needed when the log "wraps" around.
I've determined that byte 3 and 4 of that model string indicate the data format but I have no way of determining the baud rate or memory range.
I'm also working on a third model except all I have is data.
Does this change your view on this?
I certainly understand your concern about being careful. That's something I have been doing for the past years too. As far as I know libdivecomputer has never bricked any dive computer, and I certainly would like to keep it that way!
But that's not a reason not to use a single backend. One backend basically means one communication protocol. And that doesn't seems to be the case here. You have several different models that share the same communication protocol. As you explained above, you can identify the actual model from the data, so I see no valid reason to split this into different backends. If you are concerned that your code won't work correctly on models you haven't tested yet, then it's perfectly acceptable, to fail with DC_STATUS_UNSUPPORTED once you detect a model you don't know how to handle.
[That's pretty much the same situation as with the oceanics. If we encounter an unknown model, we fallback to the default model. In most cases that's just wrong, and downloading or parsing will fail. Libdivecomputer needs to be updated to learn the correct settings for each new model. The only difference here is that we don't fail but fallback to a default, because it doesn't harm the device, and usually allows us to get at least some data of the device.]
Also notice how the dc_device_open() function receives the device descriptor, which contains the model number. So if necessary, you know the requested model before starting the communication. Most backends ignore this model number, because they detect the model from the data. This autodetection is what makes it possible to select any model from the same family, and downloading will just work. If possible, I prefer doing autodetection, but if that's not possible or too risky, you can assume the model selected by the user. (If the user selects the wrong model, there is not much we can do about that.)
During the communication you close and re-open the serial port. Are you sure this is really necessary? What kind of interface does the Cochran use? A usb-serial chip (prolific, ftdi, etc)? Maybe you just need the right trick like toggling some serial lines to restart the communication? The reason why I'm asking is that in future versions, opening the serial port will likely be moved to the application side, and in that case re-opening the port will no longer be possible.
You may recall me posting about communication in the past. The cable uses an end-of-life FTDI chip to communicate with the three contacts on the DC (3-wire serial?) The cochran DCs seem tricky. They initially take commands at 9600 baud and they deliver results for small data transfers at the same rate, but for logbook and sample data they operate at what I suspect is their microcontroller's full speed, so I have unusual baud rates like 825,000 baud.
Their Analyst software always downloads the log data as a whole, something I suspect isn't necessary and sample data can be quite big (3 bytes every second) but I decided, based on Mr. Cochran's warning, that I should mimic their windows software closely. That means I open and close the connection.
I could revisit that code and see if I can remove the close/open to see if it works but I'd still need to do the baud change and there is a need for several flushes to wake the DC up. The DC also logs computer connections and if those logs looked unusual users might find Cochran contesting warranty claims. I don't know that close/open is logged, I was being careful.
I might be wrong on this, but I doubt the device can detect when an application opens the serial port. With true serial ports (and no handshaking) you can easily send data out, without anything connected to the port. And with usb-serial there is already a lot of communication going on before an application can even open the virtual serial port (usb device enumeration for example). Of course, once you're sending data, or changing serial lines the device will detect that.
In theory it makes absolutely no sense having to flush multiple times, or setting the same settings more than once. Most likely, the device just needs more time to finish whatever it was doing. So waiting a few (or more) milliseconds will work equally well. I have seen that open/close sequence before in other applications, and as I suspected it turned out to be unnecessary. I just needed the right amount of waiting time combined with the correct initialization sequence. I'm not saying that's the case here, but I suspect it is.
Note that opening/closing a serial port has a race condition. Between your open and close, some other application might open the port. Very unlikely in practice, but not impossible.
Will your plans to move the open() to the application side allow for baud rate changes that are needed by the Cochran?
Only opening the serial port will move to the application side. Everything else, like setting the correct baudrate, will remain the responsibility of libdivecomputer. So switching baudrates won't be a problem.
Jef
Removed dependency on packed structure. Removed extraneous close/opens. Formated lines to 78 chars. Merged EMC and Commander backends to common. Change nanosleep() calls to serial_sleep(). Change cochran_common_dump to use dc_buffer properly.
--- examples/cochran_download.c | 165 +++++++++++++++++++++-------- examples/universal.c | 3 +- include/libdivecomputer/cochran.h | 11 +- include/libdivecomputer/common.h | 3 +- src/Makefile.am | 6 +- src/cochran_cmdr.c | 66 ------------ src/cochran_cmdr.h | 83 --------------- src/cochran_cmdr_parser.c | 63 +++++------ src/cochran_common.c | 218 +++++++++++++++++++++++++++----------- src/cochran_common.h | 62 +++++++++-- src/cochran_common_parser.c | 78 ++++++++++++-- src/cochran_common_parser.h | 43 +++++++- src/cochran_emc.c | 67 ------------ src/cochran_emc.h | 180 ------------------------------- src/cochran_emc_parser.c | 84 +++++++-------- src/descriptor.c | 6 +- src/device.c | 7 +- src/parser.c | 7 +- 18 files changed, 515 insertions(+), 637 deletions(-) delete mode 100644 src/cochran_cmdr.c delete mode 100644 src/cochran_cmdr.h delete mode 100644 src/cochran_emc.c delete mode 100644 src/cochran_emc.h
diff --git a/examples/cochran_download.c b/examples/cochran_download.c index b42eb91..a9253af 100644 --- a/examples/cochran_download.c +++ b/examples/cochran_download.c @@ -58,14 +58,9 @@ usage (const char *filename) fprintf (stderr, "Usage:\n\n"); fprintf (stderr, " %s [options] devname\n\n", filename); fprintf (stderr, "Options:\n\n"); - fprintf (stderr, " -b name Set backend name (required).\n"); fprintf (stderr, " -d dirname Dump data to dirname.\n"); fprintf (stderr, " -f Force dump despite download errors\n"); fprintf (stderr, " -h Show this help message.\n\n"); - - fprintf (stderr, "Supported backends:\n\n"); - fprintf (stderr, " emc, Cochran EMC family\n"); - fprintf (stderr, " commander, Cochran Commander family\n"); fprintf (stderr, "\n\n"); }
@@ -108,20 +103,17 @@ search (dc_descriptor_t **out, const char *name, dc_family_t backend, unsigned i }
static dc_status_t -write_dump(dc_buffer_t *dump, const char *fname) +write_dump(const char *dump, unsigned int size, const char *fname) { - int fd, size = 0, written; - - if (dump) - size = dc_buffer_get_size(dump); + int fd, written;
if (size > 0) { fd = open(fname, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH ); if (fd == -1) { - printf("unable to open %s for writing\n", fname); + printf("Unable to open %s for writing\n", fname); exit; } - written = write(fd, dc_buffer_get_data(dump), size); + written = write(fd, dump, size); if (written != size) { printf("Error writing %s. Wrote %d expected %d.\n", fname, written, size); return (DC_STATUS_UNSUPPORTED); @@ -130,6 +122,59 @@ write_dump(dc_buffer_t *dump, const char *fname) return (DC_STATUS_SUCCESS); }
+#define hexchar(n) ("0123456789abcdef"[(n) & 15]) + +static int show_line(unsigned offset, const unsigned char *data, + unsigned size, int show_empty) +{ + unsigned char bits; + int i, off; + char buffer[120]; + + if (size > 16) + size = 16; + + bits = 0; + memset(buffer, ' ', sizeof(buffer)); + off = sprintf(buffer, "%06x ", offset); + for (i = 0; i < size; i++) { + char *hex = buffer + off + 3 * i; + char *asc = buffer + off + 50 + i; + unsigned char byte = data[i]; + + hex[0] = hexchar(byte >> 4); + hex[1] = hexchar(byte); + bits |= byte; + if (byte < 32 || byte > 126) + byte = '.'; + asc[0] = byte; + asc[1] = 0; + } + + if (bits) { + puts(buffer); + return 1; + } + if (show_empty) + puts("..."); + return 0; +} + +static void cochran_debug_write(const unsigned char *data, unsigned size) +{ + return; + + int show = 1, i; + + + for (i = 0; i < size; i += 16) + show = show_line(i, data + i, size - i, show); +} + + +#define array_uint32_le(p) ( (unsigned char) (p)[0] + ((unsigned char) (p)[1] << 8) \ + + ((unsigned char) (p)[2] << 16) + ((unsigned char) (p)[3] << 24) ) + static dc_status_t dowork (dc_context_t *context, dc_descriptor_t *descriptor, const char *devname, const char *dirname, unsigned int force_flag) { @@ -154,62 +199,96 @@ dowork (dc_context_t *context, dc_descriptor_t *descriptor, const char *devname, }
// Create directory if needed - stat(dirname, &dstat); + int src; + src = stat(dirname, &dstat);
- if ( ! (dstat.st_mode & S_IFDIR) ) { + if ( src == -1 ) { if (mkdir(dirname, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) { printf("Unable to create directory\n"); exit; } }
- // TODO Run dump and write files - struct cochran_data_dump dump = {0}; - rc = dc_device_dump (device, (dc_buffer_t *) &dump); + dc_buffer_t *dump = dc_buffer_new(0); + + rc = dc_device_dump (device, dump); + + cochran_debug_write(dc_buffer_get_data(dump), dc_buffer_get_size(dump)); + exit;
if (rc == DC_STATUS_SUCCESS || force_flag) { char fname[128]; + char *d = dc_buffer_get_data(dump); + unsigned int ptr = 0, start, size;
sprintf(fname, "%s/info-x05x9dxffx00x43x00.bin", dirname); - write_dump(dump.id0, fname); - - if (dc_buffer_get_size(dump.id0) > 0) + start = array_uint32_le(d + ptr); + size = array_uint32_le(d + ptr + 5) - start; +printf("%s, start = %d, size = %d\n", fname, start, size); + write_dump(d + start, size, fname); + + ptr += 5; + start = array_uint32_le(d + ptr); + size = array_uint32_le(d + ptr + 5) - start; + if (*(d + ptr - 1) == 1) sprintf(fname, "%s/info-xdbx7fxffx00x43x00.bin", dirname); else sprintf(fname, "%s/info-x05x9dxffx00x43x00.bin", dirname); - write_dump(dump.id1, fname); + write_dump(d + start, size, fname);
+ ptr += 5; + start = array_uint32_le(d + ptr); + size = array_uint32_le(d + ptr + 5) - start; sprintf(fname, "%s/config-x96x00.bin", dirname); - write_dump(dump.config1, fname); +printf("%s, start = %d, size = %d\n", fname, start, size); + write_dump(d + start, size, fname);
+ ptr += 5; + start = array_uint32_le(d + ptr); + size = array_uint32_le(d + ptr + 5) - start; sprintf(fname, "%s/config-x96x01.bin", dirname); - write_dump(dump.config2, fname); +printf("%s, start = %d, size = %d\n", fname, start, size); + write_dump(d + start, size, fname);
+ ptr += 5; + start = array_uint32_le(d + ptr); + size = array_uint32_le(d + ptr + 5) - start; sprintf(fname, "%s/config-x96x02.bin", dirname); - write_dump(dump.config3, fname); +printf("%s, start = %d, size = %d\n", fname, start, size); + write_dump(d + start, size, fname);
+ ptr += 5; + start = array_uint32_le(d + ptr); + size = array_uint32_le(d + ptr + 5) - start; sprintf(fname, "%s/config-x96x03.bin", dirname); - write_dump(dump.config4, fname); +printf("%s, start = %d, size = %d\n", fname, start, size); + write_dump(d + start, size, fname);
+ ptr += 5; + start = array_uint32_le(d + ptr); + size = array_uint32_le(d + ptr + 5) - start; sprintf(fname, "%s/misc-x89x05x00x00x00xdcx05.bin", dirname); - write_dump(dump.misc, fname); +printf("%s, start = %d, size = %d\n", fname, start, size); + write_dump(d + start, size, fname);
+ ptr += 5; + start = array_uint32_le(d + ptr); + size = array_uint32_le(d + ptr + 5) - start; sprintf(fname, "%s/logbook.bin", dirname); - write_dump(dump.logbook, fname); +printf("%s, start = %d, size = %d\n", fname, start, size); + write_dump(d + start, size, fname);
+ ptr += 5; + start = array_uint32_le(d + ptr); + size = array_uint32_le(d + ptr + 5) - start; sprintf(fname, "%s/sample.bin", dirname); - write_dump(dump.sample, fname); - - dc_buffer_free (dump.id0); - dc_buffer_free (dump.id1); - dc_buffer_free (dump.config1); - dc_buffer_free (dump.config2); - dc_buffer_free (dump.config3); - dc_buffer_free (dump.config4); - dc_buffer_free (dump.misc); - dc_buffer_free (dump.logbook); - dc_buffer_free (dump.sample); +printf("%s, start = %d, size = %d\n", fname, start, size); + write_dump(d + start, size, fname); + + dc_buffer_free (dump); + } else { + printf("Dive computer read failure\n"); }
// Close the device. @@ -230,7 +309,7 @@ main (int argc, char *argv[]) extern int optind, opterr, optopt;
// Default values. - dc_family_t backend = DC_FAMILY_NULL; + dc_family_t backend = DC_FAMILY_COCHRAN;
// Command line arguments const char *devname = NULL, *dirname; @@ -239,14 +318,8 @@ main (int argc, char *argv[])
// Parse command-line options. int opt = 0; - while ((opt = getopt (argc, argv, "b:d:ls:h")) != -1) { + while ((opt = getopt (argc, argv, "d:ls:h")) != -1) { switch (opt) { - case 'b': // backend - if ( ! strcmp ("emc", optarg) ) - backend = DC_FAMILY_COCHRAN_EMC; - else if ( ! strcmp( "commander", optarg) ) - backend = DC_FAMILY_COCHRAN_COMMANDER; - break; case 'd': // dump data to directory dirname = optarg; break; diff --git a/examples/universal.c b/examples/universal.c index 56445b7..c7d36f7 100644 --- a/examples/universal.c +++ b/examples/universal.c @@ -102,8 +102,7 @@ static const backend_table_t g_backends[] = { {"predator", DC_FAMILY_SHEARWATER_PREDATOR}, {"petrel", DC_FAMILY_SHEARWATER_PETREL}, {"nitekq", DC_FAMILY_DIVERITE_NITEKQ}, - {"commander", DC_FAMILY_COCHRAN_COMMANDER}, - {"emc", DC_FAMILY_COCHRAN_EMC}, + {"cochran", DC_FAMILY_COCHRAN}, };
static dc_family_t diff --git a/include/libdivecomputer/cochran.h b/include/libdivecomputer/cochran.h index 27fdcc5..f7506d7 100644 --- a/include/libdivecomputer/cochran.h +++ b/include/libdivecomputer/cochran.h @@ -43,16 +43,11 @@ struct cochran_data_dump { };
dc_status_t -cochran_cmdr_device_open (dc_device_t **device, dc_context_t *context, const char *name); +cochran_common_device_open (dc_device_t **device, dc_context_t *context, + const char *name);
dc_status_t -cochran_cmdr_parser_create (dc_parser_t **parser, dc_context_t *context); - -dc_status_t -cochran_emc_device_open (dc_device_t **device, dc_context_t *context, const char *name); - -dc_status_t -cochran_emc_parser_create (dc_parser_t **parser, dc_context_t *context); +cochran_common_parser_create (dc_parser_t **parser, dc_context_t *context);
#ifdef __cplusplus } diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h index ef86272..218548f 100644 --- a/include/libdivecomputer/common.h +++ b/include/libdivecomputer/common.h @@ -84,8 +84,7 @@ typedef enum dc_family_t { /* Dive Rite */ DC_FAMILY_DIVERITE_NITEKQ = (11 << 16), /* Cochran */ - DC_FAMILY_COCHRAN_COMMANDER = (12 << 16), - DC_FAMILY_COCHRAN_EMC, + DC_FAMILY_COCHRAN = (12 << 16), } dc_family_t;
#ifdef __cplusplus diff --git a/src/Makefile.am b/src/Makefile.am index d8098f6..33d4713 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -56,9 +56,9 @@ libdivecomputer_la_SOURCES = \ checksum.h checksum.c \ array.h array.c \ buffer.c \ - cochran_common.h cochran_common.c cochran_common_parser.h cochran_common_parser.c \ - cochran_cmdr.h cochran_cmdr.c cochran_cmdr_parser.c \ - cochran_emc.h cochran_emc.c cochran_emc_parser.c + cochran_common.h cochran_common.c \ + cochran_common_parser.h cochran_common_parser.c \ + cochran_cmdr_parser.c cochran_emc_parser.c
if OS_WIN32 libdivecomputer_la_SOURCES += serial.h serial_win32.c diff --git a/src/cochran_cmdr.c b/src/cochran_cmdr.c deleted file mode 100644 index fb6e1e0..0000000 --- a/src/cochran_cmdr.c +++ /dev/null @@ -1,66 +0,0 @@ -/* - * libdivecomputer - * - * Copyright (C) 2014 John Van Ostrand - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - -#include <stdlib.h> -#include <time.h> - -#include <libdivecomputer/cochran.h> - -#include "context-private.h" -#include "device-private.h" -#include "serial.h" - -#include "cochran_common.h" -#include "cochran_cmdr.h" - - -static const dc_device_vtable_t cochran_cmdr_device_vtable = { - DC_FAMILY_COCHRAN_COMMANDER, - cochran_common_device_set_fingerprint, /* set_fingerprint */ - cochran_common_device_read, /* read */ - NULL, /* write */ - cochran_common_device_dump, /* dump */ - cochran_common_device_foreach, /* foreach */ - cochran_common_device_close /* close */ -}; - - -dc_status_t -cochran_cmdr_device_open (dc_device_t **out, dc_context_t *context, const char *name) -{ - dc_status_t rc; - rc = cochran_common_device_open(out, context, name, &cochran_cmdr_device_vtable); - if (rc != DC_STATUS_SUCCESS) - return rc; - - cochran_device_t *device = (cochran_device_t *) *out; - - // Check family - if ((device->data.model & 0xFF0000) != COCHRAN_MODEL_COMMANDER_FAMILY) { - ERROR (context, "Device not recognized."); - serial_close (device->port); - free (device->data.id); - free (device); - return DC_STATUS_UNSUPPORTED; - } - - return DC_STATUS_SUCCESS; -} diff --git a/src/cochran_cmdr.h b/src/cochran_cmdr.h deleted file mode 100644 index bac5f57..0000000 --- a/src/cochran_cmdr.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * libdivecomputer - * - * Copyright (C) 2014 John Van Ostrand - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - - -struct cochran_cmdr_log_t { - // Pre-dive 128 bytes - unsigned char minutes, seconds; // 3 bytes - unsigned char day, hour, year, month; // 3 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[4]; // 4 bytes - unsigned char entered_or_computed_po[2];// 2 bytes ??? - unsigned char unknown5[8]; // 8 bytes - unsigned char start_depth[2]; // 2 byte [/4] -//56 - unsigned char unknown6[12]; // 12 bytes - unsigned char sit[2]; // 2 bytes -//70 - unsigned char number[2]; // 2 bytes - unsigned char unknown7[1]; // 1 byte - unsigned char altitude; // 1 byte [/4 = kft] - unsigned char unknown8[28]; // 27 bytes - unsigned char alarm_depth[2]; // 2 bytes - unsigned char unknown9[4]; // 5 bytes -//108 - unsigned char repetitive_dive; // 1 byte - unsigned char unknown10[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 unknown11[21]; // 21 bytes - unsigned char temp; // 1 byte - unsigned char unknown12[12]; // 12 bytes - unsigned char bt[2]; // 2 bytes [minutes] - unsigned char max_depth[2]; // 2 bytes [/4] - unsigned char avg_depth[2]; // 2 bytes - unsigned char unknown13[38]; // 38 bytes - unsigned char o2_percent[4][2]; // 8 bytes - unsigned char unknown14[22]; // 22 bytes - unsigned char end_tissue_nsat[16]; // 16 bytes [/256] -} __attribute__((packed)); - - -typedef struct cochran_cmdr_log_t cochran_cmdr_log_t; - - -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)); - -typedef struct cochran_emc_config1_t cochran_emc_config1_t; diff --git a/src/cochran_cmdr_parser.c b/src/cochran_cmdr_parser.c index 8c74705..8b94fcf 100644 --- a/src/cochran_cmdr_parser.c +++ b/src/cochran_cmdr_parser.c @@ -32,36 +32,18 @@ #include "array.h"
#include "cochran_common.h" -#include "cochran_cmdr.h" #include "cochran_common_parser.h"
-#define SZ_SAMPLE 2 - -static dc_status_t cochran_cmdr_parser_get_field(dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); -static dc_status_t cochran_cmdr_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata);
-static const dc_parser_vtable_t cochran_cmdr_parser_vtable = { - DC_FAMILY_COCHRAN_COMMANDER, - cochran_common_parser_set_data, /* set_data */ - cochran_common_parser_get_datetime, /* datetime */ - cochran_cmdr_parser_get_field, /* fields */ - cochran_cmdr_parser_samples_foreach, /* samples_foreach */ - cochran_common_parser_destroy /* destroy */ -}; +#define SZ_SAMPLE 2
dc_status_t -cochran_cmdr_parser_create (dc_parser_t **out, dc_context_t *context) -{ - return cochran_common_parser_create(out, context, &cochran_cmdr_parser_vtable); -} - - -static dc_status_t -cochran_cmdr_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) +cochran_cmdr_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, + unsigned int flags, void *value) { cochran_data_t *data = (cochran_data_t *) abstract->data; - cochran_cmdr_log_t *log = (cochran_cmdr_log_t *) data->current_log; + unsigned char *log = data->current_log;
dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_salinity_t *water = (dc_salinity_t *) value; @@ -69,31 +51,34 @@ cochran_cmdr_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi if (value) { switch (type) { case DC_FIELD_TEMPERATURE: - *((unsigned int*) value) = (log->temp - 32) / 1.8; + *((unsigned int*) value) = log[CMD_START_TEMP]; case DC_FIELD_DIVETIME: - *((unsigned int *) value) = array_uint16_le (log->bt) * 60; + *((unsigned int *) value) = array_uint16_le (log + EMC_BT) * 60; break; case DC_FIELD_MAXDEPTH: - *((double *) value) = array_uint16_le (log->max_depth) / 4 * FEET; + *((double *) value) = array_uint16_le (log + CMD_MAX_DEPTH) / 4 * FEET; break; case DC_FIELD_AVGDEPTH: - *((double *) value) = array_uint16_le (log->avg_depth) / 4 * FEET; + *((double *) value) = array_uint16_le (log + CMD_AVG_DEPTH) / 4 * FEET; break; case DC_FIELD_GASMIX_COUNT: *((unsigned int *) value) = 2; break; case DC_FIELD_GASMIX: - gasmix->oxygen = (double) array_uint16_le ((char *)log->o2_percent + 2 * flags) / 256 / 100; + gasmix->oxygen = (double) array_uint16_le ((char *)log + + CMD_O2_PERCENT + 2 * flags) / 256 / 100; gasmix->helium = 0; gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; break; case DC_FIELD_SALINITY: // 0 = low conductivity, 1 = high, maybe there's a 2? - water->type = ( log->water_conductivity == 0 ? DC_WATER_FRESH : DC_WATER_SALT ); - water->density = 1000 + 12.5 * log->water_conductivity; + water->type = ( log[CMD_WATER_CONDUCTIVITY] == 0 ? DC_WATER_FRESH + : DC_WATER_SALT ); + water->density = 1000 + 12.5 * log[CMD_WATER_CONDUCTIVITY]; break; case DC_FIELD_ATMOSPHERIC: - *(double *) value = ATM / BAR * pow(1 - 0.0000225577 * (double) log->altitude * 250 * FEET, 5.25588); + *(double *) value = ATM / BAR * pow(1 - 0.0000225577 + * (double) log[CMD_ALTITUDE] * 250 * FEET, 5.25588); break; default: return DC_STATUS_UNSUPPORTED; @@ -104,12 +89,13 @@ cochran_cmdr_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi }
-static dc_status_t -cochran_cmdr_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +dc_status_t +cochran_cmdr_parser_samples_foreach (dc_parser_t *abstract, + dc_sample_callback_t callback, void *userdata) { cochran_data_t *data = (cochran_data_t *)abstract->data; unsigned char *sdata = data->current_sample; - cochran_cmdr_log_t *log = (cochran_cmdr_log_t *) data->current_log; + unsigned char *log = data->current_log; unsigned int size = data->current_sample_size; unsigned char *s;
@@ -126,7 +112,7 @@ cochran_cmdr_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t // and temp ever other second.
// Prime values from the dive log section - depth = array_uint16_le (log->start_depth) / 256; + depth = array_uint16_le (log + CMD_START_DEPTH) / 4;
last_sample_time = sample.time = time; if (callback) callback (DC_SAMPLE_TIME, sample, userdata); @@ -134,7 +120,7 @@ cochran_cmdr_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t sample.depth = depth * FEET; if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
- // TODO: sample.temperature = (log->start_temperature - 32) / 1.8; + // TODO: sample.temperature = (log + CMD_START_TEMP - 32) / 1.8; // TODO: if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
while (offset < size) { @@ -165,7 +151,8 @@ cochran_cmdr_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t
// Check for event if (s[0] & 0x80) { - offset += cochran_common_handle_event(abstract, callback, userdata, s[0], offset, time); + offset += cochran_common_handle_event(abstract, callback, userdata, + s[0], offset, time); if (s[0] == 0xC5) deco_obligation = 1; // Deco obligation begins else if (s[0] == 0xC8) @@ -183,12 +170,12 @@ cochran_cmdr_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t // Ascent rate is logged in the 0th sample, temp in the 1st, repeat. if (time % 2 == 0) { // Ascent rate - ascent_rate = (s[1] & 0x7f) / 4 * (s[0] & 0x80 ? 1 : -1); + ascent_rate = (s[1] & 0x7f) / 4 * (s[1] & 0x80 ? 1 : -1); sample.ascent_rate = ascent_rate * FEET; if (callback) callback (DC_SAMPLE_ASCENT_RATE, sample, userdata); } else { // Temperature logged in half degrees F above 20 - temperature = s[1] / 2 + 20; + temperature = (float) s[1] / 2 + 20; sample.temperature = (temperature - 32.0) / 1.8;
if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); diff --git a/src/cochran_common.c b/src/cochran_common.c index 877491d..7bbe277 100644 --- a/src/cochran_common.c +++ b/src/cochran_common.c @@ -39,9 +39,21 @@ rc == -1 ? DC_STATUS_IO : DC_STATUS_TIMEOUT \ )
+static const dc_device_vtable_t cochran_common_device_vtable = { + DC_FAMILY_COCHRAN, + cochran_common_device_set_fingerprint, /* set_fingerprint */ + cochran_common_device_read, /* read */ + NULL, /* write */ + cochran_common_device_dump, /* dump */ + cochran_common_device_foreach, /* foreach */ + cochran_common_device_close /* close */ +}; +
dc_status_t -cochran_packet (cochran_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, int high_speed) +cochran_packet (cochran_device_t *device, const unsigned char command[], + unsigned int csize, unsigned char answer[], unsigned int asize, + int high_speed) { dc_device_t *abstract = (dc_device_t *) device; unsigned int bytes_read = 0, n, read_size; @@ -50,11 +62,9 @@ cochran_packet (cochran_device_t *device, const unsigned char command[], unsigne if (device_is_cancelled (abstract)) return DC_STATUS_CANCELLED;
- struct timespec tr, ts = {0}; - ts.tv_nsec = 16 * 1000000; // 16 ms // Send the command to the device, one byte at a time for (ptr = 0; ptr < csize; ptr++) { - if (ptr) nanosleep(&ts, &tr); + if (ptr) serial_sleep(device->port, 16); // 16 ms n = serial_write(device->port, command + ptr, 1); if (n != 1) { ERROR (abstract->context, "Failed to send the command."); @@ -63,9 +73,7 @@ cochran_packet (cochran_device_t *device, const unsigned char command[], unsigne }
if (high_speed) { - struct timespec tr, ts = {0}; - ts.tv_nsec = 45 * 1000000; // 45 ms - nanosleep(&ts, &tr); + serial_sleep(device->port, 45);
// Weird but I only get the right result when I do it twice // Rates are odd, like 825600 for the EMC, 115200 for commander @@ -85,7 +93,8 @@ cochran_packet (cochran_device_t *device, const unsigned char command[], unsigne
n = serial_read (device->port, answer + bytes_read, read_size); if (n != read_size) { - ERROR (abstract->context, "Failed to receive data, expected %u, read %u.", read_size, n); + ERROR (abstract->context, "Failed to receive data, expected %u," + "read %u.", read_size, n); return EXITCODE (n); }
@@ -101,16 +110,10 @@ cochran_packet (cochran_device_t *device, const unsigned char command[], unsigne }
-dc_status_t -cochran_common_serial_open(cochran_device_t *device, dc_context_t *context) +static dc_status_t +cochran_common_serial_setup (cochran_device_t *device, dc_context_t *context) { - // Open the device. - int rc = serial_open (&device->port, context, device->name); - if (rc == -1) { - ERROR (context, "Failed to open the serial port."); - free (device); - return DC_STATUS_IO; - } + int rc;
// Set the serial communication protocol (9600 8N2, no FC). rc = serial_configure (device->port, 9600, 8, SERIAL_PARITY_NONE, @@ -135,9 +138,7 @@ cochran_common_serial_open(cochran_device_t *device, dc_context_t *context) serial_flush (device->port, SERIAL_QUEUE_INPUT);
serial_set_break(device->port, 1); - struct timespec tr, ts = {0}; - ts.tv_nsec = 16 * 1000000; // 16ms - nanosleep(&ts, &tr); + serial_sleep(device->port, 16); serial_set_break(device->port, 0);
@@ -162,7 +163,23 @@ cochran_common_serial_open(cochran_device_t *device, dc_context_t *context)
dc_status_t -cochran_common_device_open (dc_device_t **out, dc_context_t *context, const char *name, const dc_device_vtable_t *vtable) +cochran_common_serial_open(cochran_device_t *device, dc_context_t *context) +{ + // Open the device. + int rc = serial_open (&device->port, context, device->name); + if (rc == -1) { + ERROR (context, "Failed to open the serial port."); + free (device); + return DC_STATUS_IO; + } + + return cochran_common_serial_setup(device, context); +} + + +dc_status_t +cochran_common_device_open (dc_device_t **out, dc_context_t *context, + const char *name) { dc_status_t rc;
@@ -170,14 +187,15 @@ cochran_common_device_open (dc_device_t **out, dc_context_t *context, const char return DC_STATUS_INVALIDARGS;
// Allocate memory. - cochran_device_t *device = (cochran_device_t *) malloc (sizeof (cochran_device_t)); + cochran_device_t *device = (cochran_device_t *) malloc ( + sizeof (cochran_device_t)); if (device == NULL) { ERROR (context, "Failed to allocate memory."); return DC_STATUS_NOMEMORY; }
// Initialize the base class. - device_init (&device->base, context, vtable); + device_init (&device->base, context, &cochran_common_device_vtable);
// Set the default values. device->port = NULL; @@ -187,7 +205,8 @@ cochran_common_device_open (dc_device_t **out, dc_context_t *context, const char device->data.sample = NULL; cochran_common_device_set_fingerprint((dc_device_t *) device, "", 0);
- if ((rc = cochran_common_serial_open(device, context)) != DC_STATUS_SUCCESS) + if ((rc = cochran_common_serial_open(device, context)) + != DC_STATUS_SUCCESS) return rc;
// Read ID from the device @@ -202,6 +221,15 @@ cochran_common_device_open (dc_device_t **out, dc_context_t *context, const char return rc; }
+ // Check ID + if ((device->data.model & 0xFF0000) == COCHRAN_MODEL_UNKNOWN) { + ERROR (context, "Device not recognized."); + serial_close (device->port); + free (device->data.id); + free (device); + return DC_STATUS_UNSUPPORTED; + } + *out = (dc_device_t *) device;
return DC_STATUS_SUCCESS; @@ -236,7 +264,8 @@ cochran_common_device_close (dc_device_t *abstract)
dc_status_t -cochran_common_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size) +cochran_common_device_set_fingerprint (dc_device_t *abstract, + const unsigned char data[], unsigned int size) { cochran_device_t *device = (cochran_device_t *) abstract; cochran_data_t *d = &(device->data); @@ -254,7 +283,8 @@ cochran_common_device_set_fingerprint (dc_device_t *abstract, const unsigned cha
dc_status_t -cochran_common_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size) +cochran_common_device_read (dc_device_t *abstract, unsigned int address, + unsigned char data[], unsigned int size) { cochran_device_t *device = (cochran_device_t*) abstract;
@@ -294,7 +324,8 @@ cochran_common_device_read (dc_device_t *abstract, unsigned int address, unsigne }
// Read data at high speed - dc_status_t rc = cochran_packet (device, command, command_size, data, size, 1); + dc_status_t rc = cochran_packet (device, command, command_size, data, + size, 1); if (rc != DC_STATUS_SUCCESS) return rc;
@@ -341,7 +372,7 @@ cochran_set_device_config (cochran_device_t *device) device->data.address_length = COCHRAN_ADDRESS_LENGTH_32; device->data.high_baud = 825600; } - else if (memcmp(device->data.id + 0x3B, "AM\x11\x32\x32\x31\x32\x02", 8) == 0) + else if (memcmp(device->data.id + 0x3B, "AM\x11""2212\x02", 8) == 0) { device->data.model = COCHRAN_MODEL_COMMANDER_AIR_NITROX; device->data.log_size = 256; @@ -558,13 +589,10 @@ cochran_read_logbook (dc_device_t *abstract) device->progress->maximum = d->logbook_size; device_event_emit (abstract, DC_EVENT_PROGRESS, device->progress);
- // We have to close and restart to get the DC's attention - serial_close(device->port); + serial_sleep(device->port, 800);
- struct timespec tr, ts = {0, 800 * 1000000}; // 800ms - nanosleep(&ts, &tr); - - cochran_common_serial_open(device, abstract->context); + // set back to 9600 baud + cochran_common_serial_setup(device, abstract->context);
// Request log book rc = cochran_common_device_read(abstract, 0, d->logbook, d->logbook_size); @@ -662,9 +690,6 @@ cochran_read_samples(dc_device_t *abstract) return DC_STATUS_NOMEMORY; }
- // We have to close the serial port to get the DC's attention - serial_close(device->port); - // Enable progress notifications. device->progress = malloc(sizeof(dc_event_progress_t)); if (device->progress == NULL) { @@ -676,13 +701,14 @@ cochran_read_samples(dc_device_t *abstract) device->progress->maximum = d->sample_size; device_event_emit (abstract, DC_EVENT_PROGRESS, device->progress);
- struct timespec tr, ts = {0, 800* 1000000}; // 800 ms - nanosleep(&ts, &tr); + serial_sleep(device->port, 800);
- cochran_common_serial_open(device, abstract->context); + // set back to 9600 baud + cochran_common_serial_setup(device, abstract->context);
// Read the sample data - rc = cochran_common_device_read (abstract, d->sample_data_offset, d->sample, d->sample_size); + rc = cochran_common_device_read (abstract, d->sample_data_offset, + d->sample, d->sample_size); if (rc != DC_STATUS_SUCCESS) { free (device->progress); ERROR (abstract->context, "Failed to read the sample data."); @@ -724,13 +750,17 @@ cochran_common_device_read_all (dc_device_t *abstract) }
+#define pack_uint32_array_le(d, i) ((d)[0] = (i) & 0xff, \ + (d)[1] = ((i) >> 8) & 0xff, \ + (d)[2] = ((i) >> 16) & 0xff, \ + (d)[3] = ((i) >> 24) & 0xff) + dc_status_t cochran_common_device_dump (dc_device_t *abstract, dc_buffer_t *data) { cochran_device_t *device = (cochran_device_t *) abstract; cochran_data_t *d = (cochran_data_t *) &(device->data); - - struct cochran_data_dump *dump = (struct cochran_data_dump *) data; + int ptr = 0;
dc_status_t rc;
@@ -739,60 +769,119 @@ cochran_common_device_dump (dc_device_t *abstract, dc_buffer_t *data) if (rc != DC_STATUS_SUCCESS) return rc;
- // Copy data for permanency. + // Reserve space for pointers + dc_buffer_resize(data, 10 * 5); + + pack_uint32_array_le(dc_buffer_get_data(data) + ptr, + dc_buffer_get_size(data));
if (d->id0) { - dump->id0 = dc_buffer_new(67); - dc_buffer_append (dump->id0, d->id0, 67); + dc_buffer_append(data, d->id0, 67); + *(dc_buffer_get_data(data) + ptr + 4) = 1; + } else { + dc_buffer_resize(data, dc_buffer_get_size(data) + 67); } + ptr += 5; + pack_uint32_array_le(dc_buffer_get_data(data) + ptr, + dc_buffer_get_size(data)); + if (d->id) { - dump->id1 = dc_buffer_new(67); - dc_buffer_append (dump->id1, d->id, 67); + dc_buffer_append (data, d->id, 67); + *(dc_buffer_get_data(data) + ptr + 4) = 1; + } else { + dc_buffer_resize(data, dc_buffer_get_size(data) + 67); } + ptr += 5; + pack_uint32_array_le(dc_buffer_get_data(data) + ptr, + dc_buffer_get_size(data)); + if (d->config1) { - dump->config1 = dc_buffer_new(512); - dc_buffer_append (dump->config1, d->config1, 512); + dc_buffer_append (data, d->config1, 512); + *(dc_buffer_get_data(data) + ptr + 4) = 1; + } else { + dc_buffer_resize(data, dc_buffer_get_size(data) + 512); } + ptr += 5; + pack_uint32_array_le(dc_buffer_get_data(data) + ptr, + dc_buffer_get_size(data)); + if (d->config2) { - dump->config2 = dc_buffer_new(512); - dc_buffer_append (dump->config2, d->config2, 512); + dc_buffer_append (data, d->config2, 512); + *(dc_buffer_get_data(data) + ptr + 4) = 1; + } else { + dc_buffer_resize(data, dc_buffer_get_size(data) + 512); } + ptr += 5; + pack_uint32_array_le(dc_buffer_get_data(data) + ptr, + dc_buffer_get_size(data)); + if (d->config3) { - dump->config3 = dc_buffer_new(512); - dc_buffer_append (dump->config3, d->config3, 512); + dc_buffer_append (data, d->config3, 512); + *(dc_buffer_get_data(data) + ptr + 4) = 1; + } else { + dc_buffer_resize(data, dc_buffer_get_size(data) + 512); } + ptr += 5; + pack_uint32_array_le(dc_buffer_get_data(data) + ptr, + dc_buffer_get_size(data)); + if (d->config4) { - dump->config4 = dc_buffer_new(512); - dc_buffer_append (dump->config4, d->config4, 512); + dc_buffer_append (data, d->config4, 512); + *(dc_buffer_get_data(data) + ptr + 4) = 1; + } else { + dc_buffer_resize(data, dc_buffer_get_size(data) + 512); } + ptr += 5; + pack_uint32_array_le(dc_buffer_get_data(data) + ptr, + dc_buffer_get_size(data)); + if (d->misc) { - dump->misc = dc_buffer_new(1500); - dc_buffer_append (dump->misc, d->misc, 1500); + dc_buffer_append (data, d->misc, 1500); + *(dc_buffer_get_data(data) + ptr + 4) = 1; + } else { + dc_buffer_resize(data, dc_buffer_get_size(data) + 1500); } + ptr += 5; + pack_uint32_array_le(dc_buffer_get_data(data) + ptr, + dc_buffer_get_size(data)); + if (d->logbook) { - dump->logbook = dc_buffer_new(d->logbook_size); - dc_buffer_append (dump->logbook, d->logbook, d->logbook_size); + dc_buffer_append (data, d->logbook, d->logbook_size); + *(dc_buffer_get_data(data) + ptr + 4) = 1; + } else { + dc_buffer_resize(data, dc_buffer_get_size(data) + d->logbook_size); } + ptr += 5; + pack_uint32_array_le(dc_buffer_get_data(data) + ptr, + dc_buffer_get_size(data)); + if (d->sample) { - dump->sample = dc_buffer_new(d->sample_size); - dc_buffer_append (dump->sample, d->sample, d->sample_size); + dc_buffer_append (data, d->sample, d->sample_size); + *(dc_buffer_get_data(data) + ptr + 4) = 1; + } else { + dc_buffer_resize(data, dc_buffer_get_size(data) + d->sample_size); }
+ ptr += 5; + pack_uint32_array_le(dc_buffer_get_data(data) + ptr, + dc_buffer_get_size(data)); + return DC_STATUS_SUCCESS; }
dc_status_t -cochran_common_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) +cochran_common_device_foreach (dc_device_t *abstract, + dc_dive_callback_t callback, void *userdata) { cochran_device_t *device = (cochran_device_t *) abstract; cochran_data_t *d = &(device->data); @@ -853,7 +942,8 @@ cochran_common_device_foreach (dc_device_t *abstract, dc_dive_callback_t callbac } d->current_dive_start_time = mktime(&t);
- if (callback && !callback ((unsigned char *) d, sizeof(device->data), d->current_fingerprint, sizeof (d->fingerprint), userdata)) + if (callback && !callback ((unsigned char *) d, sizeof(device->data), + d->current_fingerprint, sizeof (d->fingerprint), userdata)) return DC_STATUS_SUCCESS; }
diff --git a/src/cochran_common.h b/src/cochran_common.h index 136f153..7bba3fb 100644 --- a/src/cochran_common.h +++ b/src/cochran_common.h @@ -96,14 +96,62 @@ typedef struct cochran_device_t { const char *name; // serial port name serial_t *port; cochran_data_t data; // dive data used in parsing - dc_event_progress_t *progress; // We have to do progress in the _read function + dc_event_progress_t *progress; // for progress in the _read function } cochran_device_t;
-dc_status_t cochran_packet (cochran_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, int high_speed); -dc_status_t cochran_common_device_open (dc_device_t **out, dc_context_t *context, const char *name, const dc_device_vtable_t *vtable); + +// Commander log fields +#define CMD_SEC 1 +#define CMD_MIN 0 +#define CMD_HOUR 3 +#define CMD_DAY 2 +#define CMD_MON 5 +#define CMD_YEAR 4 +#define CME_START_OFFSET 6 // 4 bytes +#define CMD_WATER_CONDUCTIVITY 24 // 1 byte, 0=low, 2=high +#define CMD_START_TEMP 45 // 1 byte, F +#define CMD_START_DEPTH 56 // 2 byte, /4=ft +#define CMD_ALTITUDE 73 // 1 byte, /4=Kilofeet +#define CMD_END_OFFSET 128 // 4 bytes +#define CMD_MIN_TEMP 153 // 1 byte, F +#define CMD_BT 166 // 2 bytes, minutes +#define CMD_MAX_DEPTH 168 // 2 bytes, /4=ft +#define CMD_AVG_DEPTH 170 // 2 bytes, /4=ft +#define CMD_O2_PERCENT 210 // 8 bytes, 4 x 2 byte, /256=% + +// EMC log fields +#define EMC_SEC 0 +#define EMC_MIN 1 +#define EMC_HOUR 2 +#define EMC_DAY 3 +#define EMC_MON 4 +#define EMC_YEAR 5 +#define EMC_START_OFFSET 6 // 4 bytes +#define EMC_WATER_CONDUCTIVITY 25 // 1 byte, 0=low, 2=high +#define EMC_START_DEPTH 42 // 2 byte, /256=ft +#define EMC_START_TEMP 55 // 1 byte, F +#define EMC_ALTITUDE 89 // 1 byte, /4=Kilofeet +#define EMC_O2_PERCENT 144 // 20 bytes, 10 x 2 bytes, /256=% +#define EMC_HE_PERCENT 164 // 20 bytes, 10 x 2 bytes, /256=% +#define EMC_END_OFFSET 256 // 4 bytes +#define EMC_MIN_TEMP 293 // 1 byte, F +#define EMC_BT 304 // 2 bytes, minutes +#define EMC_MAX_DEPTH 306 // 2 bytes, /4=ft +#define EMC_AVG_DEPTH 310 // 2 bytes, /4=ft + + +dc_status_t cochran_packet (cochran_device_t *device, + const unsigned char command[], unsigned int csize, + unsigned char answer[], unsigned int asize, int high_speed); +dc_status_t cochran_common_device_open (dc_device_t **out, + dc_context_t *context, const char *name); dc_status_t cochran_common_device_close (dc_device_t *abstract); -dc_status_t cochran_common_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); -dc_status_t cochran_common_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size); +dc_status_t cochran_common_device_set_fingerprint (dc_device_t *abstract, + const unsigned char data[], unsigned int size); +dc_status_t cochran_common_device_read (dc_device_t *abstract, + unsigned int address, unsigned char data[], unsigned int size); static dc_status_t cochran_read_id (dc_device_t *abstract); -dc_status_t cochran_common_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); -dc_status_t cochran_common_device_dump (dc_device_t *abstract, dc_buffer_t *data); +dc_status_t cochran_common_device_foreach (dc_device_t *abstract, + dc_dive_callback_t callback, void *userdata); +dc_status_t cochran_common_device_dump (dc_device_t *abstract, + dc_buffer_t *data); diff --git a/src/cochran_common_parser.c b/src/cochran_common_parser.c index 744e5e7..9cc4040 100644 --- a/src/cochran_common_parser.c +++ b/src/cochran_common_parser.c @@ -34,15 +34,34 @@ #include "cochran_common_parser.h"
-dc_status_t cochran_common_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); -dc_status_t cochran_common_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); -dc_status_t cochran_common_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); +dc_status_t cochran_common_parser_set_data (dc_parser_t *abstract, + const unsigned char *data, unsigned int size); +dc_status_t cochran_common_parser_get_datetime (dc_parser_t *abstract, + dc_datetime_t *datetime); +static dc_status_t cochran_common_parser_get_field (dc_parser_t *abstract, + dc_field_type_t type, unsigned int flags, void *value); dc_status_t cochran_common_parser_destroy (dc_parser_t *abstract); -int cochran_common_handle_event (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata, unsigned char code, unsigned int offset, unsigned int time); +static dc_status_t cochran_common_parser_samples_foreach + (dc_parser_t *abstract, dc_sample_callback_t callback, + void *userdata); +int cochran_common_handle_event (dc_parser_t *abstract, + dc_sample_callback_t callback, void *userdata, unsigned char code, + unsigned int offset, unsigned int time); + + +static dc_parser_vtable_t cochran_common_parser_vtable = { + DC_FAMILY_COCHRAN, + cochran_common_parser_set_data, /* set_data */ + cochran_common_parser_get_datetime, /* datetime */ + cochran_common_parser_get_field, /* fields */ + cochran_common_parser_samples_foreach, /* samples_foreach */ + cochran_common_parser_destroy /* destroy */ +}; +
dc_status_t -cochran_common_parser_create (dc_parser_t **out, dc_context_t *context, const dc_parser_vtable_t *vtable) +cochran_common_parser_create (dc_parser_t **out, dc_context_t *context) { if (out == NULL) return DC_STATUS_INVALIDARGS; @@ -54,7 +73,7 @@ cochran_common_parser_create (dc_parser_t **out, dc_context_t *context, const dc return DC_STATUS_NOMEMORY; }
- parser_init (parser, context, vtable); + parser_init (parser, context, &cochran_common_parser_vtable);
*out = parser;
@@ -73,7 +92,8 @@ cochran_common_parser_destroy (dc_parser_t *abstract)
dc_status_t -cochran_common_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) +cochran_common_parser_set_data (dc_parser_t *abstract, + const unsigned char *data, unsigned int size) { abstract->data = data; abstract->size = size; @@ -84,7 +104,8 @@ cochran_common_parser_set_data (dc_parser_t *abstract, const unsigned char *data
// There are two date formats used by Cochran dc_status_t -cochran_common_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) +cochran_common_parser_get_datetime (dc_parser_t *abstract, + dc_datetime_t *datetime) { cochran_data_t *data = (cochran_data_t *) abstract->data; const unsigned char *log = data->current_log; @@ -108,9 +129,48 @@ cochran_common_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dateti return DC_STATUS_SUCCESS; }
+static dc_status_t +cochran_common_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, + unsigned int flags, void *value) +{ + cochran_data_t *data = (cochran_data_t *) abstract->data; + + switch (data->model & 0xFF0000) + { + case COCHRAN_MODEL_COMMANDER_FAMILY: + return cochran_cmdr_parser_get_field(abstract, type, flags, value); + break; + case COCHRAN_MODEL_EMC_FAMILY: + return cochran_emc_parser_get_field(abstract, type, flags, value); + break; + } +} + +static dc_status_t +cochran_common_parser_samples_foreach (dc_parser_t *abstract, + dc_sample_callback_t callback, void *userdata) +{ + cochran_data_t *data = (cochran_data_t *) abstract->data; + + switch (data->model & 0xFF0000) + { + case COCHRAN_MODEL_COMMANDER_FAMILY: + return cochran_cmdr_parser_samples_foreach(abstract, callback, + userdata); + break; + case COCHRAN_MODEL_EMC_FAMILY: + return cochran_emc_parser_samples_foreach(abstract, callback, + userdata); + break; + } +} + +
int -cochran_common_handle_event (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata, unsigned char code, unsigned int offset, unsigned int time) +cochran_common_handle_event (dc_parser_t *abstract, + dc_sample_callback_t callback, void *userdata, unsigned char code, + unsigned int offset, unsigned int time) { cochran_data_t *data = (cochran_data_t *) abstract->data; cochran_events_t *e = cochran_events; diff --git a/src/cochran_common_parser.h b/src/cochran_common_parser.h index d19a619..9c4a67d 100644 --- a/src/cochran_common_parser.h +++ b/src/cochran_common_parser.h @@ -36,6 +36,8 @@ static cochran_events_t cochran_events[] = { SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE }, { 0xAD, 5, "Ceiling increase", SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE }, + { 0xBD, 1, "Switched to nomal PO2 setting", + SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE }, { 0xC0, 1, "Switched to FO2 21% mode", SAMPLE_EVENT_GASCHANGE, SAMPLE_FLAGS_NONE }, { 0xC1, 1, "Ascent rate greater than limit", @@ -50,20 +52,34 @@ static cochran_events_t cochran_events[] = { SAMPLE_EVENT_DEEPSTOP, SAMPLE_FLAGS_BEGIN }, { 0xC8, 1, "PO2 too high", SAMPLE_EVENT_FLOOR, SAMPLE_FLAGS_BEGIN }, + { 0xCC, 1, "Low Cylinder 1 pressure", + SAMPLE_EVENT_NONE, SAMPLE_FLAGS_BEGIN }, { 0xCE, 1, "Non-decompression warning", SAMPLE_EVENT_RBT, SAMPLE_FLAGS_BEGIN }, + { 0xCD, 1, "Switched to deco blend", + SAMPLE_EVENT_NONE, SAMPLE_FLAGS_BEGIN }, + { 0xD0, 1, "Breathing rate alarm", + SAMPLE_EVENT_NONE, SAMPLE_FLAGS_BEGIN }, + { 0xD3, 1, "Low gas 1 flow rate", + SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE }, { 0xD6, 1, "Depth is less than ceiling", SAMPLE_EVENT_CEILING, SAMPLE_FLAGS_BEGIN }, { 0xD8, 1, "End decompression mode", SAMPLE_EVENT_DEEPSTOP, SAMPLE_FLAGS_END }, { 0xE1, 1, "End ascent rate warning", SAMPLE_EVENT_ASCENT, SAMPLE_FLAGS_END }, + { 0xE2, 1, "Low SBAT battery warning", + SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE }, { 0xE3, 1, "Switched to FO2 mode", SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE }, + { 0xE5, 1, "Switched to PO2 mode", + SAMPLE_EVENT_NONE, SAMPLE_FLAGS_NONE }, { 0xEE, 1, "End non-decompresison warning", SAMPLE_EVENT_RBT, SAMPLE_FLAGS_END }, { 0xEF, 1, "Switch to blend 2", SAMPLE_EVENT_GASCHANGE2, SAMPLE_FLAGS_NONE }, + { 0xF0, 1, "Breathing rate alarm", + SAMPLE_EVENT_NONE, SAMPLE_FLAGS_END }, { 0xF3, 1, "Switch to blend 1", SAMPLE_EVENT_GASCHANGE2, SAMPLE_FLAGS_NONE }, { 0xF6, 1, "End Depth is less than ceiling", @@ -73,8 +89,27 @@ static cochran_events_t cochran_events[] = { };
-dc_status_t cochran_common_parser_create (dc_parser_t **out, dc_context_t *context, const dc_parser_vtable_t *vtable); +// Common +dc_status_t cochran_common_parser_create (dc_parser_t **out, + dc_context_t *context); dc_status_t cochran_common_parser_destroy (dc_parser_t *abstract); -dc_status_t cochran_common_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); -dc_status_t cochran_common_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); -int cochran_common_handle_event (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata, unsigned char code, unsigned int offset, unsigned int time); +dc_status_t cochran_common_parser_set_data (dc_parser_t *abstract, + const unsigned char *data, unsigned int size); +dc_status_t cochran_common_parser_get_datetime (dc_parser_t *abstract, + dc_datetime_t *datetime); +int cochran_common_handle_event (dc_parser_t *abstract, + dc_sample_callback_t callback, void *userdata, unsigned char code, + unsigned int offset, unsigned int time); + +// EMC FAMILY +dc_status_t cochran_emc_parser_get_field (dc_parser_t *abstract, + dc_field_type_t type, unsigned int flags, void *value); +dc_status_t cochran_emc_parser_samples_foreach (dc_parser_t *abstract, + dc_sample_callback_t callback, void *userdata); + +// COMMANDER FAMILY +dc_status_t cochran_cmdr_parser_get_field(dc_parser_t *abstract, + dc_field_type_t type, unsigned int flags, void *value); +dc_status_t cochran_cmdr_parser_samples_foreach (dc_parser_t *abstract, + dc_sample_callback_t callback, void *userdata); + diff --git a/src/cochran_emc.c b/src/cochran_emc.c deleted file mode 100644 index c4b0367..0000000 --- a/src/cochran_emc.c +++ /dev/null @@ -1,67 +0,0 @@ -/* - * libdivecomputer - * - * Copyright (C) 2014 John Van Ostrand - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - -#include <stdlib.h> -#include <time.h> - -#include <libdivecomputer/cochran.h> - -#include "context-private.h" -#include "device-private.h" -#include "serial.h" - -#include "cochran_common.h" -#include "cochran_emc.h" - - -static const dc_device_vtable_t cochran_emc_device_vtable = { - DC_FAMILY_COCHRAN_EMC, - cochran_common_device_set_fingerprint, /* set_fingerprint */ - cochran_common_device_read, /* read */ - NULL, /* write */ - cochran_common_device_dump, /* dump */ - cochran_common_device_foreach, /* foreach */ - cochran_common_device_close /* close */ -}; - - -dc_status_t -cochran_emc_device_open (dc_device_t **out, dc_context_t *context, const char *name) -{ - dc_status_t rc; - - rc = cochran_common_device_open(out, context, name, &cochran_emc_device_vtable); - if (rc != DC_STATUS_SUCCESS) - return rc; - - cochran_device_t *device = (cochran_device_t *) *out; - - // Check family - if ((device->data.model & 0xFF0000) != COCHRAN_MODEL_EMC_FAMILY) { - ERROR (context, "Device not recognized."); - serial_close (device->port); - free (device->data.id); - free (device); - return DC_STATUS_UNSUPPORTED; - } - - return DC_STATUS_SUCCESS; -} diff --git a/src/cochran_emc.h b/src/cochran_emc.h deleted file mode 100644 index e21036b..0000000 --- a/src/cochran_emc.h +++ /dev/null @@ -1,180 +0,0 @@ -/* - * libdivecomputer - * - * Copyright (C) 2014 John Van Ostrand - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - -// 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 struct cochran_emc_log_t cochran_emc_log_t; - -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; - - -typedef 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 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)); - -typedef struct cochran_emc_config1_t cochran_emc_config1_t; diff --git a/src/cochran_emc_parser.c b/src/cochran_emc_parser.c index cf6e9af..f336e43 100644 --- a/src/cochran_emc_parser.c +++ b/src/cochran_emc_parser.c @@ -32,7 +32,6 @@ #include "array.h"
#include "cochran_common.h" -#include "cochran_emc.h" #include "cochran_common_parser.h"
@@ -44,33 +43,16 @@ struct dive_stats { };
-static dc_status_t cochran_emc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); -static dc_status_t cochran_emc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); -static void cochran_emc_parse_dive_stats (dc_parser_t *abstract, struct dive_stats *stats); - - -static dc_parser_vtable_t cochran_emc_parser_vtable = { - DC_FAMILY_COCHRAN_EMC, - cochran_common_parser_set_data, /* set_data */ - cochran_common_parser_get_datetime, /* datetime */ - cochran_emc_parser_get_field, /* fields */ - cochran_emc_parser_samples_foreach, /* samples_foreach */ - cochran_common_parser_destroy /* destroy */ -}; +static void cochran_emc_parse_dive_stats (dc_parser_t *abstract, + struct dive_stats *stats);
dc_status_t -cochran_emc_parser_create (dc_parser_t **out, dc_context_t *context) -{ - return cochran_common_parser_create(out, context, &cochran_emc_parser_vtable); -} - - -static dc_status_t -cochran_emc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) +cochran_emc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, + unsigned int flags, void *value) { cochran_data_t *data = (cochran_data_t *) abstract->data; - cochran_emc_log_t *log = (cochran_emc_log_t *) data->current_log; + unsigned char *log = data->current_log;
dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_salinity_t *water = (dc_salinity_t *) value; @@ -86,44 +68,50 @@ cochran_emc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsig cochran_emc_parse_dive_stats(abstract, &stats); *((unsigned int*) value) = min_temp; } else - *((unsigned int*) value) = (log->start_temperature - 32) / 1.8; + *((unsigned int*) value) = (log[EMC_START_TEMP] - 32) / 1.8; break; case DC_FIELD_DIVETIME: if (data->corrupt_dive) { cochran_emc_parse_dive_stats(abstract, &stats); *((unsigned int*) value) = dive_time; } else - *((unsigned int *) value) = array_uint16_le (log->bt) * 60; + *((unsigned int *) value) = array_uint16_le (log + EMC_BT) * 60; break; case DC_FIELD_MAXDEPTH: if (data->corrupt_dive) { cochran_emc_parse_dive_stats(abstract, &stats); *((unsigned int*) value) = max_depth; } else - *((double *) value) = array_uint16_le (log->max_depth) / 4 * FEET; + *((double *) value) = array_uint16_le (log + EMC_MAX_DEPTH) / 4 + * FEET; break; case DC_FIELD_AVGDEPTH: if (data->corrupt_dive) { cochran_emc_parse_dive_stats(abstract, &stats); *((unsigned int*) value) = avg_depth; } else - *((double *) value) = array_uint16_le (log->avg_depth) / 4 * FEET; + *((double *) value) = array_uint16_le (log + EMC_AVG_DEPTH) / 4 + * FEET; break; case DC_FIELD_GASMIX_COUNT: *((unsigned int *) value) = 10; break; case DC_FIELD_GASMIX: - gasmix->oxygen = (double) array_uint16_le ((char *)log->o2_percent + 2 * flags) / 256 / 100; - gasmix->helium = (double) array_uint16_le ((char *)log->he_percent + 2 * flags) / 256 / 100; + gasmix->oxygen = (double) array_uint16_le ((char *) log + + EMC_O2_PERCENT + 2 * flags) / 256 / 100; + gasmix->helium = (double) array_uint16_le ((char *) log + + EMC_HE_PERCENT + 2 * flags) / 256 / 100; gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; break; case DC_FIELD_SALINITY: // 0 = low conductivity, 2 = high, maybe there's a 1? - water->type = ( log->water_conductivity == 0 ? DC_WATER_FRESH : DC_WATER_SALT ); - water->density = 1000 + 12.5 * log->water_conductivity; + water->type = ( log[EMC_WATER_CONDUCTIVITY] == 0 ? DC_WATER_FRESH + : DC_WATER_SALT ); + water->density = 1000 + 12.5 * log[EMC_WATER_CONDUCTIVITY]; break; case DC_FIELD_ATMOSPHERIC: - *(double *) value = ATM / BAR * pow(1 - 0.0000225577 * (double) log->altitude * 250 * FEET, 5.25588); + *(double *) value = ATM / BAR * pow(1 - 0.0000225577 + * (double) log[EMC_ALTITUDE] * 250 * FEET, 5.25588); break; default: return DC_STATUS_UNSUPPORTED; @@ -172,12 +160,13 @@ cochran_emc_guess_sample_size(cochran_data_t *data)
-static dc_status_t -cochran_emc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +dc_status_t +cochran_emc_parser_samples_foreach (dc_parser_t *abstract, + dc_sample_callback_t callback, void *userdata) { cochran_data_t *data = (cochran_data_t *) abstract->data; unsigned char *sdata = data->current_sample; - cochran_emc_log_t *log = (cochran_emc_log_t *) data->current_log; + unsigned char *log = data->current_log; unsigned int size = data->current_sample_size; unsigned char *s;
@@ -196,12 +185,15 @@ cochran_emc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t size = cochran_emc_guess_sample_size(data); }
- // Cochran samples depth every second and varies between ascent rate - // and temp ever other second. - // In the 21st, 22nd, 23rd, 24th samples are NDL remaining, deco time and ceiling + /* + * Cochran samples depth every second and varies between ascent rate + * and temp ever other second. + * In the 21st, 22nd, 23rd, 24th samples are NDL remaining, + *deco time and ceiling + */
// Prime with values from the dive log section - depth = array_uint16_le (log->start_depth) / 256; + depth = array_uint16_le (log + EMC_START_DEPTH) / 256;
last_sample_time = sample.time = time; if (callback) callback (DC_SAMPLE_TIME, sample, userdata); @@ -209,7 +201,7 @@ cochran_emc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t sample.depth = depth * FEET; if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
- sample.temperature = (log->start_temperature - 32) / 1.8; + sample.temperature = (log[EMC_START_TEMP] - 32) / 1.8; if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
while (offset < size) { @@ -237,7 +229,8 @@ cochran_emc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t
// Check for event if (s[0] & 0x80) { - offset += cochran_common_handle_event(abstract, callback, userdata, s[0], offset, time); + offset += cochran_common_handle_event(abstract, callback, userdata, + s[0], offset, time); switch (s[0]) { case 0xC5: // Deco obligation begins @@ -276,12 +269,12 @@ cochran_emc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t // Ascent rate is logged in the 0th sample, temp in the 1st, repeat. if (time % 2 == 0) { // Ascent rate - ascent_rate = (s[1] & 0x7f) / 4 * (s[0] & 0x80 ? 1 : -1); + ascent_rate = (s[1] & 0x7f) / 4 * (s[1] & 0x80 ? 1 : -1); sample.ascent_rate = ascent_rate * FEET; if (callback) callback (DC_SAMPLE_ASCENT_RATE, sample, userdata); } else { // Temperature logged in half degrees F above 20 - temperature = s[1] / 2 + 20; + temperature = (float) s[1] / 2 + 20; sample.temperature = (temperature - 32.0) / 1.8;
if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); @@ -323,11 +316,12 @@ cochran_emc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t return DC_STATUS_SUCCESS; }
-void cochran_emc_parse_dive_stats (dc_parser_t *abstract, struct dive_stats *stats) +void cochran_emc_parse_dive_stats (dc_parser_t *abstract, + struct dive_stats *stats) { cochran_data_t *data = (cochran_data_t *) abstract->data; unsigned char *sdata = data->current_sample; - cochran_emc_log_t *log = (cochran_emc_log_t *) data->current_log; + unsigned char *log = data->current_log; unsigned int size = data->current_sample_size; unsigned char *s; unsigned int offset = 0; diff --git a/src/descriptor.c b/src/descriptor.c index 20f74e6..8836709 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -223,9 +223,9 @@ static const dc_descriptor_t g_descriptors[] = { {"Shearwater", "Petrel", DC_FAMILY_SHEARWATER_PETREL, 3}, /* Dive Rite NiTek Q */ {"Dive Rite", "NiTek Q", DC_FAMILY_DIVERITE_NITEKQ, 0}, - {"Cochran", "Commander", DC_FAMILY_COCHRAN_COMMANDER, 0}, - {"Cochran", "EMC-20H", DC_FAMILY_COCHRAN_EMC, 0}, - {"Cochran", "EMC-16", DC_FAMILY_COCHRAN_EMC, 0}, + {"Cochran", "Commander", DC_FAMILY_COCHRAN, 0}, + {"Cochran", "EMC-16", DC_FAMILY_COCHRAN, 1}, + {"Cochran", "EMC-20H", DC_FAMILY_COCHRAN, 2}, };
typedef struct dc_descriptor_iterator_t { diff --git a/src/device.c b/src/device.c index e8661b0..cdd41ab 100644 --- a/src/device.c +++ b/src/device.c @@ -155,11 +155,8 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr case DC_FAMILY_DIVERITE_NITEKQ: rc = diverite_nitekq_device_open (&device, context, name); break; - case DC_FAMILY_COCHRAN_COMMANDER: - rc = cochran_cmdr_device_open (&device, context, name); - break; - case DC_FAMILY_COCHRAN_EMC: - rc = cochran_emc_device_open (&device, context, name); + case DC_FAMILY_COCHRAN: + rc = cochran_common_device_open (&device, context, name); break; default: return DC_STATUS_INVALIDARGS; diff --git a/src/parser.c b/src/parser.c index dc6655b..07e9d2d 100644 --- a/src/parser.c +++ b/src/parser.c @@ -132,11 +132,8 @@ dc_parser_new (dc_parser_t **out, dc_device_t *device) case DC_FAMILY_DIVERITE_NITEKQ: rc = diverite_nitekq_parser_create (&parser, context); break; - case DC_FAMILY_COCHRAN_COMMANDER: - rc = cochran_cmdr_parser_create (&parser, context); - break; - case DC_FAMILY_COCHRAN_EMC: - rc = cochran_emc_parser_create (&parser, context); + case DC_FAMILY_COCHRAN: + rc = cochran_common_parser_create (&parser, context); break; default: return DC_STATUS_INVALIDARGS;
John,
Nice work. This is already a huge step in the right direction, but we're not there yet.
There is a problem in the way you store the cochran_data_t structure in the device handle. Most of this info only used by the dump/foreach function (or one of its helpers), so it doesn't really belong inside the handle. This results in a number of problems. For example you allocate several pieces of data (id, config, etc) in those functions and store them in the handle. But if someone calls one of those functions a second time, you allocate them again and overwrite the original pointers. So you'll end up with a memory leak. You can free the old data first, but I think you're better off using local data structures because there is no reason why any of this data needs to persist between the high-level calls.
Overall, I find the logic rather hard to follow because it's spread over all your cochran_read_* functions. These functions do not only download some data, but also sets some fields, which are then used in one of the other functions. I think it would be easier if the processing was a bit more separated from the downloading. I have the feeling that there's something wrong, but I can't really tell what or where. Maybe it's just that I don't understand the format very well. Do you have some data files available?
Note that several of those data pieces have a fixed size, so why even allocate them? Just use a byte array with the right size. Some like:
struct { ... unsigned char id0[67]; unsigned char id1[67]; unsigned char config1[512]; unsigned char config2[512]; ... };
Now you no longer have to worry about freeing them. Instead of config1, config2, etc you could even use an array here. That will make your code even more generic. Instead of repeating the same code, you can use a loop.
BTW, I'm not sure what's in these config pages, but based on the name, I assume it contains settings. So this is something applications may want to access directly. I suspect there the protocol also support changing those settings. Have a look at the hw_ostc_device_eeprom_{read,write}. (This is certainly not a must have feature right now, but it's something you may want to keep in mind already.)
You have the same problem with the progress field. Again, I think it makes more sense to make this a local variable, and pass it as a parameter to the internal read function. Note that you're also reporting progress for each individual step, rather than the entire download.
Those high baudrates are indeed a bit odd. Do they even work? You don't check the return value of the serial_configure() function. So if the underlying driver (or OS) doesn't support it, unexpected results may happen. I implemented the support for non-standard baudrates long time ago, and unfortunately it's not guaranteed to work everywhere. Initially it was needed for the iconhd backend, in the end it turned out that this baudrate was a bug in the mares application. The iconhd uses some microchip with CDC-ACM, where the baudrate doesn't matter at all. So every value works equally well.
On 28-10-14 01:18, John Van Ostrand wrote:
Removed dependency on packed structure.
Nice. I noticed that you introduced CMD_* and EMC_* constants to keep the code readable. An alternative I have used in some backends, is to have a layout structure containing those offsets. And then you define one such structure for each model, and store a pointer to the correct table in the device/parser handle. This allows you to write code like this:
value = array_uint16_le (log + layout->xxx);
You can see this for example in the hw_ostc_parser.c file. The nice thing is that you can re-use the same code for all models. Adding a new model is just a matter of adding a new structure.
I'm not saying you can or must use this technique in your backend, but it's maybe something to consider.
Removed extraneous close/opens.
I'm glad those are not necessary after all.
Formated lines to 78 chars.
This wasn't needed. I have no strict limits on the line length. Of course you should try to keep the line length reasonable, but if you have a longer one once in a while, I won't complain. Artificially limiting the lines to 78 chars makes the code only less readable. So please don't do that.
For example some of those long lines are because you have assignments in if conditions:
if ((rc = cochran_common_serial_open(device, context)) != DC_STATUS_SUCCESS) return rc;
replace that with:
rc = cochran_common_serial_open(device, context); if (rc != DC_STATUS_SUCCESS) return rc;
Much more readable and shorter too!
Merged EMC and Commander backends to common.
Great. Just a few issues remaining here.
You named your backend DC_FAMILY_COCHRAN and the corresponding functions cochran_common_{device,parser}_*. Please name them after one specific model. For example DC_FAMILY_COCHRAN_COMMANDER and cochran_commander_{device,parser}_*, etc. That's because in the future, there might be more than one cochran backend. The backend name is purely internal (except for the family name), so it doesn't really matter that the commander backend also supports the emc.
For the public header files, make one cochran_commander.h file containing the open and create functions (e.g. your current cochran.h file), and then a new cochran.h file that just includes this file. See the existing files for examples.
Change nanosleep() calls to serial_sleep().
We're getting closer, but the windows build is still broken:
CC cochran_common_parser.lo In file included from ../../source/src/cochran_common_parser.c:33: ../../source/src/cochran_common.h:73: error: expected specifier-qualifier-list before 'time_t' ../../source/src/cochran_common_parser.c: In function 'cochran_common_parser_get_datetime': ../../source/src/cochran_common_parser.c:113: error: 'cochran_data_t' has no member named 'date_format' ../../source/src/cochran_common_parser.c: In function 'cochran_common_parser_get_field': ../../source/src/cochran_common_parser.c:147: warning: control reaches end of non-void function ../../source/src/cochran_common_parser.c: In function 'cochran_common_parser_samples_foreach': ../../source/src/cochran_common_parser.c:166: warning: control reaches end of non-void function ../../source/src/cochran_common_parser.c: In function 'cochran_common_handle_event': ../../source/src/cochran_common_parser.c:175: warning: unused variable 'data' make[1]: *** [cochran_common_parser.lo] Error 1
I think you're missing an #include <time.h> somewhere for the use of time_t in the cochran_data_t structure. But you shouldn't need that because libdivecomputer has its own time functions and structures. Use those instead.
Change cochran_common_dump to use dc_buffer properly.
I still need to look at the details of your new dump implementation. One thing I did notice is that you forgot to remove the cochran_data_dump from the public header :-)
That's it for now.
Jef
On Sun, Nov 2, 2014 at 1:13 PM, Jef Driesen jef@libdivecomputer.org wrote:
John,
Nice work. This is already a huge step in the right direction, but we're not there yet.
There is a problem in the way you store the cochran_data_t structure in the device handle. Most of this info only used by the dump/foreach function (or one of its helpers), so it doesn't really belong inside the handle.
The structure members under "// Config items" are used to allow for different Cochran models and different feature configurations for a given model. The fields are set during cochran_set_device_config() so that I know how to handle reading the logbook and the sample data. So while I'm detecting the model I thought I might as well set the variables needed in foreach so I have one section of code to look at when adding a new model.
Some of the variables I need to keep between calls to foreach and this seemed to be a good place.
I know I'm keeping id*, config* and misc* around after they are needed. I could drop them sooner but they come in handy for the dump function.
This results in a number of problems. For example you allocate several pieces of data (id, config, etc) in those functions and store them in the handle. But if someone calls one of those functions a second time, you allocate them again and overwrite the original pointers. So you'll end up with a memory leak. You can free the old data first, but I think you're better off using local data structures because there is no reason why any of this data needs to persist between the high-level calls.
I see, calling _open and _foreach a second time could leak memory.
Fixed.
Overall, I find the logic rather hard to follow because it's spread over all your cochran_read_* functions. These functions do not only download some data, but also sets some fields, which are then used in one of the other functions. I think it would be easier if the processing was a bit more separated from the downloading. I have the feeling that there's something wrong, but I can't really tell what or where. Maybe it's just that I don't understand the format very well. Do you have some data files available?
There are two reasons I split the read up into several functions. The cochran_read_id() function reads the blocks needed to detect the DC model so it knows how to perform the other reads. I do this in the cochran_common_device_open() because I wanted to give immediate feedback to the application if the DC wasn't supported.
The other reads could be in one function but it would be a large function. There is some handle data settings in the read functions.
cochran_read_id(): determine model type and features cochran_read_config(): determine the head pointer to the sample buffer, I see now that I don't use this. cochran_read_misc(): read only to mimic Analyst cochran_read_logbook(): Get number of logged dives(from config1), calculate logbook_size cochran_read_parms(): Doesn't read anything, calculate parameters needed for reading samples. cochran_read_samples(): Reads sample data.
There really isn't much happening inside the read calls that isn't needed there. The read_config() has a few lines that can actually be removed. The read_logbook has another few lines that calculate the size, and read_samples has two lines that calculate size. Since all the read_*() functions are called by read_all() it's trivial to move them there.
Done. Calculations are moved to the read_all function.
Note that several of those data pieces have a fixed size, so why even allocate them? Just use a byte array with the right size. Some like:
struct { ... unsigned char id0[67]; unsigned char id1[67]; unsigned char config1[512]; unsigned char config2[512]; ... };
Now you no longer have to worry about freeing them. Instead of config1, config2, etc you could even use an array here. That will make your code even more generic. Instead of repeating the same code, you can use a loop.
I used mallocs to save memory. I can make them arrays. Done.
BTW, I'm not sure what's in these config pages, but based on the name, I assume it contains settings. So this is something applications may want to access directly. I suspect there the protocol also support changing those settings. Have a look at the hw_ostc_device_eeprom_{read,write}. (This is certainly not a must have feature right now, but it's something you may want to keep in mind already.)
Yes, that is device configuration as well as current calculated tissue loads. I didn't look at the OSTC code so I didn't see any evidence of setting configs on the DCs. I intend to write code to do just that for the Cochran and I wasn't sure where to do that since it looked like libdivecomputer doesn't expose an API for it. There are some settings, like date/time that aren't configurable on the DC. And I would like to be able to set configuration from Linux or even a tablet.
You have the same problem with the progress field. Again, I think it makes more sense to make this a local variable, and pass it as a parameter to the internal read function. Note that you're also reporting progress for each individual step, rather than the entire download.
I don't know the size of the entire download when I start needing a progress bar. You'll note I don't have one for cochran_read_id(), read_config() or read_misc(). These are small enough I figured one wasn't needed. I don't know the size of the sample data until I read the logbook. So it seemed the best approach was to have two.
The other backends I looked at read the from the DC in blocks of data and were able to manage the progress within the function. The cochran does very big reads at very high data rates (800Kb/s). So I can't do the progress at the cochran_common_device_read() function because it issues one command to read a huge block of data. So I did it in the cochran_packet() function. Since that is exposed I couldn't add a parameter without breaking the API. I could add a separate packet function that accepts a progress but this seems to be a better approach.
Those high baudrates are indeed a bit odd. Do they even work? You don't
check the return value of the serial_configure() function. So if the underlying driver (or OS) doesn't support it, unexpected results may happen. I implemented the support for non-standard baudrates long time ago, and unfortunately it's not guaranteed to work everywhere. Initially it was needed for the iconhd backend, in the end it turned out that this baudrate was a bug in the mares application. The iconhd uses some microchip with CDC-ACM, where the baudrate doesn't matter at all. So every value works equally well.
They are little twitchy but I get good reads most of the time. I've tried different rates and it does require a non-standard rate. At least I'm not aware of a standard rate that high.
On 28-10-14 01:18, John Van Ostrand wrote:
Removed dependency on packed structure.
Nice. I noticed that you introduced CMD_* and EMC_* constants to keep the code readable. An alternative I have used in some backends, is to have a layout structure containing those offsets. And then you define one such structure for each model, and store a pointer to the correct table in the device/parser handle. This allows you to write code like this:
value = array_uint16_le (log + layout->xxx);
You can see this for example in the hw_ostc_parser.c file. The nice thing is that you can re-use the same code for all models. Adding a new model is just a matter of adding a new structure.
I'm not saying you can or must use this technique in your backend, but it's maybe something to consider.
That's a good idea.
Removed extraneous close/opens.
I'm glad those are not necessary after all.
Formated lines to 78 chars.
This wasn't needed. I have no strict limits on the line length. Of course you should try to keep the line length reasonable, but if you have a longer one once in a while, I won't complain. Artificially limiting the lines to 78 chars makes the code only less readable. So please don't do that.
For example some of those long lines are because you have assignments in if conditions:
if ((rc = cochran_common_serial_open(device, context)) != DC_STATUS_SUCCESS) return rc;
replace that with:
rc = cochran_common_serial_open(device, context); if (rc != DC_STATUS_SUCCESS) return rc;
Much more readable and shorter too!
Fixed.
Merged EMC and Commander backends to common.
Great. Just a few issues remaining here.
You named your backend DC_FAMILY_COCHRAN and the corresponding functions cochran_common_{device,parser}_*. Please name them after one specific model. For example DC_FAMILY_COCHRAN_COMMANDER and cochran_commander_{device,parser}_*, etc. That's because in the future, there might be more than one cochran backend. The backend name is purely internal (except for the family name), so it doesn't really matter that the commander backend also supports the emc.
For the public header files, make one cochran_commander.h file containing the open and create functions (e.g. your current cochran.h file), and then a new cochran.h file that just includes this file. See the existing files for examples.
Done.
Change nanosleep() calls to serial_sleep().
We're getting closer, but the windows build is still broken:
CC cochran_common_parser.lo In file included from ../../source/src/cochran_common_parser.c:33: ../../source/src/cochran_common.h:73: error: expected specifier-qualifier-list before 'time_t' ../../source/src/cochran_common_parser.c: In function 'cochran_common_parser_get_datetime': ../../source/src/cochran_common_parser.c:113: error: 'cochran_data_t' has no member named 'date_format' ../../source/src/cochran_common_parser.c: In function 'cochran_common_parser_get_field': ../../source/src/cochran_common_parser.c:147: warning: control reaches end of non-void function ../../source/src/cochran_common_parser.c: In function 'cochran_common_parser_samples_foreach': ../../source/src/cochran_common_parser.c:166: warning: control reaches end of non-void function ../../source/src/cochran_common_parser.c: In function 'cochran_common_handle_event': ../../source/src/cochran_common_parser.c:175: warning: unused variable 'data' make[1]: *** [cochran_common_parser.lo] Error 1
I think you're missing an #include <time.h> somewhere for the use of time_t in the cochran_data_t structure. But you shouldn't need that because libdivecomputer has its own time functions and structures. Use those instead.
I think I've fixed that.
Change cochran_common_dump to use dc_buffer properly.
I still need to look at the details of your new dump implementation. One thing I did notice is that you forgot to remove the cochran_data_dump from the public header :-)
Done.
That's it for now.
Jef
John,
There is a problem with how you pass the data to the dive callback function. I didn't notice this the first time, but you pass a pointer to the cochran_data_t structure to the callback function. That's not how this is supposed to work. You need to extract the raw binary data of each dive, and then pass that to the callback function. What you do now is pass the data of all dives, with a couple of fields pointing to the dive of interest. That's wrong and needs to change.
The cochran_data_t alone is already a problem here, because it contains pointers. The dive data is supposed to be a simple byte array which can be serialized as-is. Remember, libdivecomputer has absolutely no control over how or when the application uses the data. For example the application can copy the dives, and only parse them after the download has finished. In that case the device handle will already have been closed when parsing the data. Thus anything in your structure pointing to data in that handle will have been freed, and unexpected things will happen. With a self contained blob of binary data that's not a problem. You can copy it, write it to disk, etc with no problems.
See some more comments inline.
On 03-11-14 02:58, John Van Ostrand wrote:
On Sun, Nov 2, 2014 at 1:13 PM, Jef Driesen jef@libdivecomputer.org wrote:
Nice work. This is already a huge step in the right direction, but we're not there yet.
There is a problem in the way you store the cochran_data_t structure in the device handle. Most of this info only used by the dump/foreach function (or one of its helpers), so it doesn't really belong inside the handle.
The structure members under "// Config items" are used to allow for different Cochran models and different feature configurations for a given model. The fields are set during cochran_set_device_config() so that I know how to handle reading the logbook and the sample data. So while I'm detecting the model I thought I might as well set the variables needed in foreach so I have one section of code to look at when adding a new model.
Some of the variables I need to keep between calls to foreach and this seemed to be a good place.
I know I'm keeping id*, config* and misc* around after they are needed. I could drop them sooner but they come in handy for the dump function.
It's fine to store certain data in the device handle if it's needed for the device connection state. For example the id packets are read during the open, and you need them later in the other functions. That's a perfect argument to store them in the device handle.
But nearly everything else is state for either the dump or foreach function. After those functions returns there is no need to keep any of the data around. If those functions get called a second time, they should start with a completely fresh state. Thus the "global" device handle is the wrong place to store this data. The fact that there are memory leaks when someone calls those functions twice is a symptom of that. If the state were private to the function, then you wouldn't have to worry about left overs from a previous call.
This results in a number of problems. For example you allocate several pieces of data (id, config, etc) in those functions and store them in the handle. But if someone calls one of those functions a second time, you allocate them again and overwrite the original pointers. So you'll end up with a memory leak. You can free the old data first, but I think you're better off using local data structures because there is no reason why any of this data needs to persist between the high-level calls.
I see, calling _open and _foreach a second time could leak memory.
Fixed.
It's better now, but freeing the pointers is just fixing the symptoms, and not the underlying problem (see above).
Overall, I find the logic rather hard to follow because it's spread over all your cochran_read_* functions. These functions do not only download some data, but also sets some fields, which are then used in one of the other functions. I think it would be easier if the processing was a bit more separated from the downloading. I have the feeling that there's something wrong, but I can't really tell what or where. Maybe it's just that I don't understand the format very well. Do you have some data files available?
There are two reasons I split the read up into several functions. The cochran_read_id() function reads the blocks needed to detect the DC model so it knows how to perform the other reads. I do this in the cochran_common_device_open() because I wanted to give immediate feedback to the application if the DC wasn't supported.
That's fine. I didn't had any problem with that.
The other reads could be in one function but it would be a large function. There is some handle data settings in the read functions.
cochran_read_id(): determine model type and features cochran_read_config(): determine the head pointer to the sample buffer, I see now that I don't use this. cochran_read_misc(): read only to mimic Analyst cochran_read_logbook(): Get number of logged dives(from config1), calculate logbook_size cochran_read_parms(): Doesn't read anything, calculate parameters needed for reading samples. cochran_read_samples(): Reads sample data.
There really isn't much happening inside the read calls that isn't needed there. The read_config() has a few lines that can actually be removed. The read_logbook has another few lines that calculate the size, and read_samples has two lines that calculate size. Since all the read_*() functions are called by read_all() it's trivial to move them there.
Done. Calculations are moved to the read_all function.
I always try to keep the code for downloading separate from the parsing/processing. That usually makes it easier to follow the logic. So I think your latest version is a move in the right direction.
Note that several of those data pieces have a fixed size, so why even allocate them? Just use a byte array with the right size. Some like:
struct { ... unsigned char id0[67]; unsigned char id1[67]; unsigned char config1[512]; unsigned char config2[512]; ... };
Now you no longer have to worry about freeing them. Instead of config1, config2, etc you could even use an array here. That will make your code even more generic. Instead of repeating the same code, you can use a loop.
I used mallocs to save memory. I can make them arrays. Done.
If you're allocating them anyway, you're not really saving anything :-)
BTW, I'm not sure what's in these config pages, but based on the name, I assume it contains settings. So this is something applications may want to access directly. I suspect there the protocol also support changing those settings. Have a look at the hw_ostc_device_eeprom_{read,write}. (This is certainly not a must have feature right now, but it's something you may want to keep in mind already.)
Yes, that is device configuration as well as current calculated tissue loads. I didn't look at the OSTC code so I didn't see any evidence of setting configs on the DCs. I intend to write code to do just that for the Cochran and I wasn't sure where to do that since it looked like libdivecomputer doesn't expose an API for it. There are some settings, like date/time that aren't configurable on the DC. And I would like to be able to set configuration from Linux or even a tablet.
There is indeed no generic api for writing settings, because this is done very different for different models. This is not a high priority, and something we can always look at later, when we have the basic downloading done. But I just mentioned it in case you were planning to implement this too.
You have the same problem with the progress field. Again, I think it makes more sense to make this a local variable, and pass it as a parameter to the internal read function. Note that you're also reporting progress for each individual step, rather than the entire download.
I don't know the size of the entire download when I start needing a progress bar. You'll note I don't have one for cochran_read_id(), read_config() or read_misc(). These are small enough I figured one wasn't needed. I don't know the size of the sample data until I read the logbook. So it seemed the best approach was to have two.
There are a few other backends that have the same problem where the total size isn't known from the start. What I did there, is to initialize the maximum field with the worst-case maximum (and in some cases that's UINT_MAX, see EVENT_PROGRESS_INITIALIZER). As soon as you have a better value available, you update the maximum value. There are backends where we never know the exact amount of data until the download is over. In that case we simply can't do better than assuming the worst case maximum.
The main problem with two independent progress is that the user will see a progressbar that goes from 0% to 100% twice. From the user point of view, that's just very counter intuitive, because you don't know when the download will be finished. And that's exactly the point of the progress bar in the first place.
With the worst case maximum approach, you do get one progress for the entire download. Initially the progress bar will stay near zero, but once the real maximum is known, you go nicely to 100%.
The other backends I looked at read the from the DC in blocks of data and were able to manage the progress within the function. The cochran does very big reads at very high data rates (800Kb/s). So I can't do the progress at the cochran_common_device_read() function because it issues one command to read a huge block of data. So I did it in the cochran_packet() function. Since that is exposed I couldn't add a parameter without breaking the API. I could add a separate packet function that accepts a progress but this seems to be a better approach.
Look for example at the hw_ostc3_transfer() function. The progress structure is passed as a parameter there. A single progress structure for the entire download operation is used there, and the hw_ostc3_transfer() function just updates for one specific part of the download.
You can find a similar situation in the suunto_vyper_device_read_dive() function. This is also a public function, but there is a suunto_vyper_read_dive() helper function that does the actual work and takes the progress parameter. So you don't ever see the progress parameter on the public function.
Those high baudrates are indeed a bit odd. Do they even work? You don't check the return value of the serial_configure() function. So if the underlying driver (or OS) doesn't support it, unexpected results may happen. I implemented the support for non-standard baudrates long time ago, and unfortunately it's not guaranteed to work everywhere. Initially it was needed for the iconhd backend, in the end it turned out that this baudrate was a bug in the mares application. The iconhd uses some microchip with CDC-ACM, where the baudrate doesn't matter at all. So every value works equally well.
They are little twitchy but I get good reads most of the time. I've tried different rates and it does require a non-standard rate. At least I'm not aware of a standard rate that high.
What I meant here, is that because you don't check the return value of the serial_configure() function, you don't know whether setting the custom baudrate actually succeeded. Support for custom baudrates depends heavily on the OS and driver. So if the call fails, you'll be using a different baudrate then the one you think you are using.
For example on linux, setting a custom baudrate requires to set the baudrate to 38400, and then some linux specific call to change the baudrate to the custom baudrate. But if that call fails, then the actual baudrate will be 38400, and not your custom one.
We're getting closer, but the windows build is still broken:
CC cochran_common_parser.lo In file included from ../../source/src/cochran_common_parser.c:33: ../../source/src/cochran_common.h:73: error: expected specifier-qualifier-list before 'time_t' ../../source/src/cochran_common_parser.c: In function 'cochran_common_parser_get_datetime': ../../source/src/cochran_common_parser.c:113: error: 'cochran_data_t' has no member named 'date_format' ../../source/src/cochran_common_parser.c: In function 'cochran_common_parser_get_field': ../../source/src/cochran_common_parser.c:147: warning: control reaches end of non-void function ../../source/src/cochran_common_parser.c: In function 'cochran_common_parser_samples_foreach': ../../source/src/cochran_common_parser.c:166: warning: control reaches end of non-void function ../../source/src/cochran_common_parser.c: In function 'cochran_common_handle_event': ../../source/src/cochran_common_parser.c:175: warning: unused variable 'data' make[1]: *** [cochran_common_parser.lo] Error 1
I think you're missing an #include <time.h> somewhere for the use of time_t in the cochran_data_t structure. But you shouldn't need that because libdivecomputer has its own time functions and structures. Use those instead.
I think I've fixed that.
It still doesn't compile for me. However, the only place where you use something from time.h, is to set the current_dive_start_time field which isn't used anywhere else. Just remove this, and everything starts to work.
I also need to link with -lm on my system. The symbols file also needs a small update after the renaming. See the attached patches.
The clang static analyzer also finds some potential issues. See the attached logfile.
Jef
On Sunday, November 16, 2014, Jef Driesen jef@libdivecomputer.org wrote:
John,
There is a problem with how you pass the data to the dive callback
function. I didn't notice this the first time, but you pass a pointer to the cochran_data_t structure to the callback function. That's not how this is supposed to work. You need to extract the raw binary data of each dive, and then pass that to the callback function. What you do now is pass the data of all dives, with a couple of fields pointing to the dive of interest. That's wrong and needs to change.
The cochran_data_t alone is already a problem here, because it contains
pointers. The dive data is supposed to be a simple byte array which can be serialized as-is. Remember, libdivecomputer has absolutely no control over how or when the application uses the data. For example the application can copy the dives, and only parse them after the download has finished. In that case the device handle will already have been closed when parsing the data. Thus anything in your structure pointing to data in that handle will have been freed, and unexpected things will happen. With a self contained blob of binary data that's not a problem. You can copy it, write it to disk, etc with no problems.
I'm a little confused by what is expected. I understand that an application may choose to save the dive data and not parse so I can fix that. The only way that makes sense to me is to create a blob in malloc'ed space and hope the application frees it. That doesn't really seem to make sense to me. The blob will have to contain a size, offset into the data (there is pre-dive data) and some information about the type of data it is so that the common parser code can deal with it properly. The app won't necessarily know the structure of the blob or its length so what it can do with it will be limited.
Subsurface hasn't complained about freeing memory so there's one application that would leak memory.
See some more comments inline.
On 03-11-14 02:58, John Van Ostrand wrote:
On Sun, Nov 2, 2014 at 1:13 PM, Jef Driesen jef@libdivecomputer.org
wrote:
Nice work. This is already a huge step in the right direction, but we're not there yet.
There is a problem in the way you store the cochran_data_t structure in the device handle. Most of this info only used by the dump/foreach
function
(or one of its helpers), so it doesn't really belong inside the handle.
The structure members under "// Config items" are used to allow for different Cochran models and different feature configurations for a given model. The fields are set during cochran_set_device_config() so that I
know
how to handle reading the logbook and the sample data. So while I'm detecting the model I thought I might as well set the variables needed in foreach so I have one section of code to look at when adding a new model.
Some of the variables I need to keep between calls to foreach and this seemed to be a good place.
I know I'm keeping id*, config* and misc* around after they are needed. I could drop them sooner but they come in handy for the dump function.
It's fine to store certain data in the device handle if it's needed for
the device connection state. For example the id packets are read during the open, and you need them later in the other functions. That's a perfect argument to store them in the device handle.
But nearly everything else is state for either the dump or foreach
function. After those functions returns there is no need to keep any of the data around. If those functions get called a second time, they should start with a completely fresh state. Thus the "global" device handle is the wrong place to store this data. The fact that there are memory leaks when someone calls those functions twice is a symptom of that. If the state were private to the function, then you wouldn't have to worry about left overs from a previous call.
I see I can keep state in variable in the foreach function.
This results in a number of problems. For example you allocate several pieces of data (id, config, etc) in those functions and store them in
the
handle. But if someone calls one of those functions a second time, you allocate them again and overwrite the original pointers. So you'll end
up
with a memory leak. You can free the old data first, but I think you're better off using local data structures because there is no reason why
any
of this data needs to persist between the high-level calls.
I see, calling _open and _foreach a second time could leak memory.
Fixed.
It's better now, but freeing the pointers is just fixing the symptoms,
and not the underlying problem (see above).
Overall, I find the logic rather hard to follow because it's spread over all your cochran_read_* functions. These functions do not only download some data, but also sets some fields, which are then used in one of the other functions. I think it would be easier if the processing was a bit more separated from the downloading. I have the feeling that there's something wrong, but I can't really tell what or where. Maybe it's just that I don't understand the format very well. Do you have some data
files
available?
There are two reasons I split the read up into several functions. The cochran_read_id() function reads the blocks needed to detect the DC model so it knows how to perform the other reads. I do this in the cochran_common_device_open() because I wanted to give immediate feedback
to
the application if the DC wasn't supported.
That's fine. I didn't had any problem with that.
The other reads could be in one function but it would be a large
function.
There is some handle data settings in the read functions.
cochran_read_id(): determine model type and features cochran_read_config(): determine the head pointer to the sample buffer, I see now that I don't use this. cochran_read_misc(): read only to mimic Analyst cochran_read_logbook(): Get number of logged dives(from config1),
calculate
logbook_size cochran_read_parms(): Doesn't read anything, calculate parameters needed for reading samples. cochran_read_samples(): Reads sample data.
There really isn't much happening inside the read calls that isn't needed there. The read_config() has a few lines that can actually be removed.
The
read_logbook has another few lines that calculate the size, and read_samples has two lines that calculate size. Since all the read_*() functions are called by read_all() it's trivial to move them there.
Done. Calculations are moved to the read_all function.
I always try to keep the code for downloading separate from the
parsing/processing. That usually makes it easier to follow the logic. So I think your latest version is a move in the right direction.
Note that several of those data pieces have a fixed size, so why even allocate them? Just use a byte array with the right size. Some like:
struct { ... unsigned char id0[67]; unsigned char id1[67]; unsigned char config1[512]; unsigned char config2[512]; ... };
Now you no longer have to worry about freeing them. Instead of config1, config2, etc you could even use an array here. That will make your code even more generic. Instead of repeating the same code, you can use a
loop.
I used mallocs to save memory. I can make them arrays. Done.
If you're allocating them anyway, you're not really saving anything :-)
BTW, I'm not sure what's in these config pages, but based on the name, I assume it contains settings. So this is something applications may want
to
access directly. I suspect there the protocol also support changing
those
settings. Have a look at the hw_ostc_device_eeprom_{read,write}. (This
is
certainly not a must have feature right now, but it's something you may want to keep in mind already.)
Yes, that is device configuration as well as current calculated tissue loads. I didn't look at the OSTC code so I didn't see any evidence of setting configs on the DCs. I intend to write code to do just that for
the
Cochran and I wasn't sure where to do that since it looked like libdivecomputer doesn't expose an API for it. There are some settings,
like
date/time that aren't configurable on the DC. And I would like to be able to set configuration from Linux or even a tablet.
There is indeed no generic api for writing settings, because this is done
very different for different models. This is not a high priority, and something we can always look at later, when we have the basic downloading done. But I just mentioned it in case you were planning to implement this too.
You have the same problem with the progress field. Again, I think it
makes
more sense to make this a local variable, and pass it as a parameter to
the
internal read function. Note that you're also reporting progress for
each
individual step, rather than the entire download.
I don't know the size of the entire download when I start needing a progress bar. You'll note I don't have one for cochran_read_id(), read_config() or read_misc(). These are small enough I figured one wasn't needed. I don't know the size of the sample data until I read the
logbook.
So it seemed the best approach was to have two.
There are a few other backends that have the same problem where the total
size isn't known from the start. What I did there, is to initialize the maximum field with the worst-case maximum (and in some cases that's UINT_MAX, see EVENT_PROGRESS_INITIALIZER). As soon as you have a better value available, you update the maximum value. There are backends where we never know the exact amount of data until the download is over. In that case we simply can't do better than assuming the worst case maximum.
The main problem with two independent progress is that the user will see
a progressbar that goes from 0% to 100% twice. From the user point of view, that's just very counter intuitive, because you don't know when the download will be finished. And that's exactly the point of the progress bar in the first place.
With the worst case maximum approach, you do get one progress for the
entire download. Initially the progress bar will stay near zero, but once the real maximum is known, you go nicely to 100%.
The other backends I looked at read the from the DC in blocks of data and were able to manage the progress within the function. The cochran does
very
big reads at very high data rates (800Kb/s). So I can't do the progress
at
the cochran_common_device_read() function because it issues one command
to
read a huge block of data. So I did it in the cochran_packet() function. Since that is exposed I couldn't add a parameter without breaking the
API.
I could add a separate packet function that accepts a progress but this seems to be a better approach.
Look for example at the hw_ostc3_transfer() function. The progress
structure is passed as a parameter there. A single progress structure for the entire download operation is used there, and the hw_ostc3_transfer() function just updates for one specific part of the download.
You can find a similar situation in the suunto_vyper_device_read_dive()
function. This is also a public function, but there is a suunto_vyper_read_dive() helper function that does the actual work and takes the progress parameter. So you don't ever see the progress parameter on the public function.
Those high baudrates are indeed a bit odd. Do they even work? You don't check the return value of the serial_configure() function. So if the underlying driver (or OS) doesn't support it, unexpected results may happen. I implemented the support for non-standard baudrates long time
ago,
and unfortunately it's not guaranteed to work everywhere. Initially it
was
needed for the iconhd backend, in the end it turned out that this
baudrate
was a bug in the mares application. The iconhd uses some microchip with CDC-ACM, where the baudrate doesn't matter at all. So every value works equally well.
They are little twitchy but I get good reads most of the time. I've tried different rates and it does require a non-standard rate. At least I'm not aware of a standard rate that high.
What I meant here, is that because you don't check the return value of
the serial_configure() function, you don't know whether setting the custom baudrate actually succeeded. Support for custom baudrates depends heavily on the OS and driver. So if the call fails, you'll be using a different baudrate then the one you think you are using.
For example on linux, setting a custom baudrate requires to set the
baudrate to 38400, and then some linux specific call to change the baudrate to the custom baudrate. But if that call fails, then the actual baudrate will be 38400, and not your custom one.
I can check. In the case of a failure the download would timeout. Checking the return value would save the user a few seconds.
We're getting closer, but the windows build is still broken:
CC cochran_common_parser.lo In file included from ../../source/src/cochran_common_parser.c:33: ../../source/src/cochran_common.h:73: error: expected specifier-qualifier-list before 'time_t' ../../source/src/cochran_common_parser.c: In function 'cochran_common_parser_get_datetime': ../../source/src/cochran_common_parser.c:113: error: 'cochran_data_t'
has
no member named 'date_format' ../../source/src/cochran_common_parser.c: In function 'cochran_common_parser_get_field': ../../source/src/cochran_common_parser.c:147: warning: control reaches end of non-void function ../../source/src/cochran_common_parser.c: In function 'cochran_common_parser_samples_foreach': ../../source/src/cochran_common_parser.c:166: warning: control reaches end of non-void function ../../source/src/cochran_common_parser.c: In function 'cochran_common_handle_event': ../../source/src/cochran_common_parser.c:175: warning: unused variable 'data' make[1]: *** [cochran_common_parser.lo] Error 1
I think you're missing an #include <time.h> somewhere for the use of time_t in the cochran_data_t structure. But you shouldn't need that
because
libdivecomputer has its own time functions and structures. Use those instead.
I think I've fixed that.
It still doesn't compile for me. However, the only place where you use
something from time.h, is to set the current_dive_start_time field which isn't used anywhere else. Just remove this, and everything starts to work.
I also need to link with -lm on my system. The symbols file also needs a
small update after the renaming. See the attached patches.
The clang static analyzer also finds some potential issues. See the
attached logfile.
Jef
On 2014-11-17 01:17, John Van Ostrand wrote:
On Sunday, November 16, 2014, Jef Driesen jef@libdivecomputer.org wrote:
There is a problem with how you pass the data to the dive callback function. I didn't notice this the first time, but you pass a pointer to the cochran_data_t structure to the callback function. That's not how this is supposed to work. You need to extract the raw binary data of each dive, and then pass that to the callback function. What you do now is pass the data of all dives, with a couple of fields pointing to the dive of interest. That's wrong and needs to change.
The cochran_data_t alone is already a problem here, because it contains pointers. The dive data is supposed to be a simple byte array which can be serialized as-is. Remember, libdivecomputer has absolutely no control over how or when the application uses the data. For example the application can copy the dives, and only parse them after the download has finished. In that case the device handle will already have been closed when parsing the data. Thus anything in your structure pointing to data in that handle will have been freed, and unexpected things will happen. With a self contained blob of binary data that's not a problem. You can copy it, write it to disk, etc with no problems.
I'm a little confused by what is expected. I understand that an application may choose to save the dive data and not parse so I can fix that. The only way that makes sense to me is to create a blob in malloc'ed space and hope the application frees it. That doesn't really seem to make sense to me. The blob will have to contain a size, offset into the data (there is pre-dive data) and some information about the type of data it is so that the common parser code can deal with it properly. The app won't necessarily know the structure of the blob or its length so what it can do with it will be limited.
Subsurface hasn't complained about freeing memory so there's one application that would leak memory.
I'll explain the principle with some trivial examples. A typical implementation of the foreach function will look like this:
dc_status_t xxx_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) { unsigned char dive[SIZE_DIVE]; unsigned char fingerprint[SIZE_FP];
for (i = 0; i < ndives; ++i) { /* Download the i'th dive here and pass it to the callback function .*/ callback (dive, sizeof(dive), fingerprint, sizeof(fingerprint), userdata)) } }
Of course this is overly simplified. In practice, dives are not fixed length, and the dive buffer will be dynamically allocated, but that doesn't matter here. What does matter, is that the memory buffer is clearly owned by the foreach function. As long as you're inside the callback function, the application can freely access the data. But once the callback function return, you can no longer assume the data is still accessible. The implementation of the foreach function may re-use the same buffer for the other dives, or even free the buffer.
So if you want to use the data after the callback function has returned, then you HAVE to copy the data. For example an application might not parse the dives immediately, but store them in a list, and only parse them after the download has finished:
static int dive_cb (const unsigned char *data, unsigned int size, const unsigned char *fingerprint, unsigned int fsize, void *userdata) { list *dives = userdata;
/* Copy the dive data into a new buffer. */ dc_buffer_t *buffer = NULL; dc_buffer_new(&buffer, size); dc_buffer_append(buffer, data, size);
/* Append the dive to the list. */ dives.add(buffer); }
int main (int argc, char *argv[]) { list *dives = ...;
/* Download the dives. */ dc_device_t *device = NULL; dc_device_open (&device, ...); dc_device_foreach(device, dive_cb, dives); dc_device_close(device);
/* Parse all dives. */ for (i = 0; i < dives.count; ++i){ /* Do something with the i'th dive here. */ } }
With a simple byte array, this will work just fine, because copying a byte array is well defined. But for structures containing pointers, that's not the case. If you copy the structure, the pointers will still point to their original data. But that data may no longer exist.
The important message here is that the data is owned by the foreach function. The application should certainly not try to free it. If the data is needed after the callback function returns, then the application needs to copy it.
The reason why you don't notice any problems with subsurface, is because it parses the dive inside the callback function.
Regarding your question about the data format of the blob. The application doesn't really need to know anything about the format. The parser will take care of that, by translating the raw data into something more practical.
Based on your documentation in the libdivecomputer wiki (which is excellent btw! You did a really nice job there!), each dive has a fixed size (256 bytes) header containing the logbook summary, and a variable length part with the sample data. That's exactly the same structure as used in the Oceanics. There, I simply concatenate those two parts, and use that as the raw data format for the dives. I suggest you do the same. The header is fixed size, so you don't even need to include a length.
The fact that the logbook header and profile data are stored in separate memory areas is just an implementation detail. The dive computer could equally well store it as one single blob. I assume that the reason behind using two different memory areas is the following. The length of the dive profiles is variable, and depends on many factors, like the sample rate, dive duration, etc. Thus the amount of profiles that can be stored isn't fixed. By storing the logbook summary separately, the dive computer can always display basic information for a fixed pre-defined number of dives. Even if the profile data for those dives has already been overwritten. (If you download such a dive where the profile data is no longer available, you just pass only the logbook data to the callback function.)
If the data format is different for the different models supported by the backend, and there is no identification data stored in the dive data itself, then you proceed as follows. First of all, extend the cochran_commander_parser_create() function with an extra parameter to pass the model number. Store the model number internally into the parser structure, and then you can use it when needed, just like you do now. Next, you need some way to pass the model number from the device to the parser. That is done as follows. In your xxx_device_foreach implementation, you emit a DC_EVENT_DEVINFO event containing the model number (and serial number if available). This info gets cached internally (see the device_event_emit() function), and in the dc_parser_new() function you can pass the model number to your cochran_commander_parser_create() function.
It looks like you have only two variants (e.g. commander and emc). If there is no model number in the data, you just assign artificial model numbers (e.g. commander=0 and emc=1). For completeness, you use the same numbers in the table in the descriptor.c file, like this:
{"Cochran", "Commander", DC_FAMILY_COCHRAN_COMMANDER, 0}, {"Cochran", "EMC-16", DC_FAMILY_COCHRAN_COMMANDER, 1}, {"Cochran", "EMC-20H", DC_FAMILY_COCHRAN_COMMANDER, 1},
Note that it's not a problem to list multiple models with the same model number, as long as the EMC-16 and EMC-20H share the same data format. If you want to be able to tell apart the individual models, then you'll need to assign different numbers to each of them. You can use the upper 16 bits for the main model, and the the lower 16bits for the individual models, as you already did with the COCHRAN_MODEL_XXX constants. As long as the same numbers are used everywhere, you can use whatever number work best. They are just artificial numbers :-) (For model numbers that are embedded in the data, that's of course another story.)
If you have further questions, just ask! I'll try to help where I can.
Jef
On Sun, Oct 26, 2014 at 3:08 AM, Jef Driesen jef@libdivecomputer.org wrote:
On 22-10-14 17:33, John Van Ostrand wrote:
On Wed, Oct 22, 2014 at 3:23 AM, Jef Driesen jef@libdivecomputer.org wrote:
On 2014-10-07 18:09, John Van Ostrand wrote:
Now, there are a couple of issues that needs to be addressed before this can be included in libdivecomputer:
You used a few Linux specific calls such as nanosleep() in your code. This obviously breaks the windows builds. The rule of thumb is that the dive computer backends should contain no platform specific code at all. All platform specific code should be moved to the platform specific modules. For example we already have a serial_sleep() function.
Those I should be able to change to serial_sleep.
Yeah, that's pretty straightforward.
A related issue is that you used packed structures, which are a gcc
extension. Although, I personally use mingw (gcc) for my Windows builds, the msvc compiler is supported too. Casting the raw data to a structure for easier parsing is non-portable anyway (e.g. little vs big endian). Therefore, in libdivecomputer we always de-serialize the data in a portable way using the array_uint{16,24,32}_{le,be} functions. During development the structures are indeed very convenient (I occasionally do that as well), but for the final version they should be replaced with something more portable.
Those packed structures are the dive logs and I realize they have a lot more information than libdivecomputer needs but I wanted to save that research because it's been useful in debugging and decoding other model's logs and it may become useful in the future. You'll see that they are all char or char arrays that I either de-serialize in-line (sometimes for good reason) or use the array_uint* functions. I'd prefer to keep a structure in place rather than directly access select bytes. Would a union between a char array and the structure be a portable way of packing it? Would that be an option?
A union doesn't change the fact that casting a byte array to a structure is not strictly portable (alignment, endianness, etc). In practice it will most likely just work because you only used unsigned char's, but since we can't guarantee that, I prefer not to rely on that. The _attribute__((packed)) is a gcc extension, which is not supported by the msvc compiler, so you can't use that.
For anything longer than an 8 bit value, you'll need the array_uint helper functions anyway, so the only benefit of using the structure is that you don't need to calculate the byte offset manually.
My patch does use array_uint. There are a few cases where I do it manually because two bytes of a uint16 are separated by bytes. I figured that a union might have been a portable way to pack the structure without using the _atribute__((packed)).
I've made changes to remove the structure. It's in the new patch.
Your dc_device_dump implementation uses a custom cochran_data_dump
structure instead of the dc_buffer_t structure. You either need to get rid of this custom structure, or not implement this function. Right now if anyone calls this function with dc_buffer_t structure, bad things will happen. I guess this is also the reason why you added your own example application instead of the generic example application. If really necessary, you can implement and expose backend specific functions to support features outside the generic api.
I should be able to refactor that function to use dc_buffer properly. You mentioned that the dump function was more for debugging or bypassing libdivecomputer's foreach functions so I was liberal with it. I still want to export all the data so how about I use dc_buffer directly, put pointers to the data sections in first and stack the data in after, one big blob. Maybe use a union to a structure to easily decode it.
I wrote the cochran_download program because I wanted a way for a user to be able to download their data and send it to me easily. I assume there will be lots more work needed to support various DCs. The data produced can be used with a simulator I wrote.h
The dump function is indeed mainly for debugging purposes. If your implementation follows the expected pattern (e.g. returning a proper dc_buffer_t) you wouldn't need a custom application.
If you pack all the pieces in one blob, that's fine. Based on your code most pieces have a fixed size, so you can just append them. For the variable length ones, you'll need a small header with at least the size. Just make sure you serialize the data in a portable way (for example 32bit big endian integers for those lengths).
I've done something like that. The buffer contains a pointer array in the first 50 bytes to the various data sections that follow. It's in the new patch.
You implemented two backends. One for the EMC and one for the Commander.
But the communication protocol is roughly identical, and you already handle the differences in the common code. That means you only need a single backend, which supports multiple models. This is very common in other backends too. Just name your backend after one of them (for example DC_FAMILY_COCHRAN_EMC).
I thought that being very specific about model was important in not leading users to believe their computer was supported. There are significant differences between models and possibly within a model that has different features enabled that would cause the communication or decoding to fail. I have access for four different models with 3 purchased recently which means I don't have any duplicates to compare, duplicate models with different features enable to compare, and I can't tell if things change within a model over time.
A little background may help in this point and the next. I chatted with Mike Cochran who refused his company's help in decoding because he was concerned that it would leak hints about the decompression algorithm. He also warned that others have bricked their DC trying to communicate with it and even admitted their programmers have bricked DCs while developing their Windows Analyst dive log software. I chose to take that quite seriously.
The code is extremely conservative in identifying DCs. Right now it uses a 6 digit model string (e.g. AM2315) which I suspect not only varies between models but varies within a model based on what features are enabled (e.g. more memory, additional gases) and possible based on which microcontroller is used (perhaps it changes over the life of a model.)
For example the following seem to be specific between models and depending on which features are enabled. Data format (ie log, samples) Baud rate Start and End memory addresses (needed when the log "wraps" around.
I've determined that byte 3 and 4 of that model string indicate the data format but I have no way of determining the baud rate or memory range.
I'm also working on a third model except all I have is data.
Does this change your view on this?
I certainly understand your concern about being careful. That's something I have been doing for the past years too. As far as I know libdivecomputer has never bricked any dive computer, and I certainly would like to keep it that way!
But that's not a reason not to use a single backend. One backend basically means one communication protocol. And that doesn't seems to be the case here. You have several different models that share the same communication protocol. As you explained above, you can identify the actual model from the data, so I see no valid reason to split this into different backends. If you are concerned that your code won't work correctly on models you haven't tested yet, then it's perfectly acceptable, to fail with DC_STATUS_UNSUPPORTED once you detect a model you don't know how to handle.
[That's pretty much the same situation as with the oceanics. If we encounter an unknown model, we fallback to the default model. In most cases that's just wrong, and downloading or parsing will fail. Libdivecomputer needs to be updated to learn the correct settings for each new model. The only difference here is that we don't fail but fallback to a default, because it doesn't harm the device, and usually allows us to get at least some data of the device.]
Also notice how the dc_device_open() function receives the device descriptor, which contains the model number. So if necessary, you know the requested model before starting the communication. Most backends ignore this model number, because they detect the model from the data. This autodetection is what makes it possible to select any model from the same family, and downloading will just work. If possible, I prefer doing autodetection, but if that's not possible or too risky, you can assume the model selected by the user. (If the user selects the wrong model, there is not much we can do about that.)
I've collapsed the two into one backend while keeping the two devices in the list.
During the communication you close and re-open the serial port. Are you
sure this is really necessary? What kind of interface does the Cochran use? A usb-serial chip (prolific, ftdi, etc)? Maybe you just need the right trick like toggling some serial lines to restart the communication? The reason why I'm asking is that in future versions, opening the serial port will likely be moved to the application side, and in that case re-opening the port will no longer be possible.
You may recall me posting about communication in the past. The cable uses an end-of-life FTDI chip to communicate with the three contacts on the DC (3-wire serial?) The cochran DCs seem tricky. They initially take commands at 9600 baud and they deliver results for small data transfers at the same rate, but for logbook and sample data they operate at what I suspect is their microcontroller's full speed, so I have unusual baud rates like 825,000 baud.
Their Analyst software always downloads the log data as a whole, something I suspect isn't necessary and sample data can be quite big (3 bytes every second) but I decided, based on Mr. Cochran's warning, that I should mimic their windows software closely. That means I open and close the connection.
I could revisit that code and see if I can remove the close/open to see if it works but I'd still need to do the baud change and there is a need for several flushes to wake the DC up. The DC also logs computer connections and if those logs looked unusual users might find Cochran contesting warranty claims. I don't know that close/open is logged, I was being careful.
I might be wrong on this, but I doubt the device can detect when an application opens the serial port. With true serial ports (and no handshaking) you can easily send data out, without anything connected to the port. And with usb-serial there is already a lot of communication going on before an application can even open the virtual serial port (usb device enumeration for example). Of course, once you're sending data, or changing serial lines the device will detect that.
I was partly going on the description from the Cochran manual:
"Inter-Dive Events: Number of Initializations, Unit Activation, Altitude Changes of 500 Feet, Temperature Changes of 10 degrees, Low Batteries, Sensor Malfunction, Analyst® interface with Dive Computer."
I did some testing and it seems that data downloads aren't logged. I've seen logs after using Analyst but it may be when configuration changes are made to the DC.
I also did some testing and was able to verify that a close/open wasn't needed between big data downloads. I still need to do some serial re-configuration though. I've removed the extraneous closes.
In theory it makes absolutely no sense having to flush multiple times, or setting the same settings more than once. Most likely, the device just needs more time to finish whatever it was doing. So waiting a few (or more) milliseconds will work equally well. I have seen that open/close sequence before in other applications, and as I suspected it turned out to be unnecessary. I just needed the right amount of waiting time combined with the correct initialization sequence. I'm not saying that's the case here, but I suspect it is.
I would have thought the same thing. I've worked with serial configuration (and only a little serial programming) for two decades so I understand the protocol from a practical perspective. I can't explain why flushes are needed but they are, perhaps it has something to do with firmware on the FTDI side. it's not merely make-work wait time. The DC, when woken up after prompted by several flushes, starts issuing what I call a heartbeat byte. The DC only recognizes commands after it emits one of these bytes. So my first attempt (as well as others) was to simply wait for this byte, and it never came. I've been able to wake up the DC by sending a byte but I'm wary to rely on that since it might become part of the next command if the DC just happens to be in the mood. The Analyst software uses flushes to wake up the DC.
Note that opening/closing a serial port has a race condition. Between your
open and close, some other application might open the port. Very unlikely in practice, but not impossible.
Will your plans to move the open() to the application side allow for baud
rate changes that are needed by the Cochran?
Only opening the serial port will move to the application side. Everything else, like setting the correct baudrate, will remain the responsibility of libdivecomputer. So switching baudrates won't be a problem.
Jef
The patch was sent to the list prior to this email. Let me know what you think.
On 07-10-14 18:09, John Van Ostrand wrote:
Specifically tested with EMC-20H and Commander Air_Nitrox this commit should support the families for the most part however there are may be differences in the DC signature, memory size, and other factors that will prevent opening the device and obtaining a clean import. Try it with other EMC and Commander devices and check library ERRORs for the signature information needed to add support.
Also missing is tank pressure (for Gemini) and support for logbooks that have rolled old dives off. There is support for dive samples that have rolled off.
You forgot to attach the patches!
Jef