[PATCH] Added import for Liquivision LVD log files

John Van Ostrand john at vanostrand.com
Fri Nov 7 08:30:44 PST 2014


Support includes cylinder pressures and works for v3.0 log files.
---
 CMakeLists.txt       |   1 +
 file.c               |   2 +
 liquivision.c        | 352 +++++++++++++++++++++++++++++++++++++++++++++++++++
 qt-ui/mainwindow.cpp |   8 +-
 subsurface.pro       |   1 +
 5 files changed, 361 insertions(+), 3 deletions(-)
 create mode 100644 liquivision.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
index c04e968..868ad4f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -85,6 +85,7 @@ SET(SUBSURFACE_CORE_LIB_SRCS
 	equipment.c
 	file.c
 	libdivecomputer.c
+	liquivision.c
 	load-git.c
 	membuffer.c
 	parse-xml.c
diff --git a/file.c b/file.c
index bab7909..dadc11f 100644
--- a/file.c
+++ b/file.c
@@ -357,6 +357,8 @@ static int open_by_filename(const char *filename, const char *fmt, struct memblo
 	/* Cochran export comma-separated-value files */
 	if (!strcasecmp(fmt, "DPT"))
 		return try_to_open_csv(filename, mem, CSV_DEPTH);
+	if (!strcasecmp(fmt, "LVD"))
+		return try_to_open_liquivision(filename, mem);
 	if (!strcasecmp(fmt, "TMP"))
 		return try_to_open_csv(filename, mem, CSV_TEMP);
 	if (!strcasecmp(fmt, "HP1"))
diff --git a/liquivision.c b/liquivision.c
new file mode 100644
index 0000000..bb71bfd
--- /dev/null
+++ b/liquivision.c
@@ -0,0 +1,352 @@
+#include <string.h>
+
+#include "dive.h"
+#include "divelist.h"
+#include "file.h"
+
+
+// Convert bytes into an INT
+#define array_uint16_le(p) ((unsigned int) (p)[0] \
+							+ ((p)[1]<<8) )
+#define array_uint32_le(p) ((unsigned int) (p)[0] \
+							+ ((p)[1]<<8) + ((p)[2]<<16) \
+							+ ((p)[3]<<24))
+
+
+static void
+parse_dives (int log_version, const unsigned char *buf, unsigned int buf_size) {
+	unsigned int ptr = 0;
+	unsigned char model;
+
+	struct dive *dive;
+	struct divecomputer *dc;
+	struct sample *sample;
+
+	while (ptr < buf_size) {
+		dive = alloc_dive();
+		dc = &dive->dc;
+
+		// Model 0=Xen, 1,2=Xeo, 4=Lynx, other=Liquivision
+		model = *(buf + ptr);
+		switch (model) {
+		case 0:
+			dc->model = "Xen";
+			break;
+		case 1:
+		case 2:
+			dc->model = "Xeo";
+			break;
+		case 4:
+			dc->model = "Lynx";
+			break;
+		default:
+			dc->model = "LiquiVision";
+			break;
+		}
+		ptr++;
+
+		// Dive location, assemble Location and Place
+		unsigned int len, place_len;
+		len = array_uint32_le(buf + ptr);
+		ptr += 4;
+		place_len = array_uint32_le(buf + ptr + len);
+
+		if (len && place_len) {
+			dive->location = malloc(len + place_len + 4);
+			memset(dive->location, 0, len + place_len + 4);
+			memcpy(dive->location, buf + ptr, len);
+			memcpy(dive->location + len, ", ", 2);
+			memcpy(dive->location + len + 2, buf + ptr + len + 4, place_len);
+		} else if (len) {
+			dive->location = strndup(buf + ptr, len);
+		} else if (place_len) {
+			dive->location = strndup(buf + ptr + len + 4, place_len);
+		}
+
+		ptr += len + 4 + place_len;
+
+		// Dive comment
+		len = array_uint32_le(buf + ptr);
+		ptr += 4;
+
+		// Blank notes are better than the default text
+		if (len && strncmp(buf + ptr, "Comment ...", 11)) {
+			dive->notes = strndup(buf + ptr, len);
+		}
+		ptr += len;
+
+		dive->id = array_uint32_le(buf + ptr);
+		ptr += 4;
+
+		dive->number = array_uint16_le(buf + ptr) + 1;
+		ptr += 2;
+
+		dive->duration.seconds = array_uint32_le(buf + ptr);	// seconds
+		ptr += 4;
+
+		dive->maxdepth.mm = array_uint16_le(buf + ptr) * 10;	// cm->mm
+		ptr += 2;
+
+		dive->meandepth.mm = array_uint16_le(buf + ptr) * 10;	// cm->mm
+		ptr += 2;
+
+		dive->when = array_uint32_le(buf + ptr);
+		ptr += 4;
+
+		//unsigned int end_time = array_uint32_le(buf + ptr);
+		ptr += 4;
+
+		//unsigned int sit = array_uint32_le(buf + ptr);
+		ptr += 4;
+		//if (sit == 0xffffffff) {
+		//}
+
+		dive->surface_pressure.mbar = array_uint16_le(buf + ptr);		// ???
+		ptr += 2;
+
+		//unsigned int rep_dive = array_uint16_le(buf + ptr);
+		ptr += 2;
+
+		dive->mintemp.mkelvin =  C_to_mkelvin((float)array_uint16_le(buf + ptr)/10);// C->mK
+		ptr += 2;
+
+		dive->maxtemp.mkelvin =  C_to_mkelvin((float)array_uint16_le(buf + ptr)/10);// C->mK
+		ptr += 2;
+
+		dive->salinity = *(buf + ptr);	// ???
+		ptr += 1;
+
+		unsigned int sample_count = array_uint32_le(buf + ptr);
+		ptr += 4;
+
+		// Sample interval
+		unsigned char sample_interval;
+		sample_interval = 1;
+
+		unsigned char intervals[6] = {1,2,5,10,30,60};
+		if (*(buf + ptr) < 6)
+			sample_interval = intervals[*(buf + ptr)];
+		ptr += 1;
+
+		float start_cns = 0;
+		unsigned char dive_mode = 0, algorithm = 0;
+		if (array_uint32_le(buf + ptr) != sample_count) {
+			// Xeo, with CNS and OTU
+			start_cns = *(float *) (buf + ptr);
+			ptr += 4;
+			dive->cns = *(float *) (buf + ptr);	// end cns
+			ptr += 4;
+			dive->otu = *(float *) (buf + ptr);
+			ptr += 4;
+			dive_mode = *(buf + ptr++);	// 0=Deco, 1=Gauge, 2=None
+			algorithm = *(buf + ptr++);	// 0=ZH-L16C+GF
+			sample_count = array_uint32_le(buf + ptr);
+		}
+		ptr += 4;
+
+		// Parse dive samples
+		const unsigned char *ds = buf + ptr;
+		const unsigned char *ts = buf + ptr + sample_count * 2 + 4;
+		const unsigned char *ps = buf + ptr + sample_count * 4 + 4;
+		unsigned int ps_count = array_uint32_le(ps);
+		ps += 4;
+
+		// Bump ptr
+		ptr += sample_count * 4 + 4;
+
+		// Handle events
+		unsigned int event;
+		unsigned int ps_ptr;
+		ps_ptr = 0;
+
+		unsigned int d = 0, e;
+		int event_time, mbar, sensor;
+
+		// Loop through events
+		for (e = 0; e < ps_count; e++) {
+			// Get event
+			event = array_uint16_le(ps + ps_ptr);
+			ps_ptr += 2;
+
+			switch (event) {
+			case 0x0002:	//	Unknown
+			case 0x0004:	//	Unknown
+				ps_ptr +=  4;
+				continue;
+			case 0x0005:	// Unknown
+				ps_ptr += 6;
+				continue;
+			case 0x0007:	// Gas
+				// 4 byte time
+				// 1 byte O2, 1 bye He
+				ps_ptr += 6;
+				continue;
+			case 0x0008:
+				// 4 byte time
+				// 2 byte gas set point 2
+				ps_ptr += 6;
+				continue;
+			case 0x000f:
+				// Tank pressure
+				event_time = array_uint32_le(ps + ps_ptr);
+				sensor = 0; //array_uint16_le(ps + ps_ptr + 4);
+				mbar = array_uint16_le(ps + ps_ptr + 6) * 10; // cb->mb
+				// 1 byte PSR
+				// 1 byte ST
+				ps_ptr += 10;
+				break;
+			case 0x0010:
+				ps_ptr += 26;
+				continue;
+			case 0x0015:	// Unknown
+				ps_ptr += 2;
+				continue;
+			default:
+				ps_ptr += 4;
+				continue;
+			}
+
+			int sample_time, next_time, last_time;
+			int depth_mm, last_depth, temp_mk, last_temp;
+
+			while (true) {
+				sample = prepare_sample(dc);
+
+				// Get sample times
+				sample_time = d * sample_interval;
+				depth_mm = array_uint16_le(ds + d * 2) * 10; // cm->mm
+				temp_mk = C_to_mkelvin(array_uint16_le(ts + d * 2) / 10); // dC->mK
+				next_time = (d < sample_count - 1 ? (d + 1) * sample_interval : sample_time);
+				last_time = (d ? (d - 1) * sample_interval : 0);
+
+				if (d == sample_count) {
+					// We still have events to record
+					sample->time.seconds = event_time;
+					sample->depth.mm == array_uint16_le(ds + (d - 1) * 2) * 10; // cm->mm
+					sample->temperature.mkelvin = C_to_mkelvin(array_uint16_le(ts + (d - 1) * 2) / 10); // dC->mK
+					sample->sensor = sensor;
+					sample->cylinderpressure.mbar = mbar;
+					finish_sample(dc);
+
+					break;
+				} else if (event_time > sample_time) {
+					// Record sample and loop
+					sample->time.seconds = sample_time;
+					sample->depth.mm = depth_mm;
+					sample->temperature.mkelvin = temp_mk;
+					finish_sample(dc);
+					d++;
+
+					continue;
+				} else if (event_time == sample_time) {
+					sample->time.seconds = sample_time;
+					sample->depth.mm = depth_mm;
+					sample->temperature.mkelvin = temp_mk;
+					sample->sensor = sensor;
+					sample->cylinderpressure.mbar = mbar;
+					finish_sample(dc);
+
+					break;
+				} else {	// Event is prior to sample
+					sample->time.seconds = event_time;
+					sample->sensor = sensor;
+					sample->cylinderpressure.mbar = mbar;
+					if (last_time == sample_time) {
+						sample->depth.mm = depth_mm;
+						sample->temperature.mkelvin = temp_mk;
+					} else {
+						// Extrapolate
+						last_depth = array_uint16_le(ds + (d - 1) * 2) * 10; // cm->mm
+						last_temp = C_to_mkelvin(array_uint16_le(ts + (d - 1) * 2) / 10); // dC->mK
+						sample->depth.mm = last_depth + (depth_mm - last_depth)
+							* (event_time - last_time) / sample_interval;
+						sample->temperature.mkelvin = last_temp + (temp_mk - last_temp)
+							* (event_time - last_time) / sample_interval;
+					}
+					finish_sample(dc);
+
+					break;
+				}
+			} // while (true);
+		} // for each event sample
+
+		// record trailing depth samples
+		for ( ;d < sample_count; d++) {
+			sample = prepare_sample(dc);
+			sample->time.seconds = d * sample_interval;
+
+			sample->depth.mm = array_uint16_le(ds + d * 2) * 10; // cm->mm
+			sample->temperature.mkelvin =
+				C_to_mkelvin((float)array_uint16_le(ts + d * 2) / 10);
+			finish_sample(dc);
+		}
+
+		if (log_version == 3 && model == 4) {
+			// Advance to begin of next dive
+			switch (array_uint16_le(ps + ps_ptr)) {
+			case 0x0000:
+				ps_ptr += 5;
+				break;
+			case 0x0100:
+				ps_ptr += 7;
+				break;
+			case 0x0200:
+				ps_ptr += 9;
+				break;
+			case 0x0300:
+				ps_ptr += 11;
+				break;
+			case 0x0b0b:
+				ps_ptr += 27;
+				break;
+			}
+
+			while (*(ps + ps_ptr) != 0x04)
+				ps_ptr++;
+		}
+
+		// End dive
+		dive->downloaded = true;
+		record_dive(dive);
+		mark_divelist_changed(true);
+
+		// Advance ptr for next dive
+		ptr += ps_ptr + 4;
+	} // while
+
+	save_dives("/tmp/test.xml");
+}
+
+
+int
+try_to_open_liquivision(const char *filename, struct memblock *mem)
+{
+	void *name;
+	const unsigned char *buf = mem->buffer;
+	unsigned int buf_size = mem->size;
+	unsigned int ptr;
+	int log_version;
+
+	// Get name
+	unsigned int len = array_uint32_le(buf);
+	if (len) {
+		name = malloc(len);
+		strncpy(name, buf + 4, len);
+	}
+	ptr = 4 + len;
+
+	unsigned int dive_count = array_uint32_le(buf + ptr);
+	if (dive_count == 0xffffffff) {
+		// File version 3.0
+		log_version = 3;
+		ptr += 6;
+		dive_count = array_uint32_le(buf + ptr);
+	} else {
+		log_version = 2;
+	}
+	ptr += 4;
+
+	parse_dives(log_version, buf + ptr, buf_size - ptr);
+
+	return 1;
+}
diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp
index cc2d970..5811d4e 100644
--- a/qt-ui/mainwindow.cpp
+++ b/qt-ui/mainwindow.cpp
@@ -725,7 +725,8 @@ QString MainWindow::filter()
 	QString f;
 	f += "ALL ( *.ssrf *.xml *.XML *.uddf *.udcf *.UDFC *.jlb *.JLB ";
 	f += "*.sde *.SDE *.dld *.DLD ";
