]> git.tdb.fi Git - ext/subsurface.git/commitdiff
cochran: add support for importing the exported CSV files
authorLinus Torvalds <torvalds@linux-foundation.org>
Wed, 20 Jun 2012 03:07:42 +0000 (20:07 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 20 Jun 2012 03:07:42 +0000 (20:07 -0700)
The Cochran Analyst software can export the basic dive information as
CSV files (comma-separated values).

Individual CSV files contain just one particular type of information:
depth, temperature or cylinder pressure, which is rather inconvenient.
However, the way subsurface works, you can just import these CSV files
all as individual dives, and then subsurface will automatically merge
the dives with the same date and time - and in the process it will also
merge all the samples.

So it turns out that we don't really need any special handling.  You can
literally just do

     subsurface <list-your-cochran-export-files-here>

and you're all done.

Of course, the CSV files really *are* pretty useless, since they don't
contain all the nice information about where the dive took place etc.
So you literally just get the dive profile.  But that's better than
getting nothing at all.

I'd love to actually be able to parse the real native Cochran Analyst
software CAN files, but in the meantime this is at least a starting
point.  And if I'm ever able to parse those nasty CAN-files, this makes
comparisons with the exports much easier.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
file.c

diff --git a/file.c b/file.c
index aff1d51f2d2c87b470f2b61b23d6c8b945870f73..3f06259faa1cd7087ef926e1cdd8a60b4d1bf9c1 100644 (file)
--- a/file.c
+++ b/file.c
@@ -94,6 +94,125 @@ static int try_to_open_suunto(const char *filename, struct memblock *mem, GError
        return success;
 }
 
+static time_t parse_date(const char *date)
+{
+       int hour, min, sec;
+       struct tm tm;
+       char *p;
+
+       memset(&tm, 0, sizeof(tm));
+       tm.tm_mday = strtol(date, &p, 10);
+       if (tm.tm_mday < 1 || tm.tm_mday > 31)
+               return 0;
+       for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) {
+               if (!memcmp(p, monthname(tm.tm_mon), 3))
+                       break;
+       }
+       if (tm.tm_mon > 11)
+               return 0;
+       date = p+3;
+       tm.tm_year = strtol(date, &p, 10);
+       if (date == p)
+               return 0;
+       if (tm.tm_year < 70)
+               tm.tm_year += 2000;
+       if (tm.tm_year < 100)
+               tm.tm_year += 1900;
+       if (sscanf(p, "%d:%d:%d", &hour, &min, &sec) != 3)
+               return 0;
+       tm.tm_hour = hour;
+       tm.tm_min = min;
+       tm.tm_sec = sec;
+       return utc_mktime(&tm);
+}
+
+enum csv_format {
+       CSV_DEPTH, CSV_TEMP, CSV_PRESSURE
+};
+
+static void add_sample_data(struct sample *sample, enum csv_format type, double val)
+{
+       switch (type) {
+       case CSV_DEPTH:
+               sample->depth.mm = feet_to_mm(val);
+               break;
+       case CSV_TEMP:
+               sample->temperature.mkelvin = F_to_mkelvin(val);
+               break;
+       case CSV_PRESSURE:
+               sample->cylinderpressure.mbar = psi_to_mbar(val);
+               break;
+       }
+}
+
+/*
+ * Cochran comma-separated values: depth in feet, temperature in F, pressure in psi.
+ *
+ * They start with eight comma-separated fields like:
+ *
+ *   filename: {C:\Analyst4\can\T036785.can},{C:\Analyst4\can\K031892.can}
+ *   divenr: %d
+ *   datetime: {03Sep11 16:37:22},{15Dec11 18:27:02}
+ *   ??: 1
+ *   serialnr??: {CCI134},{CCI207}
+ *   computer??: {GeminiII},{CommanderIII}
+ *   computer??: {GeminiII},{CommanderIII}
+ *   ??: 1
+ *
+ * Followed by the data values (all comma-separated, all one long line).
+ */
+static int try_to_open_csv(const char *filename, struct memblock *mem, enum csv_format type)
+{
+       char *p = mem->buffer;
+       char *header[8];
+       int i, time;
+       time_t date;
+       struct dive *dive;
+
+       for (i = 0; i < 8; i++) {
+               header[i] = p;
+               p = strchr(p, ',');
+               if (!p)
+                       return 0;
+               p++;
+       }
+
+       date = parse_date(header[2]);
+       if (!date)
+               return 0;
+
+       dive = alloc_dive();
+       dive->when = date;
+       dive->number = atoi(header[1]);
+
+       time = 0;
+       for (;;) {
+               char *end;
+               double val;
+               struct sample *sample;
+
+               errno = 0;
+               val = strtod(p,&end);
+               if (end == p)
+                       break;
+               if (errno)
+                       break;
+
+               sample = prepare_sample(&dive);
+               sample->time.seconds = time;
+               add_sample_data(sample, type, val);
+               finish_sample(dive);
+
+               time++;
+               dive->duration.seconds = time;
+               if (*end != ',')
+                       break;
+               p = end+1;
+       }
+       record_dive(dive);
+       return 1;
+}
+
 static int open_by_filename(const char *filename, const char *fmt, struct memblock *mem, GError **error)
 {
        /* Suunto Dive Manager files: SDE */
@@ -104,6 +223,14 @@ static int open_by_filename(const char *filename, const char *fmt, struct memblo
        if (!strcasecmp(fmt, "CAN"))
                return try_to_open_cochran(filename, mem, error);
 
+       /* Cochran export comma-separated-value files */
+       if (!strcasecmp(fmt, "DPT"))
+               return try_to_open_csv(filename, mem, CSV_DEPTH);
+       if (!strcasecmp(fmt, "TMP"))
+               return try_to_open_csv(filename, mem, CSV_TEMP);
+       if (!strcasecmp(fmt, "HP1"))
+               return try_to_open_csv(filename, mem, CSV_PRESSURE);
+
        return 0;
 }