Here is a patch to import Poseidon MK6 logs to Subsurface. If you need a
log file for testing, there is one available on a post from Willem with
subject: "Import of Poseidon CCR dive logs". This requires both the .txt
and .csv files and they need to be under same directory. When importing,
you must select the MkVI filetype from the file selector (and choose the
MK6.txt).

The patch exposes a bug in setpoint handling, but I do not know what is the
intended behavior, so fixing will require input from the more knowledgeable
people. The setpoint value is "compressed" when dive samples are added,
meaning that repeating values are set to zero. However, upon saving the XML
log file, this means that we are writing the real setpoint value and on
next sample we write setpoint value of 0. Should the real setpoint value be
stored on samples, or should the XML saving ignore setpoint values of 0?

There also seems to be quite a few values related to rebreather that are
not actually stored on the XML output. I suppose that just shows that CCR
support is in progress.

miika
From 33e7b423fb176dae0c2e29669bea124301ad6877 Mon Sep 17 00:00:00 2001
From: Miika Turkia <[email protected]>
Date: Wed, 28 May 2014 09:55:46 +0300
Subject: [PATCH] Support for importing Poseidon MK6 logs

This patch adds support for importing the logs from Poseidon MK6
rebreather. This DC produces logs that contain of a .txt file that has
all the meta data and a .csv file that contains the sample readings. The
CSV file is different from the others in that it has a line per each
sample reading at given time. Thus we have to merge all the lines from
one point in time into one sample reading of ours.

Signed-off-by: Miika Turkia <[email protected]>
---
 dive.h               |   1 +
 file.c               | 206 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 parse-xml.c          |   8 ++
 qt-ui/mainwindow.cpp |  26 ++++++-
 qt-ui/mainwindow.h   |   1 +
 5 files changed, 239 insertions(+), 3 deletions(-)

diff --git a/dive.h b/dive.h
index 979f429..ef5ff0f 100644
--- a/dive.h
+++ b/dive.h
@@ -603,6 +603,7 @@ extern int parse_shearwater_buffer(sqlite3 *handle, const char *url, const char
 
 extern int parse_file(const char *filename);
 extern int parse_csv_file(const char *filename, int time, int depth, int temp, int po2f, int cnsf, int ndlf, int ttsf, int stopdepthf, int pressuref, int sepidx, const char *csvtemplate, int units);
+extern int parse_txt_file(const char *filename, const char *csv);
 extern int parse_manual_file(const char *filename, int separator_index, int units, int number, int date, int time, int duration, int location, int gps, int maxdepth, int meandepth, int buddy, int notes, int weight, int tags);
 
 extern int save_dives(const char *filename);
diff --git a/file.c b/file.c
index b1e258b..1fc1b73 100644
--- a/file.c
+++ b/file.c
@@ -226,7 +226,14 @@ timestamp_t parse_date(const char *date)
 enum csv_format {
 	CSV_DEPTH,
 	CSV_TEMP,
-	CSV_PRESSURE
+	CSV_PRESSURE,
+	POSEIDON_DEPTH,
+	POSEIDON_TEMP,
+	POSEIDON_SETPOINT,
+	POSEIDON_SENSOR1,
+	POSEIDON_SENSOR2,
+	POSEIDON_PRESSURE,
+	POSEIDON_DILUENT
 };
 
 static void add_sample_data(struct sample *sample, enum csv_format type, double val)
@@ -241,6 +248,27 @@ static void add_sample_data(struct sample *sample, enum csv_format type, double
 	case CSV_PRESSURE:
 		sample->cylinderpressure.mbar = psi_to_mbar(val * 4);
 		break;
+	case POSEIDON_DEPTH:
+		sample->depth.mm = val * 0.5 *1000;
+		break;
+	case POSEIDON_TEMP:
+		sample->temperature.mkelvin = C_to_mkelvin(val * 0.2);
+		break;
+	case POSEIDON_SETPOINT:
+		sample->setpoint.mbar = val * 10;
+		break;
+	case POSEIDON_SENSOR1:
+		sample->o2sensor[0].mbar = val * 10;
+		break;
+	case POSEIDON_SENSOR2:
+		sample->o2sensor[1].mbar = val * 10;
+		break;
+	case POSEIDON_PRESSURE:
+		sample->cylinderpressure.mbar = val * 1000;
+		break;
+	case POSEIDON_DILUENT:
+		sample->diluentpressure.mbar = val * 1000;
+		break;
 	}
 }
 
@@ -385,6 +413,182 @@ int parse_file(const char *filename)
 	return 0;
 }
 
