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:
Janice McLaughlin 2023-02-08 23:35:16 +01:00 committed by Jef Driesen
parent 8e349d4046
commit 9070da3570
13 changed files with 871 additions and 33 deletions

View File

@ -71,6 +71,7 @@ LOCAL_SRC_FILES := \
src/oceans_s1_parser.c \ src/oceans_s1_parser.c \
src/packet.c \ src/packet.c \
src/parser.c \ src/parser.c \
src/pelagic_i330r.c \
src/platform.c \ src/platform.c \
src/rbstream.c \ src/rbstream.c \
src/reefnet_sensus.c \ src/reefnet_sensus.c \

View File

@ -239,6 +239,7 @@
<ClCompile Include="..\..\src\oceans_s1_parser.c" /> <ClCompile Include="..\..\src\oceans_s1_parser.c" />
<ClCompile Include="..\..\src\packet.c" /> <ClCompile Include="..\..\src\packet.c" />
<ClCompile Include="..\..\src\parser.c" /> <ClCompile Include="..\..\src\parser.c" />
<ClCompile Include="..\..\src\pelagic_i330r.c" />
<ClCompile Include="..\..\src\platform.c" /> <ClCompile Include="..\..\src\platform.c" />
<ClCompile Include="..\..\src\rbstream.c" /> <ClCompile Include="..\..\src\rbstream.c" />
<ClCompile Include="..\..\src\reefnet_sensus.c" /> <ClCompile Include="..\..\src\reefnet_sensus.c" />
@ -357,6 +358,7 @@
<ClInclude Include="..\..\src\oceans_s1_common.h" /> <ClInclude Include="..\..\src\oceans_s1_common.h" />
<ClInclude Include="..\..\src\packet.h" /> <ClInclude Include="..\..\src\packet.h" />
<ClInclude Include="..\..\src\parser-private.h" /> <ClInclude Include="..\..\src\parser-private.h" />
<ClInclude Include="..\..\src\pelagic_i330r.h" />
<ClInclude Include="..\..\src\platform.h" /> <ClInclude Include="..\..\src\platform.h" />
<ClInclude Include="..\..\src\rbstream.h" /> <ClInclude Include="..\..\src\rbstream.h" />
<ClInclude Include="..\..\src\reefnet_sensus.h" /> <ClInclude Include="..\..\src\reefnet_sensus.h" />

View File

