Compare commits

...

7 commits

Author SHA1 Message Date
Morten Borup Petersen
9ce12f88a3
Merge 407beefad6 into 91d8bfef55 2024-11-19 08:48:37 +13:00
Michael Keller
91d8bfef55 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-19 08:47:00 +13:00
Michael Keller
7f42acfdfb 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-19 08:47:00 +13:00
Robert C. Helling
7dc92e170f Round MOD to lower depths
When computing the suggested switch depth for a gas,
we should take the next stop depth above the MOD, i.e.
round down. Otherwise we can produce MOD violation warnings.

We need, however, a bit of fudge as otherwise we do not
suggest to switch to o2 at 6m.

TestPlan uses the MOD to determine the depth to switch to
Tx21/35. This happens to be 65.378m. Therefore, switching
at 66m violates the MOD, the switch should be at 63m.
This then affects the gas usage.

Signed-off-by: Robert C. Helling <helling@atdotde.de>
2024-11-19 08:46:13 +13:00
Michael Keller
a898173ce7 CICD: Update the MXE Version Used to Build the Windows Version.
This fixes the missing 'Print' functionality in Windows.

Signed-off-by: Michael Keller <github@ike.ch>
2024-11-19 08:44:55 +13:00
Morten Borup Petersen
407beefad6 fix default mem_csv value
Signed-off-by: Morten Borup Petersen <morten_bp@live.dk>
2024-11-11 16:59:48 +01:00
Morten Borup Petersen
1d2a430b80 Slight refactor of DAN parse code
Mostly NFC; this commit is mainly to get familiar with the codebase and to meet
the people who will review these changes.

I hope to make some changes to the DAN parsing code to eventually extract more
metainfo from my aqualung divecomputer's `.zxu` formatted logs. To do so, and
for me to be able to work on this efficiently, I've refactored the DAN parsing
code using a bit more modern C++-style, as well as being more true-to-spec wrt.
the (...ancient) DAN file format documentation that i could dig up... hopefully
that's an alright tradeoff for the project.

This more true-to-spec parsing also fixed a bug with the number being parsed
from the incorrect index in the ZDH vector (or, atleast i consider it a bug -
the "Export sequence" number was being used as the dive number, instead of the
"Internal Dive Sequence" number. The latter, described in the spec as: `The
sequence number assigned to the dive by the recording computer`).

