Add support for the Aqualung i330R and Apeks DSX
The Aqualung i330R and Apeks DSX use a completely new communication protocol. The main (and most problematic) difference is the use of a proprietary bluetooth pairing mechanism instead of the standard bluetooth pairing. The data format remains more or less compatible with the previous models, with only the usual changes to the parser. The initial code and reverse engineering work was contributed by Janice, with further improvements and modifications for integration in libdivecomputer by Jef. Co-authored-by: Jef Driesen <jef@libdivecomputer.org>
This commit is contained in:
parent
8e349d4046
commit
9070da3570
@ -71,6 +71,7 @@ LOCAL_SRC_FILES := \
|
||||
src/oceans_s1_parser.c \
|
||||
src/packet.c \
|
||||
src/parser.c \
|
||||
src/pelagic_i330r.c \
|
||||
src/platform.c \
|
||||
src/rbstream.c \
|
||||
src/reefnet_sensus.c \
|
||||
|
||||
@ -239,6 +239,7 @@
|
||||
<ClCompile Include="..\..\src\oceans_s1_parser.c" />
|
||||
<ClCompile Include="..\..\src\packet.c" />
|
||||
<ClCompile Include="..\..\src\parser.c" />
|
||||
<ClCompile Include="..\..\src\pelagic_i330r.c" />
|
||||
<ClCompile Include="..\..\src\platform.c" />
|
||||
<ClCompile Include="..\..\src\rbstream.c" />
|
||||
<ClCompile Include="..\..\src\reefnet_sensus.c" />
|
||||
@ -357,6 +358,7 @@
|
||||
<ClInclude Include="..\..\src\oceans_s1_common.h" />
|
||||
<ClInclude Include="..\..\src\packet.h" />
|
||||
<ClInclude Include="..\..\src\parser-private.h" />
|
||||
<ClInclude Include="..\..\src\pelagic_i330r.h" />
|
||||
<ClInclude Include="..\..\src\platform.h" />
|
||||
<ClInclude Include="..\..\src\rbstream.h" />
|
||||
<ClInclude Include="..\..\src\reefnet_sensus.h" />
|
||||
|
||||
@ -72,6 +72,7 @@ static const backend_table_t g_backends[] = {
|
||||
{"vtpro", DC_FAMILY_OCEANIC_VTPRO, 0x4245},
|
||||
{"veo250", DC_FAMILY_OCEANIC_VEO250, 0x424C},
|
||||
{"atom2", DC_FAMILY_OCEANIC_ATOM2, 0x4342},
|
||||
{"i330r", DC_FAMILY_PELAGIC_I330R, 0x4744},
|
||||
{"nemo", DC_FAMILY_MARES_NEMO, 0},
|
||||
{"puck", DC_FAMILY_MARES_PUCK, 7},
|
||||
{"darwin", DC_FAMILY_MARES_DARWIN, 0},
|
||||
|
||||
@ -74,6 +74,7 @@ typedef enum dc_family_t {
|
||||
DC_FAMILY_OCEANIC_VTPRO = (4 << 16),
|
||||
DC_FAMILY_OCEANIC_VEO250,
|
||||
DC_FAMILY_OCEANIC_ATOM2,
|
||||
DC_FAMILY_PELAGIC_I330R,
|
||||
/* Mares */
|
||||
DC_FAMILY_MARES_NEMO = (5 << 16),
|
||||
DC_FAMILY_MARES_PUCK,
|
||||
|
||||
@ -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 \
|
||||
|
||||
@ -272,6 +272,9 @@ static const dc_descriptor_t g_descriptors[] = {
|
||||
{"Aqualung", "i470TC", DC_FAMILY_OCEANIC_ATOM2, 0x4743, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic},
|
||||
{"Aqualung", "i200C", DC_FAMILY_OCEANIC_ATOM2, 0x4749, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic},
|
||||
{"Oceanic", "Geo Air", DC_FAMILY_OCEANIC_ATOM2, 0x474B, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic},
|
||||
/* Pelagic I330R */
|
||||
{"Apeks", "DSX", DC_FAMILY_PELAGIC_I330R, 0x4741, DC_TRANSPORT_BLE, dc_filter_oceanic},
|
||||
{"Aqualung", "i330R", DC_FAMILY_PELAGIC_I330R, 0x4744, DC_TRANSPORT_BLE, dc_filter_oceanic},
|
||||
/* Mares Nemo */
|
||||
{"Mares", "Nemo", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Mares", "Nemo Steel", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL},
|
||||
@ -743,8 +746,10 @@ dc_filter_oceanic (dc_descriptor_t *descriptor, dc_transport_t transport, const
|
||||
0x4654, // Oceanic Veo 4.0
|
||||
0x4655, // Sherwood Wisdom 4
|
||||
0x4656, // Oceanic Pro Plus 4
|
||||
0x4741, // Apeks DSX
|
||||
0x4742, // Sherwood Beacon
|
||||
0x4743, // Aqualung i470TC
|
||||
0x4744, // Aqualung i330R
|
||||
0x4749, // Aqualung i200C
|
||||
0x474B, // Oceanic Geo Air
|
||||
};
|
||||
|
||||
@ -38,6 +38,7 @@
|
||||
#include "oceanic_atom2.h"
|
||||
#include "oceanic_veo250.h"
|
||||
#include "oceanic_vtpro.h"
|
||||
#include "pelagic_i330r.h"
|
||||
#include "mares_darwin.h"
|
||||
#include "mares_iconhd.h"
|
||||
#include "mares_nemo.h"
|
||||
@ -162,6 +163,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr
|
||||
case DC_FAMILY_OCEANIC_ATOM2:
|
||||
rc = oceanic_atom2_device_open (&device, context, iostream, dc_descriptor_get_model (descriptor));
|
||||
break;
|
||||
case DC_FAMILY_PELAGIC_I330R:
|
||||
rc = pelagic_i330r_device_open (&device, context, iostream, dc_descriptor_get_model (descriptor));
|
||||
break;
|
||||
case DC_FAMILY_MARES_NEMO:
|
||||
rc = mares_nemo_device_open (&device, context, iostream);
|
||||
break;
|
||||
|
||||
@ -35,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
|
||||
@ -45,6 +51,7 @@ 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;
|
||||
// Cached fields.
|
||||
@ -93,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 ||
|
||||
@ -131,6 +139,14 @@ 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->cached = 0;
|
||||
@ -170,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:
|
||||
@ -280,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.
|
||||
@ -357,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.
|
||||
@ -414,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;
|
||||
@ -427,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;
|
||||
}
|
||||
@ -485,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;
|
||||
@ -506,11 +562,36 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns
|
||||
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;
|
||||
@ -524,6 +605,7 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns
|
||||
default:
|
||||
return DC_STATUS_DATAFORMAT;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
@ -587,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 ||
|
||||
@ -596,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;
|
||||
@ -618,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;
|
||||
@ -634,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;
|
||||
}
|
||||
|
||||
@ -803,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 ||
|
||||
@ -825,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);
|
||||
}
|
||||
|
||||
@ -850,8 +945,12 @@ 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) - 1;
|
||||
} else {
|
||||
pressure -= data[offset + 1];
|
||||
}
|
||||
if (tank) {
|
||||
sample.pressure.tank = tank - 1;
|
||||
sample.pressure.value = pressure * PSI / BAR;
|
||||
@ -874,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
|
||||
@ -887,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;
|
||||
@ -934,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;
|
||||
@ -972,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 ||
|
||||
|
||||
@ -58,6 +58,9 @@ oceanic_common_device_get_profile (const unsigned char data[], const oceanic_com
|
||||
} 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.
|
||||
@ -82,7 +85,7 @@ oceanic_common_device_get_profile (const unsigned char data[], const oceanic_com
|
||||
}
|
||||
|
||||
*begin = layout->highmem + first;
|
||||
*end = layout->highmem + last + pagesize;
|
||||
*end = layout->highmem + last + (layout->pt_mode_logbook < 4 ? pagesize : 0);
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
@ -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), \
|
||||
|
||||
@ -124,6 +124,7 @@ 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
|
||||
|
||||
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