@ -72,6 +72,7 @@ static const backend_table_t g_backends[] = {
{"vtpro", DC_FAMILY_OCEANIC_VTPRO, 0x4245}, {"vtpro", DC_FAMILY_OCEANIC_VTPRO, 0x4245},
{"veo250", DC_FAMILY_OCEANIC_VEO250, 0x424C}, {"veo250", DC_FAMILY_OCEANIC_VEO250, 0x424C},
{"atom2", DC_FAMILY_OCEANIC_ATOM2, 0x4342}, {"atom2", DC_FAMILY_OCEANIC_ATOM2, 0x4342},
{"i330r", DC_FAMILY_PELAGIC_I330R, 0x4744},
{"nemo", DC_FAMILY_MARES_NEMO, 0}, {"nemo", DC_FAMILY_MARES_NEMO, 0},
{"puck", DC_FAMILY_MARES_PUCK, 7}, {"puck", DC_FAMILY_MARES_PUCK, 7},
{"darwin", DC_FAMILY_MARES_DARWIN, 0}, {"darwin", DC_FAMILY_MARES_DARWIN, 0},

View File

@ -74,6 +74,7 @@ typedef enum dc_family_t {
DC_FAMILY_OCEANIC_VTPRO = (4 << 16), DC_FAMILY_OCEANIC_VTPRO = (4 << 16),
DC_FAMILY_OCEANIC_VEO250, DC_FAMILY_OCEANIC_VEO250,
DC_FAMILY_OCEANIC_ATOM2, DC_FAMILY_OCEANIC_ATOM2,
DC_FAMILY_PELAGIC_I330R,
/* Mares */ /* Mares */
DC_FAMILY_MARES_NEMO = (5 << 16), DC_FAMILY_MARES_NEMO = (5 << 16),
DC_FAMILY_MARES_PUCK, DC_FAMILY_MARES_PUCK,

View File

@ -43,6 +43,7 @@ libdivecomputer_la_SOURCES = \
oceanic_atom2.h oceanic_atom2.c oceanic_atom2_parser.c \ oceanic_atom2.h oceanic_atom2.c oceanic_atom2_parser.c \
oceanic_veo250.h oceanic_veo250.c oceanic_veo250_parser.c \ oceanic_veo250.h oceanic_veo250.c oceanic_veo250_parser.c \
oceanic_vtpro.h oceanic_vtpro.c oceanic_vtpro_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_common.h mares_common.c \
mares_nemo.h mares_nemo.c mares_nemo_parser.c \ mares_nemo.h mares_nemo.c mares_nemo_parser.c \
mares_puck.h mares_puck.c \ mares_puck.h mares_puck.c \

View File

@ -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", "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}, {"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}, {"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 */
{"Mares", "Nemo", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Nemo", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Nemo Steel", 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 0x4654, // Oceanic Veo 4.0
0x4655, // Sherwood Wisdom 4 0x4655, // Sherwood Wisdom 4
0x4656, // Oceanic Pro Plus 4 0x4656, // Oceanic Pro Plus 4
0x4741, // Apeks DSX
0x4742, // Sherwood Beacon 0x4742, // Sherwood Beacon
0x4743, // Aqualung i470TC 0x4743, // Aqualung i470TC
0x4744, // Aqualung i330R
0x4749, // Aqualung i200C 0x4749, // Aqualung i200C
0x474B, // Oceanic Geo Air 0x474B, // Oceanic Geo Air
}; };

View File

@ -38,6 +38,7 @@
#include "oceanic_atom2.h" #include "oceanic_atom2.h"
#include "oceanic_veo250.h" #include "oceanic_veo250.h"
#include "oceanic_vtpro.h" #include "oceanic_vtpro.h"
#include "pelagic_i330r.h"
#include "mares_darwin.h" #include "mares_darwin.h"
#include "mares_iconhd.h" #include "mares_iconhd.h"
#include "mares_nemo.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: case DC_FAMILY_OCEANIC_ATOM2:
rc = oceanic_atom2_device_open (&device, context, iostream, dc_descriptor_get_model (descriptor)); rc = oceanic_atom2_device_open (&device, context, iostream, dc_descriptor_get_model (descriptor));
break; 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: case DC_FAMILY_MARES_NEMO:
rc = mares_nemo_device_open (&device, context, iostream); rc = mares_nemo_device_open (&device, context, iostream);
break; break;

View File

@ -35,6 +35,12 @@
#define GAUGE 1 #define GAUGE 1
#define FREEDIVE 2 #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 NGASMIXES 6
#define HEADER 1 #define HEADER 1
@ -45,6 +51,7 @@ typedef struct oceanic_atom2_parser_t oceanic_atom2_parser_t;
struct oceanic_atom2_parser_t { struct oceanic_atom2_parser_t {
dc_parser_t base; dc_parser_t base;
unsigned int model; unsigned int model;
unsigned int logbooksize;
unsigned int headersize; unsigned int headersize;
unsigned int footersize; unsigned int footersize;
// Cached fields. // Cached fields.
@ -93,6 +100,7 @@ oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, const uns
// Set the default values. // Set the default values.
parser->model = model; parser->model = model;
parser->logbooksize = 0;
parser->headersize = 9 * PAGESIZE / 2; parser->headersize = 9 * PAGESIZE / 2;
parser->footersize = 2 * PAGESIZE / 2; parser->footersize = 2 * PAGESIZE / 2;
if (model == DATAMASK || model == COMPUMASK || 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 || } else if (model == I550C || model == WISDOM4 ||
model == I200CV2) { model == I200CV2) {
parser->headersize = 5 * PAGESIZE / 2; 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; parser->cached = 0;
@ -170,8 +186,18 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim
if (datetime) { if (datetime) {
// AM/PM bit of the 12-hour clock. // AM/PM bit of the 12-hour clock.
unsigned int pm = p[1] & 0x80; unsigned int pm = p[1] & 0x80;
unsigned int have_ampm = 1;
switch (parser->model) { 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 OC1A:
case OC1B: case OC1B:
case OC1C: case OC1C:
@ -280,9 +306,11 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim
datetime->timezone = DC_TIMEZONE_NONE; datetime->timezone = DC_TIMEZONE_NONE;
// Convert to a 24-hour clock. // Convert to a 24-hour clock.
datetime->hour %= 12; if (have_ampm) {
if (pm) datetime->hour %= 12;
datetime->hour += 12; if (pm)
datetime->hour += 12;
}
/* /*
* Workaround for the year 2010 problem. * 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 || } else if (parser->model == VEO20 || parser->model == VEO30 ||
parser->model == OCS) { parser->model == OCS) {
mode = (data[1] & 0x60) >> 5; 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. // Get the gas mixes.
@ -414,6 +446,17 @@ oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser)
} else if (parser->model == WISDOM4) { } else if (parser->model == WISDOM4) {
o2_offset = header + 4; o2_offset = header + 4;
ngasmixes = 1; 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 { } else {
o2_offset = header + 4; o2_offset = header + 4;
ngasmixes = 3; ngasmixes = 3;
@ -427,6 +470,10 @@ oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser)
for (unsigned int i = 0; i < ngasmixes; ++i) { for (unsigned int i = 0; i < ngasmixes; ++i) {
if (data[o2_offset + i * o2_step]) { if (data[o2_offset + i * o2_step]) {
parser->oxygen[i] = 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 { } else {
parser->oxygen[i] = 21; 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 == F11A || parser->model == F11B ||
parser->model == MUNDIAL2 || parser->model == MUNDIAL3) parser->model == MUNDIAL2 || parser->model == MUNDIAL3)
*((double *) value) = array_uint16_le (data + 4) / 16.0 * FEET; *((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 else
*((double *) value) = (array_uint16_le (data + parser->footer + 4) & 0x0FFF) / 16.0 * FEET; *((double *) value) = (array_uint16_le (data + parser->footer + 4) & 0x0FFF) / 16.0 * FEET;
break; 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: case DC_FIELD_GASMIX_COUNT:
*((unsigned int *) value) = parser->ngasmixes; *((unsigned int *) value) = parser->ngasmixes;
break; break;
@ -506,23 +562,49 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns
water->type = DC_WATER_SALT; water->type = DC_WATER_SALT;
} }
water->density = 0.0; 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 { } else {
return DC_STATUS_UNSUPPORTED; return DC_STATUS_UNSUPPORTED;
} }
break; break;
case DC_FIELD_DIVEMODE: case DC_FIELD_DIVEMODE:
switch (parser->mode) { if (parser->model == DSX) {
case NORMAL: switch (parser->mode) {
*((unsigned int *) value) = DC_DIVEMODE_OC; case DSX_OC:
break; case DSX_SIDEMOUNT:
case GAUGE: *((unsigned int *) value) = DC_DIVEMODE_OC;
*((unsigned int *) value) = DC_DIVEMODE_GAUGE; break;
break; case DSX_SIDEGAUGE:
case FREEDIVE: case DSX_GAUGE:
*((unsigned int *) value) = DC_DIVEMODE_FREEDIVE; *((unsigned int *) value) = DC_DIVEMODE_GAUGE;
break; break;
default: case DSX_CC:
return DC_STATUS_DATAFORMAT; *((unsigned int *) value) = DC_DIVEMODE_CCR;
break;
default:
return DC_STATUS_DATAFORMAT;
}
} else {
switch (parser->mode) {
case NORMAL:
*((unsigned int *) value) = DC_DIVEMODE_OC;
break;
case GAUGE:
*((unsigned int *) value) = DC_DIVEMODE_GAUGE;
break;
case FREEDIVE:
*((unsigned int *) value) = DC_DIVEMODE_FREEDIVE;
break;
default:
return DC_STATUS_DATAFORMAT;
}
} }
break; break;
default: default:
@ -587,15 +669,19 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
unsigned int time = 0; unsigned int time = 0;
unsigned int interval = 1000; unsigned int interval = 1000;
if (parser->mode != FREEDIVE) { if (parser->mode != FREEDIVE) {
unsigned int offset = 0x17; if (parser->model == I330R || parser->model == DSX) {
if (parser->model == A300CS || parser->model == VTX || interval = data[parser->logbooksize + 36] * 1000;
parser->model == I450T || parser->model == I750TC || } else {
parser->model == PROPLUSX || parser->model == I770R || unsigned int offset = 0x17;
parser->model == SAGE || parser->model == BEACON) if (parser->model == A300CS || parser->model == VTX ||
offset = 0x1f; parser->model == I450T || parser->model == I750TC ||
const unsigned int intervals[] = {2000, 15000, 30000, 60000}; parser->model == PROPLUSX || parser->model == I770R ||
unsigned int idx = data[offset] & 0x03; parser->model == SAGE || parser->model == BEACON)
interval = intervals[idx]; offset = 0x1f;
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) { } else if (parser->model == F11A || parser->model == F11B) {
const unsigned int intervals[] = {250, 500, 1000, 2000}; const unsigned int intervals[] = {250, 500, 1000, 2000};
unsigned int idx = data[0x29] & 0x03; 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 == I750TC || parser->model == PROPLUSX ||
parser->model == I770R || parser->model == I470TC || parser->model == I770R || parser->model == I470TC ||
parser->model == SAGE || parser->model == BEACON || parser->model == SAGE || parser->model == BEACON ||
parser->model == GEOAIR) { parser->model == GEOAIR || parser->model == I330R) {
samplesize = PAGESIZE; samplesize = PAGESIZE;
} else if (parser->model == DSX) {
samplesize = 32;
} }
unsigned int have_temperature = 1, have_pressure = 1; 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 == I200 || parser->model == I100 ||
parser->model == I300C || parser->model == TALIS || parser->model == I300C || parser->model == TALIS ||
parser->model == I200C || parser->model == I200CV2 || parser->model == I200C || parser->model == I200CV2 ||
parser->model == GEO40 || parser->model == VEO40) { parser->model == GEO40 || parser->model == VEO40 ||
parser->model == I330R) {
have_pressure = 0; 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 == I770R|| parser->model == SAGE ||
parser->model == BEACON) { parser->model == BEACON) {
temperature = data[offset + 11]; temperature = data[offset + 11];
} else if (parser->model == I330R || parser->model == DSX) {
temperature = array_uint16_le(data + offset + 10);
} else { } else {
unsigned int sign; unsigned int sign;
if (parser->model == DG03 || parser->model == PROPLUS3 || if (parser->model == DG03 || parser->model == PROPLUS3 ||
@ -825,7 +916,11 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
else else
temperature += (data[offset + 7] & 0x0C) >> 2; temperature += (data[offset + 7] & 0x0C) >> 2;
} }
sample.temperature = (temperature - 32.0) * (5.0 / 9.0); 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); 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 == PROPLUSX || parser->model == I770R ||
parser->model == SAGE || parser->model == BEACON) parser->model == SAGE || parser->model == BEACON)
pressure = array_uint16_le (data + offset + 4); 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]; pressure -= data[offset + 1];
}
if (tank) { if (tank) {
sample.pressure.tank = tank - 1; sample.pressure.tank = tank - 1;
sample.pressure.value = pressure * PSI / BAR; 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 == I470TC || parser->model == I200CV2 ||
parser->model == GEOAIR) parser->model == GEOAIR)
depth = (data[offset + 4] + (data[offset + 5] << 8)) & 0x0FFF; 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) else if (parser->model == ATOM1)
depth = data[offset + 3] * 16; depth = data[offset + 3] * 16;
else else
depth = (data[offset + 2] + (data[offset + 3] << 8)) & 0x0FFF; depth = (data[offset + 2] + (data[offset + 3] << 8)) & 0x0FFF;
sample.depth = depth / 16.0 * FEET; 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); if (callback) callback (DC_SAMPLE_DEPTH, &sample, userdata);
// Gas mix // Gas mix
@ -887,8 +992,11 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
if (parser->model == TX1) { if (parser->model == TX1) {
gasmix = data[offset] & 0x07; gasmix = data[offset] & 0x07;
have_gasmix = 1; 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) { if (gasmix < 1 || gasmix > parser->ngasmixes) {
ERROR (abstract->context, "Invalid gas mix index (%u).", gasmix); ERROR (abstract->context, "Invalid gas mix index (%u).", gasmix);
return DC_STATUS_DATAFORMAT; 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; decostop = (data[offset + 7] & 0xF0) >> 4;
decotime = array_uint16_le(data + offset + 6) & 0x0FFF; decotime = array_uint16_le(data + offset + 6) & 0x0FFF;
have_deco = 1; 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 (have_deco) {
if (decostop) { if (decostop) {
sample.deco.type = DC_DECO_DECOSTOP; sample.deco.type = DC_DECO_DECOSTOP;
sample.deco.depth = decostop * 10 * FEET; if (parser->model == I330R || parser->model == DSX) {
sample.deco.depth = decostop * FEET;
} else {
sample.deco.depth = decostop * 10 * FEET;
}
} else { } else {
sample.deco.type = DC_DECO_NDL; sample.deco.type = DC_DECO_NDL;
sample.deco.depth = 0.0; 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); 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 // Bookmarks
unsigned int have_bookmark = 0; unsigned int have_bookmark = 0;
if (parser->model == OC1A || parser->model == OC1B || if (parser->model == OC1A || parser->model == OC1B ||

View File

@ -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) { } else if (layout->pt_mode_logbook == 2 || layout->pt_mode_logbook == 3) {
first = array_uint16_le (data + 16); first = array_uint16_le (data + 16);
last = array_uint16_le (data + 18); 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. // Convert pages to bytes.
@ -82,7 +85,7 @@ oceanic_common_device_get_profile (const unsigned char data[], const oceanic_com
} }
*begin = layout->highmem + first; *begin = layout->highmem + first;
*end = layout->highmem + last + pagesize; *end = layout->highmem + last + (layout->pt_mode_logbook < 4 ? pagesize : 0);
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }

View File

@ -125,8 +125,12 @@ extern "C" {
#define I200CV2 0x4749 #define I200CV2 0x4749
#define GEOAIR 0x474B #define GEOAIR 0x474B
// i330r
#define DSX 0x4741
#define I330R 0x4744
#define PAGESIZE 0x10 #define PAGESIZE 0x10
#define FPMAXSIZE 0x20 #define FPMAXSIZE 0x200
#define OCEANIC_COMMON_MATCH(version,patterns,firmware) \ #define OCEANIC_COMMON_MATCH(version,patterns,firmware) \
oceanic_common_match ((version), (patterns), \ oceanic_common_match ((version), (patterns), \

View File

@ -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); rc = oceanic_veo250_parser_create (&parser, context, data, size, model);
break; break;
case DC_FAMILY_OCEANIC_ATOM2: case DC_FAMILY_OCEANIC_ATOM2:
case DC_FAMILY_PELAGIC_I330R:
if (model == REACTPROWHITE) if (model == REACTPROWHITE)
rc = oceanic_veo250_parser_create (&parser, context, data, size, model); rc = oceanic_veo250_parser_create (&parser, context, data, size, model);
else else

646
src/pelagic_i330r.c Normal file
View 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
View 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 */