+#define MATCH(buffer, pattern) \
+	memcmp(buffer, pattern, strlen(pattern))
+
+char *parse_mkvi_value(const char *haystack, const char *needle)
+{
+	char *lineptr, *valueptr, *endptr, *ret = NULL;
+
+	if (lineptr = strstr(haystack, needle)) {
+		if (valueptr = strstr(lineptr, ": ")) {
+			valueptr += 2;
+		}
+		if (endptr = strstr(lineptr, "\n")) {
+			*endptr = 0;
+			ret = strdup(valueptr);
+			*endptr = '\n';
+
+		}
+	}
+	return ret;
+}
+
+static int cur_cylinder_index;
+int parse_txt_file(const char *filename, const char *csv)
+{
+	struct memblock memtxt, memcsv;
+
+	if (readfile(filename, &memtxt) < 0) {
+		return report_error(translate("gettextFromC", "Failed to read '%s'"), filename);
+	}
+
+	/*
+	 * MkVI stores some information in .txt file but the whole profile and events are stored in .csv file. First
+	 * make sure the input .txt looks like proper MkVI file, then start parsing the .csv.
+	 */
+	if (MATCH(memtxt.buffer, "MkVI_Config") == 0) {
+		int d, m, y;
+		int hh = 0, mm = 0, ss = 0;
+		int prev_depth = 0, cur_sampletime = 0;
+		bool has_depth = false;
+		char *lineptr;
+
+		struct dive *dive;
+		struct divecomputer *dc;
+		struct tm cur_tm;
+		timestamp_t date;
+
+		if (sscanf(parse_mkvi_value(memtxt.buffer, "Dive started at"), "%d-%d-%d %d:%d:%d",
+					&y, &m, &d, &hh, &mm, &ss) != 6) {
+			return -1;
+		}
+
+		cur_tm.tm_year = y;
+		cur_tm.tm_mon = m - 1;
+		cur_tm.tm_mday = d;
+		cur_tm.tm_hour = hh;
+		cur_tm.tm_min = mm;
+		cur_tm.tm_sec = ss;
+
+		dive = alloc_dive();
+		dive->when = utc_mktime(&cur_tm);;
+		dive->dc.model = strdup("Poseidon MkVI Discovery");
+		dive->dc.deviceid = atoi(parse_mkvi_value(memtxt.buffer, "Rig Serial number"));
+		dive->dc.dctype = CCR;
+
+		dive->cylinder[cur_cylinder_index].type.size.mliter = 3000;
+		dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = 200000;
+		dive->cylinder[cur_cylinder_index].type.description = strdup("3l Mk6");
+		cur_cylinder_index++;
+
+		dive->cylinder[cur_cylinder_index].type.size.mliter = 3000;
+		dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = 200000;
+		dive->cylinder[cur_cylinder_index].type.description = strdup("3l Mk6");
+		cur_cylinder_index++;
+
+		dc = &dive->dc;
+
+		/*
+		 * Read samples from the CSV file. A sample contains all the lines with same timestamp. The CSV file has
+		 * the following format:
+		 *
+		 * timestamp, type, value
+		 *
+		 * And following fields are of interest to us:
+		 *
+		 * 	6	sensor1
+		 * 	7	sensor2
+		 * 	8	depth
+		 *	13	o2 tank pressure
+		 *	14	diluent tank pressure
+		 *	20	o2 setpoint
+		 *	39	water temp
+		 */
+
+		if (readfile(csv, &memcsv) < 0) {
+			return report_error(translate("gettextFromC", "Poseidon import failed: unable to read '%s'"), csv);
+		}
+		lineptr = memcsv.buffer;
+		for (;;) {
+			char *end;
+			double val;
+			struct sample *sample;
+			int type;
+			int value;
+			int sampletime;
+
+			/* Collect all the information for one sample */
+			sscanf(lineptr, "%d,%d,%d", &cur_sampletime, &type, &value);
+
+			has_depth = false;
+			sample = prepare_sample(dc);
+			sample->time.seconds = cur_sampletime;
+
+			do {
+				int i = sscanf(lineptr, "%d,%d,%d", &sampletime, &type, &value);
+				switch (i) {
+				case 3:
+					switch (type) {
+					case 6:
+						add_sample_data(sample, POSEIDON_SENSOR1, value);
+						break;
+					case 7:
+						add_sample_data(sample, POSEIDON_SENSOR2, value);
+						break;
+					case 8:
+						has_depth = true;
+						prev_depth = value;
+						add_sample_data(sample, POSEIDON_DEPTH, value);
+						break;
+					case 13:
+						add_sample_data(sample, POSEIDON_PRESSURE, value);
+						break;
+					case 14:
+						add_sample_data(sample, POSEIDON_DILUENT, value);
+						break;
+					case 20:
+						add_sample_data(sample, POSEIDON_SETPOINT, value);
+						break;
+					case 39:
+						add_sample_data(sample, POSEIDON_TEMP, value);
+						break;
+					default:
+						break;
+					} /* sample types */
+					break;
+				case EOF:
+					break;
+				default:
+					printf("Unable to parse input: %s\n", lineptr);
+					break;
+				}
+
+				lineptr = strchr(lineptr, '\n');
+				if (!lineptr || !*lineptr)
+					break;
+				lineptr++;
+
+				/* Grabbing next sample time */
+				sscanf(lineptr, "%d,%d,%d", &cur_sampletime, &type, &value);
+			} while (sampletime == cur_sampletime);
+
+			if (!has_depth)
+				add_sample_data(sample, POSEIDON_DEPTH, prev_depth);
+			finish_sample(dc);
+
+			if (!lineptr || !*lineptr)
+				break;
+		}
+		record_dive(dive);
+		return 1;
+	} else {
+		return report_error(translate("gettextFromC", "No matching DC found for file '%s'"), csv);
+	}
+
+	return 0;
+}
+
 #define MAXCOLDIGITS 3
 #define MAXCOLS 100
 int parse_csv_file(const char *filename, int timef, int depthf, int tempf, int po2f, int cnsf, int ndlf, int ttsf, int stopdepthf, int pressuref, int sepidx, const char *csvtemplate, int unitidx)
