#include <stdlib.h>
#include "seac_screen.h"
#include "context-private.h"
#include "parser-private.h"
#include "checksum.h"
#include "array.h"
#define ISINSTANCE(parser) dc_device_isinstance((parser), &seac_screen_parser_vtable)
#define SZ_HEADER 128
#define SZ_SAMPLE 64
#define NGASMIXES 2
#define INVALID 0xFFFFFFFF
typedef struct seac_screen_parser_t seac_screen_parser_t;
struct seac_screen_parser_t {
dc_parser_t base;
unsigned int cached;
unsigned int ngasmixes;
unsigned int oxygen[NGASMIXES];
unsigned int gf_low;
unsigned int gf_high;
};
static dc_status_t seac_screen_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size);
static dc_status_t seac_screen_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime);
static dc_status_t seac_screen_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value);
static dc_status_t seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata);
static const dc_parser_vtable_t seac_screen_parser_vtable = {
sizeof(seac_screen_parser_t),
DC_FAMILY_SEAC_SCREEN,
seac_screen_parser_set_data,
NULL,
NULL,
NULL,
seac_screen_parser_get_datetime,
seac_screen_parser_get_field,
seac_screen_parser_samples_foreach,
NULL
};
dc_status_t
seac_screen_parser_create (dc_parser_t **out, dc_context_t *context)
{
seac_screen_parser_t *parser = NULL;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
parser = (seac_screen_parser_t *) dc_parser_allocate (context, &seac_screen_parser_vtable);
if (parser == NULL) {
ERROR (context, "Failed to allocate memory.");
return DC_STATUS_NOMEMORY;
}
parser->cached = 0;
parser->ngasmixes = 0;
for (unsigned int i = 0; i < NGASMIXES; ++i) {
parser->oxygen[i] = 0;
}
parser->gf_low = 0;
parser->gf_high = 0;
*out = (dc_parser_t *) parser;
return DC_STATUS_SUCCESS;
}
static dc_status_t
seac_screen_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size)
{
seac_screen_parser_t *parser = (seac_screen_parser_t *)abstract;
parser->cached = 0;
parser->ngasmixes = 0;
for (unsigned int i = 0; i < NGASMIXES; ++i) {
parser->oxygen[i] = 0;
}
parser->gf_low = 0;
parser->gf_high = 0;
return DC_STATUS_SUCCESS;
}
static dc_status_t
seac_screen_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime)
{
const unsigned char *data = abstract->data;
static const signed int tz_array[] = {
-12 * 60,
-11 * 60,
-10 * 60,
-9 * 60 - 30,
-9 * 60,
-8 * 60,
-7 * 60,
-6 * 60,
-5 * 60,
-4 * 60 - 30,
-4 * 60,
-3 * 60 - 30,
-3 * 60,
-2 * 60,
-1 * 60,
0 * 60,
1 * 60,
2 * 60,
3 * 60,
3 * 60 + 30,
4 * 60,
4 * 60 + 30,
5 * 60,
5 * 60 + 30,
5 * 60 + 45,
6 * 60,
6 * 60 + 30,
7 * 60,
8 * 60,
8 * 60 + 45,
9 * 60,
9 * 60 + 30,
9 * 60 + 45,
10 * 60,
10 * 60 + 30,
11 * 60,
11 * 60 + 30,
12 * 60,
12 * 60 + 45,
13 * 60,
13 * 60 + 45,
14 * 60,
};
if (abstract->size < SZ_HEADER)
return DC_STATUS_DATAFORMAT;
dc_datetime_t utc = {0};
utc.year = data[0x10] + 2000;
utc.month = data[0x0F];
utc.day = data[0x0E];
utc.hour = data[0x0B];
utc.minute = data[0x0C];
utc.second = data[0x0D];
utc.timezone = 0;
unsigned int tz_idx = data[0x0A];
if (tz_idx >= C_ARRAY_SIZE(tz_array)) {
ERROR (abstract->context, "Invalid timezone index (%u).", tz_idx);
return DC_STATUS_DATAFORMAT;
}
int timezone = tz_array[tz_idx] * 60;
dc_ticks_t ticks = dc_datetime_mktime (&utc);
if (ticks == -1)
return DC_STATUS_DATAFORMAT;
ticks += timezone;
if (!dc_datetime_gmtime (datetime, ticks))
return DC_STATUS_DATAFORMAT;
datetime->timezone = timezone;
return DC_STATUS_SUCCESS;
}
static dc_status_t
seac_screen_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value)
{
seac_screen_parser_t *parser = (seac_screen_parser_t *) abstract;
const unsigned char *data = abstract->data;
if (abstract->size < SZ_HEADER)
return DC_STATUS_DATAFORMAT;
if (!parser->cached) {
dc_status_t rc = seac_screen_parser_samples_foreach (abstract, NULL, NULL);
if (rc != DC_STATUS_SUCCESS)
return rc;
}
dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
dc_decomodel_t *decomodel = (dc_decomodel_t *) value;
if (value) {
switch (type) {
case DC_FIELD_AVGDEPTH:
*((double *) value) = array_uint16_le (data + 0x72) / 100.0;
break;
case DC_FIELD_MAXDEPTH:
*((double *) value) = array_uint16_le (data + 0x62) / 100.0;
break;
case DC_FIELD_DIVETIME:
*((unsigned int *) value) = array_uint32_le (data + 0x64);
break;
case DC_FIELD_TEMPERATURE_MINIMUM:
*((double *) value) = array_uint16_le (data + 0x6A) / 100.0;
break;
case DC_FIELD_TEMPERATURE_MAXIMUM:
*((double *) value) = array_uint16_le (data + 0x68) / 100.0;
break;
case DC_FIELD_GASMIX_COUNT:
*((unsigned int *)value) = parser->ngasmixes;
break;
case DC_FIELD_GASMIX:
gasmix->helium = 0.0;
gasmix->oxygen = parser->oxygen[flags] / 100.0;
gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium;
break;
case DC_FIELD_DIVEMODE:
switch (data[0x26]) {
case 1:
*((dc_divemode_t *) value) = DC_DIVEMODE_OC;
break;
case 2:
*((dc_divemode_t *) value) = DC_DIVEMODE_GAUGE;
break;
case 3:
*((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE;
break;
default:
ERROR (abstract->context, "Unknown dive mode %i", data[0x26]);
return DC_STATUS_DATAFORMAT;
}
break;
case DC_FIELD_DECOMODEL:
decomodel->type = DC_DECOMODEL_BUHLMANN;
decomodel->conservatism = 0;
decomodel->params.gf.low = parser->gf_low;
decomodel->params.gf.high = parser->gf_high;
break;
default:
return DC_STATUS_UNSUPPORTED;
}
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
{
seac_screen_parser_t *parser = (seac_screen_parser_t *) abstract;
const unsigned char *data = abstract->data;
unsigned int size = abstract->size;
if (abstract->size < SZ_HEADER)
return DC_STATUS_DATAFORMAT;
if (checksum_crc16_ccitt (data, SZ_HEADER / 2, 0xFFFF, 0x0000) != 0 ||
checksum_crc16_ccitt (data + SZ_HEADER / 2, SZ_HEADER / 2, 0xFFFF, 0x0000) != 0) {
ERROR (abstract->context, "Unexpected header checksum.");
return DC_STATUS_DATAFORMAT;
}
unsigned int dive_id = array_uint32_le (data + 0x00);
unsigned int ngasmixes = 0;
unsigned int oxygen[NGASMIXES] = {0};
unsigned int o2_previous = INVALID;
unsigned int gf_low = 0;
unsigned int gf_high = 0;
unsigned int time = 0;
unsigned int offset = SZ_HEADER;
while (offset + SZ_SAMPLE <= size) {
dc_sample_value_t sample = {0};
if (checksum_crc16_ccitt (data + offset, SZ_SAMPLE, 0xFFFF, 0x0000) != 0) {
ERROR (abstract->context, "Unexpected sample checksum.");
return DC_STATUS_DATAFORMAT;
}
unsigned int id = array_uint32_le (data + offset + 0x00);
unsigned int timestamp = array_uint32_le (data + offset + 0x04);
unsigned int depth = array_uint16_le (data + offset + 0x08);
unsigned int temperature = array_uint16_le (data + offset + 0x0A);
unsigned int o2 = data[offset + 0x0C];
unsigned int decodepth = array_uint16_le (data + offset + 0x0E);
unsigned int decotime = array_uint16_le (data + offset + 0x10);
unsigned int ndl_tts = array_uint16_le (data + offset + 0x12);
unsigned int cns = array_uint16_le (data + offset + 0x16);
unsigned int gf_hi = data[offset + 0x3B];
unsigned int gf_lo = data[offset + 0x3C];
if (id != dive_id) {
ERROR (abstract->context, "Unexpected sample id (%u %u).", dive_id, id);
return DC_STATUS_DATAFORMAT;
}
if (timestamp < time) {
ERROR (abstract->context, "Timestamp moved backwards (%u %u).", timestamp, time);
return DC_STATUS_DATAFORMAT;
}
time = timestamp;
sample.time = time;
if (callback) callback (DC_SAMPLE_TIME, sample, userdata);
sample.depth = depth / 100.0;
if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
sample.temperature = temperature / 100.0;
if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
if (o2 != o2_previous) {
unsigned int idx = 0;
while (idx < ngasmixes) {
if (o2 == oxygen[idx])
break;
idx++;
}
if (idx >= ngasmixes) {
if (idx >= NGASMIXES) {
ERROR (abstract->context, "Maximum number of gas mixes reached.");
return DC_STATUS_DATAFORMAT;
}
oxygen[idx] = o2;
ngasmixes = idx + 1;
}
sample.gasmix = idx;
if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata);
o2_previous = o2;
}
if (decodepth) {
sample.deco.type = DC_DECO_DECOSTOP;
sample.deco.time = decotime;
sample.deco.depth = decodepth;
} else {
sample.deco.type = DC_DECO_NDL;
sample.deco.time = ndl_tts;
sample.deco.depth = 0;
}
if (callback) callback (DC_SAMPLE_DECO, sample, userdata);
sample.cns = cns / 100.0;
if (callback) callback (DC_SAMPLE_CNS, sample, userdata);
if (gf_low == 0 && gf_high == 0) {
gf_low = gf_lo;
gf_high = gf_hi;
}
offset += SZ_SAMPLE;
}
for (unsigned int i = 0; i < ngasmixes; ++i) {
parser->oxygen[i] = oxygen[i];
}
parser->ngasmixes = ngasmixes;
parser->gf_low = gf_low;
parser->gf_high = gf_high;
parser->cached = 1;
return DC_STATUS_SUCCESS;
}