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 */