summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--datatrak.c682
-rw-r--r--datatrak.h34
-rw-r--r--dive.c2
-rw-r--r--dives/Example.logbin0 -> 26340 bytes
-rw-r--r--file.c8
-rw-r--r--qt-ui/mainwindow.cpp2
-rw-r--r--subsurface.pro2
8 files changed, 730 insertions, 1 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 85b24efc0..1ebb67100 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -112,6 +112,7 @@ ADD_CUSTOM_TARGET(version ALL COMMAND
# compile the core library, in C.
SET(SUBSURFACE_CORE_LIB_SRCS
cochran.c
+ datatrak.c
deco.c
device.c
dive.c
diff --git a/datatrak.c b/datatrak.c
new file mode 100644
index 000000000..46480d689
--- /dev/null
+++ b/datatrak.c
@@ -0,0 +1,682 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include "datatrak.h"
+#include "dive.h"
+#include "units.h"
+#include "device.h"
+#include "gettext.h"
+
+extern struct sample *add_sample(struct sample *sample, int time, struct divecomputer *dc);
+
+unsigned char lector_bytes[2], lector_word[4], tmp_1byte, *byte;
+unsigned int tmp_2bytes;
+char is_nitrox, is_O2, is_SCR;
+unsigned long tmp_4bytes;
+
+
+static unsigned int two_bytes_to_int(unsigned char x, unsigned char y)
+{
+ return (x << 8) + y;
+}
+
+static unsigned long four_bytes_to_long(unsigned char x, unsigned char y, unsigned char z, unsigned char t)
+{
+ return (((long)x << 24) + ((long)y << 16) + ((long)z << 8) + ((long)t));
+}
+
+static unsigned char* byte_to_bits(unsigned char byte)
+{
+ unsigned char i, *bits = (unsigned char*) malloc(8 * sizeof(unsigned char));
+
+ for (i = 0; i < 8; i++) {
+ bits[i] = (byte & (1 << i));
+ }
+ return bits;
+}
+
+/*
+ * Datatrak stores the date in days since 01-01-1600, while Subsurface uses
+ * time_t (seconds since 00:00 01-01-1970). Function substracts
+ * (1970 - 1600) * 365,2425 = 135139,725 to our date variable, getting the
+ * days since Epoch.
+ */
+static time_t date_time_to_ssrfc(unsigned long date, int time)
+{
+ time_t tmp;
+ tmp = ((date - 135140) * 86400) + (time * 60);
+ return tmp;
+}
+
+static unsigned char to_8859(unsigned char char_cp850)
+{
+ unsigned char outchar;
+ unsigned char char_8859[46] = {0xc7, 0xfc, 0xe9, 0xe2, 0xe4, 0xe0, 0xe5, 0xe7,
+ 0xea, 0xeb, 0xe8, 0xef, 0xee, 0xec, 0xc4, 0xc5,
+ 0xc9, 0xe6, 0xc6, 0xf4, 0xf6, 0xf2, 0xfb, 0xf9,
+ 0xff, 0xd6, 0xdc, 0xf8, 0xa3, 0xd8, 0xd7, 0x66,
+ 0xe1, 0xed, 0xf3, 0xfa, 0xf1, 0xd1, 0xaa, 0xba,
+ 0xbf, 0xae, 0xac, 0xbd, 0xbc, 0xa1};
+ outchar = char_8859[char_cp850 - 0x80];
+ return (outchar);
+}
+
+static char *to_utf8(unsigned char *in_string)
+{
+ int outlen, inlen, i=0, j=0;
+ inlen = strlen(in_string);
+ outlen = (inlen * 2) + 1;
+
+ char *out_string = calloc(outlen, sizeof(char));
+ for (i = 0; i < inlen; i++) {
+ if (in_string[i] < 127)
+ out_string[j] = in_string[i];
+ else {
+ if (in_string[i] > 127 && in_string[i] <= 173)
+ in_string[i] = to_8859(in_string[i]);
+ out_string[j] = (in_string[i] >> 6) | 0xC0;
+ j++;
+ out_string[j] = (in_string[i] & 0x3F) | 0x80;
+ }
+ j++;
+ }
+ out_string[j+1] = '\0';
+ return(out_string);
+}
+
+/*
+ * Subsurface sample structure doesn't support the flags and alarms in the dt .log
+ * so will treat them as dc events.
+ */
+static struct sample *dtrak_profile(struct dive *dt_dive, FILE* archivo)
+{
+ int i, j = 1, interval, o2percent = dt_dive->cylinder[0].gasmix.o2.permille / 10;
+ struct sample *sample = dt_dive->dc.sample;
+ struct divecomputer *dc = &dt_dive->dc;
+
+ for (i = 1; i <= dt_dive->dc.alloc_samples; i++) {
+ fread(&lector_bytes, sizeof(unsigned char), 2, archivo);
+ interval= 20 * (i + 1);
+ sample = add_sample(sample, interval, dc);
+ sample->depth.mm = ((two_bytes_to_int(lector_bytes[0], lector_bytes[1]) & 0xFFC0) * 1000 / 410);
+ byte = byte_to_bits(two_bytes_to_int(lector_bytes[0], lector_bytes[1]) & 0x003F);
+ if (byte[0] != 0)
+ sample->in_deco = true;
+ else
+ sample->in_deco = false;
+ if (byte[1] != 0)
+ add_event(dc, sample->time.seconds, 0, 0, 0, "rbt");
+ if (byte[2] != 0)
+ add_event(dc, sample->time.seconds, 0, 0, 0, "ascent");
+ if (byte[3] != 0)
+ add_event(dc, sample->time.seconds, 0, 0, 0, "ceiling");
+ if (byte[4] != 0)
+ add_event(dc, sample->time.seconds, 0, 0, 0, "workload");
+ if (byte[5] != 0)
+ add_event(dc, sample->time.seconds, 0, 0, 0, "transmitter");
+ if (j == 3) {
+ read_bytes(1);
+ if (is_O2) {
+ read_bytes(1);
+ o2percent = tmp_1byte;
+ }
+ j = 0;
+ }
+ if (is_O2)
+ // In commit 5f44fdd setpoint replaced po2, so although this is not necesarily CCR dive ...
+ sample->setpoint.mbar = calculate_depth_to_mbar(sample->depth.mm, dt_dive->surface_pressure, 0) * o2percent / 100;
+ j++;
+ }
+ return(sample);
+}
+
+/*
+ * Reads the header of a file and returns the header struct
+ * If it's not a DATATRAK file returns header zero initalized
+ */
+static dtrakheader read_file_header(FILE* archivo)
+{
+ dtrakheader fileheader = {0,0,0,0};
+ const short headerbytes = 12;
+ unsigned char *lector = (unsigned char *) malloc(headerbytes * sizeof(unsigned char));
+
+ fread(lector, sizeof(unsigned char), headerbytes, archivo);
+ if (two_bytes_to_int(lector[0], lector[1]) != 0xA100) {
+ puts("Error, el archivo no parece un divelog DATATRAK");
+ return(fileheader);
+ }
+ fileheader.header = (lector[0] << 8) + lector[1];
+ fileheader.dc_serial_1 = two_bytes_to_int(lector[2], lector[3]);
+ fileheader.dc_serial_2 = two_bytes_to_int(lector[4], lector[5]);
+ fileheader.divesNum = two_bytes_to_int (lector[7], lector[6]);
+ free(lector);
+ return(fileheader);
+}
+
+
+/*
+ * Parses the dive extracting its data and filling a subsurface's dive structure
+ */
+static struct dive dt_dive_parser(FILE* archivo, struct dive *dt_dive)
+{
+ unsigned char n;
+ int profile_length;
+ char *tmp_notes_str = NULL;
+ unsigned char *tmp_string1 = NULL,
+ *locality = NULL,
+ *dive_point = NULL,
+ buffer[1024];
+ struct divecomputer *dc = &dt_dive->dc;
+
+ is_nitrox = is_O2 = is_SCR = 0;
+
+ /*
+ * Parse byte to byte till next dive entry
+ */
+ n = 0;
+ fread(&lector_bytes[n], sizeof(char), 1, archivo);
+ while (lector_bytes[n] != 0xA0) {
+ fread(&lector_bytes[n], sizeof(char), 1, archivo);
+ }
+
+ /*
+ * Found dive header 0xA000, verify second byte
+ */
+ fread(&lector_bytes[n+1], sizeof(char), 1, archivo);
+ if (two_bytes_to_int(lector_bytes[0], lector_bytes[1]) != 0xA000) {
+ printf("ERROR, Byte = %4x\n", two_bytes_to_int(lector_bytes[0], lector_bytes[1]));
+ dt_dive = NULL;
+ return(*dt_dive);
+ }
+
+ /*
+ * Begin parsing
+ * First, Date of dive, 4 bytes
+ */
+ read_bytes(4);
+
+
+ /*
+ * Next, Time in minutes since 00:00
+ */
+ read_bytes(2);
+
+ dt_dive->dc.when = dt_dive->when = (timestamp_t) date_time_to_ssrfc(tmp_4bytes, tmp_2bytes);
+
+ /*
+ * Now, Locality, 1st byte is long of string, rest is string
+ */
+ read_bytes(1);
+ read_string(locality);
+
+ /*
+ * Next, Dive point, defined as Locality
+ */
+ read_bytes(1);
+ read_string(dive_point);
+
+ /*
+ * Subsurface only have a location variable, so we have to merge DTrak's
+ * Locality and Dive points.
+ */
+ snprintf(buffer, sizeof(buffer), "%s, %s", locality, dive_point);
+ dt_dive->dive_site_uuid = create_dive_site(buffer);
+ free(locality);
+ free(dive_point);
+
+ /*
+ * Altitude. Don't exist in Subsurface, the equivalent would be
+ * surface air pressure which can, be calculated from altitude.
+ * As dtrak registers altitude intervals, we, arbitrarily, choose
+ * the lower altitude/pressure equivalence for each segment. So
+ *
+ * Datatrak table * Conversion formula:
+ * *
+ * byte = 1 0 - 700 m * P = P0 * exp(-(g * M * h ) / (R * T0))
+ * byte = 2 700 - 1700m * P0 = sealevel pressure = 101325 Pa
+ * byte = 3 1700 - 2700 m * g = grav. acceleration = 9,80665 m/s²
+ * byte = 4 2700 - * m * M = molar mass (dry air) = 0,0289644 Kg/mol
+ * * h = altitude over sea level (m)
+ * * R = Universal gas constant = 8,31447 J/(mol*K)
+ * * T0 = sea level standard temperature = 288,15 K
+ */
+ read_bytes(1);
+ switch (tmp_1byte) {
+ case 1:
+ dt_dive->dc.surface_pressure.mbar = 1013;
+ break;
+ case 2:
+ dt_dive->dc.surface_pressure.mbar = 932;
+ break;
+ case 3:
+ dt_dive->dc.surface_pressure.mbar = 828;
+ break;
+ case 4:
+ dt_dive->dc.surface_pressure.mbar = 735;
+ break;
+ default:
+ dt_dive->dc.surface_pressure.mbar = 1013;
+ }
+
+ /*
+ * Interval (minutes)
+ */
+ read_bytes(2);
+ if (tmp_2bytes != 0x7FFF)
+ dt_dive->dc.surfacetime.seconds = (uint32_t) tmp_2bytes * 60;
+
+ /*
+ * Weather, values table, 0 to 6
+ * Subsurface don't have this record but we can use tags
+ */
+ dt_dive->tag_list = NULL;
+ read_bytes(1);
+ switch (tmp_1byte) {
+ case 1:
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "clear")));
+ break;
+ case 2:
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "misty")));
+ break;
+ case 3:
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "fog")));
+ break;
+ case 4:
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "rain")));
+ break;
+ case 5:
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "storm")));
+ break;
+ case 6:
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "snow")));
+ break;
+ default:
+ // unknown, do nothing
+ break;
+ }
+
+ /*
+ * Air Temperature
+ */
+ read_bytes(2);
+ if (tmp_2bytes != 0x7FFF)
+ dt_dive->dc.airtemp.mkelvin = C_to_mkelvin((double)(tmp_2bytes / 100));
+
+ /*
+ * Dive suit, values table, 0 to 6
+ */
+ read_bytes(1);
+ switch (tmp_1byte) {
+ case 1:
+ dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "No suit"));
+ break;
+ case 2:
+ dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Shorty"));
+ break;
+ case 3:
+ dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Combi"));
+ break;
+ case 4:
+ dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Wet suit"));
+ break;
+ case 5:
+ dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Semidry suit"));
+ break;
+ case 6:
+ dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Dry suit"));
+ break;
+ default:
+ // unknown, do nothing
+ break;
+ }
+
+ /*
+ * Tank, volume size in liter*100. And initialize gasmix to air (default).
+ * Dtrak don't record init and end pressures, but consumed bar, so let's
+ * init a default pressure of 200 bar.
+ */
+ read_bytes(2);
+ if (tmp_2bytes != 0x7FFF) {
+ dt_dive->cylinder[0].type.size.mliter = tmp_2bytes * 10;
+ dt_dive->cylinder[0].type.description = strdup("");
+ dt_dive->cylinder[0].start.mbar = 200000;
+ dt_dive->cylinder[0].gasmix.he.permille = 0;
+ dt_dive->cylinder[0].gasmix.o2.permille = 210;
+ dt_dive->cylinder[0].manually_added = true;
+ }
+
+ /*
+ * Maximum depth, in cm.
+ */
+ read_bytes(2);
+ if (tmp_2bytes != 0x7FFF)
+ dt_dive->maxdepth.mm = dt_dive->dc.maxdepth.mm = (int32_t) tmp_2bytes * 10;
+
+ /*
+ * Dive time in minutes.
+ */
+ read_bytes(2);
+ if (tmp_2bytes != 0x7FFF)
+ dt_dive->duration.seconds = dt_dive->dc.duration.seconds = (uint32_t) tmp_2bytes * 60;
+
+ /*
+ * Minimum water temperature in C*100. If unknown, set it to 0K which
+ * is subsurface's value for "unknown"
+ */
+ read_bytes(2);
+ if (tmp_2bytes != 0x7fff)
+ dt_dive->watertemp.mkelvin = dt_dive->dc.watertemp.mkelvin = C_to_mkelvin((double)(tmp_2bytes / 100));
+ else
+ dt_dive->watertemp.mkelvin = 0;
+
+ /*
+ * Air used in bar*100.
+ */
+ read_bytes(2);
+ if ((tmp_2bytes != 0x7FFF) && (dt_dive->cylinder[0].type.size.mliter))
+ dt_dive->cylinder[0].gas_used.mliter = dt_dive->cylinder[0].type.size.mliter * (tmp_2bytes / 100.0);
+
+ /*
+ * Dive Type 1 - Bit table. Subsurface don't have this record, but
+ * will use tags. Bits 0 and 1 are not used. Reuse coincident tags.
+ */
+ read_bytes(1);
+ byte = byte_to_bits(tmp_1byte);
+ if (byte[2] != 0)
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "no stop")));
+ if (byte[3] != 0)
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "deco")));
+ if (byte[4] != 0)
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "single ascent")));
+ if (byte[5] != 0)
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "multiple ascent")));
+ if (byte[6] != 0)
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "fresh")));
+ if (byte[7] != 0)
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "salt water")));
+
+ /*
+ * Dive Type 2 - Bit table, use tags again
+ */
+ read_bytes (1);
+ byte = byte_to_bits(tmp_1byte);
+ if (byte[0] != 0) {
+ taglist_add_tag(&dt_dive->tag_list, strdup("nitrox"));
+ is_nitrox = 1;
+ }
+ if (byte[1] != 0) {
+ taglist_add_tag(&dt_dive->tag_list, strdup("rebreather"));
+ is_SCR = 1;
+ }
+
+ /*
+ * Dive Activity 1 - Bit table, use tags again
+ */
+ read_bytes(1);
+ byte = byte_to_bits(tmp_1byte);
+ if (byte[0] != 0)
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "sight seeing")));
+ if (byte[1] != 0)
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "club dive")));
+ if (byte[2] != 0)
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "instructor")));
+ if (byte[3] != 0)
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "instruction")));
+ if (byte[4] != 0)
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "night")));
+ if (byte[5] != 0)
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "cave")));
+ if (byte[6] != 0)
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "ice")));
+ if (byte[7] != 0)
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "search")));
+
+
+ /*
+ * Dive Activity 2 - Bit table, use tags again
+ */
+ read_bytes(1);
+ byte = byte_to_bits(tmp_1byte);
+ if (byte[0] != 0)
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "wreck")));
+ if (byte[1] != 0)
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "river")));
+ if (byte[2] != 0)
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "drift")));
+ if (byte[3] != 0)
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "photo")));
+ if (byte[4] != 0)
+ taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "other")));
+
+ /*
+ * Other activities - String 1st byte = long
+ * Will put this in dive notes before the true notes
+ */
+ read_bytes(1);
+ if (tmp_1byte != 0) {
+ read_string(tmp_string1);
+ snprintf(buffer, sizeof(buffer), "%s: %s\n",
+ QT_TRANSLATE_NOOP("gettextFromC", "Other activities"),
+ tmp_string1);
+ tmp_notes_str = strdup(buffer);
+ free(tmp_string1);
+ }
+
+ /*
+ * Dive buddies
+ */
+ read_bytes(1);
+ if (tmp_1byte != 0) {
+ read_string(tmp_string1);
+ dt_dive->buddy = strdup(tmp_string1);
+ free(tmp_string1);
+ }
+
+ /*
+ * Dive notes
+ */
+ read_bytes(1);
+ if (tmp_1byte != 0) {
+ read_string(tmp_string1);
+ int len = snprintf(buffer, sizeof(buffer), "%s%s:\n%s",
+ tmp_notes_str ? tmp_notes_str : "",
+ QT_TRANSLATE_NOOP("gettextFromC", "Datatrak/Wlog notes"),
+ tmp_string1);
+ dt_dive->notes = calloc((len +1), sizeof(char));
+ dt_dive->notes = memcpy(dt_dive->notes, buffer, len);
+ free(tmp_string1);
+ if (tmp_notes_str != NULL) {
+ free(tmp_notes_str);
+ }
+ }
+
+ /*
+ * Alarms 1 - Bit table - Not in Subsurface, we use the profile
+ */
+ read_bytes(1);
+
+ /*
+ * Alarms 2 - Bit table - Not in Subsurface, we use the profile
+ */
+ read_bytes(1);
+
+ /*
+ * Dive number (in datatrak, after import user has to renumber)
+ */
+ read_bytes(2);
+ dt_dive->number = tmp_2bytes;
+
+ /*
+ * Computer timestamp - Useless for Subsurface
+ */
+ read_bytes(4);
+
+ /*
+ * Model - table - Not included 0x14, 0x24, 0x41, and 0x73
+ * known to exist, but not its model name - To add in the future.
+ * Strangely 0x00 serves for manually added dives and a dc too, at
+ * least in EXAMPLE.LOG file, shipped with the software.
+ */
+ read_bytes(1);
+ switch(tmp_1byte) {
+ case (0x00):
+ dt_dive->dc.model = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Manually entered dive"));
+ break;
+ case (0x1C):
+ dt_dive->dc.model = strdup("Aladin Air");
+ break;
+ case (0x1D):
+ dt_dive->dc.model = strdup("Spiro Monitor 2 plus");
+ break;
+ case (0x1E):
+ dt_dive->dc.model = strdup("Aladin Sport");
+ break;
+ case (0x1F):
+ dt_dive->dc.model = strdup("Aladin Pro");
+ break;
+ case (0x34):
+ dt_dive->dc.model = strdup("Aladin Air X");
+ break;
+ case (0x3D):
+ dt_dive->dc.model = strdup("Spiro Monitor 2 plus");
+ break;
+ case (0x3F):
+ dt_dive->dc.model = strdup("Mares Genius");
+ break;
+ case (0x44):
+ dt_dive->dc.model = strdup("Aladin Air X");
+ break;
+ case (0x48):
+ dt_dive->dc.model = strdup("Spiro Monitor 3 Air");
+ break;
+ case (0xA4):
+ dt_dive->dc.model = strdup("Aladin Air X O2");
+ break;
+ case (0xB1):
+ dt_dive->dc.model = strdup("Citizen Hyper Aqualand");
+ break;
+ case (0xB2):
+ dt_dive->dc.model = strdup("Citizen ProMaster");
+ break;
+ case (0xB3):
+ dt_dive->dc.model = strdup("Mares Guardian");
+ break;
+ case (0xBC):
+ dt_dive->dc.model = strdup("Aladin Air X Nitrox");
+ break;
+ case (0xF4):
+ dt_dive->dc.model = strdup("Aladin Air X Nitrox");
+ break;
+ case (0xFF):
+ dt_dive->dc.model = strdup("Aladin Pro Nitrox");
+ break;
+ default:
+ dt_dive->dc.model = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Unknown"));
+ break;
+ }
+ if ((tmp_1byte & 0xF0) == 0xF0)
+ is_nitrox = 1;
+ if ((tmp_1byte & 0xF0) == 0xA0)
+ is_O2 = 1;
+
+ /*
+ * Air usage, unknown use. Probably allows or deny manually entering gas
+ * comsumption based on dc model - Useless for Subsurface
+ */
+ read_bytes(1);
+ fseek(archivo, 6, 1); // jump over 6 bytes whitout known use
+
+ /*
+ * Profile data length
+ */
+ read_bytes(2);
+ profile_length = tmp_2bytes;
+ if (profile_length != 0) {
+
+ /*
+ * 8 x 2 bytes for the tissues saturation useless for subsurface
+ * and other 6 bytes without known use
+ */
+ fseek(archivo, 22, 1);
+ if (is_nitrox || is_O2) {
+
+ /*
+ * CNS % (unsure) values table (only nitrox computers)
+ */
+ read_bytes(1);
+
+ /*
+ * % O2 in nitrox mix - (only nitrox and O2 computers but differents)
+ */
+ read_bytes(1);
+ if (is_nitrox) {
+ dt_dive->cylinder[0].gasmix.o2.permille =
+ (tmp_1byte & 0x0F ? 20.0 + 2 * (tmp_1byte & 0x0F) : 21.0) * 10;
+ } else {
+ dt_dive->cylinder[0].gasmix.o2.permille = tmp_1byte * 10;
+ read_bytes(1) // Jump over one byte, unknown use
+ }
+ }
+ /*
+ * profileLength = Nº bytes, need to know how many samples are there.
+ * 2bytes per sample plus another one each three samples. Also includes the
+ * bytes jumped over (22) and the nitrox (2) or O2 (3).
+ */
+ int samplenum = is_O2 ? ((profile_length - 25) * 3 / 8) : ((profile_length - 24) * 3 / 7);
+
+ dc->events = calloc(samplenum, sizeof(struct event));
+ dc->alloc_samples = samplenum;
+ dc->samples = 0;
+ dc->sample = calloc(samplenum, sizeof(struct sample));
+
+ dtrak_profile(dt_dive, archivo);
+ }
+ /*
+ * Initialize some dive data not supported by Datatrak/WLog
+ */
+ if (!strcmp(dt_dive->dc.model, "Manually entered dive"))
+ dt_dive->dc.deviceid = 0;
+ else
+ dt_dive->dc.deviceid = 0xffffffff;
+ create_device_node(dt_dive->dc.model, dt_dive->dc.deviceid, "", "", dt_dive->dc.model);
+ dt_dive->dc.next = NULL;
+ if (!is_SCR && dt_dive->cylinder[0].type.size.mliter) {
+ dt_dive->cylinder[0].end.mbar = dt_dive->cylinder[0].start.mbar -
+ ((dt_dive->cylinder[0].gas_used.mliter / dt_dive->cylinder[0].type.size.mliter) * 1000);
+ }
+ return(*dt_dive);
+}
+
+void datatrak_import(const char *file, struct dive_table *table)
+{
+ FILE* archivo;
+ dtrakheader *fileheader = (dtrakheader *) malloc(sizeof(dtrakheader));
+ int i = 0;
+
+ if ((archivo = fopen(file, "rb")) == NULL) {
+ puts("Error, couldn't open the file");
+ return;
+ }
+
+ /*
+ * Verify fileheader, get number of dives in datatrak divelog
+ */
+ *fileheader = read_file_header(archivo);
+
+ if (fileheader->header == 0)
+ puts("Error. Not a DATATRAK/WLOG file\n");
+ while (i < fileheader->divesNum) {
+ struct dive *ptdive = alloc_dive();
+ *ptdive = dt_dive_parser(archivo, ptdive);
+ if (!ptdive)
+ puts("Error, no dive\n");
+ i++;
+ record_dive(ptdive);
+ }
+ taglist_cleanup(&g_tag_list);
+ fclose(archivo);
+ sort_table(table);
+ free (fileheader);
+}
diff --git a/datatrak.h b/datatrak.h
new file mode 100644
index 000000000..b3b244fe8
--- /dev/null
+++ b/datatrak.h
@@ -0,0 +1,34 @@
+#ifndef DATATRAK_HEADER_H
+#define DATATRAK_HEADER_H
+
+#include <string.h>
+
+typedef struct dtrakheader_ {
+ int header; //Must be 0xA100;
+ int divesNum;
+ int dc_serial_1;
+ int dc_serial_2;
+} dtrakheader;
+
+#define read_bytes(_n) \
+ switch (_n) {\
+ case 1:\
+ fread (&lector_bytes, sizeof(char), _n, archivo);\
+ tmp_1byte = lector_bytes[0];\
+ break;\
+ case 2:\
+ fread (&lector_bytes, sizeof(char), _n, archivo);\
+ tmp_2bytes = two_bytes_to_int (lector_bytes[1], lector_bytes[0]);\
+ break;\
+ default:\
+ fread (&lector_word, sizeof(char), _n, archivo);\
+ tmp_4bytes = four_bytes_to_long (lector_word[3],lector_word[2],lector_word[1],lector_word[0]);\
+ break;\
+ }
+
+#define read_string(_property)\
+ _property = (unsigned char *) calloc((tmp_1byte + 1), sizeof(unsigned char));\
+ fread (_property, sizeof(unsigned char), tmp_1byte, archivo);\
+ _property = strcat(to_utf8(_property), "");\
+
+#endif // DATATRAK_HEADER_H
diff --git a/dive.c b/dive.c
index fc0fbb855..2fb0875a5 100644
--- a/dive.c
+++ b/dive.c
@@ -1400,7 +1400,7 @@ struct dive *fixup_dive(struct dive *dive)
#define MERGE_TXT(res, a, b, n) res->n = merge_text(a->n, b->n)
#define MERGE_NONZERO(res, a, b, n) res->n = a->n ? a->n : b->n
-static struct sample *add_sample(struct sample *sample, int time, struct divecomputer *dc)
+struct sample *add_sample(struct sample *sample, int time, struct divecomputer *dc)
{
struct sample *p = prepare_sample(dc);
diff --git a/dives/Example.log b/dives/Example.log
new file mode 100644
index 000000000..3cc8cd3f8
--- /dev/null
+++ b/dives/Example.log
Binary files differ
diff --git a/file.c b/file.c
index 25221e635..ad646c47c 100644
--- a/file.c
+++ b/file.c
@@ -22,6 +22,8 @@
#define O_BINARY 0
#endif
+extern void datatrak_import(const char *file, struct dive_table *table);
+
int readfile(const char *filename, struct memblock *mem)
{
int ret, fd;
@@ -454,6 +456,12 @@ int parse_file(const char *filename)
return -1;
}
+ /* DataTrak/Wlog */
+ if (fmt && (!strcasecmp(fmt + 1, "LOG"))) {
+ datatrak_import(filename, &dive_table);
+ return 0;
+ }
+
ret = parse_file_buffer(filename, &mem);
free(mem.buffer);
return ret;
diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp
index aa708d051..e7235f9f8 100644
--- a/qt-ui/mainwindow.cpp
+++ b/qt-ui/mainwindow.cpp
@@ -885,6 +885,7 @@ QString MainWindow::filter()
f += "UDDF (*.uddf *.UDDF);;";
f += "XML (*.xml *.XML)";
f += "Divesoft (*.dlf *.DLF)";
+ f += "Datatrak/WLog Files (*.log *.LOG)";
return f;
}
@@ -1427,6 +1428,7 @@ void MainWindow::on_actionImportDiveLog_triggered()
"UDDF/UDCF files (*.uddf *.udcf *.UDDF *.UDCF);;"
"XML files (*.xml *.XML);;"
"APD log viewer (*.apd *.APD);;"
+ "Datatrak/WLog Files (*.log *.LOG);;"
"All files (*)"));
if (fileNames.isEmpty())
diff --git a/subsurface.pro b/subsurface.pro
index 639fff647..cedc92622 100644
--- a/subsurface.pro
+++ b/subsurface.pro
@@ -105,6 +105,7 @@ HEADERS = \
configuredivecomputer.h \
configuredivecomputerthreads.h \
devicedetails.h \
+ datatrak.h \
qt-ui/statistics/monthstatistics.h \
qt-ui/statistics/statisticswidget.h \
qt-ui/statistics/statisticsbar.h \
@@ -123,6 +124,7 @@ android: HEADERS -= \
SOURCES = \
version.c \
cochran.c \
+ datatrak.c \
deco.c \
device.c \
dive.c \