Added import for Liquivision LVD log files

Support includes cylinder pressures and works for v3.0 log files.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
This commit is contained in:
John Van Ostrand 2014-11-07 11:30:44 -05:00 committed by Dirk Hohndel
parent 09e8adf54e
commit 8136306911
5 changed files with 361 additions and 3 deletions

View file

@ -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

2
file.c
View file

@ -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"))

352
liquivision.c Normal file
View file

@ -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;
}

View file

@ -727,7 +727,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);;";
@ -739,7 +740,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;
}
@ -1249,7 +1251,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(*)"));

View file

@ -120,6 +120,7 @@ SOURCES = \
file.c \
gettextfromc.cpp \
libdivecomputer.c \
liquivision.c \
load-git.c \
main.cpp \
membuffer.c \