Shearwater Petrel Native Format parsing

This will allow parsing dives from the Shearwater Teric, but depending on the
firmware could also be used on older models.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
This commit is contained in:
Dirk Hohndel 2018-09-08 09:58:08 -07:00
parent 0cbcc0518c
commit 34785f55ff

View File

@ -40,6 +40,30 @@
dc_parser_isinstance((parser), &shearwater_predator_parser_vtable) || \
dc_parser_isinstance((parser), &shearwater_petrel_parser_vtable))
// Petrel Native Format constants
#define PNF_BLOCKSIZE 0x20
#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_FINAL 0xFF
#define NUM_BLOCK_IDS 0x28
// constant for the older Predator and Predator-like formats
#define SZ_BLOCK 0x80
#define SZ_SAMPLE_PREDATOR 0x10
#define SZ_SAMPLE_PETREL 0x20
@ -65,6 +89,7 @@ struct shearwater_predator_parser_t {
dc_parser_t base;
unsigned int model;
unsigned int petrel;
unsigned int pnf;
unsigned int samplesize;
// Cached fields.
unsigned int cached;
@ -79,6 +104,9 @@ struct shearwater_predator_parser_t {
unsigned int serial;
dc_divemode_t mode;
/* Block addresses for PNF */
unsigned int block_offset[NUM_BLOCK_IDS];
/* String fields */
dc_field_string_t strings[MAXSTRINGS];
};
@ -220,6 +248,7 @@ shearwater_predator_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *d
{
const unsigned char *data = abstract->data;
unsigned int size = abstract->size;
shearwater_predator_parser_t *parser = (shearwater_predator_parser_t *) abstract;
if (size < 2 * SZ_BLOCK)
return DC_STATUS_DATAFORMAT;
@ -332,18 +361,21 @@ add_battery_info(shearwater_predator_parser_t *parser, const char *desc, unsigne
static void
add_deco_model(shearwater_predator_parser_t *parser, const unsigned char *data)
{
switch (data[67]) {
unsigned int idx_deco_model = parser->pnf ? parser->block_offset[LOG_RECORD_OPENING_2] + 18 : 67;
unsigned int idx_gfs = parser->pnf ? parser->block_offset[LOG_RECORD_OPENING_3] + 5 : 85;
switch (data[idx_deco_model]) {
case 0:
add_string_fmt(parser, "Deco model", "GF %u/%u", data[4], data[5]);
break;
case 1:
add_string_fmt(parser, "Deco model", "VPM-B +%u", data[68]);
add_string_fmt(parser, "Deco model", "VPM-B +%u", data[idx_deco_model + 1]);
break;
case 2:
add_string_fmt(parser, "Deco model", "VPM-B/GFS +%u %u%%", data[68], data[85]);
add_string_fmt(parser, "Deco model", "VPM-B/GFS +%u %u%%", data[idx_deco_model + 1], data[idx_gfs]);
break;
default:
add_string_fmt(parser, "Deco model", "Unknown model %d", data[67]);
add_string_fmt(parser, "Deco model", "Unknown model %d", data[idx_deco_model]);
}
}
@ -353,7 +385,8 @@ add_battery_type(shearwater_predator_parser_t *parser, const unsigned char *data
if (parser->logversion < 7)
return;
switch (data[120]) {
unsigned int idx_battery_type = parser->pnf ? parser->block_offset[LOG_RECORD_OPENING_4] + 9 : 120;
switch (data[idx_battery_type]) {
case 1:
add_string(parser, "Battery type", "1.5V Alkaline");
break;
@ -370,7 +403,7 @@ add_battery_type(shearwater_predator_parser_t *parser, const unsigned char *data
add_string(parser, "Battery type", "3.7V Li-Ion");
break;
default:
add_string_fmt(parser, "Battery type", "unknown type %d", data[120]);
add_string_fmt(parser, "Battery type", "unknown type %d", data[idx_battery_type]);
break;
}
}
@ -386,24 +419,66 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
return DC_STATUS_SUCCESS;
}
// the log formats are very similar - but the Petrel Native Format (PNF)
// is organized differently. There everything is in 32 byte (PNF_BLOCKSIZE) blocks
// and the offsets of various fields are different. It still seems to make sense
// to just all parse it in one place
// header and footer are concepts of the Predator and Predator-like formats
unsigned int headersize = SZ_BLOCK;
unsigned int footersize = SZ_BLOCK;
if (size < headersize + footersize) {
ERROR (abstract->context, "Invalid data length.");
return DC_STATUS_DATAFORMAT;
}
// remember if this is a Petrel Native Format download
// if yes, we need different ways to access the various data fields
// for samples it's simple, they are just offset by one (so we can use pnf as offset)
// for header and footer data it's more complicated because of the new block structure
unsigned int pnf = parser->pnf = data[0] == 0x10 ? 1 : 0;
// sanity check on the log format
// is this a Predator-like or Petrel-native (or Teric style) log?
if (parser->petrel == 0 && pnf) {
ERROR (abstract->context, "This is a Petrel-native log, but we claim this is a Predator");
return DC_STATUS_DATAFORMAT;
}
memset (parser->block_offset, 0, NUM_BLOCK_IDS * sizeof(unsigned int));
if (pnf) {
// find the offsets of the various header and footer blocks
int i = 0, j = 0;
while (i < size) {
for (j = LOG_RECORD_OPENING_0; j < NUM_BLOCK_IDS; j++) {
if (data[i] == j)
parser->block_offset[j] = i;
if (j == LOG_RECORD_OPENING_7)
j = LOG_RECORD_CLOSING_0 - 1;
}
i += PNF_BLOCKSIZE;
}
}
// there is a small risk we are taking here... if the log were damaged and one or
// more of the blocks were missing, we'll default to looking into block 0 and
// report bogus data. This may be worth testing for?
// 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 = 6;
if (data[127] > 6)
if (!pnf && data[127] > 6)
logversion = data[127];
if (pnf)
logversion = data[parser->block_offset[LOG_RECORD_OPENING_4] + 16];
INFO(abstract->context, "Shearwater log version %u\n", logversion);
memset(parser->strings, 0, sizeof(parser->strings));
add_string_fmt(parser, "Logversion", "%d%s", logversion, pnf ? "(PNF)" : "");
// Adjust the footersize for the final block.
if (parser->petrel || array_uint16_be (data + size - footersize) == 0xFFFD) {
if (parser->petrel == 1 || array_uint16_be (data + size - footersize) == 0xFFFD) {
footersize += SZ_BLOCK;
if (size < headersize + footersize) {
ERROR (abstract->context, "Invalid data length.");
@ -423,9 +498,16 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
// Transmitter battery levels
unsigned int t1_battery = 0, t2_battery = 0;
unsigned int offset = headersize;
unsigned int length = size - footersize;
// the indices in the sample block are offset by 1 in PNF
unsigned int offset = pnf ? 0 : headersize;
unsigned int length = pnf ? size : size - footersize;
while (offset < length) {
// Ignore blocks that aren't dive samples
if (pnf && data[offset] != LOG_RECORD_DIVE_SAMPLE) {
offset += parser->samplesize;
continue;
}
// Ignore empty samples.
if (array_isequal (data + offset, parser->samplesize, 0x00)) {
offset += parser->samplesize;
@ -433,14 +515,14 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
}
// Status flags.
unsigned int status = data[offset + 11];
unsigned int status = data[offset + 11 + pnf];
if ((status & OC) == 0) {
mode = DC_DIVEMODE_CCR;
}
// Gaschange.
unsigned int o2 = data[offset + 7];
unsigned int he = data[offset + 8];
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;
@ -468,17 +550,24 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
// Transmitter battery levels
if (logversion >= 7) {
// T1 at offset 27, T2 at offset 19
t1_battery |= battery_state(data + offset + 27);
t2_battery |= battery_state(data + offset + 19);
t1_battery |= battery_state(data + offset + 27 + pnf);
t2_battery |= battery_state(data + offset + 19 + pnf);
}
offset += parser->samplesize;
}
// for header and footer indices we use a variable base that is set to the
// correct value based on the log type
unsigned int base = 0;
// Cache sensor calibration for later use
unsigned int nsensors = 0, ndefaults = 0;
// calibration value for sensors
base = pnf ? parser->block_offset[LOG_RECORD_OPENING_3] + 7 : 87;
for (size_t i = 0; i < 3; ++i) {
unsigned int calibration = array_uint16_be(data + 87 + i * 2);
unsigned int calibration = array_uint16_be(data + base + i * 2);
parser->calibration[i] = calibration / 100000.0;
if (parser->model == PREDATOR) {
// The Predator expects the mV output of the cells to be
@ -487,7 +576,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
// sensors lines up and matches the average.
parser->calibration[i] *= 2.2;
}
if (data[86] & (1 << i)) {
if (data[base - 1] & (1 << i)) {
if (calibration == 2100) {
ndefaults++;
}
@ -505,7 +594,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
if (mode != DC_DIVEMODE_OC)
add_string(parser, "PPO2 source", "voted/averaged");
} else {
parser->calibrated = data[86];
parser->calibrated = data[base - 1];
if (mode != DC_DIVEMODE_OC)
add_string(parser, "PPO2 source", "cells");
}
@ -521,6 +610,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
}
parser->mode = mode;
add_string_fmt(parser, "Serial", "%08x", parser->serial);
// bytes 1-31 are identical in all formats
add_string_fmt(parser, "FW Version", "%2x", data[19]);
add_deco_model(parser, data);
add_battery_type(parser, data);
@ -556,16 +646,26 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ
dc_field_string_t *string = (dc_field_string_t *) value;
unsigned int density = 0;
// the first 32 bytes of the footer and closing block 0 are identical
unsigned int block_start = parser->pnf ? parser->block_offset[LOG_RECORD_CLOSING_0] : footer;
if (value) {
unsigned int idx;
switch (type) {
case DC_FIELD_DIVETIME:
*((unsigned int *) value) = array_uint16_be (data + footer + 6) * 60;
// FIXME: this is wrong based on the documentation I received
// it should be a 3 byte value in offsets 6-8 that is dive length in seconds
*((unsigned int *) value) = array_uint16_be (data + block_start + 6) * 60;
break;
case DC_FIELD_MAXDEPTH:
if (units == IMPERIAL)
*((double *) value) = array_uint16_be (data + footer + 4) * FEET;
*((double *) value) = array_uint16_be (data + block_start + 4) * FEET;
else
*((double *) value) = array_uint16_be (data + footer + 4);
*((double *) value) = array_uint16_be (data + block_start + 4);
// according to the documentation this should have been in tenth of a meter
// before, but the existing code for the Predator-like format didn't have
// that adjustment, so let's just do that for PNF (where we definitely need it).
if (parser->pnf)
*((double *)value) /= 10.0;
break;
case DC_FIELD_GASMIX_COUNT:
*((unsigned int *) value) = parser->ngasmixes;
@ -576,7 +676,8 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ
gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium;
break;
case DC_FIELD_SALINITY:
density = array_uint16_be (data + 83);
idx = parser->pnf ? parser->block_offset[LOG_RECORD_OPENING_3] + 3 : 83;
density = array_uint16_be (data + idx);
if (density == 1000)
water->type = DC_WATER_FRESH;
else
@ -584,6 +685,7 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ
water->density = density;
break;
case DC_FIELD_ATMOSPHERIC:
idx = parser->pnf ? parser->block_offset[LOG_RECORD_OPENING_1] + 16 : 47;
*((double *) value) = array_uint16_be (data + 47) / 1000.0;
break;
case DC_FIELD_DIVEMODE:
@ -627,12 +729,27 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
unsigned int o2_previous = 0, he_previous = 0;
unsigned int time = 0;
unsigned int offset = parser->headersize;
unsigned int length = size - parser->footersize;
unsigned int pnf = parser->pnf;
unsigned int offset = pnf ? 0 : parser->headersize;
unsigned int length = pnf ? size : size - parser->footersize;
unsigned int time_increment = 10;
// the time increment is now given in ms. not sure how we'll deal with that since all we do is full seconds
if (pnf && parser->logversion >= 9)
time_increment = array_uint16_be (data + parser->block_offset[LOG_RECORD_OPENING_5] + 23) / 1000;
while (offset < length) {
dc_sample_value_t sample = {0};
// stop parsing if we see the end block
if (pnf && data[offset] == LOG_RECORD_FINAL && data[offset + 1] == 0xFD)
break;
// Ignore blocks that aren't dive samples
if (pnf && data[offset] != LOG_RECORD_DIVE_SAMPLE) {
offset += parser->samplesize;
continue;
}
// Ignore empty samples.
if (array_isequal (data + offset, parser->samplesize, 0x00)) {
offset += parser->samplesize;
@ -640,12 +757,12 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
}
// Time (seconds).
time += 10;
time += time_increment;
sample.time = time;
if (callback) callback (DC_SAMPLE_TIME, sample, userdata);
// Depth (1/10 m or ft).
unsigned int depth = array_uint16_be (data + offset);
unsigned int depth = array_uint16_be (data + pnf + offset);
if (units == IMPERIAL)
sample.depth = depth * FEET / 10.0;
else
@ -653,7 +770,7 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
// Temperature (°C or °F).
int temperature = (signed char) data[offset + 13];
int temperature = (signed char) data[offset + pnf + 13];
if (temperature < 0) {
// Fix negative temperatures.
temperature += 102;
@ -668,30 +785,31 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
// Status flags.
unsigned int status = data[offset + 11];
unsigned int status = data[offset + pnf + 11];
if ((status & OC) == 0) {
// PPO2
if ((status & PPO2_EXTERNAL) == 0) {
if (!parser->calibrated) {
sample.ppo2 = data[offset + 6] / 100.0;
sample.ppo2 = data[offset + pnf + 6] / 100.0;
if (callback) callback (DC_SAMPLE_PPO2, sample, userdata);
} else {
sample.ppo2 = data[offset + 12] * parser->calibration[0];
sample.ppo2 = data[offset + pnf + 12] * parser->calibration[0];
if (callback && (parser->calibrated & 0x01)) callback (DC_SAMPLE_PPO2, sample, userdata);
sample.ppo2 = data[offset + 14] * parser->calibration[1];
sample.ppo2 = data[offset + pnf + 14] * parser->calibration[1];
if (callback && (parser->calibrated & 0x02)) callback (DC_SAMPLE_PPO2, sample, userdata);
sample.ppo2 = data[offset + 15] * parser->calibration[2];
sample.ppo2 = data[offset + pnf + 15] * parser->calibration[2];
if (callback && (parser->calibrated & 0x04)) callback (DC_SAMPLE_PPO2, sample, userdata);
}
}
// Setpoint
if (parser->petrel) {
sample.setpoint = data[offset + 18] / 100.0;
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 {
@ -703,13 +821,13 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
// CNS
if (parser->petrel) {
sample.cns = data[offset + 22] / 100.0;
sample.cns = data[offset + pnf + 22] / 100.0;
if (callback) callback (DC_SAMPLE_CNS, sample, userdata);
}
// Gaschange.
unsigned int o2 = data[offset + 7];
unsigned int he = data[offset + 8];
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) {
@ -724,7 +842,7 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
}
// Deco stop / NDL.
unsigned int decostop = array_uint16_be (data + offset + 2);
unsigned int decostop = array_uint16_be (data + offset + pnf + 2);
if (decostop) {
sample.deco.type = DC_DECO_DECOSTOP;
if (units == IMPERIAL)
@ -735,7 +853,7 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
sample.deco.type = DC_DECO_NDL;
sample.deco.depth = 0.0;
}
sample.deco.time = data[offset + 9] * 60;
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)
@ -750,14 +868,14 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
// 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 + 27);
unsigned int pressure = array_uint16_be (data + offset + pnf + 27);
if (pressure < 0xFFF0) {
pressure &= 0x0FFF;
sample.pressure.tank = 0;
sample.pressure.value = pressure * 2 * PSI / BAR;
if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata);
}
pressure = array_uint16_be (data + offset + 19);
pressure = array_uint16_be (data + offset + pnf + 19);
if (pressure < 0xFFF0) {
pressure &= 0x0FFF;
sample.pressure.tank = 1;
@ -772,8 +890,8 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
// 0xFD Not available in current mode
// 0xFC Not available because of DECO
// 0xFB Tank size or max pressure havent been set up
if (data[offset + 21] < 0xF0) {
sample.rbt = data[offset + 21];
if (data[offset + pnf + 21] < 0xF0) {
sample.rbt = data[offset + pnf + 21];
if (callback) callback (DC_SAMPLE_RBT, sample, userdata);
}
}