Add support for the Aqualung i330R and Apeks DSX
This commit is contained in:
parent
9641883f2f
commit
d1106cb8ba
@ -1,9 +1,9 @@
|
||||
AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include
|
||||
AM_CFLAGS = $(LIBUSB_CFLAGS) $(LIBMTP_CFLAGS) $(HIDAPI_CFLAGS) $(BLUEZ_CFLAGS)
|
||||
AM_CFLAGS = $(LIBUSB_CFLAGS) $(HIDAPI_CFLAGS) $(BLUEZ_CFLAGS)
|
||||
|
||||
lib_LTLIBRARIES = libdivecomputer.la
|
||||
|
||||
libdivecomputer_la_LIBADD = $(LIBUSB_LIBS) $(LIBMTP_LIBS) $(HIDAPI_LIBS) $(BLUEZ_LIBS) -lm
|
||||
libdivecomputer_la_LIBADD = $(LIBUSB_LIBS) $(HIDAPI_LIBS) $(BLUEZ_LIBS) -lm
|
||||
libdivecomputer_la_LDFLAGS = \
|
||||
-version-info $(DC_VERSION_LIBTOOL) \
|
||||
-no-undefined \
|
||||
@ -43,6 +43,7 @@ libdivecomputer_la_SOURCES = \
|
||||
oceanic_atom2.h oceanic_atom2.c oceanic_atom2_parser.c \
|
||||
oceanic_veo250.h oceanic_veo250.c oceanic_veo250_parser.c \
|
||||
oceanic_vtpro.h oceanic_vtpro.c oceanic_vtpro_parser.c \
|
||||
pelagic_i330r.h pelagic_i330r.c \
|
||||
mares_common.h mares_common.c \
|
||||
mares_nemo.h mares_nemo.c mares_nemo_parser.c \
|
||||
mares_puck.h mares_puck.c \
|
||||
@ -90,12 +91,6 @@ libdivecomputer_la_SOURCES = \
|
||||
bluetooth.c \
|
||||
custom.c
|
||||
|
||||
# Not merged upstream yet
|
||||
libdivecomputer_la_SOURCES += \
|
||||
usb_storage.c \
|
||||
field-cache.h field-cache.c \
|
||||
garmin.h garmin.c garmin_parser.c
|
||||
|
||||
if OS_WIN32
|
||||
libdivecomputer_la_SOURCES += serial_win32.c
|
||||
else
|
||||
|
||||
@ -20,8 +20,6 @@
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <libdivecomputer/units.h>
|
||||
|
||||
@ -37,6 +35,12 @@
|
||||
#define GAUGE 1
|
||||
#define FREEDIVE 2
|
||||
|
||||
#define DSX_CC 0
|
||||
#define DSX_OC 1
|
||||
#define DSX_SIDEMOUNT 2
|
||||
#define DSX_SIDEGAUGE 3
|
||||
#define DSX_GAUGE 4
|
||||
|
||||
#define NGASMIXES 6
|
||||
|
||||
#define HEADER 1
|
||||
@ -47,9 +51,9 @@ typedef struct oceanic_atom2_parser_t oceanic_atom2_parser_t;
|
||||
struct oceanic_atom2_parser_t {
|
||||
dc_parser_t base;
|
||||
unsigned int model;
|
||||
unsigned int logbooksize;
|
||||
unsigned int headersize;
|
||||
unsigned int footersize;
|
||||
unsigned int serial;
|
||||
// Cached fields.
|
||||
unsigned int cached;
|
||||
unsigned int header;
|
||||
@ -80,7 +84,7 @@ static const dc_parser_vtable_t oceanic_atom2_parser_vtable = {
|
||||
|
||||
|
||||
dc_status_t
|
||||
oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, const unsigned char data[], size_t size, unsigned int model, unsigned int serial)
|
||||
oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, const unsigned char data[], size_t size, unsigned int model)
|
||||
{
|
||||
oceanic_atom2_parser_t *parser = NULL;
|
||||
|
||||
@ -96,6 +100,7 @@ oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, const uns
|
||||
|
||||
// Set the default values.
|
||||
parser->model = model;
|
||||
parser->logbooksize = 0;
|
||||
parser->headersize = 9 * PAGESIZE / 2;
|
||||
parser->footersize = 2 * PAGESIZE / 2;
|
||||
if (model == DATAMASK || model == COMPUMASK ||
|
||||
@ -134,9 +139,16 @@ oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, const uns
|
||||
} else if (model == I550C || model == WISDOM4 ||
|
||||
model == I200CV2) {
|
||||
parser->headersize = 5 * PAGESIZE / 2;
|
||||
} else if (model == I330R) {
|
||||
parser->logbooksize = 64;
|
||||
parser->headersize = parser->logbooksize + 80;
|
||||
parser->footersize = 48;
|
||||
} else if (model == DSX) {
|
||||
parser->logbooksize = 512;
|
||||
parser->headersize = parser->logbooksize + 256;
|
||||
parser->footersize = 64;
|
||||
}
|
||||
|
||||
parser->serial = serial;
|
||||
parser->cached = 0;
|
||||
parser->header = 0;
|
||||
parser->footer = 0;
|
||||
@ -174,8 +186,18 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim
|
||||
if (datetime) {
|
||||
// AM/PM bit of the 12-hour clock.
|
||||
unsigned int pm = p[1] & 0x80;
|
||||
unsigned int have_ampm = 1;
|
||||
|
||||
switch (parser->model) {
|
||||
case I330R:
|
||||
case DSX:
|
||||
datetime->year = p[7] + 2000;
|
||||
datetime->month = p[6];
|
||||
datetime->day = p[5];
|
||||
datetime->hour = p[3];
|
||||
datetime->minute = p[4];
|
||||
have_ampm = 0;
|
||||
break;
|
||||
case OC1A:
|
||||
case OC1B:
|
||||
case OC1C:
|
||||
@ -284,9 +306,11 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim
|
||||
datetime->timezone = DC_TIMEZONE_NONE;
|
||||
|
||||
// Convert to a 24-hour clock.
|
||||
if (have_ampm) {
|
||||
datetime->hour %= 12;
|
||||
if (pm)
|
||||
datetime->hour += 12;
|
||||
}
|
||||
|
||||
/*
|
||||
* Workaround for the year 2010 problem.
|
||||
@ -323,7 +347,6 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
#define BUF_LEN 16
|
||||
|
||||
static dc_status_t
|
||||
oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser)
|
||||
@ -362,6 +385,10 @@ oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser)
|
||||
} else if (parser->model == VEO20 || parser->model == VEO30 ||
|
||||
parser->model == OCS) {
|
||||
mode = (data[1] & 0x60) >> 5;
|
||||
} else if (parser->model == I330R) {
|
||||
mode = data[2];
|
||||
} else if (parser->model == DSX) {
|
||||
mode = data[45];
|
||||
}
|
||||
|
||||
// Get the gas mixes.
|
||||
@ -419,6 +446,17 @@ oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser)
|
||||
} else if (parser->model == WISDOM4) {
|
||||
o2_offset = header + 4;
|
||||
ngasmixes = 1;
|
||||
} else if (parser->model == I330R) {
|
||||
ngasmixes = 3;
|
||||
o2_offset = parser->logbooksize + 16;
|
||||
} else if (parser->model == DSX) {
|
||||
if (mode < DSX_SIDEGAUGE) {
|
||||
o2_offset = parser->logbooksize + 0x89 + mode * 16;
|
||||
he_offset = parser->logbooksize + 0xB9 + mode * 16;
|
||||
ngasmixes = 6;
|
||||
} else {
|
||||
ngasmixes = 0;
|
||||
}
|
||||
} else {
|
||||
o2_offset = header + 4;
|
||||
ngasmixes = 3;
|
||||
@ -432,6 +470,10 @@ oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser)
|
||||
for (unsigned int i = 0; i < ngasmixes; ++i) {
|
||||
if (data[o2_offset + i * o2_step]) {
|
||||
parser->oxygen[i] = data[o2_offset + i * o2_step];
|
||||
// The i330R uses 20 as "Air" and 21 as 21% Nitrox
|
||||
if (parser->model == I330R && parser->oxygen[i] == 20) {
|
||||
parser->oxygen[i] = 21;
|
||||
}
|
||||
} else {
|
||||
parser->oxygen[i] = 21;
|
||||
}
|
||||
@ -474,9 +516,6 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns
|
||||
|
||||
dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
|
||||
dc_salinity_t *water = (dc_salinity_t *) value;
|
||||
dc_field_string_t *string = (dc_field_string_t *) value;
|
||||
|
||||
char buf[BUF_LEN];
|
||||
|
||||
if (value) {
|
||||
switch (type) {
|
||||
@ -493,9 +532,18 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns
|
||||
parser->model == F11A || parser->model == F11B ||
|
||||
parser->model == MUNDIAL2 || parser->model == MUNDIAL3)
|
||||
*((double *) value) = array_uint16_le (data + 4) / 16.0 * FEET;
|
||||
else if (parser->model == I330R || parser->model == DSX)
|
||||
*((double *) value) = array_uint16_le (data + parser->footer + 10) / 10.0 * FEET;
|
||||
else
|
||||
*((double *) value) = (array_uint16_le (data + parser->footer + 4) & 0x0FFF) / 16.0 * FEET;
|
||||
break;
|
||||
case DC_FIELD_AVGDEPTH:
|
||||
if (parser->model == I330R || parser->model == DSX) {
|
||||
*((double *) value) = array_uint16_le (data + parser->footer + 12) / 10.0 * FEET;
|
||||
} else {
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
}
|
||||
break;
|
||||
case DC_FIELD_GASMIX_COUNT:
|
||||
*((unsigned int *) value) = parser->ngasmixes;
|
||||
break;
|
||||
@ -507,18 +555,43 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns
|
||||
break;
|
||||
case DC_FIELD_SALINITY:
|
||||
if (parser->model == A300CS || parser->model == VTX ||
|
||||
parser->model == I750TC || parser->model == I770R) {
|
||||
parser->model == I750TC) {
|
||||
if (data[0x18] & 0x80) {
|
||||
water->type = DC_WATER_FRESH;
|
||||
} else {
|
||||
water->type = DC_WATER_SALT;
|
||||
}
|
||||
water->density = 0.0;
|
||||
} else if (parser->model == I330R || parser->model == DSX) {
|
||||
unsigned int settings = array_uint32_le (data + parser->logbooksize + 12);
|
||||
if (settings & 0x10000) {
|
||||
water->type = DC_WATER_FRESH;
|
||||
} else {
|
||||
water->type = DC_WATER_SALT;
|
||||
}
|
||||
water->density = 0.0;
|
||||
} else {
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
}
|
||||
break;
|
||||
case DC_FIELD_DIVEMODE:
|
||||
if (parser->model == DSX) {
|
||||
switch (parser->mode) {
|
||||
case DSX_OC:
|
||||
case DSX_SIDEMOUNT:
|
||||
*((unsigned int *) value) = DC_DIVEMODE_OC;
|
||||
break;
|
||||
case DSX_SIDEGAUGE:
|
||||
case DSX_GAUGE:
|
||||
*((unsigned int *) value) = DC_DIVEMODE_GAUGE;
|
||||
break;
|
||||
case DSX_CC:
|
||||
*((unsigned int *) value) = DC_DIVEMODE_CCR;
|
||||
break;
|
||||
default:
|
||||
return DC_STATUS_DATAFORMAT;
|
||||
}
|
||||
} else {
|
||||
switch (parser->mode) {
|
||||
case NORMAL:
|
||||
*((unsigned int *) value) = DC_DIVEMODE_OC;
|
||||
@ -532,17 +605,7 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns
|
||||
default:
|
||||
return DC_STATUS_DATAFORMAT;
|
||||
}
|
||||
break;
|
||||
case DC_FIELD_STRING:
|
||||
switch(flags) {
|
||||
case 0: /* Serial */
|
||||
string->desc = "Serial";
|
||||
snprintf(buf, BUF_LEN, "%06u", parser->serial);
|
||||
break;
|
||||
default:
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
}
|
||||
string->value = strdup(buf);
|
||||
break;
|
||||
default:
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
@ -606,6 +669,9 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
|
||||
unsigned int time = 0;
|
||||
unsigned int interval = 1000;
|
||||
if (parser->mode != FREEDIVE) {
|
||||
if (parser->model == I330R || parser->model == DSX) {
|
||||
interval = data[parser->logbooksize + 36] * 1000;
|
||||
} else {
|
||||
unsigned int offset = 0x17;
|
||||
if (parser->model == A300CS || parser->model == VTX ||
|
||||
parser->model == I450T || parser->model == I750TC ||
|
||||
@ -615,6 +681,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
|
||||
const unsigned int intervals[] = {2000, 15000, 30000, 60000};
|
||||
unsigned int idx = data[offset] & 0x03;
|
||||
interval = intervals[idx];
|
||||
}
|
||||
} else if (parser->model == F11A || parser->model == F11B) {
|
||||
const unsigned int intervals[] = {250, 500, 1000, 2000};
|
||||
unsigned int idx = data[0x29] & 0x03;
|
||||
@ -637,8 +704,10 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
|
||||
parser->model == I750TC || parser->model == PROPLUSX ||
|
||||
parser->model == I770R || parser->model == I470TC ||
|
||||
parser->model == SAGE || parser->model == BEACON ||
|
||||
parser->model == GEOAIR) {
|
||||
parser->model == GEOAIR || parser->model == I330R) {
|
||||
samplesize = PAGESIZE;
|
||||
} else if (parser->model == DSX) {
|
||||
samplesize = 32;
|
||||
}
|
||||
|
||||
unsigned int have_temperature = 1, have_pressure = 1;
|
||||
@ -653,7 +722,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
|
||||
parser->model == I200 || parser->model == I100 ||
|
||||
parser->model == I300C || parser->model == TALIS ||
|
||||
parser->model == I200C || parser->model == I200CV2 ||
|
||||
parser->model == GEO40 || parser->model == VEO40) {
|
||||
parser->model == GEO40 || parser->model == VEO40 ||
|
||||
parser->model == I330R) {
|
||||
have_pressure = 0;
|
||||
}
|
||||
|
||||
@ -664,12 +734,12 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
|
||||
}
|
||||
|
||||
// Initial tank pressure.
|
||||
unsigned int tank = 0;
|
||||
unsigned int tank = 1;
|
||||
unsigned int pressure = 0;
|
||||
if (have_pressure) {
|
||||
unsigned int idx = 2;
|
||||
if (parser->model == A300CS || parser->model == VTX ||
|
||||
parser->model == I750TC || parser->model == I770R)
|
||||
parser->model == I750TC)
|
||||
idx = 16;
|
||||
pressure = array_uint16_le(data + parser->header + idx);
|
||||
if (pressure == 10000)
|
||||
@ -719,17 +789,17 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
|
||||
if (sampletype == 0xAA) {
|
||||
if (parser->model == DATAMASK || parser->model == COMPUMASK) {
|
||||
// Tank pressure (1 psi) and number
|
||||
tank = 0;
|
||||
tank = 1;
|
||||
pressure = (((data[offset + 7] << 8) + data[offset + 6]) & 0x0FFF);
|
||||
} else if (parser->model == A300CS || parser->model == VTX ||
|
||||
parser->model == I750TC || parser->model == I770R ||
|
||||
parser->model == SAGE || parser->model == BEACON) {
|
||||
parser->model == I750TC || parser->model == SAGE ||
|
||||
parser->model == BEACON) {
|
||||
// Tank pressure (1 psi) and number (one based index)
|
||||
tank = (data[offset + 1] & 0x03) - 1;
|
||||
tank = data[offset + 1] & 0x03;
|
||||
pressure = ((data[offset + 7] << 8) + data[offset + 6]) & 0x0FFF;
|
||||
} else {
|
||||
// Tank pressure (2 psi) and number (one based index)
|
||||
tank = (data[offset + 1] & 0x03) - 1;
|
||||
tank = data[offset + 1] & 0x03;
|
||||
if (parser->model == ATOM2 || parser->model == EPICA || parser->model == EPICB)
|
||||
pressure = (((data[offset + 3] << 8) + data[offset + 4]) & 0x0FFF) * 2;
|
||||
else
|
||||
@ -822,6 +892,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
|
||||
parser->model == I770R|| parser->model == SAGE ||
|
||||
parser->model == BEACON) {
|
||||
temperature = data[offset + 11];
|
||||
} else if (parser->model == I330R || parser->model == DSX) {
|
||||
temperature = array_uint16_le(data + offset + 10);
|
||||
} else {
|
||||
unsigned int sign;
|
||||
if (parser->model == DG03 || parser->model == PROPLUS3 ||
|
||||
@ -844,7 +916,11 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
|
||||
else
|
||||
temperature += (data[offset + 7] & 0x0C) >> 2;
|
||||
}
|
||||
if (parser->model == I330R || parser->model == DSX) {
|
||||
sample.temperature = ((temperature / 10.0) - 32.0) * (5.0 / 9.0);
|
||||
} else {
|
||||
sample.temperature = (temperature - 32.0) * (5.0 / 9.0);
|
||||
}
|
||||
if (callback) callback (DC_SAMPLE_TEMPERATURE, &sample, userdata);
|
||||
}
|
||||
|
||||
@ -869,12 +945,18 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
|
||||
parser->model == PROPLUSX || parser->model == I770R ||
|
||||
parser->model == SAGE || parser->model == BEACON)
|
||||
pressure = array_uint16_le (data + offset + 4);
|
||||
else
|
||||
else if (parser->model == DSX) {
|
||||
pressure = array_uint16_le (data + offset + 14);
|
||||
tank = (data[offset] & 0xF0) >> 4;
|
||||
} else {
|
||||
pressure -= data[offset + 1];
|
||||
sample.pressure.tank = tank;
|
||||
}
|
||||
if (tank) {
|
||||
sample.pressure.tank = tank - 1;
|
||||
sample.pressure.value = pressure * PSI / BAR;
|
||||
if (callback) callback (DC_SAMPLE_PRESSURE, &sample, userdata);
|
||||
}
|
||||
}
|
||||
|
||||
// Depth (1/16 ft)
|
||||
unsigned int depth;
|
||||
@ -891,11 +973,17 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
|
||||
parser->model == I470TC || parser->model == I200CV2 ||
|
||||
parser->model == GEOAIR)
|
||||
depth = (data[offset + 4] + (data[offset + 5] << 8)) & 0x0FFF;
|
||||
else if (parser->model == I330R || parser->model == DSX)
|
||||
depth = array_uint16_le (data + offset + 2);
|
||||
else if (parser->model == ATOM1)
|
||||
depth = data[offset + 3] * 16;
|
||||
else
|
||||
depth = (data[offset + 2] + (data[offset + 3] << 8)) & 0x0FFF;
|
||||
if (parser->model == I330R || parser->model == DSX) {
|
||||
sample.depth = depth / 10.0 * FEET;
|
||||
} else {
|
||||
sample.depth = depth / 16.0 * FEET;
|
||||
}
|
||||
if (callback) callback (DC_SAMPLE_DEPTH, &sample, userdata);
|
||||
|
||||
// Gas mix
|
||||
@ -904,8 +992,11 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
|
||||
if (parser->model == TX1) {
|
||||
gasmix = data[offset] & 0x07;
|
||||
have_gasmix = 1;
|
||||
} else if (parser->model == DSX) {
|
||||
gasmix = (data[offset] & 0xF0) >> 4;
|
||||
have_gasmix = 1;
|
||||
}
|
||||
if (have_gasmix && gasmix != gasmix_previous) {
|
||||
if (have_gasmix && gasmix != gasmix_previous && parser->ngasmixes > 0) {
|
||||
if (gasmix < 1 || gasmix > parser->ngasmixes) {
|
||||
ERROR (abstract->context, "Invalid gas mix index (%u).", gasmix);
|
||||
return DC_STATUS_DATAFORMAT;
|
||||
@ -935,7 +1026,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
|
||||
have_deco = 1;
|
||||
} else if (parser->model == ATOM31 || parser->model == VISION ||
|
||||
parser->model == XPAIR || parser->model == I550 ||
|
||||
parser->model == I550C || parser->model == WISDOM4) {
|
||||
parser->model == I550C || parser->model == WISDOM4 ||
|
||||
parser->model == PROPLUS4 || parser->model == ATMOSAI2) {
|
||||
decostop = (data[offset + 5] & 0xF0) >> 4;
|
||||
decotime = array_uint16_le(data + offset + 4) & 0x03FF;
|
||||
have_deco = 1;
|
||||
@ -950,11 +1042,25 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
|
||||
decostop = (data[offset + 7] & 0xF0) >> 4;
|
||||
decotime = array_uint16_le(data + offset + 6) & 0x0FFF;
|
||||
have_deco = 1;
|
||||
} else if (parser->model == I330R || parser->model == DSX) {
|
||||
decostop = data[offset + 8];
|
||||
if (decostop) {
|
||||
// Deco time
|
||||
decotime = array_uint16_le(data + offset + 6);
|
||||
} else {
|
||||
// NDL
|
||||
decotime = array_uint16_le(data + offset + 4);
|
||||
}
|
||||
have_deco = 1;
|
||||
}
|
||||
if (have_deco) {
|
||||
if (decostop) {
|
||||
sample.deco.type = DC_DECO_DECOSTOP;
|
||||
if (parser->model == I330R || parser->model == DSX) {
|
||||
sample.deco.depth = decostop * FEET;
|
||||
} else {
|
||||
sample.deco.depth = decostop * 10 * FEET;
|
||||
}
|
||||
} else {
|
||||
sample.deco.type = DC_DECO_NDL;
|
||||
sample.deco.depth = 0.0;
|
||||
@ -978,7 +1084,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
|
||||
have_rbt = 1;
|
||||
} else if (parser->model == VISION || parser->model == XPAIR ||
|
||||
parser->model == I550 || parser->model == I550C ||
|
||||
parser->model == WISDOM4) {
|
||||
parser->model == WISDOM4 || parser->model == PROPLUS4 ||
|
||||
parser->model == ATMOSAI2) {
|
||||
rbt = array_uint16_le(data + offset + 6) & 0x03FF;
|
||||
have_rbt = 1;
|
||||
}
|
||||
@ -987,6 +1094,13 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
|
||||
if (callback) callback (DC_SAMPLE_RBT, &sample, userdata);
|
||||
}
|
||||
|
||||
// PPO2
|
||||
if (parser->model == I330R) {
|
||||
sample.ppo2.sensor = DC_SENSOR_NONE;
|
||||
sample.ppo2.value = data[offset + 9] / 100.0;
|
||||
if (callback) callback (DC_SAMPLE_PPO2, &sample, userdata);
|
||||
}
|
||||
|
||||
// Bookmarks
|
||||
unsigned int have_bookmark = 0;
|
||||
if (parser->model == OC1A || parser->model == OC1B ||
|
||||
|
||||
@ -32,72 +32,62 @@
|
||||
|
||||
#define VTABLE(abstract) ((const oceanic_common_device_vtable_t *) abstract->vtable)
|
||||
|
||||
#define RB_LOGBOOK_DISTANCE(a,b,l) ringbuffer_distance (a, b, 1, l->rb_logbook_begin, l->rb_logbook_end)
|
||||
#define RB_LOGBOOK_INCR(a,b,l) ringbuffer_increment (a, b, l->rb_logbook_begin, l->rb_logbook_end)
|
||||
#define RB_LOGBOOK_DISTANCE(a,b,l,m) ringbuffer_distance (a, b, m, l->rb_logbook_begin, l->rb_logbook_end)
|
||||
|
||||
#define RB_PROFILE_DISTANCE(a,b,l) ringbuffer_distance (a, b, 0, l->rb_profile_begin, l->rb_profile_end)
|
||||
#define RB_PROFILE_INCR(a,b,l) ringbuffer_increment (a, b, l->rb_profile_begin, l->rb_profile_end)
|
||||
#define RB_PROFILE_DISTANCE(a,b,l,m) ringbuffer_distance (a, b, m, l->rb_profile_begin, l->rb_profile_end)
|
||||
|
||||
#define INVALID 0
|
||||
|
||||
static unsigned int
|
||||
get_profile_first (const unsigned char data[], const oceanic_common_layout_t *layout, unsigned int pagesize)
|
||||
static dc_status_t
|
||||
oceanic_common_device_get_profile (const unsigned char data[], const oceanic_common_layout_t *layout, unsigned int *begin, unsigned int *end)
|
||||
{
|
||||
unsigned int value;
|
||||
assert (layout != NULL);
|
||||
assert (begin != NULL && end != NULL);
|
||||
|
||||
// Get the pagesize
|
||||
unsigned int pagesize = layout->highmem ? 16 * PAGESIZE : PAGESIZE;
|
||||
|
||||
// Get the profile pointers.
|
||||
unsigned int first = 0, last = 0;
|
||||
if (layout->pt_mode_logbook == 0) {
|
||||
value = array_uint16_le (data + 5);
|
||||
first = array_uint16_le (data + 5);
|
||||
last = array_uint16_le (data + 6) >> 4;
|
||||
} else if (layout->pt_mode_logbook == 1) {
|
||||
value = array_uint16_le (data + 4);
|
||||
} else if (layout->pt_mode_logbook == 3) {
|
||||
value = array_uint16_le (data + 16);
|
||||
} else {
|
||||
return array_uint16_le (data + 16);
|
||||
first = array_uint16_le (data + 4);
|
||||
last = array_uint16_le (data + 6);
|
||||
} else if (layout->pt_mode_logbook == 2 || layout->pt_mode_logbook == 3) {
|
||||
first = array_uint16_le (data + 16);
|
||||
last = array_uint16_le (data + 18);
|
||||
} else if (layout->pt_mode_logbook == 4) {
|
||||
first = array_uint32_le (data + 8);
|
||||
last = array_uint32_le (data + 12);
|
||||
}
|
||||
|
||||
// Convert pages to bytes.
|
||||
if (layout->pt_mode_logbook < 3) {
|
||||
unsigned int npages = (layout->memsize - layout->highmem) / pagesize;
|
||||
if (npages > 0x4000) {
|
||||
value &= 0x7FFF;
|
||||
first &= 0x7FFF;
|
||||
last &= 0x7FFF;
|
||||
} else if (npages > 0x2000) {
|
||||
value &= 0x3FFF;
|
||||
first &= 0x3FFF;
|
||||
last &= 0x3FFF;
|
||||
} else if (npages > 0x1000) {
|
||||
value &= 0x1FFF;
|
||||
first &= 0x1FFF;
|
||||
last &= 0x1FFF;
|
||||
} else {
|
||||
value &= 0x0FFF;
|
||||
first &= 0x0FFF;
|
||||
last &= 0x0FFF;
|
||||
}
|
||||
|
||||
return layout->highmem + value * pagesize;
|
||||
first *= pagesize;
|
||||
last *= pagesize;
|
||||
}
|
||||
|
||||
*begin = layout->highmem + first;
|
||||
*end = layout->highmem + last + (layout->pt_mode_logbook < 4 ? pagesize : 0);
|
||||
|
||||
static unsigned int
|
||||
get_profile_last (const unsigned char data[], const oceanic_common_layout_t *layout, unsigned int pagesize)
|
||||
{
|
||||
unsigned int value;
|
||||
|
||||
if (layout->pt_mode_logbook == 0) {
|
||||
value = array_uint16_le (data + 6) >> 4;
|
||||
} else if (layout->pt_mode_logbook == 1) {
|
||||
value = array_uint16_le (data + 6);
|
||||
} else if (layout->pt_mode_logbook == 3) {
|
||||
value = array_uint16_le (data + 18);
|
||||
} else {
|
||||
return array_uint16_le(data + 18);
|
||||
}
|
||||
|
||||
unsigned int npages = (layout->memsize - layout->highmem) / pagesize;
|
||||
|
||||
if (npages > 0x4000) {
|
||||
value &= 0x7FFF;
|
||||
} else if (npages > 0x2000) {
|
||||
value &= 0x3FFF;
|
||||
} else if (npages > 0x1000) {
|
||||
value &= 0x1FFF;
|
||||
} else {
|
||||
value &= 0x0FFF;
|
||||
}
|
||||
|
||||
return layout->highmem + value * pagesize;
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@ -206,11 +196,11 @@ oceanic_common_device_dump (dc_device_t *abstract, dc_buffer_t *buffer)
|
||||
return DC_STATUS_NOMEMORY;
|
||||
}
|
||||
|
||||
// Emit a vendor event.
|
||||
dc_event_vendor_t vendor;
|
||||
vendor.data = device->version;
|
||||
vendor.size = sizeof (device->version);
|
||||
device_event_emit (abstract, DC_EVENT_VENDOR, &vendor);
|
||||
// Read the device info.
|
||||
status = VTABLE(abstract)->devinfo (abstract, NULL);
|
||||
if (status != DC_STATUS_SUCCESS) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Download the memory dump.
|
||||
status = device_dump_read (abstract, 0, dc_buffer_get_data (buffer),
|
||||
@ -219,8 +209,43 @@ oceanic_common_device_dump (dc_device_t *abstract, dc_buffer_t *buffer)
|
||||
return status;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
dc_status_t
|
||||
oceanic_common_device_devinfo (dc_device_t *abstract, dc_event_progress_t *progress)
|
||||
{
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
oceanic_common_device_t *device = (oceanic_common_device_t *) abstract;
|
||||
|
||||
assert (device != NULL);
|
||||
assert (device->layout != NULL);
|
||||
|
||||
const oceanic_common_layout_t *layout = device->layout;
|
||||
|
||||
// Read the device id.
|
||||
unsigned char id[PAGESIZE] = {0};
|
||||
status = dc_device_read (abstract, layout->cf_devinfo, id, sizeof (id));
|
||||
if (status != DC_STATUS_SUCCESS) {
|
||||
ERROR (abstract->context, "Failed to read the memory page.");
|
||||
return status;
|
||||
}
|
||||
|
||||
// Update and emit a progress event.
|
||||
if (progress) {
|
||||
progress->current += PAGESIZE;
|
||||
progress->maximum += PAGESIZE;
|
||||
device_event_emit (abstract, DC_EVENT_PROGRESS, progress);
|
||||
}
|
||||
|
||||
// Emit a vendor event.
|
||||
dc_event_vendor_t vendor;
|
||||
vendor.data = device->version;
|
||||
vendor.size = sizeof (device->version);
|
||||
device_event_emit (abstract, DC_EVENT_VENDOR, &vendor);
|
||||
|
||||
// Emit a device info event.
|
||||
unsigned char *id = dc_buffer_get_data (buffer) + layout->cf_devinfo;
|
||||
dc_event_devinfo_t devinfo;
|
||||
devinfo.model = array_uint16_be (id + 8);
|
||||
devinfo.firmware = device->firmware;
|
||||
@ -240,7 +265,51 @@ oceanic_common_device_dump (dc_device_t *abstract, dc_buffer_t *buffer)
|
||||
|
||||
|
||||
dc_status_t
|
||||
oceanic_common_device_logbook (dc_device_t *abstract, dc_event_progress_t *progress, dc_buffer_t *logbook)
|
||||
oceanic_common_device_pointers (dc_device_t *abstract, dc_event_progress_t *progress,
|
||||
unsigned int *rb_logbook_begin, unsigned int *rb_logbook_end,
|
||||
unsigned int *rb_profile_begin, unsigned int *rb_profile_end)
|
||||
{
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
oceanic_common_device_t *device = (oceanic_common_device_t *) abstract;
|
||||
|
||||
assert (device != NULL);
|
||||
assert (device->layout != NULL);
|
||||
assert (rb_logbook_begin != NULL && rb_logbook_end != NULL);
|
||||
assert (rb_profile_begin != NULL && rb_profile_end != NULL);
|
||||
|
||||
const oceanic_common_layout_t *layout = device->layout;
|
||||
|
||||
// Read the pointer data.
|
||||
unsigned char pointers[PAGESIZE] = {0};
|
||||
status = dc_device_read (abstract, layout->cf_pointers, pointers, sizeof (pointers));
|
||||
if (status != DC_STATUS_SUCCESS) {
|
||||
ERROR (abstract->context, "Failed to read the memory page.");
|
||||
return status;
|
||||
}
|
||||
|
||||
// Update and emit a progress event.
|
||||
if (progress) {
|
||||
progress->current += PAGESIZE;
|
||||
progress->maximum += PAGESIZE;
|
||||
device_event_emit (abstract, DC_EVENT_PROGRESS, progress);
|
||||
}
|
||||
|
||||
// Get the pointers.
|
||||
unsigned int rb_logbook_first = array_uint16_le (pointers + 4);
|
||||
unsigned int rb_logbook_last = array_uint16_le (pointers + 6);
|
||||
unsigned int rb_profile_first = array_uint16_le (pointers + 8);
|
||||
unsigned int rb_profile_last = array_uint16_le (pointers + 10);
|
||||
|
||||
*rb_logbook_begin = rb_logbook_first;
|
||||
*rb_logbook_end = rb_logbook_last + (layout->pt_mode_global == 0 ? layout->rb_logbook_entry_size : 0);
|
||||
*rb_profile_begin = rb_profile_first;
|
||||
*rb_profile_end = rb_profile_last;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
dc_status_t
|
||||
oceanic_common_device_logbook (dc_device_t *abstract, dc_event_progress_t *progress, dc_buffer_t *logbook, unsigned int begin, unsigned int end)
|
||||
{
|
||||
oceanic_common_device_t *device = (oceanic_common_device_t *) abstract;
|
||||
dc_status_t rc = DC_STATUS_SUCCESS;
|
||||
@ -256,37 +325,30 @@ oceanic_common_device_logbook (dc_device_t *abstract, dc_event_progress_t *progr
|
||||
if (!dc_buffer_clear (logbook))
|
||||
return DC_STATUS_NOMEMORY;
|
||||
|
||||
// For devices without a logbook ringbuffer, downloading dives isn't
|
||||
// possible. This is not considered a fatal error, but handled as if there
|
||||
// are no dives present.
|
||||
if (layout->rb_logbook_begin == layout->rb_logbook_end) {
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// Read the pointer data.
|
||||
unsigned char pointers[PAGESIZE] = {0};
|
||||
rc = dc_device_read (abstract, layout->cf_pointers, pointers, sizeof (pointers));
|
||||
if (rc != DC_STATUS_SUCCESS) {
|
||||
ERROR (abstract->context, "Failed to read the memory page.");
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Get the logbook pointers.
|
||||
unsigned int rb_logbook_first = array_uint16_le (pointers + 4);
|
||||
unsigned int rb_logbook_last = array_uint16_le (pointers + 6);
|
||||
if (rb_logbook_last < layout->rb_logbook_begin ||
|
||||
rb_logbook_last >= layout->rb_logbook_end)
|
||||
// Validate the logbook pointers.
|
||||
unsigned int rb_logbook_begin = begin;
|
||||
unsigned int rb_logbook_end = end;
|
||||
if (rb_logbook_begin < layout->rb_logbook_begin ||
|
||||
rb_logbook_begin > layout->rb_logbook_end)
|
||||
{
|
||||
ERROR (abstract->context, "Invalid logbook end pointer detected (0x%04x).", rb_logbook_last);
|
||||
ERROR (abstract->context, "Invalid logbook begin pointer detected (0x%04x).", rb_logbook_begin);
|
||||
if (layout->rb_logbook_direction == 0) {
|
||||
return DC_STATUS_DATAFORMAT;
|
||||
}
|
||||
|
||||
// Calculate the end pointer.
|
||||
unsigned int rb_logbook_end = 0;
|
||||
if (layout->pt_mode_global == 0) {
|
||||
rb_logbook_end = RB_LOGBOOK_INCR (rb_logbook_last, layout->rb_logbook_entry_size, layout);
|
||||
} else {
|
||||
rb_logbook_end = rb_logbook_last;
|
||||
// Fall back to downloading the entire logbook ringbuffer as
|
||||
// workaround for an invalid logbook begin pointer!
|
||||
rb_logbook_begin = rb_logbook_end;
|
||||
}
|
||||
if (rb_logbook_end < layout->rb_logbook_begin ||
|
||||
rb_logbook_end > layout->rb_logbook_end)
|
||||
{
|
||||
ERROR (abstract->context, "Invalid logbook end pointer detected (0x%04x).", rb_logbook_end);
|
||||
if (layout->rb_logbook_direction != 0) {
|
||||
return DC_STATUS_DATAFORMAT;
|
||||
}
|
||||
// Fall back to downloading the entire logbook ringbuffer as
|
||||
// workaround for an invalid logbook end pointer!
|
||||
rb_logbook_end = rb_logbook_begin;
|
||||
}
|
||||
|
||||
// Calculate the number of bytes.
|
||||
@ -295,21 +357,9 @@ oceanic_common_device_logbook (dc_device_t *abstract, dc_event_progress_t *progr
|
||||
// full ringbuffer. We always consider the ringbuffer full in that
|
||||
// case, because an empty ringbuffer can be detected by inspecting
|
||||
// the logbook entries once they are downloaded.
|
||||
unsigned int rb_logbook_size = 0;
|
||||
if (rb_logbook_first < layout->rb_logbook_begin ||
|
||||
rb_logbook_first >= layout->rb_logbook_end)
|
||||
{
|
||||
// Fall back to downloading the entire logbook ringbuffer as
|
||||
// workaround for an invalid logbook begin pointer!
|
||||
ERROR (abstract->context, "Invalid logbook begin pointer detected (0x%04x).", rb_logbook_first);
|
||||
rb_logbook_size = layout->rb_logbook_end - layout->rb_logbook_begin;
|
||||
} else {
|
||||
rb_logbook_size = RB_LOGBOOK_DISTANCE (rb_logbook_first, rb_logbook_end, layout);
|
||||
}
|
||||
unsigned int rb_logbook_size = RB_LOGBOOK_DISTANCE (rb_logbook_begin, rb_logbook_end, layout, DC_RINGBUFFER_FULL);
|
||||
|
||||
// Update and emit a progress event.
|
||||
progress->current += PAGESIZE;
|
||||
progress->maximum += PAGESIZE;
|
||||
progress->maximum -= (layout->rb_logbook_end - layout->rb_logbook_begin) - rb_logbook_size;
|
||||
device_event_emit (abstract, DC_EVENT_PROGRESS, progress);
|
||||
|
||||
@ -327,7 +377,11 @@ oceanic_common_device_logbook (dc_device_t *abstract, dc_event_progress_t *progr
|
||||
|
||||
// Create the ringbuffer stream.
|
||||
dc_rbstream_t *rbstream = NULL;
|
||||
rc = dc_rbstream_new (&rbstream, abstract, PAGESIZE, PAGESIZE * device->multipage, layout->rb_logbook_begin, layout->rb_logbook_end, rb_logbook_end);
|
||||
rc = dc_rbstream_new (&rbstream, abstract,
|
||||
PAGESIZE, PAGESIZE * device->multipage,
|
||||
layout->rb_logbook_begin, layout->rb_logbook_end,
|
||||
layout->rb_logbook_direction ? rb_logbook_end : rb_logbook_begin,
|
||||
layout->rb_logbook_direction ? DC_RBSTREAM_BACKWARD : DC_RBSTREAM_FORWARD);
|
||||
if (rc != DC_STATUS_SUCCESS) {
|
||||
ERROR (abstract->context, "Failed to create the ringbuffer stream.");
|
||||
return rc;
|
||||
@ -403,9 +457,6 @@ oceanic_common_device_profile (dc_device_t *abstract, dc_event_progress_t *progr
|
||||
|
||||
const oceanic_common_layout_t *layout = device->layout;
|
||||
|
||||
// Get the pagesize
|
||||
unsigned int pagesize = layout->highmem ? 16 * PAGESIZE : PAGESIZE;
|
||||
|
||||
// Cache the logbook pointer and size.
|
||||
const unsigned char *logbooks = dc_buffer_get_data (logbook);
|
||||
unsigned int rb_logbook_size = dc_buffer_get_size (logbook);
|
||||
@ -413,6 +464,7 @@ oceanic_common_device_profile (dc_device_t *abstract, dc_event_progress_t *progr
|
||||
// Go through the logbook entries a first time, to get the end of
|
||||
// profile pointer and calculate the total amount of bytes in the
|
||||
// profile ringbuffer.
|
||||
unsigned int rb_profile_begin = INVALID;
|
||||
unsigned int rb_profile_end = INVALID;
|
||||
unsigned int rb_profile_size = 0;
|
||||
|
||||
@ -434,22 +486,20 @@ oceanic_common_device_profile (dc_device_t *abstract, dc_event_progress_t *progr
|
||||
}
|
||||
|
||||
// Get the profile pointers.
|
||||
unsigned int rb_entry_first = get_profile_first (logbooks + entry, layout, pagesize);
|
||||
unsigned int rb_entry_last = get_profile_last (logbooks + entry, layout, pagesize);
|
||||
if (rb_entry_first < layout->rb_profile_begin ||
|
||||
rb_entry_first >= layout->rb_profile_end ||
|
||||
rb_entry_last < layout->rb_profile_begin ||
|
||||
rb_entry_last >= layout->rb_profile_end)
|
||||
unsigned int rb_entry_begin = 0, rb_entry_end = 0;
|
||||
oceanic_common_device_get_profile (logbooks + entry, layout, &rb_entry_begin, &rb_entry_end);
|
||||
if (rb_entry_begin < layout->rb_profile_begin ||
|
||||
rb_entry_begin > layout->rb_profile_end ||
|
||||
rb_entry_end < layout->rb_profile_begin ||
|
||||
rb_entry_end > layout->rb_profile_end)
|
||||
{
|
||||
ERROR (abstract->context, "Invalid ringbuffer pointer detected (0x%06x 0x%06x).",
|
||||
rb_entry_first, rb_entry_last);
|
||||
rb_entry_begin, rb_entry_end);
|
||||
status = DC_STATUS_DATAFORMAT;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate the end pointer and the number of bytes.
|
||||
unsigned int rb_entry_end = RB_PROFILE_INCR (rb_entry_last, pagesize, layout);
|
||||
unsigned int rb_entry_size = RB_PROFILE_DISTANCE (rb_entry_first, rb_entry_last, layout) + pagesize;
|
||||
DEBUG (abstract->context, "Entry: %08x %08x", rb_entry_begin, rb_entry_end);
|
||||
|
||||
// Take the end pointer of the most recent logbook entry as the
|
||||
// end of profile pointer.
|
||||
@ -457,11 +507,13 @@ oceanic_common_device_profile (dc_device_t *abstract, dc_event_progress_t *progr
|
||||
rb_profile_end = previous = rb_entry_end;
|
||||
}
|
||||
|
||||
// Calculate the number of bytes.
|
||||
unsigned int rb_entry_size = RB_PROFILE_DISTANCE (rb_entry_begin, rb_entry_end, layout, DC_RINGBUFFER_FULL);
|
||||
|
||||
// Skip gaps between the profiles.
|
||||
unsigned int gap = 0;
|
||||
if (rb_entry_end != previous) {
|
||||
WARNING (abstract->context, "Profiles are not continuous.");
|
||||
gap = RB_PROFILE_DISTANCE (rb_entry_end, previous, layout);
|
||||
unsigned int gap = RB_PROFILE_DISTANCE (rb_entry_end, previous, layout, DC_RINGBUFFER_EMPTY);
|
||||
if (gap) {
|
||||
WARNING (abstract->context, "Profiles are not continuous (%u bytes).", gap);
|
||||
}
|
||||
|
||||
// Make sure the profile size is valid.
|
||||
@ -470,13 +522,18 @@ oceanic_common_device_profile (dc_device_t *abstract, dc_event_progress_t *progr
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the profile begin pointer.
|
||||
rb_profile_begin = rb_entry_begin;
|
||||
|
||||
// Update the total profile size.
|
||||
rb_profile_size += rb_entry_size + gap;
|
||||
|
||||
remaining -= rb_entry_size + gap;
|
||||
previous = rb_entry_first;
|
||||
previous = rb_entry_begin;
|
||||
}
|
||||
|
||||
DEBUG (abstract->context, "Profile: %08x %08x", rb_profile_begin, rb_profile_end);
|
||||
|
||||
// At this point, we know the exact amount of data
|
||||
// that needs to be transfered for the profiles.
|
||||
progress->maximum -= (layout->rb_profile_end - layout->rb_profile_begin) - rb_profile_size;
|
||||
@ -489,7 +546,7 @@ oceanic_common_device_profile (dc_device_t *abstract, dc_event_progress_t *progr
|
||||
|
||||
// Create the ringbuffer stream.
|
||||
dc_rbstream_t *rbstream = NULL;
|
||||
rc = dc_rbstream_new (&rbstream, abstract, PAGESIZE, PAGESIZE * device->multipage, layout->rb_profile_begin, layout->rb_profile_end, rb_profile_end);
|
||||
rc = dc_rbstream_new (&rbstream, abstract, PAGESIZE, PAGESIZE * device->multipage, layout->rb_profile_begin, layout->rb_profile_end, rb_profile_end, DC_RBSTREAM_BACKWARD);
|
||||
if (rc != DC_STATUS_SUCCESS) {
|
||||
ERROR (abstract->context, "Failed to create the ringbuffer stream.");
|
||||
return rc;
|
||||
@ -524,28 +581,28 @@ oceanic_common_device_profile (dc_device_t *abstract, dc_event_progress_t *progr
|
||||
}
|
||||
|
||||
// Get the profile pointers.
|
||||
unsigned int rb_entry_first = get_profile_first (logbooks + entry, layout, pagesize);
|
||||
unsigned int rb_entry_last = get_profile_last (logbooks + entry, layout, pagesize);
|
||||
if (rb_entry_first < layout->rb_profile_begin ||
|
||||
rb_entry_first >= layout->rb_profile_end ||
|
||||
rb_entry_last < layout->rb_profile_begin ||
|
||||
rb_entry_last >= layout->rb_profile_end)
|
||||
unsigned int rb_entry_begin = 0, rb_entry_end = 0;
|
||||
oceanic_common_device_get_profile (logbooks + entry, layout, &rb_entry_begin, &rb_entry_end);
|
||||
if (rb_entry_begin < layout->rb_profile_begin ||
|
||||
rb_entry_begin > layout->rb_profile_end ||
|
||||
rb_entry_end < layout->rb_profile_begin ||
|
||||
rb_entry_end > layout->rb_profile_end)
|
||||
{
|
||||
ERROR (abstract->context, "Invalid ringbuffer pointer detected (0x%06x 0x%06x).",
|
||||
rb_entry_first, rb_entry_last);
|
||||
rb_entry_begin, rb_entry_end);
|
||||
status = DC_STATUS_DATAFORMAT;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate the end pointer and the number of bytes.
|
||||
unsigned int rb_entry_end = RB_PROFILE_INCR (rb_entry_last, pagesize, layout);
|
||||
unsigned int rb_entry_size = RB_PROFILE_DISTANCE (rb_entry_first, rb_entry_last, layout) + pagesize;
|
||||
DEBUG (abstract->context, "Entry: %08x %08x", rb_entry_begin, rb_entry_end);
|
||||
|
||||
// Calculate the number of bytes.
|
||||
unsigned int rb_entry_size = RB_PROFILE_DISTANCE (rb_entry_begin, rb_entry_end, layout, DC_RINGBUFFER_FULL);
|
||||
|
||||
// Skip gaps between the profiles.
|
||||
unsigned int gap = 0;
|
||||
if (rb_entry_end != previous) {
|
||||
WARNING (abstract->context, "Profiles are not continuous.");
|
||||
gap = RB_PROFILE_DISTANCE (rb_entry_end, previous, layout);
|
||||
unsigned int gap = RB_PROFILE_DISTANCE (rb_entry_end, previous, layout, DC_RINGBUFFER_EMPTY);
|
||||
if (gap) {
|
||||
WARNING (abstract->context, "Profiles are not continuous (%u bytes).", gap);
|
||||
}
|
||||
|
||||
// Make sure the profile size is valid.
|
||||
@ -566,7 +623,7 @@ oceanic_common_device_profile (dc_device_t *abstract, dc_event_progress_t *progr
|
||||
}
|
||||
|
||||
remaining -= rb_entry_size + gap;
|
||||
previous = rb_entry_first;
|
||||
previous = rb_entry_begin;
|
||||
|
||||
// Prepend the logbook entry to the profile data. The memory buffer is
|
||||
// large enough to store this entry.
|
||||
@ -604,6 +661,7 @@ oceanic_common_device_profile (dc_device_t *abstract, dc_event_progress_t *progr
|
||||
dc_status_t
|
||||
oceanic_common_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
|
||||
{
|
||||
dc_status_t rc = DC_STATUS_SUCCESS;
|
||||
oceanic_common_device_t *device = (oceanic_common_device_t *) abstract;
|
||||
|
||||
assert (device != NULL);
|
||||
@ -611,45 +669,37 @@ oceanic_common_device_foreach (dc_device_t *abstract, dc_dive_callback_t callbac
|
||||
|
||||
const oceanic_common_layout_t *layout = device->layout;
|
||||
|
||||
// For devices without a logbook and profile ringbuffer, downloading dives
|
||||
// isn't possible. This is not considered a fatal error, but handled as if
|
||||
// there are no dives present.
|
||||
if (layout->rb_logbook_begin == layout->rb_logbook_end &&
|
||||
layout->rb_profile_begin == layout->rb_profile_end) {
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// Enable progress notifications.
|
||||
dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
|
||||
progress.maximum = PAGESIZE +
|
||||
progress.maximum =
|
||||
(layout->rb_logbook_end - layout->rb_logbook_begin) +
|
||||
(layout->rb_profile_end - layout->rb_profile_begin);
|
||||
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
|
||||
|
||||
// Emit a vendor event.
|
||||
dc_event_vendor_t vendor;
|
||||
vendor.data = device->version;
|
||||
vendor.size = sizeof (device->version);
|
||||
device_event_emit (abstract, DC_EVENT_VENDOR, &vendor);
|
||||
|
||||
// Read the device id.
|
||||
unsigned char id[PAGESIZE] = {0};
|
||||
dc_status_t rc = dc_device_read (abstract, layout->cf_devinfo, id, sizeof (id));
|
||||
// Read the device info.
|
||||
rc = VTABLE(abstract)->devinfo (abstract, &progress);
|
||||
if (rc != DC_STATUS_SUCCESS) {
|
||||
ERROR (abstract->context, "Failed to read the memory page.");
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Update and emit a progress event.
|
||||
progress.current += PAGESIZE;
|
||||
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
|
||||
// Read the ringbuffer pointers.
|
||||
unsigned int rb_logbook_begin = 0, rb_logbook_end = 0;
|
||||
unsigned int rb_profile_begin = 0, rb_profile_end = 0;
|
||||
rc = VTABLE(abstract)->pointers (abstract, &progress, &rb_logbook_begin, &rb_logbook_end, &rb_profile_begin, &rb_profile_end);
|
||||
if (rc != DC_STATUS_SUCCESS) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Emit a device info event.
|
||||
dc_event_devinfo_t devinfo;
|
||||
devinfo.model = array_uint16_be (id + 8);
|
||||
devinfo.firmware = device->firmware;
|
||||
if (layout->pt_mode_serial == 0)
|
||||
devinfo.serial = array_convert_bcd2dec (id + 10, 3);
|
||||
else if (layout->pt_mode_serial == 1)
|
||||
devinfo.serial = array_convert_bin2dec (id + 11, 3);
|
||||
else
|
||||
devinfo.serial =
|
||||
(id[11] & 0x0F) * 100000 + ((id[11] & 0xF0) >> 4) * 10000 +
|
||||
(id[12] & 0x0F) * 1000 + ((id[12] & 0xF0) >> 4) * 100 +
|
||||
(id[13] & 0x0F) * 10 + ((id[13] & 0xF0) >> 4) * 1;
|
||||
device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo);
|
||||
DEBUG (abstract->context, "Logbook: %08x %08x", rb_logbook_begin, rb_logbook_end);
|
||||
DEBUG (abstract->context, "Profile: %08x %08x", rb_profile_begin, rb_profile_end);
|
||||
|
||||
// Memory buffer for the logbook data.
|
||||
dc_buffer_t *logbook = dc_buffer_new (0);
|
||||
@ -658,7 +708,7 @@ oceanic_common_device_foreach (dc_device_t *abstract, dc_dive_callback_t callbac
|
||||
}
|
||||
|
||||
// Download the logbook ringbuffer.
|
||||
rc = VTABLE(abstract)->logbook (abstract, &progress, logbook);
|
||||
rc = VTABLE(abstract)->logbook (abstract, &progress, logbook, rb_logbook_begin, rb_logbook_end);
|
||||
if (rc != DC_STATUS_SUCCESS) {
|
||||
dc_buffer_free (logbook);
|
||||
return rc;
|
||||
|
||||
@ -125,8 +125,12 @@ extern "C" {
|
||||
#define I200CV2 0x4749
|
||||
#define GEOAIR 0x474B
|
||||
|
||||
// i330r
|
||||
#define DSX 0x4741
|
||||
#define I330R 0x4744
|
||||
|
||||
#define PAGESIZE 0x10
|
||||
#define FPMAXSIZE 0x20
|
||||
#define FPMAXSIZE 0x200
|
||||
|
||||
#define OCEANIC_COMMON_MATCH(version,patterns,firmware) \
|
||||
oceanic_common_match ((version), (patterns), \
|
||||
@ -144,6 +148,7 @@ typedef struct oceanic_common_layout_t {
|
||||
unsigned int rb_logbook_begin;
|
||||
unsigned int rb_logbook_end;
|
||||
unsigned int rb_logbook_entry_size;
|
||||
unsigned int rb_logbook_direction;
|
||||
// Profile ringbuffer
|
||||
unsigned int rb_profile_begin;
|
||||
unsigned int rb_profile_end;
|
||||
@ -168,7 +173,9 @@ typedef struct oceanic_common_device_t {
|
||||
|
||||
typedef struct oceanic_common_device_vtable_t {
|
||||
dc_device_vtable_t base;
|
||||
dc_status_t (*logbook) (dc_device_t *device, dc_event_progress_t *progress, dc_buffer_t *logbook);
|
||||
dc_status_t (*devinfo) (dc_device_t *device, dc_event_progress_t *progress);
|
||||
dc_status_t (*pointers) (dc_device_t *device, dc_event_progress_t *progress, unsigned int *rb_logbook_begin, unsigned int *rb_logbook_end, unsigned int *rb_profile_begin, unsigned int *rb_profile_end);
|
||||
dc_status_t (*logbook) (dc_device_t *device, dc_event_progress_t *progress, dc_buffer_t *logbook, unsigned int begin, unsigned int end);
|
||||
dc_status_t (*profile) (dc_device_t *device, dc_event_progress_t *progress, dc_buffer_t *logbook, dc_dive_callback_t callback, void *userdata);
|
||||
} oceanic_common_device_vtable_t;
|
||||
|
||||
@ -186,7 +193,15 @@ void
|
||||
oceanic_common_device_init (oceanic_common_device_t *device);
|
||||
|
||||
dc_status_t
|
||||
oceanic_common_device_logbook (dc_device_t *device, dc_event_progress_t *progress, dc_buffer_t *logbook);
|
||||
oceanic_common_device_devinfo (dc_device_t *device, dc_event_progress_t *progress);
|
||||
|
||||
dc_status_t
|
||||
oceanic_common_device_pointers (dc_device_t *device, dc_event_progress_t *progress,
|
||||
unsigned int *rb_logbook_begin, unsigned int *rb_logbook_end,
|
||||
unsigned int *rb_profile_begin, unsigned int *rb_profile_end);
|
||||
|
||||
dc_status_t
|
||||
oceanic_common_device_logbook (dc_device_t *device, dc_event_progress_t *progress, dc_buffer_t *logbook, unsigned int begin, unsigned int end);
|
||||
|
||||
dc_status_t
|
||||
oceanic_common_device_profile (dc_device_t *device, dc_event_progress_t *progress, dc_buffer_t *logbook, dc_dive_callback_t callback, void *userdata);
|
||||
|
||||
31
src/parser.c
31
src/parser.c
@ -66,9 +66,6 @@
|
||||
#include "oceans_s1.h"
|
||||
#include "divesoft_freedom.h"
|
||||
|
||||
// Not merged upstream yet
|
||||
#include "garmin.h"
|
||||
|
||||
#include "context-private.h"
|
||||
#include "parser-private.h"
|
||||
#include "device-private.h"
|
||||
@ -76,7 +73,7 @@
|
||||
#define REACTPROWHITE 0x4354
|
||||
|
||||
static dc_status_t
|
||||
dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, const unsigned char data[], size_t size, dc_family_t family, unsigned int model, unsigned int serial)
|
||||
dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, const unsigned char data[], size_t size, dc_family_t family, unsigned int model)
|
||||
{
|
||||
dc_status_t rc = DC_STATUS_SUCCESS;
|
||||
dc_parser_t *parser = NULL;
|
||||
@ -95,11 +92,11 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, const unsigned
|
||||
if (model == 0x01)
|
||||
rc = suunto_eon_parser_create (&parser, context, data, size, 1);
|
||||
else
|
||||
rc = suunto_vyper_parser_create (&parser, context, data, size, serial);
|
||||
rc = suunto_vyper_parser_create (&parser, context, data, size);
|
||||
break;
|
||||
case DC_FAMILY_SUUNTO_VYPER2:
|
||||
case DC_FAMILY_SUUNTO_D9:
|
||||
rc = suunto_d9_parser_create (&parser, context, data, size, model, serial);
|
||||
rc = suunto_d9_parser_create (&parser, context, data, size, model);
|
||||
break;
|
||||
case DC_FAMILY_SUUNTO_EONSTEEL:
|
||||
rc = suunto_eonsteel_parser_create(&parser, context, data, size, model);
|
||||
@ -127,10 +124,11 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, const unsigned
|
||||
rc = oceanic_veo250_parser_create (&parser, context, data, size, model);
|
||||
break;
|
||||
case DC_FAMILY_OCEANIC_ATOM2:
|
||||
case DC_FAMILY_PELAGIC_I330R:
|
||||
if (model == REACTPROWHITE)
|
||||
rc = oceanic_veo250_parser_create (&parser, context, data, size, model);
|
||||
else
|
||||
rc = oceanic_atom2_parser_create (&parser, context, data, size, model, serial);
|
||||
rc = oceanic_atom2_parser_create (&parser, context, data, size, model);
|
||||
break;
|
||||
case DC_FAMILY_MARES_NEMO:
|
||||
case DC_FAMILY_MARES_PUCK:
|
||||
@ -140,14 +138,14 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, const unsigned
|
||||
rc = mares_darwin_parser_create (&parser, context, data, size, model);
|
||||
break;
|
||||
case DC_FAMILY_MARES_ICONHD:
|
||||
rc = mares_iconhd_parser_create (&parser, context, data, size, model, serial);
|
||||
rc = mares_iconhd_parser_create (&parser, context, data, size, model);
|
||||
break;
|
||||
case DC_FAMILY_HW_OSTC:
|
||||
rc = hw_ostc_parser_create (&parser, context, data, size, serial);
|
||||
rc = hw_ostc_parser_create (&parser, context, data, size);
|
||||
break;
|
||||
case DC_FAMILY_HW_FROG:
|
||||
case DC_FAMILY_HW_OSTC3:
|
||||
rc = hw_ostc3_parser_create (&parser, context, data, size, model, serial);
|
||||
rc = hw_ostc3_parser_create (&parser, context, data, size, model);
|
||||
break;
|
||||
case DC_FAMILY_CRESSI_EDY:
|
||||
case DC_FAMILY_ZEAGLE_N2ITION3:
|
||||
@ -163,10 +161,10 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, const unsigned
|
||||
rc = atomics_cobalt_parser_create (&parser, context, data, size);
|
||||
break;
|
||||
case DC_FAMILY_SHEARWATER_PREDATOR:
|
||||
rc = shearwater_predator_parser_create (&parser, context, data, size, model, serial);
|
||||
rc = shearwater_predator_parser_create (&parser, context, data, size, model);
|
||||
break;
|
||||
case DC_FAMILY_SHEARWATER_PETREL:
|
||||
rc = shearwater_petrel_parser_create (&parser, context, data, size, model, serial);
|
||||
rc = shearwater_petrel_parser_create (&parser, context, data, size, model);
|
||||
break;
|
||||
case DC_FAMILY_DIVERITE_NITEKQ:
|
||||
rc = diverite_nitekq_parser_create (&parser, context, data, size);
|
||||
@ -209,11 +207,6 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, const unsigned
|
||||
break;
|
||||
default:
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
|
||||
// Not merged upstream yet
|
||||
case DC_FAMILY_GARMIN:
|
||||
rc = garmin_parser_create (&parser, context, data, size);
|
||||
break;
|
||||
}
|
||||
|
||||
*out = parser;
|
||||
@ -231,7 +224,7 @@ dc_parser_new (dc_parser_t **out, dc_device_t *device, const unsigned char data[
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
|
||||
status = dc_parser_new_internal (&parser, device->context, data, size,
|
||||
dc_device_get_type (device), device->devinfo.model, device->devinfo.serial);
|
||||
dc_device_get_type (device), device->devinfo.model);
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
goto error_exit;
|
||||
|
||||
@ -253,7 +246,7 @@ dc_status_t
|
||||
dc_parser_new2 (dc_parser_t **out, dc_context_t *context, dc_descriptor_t *descriptor, const unsigned char data[], size_t size)
|
||||
{
|
||||
return dc_parser_new_internal (out, context, data, size,
|
||||
dc_descriptor_get_type (descriptor), dc_descriptor_get_model (descriptor), 0);
|
||||
dc_descriptor_get_type (descriptor), dc_descriptor_get_model (descriptor));
|
||||
}
|
||||
|
||||
dc_parser_t *
|
||||
|
||||
646
src/pelagic_i330r.c
Normal file
646
src/pelagic_i330r.c
Normal file
@ -0,0 +1,646 @@
|
||||
/*
|
||||
* libdivecomputer
|
||||
*
|
||||
* Copyright (C) 2023 Janice McLaughlin
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <string.h> // memcpy
|
||||
#include <stdlib.h> // malloc, free
|
||||
#include <assert.h>
|
||||
|
||||
#include <libdivecomputer/ble.h>
|
||||
|
||||
#include "pelagic_i330r.h"
|
||||
#include "oceanic_common.h"
|
||||
|
||||
#include "context-private.h"
|
||||
#include "device-private.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "rbstream.h"
|
||||
#include "checksum.h"
|
||||
#include "array.h"
|
||||
|
||||
#define UNDEFINED 0
|
||||
|
||||
#define STARTBYTE 0xCD
|
||||
|
||||
#define FLAG_NONE 0x00
|
||||
#define FLAG_REQUEST 0x40
|
||||
#define FLAG_DATA 0x80
|
||||
#define FLAG_LAST 0xC0
|
||||
|
||||
#define CMD_ACCESS_REQUEST 0xFA
|
||||
#define CMD_ACCESS_CODE 0xFB
|
||||
#define CMD_AUTHENTICATION 0x97
|
||||
#define CMD_WAKEUP_RDONLY 0x21
|
||||
#define CMD_WAKEUP_RDWR 0x22
|
||||
#define CMD_READ_HW_CAL 0x27
|
||||
#define CMD_READ_A2D 0x25
|
||||
#define CMD_READ_DEVICE_REC 0x31
|
||||
#define CMD_READ_GEN_SET 0x29
|
||||
#define CMD_READ_EXFLASHMAP 0x2F
|
||||
#define CMD_READ_FLASH 0x0D
|
||||
|
||||
#define RSP_READY 1
|
||||
#define RSP_DONE 2
|
||||
|
||||
#define MAXPACKET 255
|
||||
|
||||
#define MAXPASSCODE 6
|
||||
|
||||
typedef struct pelagic_i330r_device_t {
|
||||
oceanic_common_device_t base;
|
||||
dc_iostream_t *iostream;
|
||||
unsigned char accesscode[16];
|
||||
unsigned char id[16];
|
||||
unsigned char hwcal[256];
|
||||
unsigned char flashmap[256];
|
||||
unsigned int model;
|
||||
} pelagic_i330r_device_t;
|
||||
|
||||
static dc_status_t pelagic_i330r_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size);
|
||||
static dc_status_t pelagic_i330r_device_devinfo (dc_device_t *abstract, dc_event_progress_t *progress);
|
||||
static dc_status_t pelagic_i330r_device_pointers (dc_device_t *abstract, dc_event_progress_t *progress, unsigned int *rb_logbook_begin, unsigned int *rb_logbook_end, unsigned int *rb_profile_begin, unsigned int *rb_profile_end);
|
||||
|
||||
static const oceanic_common_device_vtable_t pelagic_i330r_device_vtable = {
|
||||
{
|
||||
sizeof(pelagic_i330r_device_t),
|
||||
DC_FAMILY_PELAGIC_I330R,
|
||||
oceanic_common_device_set_fingerprint, /* set_fingerprint */
|
||||
pelagic_i330r_device_read, /* read */
|
||||
NULL, /* write */
|
||||
oceanic_common_device_dump, /* dump */
|
||||
oceanic_common_device_foreach, /* foreach */
|
||||
NULL, /* timesync */
|
||||
NULL /* close */
|
||||
},
|
||||
pelagic_i330r_device_devinfo,
|
||||
pelagic_i330r_device_pointers,
|
||||
oceanic_common_device_logbook,
|
||||
oceanic_common_device_profile,
|
||||
};
|
||||
|
||||
static const oceanic_common_layout_t pelagic_i330r = {
|
||||
0x00400000, /* memsize */
|
||||
0, /* highmem */
|
||||
UNDEFINED, /* cf_devinfo */
|
||||
UNDEFINED, /* cf_pointers */
|
||||
0x00102000, /* rb_logbook_begin */
|
||||
0x00106000, /* rb_logbook_end */
|
||||
64, /* rb_logbook_entry_size */
|
||||
0, /* rb_logbook_direction */
|
||||
0x0010A000, /* rb_profile_begin */
|
||||
0x00400000, /* rb_profile_end */
|
||||
1, /* pt_mode_global */
|
||||
4, /* pt_mode_logbook */
|
||||
UNDEFINED, /* pt_mode_serial */
|
||||
};
|
||||
|
||||
static const oceanic_common_layout_t pelagic_dsx = {
|
||||
0x02000000, /* memsize */
|
||||
0, /* highmem */
|
||||
UNDEFINED, /* cf_devinfo */
|
||||
UNDEFINED, /* cf_pointers */
|
||||
0x00800000, /* rb_logbook_begin */
|
||||
0x00880000, /* rb_logbook_end */
|
||||
512, /* rb_logbook_entry_size */
|
||||
1, /* rb_logbook_direction */
|
||||
0x01000000, /* rb_profile_begin */
|
||||
0x02000000, /* rb_profile_end */
|
||||
1, /* pt_mode_global */
|
||||
4, /* pt_mode_logbook */
|
||||
UNDEFINED /* pt_mode_serial */
|
||||
};
|
||||
|
||||
static unsigned char
|
||||
checksum (const unsigned char data[], unsigned int size)
|
||||
{
|
||||
unsigned int csum = 0;
|
||||
for (unsigned int i = 0; i < size; i++) {
|
||||
unsigned int a = csum ^ data[i];
|
||||
unsigned int b = (a >> 7) ^ ((a >> 4) ^ a);
|
||||
csum = ((b << 4) & 0xFF) ^ ((b << 1) & 0xFF);
|
||||
}
|
||||
return csum & 0xFF;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
pelagic_i330r_send (pelagic_i330r_device_t *device, unsigned char cmd, unsigned char flag, const unsigned char data[], unsigned int size)
|
||||
{
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
dc_device_t *abstract = (dc_device_t *) device;
|
||||
|
||||
if (size > MAXPACKET) {
|
||||
ERROR (abstract->context, "Packet payload is too large (%u).", size);
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
}
|
||||
|
||||
unsigned char packet[MAXPACKET + 5] = {
|
||||
STARTBYTE,
|
||||
flag,
|
||||
cmd,
|
||||
0,
|
||||
size
|
||||
};
|
||||
if (size) {
|
||||
memcpy(packet + 5, data, size);
|
||||
}
|
||||
packet[3] = checksum (packet, size + 5);
|
||||
|
||||
// Send the data packet.
|
||||
status = dc_iostream_write (device->iostream, packet, size + 5, NULL);
|
||||
if (status != DC_STATUS_SUCCESS) {
|
||||
ERROR (abstract->context, "Failed to send the command.");
|
||||
return status;
|
||||
}
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
pelagic_i330r_recv (pelagic_i330r_device_t *device, unsigned char cmd, unsigned char data[], unsigned int size, unsigned int *errorcode)
|
||||
{
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
dc_device_t *abstract = (dc_device_t *) device;
|
||||
unsigned char packet[MAXPACKET + 5] = {0};
|
||||
unsigned int errcode = 0;
|
||||
|
||||
unsigned int nbytes = 0;
|
||||
while (1) {
|
||||
// Read the data packet.
|
||||
size_t transferred = 0;
|
||||
status = dc_iostream_read (device->iostream, packet, sizeof(packet), &transferred);
|
||||
if (status != DC_STATUS_SUCCESS) {
|
||||
ERROR (abstract->context, "Failed to receive the data packet.");
|
||||
return status;
|
||||
}
|
||||
|
||||
// Verify the minimum packet size.
|
||||
if (transferred < 5) {
|
||||
ERROR (abstract->context, "Invalid packet length (" DC_PRINTF_SIZE ").", transferred);
|
||||
return DC_STATUS_PROTOCOL;
|
||||
}
|
||||
|
||||
// Verify the start byte.
|
||||
if (packet[0] != STARTBYTE) {
|
||||
ERROR (abstract->context, "Unexpected packet start byte (%02x).", packet[0]);
|
||||
return DC_STATUS_PROTOCOL;
|
||||
}
|
||||
|
||||
// Verify the command byte.
|
||||
if (packet[2] != cmd) {
|
||||
ERROR (abstract->context, "Unexpected packet command byte (%02x).", packet[2]);
|
||||
return DC_STATUS_PROTOCOL;
|
||||
}
|
||||
|
||||
// Verify the length byte.
|
||||
unsigned int length = packet[4];
|
||||
if (length + 5 > transferred) {
|
||||
ERROR (abstract->context, "Invalid packet length (%u).", length);
|
||||
return DC_STATUS_PROTOCOL;
|
||||
}
|
||||
|
||||
// Verify the checksum.
|
||||
unsigned char crc = packet[3]; packet[3] = 0;
|
||||
unsigned char ccrc = checksum (packet, length + 5);
|
||||
if (crc != ccrc) {
|
||||
ERROR (abstract->context, "Unexpected packet checksum (%02x %02x).", crc, ccrc);
|
||||
return DC_STATUS_PROTOCOL;
|
||||
}
|
||||
|
||||
// Check the flag byte for the last packet.
|
||||
unsigned char flag = packet[1];
|
||||
if ((flag & FLAG_LAST) == FLAG_LAST) {
|
||||
// The last packet (typically 2 bytes) does not get appended!
|
||||
if (length) {
|
||||
errcode = packet[5];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Append the payload data to the output buffer. If the output
|
||||
// buffer is too small, the error is not reported immediately
|
||||
// but delayed until all packets have been received.
|
||||
if (nbytes < size) {
|
||||
unsigned int n = length;
|
||||
if (nbytes + n > size) {
|
||||
n = size - nbytes;
|
||||
}
|
||||
memcpy (data + nbytes, packet + 5, n);
|
||||
}
|
||||
nbytes += length;
|
||||
}
|
||||
|
||||
// Verify the expected number of bytes.
|
||||
if (nbytes != size) {
|
||||
ERROR (abstract->context, "Unexpected number of bytes received (%u %u).", nbytes, size);
|
||||
return DC_STATUS_PROTOCOL;
|
||||
}
|
||||
|
||||
if (errorcode) {
|
||||
*errorcode = errcode;
|
||||
}
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
pelagic_i330r_transfer (pelagic_i330r_device_t *device, unsigned char cmd, unsigned char flag, const unsigned char data[], unsigned int size, unsigned char answer[], unsigned int asize, unsigned int response)
|
||||
{
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
dc_device_t *abstract = (dc_device_t *) device;
|
||||
unsigned int errorcode = 0;
|
||||
|
||||
status = pelagic_i330r_send (device, cmd, flag, data, size);
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
return status;
|
||||
|
||||
status = pelagic_i330r_recv (device, cmd, answer, asize, &errorcode);
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
return status;
|
||||
|
||||
if (errorcode != response) {
|
||||
ERROR (abstract->context, "Unexpected response code (%u)", errorcode);
|
||||
return DC_STATUS_PROTOCOL;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
pelagic_i330r_init_accesscode (pelagic_i330r_device_t *device)
|
||||
{
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
|
||||
const unsigned char zero[9] = {0};
|
||||
status = pelagic_i330r_transfer (device, CMD_ACCESS_REQUEST, FLAG_REQUEST, zero, sizeof(zero), NULL, 0, RSP_READY);
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
return status;
|
||||
|
||||
status = pelagic_i330r_transfer (device, CMD_ACCESS_REQUEST, FLAG_DATA, device->accesscode, sizeof(device->accesscode), NULL, 0, RSP_DONE);
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
return status;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
pelagic_i330r_init_passcode (pelagic_i330r_device_t *device, const char *pincode)
|
||||
{
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
dc_device_t *abstract = (dc_device_t *) device;
|
||||
unsigned char passcode[MAXPASSCODE] = {0};
|
||||
|
||||
// Check the maximum length.
|
||||
size_t len = pincode ? strlen (pincode) : 0;
|
||||
if (len > sizeof(passcode)) {
|
||||
ERROR (abstract->context, "Invalid pincode length (" DC_PRINTF_SIZE ").", len);
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
}
|
||||
|
||||
// Convert to binary number.
|
||||
unsigned int offset = sizeof(passcode) - len;
|
||||
for (unsigned int i = 0; i < len; i++) {
|
||||
unsigned char c = pincode[i];
|
||||
if (c < '0' || c > '9') {
|
||||
ERROR (abstract->context, "Invalid pincode character (%c).", c);
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
}
|
||||
passcode[offset + i] = c - '0';
|
||||
}
|
||||
|
||||
const unsigned char zero[9] = {0};
|
||||
status = pelagic_i330r_transfer (device, CMD_ACCESS_CODE, FLAG_REQUEST, zero, sizeof(zero), NULL, 0, RSP_READY);
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
return status;
|
||||
|
||||
status = pelagic_i330r_transfer (device, CMD_ACCESS_CODE, FLAG_DATA, passcode, sizeof(passcode), device->accesscode, sizeof(device->accesscode), RSP_DONE);
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
return status;
|
||||
|
||||
HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Access code", device->accesscode, sizeof(device->accesscode));
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
pelagic_i330r_init_handshake (pelagic_i330r_device_t *device, unsigned int readwrite)
|
||||
{
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
dc_device_t *abstract = (dc_device_t *) device;
|
||||
|
||||
const unsigned char cmd = readwrite ? CMD_WAKEUP_RDWR : CMD_WAKEUP_RDONLY;
|
||||
|
||||
const unsigned char args[9] = {0, 0, 0, 0, 0x0C, 0, 0, 0, 0};
|
||||
status = pelagic_i330r_transfer (device, cmd, FLAG_REQUEST, args, sizeof(args), device->id, sizeof(device->id), RSP_DONE);
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
return status;
|
||||
|
||||
HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "ID", device->id, sizeof(device->id));
|
||||
|
||||
device->model = array_uint16_be (device->id + 12);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
pelagic_i330r_init_auth (pelagic_i330r_device_t *device)
|
||||
{
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
|
||||
const unsigned char args[2][9] = {
|
||||
{0xFF, 0xFF, 0xFF, 0xFF}, // DSX
|
||||
{0x37, 0x30, 0x31, 0x55}, // I330R
|
||||
};
|
||||
unsigned int args_idx = device->model == DSX ? 0 : 1;
|
||||
status = pelagic_i330r_transfer (device, CMD_AUTHENTICATION, FLAG_REQUEST, args[args_idx], sizeof(args[args_idx]), NULL, 0, RSP_READY);
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
return status;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
pelagic_i330r_init (pelagic_i330r_device_t *device)
|
||||
{
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
dc_device_t *abstract = (dc_device_t *) device;
|
||||
|
||||
// Get the bluetooth access code.
|
||||
status = dc_iostream_ioctl (device->iostream, DC_IOCTL_BLE_GET_ACCESSCODE, device->accesscode, sizeof(device->accesscode));
|
||||
if (status != DC_STATUS_SUCCESS && status != DC_STATUS_UNSUPPORTED) {
|
||||
ERROR (abstract->context, "Failed to get the access code.");
|
||||
return status;
|
||||
}
|
||||
|
||||
if (array_isequal (device->accesscode, sizeof(device->accesscode), 0)) {
|
||||
// Request to display the PIN code.
|
||||
status = pelagic_i330r_init_accesscode (device);
|
||||
if (status != DC_STATUS_SUCCESS) {
|
||||
ERROR (abstract->context, "Failed to display the PIN code.");
|
||||
return status;
|
||||
}
|
||||
|
||||
// Get the bluetooth PIN code.
|
||||
char pincode[6 + 1] = {0};
|
||||
status = dc_iostream_ioctl (device->iostream, DC_IOCTL_BLE_GET_PINCODE, pincode, sizeof(pincode));
|
||||
if (status != DC_STATUS_SUCCESS) {
|
||||
ERROR (abstract->context, "Failed to get the PIN code.");
|
||||
return status;
|
||||
}
|
||||
|
||||
// Force a null terminated string.
|
||||
pincode[sizeof(pincode) - 1] = 0;
|
||||
|
||||
// Request the access code.
|
||||
status = pelagic_i330r_init_passcode (device, pincode);
|
||||
if (status != DC_STATUS_SUCCESS) {
|
||||
ERROR (abstract->context, "Failed to request the access code.");
|
||||
return status;
|
||||
}
|
||||
|
||||
// Store the bluetooth access code.
|
||||
status = dc_iostream_ioctl (device->iostream, DC_IOCTL_BLE_SET_ACCESSCODE, device->accesscode, sizeof(device->accesscode));
|
||||
if (status != DC_STATUS_SUCCESS && status != DC_STATUS_UNSUPPORTED) {
|
||||
ERROR (abstract->context, "Failed to store the access code.");
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
// Request access.
|
||||
status = pelagic_i330r_init_accesscode (device);
|
||||
if (status != DC_STATUS_SUCCESS) {
|
||||
ERROR (abstract->context, "Failed to request access.");
|
||||
return status;
|
||||
}
|
||||
|
||||
// Send the wakeup command.
|
||||
status = pelagic_i330r_init_handshake (device, 1);
|
||||
if (status != DC_STATUS_SUCCESS) {
|
||||
ERROR (abstract->context, "Failed to send the wakeup command.");
|
||||
return status;
|
||||
}
|
||||
|
||||
// Send the authentication code.
|
||||
status = pelagic_i330r_init_auth (device);
|
||||
if (status != DC_STATUS_SUCCESS) {
|
||||
ERROR (abstract->context, "Failed to send the authentication code.");
|
||||
return status;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
pelagic_i330r_download (pelagic_i330r_device_t *device, unsigned char cmd, const unsigned char data[], unsigned int size, unsigned char answer[], unsigned int asize)
|
||||
{
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
dc_device_t *abstract = (dc_device_t *) device;
|
||||
|
||||
status = pelagic_i330r_transfer (device, cmd, FLAG_REQUEST, data, size, answer, asize, RSP_DONE);
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
return status;
|
||||
|
||||
// Verify the checksum
|
||||
unsigned short crc = array_uint16_be (answer + asize - 2);
|
||||
unsigned short ccrc = checksum_crc16_ccitt (answer, asize - 2, 0xffff, 0x0000);
|
||||
if (crc != ccrc) {
|
||||
ERROR (abstract->context, "Unexpected data checksum (%04x %04x).", crc, ccrc);
|
||||
return DC_STATUS_PROTOCOL;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
dc_status_t
|
||||
pelagic_i330r_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream, unsigned int model)
|
||||
{
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
pelagic_i330r_device_t *device = NULL;
|
||||
|
||||
if (out == NULL)
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
|
||||
// Allocate memory.
|
||||
device = (pelagic_i330r_device_t *) dc_device_allocate (context, &pelagic_i330r_device_vtable.base);
|
||||
if (device == NULL) {
|
||||
ERROR (context, "Failed to allocate memory.");
|
||||
return DC_STATUS_NOMEMORY;
|
||||
}
|
||||
|
||||
// Initialize the base class.
|
||||
oceanic_common_device_init (&device->base);
|
||||
|
||||
// Override the base class values.
|
||||
device->base.multipage = 256;
|
||||
|
||||
// Set the default values.
|
||||
device->iostream = iostream;
|
||||
memset (device->accesscode, 0, sizeof(device->accesscode));
|
||||
memset (device->id, 0, sizeof(device->id));
|
||||
memset (device->hwcal, 0, sizeof(device->hwcal));
|
||||
memset (device->flashmap, 0, sizeof(device->flashmap));
|
||||
device->model = 0;
|
||||
|
||||
// Set the timeout for receiving data (3000 ms).
|
||||
status = dc_iostream_set_timeout (device->iostream, 3000);
|
||||
if (status != DC_STATUS_SUCCESS) {
|
||||
ERROR (context, "Failed to set the timeout.");
|
||||
goto error_free;
|
||||
}
|
||||
|
||||
// Perform the bluetooth authentication.
|
||||
status = pelagic_i330r_init (device);
|
||||
if (status != DC_STATUS_SUCCESS) {
|
||||
ERROR (context, "Failed to perform the bluetooth authentication.");
|
||||
goto error_free;
|
||||
}
|
||||
|
||||
// Download the calibration data.
|
||||
const unsigned char args[9] = {0, 0, 0, 0, 0, 0x01, 0, 0, 0};
|
||||
status = pelagic_i330r_download (device, CMD_READ_HW_CAL, args, sizeof(args), device->hwcal, sizeof(device->hwcal));
|
||||
if (status != DC_STATUS_SUCCESS) {
|
||||
ERROR (context, "Failed to download the calibration data.");
|
||||
goto error_free;
|
||||
}
|
||||
|
||||
HEXDUMP (context, DC_LOGLEVEL_DEBUG, "Hwcal", device->hwcal, sizeof(device->hwcal));
|
||||
|
||||
// Download the flash map.
|
||||
const unsigned char zero[9] = {0};
|
||||
status = pelagic_i330r_download (device, CMD_READ_EXFLASHMAP, zero, sizeof(zero), device->flashmap, sizeof(device->flashmap));
|
||||
if (status != DC_STATUS_SUCCESS) {
|
||||
ERROR (context, "Failed to download the flash map.");
|
||||
goto error_free;
|
||||
}
|
||||
|
||||
HEXDUMP (context, DC_LOGLEVEL_DEBUG, "Flashmap", device->flashmap, sizeof(device->flashmap));
|
||||
|
||||
// Detect the memory layout.
|
||||
if (device->model == DSX) {
|
||||
device->base.layout = &pelagic_dsx;
|
||||
} else {
|
||||
device->base.layout = &pelagic_i330r;
|
||||
}
|
||||
|
||||
*out = (dc_device_t *) device;
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
|
||||
error_free:
|
||||
dc_device_deallocate ((dc_device_t *) device);
|
||||
return status;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
pelagic_i330r_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size)
|
||||
{
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
pelagic_i330r_device_t *device = (pelagic_i330r_device_t*) abstract;
|
||||
|
||||
unsigned char command[9] = {0};
|
||||
array_uint32_le_set(command + 0, address);
|
||||
array_uint32_le_set(command + 4, size);
|
||||
|
||||
status = pelagic_i330r_transfer (device, CMD_READ_FLASH, FLAG_NONE, command, sizeof(command), data, size, RSP_DONE);
|
||||
if (status != DC_STATUS_SUCCESS) {
|
||||
return status;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
pelagic_i330r_device_devinfo (dc_device_t *abstract, dc_event_progress_t *progress)
|
||||
{
|
||||
pelagic_i330r_device_t *device = (pelagic_i330r_device_t *) abstract;
|
||||
|
||||
assert (device != NULL);
|
||||
|
||||
// Emit a device info event.
|
||||
dc_event_devinfo_t devinfo;
|
||||
devinfo.model = device->model;
|
||||
devinfo.firmware = 0;
|
||||
devinfo.serial =
|
||||
bcd2dec (device->hwcal[12]) +
|
||||
bcd2dec (device->hwcal[13]) * 100 +
|
||||
bcd2dec (device->hwcal[14]) * 10000;
|
||||
device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo);
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
pelagic_i330r_device_pointers (dc_device_t *abstract, dc_event_progress_t *progress, unsigned int *rb_logbook_begin, unsigned int *rb_logbook_end, unsigned int *rb_profile_begin, unsigned int *rb_profile_end)
|
||||
{
|
||||
pelagic_i330r_device_t *device = (pelagic_i330r_device_t *) abstract;
|
||||
|
||||
assert (device != NULL);
|
||||
assert (device->base.layout != NULL);
|
||||
assert (rb_logbook_begin != NULL && rb_logbook_end != NULL);
|
||||
assert (rb_profile_begin != NULL && rb_profile_end != NULL);
|
||||
|
||||
const oceanic_common_layout_t *layout = device->base.layout;
|
||||
|
||||
// Get the logbook pointers.
|
||||
unsigned int rb_logbook_min = array_uint32_le (device->flashmap + 0x50);
|
||||
unsigned int rb_logbook_max = array_uint32_le (device->flashmap + 0x54);
|
||||
unsigned int rb_logbook_first = array_uint32_le (device->flashmap + 0x58);
|
||||
unsigned int rb_logbook_last = array_uint32_le (device->flashmap + 0x5C);
|
||||
if (rb_logbook_min != 0 && rb_logbook_max != 0) {
|
||||
rb_logbook_max += 1;
|
||||
}
|
||||
|
||||
// Get the profile pointers.
|
||||
unsigned int rb_profile_min = array_uint32_le (device->flashmap + 0x70);
|
||||
unsigned int rb_profile_max = array_uint32_le (device->flashmap + 0x74);
|
||||
unsigned int rb_profile_first = array_uint32_le (device->flashmap + 0x78);
|
||||
unsigned int rb_profile_last = array_uint32_le (device->flashmap + 0x7C);
|
||||
if (rb_profile_min != 0 && rb_profile_max != 0) {
|
||||
rb_profile_max += 1;
|
||||
}
|
||||
|
||||
// Check the logbook ringbuffer area.
|
||||
if (rb_logbook_min != layout->rb_logbook_begin ||
|
||||
rb_logbook_max != layout->rb_logbook_end) {
|
||||
ERROR (abstract->context, "Unexpected logbook ringbuffer area (%08x %08x)",
|
||||
rb_logbook_min, rb_logbook_max);
|
||||
return DC_STATUS_DATAFORMAT;
|
||||
}
|
||||
|
||||
// Check the profile ringbuffer area.
|
||||
if (rb_profile_min != layout->rb_profile_begin ||
|
||||
rb_profile_max != layout->rb_profile_end) {
|
||||
ERROR (abstract->context, "Unexpected profile ringbuffer area (%08x %08x)",
|
||||
rb_profile_min, rb_profile_max);
|
||||
return DC_STATUS_DATAFORMAT;
|
||||
}
|
||||
|
||||
// Get the begin/end pointers.
|
||||
if (device->model == DSX) {
|
||||
*rb_logbook_begin = rb_logbook_first;
|
||||
*rb_logbook_end = rb_logbook_last;
|
||||
} else {
|
||||
*rb_logbook_begin = rb_logbook_min;
|
||||
*rb_logbook_end = rb_logbook_last + 1;
|
||||
}
|
||||
*rb_profile_begin = rb_profile_first;
|
||||
*rb_profile_end = rb_profile_last;
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
40
src/pelagic_i330r.h
Normal file
40
src/pelagic_i330r.h
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* libdivecomputer
|
||||
*
|
||||
* Copyright (C) 2023 Janice McLaughlin
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef PELAGIC_I330R_H
|
||||
#define PELAGIC_I330R_H
|
||||
|
||||
#include <libdivecomputer/context.h>
|
||||
#include <libdivecomputer/iostream.h>
|
||||
#include <libdivecomputer/device.h>
|
||||
#include <libdivecomputer/parser.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
dc_status_t
|
||||
pelagic_i330r_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream, unsigned int model);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
#endif /* PELAGIC_I330R_H */
|
||||
Loading…
x
Reference in New Issue
Block a user