-	f += "*.db *.can";
+	f += "*.db *.can ";
+	f += "*.lvd ";
 	f += ");;";
 
 	f += "Subsurface (*.ssrf);;";
@@ -737,7 +738,8 @@ QString MainWindow::filter()
 	f += "SDE (*.sde *.SDE);;";
 	f += "DLD (*.dld *.DLD);;";
 	f += "DB (*.db);;";
-	f += "CAN (*.can)";
+	f += "CAN (*.can);;";
+	f += "LVD (*.lvd)";
 
 	return f;
 }
@@ -1247,7 +1249,7 @@ void MainWindow::loadFiles(const QStringList fileNames)
 void MainWindow::on_actionImportDiveLog_triggered()
 {
 	QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open dive log file"), lastUsedDir(),
-		tr("Dive log files (*.xml *.uddf *.udcf *.csv *.jlb *.dld *.sde *.db *.can);;"
+		tr("Dive log files (*.xml *.uddf *.udcf *.csv *.jlb *.dld *.sde *.db *.can *.lvd);;"
 			"XML files (*.xml);;UDDF/UDCF files(*.uddf *.udcf);;JDiveLog files(*.jlb);;"
 			"Suunto files(*.sde *.db);;CSV files(*.csv);;MkVI files(*.txt);;All files(*)"));
 
diff --git a/subsurface.pro b/subsurface.pro
index 7b38fd6..07e7b29 100644
--- a/subsurface.pro
+++ b/subsurface.pro
@@ -120,6 +120,7 @@ SOURCES =  \
 	file.c \
 	gettextfromc.cpp \
 	libdivecomputer.c \
+	liquivision.c \
 	load-git.c \
 	main.cpp \
 	membuffer.c \
-- 
1.8.3.1



More information about the subsurface mailing list