Also contains some unrelated formatting changes; i tried to keep these minimal
(i presume these files haven't been touched in a while by `clang-format`).

Signed-off-by: Morten Borup Petersen <morten_bp@live.dk>
2024-11-02 18:31:56 +01:00
19 changed files with 420 additions and 735 deletions

View file

@ -12,8 +12,9 @@ jobs:
windows-mxe:
runs-on: ubuntu-latest
env:
VERSION: ${{ '3.3.0' }} # 'official' images should have a dot-zero version
mxe_sha: '974808c2ecb02866764d236fe533ae57ba342e7a'
VERSION: ${{ '3.4.0' }} # 'official' images should have a dot-zero version
# Fix this here as QTWebKit (needed for printing) is broken in newer versions
mxe_sha: 'd6377b2f2334694dbb040294fd0d848327e63328'
steps:
- uses: actions/checkout@v4

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

View file

@ -2457,7 +2457,9 @@ int dive::mbar_to_depth(int mbar) const
depth_t dive::gas_mod(struct gasmix mix, pressure_t po2_limit, int roundto) const
{
double depth = (double) mbar_to_depth(po2_limit.mbar * 1000 / get_o2(mix));
return depth_t { .mm = int_cast<int>(depth / roundto) * roundto };
// Rounding should be towards lower=safer depths but we give a bit
// of fudge to all to switch to o2 at 6m. So from .9 we round up.
return depth_t { .mm = (int)(depth / roundto + 0.1) * roundto };
}
/* Maximum narcotic depth rounded to multiples of roundto mm */

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

@ -1,21 +1,21 @@
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <libdivecomputer/parser.h>
#include <map>
#include <stdlib.h>
#include <unistd.h>
#include "dive.h"
#include "errorhelper.h"
#include "subsurface-string.h"
#include "divelist.h"
#include "divelog.h"
#include "errorhelper.h"
#include "file.h"
#include "format.h"
#include "parse.h"
#include "sample.h"
#include "divelist.h"
#include "gettext.h"
#include "import-csv.h"
#include "parse.h"
#include "qthelper.h"
#include "sample.h"
#include "subsurface-string.h"
#include "xmlparams.h"
#define MATCH(buffer, pattern) \
@ -107,18 +107,205 @@ static char *parse_dan_new_line(char *buf, const char *NL)
}
static int try_to_xslt_open_csv(const char *filename, std::string &mem, const char *tag);
static int parse_csv_line(char *&ptr, const char *NL, char delim, std::vector<std::string> &fields)
{
char *line_end = strstr(ptr, NL); // Find the end of the line using the newline string
bool withNL = line_end;
if (!line_end) {
// EOF - set line_end to end of 'ptr'
line_end = ptr + strlen(ptr);
}
// Create a temporary pointer to traverse the line
char *field_start = ptr;
char *field_end = nullptr;
// Skip leading delimiter
if (*field_start == delim) {
field_start++;
} else {
return report_error("DEBUG: No leading delimiter found");
}
while (field_start < line_end) {
// Find the next delimiter or end of line
field_end = static_cast<char *>(memchr(field_start, delim, line_end - field_start));
if (field_end) {
// If we found a delimiter, extract the field
fields.emplace_back(field_start, field_end - field_start);
// Move to the next character after the delimiter
field_start = field_end + 1;
} else {
// If no more delimiters, add the last field
fields.emplace_back(field_start, line_end - field_start);
break;
}
}
// Update the pointer to point to the next line
ptr = line_end;
if (withNL)
ptr += strlen(NL);
return 0;
}
// Parses a line of DAN data fields (| separated). The provided 'fields' mapping
// will get filled with as many fields as are found in the line.
static int parse_dan_fields(
const char *NL,
std::map<unsigned, std::string> &fields,
char *&ptr)
{
std::vector<std::string> csv_fields;
if (parse_csv_line(ptr, NL, '|', csv_fields) < 0)
return -1;
if (csv_fields.size() > fields.size()) {
report_info("DEBUG: More DAN fields than expected");
return -1;
}
for (size_t i = 0; i < csv_fields.size(); i++) {
fields[i] = csv_fields[i];
}
return 0;
}
// Parses the DAN ZDH dive header.
static int parse_dan_zdh(const char *NL, struct xml_params *params, char *&ptr)
{
// Skip the leading 'ZDH'
ptr += 3;
std::string temp;
// Parse all fields - we only use a subset of them, but parse all for code maintain- and debugability.
enum ZDH_FIELD {
EXPORT_SEQUENCE,
INTERNAL_DIVE_SEQUENCE,
RECORD_TYPE,
RECORDING_INTERVAL,
LEAVE_SURFACE,
AIR_TEMPERATURE,
TANK_VOLUME,
O2_MODE,
REBREATHER_DILUENT_GAS,
ALTITUDE,
};
std::map<unsigned, std::string> fields = {
{EXPORT_SEQUENCE, ""},
{INTERNAL_DIVE_SEQUENCE, ""},
{RECORD_TYPE, ""},
{RECORDING_INTERVAL, ""},
{LEAVE_SURFACE, ""},
{AIR_TEMPERATURE, ""},
{TANK_VOLUME, ""},
{O2_MODE, ""},
{REBREATHER_DILUENT_GAS, ""},
{ALTITUDE, ""},
};
if (parse_dan_fields(NL, fields, ptr) < 0)
return -1;
// Add relevant fields to the XML parameters.
// Parse date. 'leaveSurface' should (per the spec) be provided in
// the format "YYYYMMDDHHMMSS", but old code used to allow for just parsing
// the date... so we'll do that here as well.
auto &leaveSurface = fields[LEAVE_SURFACE];
if (leaveSurface.length() >= 8) {
xml_params_add(params, "date", leaveSurface.substr(0, 8));
}
// Parse time with "1" prefix
if (leaveSurface.length() >= 14) {
std::string time_str = "1" + leaveSurface.substr(8, 6);
xml_params_add(params, "time", time_str);
}
xml_params_add(params, "airTemp", fields[AIR_TEMPERATURE]);
xml_params_add(params, "diveNro", fields[INTERNAL_DIVE_SEQUENCE]);
return 0;
}
// Parse the DAN ZDT dive trailer.
static int parse_dan_zdt(const char *NL, struct xml_params *params, char *&ptr)
{
// Skip the leading 'ZDT'
ptr += 3;
enum ZDT_FIELD {
EXPORT_SEQUENCE,
INTERNAL_DIVE_SEQUENCE,
MAX_DEPTH,
REACH_SURFACE,
MIN_WATER_TEMP,
PRESSURE_DROP,
};
std::map<unsigned, std::string> fields = {
{EXPORT_SEQUENCE, ""},
{INTERNAL_DIVE_SEQUENCE, ""},
{MAX_DEPTH, ""},
{REACH_SURFACE, ""},
{MIN_WATER_TEMP, ""},
{PRESSURE_DROP, ""},
};
if (parse_dan_fields(NL, fields, ptr) < 0)
return -1;
// Add relevant fields to the XML parameters.
xml_params_add(params, "waterTemp", fields[MIN_WATER_TEMP]);
return 0;
}
static int parse_dan_zdp(const char *NL, const char *filename, struct xml_params *params, char *&ptr, std::string &mem_csv)
{
if (strncmp(ptr, "ZDP{", 4) != 0)
return report_error("DEBUG: Failed to find start of ZDP");
if (ptr && ptr[4] == '}')
return report_error(translate("gettextFromC", "No dive profile found from '%s'"), filename);
ptr = parse_dan_new_line(ptr, NL);
if (!ptr)
return -1;
// We're now in the ZDP segment. Look for the end of it.
char *end_ptr = strstr(ptr, "ZDP}");
if (!end_ptr) {
return report_error("DEBUG: failed to find end of ZDP");
}
/* Copy the current dive data to start of mem_csv buffer */
mem_csv = std::string(ptr, end_ptr - ptr);
// Skip the trailing 'ZDP}' line.
ptr = end_ptr;
ptr = parse_dan_new_line(end_ptr, NL);
return 0;
}
static int parse_dan_format(const char *filename, struct xml_params *params, struct divelog *log)
{
int ret = 0, i;
size_t end_ptr = 0;
char tmpbuf[MAXCOLDIGITS];
int ret = 0;
int params_orig_size = xml_params_count(params);
char *ptr = NULL;
const char *NL = NULL;
char *iter = NULL;
auto [mem, err] = readfile(filename);
const char *end = mem.data() + mem.size();
if (err < 0)
return report_error(translate("gettextFromC", "Failed to read '%s'"), filename);
@ -132,136 +319,43 @@ static int parse_dan_format(const char *filename, struct xml_params *params, str
return -1;
}
while ((end_ptr < mem.size()) && (ptr = strstr(mem.data() + end_ptr, "ZDH"))) {
xml_params_resize(params, params_orig_size); // restart with original parameter block
char *iter_end = NULL;
iter = ptr + 4;
iter = strchr(iter, '|');
if (iter) {
memcpy(tmpbuf, ptr + 4, iter - ptr - 4);
tmpbuf[iter - ptr - 4] = 0;
xml_params_add(params, "diveNro", tmpbuf);
}
// Iteratively parse ZDH, ZDP and ZDT fields, which together comprise a list of dives.
while (ptr < end) {
xml_params_resize(params, params_orig_size); // Restart with original parameter block
//report_info("DEBUG: BEGIN end_ptr %d round %d <%s>", end_ptr, j++, ptr);
iter = ptr + 1;
for (i = 0; i <= 4 && iter; ++i) {
iter = strchr(iter, '|');
if (iter)
++iter;
}
if (!iter) {
report_info("DEBUG: Data corrupt");
return -1;
}
/* Setting date */
memcpy(tmpbuf, iter, 8);
tmpbuf[8] = 0;
xml_params_add(params, "date", tmpbuf);
/* Setting time, gotta prepend it with 1 to
* avoid octal parsing (this is stripped out in
* XSLT */
tmpbuf[0] = '1';
memcpy(tmpbuf + 1, iter + 8, 6);
tmpbuf[7] = 0;
xml_params_add(params, "time", tmpbuf);
/* Air temperature */
memset(tmpbuf, 0, sizeof(tmpbuf));
iter = strchr(iter, '|');
if (iter) {
iter = iter + 1;
iter_end = strchr(iter, '|');
if (iter_end) {
memcpy(tmpbuf, iter, iter_end - iter);
xml_params_add(params, "airTemp", tmpbuf);
}
}
/* Search for the next line */
if (iter)
iter = parse_dan_new_line(iter, NL);
if (!iter)
return -1;
/* We got a trailer, no samples on this dive */
if (strncmp(iter, "ZDT", 3) == 0) {
end_ptr = iter - mem.data();
/* Water temperature */
memset(tmpbuf, 0, sizeof(tmpbuf));
for (i = 0; i < 5 && iter; ++i)
iter = strchr(iter + 1, '|');
if (iter) {
iter = iter + 1;
iter_end = strchr(iter, '|');
if (iter_end) {
memcpy(tmpbuf, iter, iter_end - iter);
xml_params_add(params, "waterTemp", tmpbuf);
}
}
ret |= parse_xml_buffer(filename, "<csv></csv>", 11, log, params);
continue;
}
/* After ZDH we should get either ZDT (above) or ZDP */
if (strncmp(iter, "ZDP{", 4) != 0) {
report_info("DEBUG: Input appears to violate DL7 specification");
end_ptr = iter - mem.data();
continue;
}
if (ptr && ptr[4] == '}')
return report_error(translate("gettextFromC", "No dive profile found from '%s'"), filename);
if (ptr)
// Locate the ZDH header.
while (strncmp(ptr, "ZDH", 3) != 0) {
ptr = parse_dan_new_line(ptr, NL);
if (!ptr)
return -1;
if (!ptr)
return report_error("Expected ZDH header not found");
}
end_ptr = ptr - mem.data();
if (int ret = parse_dan_zdh(NL, params, ptr); ret < 0)
return ret;
/* Copy the current dive data to start of mem_csv buffer */
std::string mem_csv(ptr, mem.size() - (ptr - mem.data()));
// Attempt to parse the ZDP field (optional)
std::string mem_csv;
if (strncmp(ptr, "ZDP", 3) == 0) {
if (int ret = parse_dan_zdp(NL, filename, params, ptr, mem_csv); ret < 0)
return ret;
}
ptr = strstr(mem_csv.data(), "ZDP}");
if (ptr) {
*ptr = 0;
// Parse the mandatorty ZDT field
if (strncmp(ptr, "ZDT", 3) == 0) {
if (int ret = parse_dan_zdt(NL, params, ptr); ret < 0)
return ret;
} else {
report_info("DEBUG: failed to find end ZDP");
return -1;
}
mem_csv.resize(ptr - mem_csv.data());
end_ptr += ptr - mem_csv.data();
iter = parse_dan_new_line(ptr + 1, NL);
if (iter && strncmp(iter, "ZDT", 3) == 0) {
/* Water temperature */
memset(tmpbuf, 0, sizeof(tmpbuf));
for (i = 0; i < 5 && iter; ++i)
iter = strchr(iter + 1, '|');
if (iter) {
iter = iter + 1;
iter_end = strchr(iter, '|');
if (iter_end) {
memcpy(tmpbuf, iter, iter_end - iter);
xml_params_add(params, "waterTemp", tmpbuf);
}
}
return report_error("Expected ZDT trailer not found");
}
if (mem_csv.empty()) {
mem_csv = "<csv></csv>";
} else {
if (try_to_xslt_open_csv(filename, mem_csv, "csv"))
return -1;
}
ret |= parse_xml_buffer(filename, mem_csv.data(), mem_csv.size(), log, params);
}

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

View file

@ -18,12 +18,17 @@ void xml_params_resize(struct xml_params *params, int count)
void xml_params_add(struct xml_params *params, const char *key, const char *value)
{
params->items.push_back({ std::string(key), std::string(value) });
xml_params_add(params, std::string(key), std::string(value));
}
void xml_params_add(struct xml_params *params, const std::string &key, const std::string &value)
{
params->items.push_back({key, value});
}
void xml_params_add_int(struct xml_params *params, const char *key, int value)
{
params->items.push_back({ std::string(key), std::to_string(value) });
params->items.push_back({std::string(key), std::to_string(value)});
}
int xml_params_count(const struct xml_params *params)

View file

@ -18,9 +18,10 @@ extern struct xml_params *alloc_xml_params();
extern void free_xml_params(struct xml_params *params);
extern void xml_params_resize(struct xml_params *params, int count);
extern void xml_params_add(struct xml_params *params, const char *key, const char *value);
extern void xml_params_add(struct xml_params *params, const std::string &key, const std::string &value);
extern void xml_params_add_int(struct xml_params *params, const char *key, int value);
extern int xml_params_count(const struct xml_params *params);
extern const char *xml_params_get_key(const struct xml_params *params, int idx); // not stable
extern const char *xml_params_get_key(const struct xml_params *params, int idx); // not stable
extern const char *xml_params_get_value(const struct xml_params *params, int idx); // not stable
extern void xml_params_set_value(struct xml_params *params, int idx, const char *value);
extern const char **xml_params_get(const struct xml_params *params); // not stable

View file

@ -9,6 +9,6 @@ ZDP{
|3300|10|||||
|3600|0|||||
ZDP}
ZDT|2|2|10.0|20180102110000|25||
ZDH|3|3|I|QS|20180103101000|28|11|FO2|||
ZDT|3|3|10.0|20180103102000|26||
ZDT|1|2|10.0|20180102110000|25||
ZDH|1|3|I|QS|20180103101000|28|11|FO2|||
ZDT|1|3|10.0|20180103102000|26||

View file

@ -1,7 +1,10 @@
# Build the image using the --build-arg option, e.g.:
# docker build -t boret/myimage:0.1 --build-arg=mxe_sha=123ABC456 .
FROM ubuntu:24.04 as base
# We need to stick with 22.04 for now because the latest MXE version
# (db430dc676e6f5d77604af150b8acc1403af4fd7) does not build a working
# version of QtWebKit, which breaks printing in Subsurface.
FROM ubuntu:22.04 as base
# update and set up the packages we need for the build
RUN apt-get update && \
@ -54,8 +57,9 @@ RUN apt-get install -y \
xz-utils \
scons
# very often master is broken, so we pass in a known good SHA
ARG mxe_sha=d6377b2f2334694dbb040294fd0d848327e63328
# Default to 'master' if no build argument is passed in
ARG mxe_sha=master
# Very often master is broken, so we pass in a known good SHA
ENV _ver=${mxe_sha}
WORKDIR /win

View file

@ -5,12 +5,12 @@
#include "core/divelog.h"
#include "core/divesite.h"
#include "core/errorhelper.h"
#include "core/trip.h"
#include "core/file.h"
#include "core/import-csv.h"
#include "core/parse.h"
#include "core/qthelper.h"
#include "core/subsurface-string.h"
#include "core/trip.h"
#include "core/xmlparams.h"
#include <QTextStream>
@ -224,7 +224,8 @@ void TestParse::testParseNewFormat()
"/dives/")
.append(files.at(i))
.toLatin1()
.data(), &divelog),
.data(),
&divelog),
0);
QCOMPARE(divelog.dives.size(), i + 1);
}
@ -452,7 +453,11 @@ void TestParse::parseDL7()
QCOMPARE(parse_csv_file(SUBSURFACE_TEST_DATA "/dives/DL7.zxu",
&params, "DL7", &divelog),
0);
QCOMPARE(divelog.dives.size(), 3);
QCOMPARE(divelog.dives[0]->number, 1);
QCOMPARE(divelog.dives[1]->number, 2);
QCOMPARE(divelog.dives[2]->number, 3);
QCOMPARE(save_dives("./testdl7out.ssrf"), 0);
FILE_COMPARE("./testdl7out.ssrf",

View file

@ -865,17 +865,17 @@ void TestPlan::testVpmbMetricRepeat()
// check minimum gas result
dp = std::find_if(testPlan.dp.begin(), testPlan.dp.end(), [](auto &dp) { return dp.minimum_gas.mbar != 0; });
QCOMPARE(lrint(dp == testPlan.dp.end() ? 0.0 : dp->minimum_gas.mbar / 1000.0), 80l);
QCOMPARE(lrint(dp == testPlan.dp.end() ? 0.0 : dp->minimum_gas.mbar / 1000.0), 85l);
// print first ceiling
printf("First ceiling %.1f m\n", dive.mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar) * 0.001);
QVERIFY(dive.dcs[0].events.size() >= 3);
// check first gas change to 21/35 at 66m
// check first gas change to 21/35 at 63m
struct event *ev = &dive.dcs[0].events[0];
QVERIFY(ev != NULL);
QCOMPARE(ev->gas.index, 1);
QCOMPARE(ev->gas.mix.o2.permille, 210);
QCOMPARE(ev->gas.mix.he.permille, 350);
QCOMPARE(get_depth_at_time(&dive.dcs[0], ev->time.seconds), 66000);
QCOMPARE(get_depth_at_time(&dive.dcs[0], ev->time.seconds), 63000);
// check second gas change to EAN50 at 21m
ev = &dive.dcs[0].events[1];
QCOMPARE(ev->gas.index, 2);