Compare commits

..

7 Commits

Author SHA1 Message Date
d5f9b352fc Bluetooth updates for DSX 2024-06-14 17:51:26 -04:00
311f8d05f4 Fix EOL to CRLF 2024-06-14 17:37:27 -04:00
deaaeee7a1 Fix undeclared variables 2024-06-14 17:31:24 -04:00
c281b04bfc Fix invalid files 2024-06-14 15:01:47 -04:00
132a3949fd Add support for the Aqualung i330R and Apeks DSX 2024-06-14 14:52:50 -04:00
27a37fc95a Revert "Add support for the Aqualung i330R and Apeks DSX"
This reverts commit d1106cb8babb993d97b816f8274e22ae4fa6a08d.
2024-06-14 14:04:43 -04:00
d1106cb8ba Add support for the Aqualung i330R and Apeks DSX 2024-06-14 13:50:39 -04:00
13 changed files with 884 additions and 32 deletions

View File

@ -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 \

View File

@ -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" />

View File

@ -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},

View File

@ -33,6 +33,21 @@ extern "C" {
*/
#define DC_IOCTL_BLE_GET_NAME DC_IOCTL_IOR('b', 0, DC_IOCTL_SIZE_VARIABLE)
/**
* Get the bluetooth authentication PIN code.
*
* The data format is a NULL terminated string.
*/
#define DC_IOCTL_BLE_GET_PINCODE DC_IOCTL_IOR('b', 1, DC_IOCTL_SIZE_VARIABLE)
/**
* Get/set the bluetooth authentication access code.
*
* The data format is a variable sized byte array.
*/
#define DC_IOCTL_BLE_GET_ACCESSCODE DC_IOCTL_IOR('b', 2, DC_IOCTL_SIZE_VARIABLE)
#define DC_IOCTL_BLE_SET_ACCESSCODE DC_IOCTL_IOW('b', 2, DC_IOCTL_SIZE_VARIABLE)
#ifdef __cplusplus
}
#endif /* __cplusplus */

View File

@ -78,6 +78,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,

View File

@ -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 \

View File

@ -275,6 +275,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", "i200Cv2", 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},
@ -755,8 +758,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 (newer model)
0x474B, // Oceanic Geo Air
};

View File

@ -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"
@ -165,6 +166,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;

View File

@ -37,6 +37,12 @@
#define GAUGE 1
#define FREEDIVE 2
#define DSX_CC 0
#define DSX_OC 1
#define DSX_SIDEMOUNT 2
#define DSX_SIDEGAUGE 3
#define DSX_GAUGE 4
#define NGASMIXES 6
#define HEADER 1
@ -47,6 +53,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;
unsigned int serial;
@ -96,6 +103,7 @@ oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, const uns
// Set the default values.
parser->model = model;
parser->logbooksize = 0;
parser->headersize = 9 * PAGESIZE / 2;
parser->footersize = 2 * PAGESIZE / 2;
if (model == DATAMASK || model == COMPUMASK ||
@ -134,6 +142,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->serial = serial;
@ -174,8 +190,18 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim
if (datetime) {
// AM/PM bit of the 12-hour clock.
unsigned int pm = p[1] & 0x80;
unsigned int have_ampm = 1;
switch (parser->model) {
case I330R:
case DSX:
datetime->year = p[7] + 2000;
datetime->month = p[6];
datetime->day = p[5];
datetime->hour = p[3];
datetime->minute = p[4];
have_ampm = 0;
break;
case OC1A:
case OC1B:
case OC1C:
@ -284,9 +310,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.
@ -362,6 +390,10 @@ oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser)
} else if (parser->model == VEO20 || parser->model == VEO30 ||
parser->model == OCS) {
mode = (data[1] & 0x60) >> 5;
} else if (parser->model == I330R) {
mode = data[2];
} else if (parser->model == DSX) {
mode = data[45];
}
// Get the gas mixes.
@ -419,6 +451,17 @@ oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser)
} else if (parser->model == WISDOM4) {
o2_offset = header + 4;
ngasmixes = 1;
} else if (parser->model == I330R) {
ngasmixes = 3;
o2_offset = parser->logbooksize + 16;
} else if (parser->model == DSX) {
if (mode < DSX_SIDEGAUGE) {
o2_offset = parser->logbooksize + 0x89 + mode * 16;
he_offset = parser->logbooksize + 0xB9 + mode * 16;
ngasmixes = 6;
} else {
ngasmixes = 0;
}
} else {
o2_offset = header + 4;
ngasmixes = 3;
@ -432,6 +475,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;
}
@ -493,9 +540,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;
@ -514,23 +570,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;
case DC_FIELD_STRING:
@ -606,15 +688,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;
@ -637,8 +723,10 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
parser->model == I750TC || parser->model == PROPLUSX ||
parser->model == I770R || parser->model == I470TC ||
parser->model == SAGE || parser->model == BEACON ||
parser->model == GEOAIR) {
parser->model == GEOAIR || parser->model == I330R) {
samplesize = PAGESIZE;
} else if (parser->model == DSX) {
samplesize = 32;
}
unsigned int have_temperature = 1, have_pressure = 1;
@ -653,7 +741,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;
}
@ -822,6 +911,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
parser->model == I770R|| parser->model == SAGE ||
parser->model == BEACON) {
temperature = data[offset + 11];
} else if (parser->model == I330R || parser->model == DSX) {
temperature = array_uint16_le(data + offset + 10);
} else {
unsigned int sign;
if (parser->model == DG03 || parser->model == PROPLUS3 ||
@ -844,7 +935,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);
}
@ -869,8 +964,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];
}
sample.pressure.tank = tank;
sample.pressure.value = pressure * PSI / BAR;
if (callback) callback (DC_SAMPLE_PRESSURE, &sample, userdata);
@ -891,11 +990,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
@ -904,8 +1009,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;
@ -950,11 +1058,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;
@ -987,6 +1109,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 ||

View File

@ -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), \

View File

@ -127,6 +127,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

647
src/pelagic_i330r.c Normal file
View File

@ -0,0 +1,647 @@
/*
* 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
*/
// test 01
#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;
}

41
src/pelagic_i330r.h Normal file
View File

@ -0,0 +1,41 @@
/*
* 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
*/
// test 01
#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 */