Compare commits

...

3 commits

Author SHA1 Message Date
Michael Keller
8ce9bc93d8
Merge bee8e0fcc5 into beb352d47c 2024-11-14 00:13:22 +00:00
Michael Keller
bee8e0fcc5 Import: Add Parsing for Divesoft Freedom+ Format Files.
Switch the Divesoft log file importer to use the parser in
libdivecomputer. This adds support for the newer Divesoft Freedom+ log
file format.
Also refactor the OSTCTools log file importer to share common
functionality.

Signed-off-by: Michael Keller <github@ike.ch>
2024-11-14 13:13:08 +13:00
Michael Keller
86fc0a3610 Import: Add Parsing for Divesoft Freedom+ Format Files.
Switch the Divesoft log file importer to use the parser in
libdivecomputer. This adds support for the newer Divesoft Freedom+ log
file format.
Also refactor the OSTCTools log file importer to share common
functionality.

Signed-off-by: Michael Keller <github@ike.ch>
2024-11-14 13:13:02 +13:00
10 changed files with 161 additions and 588 deletions

View file

@ -82,6 +82,7 @@ SOURCES += subsurface-mobile-main.cpp \
core/save-git.cpp \
core/datatrak.cpp \
core/ostctools.cpp \
core/divesoft.cpp \
core/planner.cpp \
core/save-xml.cpp \
core/cochran.cpp \

View file

