aboutsummaryrefslogtreecommitdiffstats
path: root/core/liquivision.c
diff options
context:
space:
mode:
Diffstat (limited to 'core/liquivision.c')
-rw-r--r--core/liquivision.c420
1 files changed, 420 insertions, 0 deletions
diff --git a/core/liquivision.c b/core/liquivision.c
new file mode 100644
index 000000000..9347a724a
--- /dev/null
+++ b/core/liquivision.c
@@ -0,0 +1,420 @@
+#include <string.h>
+
+#include "dive.h"
+#include "divelist.h"
+#include "file.h"
+#include "strndup.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))
+
+struct lv_event {
+ time_t time;
+ struct pressure {
+ int sensor;
+ int mbar;
+ } pressure;
+};
+
+uint16_t primary_sensor;
+
+static int handle_event_ver2(int code, const unsigned char *ps, unsigned int ps_ptr, struct lv_event *event)
+{
+ (void) code;
+ (void) ps;
+ (void) ps_ptr;
+ (void) event;
+
+ // Skip 4 bytes
+ return 4;
+}
+
+
+static int handle_event_ver3(int code, const unsigned char *ps, unsigned int ps_ptr, struct lv_event *event)
+{
+ int skip = 4;
+ uint16_t current_sensor;
+
+ switch (code) {
+ case 0x0002: // Unknown
+ case 0x0004: // Unknown
+ skip = 4;
+ break;
+ case 0x0005: // Unknown
+ skip = 6;
+ break;
+ case 0x0007: // Gas
+ // 4 byte time
+ // 1 byte O2, 1 bye He
+ skip = 6;
+ break;
+ case 0x0008:
+ // 4 byte time
+ // 2 byte gas set point 2
+ skip = 6;
+ break;
+ case 0x000f:
+ // Tank pressure
+ event->time = array_uint32_le(ps + ps_ptr);
+
+ /* As far as I know, Liquivision supports 2 sensors, own and buddie's. This is my
+ * best guess how it is represented. */
+
+ current_sensor = array_uint16_le(ps + ps_ptr + 4);
+ if (primary_sensor == 0) {
+ primary_sensor = current_sensor;
+ }
+ if (current_sensor == primary_sensor) {
+ event->pressure.sensor = 0;
+ event->pressure.mbar = array_uint16_le(ps + ps_ptr + 6) * 10; // cb->mb
+ } else {
+ /* Ignoring the buddy sensor for no as we cannot draw it on the profile.
+ event->pressure.sensor = 1;
+ event->pressure.mbar = array_uint16_le(ps + ps_ptr + 6) * 10; // cb->mb
+ */
+ }
+ // 1 byte PSR
+ // 1 byte ST
+ skip = 10;
+ break;
+ case 0x0010:
+ skip = 26;
+ break;
+ case 0x0015: // Unknown
+ skip = 2;
+ break;
+ default:
+ skip = 4;
+ break;
+ }
+
+ return skip;
+}
+
+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) {
+ int i;
+ bool found_divesite = false;
+ dive = alloc_dive();
+ primary_sensor = 0;
+ dc = &dive->dc;
+
+ /* Just the main cylinder until we can handle the buddy cylinder porperly */
+ for (i = 0; i < 1; i++)
+ fill_default_cylinder(&dive->cylinder[i]);
+
+ // Model 0=Xen, 1,2=Xeo, 4=Lynx, other=Liquivision
+ model = *(buf + ptr);
+ switch (model) {
+ case 0:
+ dc->model = strdup("Xen");
+ break;
+ case 1:
+ case 2:
+ dc->model = strdup("Xeo");
+ break;
+ case 4:
+ dc->model = strdup("Lynx");
+ break;
+ default:
+ dc->model = strdup("Liquivision");
+ break;
+ }
+ ptr++;
+
+ // Dive location, assemble Location and Place
+ unsigned int len, place_len;
+ char *location;
+ len = array_uint32_le(buf + ptr);
+ ptr += 4;
+ place_len = array_uint32_le(buf + ptr + len);
+
+ if (len && place_len) {
+ location = malloc(len + place_len + 4);
+ memset(location, 0, len + place_len + 4);
+ memcpy(location, buf + ptr, len);
+ memcpy(location + len, ", ", 2);
+ memcpy(location + len + 2, buf + ptr + len + 4, place_len);
+ } else if (len) {
+ location = strndup((char *)buf + ptr, len);
+ } else if (place_len) {
+ location = strndup((char *)buf + ptr + len + 4, place_len);
+ }
+
+ /* Store the location only if we have one */
+ if (len || place_len)
+ found_divesite = true;
+
+ 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((char *)buf + ptr, "Comment ...", 11)) {
+ dive->notes = strndup((char *)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;
+
+ // now that we have the dive time we can store the divesite
+ // (we need the dive time to create deterministic uuids)
+ if (found_divesite) {
+ dive->dive_site_uuid = find_or_create_dive_site_with_name(location, dive->when);
+ free(location);
+ }
+ //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);
+ }
+ // we aren't using the start_cns, dive_mode, and algorithm, yet
+ (void)start_cns;
+ (void)dive_mode;
+ (void)algorithm;
+
+ 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 ps_ptr;
+ ps_ptr = 0;
+
+ unsigned int event_code, d = 0, e;
+ struct lv_event event;
+
+ // Loop through events
+ for (e = 0; e < ps_count; e++) {
+ // Get event
+ event_code = array_uint16_le(ps + ps_ptr);
+ ps_ptr += 2;
+
+ if (log_version == 3) {
+ ps_ptr += handle_event_ver3(event_code, ps, ps_ptr, &event);
+ if (event_code != 0xf)
+ continue; // ignore all by pressure sensor event
+ } else { // version 2
+ ps_ptr += handle_event_ver2(event_code, ps, ps_ptr, &event);
+ continue; // ignore all events
+ }
+ int sample_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((float)array_uint16_le(ts + d * 2) / 10); // dC->mK
+ 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((float) array_uint16_le(ts + (d - 1) * 2) / 10); // dC->mK
+ sample->sensor = event.pressure.sensor;
+ sample->cylinderpressure.mbar = event.pressure.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 = event.pressure.sensor;
+ sample->cylinderpressure.mbar = event.pressure.mbar;
+ finish_sample(dc);
+
+ break;
+ } else { // Event is prior to sample
+ sample->time.seconds = event.time;
+ sample->sensor = event.pressure.sensor;
+ sample->cylinderpressure.mbar = event.pressure.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((float) 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
+
+ //DEBUG save_dives("/tmp/test.xml");
+}
+
+int try_to_open_liquivision(const char *filename, struct memblock *mem)
+{
+ (void) filename;
+ const unsigned char *buf = mem->buffer;
+ unsigned int buf_size = mem->size;
+ unsigned int ptr;
+ int log_version;
+
+ // Get name length
+ unsigned int len = array_uint32_le(buf);
+ // Ignore length field and the name
+ 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;
+}