mirror of
https://github.com/subsurface/subsurface.git
synced 2025-02-19 22:16:15 +00:00
Move the import of .FIT files into the 'Import log files' menu item, where most people will be looking for it. This also naturally opens a file selection dialog, which is more intuitive than having to select this in the dive computer import dialog. Also fix a bug affecting file imports if the log files contain coordinates - the dive log needs to be set in the import data structure. And refactor the file dialog file filters to make it more natural to add more entries. Requires https://github.com/subsurface/libdc/pull/72 to work. Signed-off-by: Michael Keller <github@ike.ch>
692 lines
23 KiB
C++
692 lines
23 KiB
C++
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
/* .asd file format is the dive import/export format for Scubapro/Uwatec software
|
|
* divelogs. It enables export from SmartTrak and import/export from LogTrak.
|
|
* It's a binary format with remembrances to ancient DataTrak format in the way
|
|
* strings are stored, and it keeps the samples from the DC as they are, so we can
|
|
* simply pass them to libdivecomputer to parse.
|
|
* The bad news:
|
|
* - Dives aren't fixed size, thus we can't avoid a sequential parsing.
|
|
* - There is a header area for each dive but this header is not the same
|
|
* libdc expects as the expected headers are different based on DC model or family.
|
|
* Thus faking a header as it's expected by libdc is a must. This has to be done per
|
|
* dive, as a single file may have dives downloaded from different DCs.
|
|
* - The byte sequence signaling the beginning of a new dive varies depending
|
|
* on the origin of the file (SmartTrak or LogTrak).
|
|
* - Files coming from SmartTrak can include dives imported from yet older
|
|
* DataTrak software. I don't know if these dives coming from serial devices can
|
|
* even be imported into LogTrak. ATM we can safely assume we are only finding
|
|
* them in SmartTrak exported dives.
|
|
* - ... Probably some more I just haven't found yet.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <locale.h>
|
|
#include <stdbool.h>
|
|
|
|
#include "dive.h"
|
|
#include "device.h"
|
|
#include "errorhelper.h"
|
|
#include "gettext.h"
|
|
#include "divelist.h"
|
|
#include "file.h"
|
|
#include "libdivecomputer.h"
|
|
#include "divesite.h"
|
|
#include "equipment.h"
|
|
#include "divelog.h"
|
|
#include "tag.h"
|
|
|
|
extern dc_status_t dt_libdc_buffer(unsigned char *ptr, int prf_length, int dc_model, unsigned char *compl_buffer);
|
|
|
|
/*
|
|
* dc model definitions
|
|
*/
|
|
#define SMARTPRO 0x10
|
|
#define GALILEO 0x11
|
|
#define ALADINTEC 0x12
|
|
#define ALADINTEC2G 0x13
|
|
#define SMARTCOM 0x14
|
|
#define ALADIN2G 0x15
|
|
#define ALADINSPORTMATRIX 0x17
|
|
#define SMARTTEC 0x18
|
|
#define GALILEOTRIMIX 0x19
|
|
#define SMARTZ 0x1C
|
|
#define MERIDIAN 0x20
|
|
#define ALADINSQUARE 0x22
|
|
#define CHROMIS 0x24
|
|
#define ALADINA1 0x25
|
|
#define MANTIS2 0x26
|
|
#define ALADINA2 0x28
|
|
#define G2TEK 0x31
|
|
#define G2 0x32
|
|
#define G3 0x34
|
|
#define G2HUD 0x42
|
|
#define LUNA2AI 0x50
|
|
#define LUNA2 0x51
|
|
|
|
/*
|
|
* Data positions in serial stream as expected by libdc
|
|
*/
|
|
#define LIBDC_MAX_DEPTH 22
|
|
#define LIBDC_DIVE_TIME 26
|
|
#define LIBDC_MAX_TEMP 28
|
|
#define LIBDC_MIN_TEMP 30
|
|
#define LIBDC_SURF_TEMP 32
|
|
#define LIBDC_GASMIX 44
|
|
#define LIBDC_TANK_PRESS 50
|
|
#define LIBDC_SETTINGS 92
|
|
#define LIBDC_MAX_DEPTH_SMART 18
|
|
#define LIBDC_DIVE_TIME_SMART 20
|
|
#define LIBDC_MIN_TEMP_SMART 22
|
|
#define LIBDC_GASMIX_SMART 24
|
|
#define LIBDC_GASMIX_SMARTZ 28
|
|
#define LIBDC_TANK_PRESS_SMARTZ 34
|
|
#define LIBDC_TANK_PRESS_SMARTCOM 30
|
|
#define LIBDC_DIVE_TIME_ALADINTEC 24
|
|
#define LIBDC_SETTINGS_ALADINTEC 52
|
|
#define LIBDC_MIN_TEMP_ALADINTEC 26
|
|
#define LIBDC_GASMIX_ALADINTEC 30
|
|
#define LIBDC_GASMIX_ALADINTEC2G 34
|
|
#define LIBDC_SETTINGS_ALADINTEC2G 60
|
|
#define LIBDC_SETTINGS_TRIMIX 68
|
|
|
|
#define LIBDC_SAMPLES_MANTIS 152
|
|
#define LIBDC_SAMPLES_G2 84
|
|
#define LIBDC_SAMPLES_ALADINTEC 108
|
|
#define LIBDC_SAMPLES_ALADINTEC2G 116
|
|
#define LIBDC_SAMPLES_SMART 92
|
|
#define LIBDC_SAMPLES_SMARTCOM 100
|
|
#define LIBDC_SAMPLES_SMARTZ 132
|
|
|
|
/*
|
|
* Data positions in asd raw data buffer passed to build_dc_data().
|
|
* There are 12 more bytes which include 2bytes for dc model and 4bytes for
|
|
* device serial number.
|
|
*/
|
|
#define ASD_SAMPLES 183
|
|
#define ASD_DIVE_TIME 32
|
|
#define ASD_MAX_TEMP 21
|
|
#define ASD_MIN_TEMP 34
|
|
#define ASD_SURF_TEMP 18
|
|
#define ASD_GAS_MIX 36 // 2 bytes. Should be x3 at least. Assume consecutive.
|
|
#define ASD_MAXDEPTH 30
|
|
#define ASD_SETTINGS 70 // Get 4 bytes although only 2 are really needed
|
|
#define ASD_TANK_PRESS_INIT 42
|
|
#define ASD_TANK_PRESS_END 44
|
|
|
|
#define G2_MAX_TEMP 148
|
|
|
|
/*
|
|
* Returns a dc_descriptor_t structure based on dc model's number.
|
|
* This ensures the model pased to libdc_buffer_parser() is a supported model and avoids
|
|
* problems with shared model num devices by taking the family into account.
|
|
* AFAIK only DC_FAMILY_UWATEC_SMART is avaliable in libdc; UWATEC_MERIDIAN kept just in case ...
|
|
*/
|
|
extern "C" dc_descriptor_t *get_data_descriptor(int data_model, dc_family_t data_fam)
|
|
{
|
|
dc_descriptor_t *descriptor = NULL, *current = NULL;
|
|
dc_iterator_t *iterator = NULL;
|
|
dc_status_t rc;
|
|
|
|
rc = dc_descriptor_iterator(&iterator);
|
|
if (rc != DC_STATUS_SUCCESS) {
|
|
report_error("[libdc]\t\t\tCreating the device descriptor iterator.\n");
|
|
return current;
|
|
}
|
|
while ((dc_iterator_next(iterator, &descriptor)) == DC_STATUS_SUCCESS) {
|
|
int desc_model = dc_descriptor_get_model(descriptor);
|
|
dc_family_t desc_fam = dc_descriptor_get_type(descriptor);
|
|
|
|
if (data_model == desc_model && (data_fam == desc_fam)) {
|
|
current = descriptor;
|
|
break;
|
|
}
|
|
dc_descriptor_free(descriptor);
|
|
}
|
|
dc_iterator_free(iterator);
|
|
return current;
|
|
}
|
|
|
|
/*
|
|
* Fills a device_data_t structure to pass to libdivecomputer.
|
|
* Detects if a device is supported or not.
|
|
*/
|
|
extern "C" int prepare_data(int data_model, dc_family_t dc_fam, device_data_t *dev_data)
|
|
{
|
|
dev_data->device = NULL;
|
|
dev_data->context = NULL;
|
|
dev_data->descriptor = get_data_descriptor(data_model, dc_fam);
|
|
if (dev_data->descriptor) {
|
|
dev_data->vendor = dc_descriptor_get_vendor(dev_data->descriptor);
|
|
dev_data->product = dc_descriptor_get_product(dev_data->descriptor);
|
|
std::string tmp (dev_data->vendor);
|
|
tmp += " ";
|
|
tmp += dev_data->product;
|
|
dev_data->model = tmp;
|
|
return DC_STATUS_SUCCESS;
|
|
} else {
|
|
report_info("Warning [prepare_data]: Unsupported DC model 0x%2x\n", data_model);
|
|
return DC_STATUS_UNSUPPORTED;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ASD provides the size of the buffer ... well, most times. Sometimes it's not the full buffer
|
|
* size, but just the samples size. I've been unable to find a pattern for this (although samples
|
|
* size seems to be limited to galileo devices coming from SmartTrak exported dives). Thus, we can't
|
|
* trust this data and we need to calculate the correct buffer size, this being the previously found
|
|
* size of the ASD dive data minus the ASD header plus the device header.
|
|
* The default option in the switch conditional shouldn't be reached. If so, we have forgotten to add
|
|
* some device.
|
|
*/
|
|
extern "C" unsigned char *allocate_libdc_buffer(int model, int asd_size, int *size)
|
|
{
|
|
unsigned char *buf;
|
|
int sample_size = asd_size - ASD_SAMPLES;
|
|
|
|
switch (model) {
|
|
case GALILEO:
|
|
case ALADIN2G:
|
|
case MERIDIAN:
|
|
case CHROMIS:
|
|
case MANTIS2:
|
|
case ALADINSQUARE:
|
|
*size = sample_size + LIBDC_SAMPLES_MANTIS;
|
|
break;
|
|
case GALILEOTRIMIX:
|
|
case G2:
|
|
case G2HUD:
|
|
case G2TEK:
|
|
case G3:
|
|
case ALADINSPORTMATRIX:
|
|
case ALADINA1:
|
|
case ALADINA2:
|
|
case LUNA2AI:
|
|
case LUNA2:
|
|
*size = sample_size + LIBDC_SAMPLES_G2;
|
|
break;
|
|
case ALADINTEC2G:
|
|
*size = sample_size + LIBDC_SAMPLES_ALADINTEC2G;
|
|
break;
|
|
case ALADINTEC:
|
|
*size = sample_size + LIBDC_SAMPLES_ALADINTEC;
|
|
break;
|
|
case SMARTPRO:
|
|
*size = sample_size + LIBDC_SAMPLES_SMART;
|
|
break;
|
|
case SMARTCOM:
|
|
*size = sample_size + LIBDC_SAMPLES_SMARTCOM;
|
|
break;
|
|
default:
|
|
report_error("[allocate_libdc_buffer]: Unsupported model 0x%.2X\n", model);
|
|
return NULL;
|
|
}
|
|
buf = (unsigned char *)calloc(*size, 1);
|
|
if (!buf)
|
|
report_error("[allocate_libdc_buffer]: Failed to place enough memory for libdc buffer. %d bytes.\n", *size);
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
* This function returns an allocated memory buffer with the completed dc data.
|
|
* The buffer has to be padded in the beginning with the header libdivecomputer expects to find,
|
|
* this is, a5a55a5a following two bytes with the buffer size.
|
|
* BTW we need to manually relocate those parts of the header needed by libdivecomputer. The
|
|
* rest of the buffer is filled with the samples.
|
|
* For older serial Aladin DCs, just use dt_libdc_buffer() from datatrak.cpp importer.
|
|
*/
|
|
extern "C" unsigned char *build_dc_data(int model, dc_family_t dc_fam, char *input, int max, int *out_size)
|
|
{
|
|
unsigned char *ptr = (unsigned char*) input;
|
|
unsigned char *buffer;
|
|
const unsigned char head_begin[] = {0xa5, 0xa5, 0x5a, 0x5a};
|
|
int buf_size = 0;
|
|
|
|
// Older serial Aladin profile imported to
|
|
// SmarTrak from Datatrak
|
|
if (dc_fam == DC_FAMILY_UWATEC_ALADIN) {
|
|
*out_size = max + 18;
|
|
buffer = (unsigned char *)calloc(*out_size, 1);
|
|
dt_libdc_buffer(ptr, max, model, buffer);
|
|
return buffer;
|
|
}
|
|
|
|
buffer = allocate_libdc_buffer(model, max, &buf_size);
|
|
if (!buffer)
|
|
return NULL; // we are OOM or with unknown DC
|
|
*out_size = buf_size;
|
|
memcpy(buffer, &head_begin, 4); // place header begining
|
|
buffer[4] = buf_size & 0xFF; // calculated buffer size
|
|
buffer[5] = buf_size >> 8;
|
|
memcpy(buffer + 6, ptr + 2, 11); // initial block (unchanged)
|
|
|
|
switch (model) {
|
|
case GALILEO: // untested with LogTrak native dives and somehow faulty
|
|
case GALILEOTRIMIX:
|
|
memcpy(buffer + LIBDC_MAX_DEPTH, ptr + ASD_MAXDEPTH, 2);
|
|
memcpy(buffer + LIBDC_DIVE_TIME, ptr + ASD_DIVE_TIME, 2);
|
|
memcpy(buffer + LIBDC_MAX_TEMP, ptr + ASD_MAX_TEMP, 2);
|
|
memcpy(buffer + LIBDC_MIN_TEMP, ptr + ASD_MIN_TEMP, 2);
|
|
memcpy(buffer + LIBDC_SURF_TEMP, ptr + ASD_SURF_TEMP, 2);
|
|
memcpy(buffer + LIBDC_GASMIX, ptr + ASD_GAS_MIX, 2);
|
|
memcpy(buffer + LIBDC_TANK_PRESS, ptr + ASD_TANK_PRESS_INIT, 2);
|
|
if (model == GALILEOTRIMIX)
|
|
buffer[43] = 0x80; // libdc checks this byte to apply trimix's parsing model or galileo's
|
|
(model == GALILEO) ? memcpy(buffer + LIBDC_SETTINGS, ptr + ASD_SETTINGS, 4) : memcpy(buffer + LIBDC_SETTINGS_TRIMIX, ptr + ASD_SETTINGS, 4);
|
|
(model == GALILEO) ? memcpy(buffer + LIBDC_SAMPLES_MANTIS, ptr + ASD_SAMPLES, max - ASD_SAMPLES) : memcpy(buffer + LIBDC_SAMPLES_G2, ptr + ASD_SAMPLES, max - ASD_SAMPLES);
|
|
break;
|
|
case ALADINTEC2G:
|
|
memcpy(buffer + LIBDC_MAX_DEPTH_SMART, ptr + ASD_MAXDEPTH, 2);
|
|
memcpy(buffer + LIBDC_DIVE_TIME_SMART, ptr + ASD_DIVE_TIME, 2);
|
|
memcpy(buffer + LIBDC_MAX_TEMP, ptr + ASD_MAX_TEMP, 2);
|
|
memcpy(buffer + LIBDC_MIN_TEMP_ALADINTEC, ptr + ASD_MIN_TEMP, 2);
|
|
memcpy(buffer + LIBDC_SURF_TEMP, ptr + ASD_SURF_TEMP, 2);
|
|
memcpy(buffer + LIBDC_GASMIX_ALADINTEC2G, ptr + ASD_GAS_MIX, 6);
|
|
memcpy(buffer + LIBDC_SETTINGS_ALADINTEC2G, ptr + ASD_SETTINGS, 4);
|
|
memcpy(buffer + LIBDC_SAMPLES_ALADINTEC2G, ptr + ASD_SAMPLES, max - ASD_SAMPLES);
|
|
break;
|
|
case ALADIN2G: // tested Logtrak native Aladin 2G and Mantis 2
|
|
case MERIDIAN:
|
|
case CHROMIS:
|
|
case MANTIS2:
|
|
case ALADINSQUARE:
|
|
memcpy(buffer + LIBDC_MAX_DEPTH, ptr + ASD_MAXDEPTH, 2);
|
|
memcpy(buffer + LIBDC_DIVE_TIME, ptr + ASD_DIVE_TIME, 2);
|
|
memcpy(buffer + LIBDC_MAX_TEMP, ptr + ASD_MAX_TEMP, 2);
|
|
memcpy(buffer + LIBDC_MIN_TEMP, ptr + ASD_MIN_TEMP, 2);
|
|
memcpy(buffer + LIBDC_SURF_TEMP, ptr + ASD_SURF_TEMP, 2);
|
|
memcpy(buffer + LIBDC_GASMIX, ptr + ASD_GAS_MIX, 2);
|
|
memcpy(buffer + LIBDC_TANK_PRESS, ptr + ASD_TANK_PRESS_INIT, 2);
|
|
memcpy(buffer + LIBDC_SETTINGS, ptr + ASD_SETTINGS, 4);
|
|
memcpy(buffer + LIBDC_SAMPLES_MANTIS, ptr + ASD_SAMPLES, max - ASD_SAMPLES);
|
|
break;
|
|
case G2: // only G2 tested, bold assumption
|
|
case G2HUD:
|
|
case G2TEK:
|
|
case G3:
|
|
case ALADINSPORTMATRIX:
|
|
case ALADINA1:
|
|
case ALADINA2:
|
|
case LUNA2AI:
|
|
case LUNA2:
|
|
memcpy(buffer + LIBDC_MAX_DEPTH, ptr + ASD_MAXDEPTH, 2);
|
|
memcpy(buffer + LIBDC_DIVE_TIME, ptr + ASD_DIVE_TIME, 2);
|
|
memcpy(buffer + LIBDC_MAX_TEMP, ptr + G2_MAX_TEMP, 2);
|
|
memcpy(buffer + LIBDC_MIN_TEMP, ptr + ASD_MIN_TEMP, 2);
|
|
memcpy(buffer + LIBDC_SURF_TEMP, ptr + ASD_SURF_TEMP, 2);
|
|
memcpy(buffer + LIBDC_SETTINGS_TRIMIX, ptr + ASD_SETTINGS, 4);
|
|
memcpy(buffer + LIBDC_SAMPLES_G2, ptr + ASD_SAMPLES, max - ASD_SAMPLES);
|
|
break;
|
|
case ALADINTEC:
|
|
memcpy(buffer + LIBDC_MAX_DEPTH, ptr + ASD_MAXDEPTH, 2);
|
|
memcpy(buffer + LIBDC_DIVE_TIME_ALADINTEC, ptr + ASD_DIVE_TIME, 2);
|
|
memcpy(buffer + LIBDC_MAX_TEMP, ptr + ASD_SURF_TEMP, 2);
|
|
memcpy(buffer + LIBDC_MIN_TEMP_ALADINTEC, ptr + ASD_MIN_TEMP, 2);
|
|
memcpy(buffer + LIBDC_GASMIX_ALADINTEC, ptr + ASD_GAS_MIX, 2);
|
|
memcpy(buffer + LIBDC_SETTINGS_ALADINTEC, ptr + ASD_SETTINGS, 4);
|
|
memcpy(buffer + LIBDC_SAMPLES_ALADINTEC, ptr + ASD_SAMPLES, max - ASD_SAMPLES);
|
|
break;
|
|
case SMARTPRO:
|
|
memcpy(buffer + LIBDC_MAX_DEPTH_SMART, ptr + ASD_MAXDEPTH, 2);
|
|
memcpy(buffer + LIBDC_DIVE_TIME_SMART, ptr + ASD_DIVE_TIME, 2);
|
|
memcpy(buffer + LIBDC_MIN_TEMP_SMART, ptr + ASD_MIN_TEMP, 2);
|
|
memcpy(buffer + LIBDC_GASMIX_SMART, ptr + ASD_GAS_MIX, 2);
|
|
memcpy(buffer + LIBDC_SAMPLES_SMART, ptr + ASD_SAMPLES, max - ASD_SAMPLES);
|
|
break;
|
|
case SMARTCOM:
|
|
memcpy(buffer + LIBDC_MAX_DEPTH_SMART, ptr + ASD_MAXDEPTH, 2);
|
|
memcpy(buffer + LIBDC_DIVE_TIME_SMART, ptr + ASD_DIVE_TIME, 2);
|
|
memcpy(buffer + LIBDC_MIN_TEMP_SMART, ptr + ASD_MIN_TEMP, 2);
|
|
memcpy(buffer + LIBDC_GASMIX_SMART, ptr + ASD_GAS_MIX, 2);
|
|
memcpy(buffer + LIBDC_TANK_PRESS_SMARTCOM, ptr + ASD_TANK_PRESS_INIT, 4);
|
|
memcpy(buffer + LIBDC_SAMPLES_SMARTCOM, ptr + ASD_SAMPLES, max - ASD_SAMPLES);
|
|
break;
|
|
default:
|
|
report_error("Unsupported DC model 0x%2x\n", model);
|
|
free(buffer);
|
|
buffer = NULL;
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
/*
|
|
* Search for a given sequence of bytes in a string.
|
|
* Returns the position of the first byte of the sequence or string::npos if
|
|
* not found.
|
|
* Parameter s is the size of the sequence.
|
|
*/
|
|
|
|
std::size_t uchar_find(const std::string &in, const unsigned char *seq, const int s)
|
|
{
|
|
return in.find(std::string(reinterpret_cast<const char *>(seq), s));
|
|
}
|
|
|
|
/*
|
|
* Return a utf-8 string from an .asd string.
|
|
* String fields in .asd file begin with a 3 bytes BOM followed by a 1 byte
|
|
* number, e.g. FF FE FF nn, where nn is the length of string following,
|
|
* in chars (two bytes unicode chars). May be zero, meaning an empty field.
|
|
* If anything fails the pointer to next field will be set to "", signaling
|
|
* a weirdness. The caller must handle it (aborting seems preferable as we can
|
|
* have data corruption issues).
|
|
*/
|
|
std::string asd_to_string(const std::string &input, std::string &output)
|
|
{
|
|
if (input.empty()) {
|
|
output = input;
|
|
return input;
|
|
}
|
|
|
|
const auto tmp = reinterpret_cast<const unsigned char *>(input.data()) ;
|
|
const int size = tmp[3] * 2; // worst case, all chars are 2 bytes long
|
|
|
|
// this is not a failure but an empty string
|
|
if (size == 0) {
|
|
output = input.substr(4);
|
|
return "";
|
|
}
|
|
|
|
// convert string to UTF-8
|
|
std::vector<char> buffer(size, 0);
|
|
for (int i = 0, j = 0; i < size; i += 2, ++j) {
|
|
const unsigned char c = (tmp[i + 5] << 4) + tmp[i + 4]; // 4 is the BOM size
|
|
if (c <= 0x7F) {
|
|
buffer[j] = c;
|
|
} else {
|
|
buffer[j] = (c >> 6) | 0xC0;
|
|
buffer[++j] = (c & 0x3F) | 0x80;
|
|
}
|
|
}
|
|
output = input.substr(size + 4);
|
|
|
|
buffer.erase(std::remove(buffer.begin(), buffer.end(), 0), buffer.end());
|
|
const std::string ret = std::string(buffer.begin(), buffer.end());
|
|
// drop meaningless strings comming from SmartTrak if any
|
|
return ret.compare(" ") && ret.compare("-") && ret.compare("???") && ret.compare("---") ? ret : "";
|
|
}
|
|
|
|
/*
|
|
* Build a dive site with coords and name if it doesn't exist yet and place it in the table.
|
|
*/
|
|
static void asd_build_dive_site(const std::string &instring, const std::string &coords, struct divelog *log, struct dive_site **asd_site)
|
|
{
|
|
struct dive_site *site;
|
|
double gpsX = 0, gpsY = 0;
|
|
location_t gps_loc;
|
|
|
|
if (!coords.empty())
|
|
sscanf(coords.data(), "%lf %lf", &gpsX, &gpsY);
|
|
|
|
gps_loc = create_location(gpsX, gpsY);
|
|
site = log->sites.get_by_name(instring);
|
|
if (!site) {
|
|
if (!has_location(&gps_loc))
|
|
site = log->sites.create(instring);
|
|
else
|
|
site = log->sites.create(instring, gps_loc);
|
|
}
|
|
*asd_site = site;
|
|
}
|
|
|
|
/*
|
|
* Check if file is an export from SmartTrak divelog.
|
|
* ASD files have a unicode string at the beginning with some info, including
|
|
* the string "SmartTRAK" if generated with this software,
|
|
*/
|
|
bool is_smtk(const std::string &p)
|
|
{
|
|
const unsigned char smart[10] = {0x53,0x00,0x6d,0x00,0x61,0x00,0x72,0x00,0x74,0x00};
|
|
return uchar_find(p, smart, 10) < p.find("CLogEntry");
|
|
}
|
|
|
|
/*
|
|
* A block of buddies or equipment consist of a integer n (2 bytes big endian) followed by
|
|
* n asd strings, n = 0 meaning no strings follow. Caller func passes n (if n > 0) and points
|
|
* to the beginning of strings. Buddies are placed in dive buddies list and equipment in a string,
|
|
* returning a pointer to next byte after the parsed block.
|
|
* These are only used in SmartTrak exported .asd files, as LogTrak doesn't support such fields.
|
|
*/
|
|
std::string asd_build_others(const std::string &input, int idx, std::string &output)
|
|
{
|
|
std::string head;
|
|
std::string ptr = input;
|
|
int i = idx;
|
|
|
|
if (input.empty()) {
|
|
output = "";
|
|
return "";
|
|
}
|
|
|
|
while (i > 0) {
|
|
std::string tail = asd_to_string(ptr, ptr);
|
|
if (! head.empty())
|
|
head += ", ";
|
|
head += tail;
|
|
i--;
|
|
}
|
|
output = head;
|
|
return ptr;
|
|
}
|
|
|
|
/*
|
|
* Parse a dive in a mem buffer and return 0 for correct ending or negative
|
|
* values signaling an error. -1 would be a recoverable error and -2
|
|
* a weird issue (abort further parsing would be preferred).
|
|
*/
|
|
static int asd_dive_parser(const std::string &input, struct dive *asd_dive, struct divelog *log)//, const device_table &devices)
|
|
{
|
|
int dc_model, s = 0, rc = 0, k, j;
|
|
dc_family_t dc_fam;
|
|
long dc_serial;
|
|
const unsigned char str_seq[] = {0xff, 0xfe, 0xff}, dc_profile_begin[4] = {0x01, 0x00, 0x00, 0xFF};
|
|
unsigned char *dc_data;
|
|
auto devdata = std::make_unique<device_data_t>();
|
|
devdata->log = log;
|
|
asd_dive->dcs[0].serial.resize(64);
|
|
weightsystem_t ws;
|
|
std::string tmp, d_locat, d_point, d_coords, notes, viz, w_type, w_surf, weather, buddies, equipment;
|
|
std::size_t size;
|
|
|
|
//input should point to dc model integer
|
|
dc_model = input[0];
|
|
dc_fam = (input[1] > 0x00) ? DC_FAMILY_UWATEC_ALADIN : DC_FAMILY_UWATEC_SMART;
|
|
tmp = input.substr(8);
|
|
|
|
dc_serial = (tmp[3] << 24) + (tmp[2] << 16) + (tmp[1] << 8) + tmp[0];
|
|
|
|
if (!dc_model) { // this would be the manual dive case (coming from SmartTrak)
|
|
asd_dive->dcs[0].model = "Manually entered dive";
|
|
} else {
|
|
rc = prepare_data(dc_model, dc_fam, devdata.get());
|
|
if (rc != DC_STATUS_SUCCESS)
|
|
goto bailout;
|
|
asd_dive->dcs[0].model = devdata->model;
|
|
tmp = tmp.substr(4);
|
|
|
|
if (dc_fam == DC_FAMILY_UWATEC_ALADIN) {
|
|
std::size_t pos = uchar_find(tmp, dc_profile_begin, 4);
|
|
tmp = tmp.substr(pos + 4);
|
|
}
|
|
size = uchar_find(tmp, str_seq, 3); // size of DC data
|
|
dc_data = build_dc_data(dc_model, dc_fam, tmp.data(), size, &s);
|
|
if (!dc_data)
|
|
goto bailout;
|
|
|
|
rc = libdc_buffer_parser(asd_dive, devdata.get(), dc_data, s);
|
|
free(dc_data);
|
|
if (rc != DC_STATUS_SUCCESS)
|
|
goto bailout;
|
|
// set serial in DC info, and set a node for the device.
|
|
asd_dive->dcs[0].serial = std::to_string(dc_serial);
|
|
create_device_node(log->devices, asd_dive->dcs[0].model, asd_dive->dcs[0].serial, asd_dive->dcs[0].model);
|
|
|
|
// Now the non DC data fields.
|
|
tmp = tmp.substr(size);
|
|
}
|
|
|
|
// There are no string fields merged with string ones, making things a bit more dificult.
|
|
d_locat = asd_to_string(tmp, tmp);
|
|
if (tmp.empty())
|
|
goto buffail;
|
|
|
|
d_point = asd_to_string(tmp, tmp);
|
|
if (tmp.empty())
|
|
goto buffail;
|
|
|
|
if (!d_point.empty() && d_locat.compare(d_point))
|
|
d_locat += ", " + d_point;
|
|
d_coords = asd_to_string(tmp, tmp);
|
|
if (tmp.empty())
|
|
goto buffail;
|
|
|
|
asd_build_dive_site(d_locat, d_coords, log, &asd_dive->dive_site);
|
|
// next two bytes are the tank volume (mililiters) and two following are
|
|
// unknown (always zero, may be a 2º tank).
|
|
asd_dive->get_or_create_cylinder(0)->type.size.mliter = (tmp[1] << 8) + tmp[0];
|
|
tmp = tmp.substr(4);
|
|
asd_dive->get_cylinder(0)->type.description = asd_to_string(tmp, tmp);
|
|
if (tmp.empty())
|
|
goto buffail;
|
|
|
|
asd_dive->suit = asd_to_string(tmp, tmp);
|
|
if (tmp.empty())
|
|
goto buffail;
|
|
|
|
// next two bytes are the weight in gr. Two following are zeroed.
|
|
ws = { { .grams = (tmp[1] << 8) + tmp[0] }, translate("gettextFromC", "unknown"), false };
|
|
asd_dive->weightsystems.push_back(std::move(ws));
|
|
tmp = tmp.substr(4);
|
|
|
|
// weather
|
|
weather = asd_to_string(tmp, tmp);
|
|
if (!weather.empty())
|
|
taglist_add_tag(asd_dive->tags, weather);
|
|
if (tmp.empty())
|
|
goto buffail;
|
|
|
|
// water surface
|
|
w_surf = asd_to_string(tmp,tmp);
|
|
if (!w_surf.empty())
|
|
taglist_add_tag(asd_dive->tags, w_surf);
|
|
if (tmp.empty())
|
|
goto buffail;
|
|
|
|
// water type (localized string; can't be used for salinity)
|
|
w_type = asd_to_string(tmp, tmp);
|
|
if (!w_type.empty())
|
|
taglist_add_tag(asd_dive->tags, w_type);
|
|
if (tmp.empty())
|
|
goto buffail;
|
|
|
|
// visibility (localized string; can't be used for rating viz)
|
|
viz = asd_to_string(tmp, tmp);
|
|
if (!viz.empty())
|
|
taglist_add_tag(asd_dive->tags, viz);
|
|
if (tmp.empty())
|
|
goto buffail;
|
|
|
|
tmp = tmp.substr(2);
|
|
// Nº of buddies strings following
|
|
j = (tmp[1] << 8 ) + tmp[0];
|
|
tmp = tmp.substr(2);
|
|
if (j > 0) {
|
|
tmp = asd_build_others(tmp, j, buddies);
|
|
asd_dive->buddy = buddies;
|
|
}
|
|
if (tmp.empty())
|
|
goto buffail;
|
|
|
|
// Nº of equipment items strings following
|
|
k = (tmp[1] << 8 ) + tmp[0];
|
|
tmp = tmp.substr(2);
|
|
if (k > 0)
|
|
tmp = asd_build_others(tmp, k, equipment);
|
|
if (tmp.empty())
|
|
goto buffail;
|
|
|
|
// Notes if any. Include equipment in notes.
|
|
notes = asd_to_string(tmp, tmp);
|
|
if (! equipment.empty()) {
|
|
if (notes.size() > 0) {
|
|
notes += "\n";
|
|
}
|
|
notes += (translate("gettextFromC", "Equipment: ") + equipment);
|
|
}
|
|
asd_dive->notes = notes;
|
|
if (tmp.empty())
|
|
goto buffail;
|
|
|
|
return 0;
|
|
bailout: // give a pointer, we can parse other dives yet
|
|
report_error("Warning [asd_dive_parser]: Non critical error in dive %d. Keep on parsing next dive.\n", asd_dive->number);
|
|
return -1;
|
|
buffail: // quit caller func, something weird is going on
|
|
report_error("[asd_dive_parser()]\tMemory corruption or file damaged. Dive %d\n", asd_dive->number);
|
|
return -2;
|
|
}
|
|
|
|
/*
|
|
* Main function
|
|
*/
|
|
int scubapro_asd_import(const std::string &mem, struct divelog *log)
|
|
{
|
|
std::string runner;
|
|
const char init_seq[4] = {0x07, 0x00, 0x10, 0x00};
|
|
std::string first_dive_seq = {"CLogEntry"};
|
|
unsigned char dive_seq_lt[4] = {0x00, 0x00, 0x80, 0x01};
|
|
unsigned char dive_seq_smtk[4] = {0x00, 0x00, 0x01, 0x80};
|
|
const unsigned char *dive_seq = dive_seq_lt;
|
|
int dive_count = 0;
|
|
|
|
setlocale(LC_NUMERIC, "POSIX");
|
|
setlocale(LC_CTYPE, "");
|
|
|
|
// check header
|
|
if (! mem.compare(0, 4, init_seq)) {
|
|
report_error("This doesn't look like an .asd file. Please check it.\n");
|
|
return 1;
|
|
}
|
|
// is this .asd coming from SmartTrak ?
|
|
if (is_smtk(mem))
|
|
dive_seq = dive_seq_smtk;
|
|
|
|
// Jump to initial dive data secuence
|
|
std::size_t pos = mem.find(first_dive_seq);
|
|
if (pos != std::string::npos) {
|
|
runner = mem.substr(pos + 9);
|
|
} else {
|
|
report_error("This doesn't look like an .asd file. Please check it.\n");
|
|
return 1;
|
|
}
|
|
|
|
// We are on the first byte of the first dive (DC model). Subsequent dives in log
|
|
// will begin with 0x80 0x01 or 0x01 0x80 depending on original software.
|
|
while (pos < std::string::npos) {
|
|
auto asd_dive = std::make_unique<dive>();
|
|
dive_count++;
|
|
asd_dive->number = dive_count;
|
|
pos = 0;
|
|
int rc = asd_dive_parser(runner, asd_dive.get(), log);
|
|
switch (rc) {
|
|
case 0: {
|
|
log->dives.record_dive(std::move(asd_dive));
|
|
break;
|
|
}
|
|
case -1: { // recoverable error
|
|
report_error("Error parsing dive %d\n", dive_count);
|
|
break;
|
|
}
|
|
case -2: { // no recoverable error
|
|
report_error("Error parsing dive %d\n", dive_count);
|
|
return 1;
|
|
}
|
|
}
|
|
// Look for next dive and jump to it
|
|
pos = uchar_find(runner, dive_seq, 4);
|
|
runner = runner.substr(pos + 4);
|
|
};
|
|
log->dives.sort();
|
|
return 0;
|
|
}
|