#include <stdlib.h>
#include <string.h>
#include <libdivecomputer/units.h>
#include "divesoft_freedom.h"
#include "context-private.h"
#include "parser-private.h"
#include "checksum.h"
#include "array.h"
#define UNDEFINED 0xFFFFFFFF
#define EPOCH 946684800
#define OC 0
#define OXYGEN 1
#define DILUENT 2
#define NSENSORS 4
#define NGASMIXES 12
#define NTANKS 12
#define HEADER_SIGNATURE_V1 0x45766944
#define HEADER_SIGNATURE_V2 0x45566944
#define HEADER_SIZE_V1 32
#define HEADER_SIZE_V2 64
#define RECORD_SIZE 16
#define SEAWATER 1028
#define FRESHWATER 1000
typedef enum logrecord_t {
LREC_POINT = 0,
LREC_MANIPULATION = 1,
LREC_AUTO = 2,
LREC_DIVER_ERROR = 3,
LREC_INTERNAL_ERROR = 4,
LREC_ACTIVITY = 5,
LREC_CONFIGURATION = 6,
LREC_MEASURE = 7,
LREC_STATE = 8,
LREC_INFO = 9,
} logrecord_t;
typedef enum point_id_t {
POINT_1 = 0,
POINT_2 = 1,
POINT_1_OLD = 0x3FF,
} point_id_t;
typedef enum configuration_id_t {
CFG_ID_TEST_CCR_FULL = 0,
CFG_ID_TEST_CCR_PARTIAL = 1,
CFG_ID_OXYGEN_CALIBRATION = 2,
CFG_ID_SERIAL = 3,
CFG_ID_DECO = 4,
CFG_ID_VERSION = 5,
CFG_ID_ASCENT = 6,
CFG_ID_AI = 7,
CFG_ID_CCR = 8,
CFG_ID_DILUENTS = 9,
} configuration_id_t;
typedef enum measure_id_t {
MEASURE_ID_OXYGEN = 0,
MEASURE_ID_BATTERY = 1,
MEASURE_ID_HELIUM = 2,
MEASURE_ID_OXYGEN_MV = 3,
MEASURE_ID_GPS = 4,
MEASURE_ID_PRESSURE = 5,
MEASURE_ID_AI_SAC = 6,
MEASURE_ID_AI_PRESSURE = 7,
MEASURE_ID_BRIGHTNESS = 8,
MEASURE_ID_AI_STAT = 9,
} measure_id_t;
typedef enum state_id_t {
STATE_ID_DECO_N2LOW = 0,
STATE_ID_DECO_N2HIGH = 1,
STATE_ID_DECO_HELOW = 2,
STATE_ID_DECO_HEHIGH = 3,
STATE_ID_PLAN_STEPS = 4,
} state_id_t;
typedef enum event_t {
EVENT_DUMMY = 0,
EVENT_SETPOINT_MANUAL = 1,
EVENT_SETPOINT_AUTO = 2,
EVENT_OC = 3,
EVENT_CCR = 4,
EVENT_MIX_CHANGED = 5,
EVENT_START = 6,
EVENT_TOO_FAST = 7,
EVENT_ABOVE_CEILING = 8,
EVENT_TOXIC = 9,
EVENT_HYPOX = 10,
EVENT_CRITICAL = 11,
EVENT_SENSOR_DISABLED = 12,
EVENT_SENSOR_ENABLED = 13,
EVENT_O2_BACKUP = 14,
EVENT_PEER_DOWN = 15,
EVENT_HS_DOWN = 16,
EVENT_INCONSISTENT = 17,
EVENT_KEYDOWN = 18,
EVENT_SCR = 19,
EVENT_ABOVE_STOP = 20,
EVENT_SAFETY_MISS = 21,
EVENT_FATAL = 22,
EVENT_DILUENT = 23,
EVENT_CHANGE_MODE = 24,
EVENT_SOLENOID = 25,
EVENT_BOOKMARK = 26,
EVENT_GF_SWITCH = 27,
EVENT_PEER_UP = 28,
EVENT_HS_UP = 29,
EVENT_CNS = 30,
EVENT_BATTERY_LOW = 31,
EVENT_PPO2_LOST = 32,
EVENT_SENSOR_VALUE_BAD = 33,
EVENT_SAFETY_STOP_END = 34,
EVENT_DECO_STOP_END = 35,
EVENT_DEEP_STOP_END = 36,
EVENT_NODECO_END = 37,
EVENT_DEPTH_REACHED = 38,
EVENT_TIME_ELAPSED = 39,
EVENT_STACK_USAGE = 40,
EVENT_GAS_SWITCH_INFO = 41,
EVENT_PRESSURE_SENS_WARN = 42,
EVENT_PRESSURE_SENS_FAIL = 43,
EVENT_CHECK_O2_SENSORS = 44,
EVENT_SWITCH_TO_COMP_SCR = 45,
EVENT_GAS_LOST = 46,
EVENT_AIRBREAK = 47,
EVENT_AIRBREAK_END = 48,
EVENT_AIRBREAK_MISSED = 49,
EVENT_BORMT_EXPIRATION = 50,
EVENT_BORMT_EXPIRED = 51,
EVENT_SENSOR_EXCLUDED = 52,
EVENT_PREBR_SKIPPED = 53,
EVENT_BOCCR_BORMT_EXPIRED = 54,
EVENT_WAYPOINT = 55,
EVENT_TURNAROUND = 56,
EVENT_SOLENOID_FAILURE = 57,
EVENT_SM_CYL_PRESS_DIFF = 58,
EVENT_BAILOUT_MOD_EXCEEDED = 59,
} event_t;
typedef enum divemode_t {
STMODE_UNKNOWN = 0,
STMODE_OC = 1,
STMODE_CCR = 2,
STMODE_MCCR = 3,
STMODE_FREE = 4,
STMODE_GAUGE = 5,
STMODE_ASCR = 6,
STMODE_PSCR = 7,
STMODE_BOCCR = 8,
} divemode_t;
typedef enum setpoint_change_t {
SP_MANUAL = 0,
SP_AUTO_START = 1,
SP_AUTO_HYPOX = 2,
SP_AUTO_TIMEOUT = 3,
SP_AUTO_ASCENT = 4,
SP_AUTO_STALL = 5,
SP_AUTO_SPLOW = 6,
SP_AUTO_DEPTH_DESC = 7,
SP_AUTO_DEPTH_ASC = 8,
} setpoint_change_t;
typedef enum sensor_state_t {
SENSTAT_NORMAL = 0,
SENSTAT_OVERRANGE = 1,
SENSTAT_DISABLED = 2,
SENSTAT_EXCLUDED = 3,
SENSTAT_UNCALIBRATED = 4,
SENSTAT_ERROR = 5,
SENSTAT_OFFLINE = 6,
SENSTAT_INHIBITED = 7,
SENSTAT_NOT_EXIST = 8,
} sensor_state_t;
typedef enum battery_state_t {
BATSTATE_NO_BATTERY = 0,
BATSTATE_UNKNOWN = 1,
BATSTATE_DISCHARGING = 2,
BATSTATE_CHARGING = 3,
BATSTATE_FULL = 4,
} battery_state_t;
typedef struct divesoft_freedom_gasmix_t {
unsigned int oxygen;
unsigned int helium;
unsigned int type;
unsigned int id;
} divesoft_freedom_gasmix_t;
typedef struct divesoft_freedom_tank_t {
unsigned int volume;
unsigned int workpressure;
unsigned int beginpressure;
unsigned int endpressure;
unsigned int transmitter;
unsigned int active;
} divesoft_freedom_tank_t;
typedef struct divesoft_freedom_parser_t {
dc_parser_t base;
unsigned int cached;
unsigned int version;
unsigned int headersize;
unsigned int divetime;
unsigned int divemode;
int temperature_min;
unsigned int maxdepth;
unsigned int atmospheric;
unsigned int avgdepth;
unsigned int ngasmixes;
divesoft_freedom_gasmix_t gasmix[NGASMIXES];
unsigned int diluent;
unsigned int ntanks;
divesoft_freedom_tank_t tank[NTANKS];
unsigned int vpm;
unsigned int gf_lo;
unsigned int gf_hi;
unsigned int seawater;
unsigned int calibration[NSENSORS];
unsigned int calibrated;
} divesoft_freedom_parser_t;
static dc_status_t divesoft_freedom_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size);
static dc_status_t divesoft_freedom_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime);
static dc_status_t divesoft_freedom_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value);
static dc_status_t divesoft_freedom_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata);
static const dc_parser_vtable_t divesoft_freedom_parser_vtable = {
sizeof(divesoft_freedom_parser_t),
DC_FAMILY_DIVESOFT_FREEDOM,
divesoft_freedom_parser_set_data,
NULL,
NULL,
NULL,
divesoft_freedom_parser_get_datetime,
divesoft_freedom_parser_get_field,
divesoft_freedom_parser_samples_foreach,
NULL
};
static unsigned int
divesoft_freedom_find_gasmix (divesoft_freedom_gasmix_t gasmix[], unsigned int count, unsigned int oxygen, unsigned int helium, unsigned int type)
{
unsigned int i = 0;
while (i < count) {
if (oxygen == gasmix[i].oxygen &&
helium == gasmix[i].helium &&
type == gasmix[i].type)
break;
i++;
}
return i;
}
static unsigned int
divesoft_freedom_find_tank (divesoft_freedom_tank_t tank[], unsigned int count, unsigned int transmitter)
{
unsigned int i = 0;
while (i < count) {
if (transmitter == tank[i].transmitter)
break;
i++;
}
return i;
}
static unsigned int
divesoft_freedom_is_ccr (unsigned int divemode)
{
return divemode == STMODE_CCR || divemode == STMODE_MCCR ||
divemode == STMODE_ASCR || divemode == STMODE_PSCR ||
divemode == STMODE_BOCCR;
}
static dc_status_t
divesoft_freedom_cache (divesoft_freedom_parser_t *parser)
{
dc_parser_t *abstract = (dc_parser_t *) parser;
const unsigned char *data = abstract->data;
unsigned int size = abstract->size;
if (parser->cached) {
return DC_STATUS_SUCCESS;
}
unsigned int headersize = 4;
if (size < headersize) {
ERROR (abstract->context, "Unexpected header size (%u).", size);
return DC_STATUS_DATAFORMAT;
}
unsigned int version = array_uint32_le (data);
if (version == HEADER_SIGNATURE_V1) {
headersize = HEADER_SIZE_V1;
} else if (version == HEADER_SIGNATURE_V2) {
headersize = HEADER_SIZE_V2;
} else {
ERROR (abstract->context, "Unexpected header version (%08x).", version);
return DC_STATUS_DATAFORMAT;
}
if (size < headersize) {
ERROR (abstract->context, "Unexpected header size (%u).", size);
return DC_STATUS_DATAFORMAT;
}
unsigned short crc = array_uint16_le (data + 4);
unsigned short ccrc = checksum_crc16r_ansi (data + 6, headersize - 6, 0xFFFF, 0x0000);
if (crc != ccrc) {
ERROR (abstract->context, "Invalid header checksum (%04x %04x).", crc, ccrc);
return DC_STATUS_DATAFORMAT;
}
unsigned int divetime = 0;
unsigned int divemode = 0;
unsigned int temperature_min = 0;
unsigned int maxdepth = 0;
unsigned int atmospheric = 0;
unsigned int avgdepth = 0;
unsigned int diluent_o2 = 0, diluent_he = 0;
if (version == HEADER_SIGNATURE_V1) {
unsigned int misc1 = array_uint32_le (data + 12);
unsigned int misc2 = array_uint32_le (data + 16);
divetime = misc1 & 0x1FFFF;
divemode = (misc1 & 0x38000000) >> 27;
temperature_min = (signed int) signextend ((misc2 & 0xFFC0000) >> 18, 10);
maxdepth = array_uint16_le (data + 20);
atmospheric = array_uint16_le (data + 24);
avgdepth = 0;
diluent_o2 = data[26];
diluent_he = data[27];
} else {
divetime = array_uint32_le (data + 12);
divemode = data[18];
temperature_min = (signed short) array_uint16_le (data + 24);
maxdepth = array_uint16_le (data + 28);
atmospheric = array_uint16_le (data + 32);
avgdepth = array_uint16_le (data + 38);
diluent_o2 = 0;
diluent_he = 0;
DEBUG (abstract->context, "Device: serial=%.4s-%.8s",
data + 52, data + 56);
}
divesoft_freedom_gasmix_t gasmix_ai[NGASMIXES] = {0},
gasmix_diluent[NGASMIXES] = {0},
gasmix_event[NGASMIXES] = {0};
unsigned int ngasmix_ai = 0,
ngasmix_diluent = 0,
ngasmix_event = 0;
divesoft_freedom_tank_t tank[NTANKS] = {0};
unsigned int ntanks = 0;
unsigned int vpm = 0, gf_lo = 0, gf_hi = 0;
unsigned int seawater = 0;
unsigned int calibration[NSENSORS] = {0};
unsigned int calibrated = 0;
unsigned int gasmixid_previous = UNDEFINED;
unsigned int offset = headersize;
while (offset + RECORD_SIZE <= size) {
if (array_isequal(data + offset, RECORD_SIZE, 0xFF)) {
WARNING (abstract->context, "Skipping empty sample.");
offset += RECORD_SIZE;
continue;
}
unsigned int flags = array_uint32_le (data + offset);
unsigned int type = (flags & 0x0000000F) >> 0;
unsigned int id = (flags & 0x7FE00000) >> 21;
if (type == LREC_CONFIGURATION) {
if (id == CFG_ID_DECO) {
unsigned int misc = array_uint16_le (data + offset + 4);
gf_lo = data[offset + 8];
gf_hi = data[offset + 9];
seawater = misc & 0x02;
vpm = misc & 0x20;
} else if (id == CFG_ID_VERSION) {
DEBUG (abstract->context, "Device: model=%u, hw=%u.%u, sw=%u.%u.%u.%u flags=%u",
data[offset + 4],
data[offset + 5], data[offset + 6],
data[offset + 7], data[offset + 8], data[offset + 9],
array_uint32_le (data + offset + 12),
array_uint16_le (data + offset + 10));
} else if (id == CFG_ID_SERIAL) {
DEBUG (abstract->context, "Device: serial=%.4s-%.8s",
data + offset + 4, data + offset + 8);
} else if (id == CFG_ID_DILUENTS) {
for (unsigned int i = 0; i < 4; ++i) {
unsigned int o2 = data[offset + 4 + i * 3 + 0];
unsigned int he = data[offset + 4 + i * 3 + 1];
unsigned int state = data[offset + 4 + i * 3 + 2];
if (state & 0x01) {
if (ngasmix_diluent >= NGASMIXES) {
ERROR (abstract->context, "Maximum number of gas mixes reached.");
return DC_STATUS_NOMEMORY;
}
gasmix_diluent[ngasmix_diluent].oxygen = o2;
gasmix_diluent[ngasmix_diluent].helium = he;
gasmix_diluent[ngasmix_diluent].type = DILUENT;
gasmix_diluent[ngasmix_diluent].id = (state & 0xFE) >> 1;
ngasmix_diluent++;
}
}
} else if (id == CFG_ID_OXYGEN_CALIBRATION) {
for (unsigned int i = 0; i < NSENSORS; ++i) {
calibration[i] = array_uint16_le (data + offset + 4 + i * 2);
}
calibrated = 1;
} else if (id == CFG_ID_AI) {
unsigned int o2 = data[offset + 4];
unsigned int he = data[offset + 5];
unsigned int volume = array_uint16_le (data + offset + 6);
unsigned int workpressure = array_uint16_le (data + offset + 8);
unsigned int transmitter = data[offset + 10];
unsigned int gasmixid = data[offset + 11];
if (gasmixid < 10 && gasmixid <= gasmixid_previous && gasmixid_previous != UNDEFINED) {
WARNING (abstract->context, "Fixed the CCR gas mix id (%u -> %u) for tank %u.",
gasmixid, gasmixid + 10, ntanks);
gasmixid += 10;
}
gasmixid_previous = gasmixid;
if (ngasmix_ai >= NGASMIXES) {
ERROR (abstract->context, "Maximum number of gas mixes reached.");
return DC_STATUS_NOMEMORY;
}
gasmix_ai[ngasmix_ai].oxygen = o2;
gasmix_ai[ngasmix_ai].helium = he;
if (gasmixid == 10) {
gasmix_ai[ngasmix_ai].type = OXYGEN;
} else if (gasmixid == 11) {
gasmix_ai[ngasmix_ai].type = DILUENT;
} else {
gasmix_ai[ngasmix_ai].type = OC;
}
gasmix_ai[ngasmix_ai].id = gasmixid;
ngasmix_ai++;
if (ntanks >= NTANKS) {
ERROR (abstract->context, "Maximum number of tanks reached.");
return DC_STATUS_NOMEMORY;
}
tank[ntanks].volume = volume;
tank[ntanks].workpressure = workpressure;
tank[ntanks].transmitter = transmitter;
ntanks++;
}
} else if ((type >= LREC_MANIPULATION && type <= LREC_ACTIVITY) || type == LREC_INFO) {
unsigned int event = array_uint16_le (data + offset + 4);
if (event == EVENT_MIX_CHANGED || event == EVENT_DILUENT || event == EVENT_CHANGE_MODE) {
unsigned int o2 = data[offset + 6];
unsigned int he = data[offset + 7];
unsigned int mixtype = OC;
if (event == EVENT_DILUENT) {
mixtype = DILUENT;
} else if (event == EVENT_CHANGE_MODE) {
unsigned int mode = data[offset + 8];
if (divesoft_freedom_is_ccr (mode)) {
mixtype = DILUENT;
}
}
unsigned int idx = divesoft_freedom_find_gasmix (gasmix_event, ngasmix_event, o2, he, mixtype);
if (idx >= ngasmix_event) {
if (ngasmix_event >= NGASMIXES) {
ERROR (abstract->context, "Maximum number of gas mixes reached.");
return DC_STATUS_NOMEMORY;
}
gasmix_event[ngasmix_event].oxygen = o2;
gasmix_event[ngasmix_event].helium = he;
gasmix_event[ngasmix_event].type = mixtype;
gasmix_event[ngasmix_event].id = UNDEFINED;
ngasmix_event++;
}
}
} else if (type == LREC_MEASURE) {
if (id == MEASURE_ID_AI_PRESSURE) {
for (unsigned int i = 0; i < NTANKS; ++i) {
unsigned int pressure = data[offset + 4 + i];
if (pressure == 0 || pressure == 0xFF)
continue;
unsigned int idx = divesoft_freedom_find_tank (tank, ntanks, i);
if (idx >= ntanks) {
ERROR (abstract->context, "Tank %u not found.", idx);
return DC_STATUS_DATAFORMAT;
}
if (!tank[idx].active) {
tank[idx].active = 1;
tank[idx].beginpressure = pressure;
tank[idx].endpressure = pressure;
}
tank[idx].endpressure = pressure;
}
}
}
offset += RECORD_SIZE;
}
unsigned int ngasmixes = 0;
divesoft_freedom_gasmix_t gasmix[NGASMIXES] = {0};
unsigned int diluent = UNDEFINED;
for (unsigned int i = 0; i < ngasmix_ai; ++i) {
gasmix[ngasmixes] = gasmix_ai[i];
ngasmixes++;
}
for (unsigned int i = 0; i < ngasmix_diluent; ++i) {
unsigned int idx = divesoft_freedom_find_gasmix (gasmix, ngasmixes,
gasmix_diluent[i].oxygen, gasmix_diluent[i].helium, gasmix_diluent[i].type);
if (idx >= ngasmixes) {
if (ngasmixes >= NGASMIXES) {
ERROR (abstract->context, "Maximum number of gas mixes reached.");
return DC_STATUS_NOMEMORY;
}
gasmix[ngasmixes] = gasmix_diluent[i];
ngasmixes++;
}
}
if (divesoft_freedom_is_ccr (divemode) &&
(diluent_o2 != 0 || diluent_he != 0)) {
unsigned int idx = divesoft_freedom_find_gasmix (gasmix, ngasmixes,
diluent_o2, diluent_he, DILUENT);
if (idx >= ngasmixes) {
if (ngasmixes >= NGASMIXES) {
ERROR (abstract->context, "Maximum number of gas mixes reached.");
return DC_STATUS_NOMEMORY;
}
gasmix[ngasmixes].oxygen = diluent_o2;
gasmix[ngasmixes].helium = diluent_he;
gasmix[ngasmixes].type = DILUENT;
gasmix[ngasmixes].id = UNDEFINED;
ngasmixes++;
}
diluent = idx;
}
for (unsigned int i = 0; i < ngasmix_event; ++i) {
unsigned int idx = divesoft_freedom_find_gasmix (gasmix, ngasmixes,
gasmix_event[i].oxygen, gasmix_event[i].helium, gasmix_event[i].type);
if (idx >= ngasmixes) {
if (ngasmixes >= NGASMIXES) {
ERROR (abstract->context, "Maximum number of gas mixes reached.");
return DC_STATUS_NOMEMORY;
}
gasmix[ngasmixes] = gasmix_event[i];
ngasmixes++;
}
}
parser->cached = 1;
parser->version = version;
parser->headersize = headersize;
parser->divetime = divetime;
parser->divemode = divemode;
parser->temperature_min = temperature_min;
parser->maxdepth = maxdepth;
parser->atmospheric = atmospheric;
parser->avgdepth = avgdepth;
parser->ngasmixes = ngasmixes;
for (unsigned int i = 0; i < ngasmixes; ++i) {
parser->gasmix[i] = gasmix[i];
}
parser->diluent = diluent;
parser->ntanks = ntanks;
for (unsigned int i = 0; i < ntanks; ++i) {
parser->tank[i] = tank[i];
}
parser->vpm = vpm;
parser->gf_lo = gf_lo;
parser->gf_hi = gf_hi;
parser->seawater = seawater;
for (unsigned int i = 0; i < NSENSORS; ++i) {
parser->calibration[i] = calibration[i];
}
parser->calibrated = calibrated;
return DC_STATUS_SUCCESS;
}
dc_status_t
divesoft_freedom_parser_create (dc_parser_t **out, dc_context_t *context)
{
divesoft_freedom_parser_t *parser = NULL;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
parser = (divesoft_freedom_parser_t *) dc_parser_allocate (context, &divesoft_freedom_parser_vtable);
if (parser == NULL) {
ERROR (context, "Failed to allocate memory.");
return DC_STATUS_NOMEMORY;
}
parser->cached = 0;
parser->version = 0;
parser->headersize = 0;
parser->divetime = 0;
parser->divemode = 0;
parser->temperature_min = 0;
parser->maxdepth = 0;
parser->atmospheric = 0;
parser->avgdepth = 0;
parser->ngasmixes = 0;
for (unsigned int i = 0; i < NGASMIXES; ++i) {
parser->gasmix[i].oxygen = 0;
parser->gasmix[i].helium = 0;
parser->gasmix[i].type = 0;
parser->gasmix[i].id = 0;
}
parser->diluent = UNDEFINED;
parser->ntanks = 0;
for (unsigned int i = 0; i < NTANKS; ++i) {
parser->tank[i].volume = 0;
parser->tank[i].workpressure = 0;
parser->tank[i].beginpressure = 0;
parser->tank[i].endpressure = 0;
parser->tank[i].transmitter = 0;
parser->tank[i].active = 0;
}
parser->vpm = 0;
parser->gf_lo = 0;
parser->gf_hi = 0;
parser->seawater = 0;
for (unsigned int i = 0; i < NSENSORS; ++i) {
parser->calibration[i] = 0;
}
parser->calibrated = 0;
*out = (dc_parser_t *) parser;
return DC_STATUS_SUCCESS;
}
static dc_status_t
divesoft_freedom_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size)
{
divesoft_freedom_parser_t *parser = (divesoft_freedom_parser_t *) abstract;
parser->cached = 0;
parser->version = 0;
parser->headersize = 0;
parser->divetime = 0;
parser->divemode = 0;
parser->temperature_min = 0;
parser->maxdepth = 0;
parser->atmospheric = 0;
parser->avgdepth = 0;
parser->ngasmixes = 0;
for (unsigned int i = 0; i < NGASMIXES; ++i) {
parser->gasmix[i].oxygen = 0;
parser->gasmix[i].helium = 0;
parser->gasmix[i].type = 0;
parser->gasmix[i].id = 0;
}
parser->diluent = UNDEFINED;
parser->ntanks = 0;
for (unsigned int i = 0; i < NTANKS; ++i) {
parser->tank[i].volume = 0;
parser->tank[i].workpressure = 0;
parser->tank[i].beginpressure = 0;
parser->tank[i].endpressure = 0;
parser->tank[i].transmitter = 0;
parser->tank[i].active = 0;
}
parser->vpm = 0;
parser->gf_lo = 0;
parser->gf_hi = 0;
parser->seawater = 0;
for (unsigned int i = 0; i < NSENSORS; ++i) {
parser->calibration[i] = 0;
}
parser->calibrated = 0;
return DC_STATUS_SUCCESS;
}
static dc_status_t
divesoft_freedom_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime)
{
dc_status_t status = DC_STATUS_SUCCESS;
divesoft_freedom_parser_t *parser = (divesoft_freedom_parser_t *) abstract;
const unsigned char *data = abstract->data;
status = divesoft_freedom_cache (parser);
if (status != DC_STATUS_SUCCESS)
return status;
unsigned int timestamp = array_uint32_le (data + 8);
dc_ticks_t ticks = (dc_ticks_t) timestamp + EPOCH;
if (!dc_datetime_gmtime (datetime, ticks))
return DC_STATUS_DATAFORMAT;
if (parser->version == HEADER_SIGNATURE_V2) {
datetime->timezone = ((signed short) array_uint16_le (data + 40)) * 60;
} else {
datetime->timezone = DC_TIMEZONE_NONE;
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
divesoft_freedom_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value)
{
dc_status_t status = DC_STATUS_SUCCESS;
divesoft_freedom_parser_t *parser = (divesoft_freedom_parser_t *) abstract;
status = divesoft_freedom_cache (parser);
if (status != DC_STATUS_SUCCESS)
return status;
dc_salinity_t *water = (dc_salinity_t *) value;
dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
dc_tank_t *tank = (dc_tank_t *) value;
dc_decomodel_t *decomodel = (dc_decomodel_t *) value;
if (value) {
switch (type) {
case DC_FIELD_DIVETIME:
*((unsigned int *) value) = parser->divetime;
break;
case DC_FIELD_MAXDEPTH:
*((double *) value) = parser->maxdepth / 100.0;
break;
case DC_FIELD_AVGDEPTH:
if (parser->version != HEADER_SIGNATURE_V2)
return DC_STATUS_UNSUPPORTED;
*((double *) value) = parser->avgdepth / 100.0;
break;
case DC_FIELD_TEMPERATURE_MINIMUM:
*((double *) value) = parser->temperature_min / 10.0;
break;
case DC_FIELD_ATMOSPHERIC:
*((double *) value) = parser->atmospheric * 10.0 / BAR;
break;
case DC_FIELD_SALINITY:
water->type = parser->seawater ? DC_WATER_SALT : DC_WATER_FRESH;
water->density = parser->seawater ? SEAWATER : FRESHWATER;
break;
case DC_FIELD_DIVEMODE:
switch (parser->divemode) {
case STMODE_OC:
*((dc_divemode_t *) value) = DC_DIVEMODE_OC;
break;
case STMODE_CCR:
case STMODE_MCCR:
case STMODE_BOCCR:
*((dc_divemode_t *) value) = DC_DIVEMODE_CCR;
break;
case STMODE_FREE:
*((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE;
break;
case STMODE_GAUGE:
*((dc_divemode_t *) value) = DC_DIVEMODE_GAUGE;
break;
case STMODE_ASCR:
case STMODE_PSCR:
*((dc_divemode_t *) value) = DC_DIVEMODE_SCR;
break;
case STMODE_UNKNOWN:
return DC_STATUS_UNSUPPORTED;
default:
return DC_STATUS_DATAFORMAT;
}
break;
case DC_FIELD_GASMIX_COUNT:
*((unsigned int *) value) = parser->ngasmixes;
break;
case DC_FIELD_GASMIX:
gasmix->helium = parser->gasmix[flags].helium / 100.0;
gasmix->oxygen = parser->gasmix[flags].oxygen / 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:
if (parser->tank[flags].volume > 990 ||
parser->tank[flags].workpressure > 400) {
tank->type = DC_TANKVOLUME_NONE;
tank->volume = 0.0;
tank->workpressure = 0.0;
} else {
tank->type = DC_TANKVOLUME_METRIC;
tank->volume = parser->tank[flags].volume / 10.0;
tank->workpressure = parser->tank[flags].workpressure;
}
tank->beginpressure = parser->tank[flags].beginpressure * 2.0;
tank->endpressure = parser->tank[flags].endpressure * 2.0;
tank->gasmix = flags;
break;
case DC_FIELD_DECOMODEL:
if (parser->vpm) {
decomodel->type = DC_DECOMODEL_VPM;
decomodel->conservatism = 0;
} else {
decomodel->type = DC_DECOMODEL_BUHLMANN;
decomodel->conservatism = 0;
decomodel->params.gf.low = parser->gf_lo;
decomodel->params.gf.high = parser->gf_hi;
}
break;
default:
return DC_STATUS_UNSUPPORTED;
}
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
divesoft_freedom_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
{
dc_status_t status = DC_STATUS_SUCCESS;
divesoft_freedom_parser_t *parser = (divesoft_freedom_parser_t *) abstract;
const unsigned char *data = abstract->data;
unsigned int size = abstract->size;
status = divesoft_freedom_cache (parser);
if (status != DC_STATUS_SUCCESS)
return status;
unsigned int time = UNDEFINED;
unsigned int initial = 0;
unsigned int offset = parser->headersize;
while (offset + RECORD_SIZE <= size) {
dc_sample_value_t sample = {0};
if (array_isequal(data + offset, RECORD_SIZE, 0xFF)) {
WARNING (abstract->context, "Skipping empty sample.");
offset += RECORD_SIZE;
continue;
}
unsigned int flags = array_uint32_le (data + offset);
unsigned int type = (flags & 0x0000000F) >> 0;
unsigned int timestamp = (flags & 0x001FFFF0) >> 4;
unsigned int id = (flags & 0x7FE00000) >> 21;
if (timestamp != time) {
if (timestamp < time && time != UNDEFINED) {
if (time - timestamp > 5) {
ERROR (abstract->context, "Timestamp moved backwards (%u %u).", timestamp, time);
return DC_STATUS_DATAFORMAT;
}
WARNING (abstract->context, "Timestamp moved backwards (%u %u).", timestamp, time);
offset += RECORD_SIZE;
continue;
}
time = timestamp;
sample.time = time;
if (callback) callback(DC_SAMPLE_TIME, sample, userdata);
}
if (!initial) {
if (parser->diluent != UNDEFINED) {
sample.gasmix = parser->diluent;
if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata);
}
initial = 1;
}
if (type == LREC_POINT) {
unsigned int depth = array_uint16_le (data + offset + 4);
unsigned int ppo2 = array_uint16_le (data + offset + 6);
sample.depth = depth / 100.0;
if (callback) callback(DC_SAMPLE_DEPTH, sample, userdata);
if (ppo2) {
sample.ppo2 = ppo2 * 10.0 / BAR;
if (callback) callback(DC_SAMPLE_PPO2, sample, userdata);
}
if (id == POINT_2) {
unsigned int orientation = array_uint32_le (data + offset + 8);
unsigned int heading = orientation & 0x1FF;
sample.bearing = heading;
if (callback) callback (DC_SAMPLE_BEARING, sample, userdata);
} else if (id == POINT_1 || id == POINT_1_OLD) {
unsigned int misc = array_uint32_le (data + offset + 8);
unsigned int ceiling = array_uint16_le (data + offset + 12);
unsigned int setpoint = data[offset + 15];
unsigned int ndl = (misc & 0x000003FF);
unsigned int tts = (misc & 0x000FFC00) >> 10;
unsigned int temp = (misc & 0x3FF00000) >> 20;
sample.temperature = (signed int) signextend (temp, 10) / 10.0;
if (callback) callback(DC_SAMPLE_TEMPERATURE, sample, userdata);
if (ceiling) {
sample.deco.type = DC_DECO_DECOSTOP;
sample.deco.time = 0;
sample.deco.depth = ceiling / 100.0;
} else {
sample.deco.type = DC_DECO_NDL;
sample.deco.time = ndl * 60;
sample.deco.depth = 0.0;
}
if (callback) callback(DC_SAMPLE_DECO, sample, userdata);
if (setpoint) {
sample.setpoint = setpoint / 100.0;
if (callback) callback(DC_SAMPLE_SETPOINT, sample, userdata);
}
}
} else if ((type >= LREC_MANIPULATION && type <= LREC_ACTIVITY) || type == LREC_INFO) {
unsigned int event = array_uint16_le (data + offset + 4);
if (event == EVENT_BOOKMARK) {
sample.event.type = SAMPLE_EVENT_BOOKMARK;
sample.event.time = 0;
sample.event.flags = 0;
sample.event.value = 0;
if (callback) callback(DC_SAMPLE_EVENT, sample, userdata);
} else if (event == EVENT_MIX_CHANGED || event == EVENT_DILUENT || event == EVENT_CHANGE_MODE) {
unsigned int o2 = data[offset + 6];
unsigned int he = data[offset + 7];
unsigned int mixtype = OC;
if (event == EVENT_DILUENT) {
mixtype = DILUENT;
} else if (event == EVENT_CHANGE_MODE) {
unsigned int mode = data[offset + 8];
if (divesoft_freedom_is_ccr (mode)) {
mixtype = DILUENT;
}
}
unsigned int idx = divesoft_freedom_find_gasmix (parser->gasmix, parser->ngasmixes, o2, he, mixtype);
if (idx >= parser->ngasmixes) {
ERROR (abstract->context, "Gas mix (%u/%u) not found.", o2, he);
return DC_STATUS_DATAFORMAT;
}
sample.gasmix = idx;
if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata);
} else if (event == EVENT_CNS) {
sample.cns = array_uint16_le (data + offset + 6) / 100.0;
if (callback) callback(DC_SAMPLE_CNS, sample, userdata);
} else if (event == EVENT_SETPOINT_MANUAL || event == EVENT_SETPOINT_AUTO) {
sample.setpoint = data[6] / 100.0;
if (callback) callback(DC_SAMPLE_SETPOINT, sample, userdata);
}
} else if (type == LREC_MEASURE) {
if (id == MEASURE_ID_AI_PRESSURE) {
for (unsigned int i = 0; i < NTANKS; ++i) {
unsigned int pressure = data[offset + 4 + i];
if (pressure == 0 || pressure == 0xFF)
continue;
unsigned int idx = divesoft_freedom_find_tank (parser->tank, parser->ntanks, i);
if (idx >= parser->ntanks) {
ERROR (abstract->context, "Tank %u not found.", idx);
return DC_STATUS_DATAFORMAT;
}
sample.pressure.tank = idx;
sample.pressure.value = pressure * 2.0;
if (callback) callback(DC_SAMPLE_PRESSURE, sample, userdata);
}
} else if (id == MEASURE_ID_OXYGEN) {
for (unsigned int i = 0; i < NSENSORS; ++i) {
unsigned int ppo2 = array_uint16_le (data + offset + 4 + i * 2);
if (ppo2 == 0 || ppo2 == 0xFFFF)
continue;
sample.ppo2 = ppo2 * 10.0 / BAR;
if (callback) callback(DC_SAMPLE_PPO2, sample, userdata);
}
} else if (id == MEASURE_ID_OXYGEN_MV) {
for (unsigned int i = 0; i < NSENSORS; ++i) {
unsigned int value = array_uint16_le (data + offset + 4 + i * 2);
unsigned int state = data[offset + 12 + i];
if (!parser->calibrated || state == SENSTAT_UNCALIBRATED ||
state == SENSTAT_NOT_EXIST)
continue;
sample.ppo2 = value / 100.0 * parser->calibration[i] / BAR;
if (callback) callback(DC_SAMPLE_PPO2, sample, userdata);
}
}
} else if (type == LREC_STATE) {
}
offset += RECORD_SIZE;
}
return DC_STATUS_SUCCESS;
}