[PATCH] Added import for Liquivision LVD log files
Henrik Brautaset Aronsen
subsurface at henrik.synth.no
Fri Nov 7 09:10:55 PST 2014
Great work!
H
On Fri, Nov 7, 2014 at 5:30 PM, John Van Ostrand <john at vanostrand.com>
wrote:
> 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
>
> _______________________________________________
> subsurface mailing list
> subsurface at subsurface-divelog.org
> http://lists.subsurface-divelog.org/cgi-bin/mailman/listinfo/subsurface
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.subsurface-divelog.org/pipermail/subsurface/attachments/20141107/4eaba07e/attachment-0001.html>
More information about the subsurface
mailing list