diff --git a/contrib/android/Android.mk b/contrib/android/Android.mk
index fcceb81..338c6be 100644
--- a/contrib/android/Android.mk
+++ b/contrib/android/Android.mk
@@ -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 \
diff --git a/contrib/msvc/libdivecomputer.vcxproj b/contrib/msvc/libdivecomputer.vcxproj
index 704ef8b..991f1ac 100644
--- a/contrib/msvc/libdivecomputer.vcxproj
+++ b/contrib/msvc/libdivecomputer.vcxproj
@@ -239,6 +239,7 @@
+
@@ -357,6 +358,7 @@
+
diff --git a/examples/common.c b/examples/common.c
index bccb8bd..767acb2 100644
--- a/examples/common.c
+++ b/examples/common.c
@@ -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},
diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h
index 6bf3556..dbb463b 100644
--- a/include/libdivecomputer/common.h
+++ b/include/libdivecomputer/common.h
@@ -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,
diff --git a/src/Makefile.am b/src/Makefile.am
index be3f2e3..16680d2 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -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 \
diff --git a/src/descriptor.c b/src/descriptor.c
index 66555e7..096c8ee 100644
--- a/src/descriptor.c
+++ b/src/descriptor.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
};
diff --git a/src/device.c b/src/device.c
index 6979f22..ff6a198 100644
--- a/src/device.c
+++ b/src/device.c
@@ -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;
diff --git a/src/oceanic_atom2_parser.c b/src/oceanic_atom2_parser.c
index 6de04f2..849478c 100644
--- a/src/oceanic_atom2_parser.c
+++ b/src/oceanic_atom2_parser.c
@@ -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.
- datetime->hour %= 12;
- if (pm)
- datetime->hour += 12;
+ 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,23 +562,49 @@ 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:
- 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;
+ 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;
+ 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;
default:
@@ -587,15 +669,19 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
unsigned int time = 0;
unsigned int interval = 1000;
if (parser->mode != FREEDIVE) {
- unsigned int offset = 0x17;
- if (parser->model == A300CS || parser->model == VTX ||
- parser->model == I450T || parser->model == I750TC ||
- parser->model == PROPLUSX || parser->model == I770R ||
- parser->model == SAGE || parser->model == BEACON)
- offset = 0x1f;
- const unsigned int intervals[] = {2000, 15000, 30000, 60000};
- unsigned int idx = data[offset] & 0x03;
- interval = intervals[idx];
+ 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 ||
+ parser->model == PROPLUSX || parser->model == I770R ||
+ parser->model == SAGE || parser->model == BEACON)
+ 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) {
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;
}
- 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);
}
@@ -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;
- 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);
// 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;
- 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 {
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 ||
diff --git a/src/oceanic_common.c b/src/oceanic_common.c
index 1e00047..fc80bc1 100644
--- a/src/oceanic_common.c
+++ b/src/oceanic_common.c
@@ -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;
}
diff --git a/src/oceanic_common.h b/src/oceanic_common.h
index b468b07..43254a9 100644
--- a/src/oceanic_common.h
+++ b/src/oceanic_common.h
@@ -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), \
diff --git a/src/parser.c b/src/parser.c
index 011115d..bc143f4 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -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
diff --git a/src/pelagic_i330r.c b/src/pelagic_i330r.c
new file mode 100644
index 0000000..17aad3b
--- /dev/null
+++ b/src/pelagic_i330r.c
@@ -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 // memcpy
+#include // malloc, free
+#include
+
+#include
+
+#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;
+}
diff --git a/src/pelagic_i330r.h b/src/pelagic_i330r.h
new file mode 100644
index 0000000..23045cd
--- /dev/null
+++ b/src/pelagic_i330r.h
@@ -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
+#include
+#include
+#include
+
+#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 */