Add support for the new Excursion v6+ firmware

The new Excursion v6 firmware supports some new commands for accessing
the dive index, and also uses a completely new data format. To preserve
backwards compatibility in the download logic to some extent, some
critical fields such as the profile length, remain stored at identical
offsets.

The new data format now contains a version field to allow for future
modifications. This version field is located at byte offset 3, which
corresponds to the highest byte of the 32 bit dive number in the old
format. Thus, unless someone manages to reach 16M dives, this field will
always be zero for the old format.

Co-authored-by: Ryan Gardner <ryebrye@gmail.com>
This commit is contained in:
Jef Driesen 2022-09-08 08:58:28 +02:00
parent 78373d827b
commit 201be561d4
2 changed files with 633 additions and 55 deletions

View File

@ -32,7 +32,8 @@
#define MAXPACKET 255
#define HEADERSIZE 156
#define HEADERSIZE_MIN 128
#define HEADERSIZE_V0 156
#define NSTEPS 1000
#define STEP(i,n) (NSTEPS * (i) / (n))
@ -58,6 +59,11 @@
#define GRP_DIVE 0xC0
#define CMD_DIVE_HEADER 0x02
#define CMD_DIVE_PROFILE 0x03
#define CMD_DIVE_COUNT 0x05
#define CMD_DIVE_INDEX_LAST 0x06
#define CMD_DIVE_INDEX_FIRST 0x07
#define CMD_DIVE_INDEX_PREVIOUS 0x08
#define CMD_DIVE_INDEX_NEXT 0x09
typedef struct deepsix_excursion_device_t {
dc_device_t base;
@ -216,8 +222,8 @@ deepsix_excursion_device_open (dc_device_t **out, dc_context_t *context, dc_iost
goto error_free;
}
// Set the timeout for receiving data (1000ms).
status = dc_iostream_set_timeout (device->iostream, 1000);
// Set the timeout for receiving data (3000ms).
status = dc_iostream_set_timeout (device->iostream, 3000);
if (status != DC_STATUS_SUCCESS) {
ERROR (context, "Failed to set the timeout.");
goto error_free;
@ -300,17 +306,41 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call
devinfo.serial = array_convert_str2num (rsp_serial + 3, sizeof(rsp_serial) - 3);
device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo);
// Firmware version 6+ uses the new commands.
unsigned int fw6 = memcmp(rsp_software, "D01", 3) == 0 && rsp_software[4] >= '6';
unsigned int ndives = 0, last = 0;
if (fw6) {
// Read the number of dives.
unsigned char rsp_ndives[2] = {0};
status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_COUNT, DIR_READ, NULL, 0, rsp_ndives, sizeof(rsp_ndives), NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to read the number of dives");
return status;
}
// Read the index of the last dive.
const unsigned char cmd_index[2] = {0};
unsigned char rsp_index[2] = {0};
status = deepsix_excursion_transfer (device, GRP_INFO, CMD_INFO_LASTDIVE, DIR_READ, cmd_index, sizeof(cmd_index), rsp_index, sizeof(rsp_index), NULL);
unsigned char rsp_last[4] = {0};
status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_INDEX_LAST, DIR_READ, NULL, 0, rsp_last, sizeof(rsp_last), NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to read the last dive index.");
return status;
}
// Calculate the number of dives.
unsigned int ndives = array_uint16_le (rsp_index);
ndives = array_uint16_le (rsp_ndives);
last = array_uint16_le (rsp_last);
} else {
// Read the index of the last dive.
const unsigned char cmd_last[2] = {0};
unsigned char rsp_last[2] = {0};
status = deepsix_excursion_transfer (device, GRP_INFO, CMD_INFO_LASTDIVE, DIR_READ, cmd_last, sizeof(cmd_last), rsp_last, sizeof(rsp_last), NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to read the last dive index.");
return status;
}
ndives = last = array_uint16_le (rsp_last);
}
// Update and emit a progress event.
progress.current = 1 * NSTEPS;
@ -323,32 +353,58 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call
return DC_STATUS_NOMEMORY;
}
unsigned int number = last;
for (unsigned int i = 0; i < ndives; ++i) {
unsigned int number = ndives - i;
if (fw6) {
if (i > 0) {
const unsigned char cmd_previous[] = {
(number ) & 0xFF,
(number >> 8) & 0xFF,
(number >> 16) & 0xFF,
(number >> 24) & 0xFF};
unsigned char rsp_previous[4] = {0};
status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_INDEX_PREVIOUS, DIR_READ,
cmd_previous, sizeof(cmd_previous), rsp_previous, sizeof(rsp_previous), NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to read the previous dive index");
return status;
}
number = array_uint32_le (rsp_previous);
}
} else {
number = ndives - i;
}
unsigned int headersize = 0;
const unsigned char cmd_header[] = {
(number ) & 0xFF,
(number >> 8) & 0xFF};
unsigned char rsp_header[HEADERSIZE] = {0};
status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_HEADER, DIR_READ, cmd_header, sizeof(cmd_header), rsp_header, sizeof(rsp_header), NULL);
unsigned char rsp_header[MAXPACKET] = {0};
status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_HEADER, DIR_READ,
cmd_header, sizeof(cmd_header), rsp_header, sizeof(rsp_header), &headersize);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to read the dive header.");
goto error_free;
}
if (headersize < HEADERSIZE_MIN || headersize != (fw6 ? rsp_header[2] : HEADERSIZE_V0)) {
ERROR (abstract->context, "Unexpected size of the dive header (%u).", headersize);
goto error_free;
}
if (memcmp(rsp_header + FP_OFFSET, device->fingerprint, sizeof(device->fingerprint)) == 0)
break;
unsigned int length = array_uint32_le (rsp_header + 8);
// Update and emit a progress event.
progress.current = (i + 1) * NSTEPS + STEP(sizeof(rsp_header), sizeof(rsp_header) + length);
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
progress.current = (i + 1) * NSTEPS + STEP(headersize, headersize + length);
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
dc_buffer_clear(buffer);
dc_buffer_reserve(buffer, sizeof(rsp_header) + length);
dc_buffer_reserve(buffer, headersize + length);
if (!dc_buffer_append(buffer, rsp_header, sizeof(rsp_header))) {
if (!dc_buffer_append(buffer, rsp_header, headersize)) {
ERROR (abstract->context, "Insufficient buffer space available.");
status = DC_STATUS_NOMEMORY;
goto error_free;
@ -365,7 +421,8 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call
(offset >> 16) & 0xFF,
(offset >> 24) & 0xFF};
unsigned char rsp_profile[MAXPACKET] = {0};
status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_PROFILE, DIR_READ, cmd_profile, sizeof(cmd_profile), rsp_profile, sizeof(rsp_profile), &len);
status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_PROFILE, DIR_READ,
cmd_profile, sizeof(cmd_profile), rsp_profile, sizeof(rsp_profile), &len);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to read the dive profile.");
goto error_free;
@ -378,8 +435,8 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call
}
// Update and emit a progress event.
progress.current = (i + 1) * NSTEPS + STEP(sizeof(rsp_header) + offset + n, sizeof(rsp_header) + length);
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
progress.current = (i + 1) * NSTEPS + STEP(headersize + offset + n, headersize + length);
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
if (!dc_buffer_append(buffer, rsp_profile, n)) {
ERROR (abstract->context, "Insufficient buffer space available.");

View File

@ -31,7 +31,11 @@
#include "parser-private.h"
#include "array.h"
#define HEADERSIZE 156
#define HEADERSIZE_MIN 128
#define MAX_SAMPLES 7
#define MAX_EVENTS 7
#define MAX_GASMIXES 20
#define ALARM 0x0001
#define TEMPERATURE 0x0002
@ -39,20 +43,81 @@
#define CEILING 0x0004
#define CNS 0x0005
#define DENSITY 1024.0
#define SAMPLE_TEMPERATURE 0
#define SAMPLE_DECO_NDL 1
#define SAMPLE_CNS 2
#define EVENT_CHANGE_GAS 7
#define EVENT_ALARMS 8
#define EVENT_CHANGE_SETPOINT 9
#define EVENT_SAMPLES_MISSED 10
#define EVENT_RESERVED 15
#define ALARM_ASCENTRATE 0
#define ALARM_CEILING 1
#define ALARM_PO2 2
#define ALARM_MAXDEPTH 3
#define ALARM_DIVETIME 4
#define ALARM_CNS 5
#define DECOSTOP 0x02
#define SAFETYSTOP 0x04
#define DENSITY 1024
#define UNDEFINED 0xFFFFFFFF
#define FWVERSION(major,minor) ( \
((((major) + '0') & 0xFF) << 8) | \
((minor) & 0xFF))
typedef struct deepsix_excursion_layout_t {
unsigned int headersize;
unsigned int version;
unsigned int divemode;
unsigned int samplerate;
unsigned int salinity;
unsigned int datetime;
unsigned int divetime;
unsigned int maxdepth;
unsigned int temperature_min;
unsigned int avgdepth;
unsigned int firmware;
unsigned int temperature_surf;
unsigned int atmospheric;
unsigned int gf;
} deepsix_excursion_layout_t;
typedef struct deepsix_excursion_gasmix_t {
unsigned int id;
unsigned int oxygen;
unsigned int helium;
} deepsix_excursion_gasmix_t;
typedef struct deepsix_excursion_sample_info_t {
unsigned int type;
unsigned int divisor;
unsigned int size;
} deepsix_excursion_sample_info_t;
typedef struct deepsix_excursion_event_info_t {
unsigned int type;
unsigned int size;
} deepsix_excursion_event_info_t;
typedef struct deepsix_excursion_parser_t {
dc_parser_t base;
unsigned int cached;
unsigned int ngasmixes;
deepsix_excursion_gasmix_t gasmix[MAX_GASMIXES];
} deepsix_excursion_parser_t;
static dc_status_t deepsix_excursion_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size);
static dc_status_t deepsix_excursion_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime);
static dc_status_t deepsix_excursion_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value);
static dc_status_t deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata);
static dc_status_t deepsix_excursion_parser_samples_foreach_v0 (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata);
static dc_status_t deepsix_excursion_parser_samples_foreach_v1 (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata);
static const dc_parser_vtable_t deepsix_parser_vtable = {
sizeof(deepsix_excursion_parser_t),
@ -67,6 +132,58 @@ static const dc_parser_vtable_t deepsix_parser_vtable = {
NULL /* destroy */
};
static const deepsix_excursion_layout_t deepsix_excursion_layout_v0 = {
156,/* headersize */
UNDEFINED, /* version */
4, /* divemode */
24, /* samplerate */
UNDEFINED, /* salinity */
12, /* datetime */
20, /* divetime */
28, /* maxdepth */
32, /* temperature_min */
UNDEFINED, /* avgdepth */
48, /* firmware */
UNDEFINED, /* temperature_surf */
56, /* atmospheric */
UNDEFINED, /* gf */
};
static const deepsix_excursion_layout_t deepsix_excursion_layout_v1 = {
129,/* headersize */
3, /* version */
4, /* divemode */
5, /* samplerate */
7, /* salinity */
12, /* datetime */
19, /* divetime */
29, /* maxdepth */
31, /* temperature_min */
33, /* avgdepth */
35, /* firmware */
43, /* temperature_surf */
45, /* atmospheric */
127, /* gf */
};
static double
pressure_to_depth(unsigned int depth, unsigned int atmospheric, unsigned int density)
{
return ((signed int)(depth - atmospheric)) * (BAR / 1000.0) / (density * GRAVITY);
}
static unsigned int
deepsix_excursion_find_gasmix(deepsix_excursion_parser_t *parser, unsigned int o2, unsigned int he, unsigned int id)
{
unsigned int i = 0;
while (i < parser->ngasmixes) {
if (o2 == parser->gasmix[i].oxygen && he == parser->gasmix[i].helium && id == parser->gasmix[i].id)
break;
i++;
}
return i;
}
dc_status_t
deepsix_excursion_parser_create (dc_parser_t **out, dc_context_t *context)
{
@ -82,6 +199,15 @@ deepsix_excursion_parser_create (dc_parser_t **out, dc_context_t *context)
return DC_STATUS_NOMEMORY;
}
// Set the default values.
parser->cached = 0;
parser->ngasmixes = 0;
for (unsigned int i = 0; i < MAX_GASMIXES; ++i) {
parser->gasmix[i].id = 0;
parser->gasmix[i].oxygen = 0;
parser->gasmix[i].helium = 0;
}
*out = (dc_parser_t *) parser;
return DC_STATUS_SUCCESS;
@ -90,6 +216,17 @@ deepsix_excursion_parser_create (dc_parser_t **out, dc_context_t *context)
static dc_status_t
deepsix_excursion_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size)
{
deepsix_excursion_parser_t *parser = (deepsix_excursion_parser_t *) abstract;
// Reset the cache.
parser->cached = 0;
parser->ngasmixes = 0;
for (unsigned int i = 0; i < MAX_GASMIXES; ++i) {
parser->gasmix[i].id = 0;
parser->gasmix[i].oxygen = 0;
parser->gasmix[i].helium = 0;
}
return DC_STATUS_SUCCESS;
}
@ -99,24 +236,37 @@ deepsix_excursion_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dat
const unsigned char *data = abstract->data;
unsigned int size = abstract->size;
if (size < HEADERSIZE)
if (size < HEADERSIZE_MIN)
return DC_STATUS_DATAFORMAT;
unsigned int firmware = array_uint16_be (data + 48 + 4);
unsigned int version = data[3];
const deepsix_excursion_layout_t *layout = version == 0 ?
&deepsix_excursion_layout_v0 : &deepsix_excursion_layout_v1;
if (size < layout->headersize)
return DC_STATUS_DATAFORMAT;
unsigned int firmware = array_uint16_be (data + layout->firmware + 4);
const unsigned char *p = data + layout->datetime;
if (datetime) {
datetime->year = data[12] + 2000;
datetime->month = data[13];
datetime->day = data[14];
datetime->hour = data[15];
datetime->minute = data[16];
datetime->second = data[17];
datetime->year = p[0] + 2000;
datetime->month = p[1];
datetime->day = p[2];
datetime->hour = p[3];
datetime->minute = p[4];
datetime->second = p[5];
if (version == 0) {
if (firmware >= FWVERSION(5, 'B')) {
datetime->timezone = (data[18] - 12) * 3600;
datetime->timezone = (p[6] - 12) * 3600;
} else {
datetime->timezone = DC_TIMEZONE_NONE;
}
} else {
datetime->timezone = ((signed char) p[6]) * 900;
}
}
return DC_STATUS_SUCCESS;
@ -125,37 +275,74 @@ deepsix_excursion_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dat
static dc_status_t
deepsix_excursion_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value)
{
deepsix_excursion_parser_t *parser = (deepsix_excursion_parser_t *) abstract;
const unsigned char *data = abstract->data;
unsigned int size = abstract->size;
if (size < HEADERSIZE)
dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
dc_salinity_t *water = (dc_salinity_t *) value;
dc_decomodel_t *decomodel = (dc_decomodel_t *) value;
if (size < HEADERSIZE_MIN)
return DC_STATUS_DATAFORMAT;
unsigned int atmospheric = array_uint32_le(data + 56);
unsigned int version = data[3];
const deepsix_excursion_layout_t *layout = version == 0 ?
&deepsix_excursion_layout_v0 : &deepsix_excursion_layout_v1;
dc_salinity_t *water = (dc_salinity_t *) value;
if (size < layout->headersize)
return DC_STATUS_DATAFORMAT;
if (version != 0 && !parser->cached) {
dc_status_t rc = deepsix_excursion_parser_samples_foreach_v1(abstract, NULL, NULL);
if (rc != DC_STATUS_SUCCESS)
return rc;
}
unsigned int atmospheric = array_uint16_le(data + layout->atmospheric);
unsigned int density = DENSITY;
if (layout->salinity != UNDEFINED) {
density = 1000 + data[layout->salinity] * 10;
}
if (value) {
switch (type) {
case DC_FIELD_DIVETIME:
*((unsigned int *) value) = array_uint32_le(data + 20);
*((unsigned int *) value) = array_uint32_le(data + layout->divetime);
break;
case DC_FIELD_MAXDEPTH:
*((double *) value) = (signed int)(array_uint32_le(data + 28) - atmospheric) * (BAR / 1000.0) / (DENSITY * GRAVITY);
*((double *) value) = pressure_to_depth(array_uint16_le(data + layout->maxdepth), atmospheric, density);
break;
case DC_FIELD_AVGDEPTH:
if (layout->avgdepth == UNDEFINED)
return DC_STATUS_UNSUPPORTED;
*((double *) value) = pressure_to_depth(array_uint16_le(data + layout->avgdepth), atmospheric, density);
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_TEMPERATURE_MINIMUM:
*((double *) value) = (signed int) array_uint32_le(data + 32) / 10.0;
*((double *) value) = (signed int) array_uint16_le(data + layout->temperature_min) / 10.0;
break;
case DC_FIELD_TEMPERATURE_SURFACE:
if (layout->temperature_surf == UNDEFINED)
return DC_STATUS_UNSUPPORTED;
*((double *) value) = (signed int) array_uint16_le(data + layout->temperature_surf) / 10.0;
break;
case DC_FIELD_ATMOSPHERIC:
*((double *) value) = atmospheric / 1000.0;
break;
case DC_FIELD_SALINITY:
water->type = DC_WATER_SALT;
water->density = DENSITY;
water->type = (density == 1000) ? DC_WATER_FRESH : DC_WATER_SALT;
water->density = density;
break;
case DC_FIELD_DIVEMODE:
switch (array_uint32_le(data + 4)) {
switch (data[layout->divemode]) {
case 0:
*((dc_divemode_t *) value) = DC_DIVEMODE_OC;
break;
@ -169,6 +356,17 @@ deepsix_excursion_parser_get_field (dc_parser_t *abstract, dc_field_type_t type,
return DC_STATUS_DATAFORMAT;
}
break;
case DC_FIELD_DECOMODEL:
decomodel->type = DC_DECOMODEL_BUHLMANN;
decomodel->conservatism = 0;
if (layout->gf != UNDEFINED) {
decomodel->params.gf.low = data[layout->gf + 0];
decomodel->params.gf.high = data[layout->gf + 1];
} else {
decomodel->params.gf.low = 0;
decomodel->params.gf.high = 0;
}
break;
default:
return DC_STATUS_UNSUPPORTED;
}
@ -178,23 +376,24 @@ deepsix_excursion_parser_get_field (dc_parser_t *abstract, dc_field_type_t type,
}
static dc_status_t
deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
deepsix_excursion_parser_samples_foreach_v0 (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
{
const unsigned char *data = abstract->data;
unsigned int size = abstract->size;
const deepsix_excursion_layout_t *layout = &deepsix_excursion_layout_v0;
if (size < HEADERSIZE)
if (size < layout->headersize)
return DC_STATUS_DATAFORMAT;
int firmware4c = memcmp(data + 48, "D01-4C", 6) == 0;
int firmware4c = memcmp(data + layout->firmware, "D01-4C", 6) == 0;
unsigned int maxtype = firmware4c ? TEMPERATURE : CNS;
unsigned int interval = array_uint32_le(data + 24);
unsigned int atmospheric = array_uint32_le(data + 56);
unsigned int interval = array_uint32_le(data + layout->samplerate);
unsigned int atmospheric = array_uint32_le(data + layout->atmospheric);
unsigned int time = 0;
unsigned int offset = HEADERSIZE;
unsigned int offset = layout->headersize;
while (offset + 1 < size) {
dc_sample_value_t sample = {0};
@ -227,7 +426,7 @@ deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb
sample.time = time;
if (callback) callback(DC_SAMPLE_TIME, sample, userdata);
sample.depth = (signed int)(depth - atmospheric) * (BAR / 1000.0) / (DENSITY * GRAVITY);
sample.depth = pressure_to_depth(depth, atmospheric, DENSITY);
if (callback) callback(DC_SAMPLE_DEPTH, sample, userdata);
}
@ -263,3 +462,325 @@ deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb
return DC_STATUS_SUCCESS;
}
static dc_status_t
deepsix_excursion_parser_samples_foreach_v1 (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
{
deepsix_excursion_parser_t *parser = (deepsix_excursion_parser_t *) abstract;
const unsigned char *data = abstract->data;
unsigned int size = abstract->size;
const deepsix_excursion_layout_t *layout = &deepsix_excursion_layout_v1;
if (size < layout->headersize)
return DC_STATUS_DATAFORMAT;
unsigned int headersize = data[2];
if (headersize < layout->headersize)
return DC_STATUS_DATAFORMAT;
unsigned int samplerate = data[layout->samplerate];
unsigned int atmospheric = array_uint16_le(data + layout->atmospheric);
unsigned int density = 1000 + data[layout->salinity] * 10;
unsigned int offset = headersize;
if (offset + 1 > size) {
ERROR (abstract->context, "Buffer overflow detected!");
return DC_STATUS_DATAFORMAT;
}
unsigned int nconfig = data[offset];
if (nconfig > MAX_SAMPLES) {
ERROR(abstract->context, "Too many sample descriptors (%u).", nconfig);
return DC_STATUS_DATAFORMAT;
}
offset += 1;
if (offset + 3 * nconfig > size) {
ERROR (abstract->context, "Buffer overflow detected!");
return DC_STATUS_DATAFORMAT;
}
deepsix_excursion_sample_info_t sample_info[MAX_SAMPLES] = {{0}};
for (unsigned int i = 0; i < nconfig; i++) {
sample_info[i].type = data[offset + 3 * i + 0];
sample_info[i].size = data[offset + 3 * i + 1];
sample_info[i].divisor = data[offset + 3 * i + 2];
if (sample_info[i].divisor) {
switch (sample_info[i].type) {
case SAMPLE_CNS:
case SAMPLE_TEMPERATURE:
if (sample_info[i].size != 2) {
ERROR(abstract->context, "Unexpected sample size (%u).", sample_info[i].size);
return DC_STATUS_DATAFORMAT;
}
break;
case SAMPLE_DECO_NDL:
if (sample_info[i].size != 7) {
ERROR(abstract->context, "Unexpected sample size (%u).", sample_info[i].size);
return DC_STATUS_DATAFORMAT;
}
break;
default:
WARNING (abstract->context, "Unknown sample descriptor (%u %u %u).",
sample_info[i].type, sample_info[i].size, sample_info[i].divisor);
break;
}
}
}
offset += 3 * nconfig;
if (offset + 1 > size) {
ERROR (abstract->context, "Buffer overflow detected!");
return DC_STATUS_DATAFORMAT;
}
unsigned int nevents = data[offset];
if (nevents > MAX_EVENTS) {
ERROR(abstract->context, "Too many event descriptors (%u).", nevents);
return DC_STATUS_DATAFORMAT;
}
offset += 1;
if (offset + 2 * nevents > size) {
ERROR (abstract->context, "Buffer overflow detected!");
return DC_STATUS_DATAFORMAT;
}
deepsix_excursion_event_info_t event_info[MAX_EVENTS] = {{0}};
for (unsigned int i = 0; i < nevents; i++) {
event_info[i].type = data[offset + 2 * i];
event_info[i].size = data[offset + 2 * i + 1];
switch (event_info[i].type) {
case EVENT_ALARMS:
if (event_info[i].size != 1) {
ERROR(abstract->context, "Unexpected event size (%u).", event_info[i].size);
return DC_STATUS_DATAFORMAT;
}
break;
case EVENT_CHANGE_GAS:
if (event_info[i].size != 3) {
ERROR(abstract->context, "Unexpected event size (%u).", event_info[i].size);
return DC_STATUS_DATAFORMAT;
}
break;
case EVENT_SAMPLES_MISSED:
if (event_info[i].size != 6) {
ERROR(abstract->context, "Unexpected event size (%u).", event_info[i].size);
return DC_STATUS_DATAFORMAT;
}
break;
default:
WARNING (abstract->context, "Unknown event descriptor (%u %u).",
event_info[i].type, event_info[i].size);
break;
}
}
offset += 2 * nevents;
unsigned int time = 0;
unsigned int nsamples = 0;
while (offset + 3 <= size) {
dc_sample_value_t sample = {0};
nsamples++;
// Time (seconds).
time += samplerate;
sample.time = time;
if (callback) callback (DC_SAMPLE_TIME, sample, userdata);
unsigned int depth = array_uint16_le (data + offset);
sample.depth = pressure_to_depth(depth, atmospheric, density);
if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
offset += 2;
// event info
unsigned int length = data[offset];
offset += 1;
if (offset + length > size) {
ERROR (abstract->context, "Buffer overflow detected!");
return DC_STATUS_DATAFORMAT;
}
if (length) {
if (length < 2) {
ERROR (abstract->context, "Buffer overflow detected!");
return DC_STATUS_DATAFORMAT;
}
unsigned int events = array_uint16_le (data + offset);
unsigned int event_offset = 2;
for (unsigned int i = 0; i < nevents; i++) {
if ((events & (1 << event_info[i].type)) == 0)
continue;
if (event_offset + event_info[i].size > length) {
ERROR (abstract->context, "Buffer overflow detected!");
return DC_STATUS_DATAFORMAT;
}
unsigned int alarms = 0;
unsigned int id = 0, o2 = 0, he = 0;
unsigned int mix_idx = 0;
unsigned int count = 0, timestamp = 0;
switch (event_info[i].type) {
case EVENT_ALARMS:
alarms = data[offset + event_offset];
for (unsigned int v = alarms, j = 0; v; v >>= 1, ++j) {
if ((v & 1) == 0)
continue;
sample.event.type = SAMPLE_EVENT_NONE;
sample.event.time = 0;
sample.event.flags = 0;
sample.event.value = 0;
switch (j) {
case ALARM_ASCENTRATE:
sample.event.type = SAMPLE_EVENT_ASCENT;
break;
case ALARM_CEILING:
sample.event.type = SAMPLE_EVENT_CEILING;
break;
case ALARM_PO2:
sample.event.type = SAMPLE_EVENT_PO2;
break;
case ALARM_MAXDEPTH:
sample.event.type = SAMPLE_EVENT_MAXDEPTH;
break;
case ALARM_DIVETIME:
sample.event.type = SAMPLE_EVENT_DIVETIME;
break;
case ALARM_CNS:
break;
default:
WARNING (abstract->context, "Unknown event (%u).", j);
break;
}
if (sample.event.type != SAMPLE_EVENT_NONE) {
if (callback) callback (DC_SAMPLE_EVENT, sample, userdata);
}
}
break;
case EVENT_CHANGE_GAS:
id = data[offset + event_offset];
o2 = data[offset + event_offset + 1];
he = data[offset + event_offset + 2];
mix_idx = deepsix_excursion_find_gasmix(parser, o2, he, id);
if (mix_idx >= parser->ngasmixes) {
if (mix_idx >= MAX_GASMIXES) {
ERROR (abstract->context, "Maximum number of gas mixes reached.");
return DC_STATUS_NOMEMORY;
}
parser->gasmix[mix_idx].oxygen = o2;
parser->gasmix[mix_idx].helium = he;
parser->gasmix[mix_idx].id = id;
parser->ngasmixes = mix_idx + 1;
}
sample.gasmix = mix_idx;
if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata);
break;
case EVENT_SAMPLES_MISSED:
count = array_uint16_le(data + offset + event_offset);
timestamp = array_uint32_le(data + offset + event_offset + 2);
if (timestamp < time) {
ERROR (abstract->context, "Timestamp moved backwards (%u %u).", timestamp, time);
return DC_STATUS_DATAFORMAT;
}
nsamples += count;
time = timestamp;
break;
default:
WARNING (abstract->context, "Unknown event (%u %u).",
event_info[i].type, event_info[i].size);
break;
}
event_offset += event_info[i].size;
}
// Skip remaining sample bytes (if any).
if (event_offset < length) {
WARNING (abstract->context, "Remaining %u bytes skipped.", length - event_offset);
}
offset += length;
}
for (unsigned int i = 0; i < nconfig; ++i) {
if (sample_info[i].divisor && (nsamples % sample_info[i].divisor) == 0) {
if (offset + sample_info[i].size > size) {
ERROR (abstract->context, "Buffer overflow detected!");
return DC_STATUS_DATAFORMAT;
}
unsigned int value = 0;
unsigned int deco_flags = 0, deco_ndl_tts = 0;
unsigned int deco_depth = 0, deco_time = 0;
switch (sample_info[i].type) {
case SAMPLE_TEMPERATURE:
value = array_uint16_le(data + offset);
sample.temperature = value / 10.0;
if (callback) callback(DC_SAMPLE_TEMPERATURE, sample, userdata);
break;
case SAMPLE_CNS:
value = array_uint16_le(data + offset);
sample.cns = value / 10000.0;
if (callback) callback (DC_SAMPLE_CNS, sample, userdata);
break;
case SAMPLE_DECO_NDL:
deco_flags = data[offset];
deco_ndl_tts = array_uint16_le(data + offset + 1);
deco_depth = array_uint16_le(data + offset + 3);
deco_time = array_uint16_le(data + offset + 5);
if (deco_flags & DECOSTOP) {
sample.deco.type = DC_DECO_DECOSTOP;
sample.deco.depth = pressure_to_depth(deco_depth, atmospheric, density);
sample.deco.time = deco_time;
} else if (deco_flags & SAFETYSTOP) {
sample.deco.type = DC_DECO_SAFETYSTOP;
sample.deco.depth = pressure_to_depth(deco_depth, atmospheric, density);
sample.deco.time = deco_time;
} else {
sample.deco.type = DC_DECO_NDL;
sample.deco.depth = 0;
sample.deco.time = deco_ndl_tts;
}
if (callback) callback (DC_SAMPLE_DECO, sample, userdata);
break;
default:
break;
}
offset += sample_info[i].size;
}
}
}
parser->cached = 1;
return DC_STATUS_SUCCESS;
}
static dc_status_t
deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
{
const unsigned char *data = abstract->data;
unsigned int size = abstract->size;
if (size < HEADERSIZE_MIN)
return DC_STATUS_DATAFORMAT;
unsigned int version = data[3];
if (version == 0) {
return deepsix_excursion_parser_samples_foreach_v0(abstract, callback, userdata);
} else {
return deepsix_excursion_parser_samples_foreach_v1(abstract, callback, userdata);
}
}