diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index f6a7b1d..7d3a420 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -33,9 +33,30 @@ 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_FINAL 0xFF + #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 @@ -47,10 +68,13 @@ #define IMPERIAL 1 #define NGASMIXES 10 +#define NRECORDS 7 #define PREDATOR 2 #define PETREL 3 +#define UNDEFINED 0xFFFFFFFF + typedef struct shearwater_predator_parser_t shearwater_predator_parser_t; struct shearwater_predator_parser_t { @@ -60,15 +84,22 @@ struct shearwater_predator_parser_t { 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 oxygen[NGASMIXES]; unsigned int helium[NGASMIXES]; unsigned int calibrated; double calibration[3]; dc_divemode_t mode; + 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); @@ -76,6 +107,8 @@ static dc_status_t shearwater_predator_parser_get_datetime (dc_parser_t *abstrac 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, @@ -141,9 +174,15 @@ shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsig 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->oxygen[i] = 0; @@ -154,6 +193,9 @@ shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsig parser->calibration[i] = 0.0; } parser->mode = DC_DIVEMODE_OC; + parser->units = METRIC; + parser->density = 1025; + parser->atmospheric = ATM / (BAR / 1000); *out = (dc_parser_t *) parser; @@ -182,9 +224,15 @@ shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char // 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->oxygen[i] = 0; @@ -195,6 +243,9 @@ shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char parser->calibration[i] = 0.0; } parser->mode = DC_DIVEMODE_OC; + parser->units = METRIC; + parser->density = 1025; + parser->atmospheric = ATM / (BAR / 1000); return DC_STATUS_SUCCESS; } @@ -203,13 +254,15 @@ shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char 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; - unsigned int size = abstract->size; - if (size < 2 * SZ_BLOCK) - return DC_STATUS_DATAFORMAT; + // 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 + 12); + unsigned int ticks = array_uint32_be (data + parser->opening[0] + 12); if (!dc_datetime_gmtime (datetime, ticks)) return DC_STATUS_DATAFORMAT; @@ -231,26 +284,48 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) return DC_STATUS_SUCCESS; } - unsigned int headersize = SZ_BLOCK; - unsigned int footersize = SZ_BLOCK; - if (size < headersize + footersize) { + // Verify the minimum length. + if (size < 2) { ERROR (abstract->context, "Invalid data length."); return DC_STATUS_DATAFORMAT; } - // 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) - logversion = data[127]; - - // Adjust the footersize for the final block. - if (parser->petrel || array_uint16_be (data + size - footersize) == 0xFFFD) { - footersize += SZ_BLOCK; + // 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 < NRECORDS; ++i) { + parser->opening[i] = 0; + parser->closing[i] = size - footersize; + } } // Default dive mode. @@ -271,46 +346,76 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) continue; } - // Status flags. - unsigned int status = data[offset + 11]; - if ((status & OC) == 0) { - mode = DC_DIVEMODE_CCR; - } + // Get the record type. + unsigned int type = pnf ? data[offset] : LOG_RECORD_DIVE_SAMPLE; - // Gaschange. - unsigned int o2 = data[offset + 7]; - unsigned int he = data[offset + 8]; - if (o2 != o2_previous || he != he_previous) { - // Find the gasmix in the list. - unsigned int idx = 0; - while (idx < ngasmixes) { - if (o2 == oxygen[idx] && he == helium[idx]) - break; - idx++; + if (type == LOG_RECORD_DIVE_SAMPLE) { + // Status flags. + unsigned int status = data[offset + 11 + pnf]; + if ((status & OC) == 0) { + mode = DC_DIVEMODE_CCR; } - // 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; + // 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 == oxygen[idx] && he == helium[idx]) + break; + idx++; } - oxygen[idx] = o2; - helium[idx] = he; - ngasmixes = idx + 1; - } - o2_previous = o2; - he_previous = he; + // 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; + } + oxygen[idx] = o2; + helium[idx] = he; + ngasmixes = idx + 1; + } + + o2_previous = o2; + he_previous = he; + } + } else if (type == LOG_RECORD_FREEDIVE_SAMPLE) { + // Freedive record + mode = DC_DIVEMODE_FREEDIVE; + } else if (type >= LOG_RECORD_OPENING_0 && type <= LOG_RECORD_OPENING_7) { + // Opening record + parser->opening[type - LOG_RECORD_OPENING_0] = offset; + } 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. + for (unsigned int i = 0; i < NRECORDS - 2; ++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; + } + } + + // 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 = data[parser->opening[4] + (pnf ? 16 : 127)]; + // 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 + 87 + i * 2); + 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 @@ -319,7 +424,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 << i)) { if (calibration == 2100) { ndefaults++; } @@ -335,10 +440,11 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) WARNING (abstract->context, "Disabled all O2 sensors due to a default calibration value."); parser->calibrated = 0; } else { - parser->calibrated = data[86]; + parser->calibrated = data[base]; } // Cache the data for later use. + parser->pnf = pnf; parser->logversion = logversion; parser->headersize = headersize; parser->footersize = footersize; @@ -348,6 +454,9 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) parser->helium[i] = helium[i]; } parser->mode = mode; + 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; @@ -359,33 +468,30 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ 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; - // Get the offset to the footer record. - unsigned int footer = size - parser->footersize; - - // Get the unit system. - unsigned int units = data[8]; - dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_salinity_t *water = (dc_salinity_t *) value; - unsigned int density = 0; if (value) { switch (type) { case DC_FIELD_DIVETIME: - *((unsigned int *) value) = array_uint16_be (data + footer + 6) * 60; + 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 (units == IMPERIAL) - *((double *) value) = array_uint16_be (data + footer + 4) * FEET; + if (parser->units == IMPERIAL) + *((double *) value) = array_uint16_be (data + parser->closing[0] + 4) * FEET; else - *((double *) value) = array_uint16_be (data + footer + 4); + *((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; @@ -396,15 +502,14 @@ 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); - if (density == 1000) + if (parser->density == 1000) water->type = DC_WATER_FRESH; else water->type = DC_WATER_SALT; - water->density = density; + water->density = parser->density; break; case DC_FIELD_ATMOSPHERIC: - *((double *) value) = array_uint16_be (data + 47) / 1000.0; + *((double *) value) = parser->atmospheric / 1000.0; break; case DC_FIELD_DIVEMODE: *((dc_divemode_t *) value) = parser->mode; @@ -431,13 +536,22 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal if (rc != DC_STATUS_SUCCESS) return rc; - // Get the unit system. - unsigned int units = data[8]; - // 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) { + 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) { @@ -449,142 +563,175 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal continue; } - // Time (seconds). - time += 10; - sample.time = time; - if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + // Get the record type. + unsigned int type = pnf ? data[offset] : LOG_RECORD_DIVE_SAMPLE; - // Depth (1/10 m or ft). - unsigned int depth = array_uint16_be (data + offset); - if (units == IMPERIAL) - sample.depth = depth * FEET / 10.0; - else - sample.depth = depth / 10.0; - if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + if (type == LOG_RECORD_DIVE_SAMPLE) { + // Time (seconds). + time += interval; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); - // Temperature (°C or °F). - int temperature = (signed char) data[offset + 13]; - if (temperature < 0) { - // Fix negative temperatures. - temperature += 102; - if (temperature > 0) { - temperature = 0; - } - } - if (units == IMPERIAL) - sample.temperature = (temperature - 32.0) * (5.0 / 9.0); - else - sample.temperature = temperature; - if (callback) callback (DC_SAMPLE_TEMPERATURE, 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); - // Status flags. - unsigned int status = data[offset + 11]; - - if ((status & OC) == 0) { - // PPO2 - if ((status & PPO2_EXTERNAL) == 0) { -#ifdef SENSOR_AVERAGE - sample.ppo2 = data[offset + 6] / 100.0; - if (callback) callback (DC_SAMPLE_PPO2, sample, userdata); -#else - sample.ppo2 = data[offset + 12] * parser->calibration[0]; - if (callback && (parser->calibrated & 0x01)) callback (DC_SAMPLE_PPO2, sample, userdata); - - sample.ppo2 = data[offset + 14] * parser->calibration[1]; - if (callback && (parser->calibrated & 0x02)) callback (DC_SAMPLE_PPO2, sample, userdata); - - sample.ppo2 = data[offset + 15] * parser->calibration[2]; - if (callback && (parser->calibrated & 0x04)) callback (DC_SAMPLE_PPO2, sample, userdata); -#endif - } - - // Setpoint - if (parser->petrel) { - sample.setpoint = data[offset + 18] / 100.0; - } else { - if (status & SETPOINT_HIGH) { - sample.setpoint = data[18] / 100.0; - } else { - sample.setpoint = data[17] / 100.0; + // 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 (callback) callback (DC_SAMPLE_SETPOINT, sample, userdata); - } - - // CNS - if (parser->petrel) { - sample.cns = data[offset + 22] / 100.0; - if (callback) callback (DC_SAMPLE_CNS, sample, userdata); - } - - // Gaschange. - unsigned int o2 = data[offset + 7]; - unsigned int he = data[offset + 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 + 2); - if (decostop) { - sample.deco.type = DC_DECO_DECOSTOP; - if (units == IMPERIAL) - sample.deco.depth = decostop * FEET; + if (parser->units == IMPERIAL) + sample.temperature = (temperature - 32.0) * (5.0 / 9.0); else - sample.deco.depth = decostop; - } else { - sample.deco.type = DC_DECO_NDL; - sample.deco.depth = 0.0; - } - sample.deco.time = data[offset + 9] * 60; - if (callback) callback (DC_SAMPLE_DECO, sample, userdata); + sample.temperature = temperature; + if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); - // for logversion 7 and newer (introduced for Perdix AI) - // detect tank pressure - if (parser->logversion >= 7) { - // 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 + 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); - if (pressure < 0xFFF0) { - pressure &= 0x0FFF; - sample.pressure.tank = 1; - sample.pressure.value = pressure * 2 * PSI / BAR; - if (callback) callback (DC_SAMPLE_PRESSURE, 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); } - // 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 + 21] < 0xF0) { - sample.rbt = data[offset + 21]; - if (callback) callback (DC_SAMPLE_RBT, 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) { + // 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 + 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 + pnf + 19); + if (pressure < 0xFFF0) { + pressure &= 0x0FFF; + sample.pressure.tank = 1; + 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_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 = (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); } }