For freedives it makes no sense to report any gas mixes. The freedives also use a different sample format, which doesn't generate any gas change events.
1107 lines
34 KiB
C
1107 lines
34 KiB
C
/*
|
||
* libdivecomputer
|
||
*
|
||
* Copyright (C) 2012 Jef Driesen
|
||
*
|
||
* This library is free software; you can redistribute it and/or
|
||
* modify it under the terms of the GNU Lesser General Public
|
||
* License as published by the Free Software Foundation; either
|
||
* version 2.1 of the License, or (at your option) any later version.
|
||
*
|
||
* This library is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
* Lesser General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU Lesser General Public
|
||
* License along with this library; if not, write to the Free Software
|
||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||
* MA 02110-1301 USA
|
||
*/
|
||
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
|
||
#include <libdivecomputer/units.h>
|
||
|
||
#include "shearwater_predator.h"
|
||
#include "shearwater_petrel.h"
|
||
#include "context-private.h"
|
||
#include "parser-private.h"
|
||
#include "array.h"
|
||
|
||
#define ISINSTANCE(parser) ( \
|
||
dc_parser_isinstance((parser), &shearwater_predator_parser_vtable) || \
|
||
dc_parser_isinstance((parser), &shearwater_petrel_parser_vtable))
|
||
|
||
#define LOG_RECORD_DIVE_SAMPLE 0x01
|
||
#define LOG_RECORD_FREEDIVE_SAMPLE 0x02
|
||
#define LOG_RECORD_OPENING_0 0x10
|
||
#define LOG_RECORD_OPENING_1 0x11
|
||
#define LOG_RECORD_OPENING_2 0x12
|
||
#define LOG_RECORD_OPENING_3 0x13
|
||
#define LOG_RECORD_OPENING_4 0x14
|
||
#define LOG_RECORD_OPENING_5 0x15
|
||
#define LOG_RECORD_OPENING_6 0x16
|
||
#define LOG_RECORD_OPENING_7 0x17
|
||
#define LOG_RECORD_CLOSING_0 0x20
|
||
#define LOG_RECORD_CLOSING_1 0x21
|
||
#define LOG_RECORD_CLOSING_2 0x22
|
||
#define LOG_RECORD_CLOSING_3 0x23
|
||
#define LOG_RECORD_CLOSING_4 0x24
|
||
#define LOG_RECORD_CLOSING_5 0x25
|
||
#define LOG_RECORD_CLOSING_6 0x26
|
||
#define LOG_RECORD_CLOSING_7 0x27
|
||
#define LOG_RECORD_INFO_EVENT 0x30
|
||
#define LOG_RECORD_DIVE_SAMPLE_EXT 0xE1
|
||
#define LOG_RECORD_FINAL 0xFF
|
||
|
||
#define INFO_EVENT_TAG_LOG 38
|
||
|
||
#define SZ_BLOCK 0x80
|
||
#define SZ_SAMPLE_PREDATOR 0x10
|
||
#define SZ_SAMPLE_PETREL 0x20
|
||
#define SZ_SAMPLE_FREEDIVE 0x08
|
||
|
||
#define GASSWITCH 0x01
|
||
#define PPO2_EXTERNAL 0x02
|
||
#define SETPOINT_HIGH 0x04
|
||
#define SC 0x08
|
||
#define OC 0x10
|
||
|
||
#define M_CC 0
|
||
#define M_OC_TEC 1
|
||
#define M_GAUGE 2
|
||
#define M_PPO2 3
|
||
#define M_SC 4
|
||
#define M_CC2 5
|
||
#define M_OC_REC 6
|
||
#define M_FREEDIVE 7
|
||
|
||
#define AI_OFF 0
|
||
#define AI_HPCCR 4
|
||
#define AI_ON 5
|
||
|
||
#define GF 0
|
||
#define VPMB 1
|
||
#define VPMB_GFS 2
|
||
#define DCIEM 3
|
||
|
||
#define METRIC 0
|
||
#define IMPERIAL 1
|
||
|
||
#define NGASMIXES 20
|
||
#define NFIXED 10
|
||
#define NTANKS 6
|
||
#define NRECORDS 8
|
||
|
||
#define PREDATOR 2
|
||
#define PETREL 3
|
||
#define TERIC 8
|
||
|
||
#define UNDEFINED 0xFFFFFFFF
|
||
|
||
typedef struct shearwater_predator_parser_t shearwater_predator_parser_t;
|
||
|
||
typedef struct shearwater_predator_gasmix_t {
|
||
unsigned int oxygen;
|
||
unsigned int helium;
|
||
} shearwater_predator_gasmix_t;
|
||
|
||
typedef struct shearwater_predator_tank_t {
|
||
unsigned int enabled;
|
||
unsigned int active;
|
||
unsigned int beginpressure;
|
||
unsigned int endpressure;
|
||
unsigned int pressure_max;
|
||
unsigned int pressure_reserve;
|
||
unsigned int serial;
|
||
char name[2];
|
||
} shearwater_predator_tank_t;
|
||
|
||
struct shearwater_predator_parser_t {
|
||
dc_parser_t base;
|
||
unsigned int model;
|
||
unsigned int petrel;
|
||
unsigned int samplesize;
|
||
// Cached fields.
|
||
unsigned int cached;
|
||
unsigned int pnf;
|
||
unsigned int logversion;
|
||
unsigned int headersize;
|
||
unsigned int footersize;
|
||
unsigned int opening[NRECORDS];
|
||
unsigned int closing[NRECORDS];
|
||
unsigned int final;
|
||
unsigned int ngasmixes;
|
||
unsigned int ntanks;
|
||
shearwater_predator_gasmix_t gasmix[NGASMIXES];
|
||
shearwater_predator_tank_t tank[NTANKS];
|
||
unsigned int tankidx[NTANKS];
|
||
unsigned int aimode;
|
||
unsigned int calibrated;
|
||
double calibration[3];
|
||
unsigned int divemode;
|
||
unsigned int units;
|
||
unsigned int atmospheric;
|
||
unsigned int density;
|
||
};
|
||
|
||
static dc_status_t shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size);
|
||
static dc_status_t shearwater_predator_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime);
|
||
static dc_status_t shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value);
|
||
static dc_status_t shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata);
|
||
|
||
static dc_status_t shearwater_predator_parser_cache (shearwater_predator_parser_t *parser);
|
||
|
||
static const dc_parser_vtable_t shearwater_predator_parser_vtable = {
|
||
sizeof(shearwater_predator_parser_t),
|
||
DC_FAMILY_SHEARWATER_PREDATOR,
|
||
shearwater_predator_parser_set_data, /* set_data */
|
||
NULL, /* set_clock */
|
||
NULL, /* set_atmospheric */
|
||
NULL, /* set_density */
|
||
shearwater_predator_parser_get_datetime, /* datetime */
|
||
shearwater_predator_parser_get_field, /* fields */
|
||
shearwater_predator_parser_samples_foreach, /* samples_foreach */
|
||
NULL /* destroy */
|
||
};
|
||
|
||
static const dc_parser_vtable_t shearwater_petrel_parser_vtable = {
|
||
sizeof(shearwater_predator_parser_t),
|
||
DC_FAMILY_SHEARWATER_PETREL,
|
||
shearwater_predator_parser_set_data, /* set_data */
|
||
NULL, /* set_clock */
|
||
NULL, /* set_atmospheric */
|
||
NULL, /* set_density */
|
||
shearwater_predator_parser_get_datetime, /* datetime */
|
||
shearwater_predator_parser_get_field, /* fields */
|
||
shearwater_predator_parser_samples_foreach, /* samples_foreach */
|
||
NULL /* destroy */
|
||
};
|
||
|
||
|
||
static unsigned int
|
||
shearwater_predator_find_gasmix (shearwater_predator_parser_t *parser, unsigned int o2, unsigned int he)
|
||
{
|
||
unsigned int i = 0;
|
||
while (i < parser->ngasmixes) {
|
||
if (o2 == parser->gasmix[i].oxygen && he == parser->gasmix[i].helium)
|
||
break;
|
||
i++;
|
||
}
|
||
|
||
return i;
|
||
}
|
||
|
||
|
||
static dc_status_t
|
||
shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model, unsigned int petrel)
|
||
{
|
||
shearwater_predator_parser_t *parser = NULL;
|
||
const dc_parser_vtable_t *vtable = NULL;
|
||
unsigned int samplesize = 0;
|
||
|
||
if (out == NULL)
|
||
return DC_STATUS_INVALIDARGS;
|
||
|
||
if (petrel) {
|
||
vtable = &shearwater_petrel_parser_vtable;
|
||
samplesize = SZ_SAMPLE_PETREL;
|
||
} else {
|
||
vtable = &shearwater_predator_parser_vtable;
|
||
samplesize = SZ_SAMPLE_PREDATOR;
|
||
}
|
||
|
||
// Allocate memory.
|
||
parser = (shearwater_predator_parser_t *) dc_parser_allocate (context, vtable);
|
||
if (parser == NULL) {
|
||
ERROR (context, "Failed to allocate memory.");
|
||
return DC_STATUS_NOMEMORY;
|
||
}
|
||
|
||
// Set the default values.
|
||
parser->model = model;
|
||
parser->petrel = petrel;
|
||
parser->samplesize = samplesize;
|
||
parser->cached = 0;
|
||
parser->pnf = 0;
|
||
parser->logversion = 0;
|
||
parser->headersize = 0;
|
||
parser->footersize = 0;
|
||
for (unsigned int i = 0; i < NRECORDS; ++i) {
|
||
parser->opening[i] = UNDEFINED;
|
||
parser->closing[i] = UNDEFINED;
|
||
}
|
||
parser->final = UNDEFINED;
|
||
parser->ngasmixes = 0;
|
||
for (unsigned int i = 0; i < NGASMIXES; ++i) {
|
||
parser->gasmix[i].oxygen = 0;
|
||
parser->gasmix[i].helium = 0;
|
||
}
|
||
parser->ntanks = 0;
|
||
for (unsigned int i = 0; i < NTANKS; ++i) {
|
||
parser->tank[i].enabled = 0;
|
||
parser->tank[i].active = 0;
|
||
parser->tank[i].beginpressure = 0;
|
||
parser->tank[i].endpressure = 0;
|
||
parser->tank[i].pressure_max = 0;
|
||
parser->tank[i].pressure_reserve = 0;
|
||
parser->tank[i].serial = 0;
|
||
memset (parser->tank[i].name, 0, sizeof (parser->tank[i].name));
|
||
parser->tankidx[i] = i;
|
||
}
|
||
parser->aimode = AI_OFF;
|
||
parser->calibrated = 0;
|
||
for (unsigned int i = 0; i < 3; ++i) {
|
||
parser->calibration[i] = 0.0;
|
||
}
|
||
parser->divemode = M_OC_TEC;
|
||
parser->units = METRIC;
|
||
parser->density = DEF_DENSITY_SALT;
|
||
parser->atmospheric = DEF_ATMOSPHERIC / (BAR / 1000);
|
||
|
||
*out = (dc_parser_t *) parser;
|
||
|
||
return DC_STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
dc_status_t
|
||
shearwater_predator_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model)
|
||
{
|
||
return shearwater_common_parser_create (out, context, model, 0);
|
||
}
|
||
|
||
|
||
dc_status_t
|
||
shearwater_petrel_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model)
|
||
{
|
||
return shearwater_common_parser_create (out, context, model, 1);
|
||
}
|
||
|
||
|
||
static dc_status_t
|
||
shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size)
|
||
{
|
||
shearwater_predator_parser_t *parser = (shearwater_predator_parser_t *) abstract;
|
||
|
||
// Reset the cache.
|
||
parser->cached = 0;
|
||
parser->pnf = 0;
|
||
parser->logversion = 0;
|
||
parser->headersize = 0;
|
||
parser->footersize = 0;
|
||
for (unsigned int i = 0; i < NRECORDS; ++i) {
|
||
parser->opening[i] = UNDEFINED;
|
||
parser->closing[i] = UNDEFINED;
|
||
}
|
||
parser->final = UNDEFINED;
|
||
parser->ngasmixes = 0;
|
||
for (unsigned int i = 0; i < NGASMIXES; ++i) {
|
||
parser->gasmix[i].oxygen = 0;
|
||
parser->gasmix[i].helium = 0;
|
||
}
|
||
parser->ntanks = 0;
|
||
for (unsigned int i = 0; i < NTANKS; ++i) {
|
||
parser->tank[i].enabled = 0;
|
||
parser->tank[i].active = 0;
|
||
parser->tank[i].beginpressure = 0;
|
||
parser->tank[i].endpressure = 0;
|
||
parser->tank[i].pressure_max = 0;
|
||
parser->tank[i].pressure_reserve = 0;
|
||
parser->tank[i].serial = 0;
|
||
memset (parser->tank[i].name, 0, sizeof (parser->tank[i].name));
|
||
parser->tankidx[i] = i;
|
||
}
|
||
parser->aimode = AI_OFF;
|
||
parser->calibrated = 0;
|
||
for (unsigned int i = 0; i < 3; ++i) {
|
||
parser->calibration[i] = 0.0;
|
||
}
|
||
parser->divemode = M_OC_TEC;
|
||
parser->units = METRIC;
|
||
parser->density = DEF_DENSITY_SALT;
|
||
parser->atmospheric = DEF_ATMOSPHERIC / (BAR / 1000);
|
||
|
||
return DC_STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
static dc_status_t
|
||
shearwater_predator_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime)
|
||
{
|
||
shearwater_predator_parser_t *parser = (shearwater_predator_parser_t *) abstract;
|
||
const unsigned char *data = abstract->data;
|
||
|
||
// Cache the parser data.
|
||
dc_status_t rc = shearwater_predator_parser_cache (parser);
|
||
if (rc != DC_STATUS_SUCCESS)
|
||
return rc;
|
||
|
||
unsigned int ticks = array_uint32_be (data + parser->opening[0] + 12);
|
||
|
||
if (!dc_datetime_gmtime (datetime, ticks))
|
||
return DC_STATUS_DATAFORMAT;
|
||
|
||
if (parser->model == TERIC && parser->logversion >= 9 && parser->opening[5] != UNDEFINED) {
|
||
int utc_offset = (int) array_uint32_be (data + parser->opening[5] + 26);
|
||
int dst = data[parser->opening[5] + 30];
|
||
datetime->timezone = utc_offset * 60 + dst * 3600;
|
||
} else {
|
||
datetime->timezone = DC_TIMEZONE_NONE;
|
||
}
|
||
|
||
return DC_STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
static dc_status_t
|
||
shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
|
||
{
|
||
dc_parser_t *abstract = (dc_parser_t *) parser;
|
||
const unsigned char *data = parser->base.data;
|
||
unsigned int size = parser->base.size;
|
||
|
||
if (parser->cached) {
|
||
return DC_STATUS_SUCCESS;
|
||
}
|
||
|
||
// Log versions before 6 weren't reliably stored in the data, but
|
||
// 6 is also the oldest version that we assume in our code
|
||
unsigned int logversion = 0;
|
||
|
||
// Verify the minimum length.
|
||
if (size < 2) {
|
||
ERROR (abstract->context, "Invalid data length.");
|
||
return DC_STATUS_DATAFORMAT;
|
||
}
|
||
|
||
// The Petrel Native Format (PNF) is very similar to the legacy
|
||
// Predator and Predator-like format. The samples are simply offset
|
||
// by one (so we can use pnf as the offset). For the header and
|
||
// footer data, it's more complicated because of the new 32 byte
|
||
// block structure.
|
||
unsigned int pnf = parser->petrel ? array_uint16_be (data) != 0xFFFF : 0;
|
||
unsigned int headersize = 0;
|
||
unsigned int footersize = 0;
|
||
if (!pnf) {
|
||
// Opening and closing blocks.
|
||
headersize = SZ_BLOCK;
|
||
footersize = SZ_BLOCK;
|
||
if (size < headersize + footersize) {
|
||
ERROR (abstract->context, "Invalid data length.");
|
||
return DC_STATUS_DATAFORMAT;
|
||
}
|
||
|
||
// Adjust the footersize for the final block.
|
||
if (parser->petrel || array_uint16_be (data + size - footersize) == 0xFFFD) {
|
||
footersize += SZ_BLOCK;
|
||
if (size < headersize + footersize) {
|
||
ERROR (abstract->context, "Invalid data length.");
|
||
return DC_STATUS_DATAFORMAT;
|
||
}
|
||
|
||
parser->final = size - SZ_BLOCK;
|
||
}
|
||
|
||
// The Predator and Predator-like format have just one large 128
|
||
// byte opening and closing block. To minimize the differences
|
||
// with the PNF format, all record offsets are assigned the same
|
||
// value here.
|
||
for (unsigned int i = 0; i <= 4; ++i) {
|
||
parser->opening[i] = 0;
|
||
parser->closing[i] = size - footersize;
|
||
}
|
||
|
||
// Log version
|
||
logversion = data[127];
|
||
}
|
||
|
||
// Default dive mode.
|
||
unsigned int divemode = M_OC_TEC;
|
||
|
||
// Get the gas mixes.
|
||
unsigned int ngasmixes = NFIXED;
|
||
shearwater_predator_gasmix_t gasmix[NGASMIXES] = {0};
|
||
shearwater_predator_tank_t tank[NTANKS] = {0};
|
||
unsigned int o2_previous = 0, he_previous = 0;
|
||
unsigned int aimode = AI_OFF;
|
||
if (!pnf) {
|
||
for (unsigned int i = 0; i < NFIXED; ++i) {
|
||
gasmix[i].oxygen = data[20 + i];
|
||
gasmix[i].helium = data[30 + i];
|
||
}
|
||
}
|
||
|
||
unsigned int offset = headersize;
|
||
unsigned int length = size - footersize;
|
||
while (offset + parser->samplesize <= length) {
|
||
// Ignore empty samples.
|
||
if (array_isequal (data + offset, parser->samplesize, 0x00)) {
|
||
offset += parser->samplesize;
|
||
continue;
|
||
}
|
||
|
||
// Get the record type.
|
||
unsigned int type = pnf ? data[offset] : LOG_RECORD_DIVE_SAMPLE;
|
||
|
||
if (type == LOG_RECORD_DIVE_SAMPLE) {
|
||
// Status flags.
|
||
unsigned int status = data[offset + 11 + pnf];
|
||
if ((status & OC) == 0) {
|
||
divemode = status & SC ? M_SC : M_CC;
|
||
}
|
||
|
||
// Gaschange.
|
||
unsigned int o2 = data[offset + 7 + pnf];
|
||
unsigned int he = data[offset + 8 + pnf];
|
||
if (o2 != o2_previous || he != he_previous) {
|
||
// Find the gasmix in the list.
|
||
unsigned int idx = 0;
|
||
while (idx < ngasmixes) {
|
||
if (o2 == gasmix[idx].oxygen && he == gasmix[idx].helium)
|
||
break;
|
||
idx++;
|
||
}
|
||
|
||
// Add it to list if not found.
|
||
if (idx >= ngasmixes) {
|
||
if (idx >= NGASMIXES) {
|
||
ERROR (abstract->context, "Maximum number of gas mixes reached.");
|
||
return DC_STATUS_NOMEMORY;
|
||
}
|
||
gasmix[idx].oxygen = o2;
|
||
gasmix[idx].helium = he;
|
||
ngasmixes = idx + 1;
|
||
}
|
||
|
||
o2_previous = o2;
|
||
he_previous = he;
|
||
}
|
||
|
||
// Tank pressure
|
||
if (logversion >= 7) {
|
||
const unsigned int idx[2] = {27, 19};
|
||
for (unsigned int i = 0; i < 2; ++i) {
|
||
// Values above 0xFFF0 are special codes:
|
||
// 0xFFFF AI is off
|
||
// 0xFFFE No comms for 90 seconds+
|
||
// 0xFFFD No comms for 30 seconds
|
||
// 0xFFFC Transmitter not paired
|
||
// For regular values, the top 4 bits contain the battery
|
||
// level (0=normal, 1=critical, 2=warning), and the lower 12
|
||
// bits the tank pressure in units of 2 psi.
|
||
unsigned int pressure = array_uint16_be (data + offset + pnf + idx[i]);
|
||
unsigned int id = (aimode == AI_HPCCR ? 4 : 0) + i;
|
||
if (pressure < 0xFFF0) {
|
||
pressure &= 0x0FFF;
|
||
if (!tank[id].active) {
|
||
tank[id].active = 1;
|
||
tank[id].beginpressure = pressure;
|
||
tank[id].endpressure = pressure;
|
||
}
|
||
tank[id].endpressure = pressure;
|
||
}
|
||
}
|
||
}
|
||
} else if (type == LOG_RECORD_DIVE_SAMPLE_EXT) {
|
||
// Tank pressure
|
||
if (logversion >= 13) {
|
||
for (unsigned int i = 0; i < 2; ++i) {
|
||
unsigned int pressure = array_uint16_be (data + offset + pnf + i * 2);
|
||
unsigned int id = 2 + i;
|
||
if (pressure < 0xFFF0) {
|
||
pressure &= 0x0FFF;
|
||
if (!tank[id].active) {
|
||
tank[id].active = 1;
|
||
tank[id].beginpressure = pressure;
|
||
tank[id].endpressure = pressure;
|
||
}
|
||
tank[id].endpressure = pressure;
|
||
}
|
||
}
|
||
}
|
||
// Tank pressure (HP CCR)
|
||
if (logversion >= 14) {
|
||
for (unsigned int i = 0; i < 2; ++i) {
|
||
unsigned int pressure = array_uint16_be (data + offset + pnf + 4 + i * 2);
|
||
unsigned int id = 4 + i;
|
||
if (pressure) {
|
||
if (!tank[id].active) {
|
||
tank[id].active = 1;
|
||
tank[id].enabled = 1;
|
||
tank[id].beginpressure = pressure;
|
||
tank[id].endpressure = pressure;
|
||
tank[id].name[0] = i == 0 ? 'D': 'O';
|
||
tank[id].name[1] = 0;
|
||
}
|
||
tank[id].endpressure = pressure;
|
||
}
|
||
}
|
||
}
|
||
} else if (type == LOG_RECORD_FREEDIVE_SAMPLE) {
|
||
// Freedive record
|
||
divemode = M_FREEDIVE;
|
||
} else if (type >= LOG_RECORD_OPENING_0 && type <= LOG_RECORD_OPENING_7) {
|
||
// Opening record
|
||
parser->opening[type - LOG_RECORD_OPENING_0] = offset;
|
||
|
||
if (type == LOG_RECORD_OPENING_0) {
|
||
for (unsigned int i = 0; i < NFIXED; ++i) {
|
||
gasmix[i].oxygen = data[offset + 20 + i];
|
||
}
|
||
for (unsigned int i = 0; i < 2; ++i) {
|
||
gasmix[i].helium = data[offset + 30 + i];
|
||
}
|
||
} else if (type == LOG_RECORD_OPENING_1) {
|
||
for (unsigned int i = 2; i < NFIXED; ++i) {
|
||
gasmix[i].helium = data[offset + 1 + i - 2];
|
||
}
|
||
} else if (type == LOG_RECORD_OPENING_4) {
|
||
// Log version
|
||
logversion = data[offset + 16];
|
||
|
||
// Air integration mode
|
||
if (logversion >= 7) {
|
||
aimode = data[offset + 28];
|
||
if (logversion < 13) {
|
||
if (aimode == 1 || aimode == 2) {
|
||
tank[aimode - 1].enabled = 1;
|
||
} else if (aimode == 3) {
|
||
tank[0].enabled = 1;
|
||
tank[1].enabled = 1;
|
||
}
|
||
}
|
||
if (logversion < 14) {
|
||
if (aimode == AI_HPCCR) {
|
||
for (unsigned int i = 0; i < 2; ++i) {
|
||
tank[4 + i].enabled = 1;
|
||
tank[4 + i].name[0] = i == 0 ? 'D': 'O';
|
||
tank[4 + i].name[1] = 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} else if (type == LOG_RECORD_OPENING_5) {
|
||
if (logversion >= 9) {
|
||
tank[0].serial = array_convert_bcd2dec (data + offset + 1, 3);
|
||
tank[0].pressure_max = array_uint16_be(data + offset + 6);
|
||
tank[0].pressure_reserve = array_uint16_be(data + offset + 8);
|
||
|
||
tank[1].serial = array_convert_bcd2dec(data + offset + 10, 3);
|
||
tank[1].pressure_max = array_uint16_be(data + offset + 15);
|
||
tank[1].pressure_reserve = array_uint16_be(data + offset + 17);
|
||
}
|
||
} else if (type == LOG_RECORD_OPENING_6) {
|
||
if (logversion >= 13) {
|
||
tank[0].enabled = data[offset + 19];
|
||
memcpy (tank[0].name, data + offset + 20, sizeof (tank[0].name));
|
||
|
||
tank[1].enabled = data[offset + 22];
|
||
memcpy (tank[1].name, data + offset + 23, sizeof (tank[1].name));
|
||
|
||
tank[2].serial = array_convert_bcd2dec(data + offset + 25, 3);
|
||
tank[2].pressure_max = array_uint16_be(data + offset + 28);
|
||
tank[2].pressure_reserve = array_uint16_be(data + offset + 30);
|
||
}
|
||
} else if (type == LOG_RECORD_OPENING_7) {
|
||
if (logversion >= 13) {
|
||
tank[2].enabled = data[offset + 1];
|
||
memcpy (tank[2].name, data + offset + 2, sizeof (tank[2].name));
|
||
|
||
tank[3].serial = array_convert_bcd2dec(data + offset + 4, 3);
|
||
tank[3].pressure_max = array_uint16_be(data + offset + 7);
|
||
tank[3].pressure_reserve = array_uint16_be(data + offset + 9);
|
||
tank[3].enabled = data[offset + 11];
|
||
memcpy (tank[3].name, data + offset + 12, sizeof (tank[3].name));
|
||
}
|
||
}
|
||
} else if (type >= LOG_RECORD_CLOSING_0 && type <= LOG_RECORD_CLOSING_7) {
|
||
// Closing record
|
||
parser->closing[type - LOG_RECORD_CLOSING_0] = offset;
|
||
} else if (type == LOG_RECORD_FINAL) {
|
||
// Final record
|
||
parser->final = offset;
|
||
}
|
||
|
||
offset += parser->samplesize;
|
||
}
|
||
|
||
// Verify the required opening/closing records.
|
||
// At least in firmware v71 and newer, Petrel and Petrel 2 also use PNF,
|
||
// and there opening/closing record 5 (which contains AI information plus
|
||
// the sample interval) don't appear to exist - so don't mark them as required
|
||
for (unsigned int i = 0; i <= 4; ++i) {
|
||
if (parser->opening[i] == UNDEFINED || parser->closing[i] == UNDEFINED) {
|
||
ERROR (abstract->context, "Opening or closing record %u not found.", i);
|
||
return DC_STATUS_DATAFORMAT;
|
||
}
|
||
}
|
||
|
||
// Cache sensor calibration for later use
|
||
unsigned int nsensors = 0, ndefaults = 0;
|
||
unsigned int base = parser->opening[3] + (pnf ? 6 : 86);
|
||
for (size_t i = 0; i < 3; ++i) {
|
||
unsigned int calibration = array_uint16_be(data + base + 1 + i * 2);
|
||
parser->calibration[i] = calibration / 100000.0;
|
||
if (parser->model == PREDATOR) {
|
||
// The Predator expects the mV output of the cells to be
|
||
// within 30mV to 70mV in 100% O2 at 1 atmosphere. If the
|
||
// calibration value is scaled with a factor 2.2, then the
|
||
// sensors lines up and matches the average.
|
||
parser->calibration[i] *= 2.2;
|
||
}
|
||
if (data[base] & (1 << i)) {
|
||
if (calibration == 2100) {
|
||
ndefaults++;
|
||
}
|
||
nsensors++;
|
||
}
|
||
}
|
||
if (nsensors && nsensors == ndefaults) {
|
||
// If all (calibrated) sensors still have their factory default
|
||
// calibration values (2100), they are probably not calibrated
|
||
// properly. To avoid returning incorrect ppO2 values to the
|
||
// application, they are manually disabled (e.g. marked as
|
||
// uncalibrated).
|
||
WARNING (abstract->context, "Disabled all O2 sensors due to a default calibration value.");
|
||
parser->calibrated = 0;
|
||
} else {
|
||
parser->calibrated = data[base];
|
||
}
|
||
|
||
// Get the dive mode from the header (if available).
|
||
if (logversion >= 8) {
|
||
divemode = data[parser->opening[4] + (pnf ? 1 : 112)];
|
||
}
|
||
|
||
// Get the correct model number from the final block.
|
||
if (parser->final != UNDEFINED) {
|
||
parser->model = data[parser->final + 13];
|
||
}
|
||
|
||
// Fix the Teric tank serial number.
|
||
if (parser->model == TERIC) {
|
||
for (unsigned int i = 0; i < NTANKS; ++i) {
|
||
tank[i].serial =
|
||
((tank[i].serial / 10000) % 100) +
|
||
((tank[i].serial / 100) % 100) * 100 +
|
||
((tank[i].serial ) % 100) * 10000;
|
||
}
|
||
}
|
||
|
||
// Cache the data for later use.
|
||
parser->pnf = pnf;
|
||
parser->logversion = logversion;
|
||
parser->headersize = headersize;
|
||
parser->footersize = footersize;
|
||
parser->ngasmixes = 0;
|
||
if (divemode != M_FREEDIVE) {
|
||
for (unsigned int i = 0; i < ngasmixes; ++i) {
|
||
if (gasmix[i].oxygen == 0 && gasmix[i].helium == 0)
|
||
continue;
|
||
parser->gasmix[parser->ngasmixes] = gasmix[i];
|
||
parser->ngasmixes++;
|
||
}
|
||
}
|
||
parser->ntanks = 0;
|
||
for (unsigned int i = 0; i < NTANKS; ++i) {
|
||
if (tank[i].active) {
|
||
parser->tankidx[i] = parser->ntanks;
|
||
parser->tank[parser->ntanks] = tank[i];
|
||
parser->ntanks++;
|
||
} else {
|
||
parser->tankidx[i] = UNDEFINED;
|
||
}
|
||
}
|
||
parser->aimode = aimode;
|
||
parser->divemode = divemode;
|
||
parser->units = data[parser->opening[0] + 8];
|
||
parser->atmospheric = array_uint16_be (data + parser->opening[1] + (parser->pnf ? 16 : 47));
|
||
parser->density = array_uint16_be (data + parser->opening[3] + (parser->pnf ? 3 : 83));
|
||
parser->cached = 1;
|
||
|
||
return DC_STATUS_SUCCESS;
|
||
}
|
||
|
||
static dc_status_t
|
||
shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value)
|
||
{
|
||
shearwater_predator_parser_t *parser = (shearwater_predator_parser_t *) abstract;
|
||
|
||
const unsigned char *data = abstract->data;
|
||
|
||
// Cache the parser data.
|
||
dc_status_t rc = shearwater_predator_parser_cache (parser);
|
||
if (rc != DC_STATUS_SUCCESS)
|
||
return rc;
|
||
|
||
unsigned int decomodel_idx = parser->pnf ? parser->opening[2] + 18 : 67;
|
||
unsigned int gf_idx = parser->pnf ? parser->opening[0] + 4 : 4;
|
||
|
||
dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
|
||
dc_tank_t *tank = (dc_tank_t *) value;
|
||
dc_salinity_t *water = (dc_salinity_t *) value;
|
||
dc_decomodel_t *decomodel = (dc_decomodel_t *) value;
|
||
|
||
if (value) {
|
||
switch (type) {
|
||
case DC_FIELD_DIVETIME:
|
||
if (parser->pnf)
|
||
*((unsigned int *) value) = array_uint24_be (data + parser->closing[0] + 6);
|
||
else
|
||
*((unsigned int *) value) = array_uint16_be (data + parser->closing[0] + 6) * 60;
|
||
break;
|
||
case DC_FIELD_MAXDEPTH:
|
||
if (parser->units == IMPERIAL)
|
||
*((double *) value) = array_uint16_be (data + parser->closing[0] + 4) * FEET;
|
||
else
|
||
*((double *) value) = array_uint16_be (data + parser->closing[0] + 4);
|
||
if (parser->pnf)
|
||
*((double *)value) /= 10.0;
|
||
break;
|
||
case DC_FIELD_GASMIX_COUNT:
|
||
*((unsigned int *) value) = parser->ngasmixes;
|
||
break;
|
||
case DC_FIELD_GASMIX:
|
||
gasmix->oxygen = parser->gasmix[flags].oxygen / 100.0;
|
||
gasmix->helium = parser->gasmix[flags].helium / 100.0;
|
||
gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium;
|
||
break;
|
||
case DC_FIELD_TANK_COUNT:
|
||
*((unsigned int *) value) = parser->ntanks;
|
||
break;
|
||
case DC_FIELD_TANK:
|
||
tank->type = DC_TANKVOLUME_NONE;
|
||
tank->volume = 0.0;
|
||
tank->workpressure = 0.0;
|
||
tank->beginpressure = parser->tank[flags].beginpressure * 2 * PSI / BAR;
|
||
tank->endpressure = parser->tank[flags].endpressure * 2 * PSI / BAR;
|
||
tank->gasmix = DC_GASMIX_UNKNOWN;
|
||
break;
|
||
case DC_FIELD_SALINITY:
|
||
if (parser->density == 1000)
|
||
water->type = DC_WATER_FRESH;
|
||
else
|
||
water->type = DC_WATER_SALT;
|
||
water->density = parser->density;
|
||
break;
|
||
case DC_FIELD_ATMOSPHERIC:
|
||
*((double *) value) = parser->atmospheric / 1000.0;
|
||
break;
|
||
case DC_FIELD_DIVEMODE:
|
||
switch (parser->divemode) {
|
||
case M_CC:
|
||
case M_CC2:
|
||
*((dc_divemode_t *) value) = DC_DIVEMODE_CCR;
|
||
break;
|
||
case M_SC:
|
||
*((dc_divemode_t *) value) = DC_DIVEMODE_SCR;
|
||
break;
|
||
case M_OC_TEC:
|
||
case M_OC_REC:
|
||
*((dc_divemode_t *) value) = DC_DIVEMODE_OC;
|
||
break;
|
||
case M_GAUGE:
|
||
case M_PPO2:
|
||
*((dc_divemode_t *) value) = DC_DIVEMODE_GAUGE;
|
||
break;
|
||
case M_FREEDIVE:
|
||
*((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE;
|
||
break;
|
||
default:
|
||
return DC_STATUS_DATAFORMAT;
|
||
}
|
||
break;
|
||
case DC_FIELD_DECOMODEL:
|
||
switch (data[decomodel_idx]) {
|
||
case GF:
|
||
decomodel->type = DC_DECOMODEL_BUHLMANN;
|
||
decomodel->conservatism = 0;
|
||
decomodel->params.gf.low = data[gf_idx + 0];
|
||
decomodel->params.gf.high = data[gf_idx + 1];
|
||
break;
|
||
case VPMB:
|
||
case VPMB_GFS:
|
||
decomodel->type = DC_DECOMODEL_VPM;
|
||
decomodel->conservatism = data[decomodel_idx + 1];
|
||
break;
|
||
case DCIEM:
|
||
decomodel->type = DC_DECOMODEL_DCIEM;
|
||
decomodel->conservatism = 0;
|
||
break;
|
||
default:
|
||
return DC_STATUS_DATAFORMAT;
|
||
}
|
||
break;
|
||
default:
|
||
return DC_STATUS_UNSUPPORTED;
|
||
}
|
||
}
|
||
|
||
return DC_STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
static dc_status_t
|
||
shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
|
||
{
|
||
shearwater_predator_parser_t *parser = (shearwater_predator_parser_t *) abstract;
|
||
|
||
const unsigned char *data = abstract->data;
|
||
unsigned int size = abstract->size;
|
||
|
||
// Cache the parser data.
|
||
dc_status_t rc = shearwater_predator_parser_cache (parser);
|
||
if (rc != DC_STATUS_SUCCESS)
|
||
return rc;
|
||
|
||
// Previous gas mix.
|
||
unsigned int o2_previous = 0, he_previous = 0;
|
||
|
||
// Sample interval.
|
||
unsigned int time = 0;
|
||
unsigned int interval = 10;
|
||
if (parser->pnf && parser->logversion >= 9 && parser->opening[5] != UNDEFINED) {
|
||
interval = array_uint16_be (data + parser->opening[5] + 23);
|
||
if (interval % 1000 != 0) {
|
||
ERROR (abstract->context, "Unsupported sample interval (%u ms).", interval);
|
||
return DC_STATUS_DATAFORMAT;
|
||
}
|
||
interval /= 1000;
|
||
}
|
||
|
||
unsigned int pnf = parser->pnf;
|
||
unsigned int offset = parser->headersize;
|
||
unsigned int length = size - parser->footersize;
|
||
while (offset + parser->samplesize <= length) {
|
||
dc_sample_value_t sample = {0};
|
||
|
||
// Ignore empty samples.
|
||
if (array_isequal (data + offset, parser->samplesize, 0x00)) {
|
||
offset += parser->samplesize;
|
||
continue;
|
||
}
|
||
|
||
// Get the record type.
|
||
unsigned int type = pnf ? data[offset] : LOG_RECORD_DIVE_SAMPLE;
|
||
|
||
if (type == LOG_RECORD_DIVE_SAMPLE) {
|
||
// Time (seconds).
|
||
time += interval;
|
||
sample.time = time;
|
||
if (callback) callback (DC_SAMPLE_TIME, sample, userdata);
|
||
|
||
// Depth (1/10 m or ft).
|
||
unsigned int depth = array_uint16_be (data + pnf + offset);
|
||
if (parser->units == IMPERIAL)
|
||
sample.depth = depth * FEET / 10.0;
|
||
else
|
||
sample.depth = depth / 10.0;
|
||
if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
|
||
|
||
// Temperature (°C or °F).
|
||
int temperature = (signed char) data[offset + pnf + 13];
|
||
if (temperature < 0) {
|
||
// Fix negative temperatures.
|
||
temperature += 102;
|
||
if (temperature > 0) {
|
||
temperature = 0;
|
||
}
|
||
}
|
||
if (parser->units == IMPERIAL)
|
||
sample.temperature = (temperature - 32.0) * (5.0 / 9.0);
|
||
else
|
||
sample.temperature = temperature;
|
||
if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
|
||
|
||
// Status flags.
|
||
unsigned int status = data[offset + pnf + 11];
|
||
|
||
if ((status & OC) == 0) {
|
||
// PPO2
|
||
if ((status & PPO2_EXTERNAL) == 0) {
|
||
#ifdef SENSOR_AVERAGE
|
||
sample.ppo2 = data[offset + pnf + 6] / 100.0;
|
||
if (callback) callback (DC_SAMPLE_PPO2, sample, userdata);
|
||
#else
|
||
sample.ppo2 = data[offset + pnf + 12] * parser->calibration[0];
|
||
if (callback && (parser->calibrated & 0x01)) callback (DC_SAMPLE_PPO2, sample, userdata);
|
||
|
||
sample.ppo2 = data[offset + pnf + 14] * parser->calibration[1];
|
||
if (callback && (parser->calibrated & 0x02)) callback (DC_SAMPLE_PPO2, sample, userdata);
|
||
|
||
sample.ppo2 = data[offset + pnf + 15] * parser->calibration[2];
|
||
if (callback && (parser->calibrated & 0x04)) callback (DC_SAMPLE_PPO2, sample, userdata);
|
||
#endif
|
||
}
|
||
|
||
// Setpoint
|
||
if (parser->petrel) {
|
||
sample.setpoint = data[offset + pnf + 18] / 100.0;
|
||
} else {
|
||
// this will only ever be called for the actual Predator, so no adjustment needed for PNF
|
||
if (status & SETPOINT_HIGH) {
|
||
sample.setpoint = data[18] / 100.0;
|
||
} else {
|
||
sample.setpoint = data[17] / 100.0;
|
||
}
|
||
}
|
||
if (callback) callback (DC_SAMPLE_SETPOINT, sample, userdata);
|
||
}
|
||
|
||
// CNS
|
||
if (parser->petrel) {
|
||
sample.cns = data[offset + pnf + 22] / 100.0;
|
||
if (callback) callback (DC_SAMPLE_CNS, sample, userdata);
|
||
}
|
||
|
||
// Gaschange.
|
||
unsigned int o2 = data[offset + pnf + 7];
|
||
unsigned int he = data[offset + pnf + 8];
|
||
if (o2 != o2_previous || he != he_previous) {
|
||
unsigned int idx = shearwater_predator_find_gasmix (parser, o2, he);
|
||
if (idx >= parser->ngasmixes) {
|
||
ERROR (abstract->context, "Invalid gas mix.");
|
||
return DC_STATUS_DATAFORMAT;
|
||
}
|
||
|
||
sample.gasmix = idx;
|
||
if (callback) callback (DC_SAMPLE_GASMIX, sample, userdata);
|
||
o2_previous = o2;
|
||
he_previous = he;
|
||
}
|
||
|
||
// Deco stop / NDL.
|
||
unsigned int decostop = array_uint16_be (data + offset + pnf + 2);
|
||
if (decostop) {
|
||
sample.deco.type = DC_DECO_DECOSTOP;
|
||
if (parser->units == IMPERIAL)
|
||
sample.deco.depth = decostop * FEET;
|
||
else
|
||
sample.deco.depth = decostop;
|
||
} else {
|
||
sample.deco.type = DC_DECO_NDL;
|
||
sample.deco.depth = 0.0;
|
||
}
|
||
sample.deco.time = data[offset + pnf + 9] * 60;
|
||
if (callback) callback (DC_SAMPLE_DECO, sample, userdata);
|
||
|
||
// for logversion 7 and newer (introduced for Perdix AI)
|
||
// detect tank pressure
|
||
if (parser->logversion >= 7) {
|
||
const unsigned int idx[2] = {27, 19};
|
||
for (unsigned int i = 0; i < 2; ++i) {
|
||
// Tank pressure
|
||
// Values above 0xFFF0 are special codes:
|
||
// 0xFFFF AI is off
|
||
// 0xFFFE No comms for 90 seconds+
|
||
// 0xFFFD No comms for 30 seconds
|
||
// 0xFFFC Transmitter not paired
|
||
// For regular values, the top 4 bits contain the battery
|
||
// level (0=normal, 1=critical, 2=warning), and the lower 12
|
||
// bits the tank pressure in units of 2 psi.
|
||
unsigned int pressure = array_uint16_be (data + offset + pnf + idx[i]);
|
||
unsigned int id = (parser->aimode == AI_HPCCR ? 4 : 0) + i;
|
||
if (pressure < 0xFFF0) {
|
||
pressure &= 0x0FFF;
|
||
sample.pressure.tank = parser->tankidx[id];
|
||
sample.pressure.value = pressure * 2 * PSI / BAR;
|
||
if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata);
|
||
}
|
||
}
|
||
|
||
// Gas time remaining in minutes
|
||
// Values above 0xF0 are special codes:
|
||
// 0xFF Not paired
|
||
// 0xFE No communication
|
||
// 0xFD Not available in current mode
|
||
// 0xFC Not available because of DECO
|
||
// 0xFB Tank size or max pressure haven’t been set up
|
||
if (data[offset + pnf + 21] < 0xF0) {
|
||
sample.rbt = data[offset + pnf + 21];
|
||
if (callback) callback (DC_SAMPLE_RBT, sample, userdata);
|
||
}
|
||
}
|
||
} else if (type == LOG_RECORD_DIVE_SAMPLE_EXT) {
|
||
// Tank pressure
|
||
if (parser->logversion >= 13) {
|
||
for (unsigned int i = 0; i < 2; ++i) {
|
||
unsigned int pressure = array_uint16_be (data + offset + pnf + i * 2);
|
||
unsigned int id = 2 + i;
|
||
if (pressure < 0xFFF0) {
|
||
pressure &= 0x0FFF;
|
||
sample.pressure.tank = parser->tankidx[id];
|
||
sample.pressure.value = pressure * 2 * PSI / BAR;
|
||
if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata);
|
||
}
|
||
}
|
||
}
|
||
// Tank pressure (HP CCR)
|
||
if (parser->logversion >= 14) {
|
||
for (unsigned int i = 0; i < 2; ++i) {
|
||
unsigned int pressure = array_uint16_be (data + offset + pnf + 4 + i * 2);
|
||
unsigned int id = 4 + i;
|
||
if (pressure) {
|
||
sample.pressure.tank = parser->tankidx[id];
|
||
sample.pressure.value = pressure * 2 * PSI / BAR;
|
||
if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata);
|
||
}
|
||
}
|
||
}
|
||
} else if (type == LOG_RECORD_FREEDIVE_SAMPLE) {
|
||
// A freedive record is actually 4 samples, each 8-bytes,
|
||
// packed into a standard 32-byte sized record. At the end
|
||
// of a dive, unused partial records will be 0 padded.
|
||
for (unsigned int i = 0; i < 4; ++i) {
|
||
unsigned int idx = offset + i * SZ_SAMPLE_FREEDIVE;
|
||
|
||
// Ignore empty samples.
|
||
if (array_isequal (data + idx, SZ_SAMPLE_FREEDIVE, 0x00)) {
|
||
break;
|
||
}
|
||
|
||
// Time (seconds).
|
||
time += interval;
|
||
sample.time = time;
|
||
if (callback) callback (DC_SAMPLE_TIME, sample, userdata);
|
||
|
||
// Depth (absolute pressure in millibar)
|
||
unsigned int depth = array_uint16_be (data + idx + 1);
|
||
sample.depth = (signed int)(depth - parser->atmospheric) * (BAR / 1000.0) / (parser->density * GRAVITY);
|
||
if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
|
||
|
||
// Temperature (1/10 °C).
|
||
int temperature = (signed short) array_uint16_be (data + idx + 3);
|
||
sample.temperature = temperature / 10.0;
|
||
if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
|
||
}
|
||
} else if (type == LOG_RECORD_INFO_EVENT) {
|
||
unsigned int event = data[offset + 1];
|
||
unsigned int timestamp = array_uint32_be (data + offset + 4);
|
||
unsigned int w1 = array_uint32_be (data + offset + 8);
|
||
unsigned int w2 = array_uint32_be (data + offset + 12);
|
||
|
||
if (event == INFO_EVENT_TAG_LOG) {
|
||
// Compass heading
|
||
if (w1 != 0xFFFFFFFF) {
|
||
sample.bearing = w1;
|
||
if (callback) callback (DC_SAMPLE_BEARING, sample, userdata);
|
||
}
|
||
|
||
// Tag
|
||
sample.event.type = SAMPLE_EVENT_BOOKMARK;
|
||
sample.event.time = 0;
|
||
sample.event.flags = 0;
|
||
sample.event.value = w2;
|
||
if (callback) callback (DC_SAMPLE_EVENT, sample, userdata);
|
||
}
|
||
}
|
||
|
||
offset += parser->samplesize;
|
||
}
|
||
|
||
return DC_STATUS_SUCCESS;
|
||
}
|