diff --git a/parse-xml.c b/parse-xml.c
index d54456e..6da99b3 100644
--- a/parse-xml.c
+++ b/parse-xml.c
@@ -17,6 +17,7 @@
 
 #include "dive.h"
 #include "device.h"
+#include "membuffer.h"
 
 int verbose, quit;
 int metric = 1;
@@ -1738,6 +1739,13 @@ void parse_xml_buffer(const char *url, const char *buffer, int size,
 	xmlFreeDoc(doc);
 }
 
+void parse_mkvi_buffer(struct membuffer *txt, struct membuffer *csv, const char *starttime)
+{
+	dive_start();
+	divedate(starttime, &cur_dive->when);
+	dive_end();
+}
+
 extern int dm4_events(void *handle, int columns, char **data, char **column)
 {
 	event_start();
diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp
index dfe8a23..cb1ffea 100644
--- a/qt-ui/mainwindow.cpp
+++ b/qt-ui/mainwindow.cpp
@@ -1174,6 +1174,23 @@ void MainWindow::importFiles(const QStringList fileNames)
 	refreshDisplay();
 }
 
+void MainWindow::importTxtFiles(const QStringList fileNames)
+{
+	if (fileNames.isEmpty())
+		return;
+
+	QByteArray fileNamePtr, csv;
+
+	for (int i = 0; i < fileNames.size(); ++i) {
+		fileNamePtr = QFile::encodeName(fileNames.at(i));
+		csv = fileNamePtr.data();
+		csv.replace(strlen(csv.data()) - 3, 3, "csv");
+		parse_txt_file(fileNamePtr.data(), csv);
+	}
+	process_dives(true, false);
+	refreshDisplay();
+}
+
 void MainWindow::loadFiles(const QStringList fileNames)
 {
 	if (fileNames.isEmpty())
@@ -1208,14 +1225,15 @@ void MainWindow::on_actionImportDiveLog_triggered()
 	QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open dive log file"), lastUsedDir(),
 		tr("Dive log files (*.xml *.uddf *.udcf *.csv *.jlb *.dld *.sde *.db);;"
 			"XML files (*.xml);;UDDF/UDCF files(*.uddf *.udcf);;JDiveLog files(*.jlb);;"
-			"Suunto Files(*.sde *.db);;CSV Files(*.csv);;All Files(*)"));
+			"Suunto Files(*.sde *.db);;CSV Files(*.csv);;MkVI Files(*.txt);;All Files(*)"));
 
 	if (fileNames.isEmpty())
 		return;
 	updateLastUsedDir(QFileInfo(fileNames[0]).dir().path());
 
-	QStringList logFiles = fileNames.filter(QRegExp("^.*\\.(?!csv)", Qt::CaseInsensitive));
+	QStringList logFiles = fileNames.filter(QRegExp("^.*\\.(?!csv|?!txt)", Qt::CaseInsensitive));
 	QStringList csvFiles = fileNames.filter(".csv", Qt::CaseInsensitive);
+	QStringList txtFiles = fileNames.filter(".txt", Qt::CaseInsensitive);
 	if (logFiles.size()) {
 		importFiles(logFiles);
 	}
@@ -1226,6 +1244,10 @@ void MainWindow::on_actionImportDiveLog_triggered()
 		process_dives(true, false);
 		refreshDisplay();
 	}
+
+	if (txtFiles.size()) {
+		importTxtFiles(txtFiles);
+	}
 }
 
 void MainWindow::editCurrentDive()
diff --git a/qt-ui/mainwindow.h b/qt-ui/mainwindow.h
index edf33a5..26f4176 100644
--- a/qt-ui/mainwindow.h
+++ b/qt-ui/mainwindow.h
@@ -73,6 +73,7 @@ public:
 	void enableDcShortcuts();
 	void loadFiles(const QStringList files);
 	void importFiles(const QStringList importFiles);
+	void importTxtFiles(const QStringList fileNames);
 	void cleanUpEmpty();
 	void setToolButtonsEnabled(bool enabled);
 	ProfileWidget2 *graphics() const;
-- 
1.9.1

_______________________________________________
subsurface mailing list
[email protected]
http://lists.subsurface-divelog.org/cgi-bin/mailman/listinfo/subsurface

Reply via email to