mirror of
https://github.com/subsurface/subsurface.git
synced 2024-11-30 22:20:21 +00:00
Compare commits
7 commits
320df0a7e8
...
32f92d9497
Author | SHA1 | Date | |
---|---|---|---|
|
32f92d9497 | ||
|
91d8bfef55 | ||
|
7f42acfdfb | ||
|
7dc92e170f | ||
|
a898173ce7 | ||
|
5d38fd3fe8 | ||
|
1742d6eabb |
18 changed files with 260 additions and 632 deletions
|
@ -12,8 +12,9 @@ jobs:
|
||||||
windows-mxe:
|
windows-mxe:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ '3.3.0' }} # 'official' images should have a dot-zero version
|
VERSION: ${{ '3.4.0' }} # 'official' images should have a dot-zero version
|
||||||
mxe_sha: '974808c2ecb02866764d236fe533ae57ba342e7a'
|
# Fix this here as QTWebKit (needed for printing) is broken in newer versions
|
||||||
|
mxe_sha: 'd6377b2f2334694dbb040294fd0d848327e63328'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
|
@ -82,6 +82,7 @@ SOURCES += subsurface-mobile-main.cpp \
|
||||||
core/save-git.cpp \
|
core/save-git.cpp \
|
||||||
core/datatrak.cpp \
|
core/datatrak.cpp \
|
||||||
core/ostctools.cpp \
|
core/ostctools.cpp \
|
||||||
|
core/divesoft.cpp \
|
||||||
core/planner.cpp \
|
core/planner.cpp \
|
||||||
core/save-xml.cpp \
|
core/save-xml.cpp \
|
||||||
core/cochran.cpp \
|
core/cochran.cpp \
|
||||||
|
|
|
@ -77,6 +77,7 @@ set(SUBSURFACE_CORE_LIB_SRCS
|
||||||
divesitetable.h
|
divesitetable.h
|
||||||
divesitehelpers.cpp
|
divesitehelpers.cpp
|
||||||
divesitehelpers.h
|
divesitehelpers.h
|
||||||
|
divesoft.cpp
|
||||||
downloadfromdcthread.cpp
|
downloadfromdcthread.cpp
|
||||||
downloadfromdcthread.h
|
downloadfromdcthread.h
|
||||||
event.cpp
|
event.cpp
|
||||||
|
|
|
@ -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
|
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));
|
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 */
|
/* Maximum narcotic depth rounded to multiples of roundto mm */
|
||||||
|
|
48
core/divesoft.cpp
Normal file
48
core/divesoft.cpp
Normal 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;
|
||||||
|
}
|
|
@ -267,6 +267,43 @@ bool remote_repo_uptodate(const char *filename, struct git_info *info)
|
||||||
return false;
|
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)
|
int parse_file(const char *filename, struct divelog *log)
|
||||||
{
|
{
|
||||||
struct git_info info;
|
struct git_info info;
|
||||||
|
@ -304,8 +341,13 @@ int parse_file(const char *filename, struct divelog *log)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Divesoft Freedom */
|
/* Divesoft Freedom */
|
||||||
if (fmt && (!strcasecmp(fmt + 1, "DLF")))
|
if (fmt && (!strcasecmp(fmt + 1, "DLF"))) {
|
||||||
return parse_dlf_buffer((unsigned char *)mem.data(), mem.size(), log);
|
auto buffer = read_into_buffer(filename);
|
||||||
|
if (buffer == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return divesoft_import(buffer, log);
|
||||||
|
}
|
||||||
|
|
||||||
/* DataTrak/Wlog */
|
/* DataTrak/Wlog */
|
||||||
if (fmt && !strcasecmp(fmt + 1, "LOG")) {
|
if (fmt && !strcasecmp(fmt + 1, "LOG")) {
|
||||||
|
@ -321,8 +363,11 @@ int parse_file(const char *filename, struct divelog *log)
|
||||||
|
|
||||||
/* OSTCtools */
|
/* OSTCtools */
|
||||||
if (fmt && (!strcasecmp(fmt + 1, "DIVE"))) {
|
if (fmt && (!strcasecmp(fmt + 1, "DIVE"))) {
|
||||||
ostctools_import(filename, log);
|
auto buffer = read_into_buffer(filename);
|
||||||
return 0;
|
if (buffer == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return ostctools_import(buffer, log);
|
||||||
}
|
}
|
||||||
|
|
||||||
return parse_file_buffer(filename, mem, log);
|
return parse_file_buffer(filename, mem, log);
|
||||||
|
|
|
@ -8,11 +8,13 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
struct divelog;
|
struct divelog;
|
||||||
struct zip;
|
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 parse_file(const char *filename, struct divelog *log);
|
||||||
extern int try_to_open_zip(const char *filename, struct divelog *log);
|
extern int try_to_open_zip(const char *filename, struct divelog *log);
|
||||||
|
|
|
@ -1534,6 +1534,26 @@ std::string do_libdivecomputer_import(device_data_t *data)
|
||||||
return err;
|
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.
|
* Parse data buffers instead of dc devices downloaded data.
|
||||||
* Intended to be used to parse profile data from binary files during import tasks.
|
* 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()
|
* Note that dc_descriptor_t in data *must* have been filled using dc_descriptor_iterator()
|
||||||
* calls.
|
* 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_status_t rc;
|
||||||
dc_parser_t *parser = NULL;
|
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_OSTC:
|
||||||
case DC_FAMILY_HW_FROG:
|
case DC_FAMILY_HW_FROG:
|
||||||
case DC_FAMILY_HW_OSTC3:
|
case DC_FAMILY_HW_OSTC3:
|
||||||
|
case DC_FAMILY_DIVESOFT_FREEDOM:
|
||||||
rc = dc_parser_new2(&parser, data->context, data->descriptor, buffer, size);
|
rc = dc_parser_new2(&parser, data->context, data->descriptor, buffer, size);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -54,7 +54,8 @@ struct device_data_t {
|
||||||
|
|
||||||
const char *errmsg (dc_status_t rc);
|
const char *errmsg (dc_status_t rc);
|
||||||
std::string do_libdivecomputer_import(device_data_t *data);
|
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);
|
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);
|
dc_descriptor_t *get_descriptor(dc_family_t type, unsigned int model);
|
||||||
|
|
||||||
|
|
|
@ -14,101 +14,48 @@
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
#include "libdivecomputer.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
|
* 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
|
* 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.
|
* 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;
|
if (buffer->size() < 456)
|
||||||
device_data_t devdata;
|
return report_error("%s", translate("gettextFromC", "Invalid OSTCTools file"));
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read dive number from the log
|
// Read dive number from the log
|
||||||
if (fseek(archive, 258, 0) == -1) {
|
auto ostcdive = std::make_unique<dive>();
|
||||||
report_error(failed_to_read_msg, file);
|
ostcdive->number = (*buffer)[258] + ((*buffer)[259] << 8);
|
||||||
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);
|
|
||||||
|
|
||||||
// Read device's serial number
|
// Read device's serial number
|
||||||
if (fseek(archive, 265, 0) == -1) {
|
unsigned int serial = (*buffer)[265] + ((*buffer)[266] << 8);
|
||||||
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);
|
|
||||||
|
|
||||||
// Read dive's raw data, header + profile
|
// Trim the buffer to the actual dive data
|
||||||
if (fseek(archive, 456, 0) == -1) {
|
buffer->erase(buffer->begin(), buffer->begin() + 456);
|
||||||
report_error(failed_to_read_msg, file);
|
unsigned int i = 0;
|
||||||
fclose(archive);
|
bool end_marker = false;
|
||||||
return;
|
for (auto c: *buffer) {
|
||||||
}
|
|
||||||
while ((c = getc(archive)) != EOF) {
|
|
||||||
buffer[i] = c;
|
|
||||||
if (buffer[i] == 0xFD && buffer[i - 1] == 0xFD)
|
|
||||||
break;
|
|
||||||
i++;
|
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
|
// 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;
|
dc_fam = DC_FAMILY_HW_OSTC;
|
||||||
} else {
|
} else {
|
||||||
switch (buffer[8]) {
|
switch ((*buffer)[8]) {
|
||||||
case 0x22:
|
case 0x22:
|
||||||
dc_fam = DC_FAMILY_HW_FROG;
|
dc_fam = DC_FAMILY_HW_FROG;
|
||||||
break;
|
break;
|
||||||
|
@ -117,12 +64,12 @@ void ostctools_import(const char *file, struct divelog *log)
|
||||||
dc_fam = DC_FAMILY_HW_OSTC3;
|
dc_fam = DC_FAMILY_HW_OSTC3;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
report_error(translate("gettextFromC", "Unknown DC in dive %d"), ostcdive->number);
|
return report_error(translate("gettextFromC", "Unknown DC in dive %d"), ostcdive->number);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to determine the model based on serial number
|
// Try to determine the model based on serial number
|
||||||
|
int model;
|
||||||
switch (dc_fam) {
|
switch (dc_fam) {
|
||||||
case DC_FAMILY_HW_OSTC:
|
case DC_FAMILY_HW_OSTC:
|
||||||
if (serial > 7000)
|
if (serial > 7000)
|
||||||
|
@ -145,17 +92,16 @@ void ostctools_import(const char *file, struct divelog *log)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare data to pass to libdivecomputer.
|
// Prepare data to pass to libdivecomputer.
|
||||||
ret = ostc_prepare_data(model, dc_fam, devdata);
|
device_data_t devdata;
|
||||||
if (ret == 0) {
|
int ret = prepare_device_descriptor(model, dc_fam, devdata);
|
||||||
report_error(translate("gettextFromC", "Unknown DC in dive %d"), ostcdive->number);
|
if (ret == 0)
|
||||||
return;
|
return report_error(translate("gettextFromC", "Unknown DC in dive %d"), ostcdive->number);
|
||||||
}
|
|
||||||
ostcdive->dcs[0].model = devdata.vendor + " " + devdata.model + " (Imported from OSTCTools)";
|
ostcdive->dcs[0].model = devdata.vendor + " " + devdata.model + " (Imported from OSTCTools)";
|
||||||
|
|
||||||
// Parse the dive data
|
// 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)
|
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
|
// 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.
|
// 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);
|
add_extra_data(&ostcdive->dcs[0], "Serial", ostcdive->dcs[0].serial);
|
||||||
|
|
||||||
log->dives.record_dive(std::move(ostcdive));
|
log->dives.record_dive(std::move(ostcdive));
|
||||||
|
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1770,499 +1770,6 @@ int parse_xml_buffer(const char *url, const char *buffer, int, struct divelog *l
|
||||||
return ret;
|
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()
|
void parse_xml_init()
|
||||||
{
|
{
|
||||||
LIBXML_TEST_VERSION
|
LIBXML_TEST_VERSION
|
||||||
|
|
|
@ -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_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_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_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);
|
std::string trimspace(const char *buffer);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
import QtQuick 2.5
|
import QtQuick
|
||||||
import QtLocation 5.3
|
import QtLocation
|
||||||
import QtPositioning 5.3
|
import QtPositioning
|
||||||
import org.subsurfacedivelog.mobile 1.0
|
import org.subsurfacedivelog.mobile 1.0
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
@ -15,7 +15,7 @@ Item {
|
||||||
id: mapHelper
|
id: mapHelper
|
||||||
map: map
|
map: map
|
||||||
editMode: false
|
editMode: false
|
||||||
onSelectedDivesChanged: rootItem.selectedDivesChanged(list)
|
onSelectedDivesChanged: (list) => { rootItem.selectedDivesChanged(list) }
|
||||||
onEditModeChanged: editMessage.isVisible = editMode === true ? 1 : 0
|
onEditModeChanged: editMessage.isVisible = editMode === true ? 1 : 0
|
||||||
onCoordinatesChanged: {}
|
onCoordinatesChanged: {}
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
@ -29,7 +29,6 @@ Item {
|
||||||
id: map
|
id: map
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
zoomLevel: defaultZoomIn
|
zoomLevel: defaultZoomIn
|
||||||
|
|
||||||
property var mapType
|
property var mapType
|
||||||
readonly property var defaultCenter: QtPositioning.coordinate(0, 0)
|
readonly property var defaultCenter: QtPositioning.coordinate(0, 0)
|
||||||
readonly property real defaultZoomIn: 12.0
|
readonly property real defaultZoomIn: 12.0
|
||||||
|
@ -41,12 +40,46 @@ Item {
|
||||||
property real newZoomOut: 1.0
|
property real newZoomOut: 1.0
|
||||||
property var clickCoord: QtPositioning.coordinate(0, 0)
|
property var clickCoord: QtPositioning.coordinate(0, 0)
|
||||||
property bool isReady: false
|
property bool isReady: false
|
||||||
|
|
||||||
Component.onCompleted: isReady = true
|
Component.onCompleted: isReady = true
|
||||||
onZoomLevelChanged: {
|
onZoomLevelChanged: {
|
||||||
if (isReady)
|
if (isReady)
|
||||||
mapHelper.calculateSmallCircleRadius(map.center)
|
mapHelper.calculateSmallCircleRadius(map.center)
|
||||||
}
|
}
|
||||||
|
property geoCoordinate startCentroid
|
||||||
|
startCentroid: newCenter
|
||||||
|
|
||||||
|
PinchHandler {
|
||||||
|
id: pinch
|
||||||
|
target: null
|
||||||
|
onActiveChanged: if (active) {
|
||||||
|
map.startCentroid = map.toCoordinate(pinch.centroid.position, false)
|
||||||
|
}
|
||||||
|
onScaleChanged: (delta) => {
|
||||||
|
map.zoomLevel += Math.log2(delta)
|
||||||
|
map.alignCoordinateToPoint(map.startCentroid, pinch.centroid.position)
|
||||||
|
}
|
||||||
|
onRotationChanged: (delta) => {
|
||||||
|
map.bearing -= delta
|
||||||
|
map.alignCoordinateToPoint(map.startCentroid, pinch.centroid.position)
|
||||||
|
}
|
||||||
|
grabPermissions: PointerHandler.TakeOverForbidden
|
||||||
|
}
|
||||||
|
WheelHandler {
|
||||||
|
id: wheel
|
||||||
|
// workaround for QTBUG-87646 / QTBUG-112394 / QTBUG-112432:
|
||||||
|
// Magic Mouse pretends to be a trackpad but doesn't work with PinchHandler
|
||||||
|
// and we don't yet distinguish mice and trackpads on Wayland either
|
||||||
|
acceptedDevices: Qt.platform.pluginName === "cocoa" || Qt.platform.pluginName === "wayland"
|
||||||
|
? PointerDevice.Mouse | PointerDevice.TouchPad
|
||||||
|
: PointerDevice.Mouse
|
||||||
|
rotationScale: 1/120
|
||||||
|
property: "zoomLevel"
|
||||||
|
}
|
||||||
|
DragHandler {
|
||||||
|
id: drag
|
||||||
|
target: null
|
||||||
|
onTranslationChanged: (delta) => map.pan(-delta.x, -delta.y)
|
||||||
|
}
|
||||||
|
|
||||||
MapItemView {
|
MapItemView {
|
||||||
id: mapItemView
|
id: mapItemView
|
||||||
|
@ -68,6 +101,8 @@ Item {
|
||||||
MouseArea {
|
MouseArea {
|
||||||
drag.target: (mapHelper.editMode && model.isSelected) ? mapItem : undefined
|
drag.target: (mapHelper.editMode && model.isSelected) ? mapItem : undefined
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!mapHelper.editMode && model.divesite)
|
if (!mapHelper.editMode && model.divesite)
|
||||||
mapHelper.selectedLocationChanged(model.divesite)
|
mapHelper.selectedLocationChanged(model.divesite)
|
||||||
|
@ -122,8 +157,8 @@ Item {
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onPressed: { map.stopZoomAnimations(); mouse.accepted = false }
|
onPressed: (mouse) => { map.stopZoomAnimations(); mouse.accepted = false }
|
||||||
onWheel: { map.stopZoomAnimations(); wheel.accepted = false }
|
onWheel: (wheel) => { map.stopZoomAnimations(); wheel.accepted = false }
|
||||||
onDoubleClicked: map.doubleClickHandler(map.toCoordinate(Qt.point(mouseX, mouseY)))
|
onDoubleClicked: map.doubleClickHandler(map.toCoordinate(Qt.point(mouseX, mouseY)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,15 +4,15 @@
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
|
||||||
#include "qmlmapwidgethelper.h"
|
|
||||||
#include "core/divefilter.h"
|
#include "core/divefilter.h"
|
||||||
#include "core/divelist.h"
|
#include "core/divelist.h"
|
||||||
#include "core/divelog.h"
|
#include "core/divelog.h"
|
||||||
#include "core/divesite.h"
|
#include "core/divesite.h"
|
||||||
#include "core/qthelper.h"
|
#include "core/qthelper.h"
|
||||||
#include "core/range.h"
|
#include "core/range.h"
|
||||||
#include "qt-models/maplocationmodel.h"
|
#include "qmlmapwidgethelper.h"
|
||||||
#include "qt-models/divelocationmodel.h"
|
#include "qt-models/divelocationmodel.h"
|
||||||
|
#include "qt-models/maplocationmodel.h"
|
||||||
#ifndef SUBSURFACE_MOBILE
|
#ifndef SUBSURFACE_MOBILE
|
||||||
#include "desktop-widgets/mapwidget.h"
|
#include "desktop-widgets/mapwidget.h"
|
||||||
#endif
|
#endif
|
||||||
|
@ -251,8 +251,8 @@ QString MapWidgetHelper::pluginObject()
|
||||||
{
|
{
|
||||||
QString lang = getUiLanguage().replace('_', '-');
|
QString lang = getUiLanguage().replace('_', '-');
|
||||||
QString cacheFolder = QString::fromStdString(system_default_directory() + "/googlemaps").replace("\\", "/");
|
QString cacheFolder = QString::fromStdString(system_default_directory() + "/googlemaps").replace("\\", "/");
|
||||||
return QStringLiteral("import QtQuick 2.0;"
|
return QStringLiteral("import QtQuick;"
|
||||||
"import QtLocation 5.3;"
|
"import QtLocation;"
|
||||||
"Plugin {"
|
"Plugin {"
|
||||||
" id: mapPlugin;"
|
" id: mapPlugin;"
|
||||||
" name: 'googlemaps';"
|
" name: 'googlemaps';"
|
||||||
|
@ -263,5 +263,6 @@ QString MapWidgetHelper::pluginObject()
|
||||||
" console.warn('MapWidget.qml: cannot find a plugin named: ' + name);"
|
" console.warn('MapWidget.qml: cannot find a plugin named: ' + name);"
|
||||||
" }"
|
" }"
|
||||||
" }"
|
" }"
|
||||||
"}").arg(lang, cacheFolder);
|
"}")
|
||||||
|
.arg(lang, cacheFolder);
|
||||||
}
|
}
|
||||||
|
|
|
@ -571,7 +571,11 @@ if [ "$QUICK" != "1" ] && [ "$BUILD_DESKTOP$BUILD_MOBILE" != "" ] && ( [[ $QT_VE
|
||||||
# build the googlemaps map plugin
|
# build the googlemaps map plugin
|
||||||
|
|
||||||
cd "$SRC"
|
cd "$SRC"
|
||||||
|
if [ "$BUILD_WITH_QT6" = "1" ] ; then
|
||||||
|
./${SRC_DIR}/scripts/get-dep-lib.sh -qt6 single . googlemaps
|
||||||
|
else
|
||||||
./${SRC_DIR}/scripts/get-dep-lib.sh single . googlemaps
|
./${SRC_DIR}/scripts/get-dep-lib.sh single . googlemaps
|
||||||
|
fi
|
||||||
pushd googlemaps
|
pushd googlemaps
|
||||||
mkdir -p build
|
mkdir -p build
|
||||||
mkdir -p J10build
|
mkdir -p J10build
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
# Build the image using the --build-arg option, e.g.:
|
# Build the image using the --build-arg option, e.g.:
|
||||||
# docker build -t boret/myimage:0.1 --build-arg=mxe_sha=123ABC456 .
|
# 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
|
# update and set up the packages we need for the build
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
|
@ -54,8 +57,9 @@ RUN apt-get install -y \
|
||||||
xz-utils \
|
xz-utils \
|
||||||
scons
|
scons
|
||||||
|
|
||||||
# very often master is broken, so we pass in a known good SHA
|
# Default to 'master' if no build argument is passed in
|
||||||
ARG mxe_sha=d6377b2f2334694dbb040294fd0d848327e63328
|
ARG mxe_sha=master
|
||||||
|
# Very often master is broken, so we pass in a known good SHA
|
||||||
ENV _ver=${mxe_sha}
|
ENV _ver=${mxe_sha}
|
||||||
|
|
||||||
WORKDIR /win
|
WORKDIR /win
|
||||||
|
|
|
@ -79,13 +79,17 @@ curl_download_library() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QT6="0"
|
||||||
# deal with all the command line arguments
|
# deal with all the command line arguments
|
||||||
|
if [ "$1" == "-qt6" ] ; then
|
||||||
|
shift
|
||||||
|
QT6="1"
|
||||||
|
fi
|
||||||
if [ $# -ne 2 ] && [ $# -ne 3 ] ; then
|
if [ $# -ne 2 ] && [ $# -ne 3 ] ; then
|
||||||
echo "wrong number of parameters, format:"
|
echo "wrong number of parameters, format:"
|
||||||
echo "get-dep-lib.sh <platform> <install dir>"
|
echo "get-dep-lib.sh [ -qt6 ] <platform> <install dir>"
|
||||||
echo "get-dep-lib.sh single <install dir> <lib>"
|
echo "get-dep-lib.sh [ -qt6 ] single <install dir> <lib>"
|
||||||
echo "get-dep-lib.sh singleAndroid <install dir> <lib>"
|
echo "get-dep-lib.sh [ -qt6 ] singleAndroid <install dir> <lib>"
|
||||||
echo "where"
|
echo "where"
|
||||||
echo "<platform> is one of scripts, ios or android"
|
echo "<platform> is one of scripts, ios or android"
|
||||||
echo "(the name of the directory where build.sh resides)"
|
echo "(the name of the directory where build.sh resides)"
|
||||||
|
@ -169,7 +173,11 @@ for package in "${PACKAGES[@]}" ; do
|
||||||
git_checkout_library breeze-icons $CURRENT_BREEZE_ICONS https://github.com/kde/breeze-icons.git
|
git_checkout_library breeze-icons $CURRENT_BREEZE_ICONS https://github.com/kde/breeze-icons.git
|
||||||
;;
|
;;
|
||||||
googlemaps)
|
googlemaps)
|
||||||
|
if [ "$QT6" = "1" ] ; then
|
||||||
|
git_checkout_library googlemaps master https://github.com/vladest/googlemaps.git
|
||||||
|
else
|
||||||
git_checkout_library googlemaps master https://github.com/Subsurface/googlemaps.git
|
git_checkout_library googlemaps master https://github.com/Subsurface/googlemaps.git
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
hidapi)
|
hidapi)
|
||||||
git_checkout_library hidapi master https://github.com/libusb/hidapi.git
|
git_checkout_library hidapi master https://github.com/libusb/hidapi.git
|
||||||
|
|
|
@ -865,17 +865,17 @@ void TestPlan::testVpmbMetricRepeat()
|
||||||
|
|
||||||
// check minimum gas result
|
// check minimum gas result
|
||||||
dp = std::find_if(testPlan.dp.begin(), testPlan.dp.end(), [](auto &dp) { return dp.minimum_gas.mbar != 0; });
|
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
|
// print first ceiling
|
||||||
printf("First ceiling %.1f m\n", dive.mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar) * 0.001);
|
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);
|
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];
|
struct event *ev = &dive.dcs[0].events[0];
|
||||||
QVERIFY(ev != NULL);
|
QVERIFY(ev != NULL);
|
||||||
QCOMPARE(ev->gas.index, 1);
|
QCOMPARE(ev->gas.index, 1);
|
||||||
QCOMPARE(ev->gas.mix.o2.permille, 210);
|
QCOMPARE(ev->gas.mix.o2.permille, 210);
|
||||||
QCOMPARE(ev->gas.mix.he.permille, 350);
|
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
|
// check second gas change to EAN50 at 21m
|
||||||
ev = &dive.dcs[0].events[1];
|
ev = &dive.dcs[0].events[1];
|
||||||
QCOMPARE(ev->gas.index, 2);
|
QCOMPARE(ev->gas.index, 2);
|
||||||
|
|
Loading…
Reference in a new issue