@ -77,6 +77,7 @@ set(SUBSURFACE_CORE_LIB_SRCS
divesitetable.h
divesitehelpers.cpp
divesitehelpers.h
divesoft.cpp
downloadfromdcthread.cpp
downloadfromdcthread.h
event.cpp

48
core/divesoft.cpp Normal file
View file

@ -0,0 +1,48 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "errorhelper.h"
#include "subsurface-string.h"
#include "gettext.h"
#include "dive.h"
#include "divelist.h"
#include "divelog.h"
#include "extradata.h"
#include "format.h"
#include "libdivecomputer.h"
// As supplied by Divesoft
static const char divesoft_liberty_serial_prefix[] = "7026";
static const char divesoft_freedom_serial_prefix[] = "7044";
static const char divesoft_freedom_plus_serial_prefix[] = "7273";
// From libdivecomputer
static const int divesoft_liberty_model = 10;
static const int divesoft_freedom_model = 19;
int divesoft_import(const std::unique_ptr<std::vector<unsigned char>> &buffer, struct divelog *log)
{
int model = 0;
if (strncmp((char *)(buffer->data() + 52), divesoft_liberty_serial_prefix, 4) == 0)
model = divesoft_liberty_model;
else if (strncmp((char *)(buffer->data() + 52), divesoft_freedom_serial_prefix, 4) == 0 || strncmp((char *)(buffer->data() + 52), divesoft_freedom_plus_serial_prefix, 4) == 0)
model = divesoft_freedom_model;
device_data_t devdata;
int ret = prepare_device_descriptor(model, DC_FAMILY_DIVESOFT_FREEDOM, devdata);
if (ret == 0)
return report_error("%s", translate("gettextFromC", "Unknown DC"));
auto d = std::make_unique<dive>();
d->dcs[0].model = devdata.vendor + " " + devdata.model + " (Imported from file)";
// Parse the dive data
dc_status_t rc = libdc_buffer_parser(d.get(), &devdata, buffer->data(), buffer->size());
if (rc != DC_STATUS_SUCCESS)
return report_error(translate("gettextFromC", "Error - %s - parsing dive %d"), errmsg(rc), d->number);
log->dives.record_dive(std::move(d));
return 1;
}

View file

@ -267,6 +267,43 @@ bool remote_repo_uptodate(const char *filename, struct git_info *info)
return false;
}
static std::unique_ptr<std::vector<unsigned char>> read_into_buffer(const char *file)
{
const char *failed_to_read_msg = translate("gettextFromC", "Failed to read '%s'");
struct stat file_status;
if (stat(file, &file_status) < 0) {
report_error(failed_to_read_msg, file);
return NULL;
}
FILE *archive;
if ((archive = subsurface_fopen(file, "rb")) == NULL) {
report_error(failed_to_read_msg, file);
return NULL;
}
// Read dive's raw data
auto buffer = std::make_unique<std::vector<unsigned char>>(file_status.st_size, 0);
int i = 0, c;
while ((c = getc(archive)) != EOF) {
(*buffer)[i] = c;
i++;
}
if (ferror(archive)) {
report_error(failed_to_read_msg, file);
fclose(archive);
return NULL;
}
fclose(archive);
return buffer;
}
int parse_file(const char *filename, struct divelog *log)
{
struct git_info info;
@ -304,8 +341,13 @@ int parse_file(const char *filename, struct divelog *log)
}
/* Divesoft Freedom */
if (fmt && (!strcasecmp(fmt + 1, "DLF")))
return parse_dlf_buffer((unsigned char *)mem.data(), mem.size(), log);
if (fmt && (!strcasecmp(fmt + 1, "DLF"))) {
auto buffer = read_into_buffer(filename);
if (buffer == NULL)
return -1;
return divesoft_import(buffer, log);
}
/* DataTrak/Wlog */
if (fmt && !strcasecmp(fmt + 1, "LOG")) {
@ -321,8 +363,11 @@ int parse_file(const char *filename, struct divelog *log)
/* OSTCtools */
if (fmt && (!strcasecmp(fmt + 1, "DIVE"))) {
ostctools_import(filename, log);
return 0;
auto buffer = read_into_buffer(filename);
if (buffer == NULL)
return -1;
return ostctools_import(buffer, log);
}
return parse_file_buffer(filename, mem, log);

View file

@ -8,11 +8,13 @@
#include <stdio.h>
#include <vector>
#include <utility>
#include <memory>
struct divelog;
struct zip;
extern void ostctools_import(const char *file, struct divelog *log);
extern int ostctools_import(const std::unique_ptr<std::vector<unsigned char>> &buffer, struct divelog *log);
extern int divesoft_import(const std::unique_ptr<std::vector<unsigned char>> &buffer, struct divelog *log);
extern int parse_file(const char *filename, struct divelog *log);
extern int try_to_open_zip(const char *filename, struct divelog *log);

View file

@ -1534,6 +1534,26 @@ std::string do_libdivecomputer_import(device_data_t *data)
return err;
}
/*
* Fills a device_data_t structure with known dc data and a descriptor.
*/
int prepare_device_descriptor(int data_model, dc_family_t dc_fam, device_data_t &dev_data)
{
dev_data.device = NULL;
dev_data.context = NULL;
dc_descriptor_t *data_descriptor = get_descriptor(dc_fam, data_model);
if (data_descriptor) {
dev_data.descriptor = data_descriptor;
dev_data.vendor = dc_descriptor_get_vendor(data_descriptor);
dev_data.model = dc_descriptor_get_product(data_descriptor);
} else {
return 0;
}
return 1;
}
/*
* Parse data buffers instead of dc devices downloaded data.
* Intended to be used to parse profile data from binary files during import tasks.
@ -1543,7 +1563,7 @@ std::string do_libdivecomputer_import(device_data_t *data)
* Note that dc_descriptor_t in data *must* have been filled using dc_descriptor_iterator()
* calls.
*/
dc_status_t libdc_buffer_parser(struct dive *dive, device_data_t *data, unsigned char *buffer, int size)
dc_status_t libdc_buffer_parser(struct dive *dive, device_data_t *data, const unsigned char *buffer, int size)
{
dc_status_t rc;
dc_parser_t *parser = NULL;
@ -1556,6 +1576,7 @@ dc_status_t libdc_buffer_parser(struct dive *dive, device_data_t *data, unsigned
case DC_FAMILY_HW_OSTC:
case DC_FAMILY_HW_FROG:
case DC_FAMILY_HW_OSTC3:
case DC_FAMILY_DIVESOFT_FREEDOM:
rc = dc_parser_new2(&parser, data->context, data->descriptor, buffer, size);
break;
default:

View file

@ -54,7 +54,8 @@ struct device_data_t {
const char *errmsg (dc_status_t rc);
std::string do_libdivecomputer_import(device_data_t *data);
dc_status_t libdc_buffer_parser(struct dive *dive, device_data_t *data, unsigned char *buffer, int size);
int prepare_device_descriptor(int data_model, dc_family_t dc_fam, device_data_t &dev_data);
dc_status_t libdc_buffer_parser(struct dive *dive, device_data_t *data, const unsigned char *buffer, int size);
void logfunc(dc_context_t *context, dc_loglevel_t loglevel, const char *file, unsigned int line, const char *function, const char *msg, void *userdata);
dc_descriptor_t *get_descriptor(dc_family_t type, unsigned int model);

View file

@ -14,101 +14,48 @@
#include "format.h"
#include "libdivecomputer.h"
/*
* Fills a device_data_t structure with known dc data and a descriptor.
*/
static int ostc_prepare_data(int data_model, dc_family_t dc_fam, device_data_t &dev_data)
{
dc_descriptor_t *data_descriptor;
dev_data.device = NULL;
dev_data.context = NULL;
data_descriptor = get_descriptor(dc_fam, data_model);
if (data_descriptor) {
dev_data.descriptor = data_descriptor;
dev_data.vendor = dc_descriptor_get_vendor(data_descriptor);
dev_data.model = dc_descriptor_get_product(data_descriptor);
} else {
return 0;
}
return 1;
}
/*
* OSTCTools stores the raw dive data in heavily padded files, one dive
* each file. So it's not necessary to iterate once and again on a parsing
* function. Actually there's only one kind of archive for every DC model.
*/
void ostctools_import(const char *file, struct divelog *log)
int ostctools_import(const std::unique_ptr<std::vector<unsigned char>> &buffer, struct divelog *log)
{
FILE *archive;
device_data_t devdata;
dc_family_t dc_fam;
std::vector<unsigned char> buffer(65536, 0);
unsigned char uc_tmp[2];
auto ostcdive = std::make_unique<dive>();
dc_status_t rc = DC_STATUS_SUCCESS;
int model, ret, i = 0, c;
unsigned int serial;
const char *failed_to_read_msg = translate("gettextFromC", "Failed to read '%s'");
// Open the archive
if ((archive = subsurface_fopen(file, "rb")) == NULL) {
report_error(failed_to_read_msg, file);
return;
}
if (buffer->size() < 456)
return report_error("%s", translate("gettextFromC", "Invalid OSTCTools file"));
// Read dive number from the log
if (fseek(archive, 258, 0) == -1) {
report_error(failed_to_read_msg, file);
fclose(archive);
return;
}
if (fread(uc_tmp, 1, 2, archive) != 2) {
report_error(failed_to_read_msg, file);
fclose(archive);
return;
}
ostcdive->number = uc_tmp[0] + (uc_tmp[1] << 8);
auto ostcdive = std::make_unique<dive>();
ostcdive->number = (*buffer)[258] + ((*buffer)[259] << 8);
// Read device's serial number
if (fseek(archive, 265, 0) == -1) {
report_error(failed_to_read_msg, file);
fclose(archive);
return;
}
if (fread(uc_tmp, 1, 2, archive) != 2) {
report_error(failed_to_read_msg, file);
fclose(archive);
return;
}
serial = uc_tmp[0] + (uc_tmp[1] << 8);
unsigned int serial = (*buffer)[265] + ((*buffer)[266] << 8);
// Read dive's raw data, header + profile
if (fseek(archive, 456, 0) == -1) {
report_error(failed_to_read_msg, file);
fclose(archive);
return;
}
while ((c = getc(archive)) != EOF) {
buffer[i] = c;
if (buffer[i] == 0xFD && buffer[i - 1] == 0xFD)
break;
// Trim the buffer to the actual dive data
buffer->erase(buffer->begin(), buffer->begin() + 456);
unsigned int i = 0;
bool end_marker = false;
for (auto c: *buffer) {
i++;
if (c == 0xFD) {
if (end_marker)
break;
else
end_marker = true;
} else {
end_marker = false;
}
}
if (ferror(archive)) {
report_error(failed_to_read_msg, file);
fclose(archive);
return;
}
fclose(archive);
if (end_marker)
buffer->erase(buffer->begin() + i, buffer->end());
// Try to determine the dc family based on the header type
if (buffer[2] == 0x20 || buffer[2] == 0x21) {
dc_family_t dc_fam;
if ((*buffer)[2] == 0x20 || (*buffer)[2] == 0x21) {
dc_fam = DC_FAMILY_HW_OSTC;
} else {
switch (buffer[8]) {
switch ((*buffer)[8]) {
case 0x22:
dc_fam = DC_FAMILY_HW_FROG;
break;
@ -117,12 +64,12 @@ void ostctools_import(const char *file, struct divelog *log)
dc_fam = DC_FAMILY_HW_OSTC3;
break;
default:
report_error(translate("gettextFromC", "Unknown DC in dive %d"), ostcdive->number);
return;
return report_error(translate("gettextFromC", "Unknown DC in dive %d"), ostcdive->number);
}
}
// Try to determine the model based on serial number
int model;
switch (dc_fam) {
case DC_FAMILY_HW_OSTC:
if (serial > 7000)
@ -145,17 +92,16 @@ void ostctools_import(const char *file, struct divelog *log)
}
// Prepare data to pass to libdivecomputer.
ret = ostc_prepare_data(model, dc_fam, devdata);
if (ret == 0) {
report_error(translate("gettextFromC", "Unknown DC in dive %d"), ostcdive->number);
return;
}
device_data_t devdata;
int ret = prepare_device_descriptor(model, dc_fam, devdata);
if (ret == 0)
return report_error(translate("gettextFromC", "Unknown DC in dive %d"), ostcdive->number);
ostcdive->dcs[0].model = devdata.vendor + " " + devdata.model + " (Imported from OSTCTools)";
// Parse the dive data
rc = libdc_buffer_parser(ostcdive.get(), &devdata, buffer.data(), i + 1);
dc_status_t rc = libdc_buffer_parser(ostcdive.get(), &devdata, buffer->data(), buffer->size());
if (rc != DC_STATUS_SUCCESS)
report_error(translate("gettextFromC", "Error - %s - parsing dive %d"), errmsg(rc), ostcdive->number);
return report_error(translate("gettextFromC", "Error - %s - parsing dive %d"), errmsg(rc), ostcdive->number);
// Serial number is not part of the header nor the profile, so libdc won't
// catch it. If Serial is part of the extra_data, and set to zero, replace it.
@ -169,4 +115,6 @@ void ostctools_import(const char *file, struct divelog *log)
add_extra_data(&ostcdive->dcs[0], "Serial", ostcdive->dcs[0].serial);
log->dives.record_dive(std::move(ostcdive));
return 1;
}

View file

@ -1770,499 +1770,6 @@ int parse_xml_buffer(const char *url, const char *buffer, int, struct divelog *l
return ret;
}
/*
* Parse a unsigned 32-bit integer in little-endian mode,
* that is seconds since Jan 1, 2000.
*/
static timestamp_t parse_dlf_timestamp(unsigned char *buffer)
{
timestamp_t offset;
offset = buffer[3];
offset = (offset << 8) + buffer[2];
offset = (offset << 8) + buffer[1];
offset = (offset << 8) + buffer[0];
// Jan 1, 2000 is 946684800 seconds after Jan 1, 1970, which is
// the Unix epoch date that "timestamp_t" uses.
return offset + 946684800;
}
int parse_dlf_buffer(unsigned char *buffer, size_t size, struct divelog *log)
{
using namespace std::string_literals;
unsigned char *ptr = buffer;
unsigned char event;
bool found;
unsigned int time = 0;
char serial[6];
struct battery_status {
uint16_t volt1;
uint8_t percent1;
uint16_t volt2;
uint8_t percent2;
};
struct battery_status battery_start = {0, 0, 0, 0};
struct battery_status battery_end = {0, 0, 0, 0};
uint16_t o2_sensor_calibration_values[4] = {0};
cylinder_t *cyl;
struct parser_state state;
state.log = log;
// Check for the correct file magic
if (ptr[0] != 'D' || ptr[1] != 'i' || ptr[2] != 'v' || ptr[3] != 'E')
return -1;
dive_start(&state);
divecomputer_start(&state);
state.cur_dc->model = "DLF import";
// (ptr[7] << 8) + ptr[6] Is "Serial"
snprintf(serial, sizeof(serial), "%d", (ptr[7] << 8) + ptr[6]);
state.cur_dc->serial = serial;
state.cur_dc->when = parse_dlf_timestamp(ptr + 8);
state.cur_dive->when = state.cur_dc->when;
state.cur_dc->duration.seconds = ((ptr[14] & 0xFE) << 16) + (ptr[13] << 8) + ptr[12];
// ptr[14] >> 1 is scrubber used in %
// 3 bit dive type
switch((ptr[15] & 0x38) >> 3) {
case 0: // unknown
case 1:
state.cur_dc->divemode = OC;
break;
case 2:
state.cur_dc->divemode = CCR;
break;
case 3:
state.cur_dc->divemode = CCR; // mCCR
break;
case 4:
state.cur_dc->divemode = FREEDIVE;
break;
case 5:
state.cur_dc->divemode = OC; // Gauge
break;
case 6:
state.cur_dc->divemode = PSCR; // ASCR
break;
case 7:
state.cur_dc->divemode = PSCR;
break;
}
state.cur_dc->maxdepth.mm = ((ptr[21] << 8) + ptr[20]) * 10;
state.cur_dc->surface_pressure.mbar = ((ptr[25] << 8) + ptr[24]) / 10;
// Declare initial mix as first cylinder
cyl = state.cur_dive->get_or_create_cylinder(0);
cyl->gasmix.o2.permille = ptr[26] * 10;
cyl->gasmix.he.permille = ptr[27] * 10;
/* Done with parsing what we know about the dive header */
ptr += 32;
// We're going to interpret ppO2 saved as a sensor value in these modes.
if (state.cur_dc->divemode == CCR || state.cur_dc->divemode == PSCR)
state.cur_dc->no_o2sensors = 1;
for (; ptr < buffer + size; ptr += 16) {
time = ((ptr[0] >> 4) & 0x0f) +
((ptr[1] << 4) & 0xff0) +
((ptr[2] << 12) & 0x1f000);
event = ptr[0] & 0x0f;
switch (event) {
case 0:
/* Regular sample */
sample_start(&state);
state.cur_sample->time.seconds = time;
state.cur_sample->depth.mm = ((ptr[5] << 8) + ptr[4]) * 10;
// Crazy precision on these stored values...
// Only store value if we're in CCR/PSCR mode,
// because we rather calculate ppo2 our selfs.
if (state.cur_dc->divemode == CCR || state.cur_dc->divemode == PSCR)
state.cur_sample->o2sensor[0].mbar = ((ptr[7] << 8) + ptr[6]) / 10;
// In some test files, ndl / tts / temp is bogus if this bits are 1
// flag bits in ptr[11] & 0xF0 is probably involved to,
if ((ptr[2] >> 5) != 1) {
// NDL in minutes, 10 bit
state.cur_sample->ndl.seconds = (((ptr[9] & 0x03) << 8) + ptr[8]) * 60;
// TTS in minutes, 10 bit
state.cur_sample->tts.seconds = (((ptr[10] & 0x0F) << 6) + (ptr[9] >> 2)) * 60;
// Temperature in 1/10 C, 10 bit signed
state.cur_sample->temperature.mkelvin = ((ptr[11] & 0x20) ? -1 : 1) * (((ptr[11] & 0x1F) << 4) + (ptr[10] >> 4)) * 100 + ZERO_C_IN_MKELVIN;
}
state.cur_sample->stopdepth.mm = ((ptr[13] << 8) + ptr[12]) * 10;
if (state.cur_sample->stopdepth.mm)
state.cur_sample->in_deco = true;
//ptr[14] is helium content, always zero?
//ptr[15] is setpoint, what the computer thinks you should aim for?
sample_end(&state);
break;
case 1: /* dive event */
case 2: /* automatic parameter change */
case 3: /* diver error */
case 4: /* internal error */
case 5: /* device activity log */
//Event 18 is a button press. Lets ingore that event.
if (ptr[4] == 18)
continue;
event_start(&state);
state.cur_event.time.seconds = time;
switch (ptr[4]) {
case 1:
state.cur_event.name = "Setpoint Manual"s;
state.cur_event.value = ptr[6];
sample_start(&state);
state.cur_sample->setpoint.mbar = ptr[6] * 10;
sample_end(&state);
break;
case 2:
state.cur_event.name = "Setpoint Auto"s;
state.cur_event.value = ptr[6];
sample_start(&state);
state.cur_sample->setpoint.mbar = ptr[6] * 10;
sample_end(&state);
switch (ptr[7]) {
case 0:
state.cur_event.name += " Manual"s;
break;
case 1:
state.cur_event.name += " Auto Start"s;
break;
case 2:
state.cur_event.name += " Auto Hypox"s;
break;
case 3:
state.cur_event.name += " Auto Timeout"s;
break;
case 4:
state.cur_event.name += " Auto Ascent"s;
break;
case 5:
state.cur_event.name += " Auto Stall"s;
break;
case 6:
state.cur_event.name += " Auto SP Low"s;
break;
default:
break;
}
break;
case 3:
// obsolete
state.cur_event.name = "OC"s;
break;
case 4:
// obsolete
state.cur_event.name = "CCR"s;
break;
case 5:
state.cur_event.name = "gaschange"s;
state.cur_event.type = SAMPLE_EVENT_GASCHANGE2;
state.cur_event.value = ptr[7] << 8 ^ ptr[6];
for (const auto [i, cyl]: enumerated_range(state.cur_dive->cylinders)) {
if (cyl.gasmix.o2.permille == ptr[6] * 10 && cyl.gasmix.he.permille == ptr[7] * 10) {
found = true;
state.cur_event.gas.index = i;
break;
}
}
if (!found) {
cyl = cylinder_start(&state);
cyl->gasmix.o2.permille = ptr[6] * 10;
cyl->gasmix.he.permille = ptr[7] * 10;
cylinder_end(&state);
state.cur_event.gas.index = static_cast<int>(state.cur_dive->cylinders.size()) - 1;
}
break;
case 6:
state.cur_event.name = "Start"s;
break;
case 7:
state.cur_event.name = "Too Fast"s;
break;
case 8:
state.cur_event.name = "Above Ceiling"s;
break;
case 9:
state.cur_event.name = "Toxic"s;
break;
case 10:
state.cur_event.name = "Hypox"s;
break;
case 11:
state.cur_event.name = "Critical"s;
break;
case 12:
state.cur_event.name = "Sensor Disabled"s;
break;
case 13:
state.cur_event.name = "Sensor Enabled"s;
break;
case 14:
state.cur_event.name = "O2 Backup"s;
break;
case 15:
state.cur_event.name = "Peer Down"s;
break;
case 16:
state.cur_event.name = "HS Down"s;
break;
case 17:
state.cur_event.name = "Inconsistent"s;
break;
case 18:
// key pressed - It should never get in here
// as we ingored it at the parent 'case 5'.
break;
case 19:
// obsolete
state.cur_event.name = "SCR"s;
break;
case 20:
state.cur_event.name = "Above Stop"s;
break;
case 21:
state.cur_event.name = "Safety Miss"s;
break;
case 22:
state.cur_event.name = "Fatal"s;
break;
case 23:
state.cur_event.name = "gaschange"s;
state.cur_event.type = SAMPLE_EVENT_GASCHANGE2;
state.cur_event.value = ptr[7] << 8 ^ ptr[6];
event_end(&state);
break;
case 24:
state.cur_event.name = "gaschange"s;
state.cur_event.type = SAMPLE_EVENT_GASCHANGE2;
state.cur_event.value = ptr[7] << 8 ^ ptr[6];
event_end(&state);
// This is both a mode change and a gas change event
// so we encode it as two separate events.
event_start(&state);
state.cur_event.name = "Change Mode"s;
switch (ptr[8]) {
case 1:
state.cur_event.name += ": OC"s;
break;
case 2:
state.cur_event.name += ": CCR"s;
break;
case 3:
state.cur_event.name += ": mCCR"s;
break;
case 4:
state.cur_event.name += ": Free"s;
break;
case 5:
state.cur_event.name += ": Gauge"s;
break;
case 6:
state.cur_event.name += ": ASCR"s;
break;
case 7:
state.cur_event.name += ": PSCR"s;
break;
default:
break;
}
event_end(&state);
break;
case 25:
// uint16_t solenoid_bitmap = (ptr[7] << 8) + (ptr[6] << 0);
// uint32_t time = (ptr[11] << 24) + (ptr[10] << 16) + (ptr[9] << 8) + (ptr[8] << 0);
state.cur_event.name = format_string_std("CCR O2 solenoid %s", ptr[12] ? "opened": "closed");
break;
case 26:
state.cur_event.name = "User mark"s;
break;
case 27:
state.cur_event.name = format_string_std("%sGF Switch (%d/%d)", ptr[6] ? "Bailout, ": "", ptr[7], ptr[8]);
break;
case 28:
state.cur_event.name = "Peer Up"s;
break;
case 29:
state.cur_event.name = "HS Up"s;
break;
case 30:
state.cur_event.name = format_string_std("CNS %d%%", ptr[6]);
break;
default:
// No values above 30 had any description
break;
}
event_end(&state);
break;
case 6:
/* device configuration */
switch (((ptr[3] & 0x7f) << 3) + ((ptr[2] & 0xe0) >> 5)) {
// Buffer to print extra string into
// Local variables to temporary decode into
struct tm tm;
const char *device;
const char *deep_stops;
case 0: // TEST_CCR_FULL_1
utc_mkdate(parse_dlf_timestamp(ptr + 12), &tm);
add_extra_data(state.cur_dc, "TEST_CCR_FULL_1",
format_string_std("START=%04u-%02u-%02u %02u:%02u:%02u,TEST=%02X%02X%02X%02X,RESULT=%02X%02X%02X%02X",
tm.tm_year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, ptr[7], ptr[6], ptr[5], ptr[4], ptr[11], ptr[10], ptr[9], ptr[8]));
break;
case 1: // TEST_CCR_PARTIAL_1
utc_mkdate(parse_dlf_timestamp(ptr + 12), &tm);
add_extra_data(state.cur_dc, "TEST_CCR_PARTIAL_1",
format_string_std("START=%04u-%02u-%02u %02u:%02u:%02u,TEST=%02X%02X%02X%02X,RESULT=%02X%02X%02X%02X",
tm.tm_year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, ptr[7], ptr[6], ptr[5], ptr[4], ptr[11], ptr[10], ptr[9], ptr[8]));
break;
case 2: // CFG_OXYGEN_CALIBRATION
utc_mkdate(parse_dlf_timestamp(ptr + 12), &tm);
o2_sensor_calibration_values[0] = (ptr[5] << 8) + ptr[4];
o2_sensor_calibration_values[1] = (ptr[7] << 8) + ptr[6];
o2_sensor_calibration_values[2] = (ptr[9] << 8) + ptr[8];
o2_sensor_calibration_values[3] = (ptr[11] << 8) + ptr[10];
add_extra_data(state.cur_dc, "CFG_OXYGEN_CALIBRATION",
format_string_std("%04u,%04u,%04u,%04u,TIME=%04u-%02u-%02u %02u:%02u:%02u",
o2_sensor_calibration_values[0], o2_sensor_calibration_values[1], o2_sensor_calibration_values[2], o2_sensor_calibration_values[3], tm.tm_year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec));
break;
case 3: // CFG_SERIAL
add_extra_data(state.cur_dc, "CFG_SERIAL",
format_string_std("PRODUCT=%c%c%c%c,SERIAL=%c%c%c%c%c%c%c%c",
ptr[4], ptr[5], ptr[6], ptr[7], ptr[8], ptr[9], ptr[10], ptr[11], ptr[12], ptr[13], ptr[14], ptr[15]));
break;
case 4: // CFG_CONFIG_DECO
switch ((ptr[5] & 0xC0) >> 6) {
case 0:
deep_stops = "none";
break;
case 1:
deep_stops = "Pyle";
break;
case 2:
deep_stops = "Sladek";
break;
default:
deep_stops = "unknown";
break;
}
add_extra_data(state.cur_dc, "CFG_CONFIG_DECO part 1",
format_string_std("%s,%s,%s,safety stop required=%s,last_stop=%s,deco_algorithm=%s,stop_rounding=%u,deep_stops=%s",
(ptr[4] & 0x80) ? "imperial" : "metric", (ptr[4] & 0x40) ? "sea" : "fresh", (ptr[4] & 0x30) ? "stops" : "ceiling", (ptr[4] & 0x10) ? "yes" : "no", (ptr[4] & 0x08) ? "6m" : "3m", (ptr[4] & 0x04) ? "VPM" : "Buhlmann+GF", (ptr[4] & 0x03) ? (ptr[4] & 0x03) * 30 : 1, deep_stops));
add_extra_data(state.cur_dc, "CFG_CONFIG_DECO part 2",
format_string_std("deep_stop_len=%u min,gas_switch_len=%u min,gf_low=%u,gf_high=%u,gf_low_bailout=%u,gf_high_bailout=%u,ppO2_low=%4.2f,ppO2_high=%4.2f",
(ptr[5] & 0x38) >> 3, ptr[5] & 0x07, ptr[6], ptr[7], ptr[8], ptr[9], ptr[10] / 100.0f, ptr[11] / 100.0f));
add_extra_data(state.cur_dc, "CFG_CONFIG_DECO part 3",
format_string_std("alarm_global=%u,alarm_cns=%u,alarm_ppO2=%u,alarm_ceiling=%u,alarm_stop_miss=%u,alarm_decentrate=%u,alarm_ascentrate=%u",
(ptr[12] & 0x80) >> 7, (ptr[12] & 0x40) >> 6, (ptr[12] & 0x20) >> 5, (ptr[12] & 0x10) >> 4, (ptr[12] & 0x08) >> 3, (ptr[12] & 0x04) >> 2, (ptr[12] & 0x02) >> 1));
break;
case 5: // CFG_VERSION
switch (ptr[4]) {
case 0:
device = "FREEDOM";
break;
case 1:
device = "LIBERTY_CU";
break;
case 2:
device = "LIBERTY_HS";
break;
default:
device = "UNKNOWN";
break;
}
add_extra_data(state.cur_dc, "CFG_VERSION",
format_string_std("DEVICE=%s,HW=%d.%d,FW=%d.%d.%d.%d,FLAGS=%04X",
device, ptr[5], ptr[6], ptr[7], ptr[8], ptr[9], (ptr[15] << 24) + (ptr[14] << 16) + (ptr[13] << 8) + (ptr[12]), (ptr[11] << 8) + ptr[10]));
break;
}
break;
case 7:
/* measure record */
switch (ptr[2] >> 5) {
case 1:
/* Record starting battery level */
if (!battery_start.volt1 && !battery_start.volt2) {
battery_start.volt1 = (ptr[5] << 8) + ptr[4];
battery_start.percent1 = ptr[6];
battery_start.volt2 = (ptr[9] << 8) + ptr[8];
battery_start.percent2 = ptr[10];
}
/* Measure Battery, recording the last reading only */
battery_end.volt1 = (ptr[5] << 8) + ptr[4];
battery_end.percent1 = ptr[6];
battery_end.volt2 = (ptr[9] << 8) + ptr[8];
battery_end.percent2 = ptr[10];
break;
case 2:
/* Measure He */
//report_info("%ds he2 cells(0.01 mV): %d %d", time, (ptr[5] << 8) + ptr[4], (ptr[9] << 8) + ptr[8]);
break;
case 3:
/* Measure Oxygen */
//report_info("%d s: o2 cells(0.01 mV): %d %d %d %d", time, (ptr[5] << 8) + ptr[4], (ptr[7] << 8) + ptr[6], (ptr[9] << 8) + ptr[8], (ptr[11] << 8) + ptr[10]);
// [Pa/mV] coeficient O2
// 100 Pa == 1 mbar
sample_start(&state);
state.cur_sample->time.seconds = time;
state.cur_sample->o2sensor[0].mbar = ( ((ptr[5] << 8) + ptr[4]) * o2_sensor_calibration_values[0]) / 10000;
state.cur_sample->o2sensor[1].mbar = ( ((ptr[7] << 8) + ptr[6]) * o2_sensor_calibration_values[1]) / 10000;
state.cur_sample->o2sensor[2].mbar = ( ((ptr[9] << 8) + ptr[8]) * o2_sensor_calibration_values[2]) / 10000;
state.cur_sample->o2sensor[3].mbar = ( ((ptr[11] << 8) + ptr[10]) * o2_sensor_calibration_values[3]) / 10000;
sample_end(&state);
break;
case 4:
/* Measure GPS */
state.cur_location.lat.udeg = (int)((ptr[7] << 24) + (ptr[6] << 16) + (ptr[5] << 8) + (ptr[4] << 0));
state.cur_location.lon.udeg = (int)((ptr[11] << 24) + (ptr[10] << 16) + (ptr[9] << 8) + (ptr[8] << 0));
state.log->sites.create("DLF imported"s, state.cur_location)->add_dive(state.cur_dive.get());
break;
default:
break;
}
break;
case 8:
/* Deco event */
break;
default:
/* Unknown... */
break;
}
}
/* Recording the starting battery status to extra data */
if (battery_start.volt1) {
std::string str = format_string_std("%dmV (%d%%)", battery_start.volt1, battery_start.percent1);
add_extra_data(state.cur_dc, "Battery 1 (start)", str.c_str());
str = format_string_std("%dmV (%d%%)", battery_start.volt2, battery_start.percent2);
add_extra_data(state.cur_dc, "Battery 2 (start)", str.c_str());
}
/* Recording the ending battery status to extra data */
if (battery_end.volt1) {
std::string str = format_string_std("%dmV (%d%%)", battery_end.volt1, battery_end.percent1);
add_extra_data(state.cur_dc, "Battery 1 (end)", str.c_str());
str = format_string_std("%dmV (%d%%)", battery_end.volt2, battery_end.percent2);
add_extra_data(state.cur_dc, "Battery 2 (end)", str.c_str());
}
divecomputer_end(&state);
dive_end(&state);
return 0;
}
void parse_xml_init()
{
LIBXML_TEST_VERSION

View file

@ -150,7 +150,6 @@ int parse_shearwater_buffer(sqlite3 *handle, const char *url, const char *buf, i
int parse_shearwater_cloud_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct divelog *log);
int parse_cobalt_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct divelog *log);
int parse_divinglog_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct divelog *log);
int parse_dlf_buffer(unsigned char *buffer, size_t size, struct divelog *log);
std::string trimspace(const char *buffer);
#endif