From 91acd9bb2d2c6be3213070bf831ccac37eeec732 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 18 Jun 2020 08:28:59 +0200 Subject: [PATCH 01/11] Add support for the Aqualung i470TC --- src/descriptor.c | 2 ++ src/oceanic_atom2.c | 1 + src/oceanic_atom2_parser.c | 25 +++++++++++++++++-------- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index 54ae5c9..de3b52a 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -248,6 +248,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Oceanic", "Veo 4.0", DC_FAMILY_OCEANIC_ATOM2, 0x4654, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, {"Sherwood", "Wisdom 4", DC_FAMILY_OCEANIC_ATOM2, 0x4655, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, {"Oceanic", "Pro Plus 4", DC_FAMILY_OCEANIC_ATOM2, 0x4656, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, + {"Aqualung", "i470TC", DC_FAMILY_OCEANIC_ATOM2, 0x4743, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, /* 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}, @@ -623,6 +624,7 @@ static int dc_filter_oceanic (dc_transport_t transport, const void *userdata) 0x4654, // Oceanic Veo 4.0 0x4655, // Sherwood Wisdom 4 0x4656, // Oceanic Pro Plus 4 + 0x4743, // Aqualung i470TC }; if (transport == DC_TRANSPORT_BLE) { diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index 9183b0a..458e9e2 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -176,6 +176,7 @@ static const oceanic_common_version_t oceanic_oc1_version[] = { {"AQUAI550 \0\0 1024"}, {"AQUA550C \0\0 1024"}, {"WISDOM04 \0\0 1024"}, + {"AQUA470C \0\0 1024"}, }; static const oceanic_common_version_t oceanic_oci_version[] = { diff --git a/src/oceanic_atom2_parser.c b/src/oceanic_atom2_parser.c index 74f73cf..94c6ba3 100644 --- a/src/oceanic_atom2_parser.c +++ b/src/oceanic_atom2_parser.c @@ -96,6 +96,7 @@ #define VEO40 0x4654 #define WISDOM4 0x4655 #define PROPLUS4 0x4656 +#define I470TC 0x4743 #define NORMAL 0 #define GAUGE 1 @@ -169,7 +170,7 @@ oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, unsigned model == I300 || model == I550 || model == I200 || model == I200C || model == I300C || model == GEO40 || - model == VEO40) { + model == VEO40 || model == I470TC) { parser->headersize -= PAGESIZE; } else if (model == VT4 || model == VT41) { parser->headersize += PAGESIZE; @@ -271,6 +272,7 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim case VISION: case XPAIR: case WISDOM4: + case I470TC: datetime->year = ((p[5] & 0xE0) >> 5) + ((p[7] & 0xE0) >> 2) + 2000; datetime->month = (p[3] & 0x0F); datetime->day = ((p[0] & 0x80) >> 3) + ((p[3] & 0xF0) >> 4); @@ -483,6 +485,10 @@ oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser) o2_offset = 0x30; ngasmixes = 4; o2_step = 2; + } else if (parser->model == I470TC) { + o2_offset = 0x28; + ngasmixes = 3; + o2_step = 2; } else if (parser->model == WISDOM4) { o2_offset = header + 4; ngasmixes = 1; @@ -720,7 +726,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == TX1 || parser->model == A300CS || parser->model == VTX || parser->model == I450T || parser->model == I750TC || parser->model == PROPLUSX || - parser->model == I770R) { + parser->model == I770R || parser->model == I470TC) { samplesize = PAGESIZE; } @@ -854,7 +860,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ } // Time. - if (parser->model == I450T) { + if (parser->model == I450T || parser->model == I470TC) { unsigned int minute = bcd2dec(data[offset + 0]); unsigned int hour = bcd2dec(data[offset + 1] & 0x0F); unsigned int second = bcd2dec(data[offset + 2]); @@ -895,7 +901,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I450T || parser->model == I300 || parser->model == I200 || parser->model == I100 || parser->model == I300C || parser->model == I200C || - parser->model == GEO40 || parser->model == VEO40) { + parser->model == GEO40 || parser->model == VEO40 || + parser->model == I470TC) { temperature = data[offset + 3]; } else if (parser->model == OCS || parser->model == TX1) { temperature = data[offset + 1]; @@ -937,7 +944,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ if (have_pressure) { if (parser->model == OC1A || parser->model == OC1B || parser->model == OC1C || parser->model == OCI || - parser->model == I450T) + parser->model == I450T || parser->model == I470TC) pressure = (data[offset + 10] + (data[offset + 11] << 8)) & 0x0FFF; else if (parser->model == VT4 || parser->model == VT41|| parser->model == ATOM3 || parser->model == ATOM31 || @@ -970,7 +977,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I450T || parser->model == I300 || parser->model == I200 || parser->model == I100 || parser->model == I300C || parser->model == I200C || - parser->model == GEO40 || parser->model == VEO40) + parser->model == GEO40 || parser->model == VEO40 || + parser->model == I470TC) depth = (data[offset + 4] + (data[offset + 5] << 8)) & 0x0FFF; else if (parser->model == ATOM1) depth = data[offset + 3] * 16; @@ -1024,7 +1032,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == OC1C || parser->model == OCI || parser->model == I100 || parser->model == I300C || parser->model == I450T || parser->model == I200C || - parser->model == GEO40 || parser->model == VEO40) { + parser->model == GEO40 || parser->model == VEO40 || + parser->model == I470TC) { decostop = (data[offset + 7] & 0xF0) >> 4; decotime = array_uint16_le(data + offset + 6) & 0x0FFF; have_deco = 1; @@ -1049,7 +1058,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ } else if (parser->model == I450T || parser->model == OC1A || parser->model == OC1B || parser->model == OC1C || parser->model == OCI || parser->model == PROPLUSX || - parser->model == I770R) { + parser->model == I770R || parser->model == I470TC) { rbt = array_uint16_le(data + offset + 8) & 0x01FF; have_rbt = 1; } else if (parser->model == VISION || parser->model == XPAIR || From 71a149d776a4abbc7f0de4e01966685e55ffad1e Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 26 Apr 2020 11:08:45 +0200 Subject: [PATCH 02/11] Add support for Liquivision dive computers --- examples/common.c | 1 + include/libdivecomputer/common.h | 2 + msvc/libdivecomputer.vcproj | 12 + src/Makefile.am | 1 + src/checksum.c | 45 ++ src/checksum.h | 3 + src/descriptor.c | 5 + src/device.c | 4 + src/liquivision_lynx.c | 677 +++++++++++++++++++++++++++++++ src/liquivision_lynx.h | 43 ++ src/liquivision_lynx_parser.c | 596 +++++++++++++++++++++++++++ src/parser.c | 4 + 12 files changed, 1393 insertions(+) create mode 100644 src/liquivision_lynx.c create mode 100644 src/liquivision_lynx.h create mode 100644 src/liquivision_lynx_parser.c diff --git a/examples/common.c b/examples/common.c index e305536..cfabc03 100644 --- a/examples/common.c +++ b/examples/common.c @@ -91,6 +91,7 @@ static const backend_table_t g_backends[] = { {"cochran", DC_FAMILY_COCHRAN_COMMANDER, 0}, {"divecomputereu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0}, {"mclean", DC_FAMILY_MCLEAN_EXTREME, 0}, + {"lynx", DC_FAMILY_LIQUIVISION_LYNX, 0}, }; static const transport_table_t g_transports[] = { diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h index a8703ab..b8216ac 100644 --- a/include/libdivecomputer/common.h +++ b/include/libdivecomputer/common.h @@ -106,6 +106,8 @@ typedef enum dc_family_t { DC_FAMILY_TECDIVING_DIVECOMPUTEREU = (15 << 16), /* McLean */ DC_FAMILY_MCLEAN_EXTREME = (16 << 16), + /* Liquivision */ + DC_FAMILY_LIQUIVISION_LYNX = (17 << 16), } dc_family_t; #ifdef __cplusplus diff --git a/msvc/libdivecomputer.vcproj b/msvc/libdivecomputer.vcproj index 1c8b772..0580e56 100644 --- a/msvc/libdivecomputer.vcproj +++ b/msvc/libdivecomputer.vcproj @@ -318,6 +318,14 @@ RelativePath="..\src\iterator.c" > + + + + @@ -684,6 +692,10 @@ RelativePath="..\include\libdivecomputer\iterator.h" > + + diff --git a/src/Makefile.am b/src/Makefile.am index f2bcd46..7b18286 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -73,6 +73,7 @@ libdivecomputer_la_SOURCES = \ cochran_commander.h cochran_commander.c cochran_commander_parser.c \ tecdiving_divecomputereu.h tecdiving_divecomputereu.c tecdiving_divecomputereu_parser.c \ mclean_extreme.h mclean_extreme.c mclean_extreme_parser.c \ + liquivision_lynx.h liquivision_lynx.c liquivision_lynx_parser.c \ socket.h socket.c \ irda.c \ usbhid.c \ diff --git a/src/checksum.c b/src/checksum.c index 816c58d..9d06630 100644 --- a/src/checksum.c +++ b/src/checksum.c @@ -157,3 +157,48 @@ checksum_crc32 (const unsigned char data[], unsigned int size) return crc ^ 0xffffffff; } + +unsigned int +checksum_crc32b (const unsigned char data[], unsigned int size) +{ + static const unsigned int crc_table[] = { + 0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9, 0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005, + 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61, 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD, + 0x4C11DB70, 0x48D0C6C7, 0x4593E01E, 0x4152FDA9, 0x5F15ADAC, 0x5BD4B01B, 0x569796C2, 0x52568B75, + 0x6A1936C8, 0x6ED82B7F, 0x639B0DA6, 0x675A1011, 0x791D4014, 0x7DDC5DA3, 0x709F7B7A, 0x745E66CD, + 0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039, 0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5, + 0xBE2B5B58, 0xBAEA46EF, 0xB7A96036, 0xB3687D81, 0xAD2F2D84, 0xA9EE3033, 0xA4AD16EA, 0xA06C0B5D, + 0xD4326D90, 0xD0F37027, 0xDDB056FE, 0xD9714B49, 0xC7361B4C, 0xC3F706FB, 0xCEB42022, 0xCA753D95, + 0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1, 0xE13EF6F4, 0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D, + 0x34867077, 0x30476DC0, 0x3D044B19, 0x39C556AE, 0x278206AB, 0x23431B1C, 0x2E003DC5, 0x2AC12072, + 0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16, 0x018AEB13, 0x054BF6A4, 0x0808D07D, 0x0CC9CDCA, + 0x7897AB07, 0x7C56B6B0, 0x71159069, 0x75D48DDE, 0x6B93DDDB, 0x6F52C06C, 0x6211E6B5, 0x66D0FB02, + 0x5E9F46BF, 0x5A5E5B08, 0x571D7DD1, 0x53DC6066, 0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA, + 0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E, 0xBFA1B04B, 0xBB60ADFC, 0xB6238B25, 0xB2E29692, + 0x8AAD2B2F, 0x8E6C3698, 0x832F1041, 0x87EE0DF6, 0x99A95DF3, 0x9D684044, 0x902B669D, 0x94EA7B2A, + 0xE0B41DE7, 0xE4750050, 0xE9362689, 0xEDF73B3E, 0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2, + 0xC6BCF05F, 0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686, 0xD5B88683, 0xD1799B34, 0xDC3ABDED, 0xD8FBA05A, + 0x690CE0EE, 0x6DCDFD59, 0x608EDB80, 0x644FC637, 0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB, + 0x4F040D56, 0x4BC510E1, 0x46863638, 0x42472B8F, 0x5C007B8A, 0x58C1663D, 0x558240E4, 0x51435D53, + 0x251D3B9E, 0x21DC2629, 0x2C9F00F0, 0x285E1D47, 0x36194D42, 0x32D850F5, 0x3F9B762C, 0x3B5A6B9B, + 0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF, 0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623, + 0xF12F560E, 0xF5EE4BB9, 0xF8AD6D60, 0xFC6C70D7, 0xE22B20D2, 0xE6EA3D65, 0xEBA91BBC, 0xEF68060B, + 0xD727BBB6, 0xD3E6A601, 0xDEA580D8, 0xDA649D6F, 0xC423CD6A, 0xC0E2D0DD, 0xCDA1F604, 0xC960EBB3, + 0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7, 0xAE3AFBA2, 0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B, + 0x9B3660C6, 0x9FF77D71, 0x92B45BA8, 0x9675461F, 0x8832161A, 0x8CF30BAD, 0x81B02D74, 0x857130C3, + 0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640, 0x4E8EE645, 0x4A4FFBF2, 0x470CDD2B, 0x43CDC09C, + 0x7B827D21, 0x7F436096, 0x7200464F, 0x76C15BF8, 0x68860BFD, 0x6C47164A, 0x61043093, 0x65C52D24, + 0x119B4BE9, 0x155A565E, 0x18197087, 0x1CD86D30, 0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC, + 0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088, 0x2497D08D, 0x2056CD3A, 0x2D15EBE3, 0x29D4F654, + 0xC5A92679, 0xC1683BCE, 0xCC2B1D17, 0xC8EA00A0, 0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB, 0xDBEE767C, + 0xE3A1CBC1, 0xE760D676, 0xEA23F0AF, 0xEEE2ED18, 0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4, + 0x89B8FD09, 0x8D79E0BE, 0x803AC667, 0x84FBDBD0, 0x9ABC8BD5, 0x9E7D9662, 0x933EB0BB, 0x97FFAD0C, + 0xAFB010B1, 0xAB710D06, 0xA6322BDF, 0xA2F33668, 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4, + }; + + unsigned int crc = 0xffffffff; + for (unsigned int i = 0; i < size; ++i) + crc = crc_table[((crc >> 24) ^ data[i]) & 0xFF] ^ (crc << 8); + + return crc ^ 0xffffffff; +} diff --git a/src/checksum.h b/src/checksum.h index 9a53aa6..c8a9001 100644 --- a/src/checksum.h +++ b/src/checksum.h @@ -44,6 +44,9 @@ checksum_crc16_ccitt (const unsigned char data[], unsigned int size, unsigned sh unsigned int checksum_crc32 (const unsigned char data[], unsigned int size); +unsigned int +checksum_crc32b (const unsigned char data[], unsigned int size); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/descriptor.c b/src/descriptor.c index de3b52a..a8d221c 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -380,6 +380,11 @@ static const dc_descriptor_t g_descriptors[] = { {"Tecdiving", "DiveComputer.eu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_tecdiving}, /* McLean Extreme */ { "McLean", "Extreme", DC_FAMILY_MCLEAN_EXTREME, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_mclean}, + /* Liquivision */ + {"Liquivision", "Xen", DC_FAMILY_LIQUIVISION_LYNX, 0, DC_TRANSPORT_SERIAL, NULL}, + {"Liquivision", "Xeo", DC_FAMILY_LIQUIVISION_LYNX, 1, DC_TRANSPORT_SERIAL, NULL}, + {"Liquivision", "Lynx", DC_FAMILY_LIQUIVISION_LYNX, 2, DC_TRANSPORT_SERIAL, NULL}, + {"Liquivision", "Kaon", DC_FAMILY_LIQUIVISION_LYNX, 3, DC_TRANSPORT_SERIAL, NULL}, }; static int diff --git a/src/device.c b/src/device.c index 92cfa36..271c2f4 100644 --- a/src/device.c +++ b/src/device.c @@ -58,6 +58,7 @@ #include "cochran_commander.h" #include "tecdiving_divecomputereu.h" #include "mclean_extreme.h" +#include "liquivision_lynx.h" #include "device-private.h" #include "context-private.h" @@ -215,6 +216,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr case DC_FAMILY_MCLEAN_EXTREME: rc = mclean_extreme_device_open (&device, context, iostream); break; + case DC_FAMILY_LIQUIVISION_LYNX: + rc = liquivision_lynx_device_open (&device, context, iostream); + break; default: return DC_STATUS_INVALIDARGS; } diff --git a/src/liquivision_lynx.c b/src/liquivision_lynx.c new file mode 100644 index 0000000..b91b326 --- /dev/null +++ b/src/liquivision_lynx.c @@ -0,0 +1,677 @@ +/* + * libdivecomputer + * + * Copyright (C) 2020 Jef Driesen + * + * 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 +#include +#include + +#include + +#include "liquivision_lynx.h" +#include "context-private.h" +#include "device-private.h" +#include "ringbuffer.h" +#include "rbstream.h" +#include "checksum.h" +#include "array.h" + +#define ISINSTANCE(device) dc_device_isinstance((device), &liquivision_lynx_device_vtable) + +#define XEN 0 +#define XEO 1 +#define LYNX 2 +#define KAON 3 + +#define XEN_V1 0x83321485 // Not supported +#define XEN_V2 0x83321502 +#define XEN_V3 0x83328401 + +#define XEO_V1_A 0x17485623 +#define XEO_V1_B 0x27485623 +#define XEO_V2_A 0x17488401 +#define XEO_V2_B 0x27488401 +#define XEO_V3_A 0x17488402 +#define XEO_V3_B 0x27488402 + +#define LYNX_V1 0x67488403 +#define LYNX_V2 0x67488404 +#define LYNX_V3 0x67488405 + +#define KAON_V1 0x37488402 +#define KAON_V2 0x47488402 + +#define MAXRETRIES 2 +#define MAXPACKET 12 +#define SEGMENTSIZE 0x400 +#define PAGESIZE 0x1000 +#define MEMSIZE 0x200000 + +#define RB_LOGBOOK_BEGIN (1 * PAGESIZE) +#define RB_LOGBOOK_END (25 * PAGESIZE) +#define RB_LOGBOOK_SIZE (RB_LOGBOOK_END - RB_LOGBOOK_BEGIN) +#define RB_LOGBOOK_DISTANCE(a,b) ringbuffer_distance (a, b, 1, RB_LOGBOOK_BEGIN, RB_LOGBOOK_END) + +#define RB_PROFILE_BEGIN (25 * PAGESIZE) +#define RB_PROFILE_END (500 * PAGESIZE) +#define RB_PROFILE_SIZE (RB_PROFILE_END - RB_PROFILE_BEGIN) +#define RB_PROFILE_DISTANCE(a,b) ringbuffer_distance (a, b, 1, RB_PROFILE_BEGIN, RB_PROFILE_END) + +#define SZ_HEADER_XEN 80 +#define SZ_HEADER_OTHER 96 +#define SZ_HEADER_MAX SZ_HEADER_OTHER + +typedef struct liquivision_lynx_device_t { + dc_device_t base; + dc_iostream_t *iostream; + unsigned char fingerprint[4]; +} liquivision_lynx_device_t; + +static dc_status_t liquivision_lynx_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); +static dc_status_t liquivision_lynx_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size); +static dc_status_t liquivision_lynx_device_dump (dc_device_t *abstract, dc_buffer_t *buffer); +static dc_status_t liquivision_lynx_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +static dc_status_t liquivision_lynx_device_close (dc_device_t *abstract); + +static const dc_device_vtable_t liquivision_lynx_device_vtable = { + sizeof(liquivision_lynx_device_t), + DC_FAMILY_LIQUIVISION_LYNX, + liquivision_lynx_device_set_fingerprint, /* set_fingerprint */ + liquivision_lynx_device_read, /* read */ + NULL, /* write */ + liquivision_lynx_device_dump, /* dump */ + liquivision_lynx_device_foreach, /* foreach */ + NULL, /* timesync */ + liquivision_lynx_device_close /* close */ +}; + +static dc_status_t +liquivision_lynx_send (liquivision_lynx_device_t *device, 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) + return DC_STATUS_INVALIDARGS; + + // Build the packet. + unsigned char packet[2 + MAXPACKET + 2] = {0}; + packet[0] = 0x00; + packet[1] = 0xB1; + if (size) { + memcpy (packet + 2, data, size); + } + packet[2 + size + 0] = 0x0B; + packet[2 + size + 1] = 0x0E; + + // Send the packet to the device. + status = dc_iostream_write (device->iostream, packet, size + 4, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the packet."); + return status; + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +liquivision_lynx_recv (liquivision_lynx_device_t *device, unsigned char data[], unsigned int size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + + if (size > SEGMENTSIZE) + return DC_STATUS_INVALIDARGS; + + // Receive the packet from the device. + unsigned char packet[1 + SEGMENTSIZE + 2] = {0}; + status = dc_iostream_read (device->iostream, packet, 1 + size + 2, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the packet."); + return status; + } + + // Verify the start byte. + if (packet[0] != 0xC5) { + ERROR (abstract->context, "Unexpected answer start byte (%02x).", packet[0]); + return DC_STATUS_PROTOCOL; + } + + // Verify the checksum. + unsigned short crc = array_uint16_be (packet + 1 + size); + unsigned short ccrc = checksum_crc16_ccitt (packet + 1, size, 0xffff); + if (crc != ccrc) { + ERROR (abstract->context, "Unexpected answer checksum (%04x %04x).", crc, ccrc); + return DC_STATUS_PROTOCOL; + } + + if (size) { + memcpy (data, packet + 1, size); + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +liquivision_lynx_packet (liquivision_lynx_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + + if (device_is_cancelled (abstract)) + return DC_STATUS_CANCELLED; + + status = liquivision_lynx_send (device, command, csize); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the command."); + return status; + } + + if (asize) { + status = liquivision_lynx_recv (device, answer, asize); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the answer."); + return status; + } + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +liquivision_lynx_transfer (liquivision_lynx_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize) +{ + unsigned int nretries = 0; + dc_status_t rc = DC_STATUS_SUCCESS; + while ((rc = liquivision_lynx_packet (device, command, csize, answer, asize)) != DC_STATUS_SUCCESS) { + if (rc != DC_STATUS_TIMEOUT && rc != DC_STATUS_PROTOCOL) + return rc; + + // Abort if the maximum number of retries is reached. + if (nretries++ >= MAXRETRIES) + return rc; + + // Delay the next attempt. + dc_iostream_sleep (device->iostream, 100); + dc_iostream_purge (device->iostream, DC_DIRECTION_INPUT); + } + + return DC_STATUS_SUCCESS; +} + +dc_status_t +liquivision_lynx_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) +{ + dc_status_t status = DC_STATUS_SUCCESS; + liquivision_lynx_device_t *device = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + device = (liquivision_lynx_device_t *) dc_device_allocate (context, &liquivision_lynx_device_vtable); + if (device == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Set the default values. + device->iostream = iostream; + memset (device->fingerprint, 0, sizeof (device->fingerprint)); + + // Set the serial communication protocol (9600 8N1). + status = dc_iostream_configure (device->iostream, 9600, 8, DC_PARITY_NONE, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the terminal attributes."); + goto error_free; + } + + // 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; + } + + // Set the DTR line. + status = dc_iostream_set_dtr (device->iostream, 0); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the DTR line."); + goto error_free; + } + + // Set the RTS line. + status = dc_iostream_set_rts (device->iostream, 0); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the RTS line."); + goto error_free; + } + + // Make sure everything is in a sane state. + dc_iostream_sleep (device->iostream, 100); + dc_iostream_purge (device->iostream, DC_DIRECTION_ALL); + + // Wakeup the device. + for (unsigned int i = 0; i < 6000; ++i) { + const unsigned char init[] = {0xAA}; + dc_iostream_write (device->iostream, init, sizeof (init), NULL); + } + + *out = (dc_device_t *) device; + + return DC_STATUS_SUCCESS; + +error_free: + dc_device_deallocate ((dc_device_t *) device); + return status; +} + + +static dc_status_t +liquivision_lynx_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size) +{ + liquivision_lynx_device_t *device = (liquivision_lynx_device_t *) abstract; + + if (size && size != sizeof (device->fingerprint)) + return DC_STATUS_INVALIDARGS; + + if (size) + memcpy (device->fingerprint, data, sizeof (device->fingerprint)); + else + memset (device->fingerprint, 0, sizeof (device->fingerprint)); + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +liquivision_lynx_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + liquivision_lynx_device_t *device = (liquivision_lynx_device_t *) abstract; + + if ((address % SEGMENTSIZE != 0) || + (size % SEGMENTSIZE != 0)) + return DC_STATUS_INVALIDARGS; + + // Get the page and segment number. + unsigned int page = (address / PAGESIZE); + unsigned int segment = (address % PAGESIZE) / SEGMENTSIZE; + + unsigned int nbytes = 0; + while (nbytes < size) { + const unsigned char command[] = { + 0x50, 0x41, 0x47, 0x45, + '0' + ((page / 100) % 10), + '0' + ((page / 10) % 10), + '0' + ((page / 1) % 10), + '0' + ((page / 100) % 10), + '0' + ((page / 10) % 10), + '0' + ((page / 1) % 10), + '0' + segment, + '0' + segment + }; + + status = liquivision_lynx_transfer (device, command, sizeof(command), data + nbytes, SEGMENTSIZE); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read page %u segment %u.", page, segment); + return status; + } + + nbytes += SEGMENTSIZE; + segment++; + if (segment == (PAGESIZE / SEGMENTSIZE)) { + segment = 0; + page++; + } + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +liquivision_lynx_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) +{ + // Allocate the required amount of memory. + if (!dc_buffer_resize (buffer, MEMSIZE)) { + ERROR (abstract->context, "Insufficient buffer space available."); + return DC_STATUS_NOMEMORY; + } + + return device_dump_read (abstract, dc_buffer_get_data (buffer), + dc_buffer_get_size (buffer), SEGMENTSIZE); +} + +static dc_status_t +liquivision_lynx_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) +{ + dc_status_t status = DC_STATUS_SUCCESS; + liquivision_lynx_device_t *device = (liquivision_lynx_device_t *) abstract; + + // Enable progress notifications. + dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; + progress.maximum = SEGMENTSIZE + RB_LOGBOOK_SIZE + RB_PROFILE_SIZE; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Send the info command. + unsigned char rsp_info[6] = {0}; + const unsigned char cmd_info[] = {0x49, 0x4E, 0x46, 0x4F, 0x49, 0x4E, 0x46, 0x4F, 0x49, 0x4E, 0x46, 0x4F}; + status = liquivision_lynx_transfer (device, cmd_info, sizeof(cmd_info), rsp_info, sizeof(rsp_info)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the info command."); + goto error_exit; + } + + // Get the model and version. + unsigned int model = array_uint16_le(rsp_info + 0); + unsigned int version = array_uint32_le(rsp_info + 2); + + // Send the more info command. + unsigned char rsp_more[12] = {0}; + const unsigned char cmd_more[] = {0x4D, 0x4F, 0x52, 0x45, 0x49, 0x4E, 0x46, 0x4F, 0x4D, 0x4F, 0x52, 0x45}; + status = liquivision_lynx_transfer (device, cmd_more, sizeof(cmd_more), rsp_more, sizeof(rsp_more)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the more info command."); + goto error_exit; + } + + // Emit a device info event. + dc_event_devinfo_t devinfo; + devinfo.model = model; + devinfo.firmware = 0; + devinfo.serial = array_uint32_le(rsp_more + 0); + device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); + + // Read the config segment. + unsigned char config[SEGMENTSIZE] = {0}; + status = liquivision_lynx_device_read (abstract, 0, config, sizeof (config)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the memory."); + goto error_exit; + } + + // Get the header size. + unsigned int headersize = (model == XEN) ? SZ_HEADER_XEN : SZ_HEADER_OTHER; + + // Get the number of headers per page. + unsigned int npages = PAGESIZE / headersize; + + // Get the logbook pointers. + unsigned int begin = array_uint16_le (config + 0x46); + unsigned int end = array_uint16_le (config + 0x48); + unsigned int rb_logbook_begin = RB_LOGBOOK_BEGIN + (begin / npages) * PAGESIZE + (begin % npages) * headersize; + unsigned int rb_logbook_end = RB_LOGBOOK_BEGIN + (end / npages) * PAGESIZE + (end % npages) * headersize; + if (rb_logbook_begin < RB_LOGBOOK_BEGIN || rb_logbook_begin > RB_LOGBOOK_END || + rb_logbook_end < RB_LOGBOOK_BEGIN || rb_logbook_end > RB_LOGBOOK_END) { + ERROR (abstract->context, "Invalid logbook pointers (%04x, %04x).", + rb_logbook_begin, rb_logbook_end); + status = DC_STATUS_DATAFORMAT; + goto error_exit; + } + + // Calculate the logbook size. +#if 0 + unsigned int rb_logbook_size = RB_LOGBOOK_DISTANCE (rb_logbook_begin, rb_logbook_end); +#else + // The logbook begin pointer is explicitly ignored, because it only takes + // into account dives for which the profile is still available. + unsigned int rb_logbook_size = RB_LOGBOOK_SIZE; +#endif + + // Get the profile pointers. + unsigned int rb_profile_begin = array_uint32_le (config + 0x4A); + unsigned int rb_profile_end = array_uint32_le (config + 0x4E); + if (rb_profile_begin < RB_PROFILE_BEGIN || rb_profile_begin > RB_PROFILE_END || + rb_profile_end < RB_PROFILE_BEGIN || rb_profile_end > RB_PROFILE_END) { + ERROR (abstract->context, "Invalid profile pointers (%04x, %04x).", + rb_profile_begin, rb_profile_end); + status = DC_STATUS_DATAFORMAT; + goto error_exit; + } + + // Update and emit a progress event. + progress.current += SEGMENTSIZE; + progress.maximum -= RB_LOGBOOK_SIZE - rb_logbook_size; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Allocate memory for the logbook entries. + unsigned char *logbook = (unsigned char *) malloc (rb_logbook_size); + if (logbook == NULL) { + status = DC_STATUS_NOMEMORY; + goto error_exit; + } + + // Create the ringbuffer stream. + dc_rbstream_t *rblogbook = NULL; + status = dc_rbstream_new (&rblogbook, abstract, SEGMENTSIZE, SEGMENTSIZE, RB_LOGBOOK_BEGIN, RB_LOGBOOK_END, rb_logbook_end); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to create the ringbuffer stream."); + goto error_free_logbook; + } + + // The logbook ringbuffer is read backwards to retrieve the most recent + // entries first. If an already downloaded entry is identified (by means + // of its fingerprint), the transfer is aborted immediately to reduce + // the transfer time. + unsigned int nbytes = 0; + unsigned int offset = rb_logbook_size; + unsigned int address = rb_logbook_end; + while (nbytes < rb_logbook_size) { + // Handle the ringbuffer wrap point. + if (address == RB_LOGBOOK_BEGIN) + address = RB_LOGBOOK_END; + + // Skip the padding bytes. + if ((address % PAGESIZE) == 0) { + unsigned int padding = PAGESIZE % headersize; + unsigned char dummy[SZ_HEADER_MAX] = {0}; + status = dc_rbstream_read (rblogbook, &progress, dummy, padding); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the memory."); + goto error_free_rblogbook; + } + + address -= padding; + nbytes += padding; + } + + // Move to the start of the current entry. + address -= headersize; + offset -= headersize; + + // Read the logbook entry. + status = dc_rbstream_read (rblogbook, &progress, logbook + offset, headersize); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the memory."); + goto error_free_rblogbook; + } + + nbytes += headersize; + + if (array_isequal (logbook + offset, headersize, 0xFF)) { + offset += headersize; + break; + } + + // Verify the checksum. + unsigned int unused = 2; + if (version == XEO_V1_A || version == XEO_V1_B) { + unused = 6; + } + unsigned char header[SZ_HEADER_MAX] = {0}; + memcpy (header + 0, rsp_info + 2, 4); + memcpy (header + 4, logbook + offset + 4, headersize - 4); + unsigned int crc = array_uint32_le (logbook + offset + 0); + unsigned int ccrc = checksum_crc32b (header, headersize - unused); + if (crc != ccrc) { + WARNING (abstract->context, "Invalid dive checksum (%08x %08x)", crc, ccrc); + status = DC_STATUS_DATAFORMAT; + goto error_free_rblogbook; + } + + // Compare the fingerprint to identify previously downloaded entries. + if (memcmp (logbook + offset, device->fingerprint, sizeof(device->fingerprint)) == 0) { + offset += headersize; + break; + } + } + + // Update and emit a progress event. + progress.maximum -= rb_logbook_size - nbytes; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Go through the logbook entries a first time, to calculate the total + // amount of bytes in the profile ringbuffer. + unsigned int rb_profile_size = 0; + + // Traverse the logbook ringbuffer backwards to retrieve the most recent + // dives first. The logbook ringbuffer is linearized at this point, so + // we do not have to take into account any memory wrapping near the end + // of the memory buffer. + unsigned int remaining = RB_PROFILE_SIZE; + unsigned int previous = rb_profile_end; + unsigned int entry = rb_logbook_size; + while (entry != offset) { + // Move to the start of the current entry. + entry -= headersize; + + // Get the profile pointer. + unsigned int current = array_uint32_le (logbook + entry + 16); + if (current < RB_PROFILE_BEGIN || current >= RB_PROFILE_END) { + ERROR (abstract->context, "Invalid profile ringbuffer pointer (%08x).", current); + status = DC_STATUS_DATAFORMAT; + goto error_free_rblogbook; + } + + // Calculate the length. + unsigned int length = RB_PROFILE_DISTANCE (current, previous); + + // Make sure the profile size is valid. + if (length > remaining) { + remaining = 0; + length = 0; + } + + // Update the total profile size. + rb_profile_size += length; + + // Move to the start of the current dive. + remaining -= length; + previous = current; + } + + // At this point, we know the exact amount of data + // that needs to be transferred for the profiles. + progress.maximum -= RB_PROFILE_SIZE - rb_profile_size; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Allocate memory for the profile data. + unsigned char *profile = (unsigned char *) malloc (headersize + rb_profile_size); + if (profile == NULL) { + status = DC_STATUS_NOMEMORY; + goto error_free_rblogbook; + } + + // Create the ringbuffer stream. + dc_rbstream_t *rbprofile = NULL; + status = dc_rbstream_new (&rbprofile, abstract, SEGMENTSIZE, SEGMENTSIZE, RB_PROFILE_BEGIN, RB_PROFILE_END, rb_profile_end); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to create the ringbuffer stream."); + goto error_free_profile; + } + + // Traverse the logbook ringbuffer backwards to retrieve the most recent + // dives first. The logbook ringbuffer is linearized at this point, so + // we do not have to take into account any memory wrapping near the end + // of the memory buffer. + remaining = rb_profile_size; + previous = rb_profile_end; + entry = rb_logbook_size; + while (entry != offset) { + // Move to the start of the current entry. + entry -= headersize; + + // Get the profile pointer. + unsigned int current = array_uint32_le (logbook + entry + 16); + if (current < RB_PROFILE_BEGIN || current >= RB_PROFILE_END) { + ERROR (abstract->context, "Invalid profile ringbuffer pointer (%08x).", current); + status = DC_STATUS_DATAFORMAT; + goto error_free_rbprofile; + } + + // Calculate the length. + unsigned int length = RB_PROFILE_DISTANCE (current, previous); + + // Make sure the profile size is valid. + if (length > remaining) { + remaining = 0; + length = 0; + } + + // Move to the start of the current dive. + remaining -= length; + previous = current; + + // Read the dive. + status = dc_rbstream_read (rbprofile, &progress, profile + remaining + headersize, length); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the dive."); + goto error_free_rbprofile; + } + + // Prepend the logbook entry to the profile data. The memory buffer is + // large enough to store this entry. The checksum is replaced with the + // flash version number. + memcpy (profile + remaining + 0, rsp_info + 2, 4); + memcpy (profile + remaining + 4, logbook + entry + 4, headersize - 4); + + if (callback && !callback (profile + remaining, headersize + length, logbook + entry, sizeof(device->fingerprint), userdata)) { + break; + } + } + +error_free_rbprofile: + dc_rbstream_free (rbprofile); +error_free_profile: + free (profile); +error_free_rblogbook: + dc_rbstream_free (rblogbook); +error_free_logbook: + free (logbook); +error_exit: + return status; +} + +static dc_status_t +liquivision_lynx_device_close (dc_device_t *abstract) +{ + dc_status_t status = DC_STATUS_SUCCESS; + liquivision_lynx_device_t *device = (liquivision_lynx_device_t*) abstract; + dc_status_t rc = DC_STATUS_SUCCESS; + + // Send the finish command. + const unsigned char cmd_finish[] = {0x46, 0x49, 0x4E, 0x49, 0x53, 0x48, 0x46, 0x49, 0x4E, 0x49, 0x53, 0x48}; + status = liquivision_lynx_transfer (device, cmd_finish, sizeof(cmd_finish), NULL, 0); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the finish command."); + dc_status_set_error(&status, rc); + } + + return status; +} diff --git a/src/liquivision_lynx.h b/src/liquivision_lynx.h new file mode 100644 index 0000000..52e7294 --- /dev/null +++ b/src/liquivision_lynx.h @@ -0,0 +1,43 @@ +/* + * libdivecomputer + * + * Copyright (C) 2020 Jef Driesen + * + * 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 LIQUIVISION_LYNX_H +#define LIQUIVISION_LYNX_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +dc_status_t +liquivision_lynx_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); + +dc_status_t +liquivision_lynx_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int model); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* LIQUIVISION_LYNX_H */ diff --git a/src/liquivision_lynx_parser.c b/src/liquivision_lynx_parser.c new file mode 100644 index 0000000..8489a50 --- /dev/null +++ b/src/liquivision_lynx_parser.c @@ -0,0 +1,596 @@ +/* + * libdivecomputer + * + * Copyright (C) 2020 Jef Driesen + * + * 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 +#include + +#include + +#include "liquivision_lynx.h" +#include "context-private.h" +#include "parser-private.h" +#include "array.h" + +#define ISINSTANCE(parser) dc_parser_isinstance((parser), &liquivision_lynx_parser_vtable) + +#define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array)) + +#define XEN 0 +#define XEO 1 +#define LYNX 2 +#define KAON 3 + +#define XEN_V1 0x83321485 // Not supported +#define XEN_V2 0x83321502 +#define XEN_V3 0x83328401 + +#define XEO_V1_A 0x17485623 +#define XEO_V1_B 0x27485623 +#define XEO_V2_A 0x17488401 +#define XEO_V2_B 0x27488401 +#define XEO_V3_A 0x17488402 +#define XEO_V3_B 0x27488402 + +#define LYNX_V1 0x67488403 +#define LYNX_V2 0x67488404 +#define LYNX_V3 0x67488405 + +#define KAON_V1 0x37488402 +#define KAON_V2 0x47488402 + +#define SZ_HEADER_XEN 80 +#define SZ_HEADER_OTHER 96 + +#define FRESH 0 +#define BRACKISH 1 +#define SALT 2 + +#define DECO 0 +#define GAUGE 1 +#define TEC 2 +#define REC 3 + +#define NORMAL 0 +#define BOOKMARK 1 +#define ALARM_DEPTH 2 +#define ALARM_TIME 3 +#define ALARM_VELOCITY 4 +#define DECOSTOP 5 +#define DECOSTOP_BREACHED 6 +#define GASMIX 7 +#define SETPOINT 8 +#define BAILOUT_ON 9 +#define BAILOUT_OFF 10 +#define EMERGENCY_ON 11 +#define EMERGENCY_OFF 12 +#define LOST_GAS 13 +#define SAFETY_STOP 14 +#define TANK_PRESSURE 15 +#define TANK_LIST 16 + +#define NGASMIXES 11 +#define NTANKS 11 + +#define INVALID 0xFFFFFFFF + +typedef struct liquivision_lynx_parser_t liquivision_lynx_parser_t; + +typedef struct liquivision_lynx_gasmix_t { + unsigned int oxygen; + unsigned int helium; +} liquivision_lynx_gasmix_t; + +typedef struct liquivision_lynx_tank_t { + unsigned int id; + unsigned int beginpressure; + unsigned int endpressure; +} liquivision_lynx_tank_t; + +struct liquivision_lynx_parser_t { + dc_parser_t base; + unsigned int model; + unsigned int headersize; + // Cached fields. + unsigned int cached; + unsigned int ngasmixes; + unsigned int ntanks; + liquivision_lynx_gasmix_t gasmix[NGASMIXES]; + liquivision_lynx_tank_t tank[NTANKS]; +}; + +static dc_status_t liquivision_lynx_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t liquivision_lynx_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); +static dc_status_t liquivision_lynx_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); +static dc_status_t liquivision_lynx_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); + +static const dc_parser_vtable_t liquivision_lynx_parser_vtable = { + sizeof(liquivision_lynx_parser_t), + DC_FAMILY_LIQUIVISION_LYNX, + liquivision_lynx_parser_set_data, /* set_data */ + liquivision_lynx_parser_get_datetime, /* datetime */ + liquivision_lynx_parser_get_field, /* fields */ + liquivision_lynx_parser_samples_foreach, /* samples_foreach */ + NULL /* destroy */ +}; + + +dc_status_t +liquivision_lynx_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model) +{ + liquivision_lynx_parser_t *parser = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + parser = (liquivision_lynx_parser_t *) dc_parser_allocate (context, &liquivision_lynx_parser_vtable); + if (parser == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Set the default values. + parser->model = model; + parser->headersize = (model == XEN) ? SZ_HEADER_XEN : SZ_HEADER_OTHER; + parser->cached = 0; + parser->ngasmixes = 0; + parser->ntanks = 0; + for (unsigned int i = 0; i < NGASMIXES; ++i) { + parser->gasmix[i].oxygen = 0; + parser->gasmix[i].helium = 0; + } + for (unsigned int i = 0; i < NTANKS; ++i) { + parser->tank[i].id = 0; + parser->tank[i].beginpressure = 0; + parser->tank[i].endpressure = 0; + } + + *out = (dc_parser_t *) parser; + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +liquivision_lynx_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) +{ + liquivision_lynx_parser_t *parser = (liquivision_lynx_parser_t *) abstract; + + // Reset the cache. + parser->cached = 0; + parser->ngasmixes = 0; + parser->ntanks = 0; + for (unsigned int i = 0; i < NGASMIXES; ++i) { + parser->gasmix[i].oxygen = 0; + parser->gasmix[i].helium = 0; + } + for (unsigned int i = 0; i < NTANKS; ++i) { + parser->tank[i].id = 0; + parser->tank[i].beginpressure = 0; + parser->tank[i].endpressure = 0; + } + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +liquivision_lynx_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) +{ + liquivision_lynx_parser_t *parser = (liquivision_lynx_parser_t *) abstract; + + if (abstract->size < parser->headersize) + return DC_STATUS_DATAFORMAT; + + const unsigned char *p = abstract->data + 40; + + if (datetime) { + datetime->year = array_uint16_le (p + 18); + datetime->month = array_uint16_le (p + 16) + 1; + datetime->day = array_uint16_le (p + 12) + 1; + datetime->hour = array_uint16_le (p + 8); + datetime->minute = array_uint16_le (p + 6); + datetime->second = array_uint16_le (p + 4); + datetime->timezone = DC_TIMEZONE_NONE; + } + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +liquivision_lynx_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) +{ + liquivision_lynx_parser_t *parser = (liquivision_lynx_parser_t *) abstract; + + if (abstract->size < parser->headersize) + return DC_STATUS_DATAFORMAT; + + if (!parser->cached) { + dc_status_t rc = liquivision_lynx_parser_samples_foreach (abstract, NULL, NULL); + if (rc != DC_STATUS_SUCCESS) + return rc; + } + + dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + dc_tank_t *tank = (dc_tank_t *) value; + dc_salinity_t *water = (dc_salinity_t *) value; + + if (value) { + switch (type) { + case DC_FIELD_DIVETIME: + *((unsigned int *) value) = array_uint32_le (abstract->data + 4); + break; + case DC_FIELD_MAXDEPTH: + *((double *) value) = array_uint16_le (abstract->data + 28) / 100.0; + break; + case DC_FIELD_AVGDEPTH: + *((double *) value) = array_uint16_le (abstract->data + 30) / 100.0; + break; + case DC_FIELD_TEMPERATURE_MINIMUM: + *((double *) value) = (signed short) array_uint16_le (abstract->data + 34) / 10.0; + break; + case DC_FIELD_TEMPERATURE_MAXIMUM: + *((double *) value) = (signed short) array_uint16_le (abstract->data + 36) / 10.0; + break; + case DC_FIELD_SALINITY: + switch (abstract->data[38]) { + case FRESH: + water->type = DC_WATER_FRESH; + water->density = 1000.0; + break; + case BRACKISH: + water->type = DC_WATER_SALT; + water->density = 1015.0; + break; + case SALT: + water->type = DC_WATER_SALT; + water->density = 1025.0; + break; + default: + return DC_STATUS_DATAFORMAT; + } + break; + case DC_FIELD_ATMOSPHERIC: + *((double *) value) = array_uint16_le (abstract->data + 26) / 1000.0; + break; + case DC_FIELD_DIVEMODE: + if (parser->model == XEN) { + *((unsigned int *) value) = DC_DIVEMODE_GAUGE; + } else { + switch (abstract->data[92] & 0x0F) { + case DECO: + case TEC: + case REC: + *((unsigned int *) value) = DC_DIVEMODE_OC; + break; + case GAUGE: + *((unsigned int *) value) = DC_DIVEMODE_GAUGE; + break; + default: + return DC_STATUS_DATAFORMAT; + } + } + break; + case DC_FIELD_GASMIX_COUNT: + *((unsigned int *) value) = parser->ngasmixes; + break; + case DC_FIELD_GASMIX: + gasmix->helium = parser->gasmix[flags].helium / 100.0; + gasmix->oxygen = parser->gasmix[flags].oxygen / 100.0; + gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; + break; + case DC_FIELD_TANK_COUNT: + *((unsigned int *) value) = parser->ntanks; + break; + case DC_FIELD_TANK: + tank->type = DC_TANKVOLUME_NONE; + tank->volume = 0.0; + tank->workpressure = 0.0; + tank->beginpressure = parser->tank[flags].beginpressure / 100.0; + tank->endpressure = parser->tank[flags].endpressure / 100.0; + tank->gasmix = DC_GASMIX_UNKNOWN; + break; + default: + return DC_STATUS_UNSUPPORTED; + } + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +liquivision_lynx_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +{ + liquivision_lynx_parser_t *parser = (liquivision_lynx_parser_t *) abstract; + const unsigned char *data = abstract->data; + unsigned int size = abstract->size; + + if (size < parser->headersize) + return DC_STATUS_DATAFORMAT; + + // Get the version. + unsigned int version = array_uint32_le(data); + + // Get the sample interval. + unsigned int interval_idx = data[39]; + const unsigned int intervals[] = {1, 2, 5, 10, 30, 60}; + if (interval_idx > C_ARRAY_SIZE(intervals)) { + ERROR (abstract->context, "Invalid sample interval index %u", interval_idx); + return DC_STATUS_DATAFORMAT; + } + unsigned int interval = intervals[interval_idx]; + + // Get the number of samples and events. + unsigned int nsamples = array_uint32_le (data + 8); + unsigned int nevents = array_uint32_le (data + 12); + + unsigned int ngasmixes = 0; + unsigned int ntanks = 0; + liquivision_lynx_gasmix_t gasmix[NGASMIXES] = {0}; + liquivision_lynx_tank_t tank[NTANKS] = {0}; + unsigned int o2_previous = INVALID, he_previous = INVALID; + unsigned int gasmix_idx = INVALID; + unsigned int have_gasmix = 0; + unsigned int tank_id_previous = INVALID; + unsigned int tank_idx = INVALID; + unsigned int pressure[NTANKS] = {0}; + unsigned int have_pressure = 0; + unsigned int setpoint = 0, have_setpoint = 0; + unsigned int deco = 0, have_deco = 0; + + unsigned int time = 0; + unsigned int samples = 0; + unsigned int events = 0; + unsigned int offset = parser->headersize; + while (offset + 2 <= size) { + dc_sample_value_t sample = {0}; + + unsigned int value = array_uint16_le (data + offset); + offset += 2; + + if (value & 0x8000) { + if (events >= nevents) { + break; + } + + if (offset + 4 > size) { + ERROR (abstract->context, "Buffer overflow at offset %u", offset); + return DC_STATUS_DATAFORMAT; + } + + unsigned int type = value & 0x7FFF; + unsigned int timestamp = array_uint32_le (data + offset + 2); + offset += 4; + + // Get the sample length. + unsigned int length = 0; + switch (type) { + case DECOSTOP: + case GASMIX: + length = 2; + break; + case SETPOINT: + length = 1; + break; + case TANK_LIST: + length = NTANKS * 2; + break; + case TANK_PRESSURE: + if (version == LYNX_V1) { + length = 4; + } else { + length = 6; + } + break; + default: + break; + } + + if (offset + length > size) { + ERROR (abstract->context, "Buffer overflow at offset %u", offset); + return DC_STATUS_DATAFORMAT; + } + + unsigned int o2 = 0, he = 0; + unsigned int tank_id = 0, tank_pressure = 0; + + switch (type) { + case NORMAL: + case BOOKMARK: + case ALARM_DEPTH: + case ALARM_TIME: + case ALARM_VELOCITY: + case DECOSTOP_BREACHED: + case BAILOUT_ON: + case BAILOUT_OFF: + case EMERGENCY_ON: + case EMERGENCY_OFF: + case LOST_GAS: + case SAFETY_STOP: + break; + case DECOSTOP: + deco = array_uint16_le (data + offset); + have_deco = 1; + break; + case GASMIX: + o2 = data[offset + 0]; + he = data[offset + 1]; + if (o2 != o2_previous || he != he_previous) { + // Find the gasmix in the list. + unsigned int i = 0; + while (i < ngasmixes) { + if (o2 == gasmix[i].oxygen && he == gasmix[i].helium) + break; + i++; + } + + // Add it to list if not found. + if (i >= ngasmixes) { + if (i >= NGASMIXES) { + ERROR (abstract->context, "Maximum number of gas mixes reached."); + return DC_STATUS_DATAFORMAT; + } + gasmix[i].oxygen = o2; + gasmix[i].helium = he; + ngasmixes = i + 1; + } + + o2_previous = o2; + he_previous = he; + gasmix_idx = i; + have_gasmix = 1; + } + break; + case SETPOINT: + setpoint = data[offset]; + have_setpoint = 1; + break; + case TANK_PRESSURE: + tank_id = array_uint16_le (data + offset + 0); + tank_pressure = array_uint16_le (data + offset + 2); + if (tank_id != tank_id_previous) { + // Find the tank in the list. + unsigned int i = 0; + while (i < ntanks) { + if (tank_id == tank[i].id) + break; + i++; + } + + // Add a new tank if necessary. + if (i >= ntanks) { + if (i >= NTANKS) { + ERROR (abstract->context, "Maximum number of tanks reached."); + return DC_STATUS_DATAFORMAT; + } + tank[i].id = tank_id; + tank[i].beginpressure = tank_pressure; + tank[i].endpressure = tank_pressure; + ntanks = i + 1; + } + + tank_id_previous = tank_id; + tank_idx = i; + } + tank[tank_idx].endpressure = tank_pressure; + pressure[tank_idx] = tank_pressure; + have_pressure |= 1 << tank_idx; + break; + case TANK_LIST: + break; + default: + WARNING (abstract->context, "Unknown event %u", type); + break; + } + + offset += length; + events++; + } else { + if (samples >= nsamples) { + break; + } + + // Get the sample length. + unsigned int length = 2; + if (version == XEO_V1_A || version == XEO_V2_A || + version == XEO_V3_A || version == KAON_V1 || + version == KAON_V2) { + length += 14; + } + + if (offset + length > size) { + ERROR (abstract->context, "Buffer overflow at offset %u", offset); + return DC_STATUS_DATAFORMAT; + } + + // Time (seconds). + time += interval; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + + // Depth (1/100 m). + sample.depth = value / 100.0; + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + + // Temperature (1/10 °C). + int temperature = (signed short) array_uint16_le (data + offset); + sample.temperature = temperature / 10.0; + if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); + + // Gas mix + if (have_gasmix) { + sample.gasmix = gasmix_idx; + if (callback) callback (DC_SAMPLE_GASMIX, sample, userdata); + have_gasmix = 0; + } + + // Setpoint (1/10 bar). + if (have_setpoint) { + sample.setpoint = setpoint / 10.0; + if (callback) callback (DC_SAMPLE_SETPOINT, sample, userdata); + have_setpoint = 0; + } + + // Tank pressure (1/100 bar). + if (have_pressure) { + for (unsigned int i = 0; i < ntanks; ++i) { + if (have_pressure & (1 << i)) { + sample.pressure.tank = i; + sample.pressure.value = pressure[i] / 100.0; + if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata); + } + } + have_pressure = 0; + } + + // Deco/ndl + if (have_deco) { + if (deco) { + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.depth = deco / 100.0; + } else { + sample.deco.type = DC_DECO_NDL; + sample.deco.depth = 0.0; + } + sample.deco.time = 0; + if (callback) callback (DC_SAMPLE_DECO, sample, userdata); + have_deco = 0; + } + + offset += length; + samples++; + } + } + + // Cache the data for later use. + for (unsigned int i = 0; i < ntanks; ++i) { + parser->tank[i] = tank[i]; + } + for (unsigned int i = 0; i < ngasmixes; ++i) { + parser->gasmix[i] = gasmix[i]; + } + parser->ngasmixes = ngasmixes; + parser->ntanks = ntanks; + parser->cached = 1; + + return DC_STATUS_SUCCESS; +} diff --git a/src/parser.c b/src/parser.c index 977db14..2f7251d 100644 --- a/src/parser.c +++ b/src/parser.c @@ -58,6 +58,7 @@ #include "cochran_commander.h" #include "tecdiving_divecomputereu.h" #include "mclean_extreme.h" +#include "liquivision_lynx.h" #include "context-private.h" #include "parser-private.h" @@ -176,6 +177,9 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa case DC_FAMILY_MCLEAN_EXTREME: rc = mclean_extreme_parser_create (&parser, context); break; + case DC_FAMILY_LIQUIVISION_LYNX: + rc = liquivision_lynx_parser_create (&parser, context, model); + break; default: return DC_STATUS_INVALIDARGS; } From cdd618e68346e0141da2f7652151a8349a483681 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 26 Jul 2020 17:27:49 +0200 Subject: [PATCH 03/11] Fix the McLean Extreme bluetooth name --- src/descriptor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/descriptor.c b/src/descriptor.c index a8d221c..7f2de39 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -642,7 +642,7 @@ static int dc_filter_oceanic (dc_transport_t transport, const void *userdata) static int dc_filter_mclean(dc_transport_t transport, const void *userdata) { static const char * const bluetooth[] = { - "Extreme", + "McLean Extreme", }; if (transport == DC_TRANSPORT_BLUETOOTH) { From 6986840c0dfc965d599803b97a5f2b05166e1b9a Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 29 Jul 2020 14:39:53 +0200 Subject: [PATCH 04/11] Increase the receive timeout to 5 seconds When the fingerprint feature isn't used (or with a timestamp of zero), the response to the SIZE (0xC6) and DATA (0xC4) commands is received almost instantly: [0.302704] W: C60000000010270000 [0.366727] R: DCF90F00 [0.367829] W: C40000000010270000 [0.394812] R: E0F90F00 But when the fingerprint feature is used (with a non-zero timestamp), there is a noticable delay: [0.341218] W: C64CEB204D10270000 [1.927905] R: FE0B0000 [1.931610] W: C44CEB204D10270000 [5.092081] R: 020C0000 In this particular case, the total amount of dive data was close to 1M bytes, which pushed the delay over the 3 second timeout, and caused the download to fail. Increasing the timeout to 5 seconds fixed the problem. The most likely explanation is that the dive computer needs to scan its internal logbook to determine which dives and how many bytes to send. Because that involves reading relative slow flash memory, this can take up to a few seconds, especially if there are many dives present. The duration of the delay also depends on the value of the fingerprint timestamp: the less dives we try to download, the longer the delay! I suspect that's because the most recent dives are located near the end of the logbook. Hence, the less dives we request, the more dives the dive computer needs to skip. Below are some timings for downloading espectively 792410, 531856 and 270850 bytes from the same dive computer, but with a different fingerprint value: [0.216305] W: C6F8D5C84510270000 [0.373511] R: 56170C00 [0.378929] W: C4F8D5C84510270000 [0.661388] R: 5A170C00 [0.236246] W: C620D80F4810270000 [0.559608] R: 8C1D0800 [0.563755] W: C420D80F4810270000 [1.171631] R: 901D0800 [0.246193] W: C654E6434A10270000 [0.826365] R: FE210400 [0.831760] W: C454E6434A10270000 [1.974351] R: 02220400 --- src/uwatec_smart.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uwatec_smart.c b/src/uwatec_smart.c index 23ce6b9..5918646 100644 --- a/src/uwatec_smart.c +++ b/src/uwatec_smart.c @@ -477,8 +477,8 @@ uwatec_smart_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_ goto error_free; } - // Set the timeout for receiving data (3000ms). - status = dc_iostream_set_timeout (device->iostream, 3000); + // Set the timeout for receiving data (5000ms). + status = dc_iostream_set_timeout (device->iostream, 5000); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to set the timeout."); goto error_free; From edacbb2f13d12f113642b18e278454a7404abd2d Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 29 Apr 2020 18:38:10 +0200 Subject: [PATCH 05/11] Disable direct access to the filter function Replace the small helper function to retrieve the function pointer and then call the function, with another helper function to call the filter function directly. This way the function pointer doesn't need to be exposed at all. --- src/bluetooth.c | 6 +++--- src/descriptor-private.h | 6 ++---- src/descriptor.c | 12 +++++++----- src/irda.c | 4 +--- src/serial_posix.c | 6 +++--- src/serial_win32.c | 6 +++--- src/usbhid.c | 8 ++++---- 7 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/bluetooth.c b/src/bluetooth.c index 282a31a..e6fafd5 100644 --- a/src/bluetooth.c +++ b/src/bluetooth.c @@ -79,7 +79,7 @@ static dc_status_t dc_bluetooth_iterator_free (dc_iterator_t *iterator); typedef struct dc_bluetooth_iterator_t { dc_iterator_t base; - dc_filter_t filter; + dc_descriptor_t *descriptor; #ifdef _WIN32 HANDLE hLookup; #else @@ -376,7 +376,7 @@ dc_bluetooth_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descri iterator->count = ndevices; iterator->current = 0; #endif - iterator->filter = dc_descriptor_get_filter (descriptor); + iterator->descriptor = descriptor; *out = (dc_iterator_t *) iterator; @@ -456,7 +456,7 @@ dc_bluetooth_iterator_next (dc_iterator_t *abstract, void *out) INFO (abstract->context, "Discover: address=" DC_ADDRESS_FORMAT ", name=%s", address, name ? name : ""); - if (iterator->filter && !iterator->filter (DC_TRANSPORT_BLUETOOTH, name)) { + if (!dc_descriptor_filter (iterator->descriptor, DC_TRANSPORT_BLUETOOTH, name)) { continue; } diff --git a/src/descriptor-private.h b/src/descriptor-private.h index 5023f7f..06c2980 100644 --- a/src/descriptor-private.h +++ b/src/descriptor-private.h @@ -33,10 +33,8 @@ typedef struct dc_usb_desc_t { unsigned short pid; } dc_usb_desc_t; -typedef int (*dc_filter_t) (dc_transport_t transport, const void *userdata); - -dc_filter_t -dc_descriptor_get_filter (dc_descriptor_t *descriptor); +int +dc_descriptor_filter (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); #ifdef __cplusplus } diff --git a/src/descriptor.c b/src/descriptor.c index 7f2de39..183d3d5 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -40,6 +40,8 @@ typedef int (*dc_match_t)(const void *, const void *); +typedef int (*dc_filter_t) (dc_transport_t transport, const void *userdata); + static int dc_filter_uwatec (dc_transport_t transport, const void *userdata); static int dc_filter_suunto (dc_transport_t transport, const void *userdata); static int dc_filter_shearwater (dc_transport_t transport, const void *userdata); @@ -745,11 +747,11 @@ dc_descriptor_get_transports (dc_descriptor_t *descriptor) return descriptor->transports; } -dc_filter_t -dc_descriptor_get_filter (dc_descriptor_t *descriptor) +int +dc_descriptor_filter (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) { - if (descriptor == NULL) - return NULL; + if (descriptor == NULL || descriptor->filter == NULL) + return 1; - return descriptor->filter; + return descriptor->filter (transport, userdata); } diff --git a/src/irda.c b/src/irda.c index 214e39f..89b7e50 100644 --- a/src/irda.c +++ b/src/irda.c @@ -212,8 +212,6 @@ dc_irda_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descriptor_ S_CLOSE (fd); dc_socket_exit (context); - dc_filter_t filter = dc_descriptor_get_filter (descriptor); - unsigned int count = 0; #ifdef _WIN32 for (size_t i = 0; i < list->numDevice; ++i) { @@ -233,7 +231,7 @@ dc_irda_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descriptor_ INFO (context, "Discover: address=%08x, name=%s, charset=%02x, hints=%04x", address, name, charset, hints); - if (filter && !filter (DC_TRANSPORT_IRDA, name)) { + if (!dc_descriptor_filter (descriptor, DC_TRANSPORT_IRDA, name)) { continue; } diff --git a/src/serial_posix.c b/src/serial_posix.c index 897b428..cca501e 100644 --- a/src/serial_posix.c +++ b/src/serial_posix.c @@ -88,7 +88,7 @@ struct dc_serial_device_t { typedef struct dc_serial_iterator_t { dc_iterator_t base; - dc_filter_t filter; + dc_descriptor_t *descriptor; DIR *dp; } dc_serial_iterator_t; @@ -189,7 +189,7 @@ dc_serial_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descripto goto error_free; } - iterator->filter = dc_descriptor_get_filter (descriptor); + iterator->descriptor = descriptor; *out = (dc_iterator_t *) iterator; @@ -230,7 +230,7 @@ dc_serial_iterator_next (dc_iterator_t *abstract, void *out) return DC_STATUS_NOMEMORY; } - if (iterator->filter && !iterator->filter (DC_TRANSPORT_SERIAL, filename)) { + if (!dc_descriptor_filter (iterator->descriptor, DC_TRANSPORT_SERIAL, filename)) { continue; } diff --git a/src/serial_win32.c b/src/serial_win32.c index 3cb10ff..b51aafd 100644 --- a/src/serial_win32.c +++ b/src/serial_win32.c @@ -57,7 +57,7 @@ struct dc_serial_device_t { typedef struct dc_serial_iterator_t { dc_iterator_t base; - dc_filter_t filter; + dc_descriptor_t *descriptor; HKEY hKey; DWORD count; DWORD current; @@ -180,7 +180,7 @@ dc_serial_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descripto } } - iterator->filter = dc_descriptor_get_filter (descriptor); + iterator->descriptor = descriptor; iterator->hKey = hKey; iterator->count = count; iterator->current = 0; @@ -226,7 +226,7 @@ dc_serial_iterator_next (dc_iterator_t *abstract, void *out) // Null terminate the string. data[data_len] = 0; - if (iterator->filter && !iterator->filter (DC_TRANSPORT_SERIAL, data)) { + if (!dc_descriptor_filter (iterator->descriptor, DC_TRANSPORT_SERIAL, data)) { continue; } diff --git a/src/usbhid.c b/src/usbhid.c index e26d42b..9ed6648 100644 --- a/src/usbhid.c +++ b/src/usbhid.c @@ -102,7 +102,7 @@ static dc_status_t dc_usbhid_close (dc_iostream_t *iostream); typedef struct dc_usbhid_iterator_t { dc_iterator_t base; - dc_filter_t filter; + dc_descriptor_t *descriptor; dc_usbhid_session_t *session; #if defined(USE_LIBUSB) struct libusb_device **devices; @@ -398,7 +398,7 @@ dc_usbhid_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descripto iterator->devices = devices; iterator->current = devices; #endif - iterator->filter = dc_descriptor_get_filter (descriptor); + iterator->descriptor = descriptor; *out = (dc_iterator_t *) iterator; @@ -435,7 +435,7 @@ dc_usbhid_iterator_next (dc_iterator_t *abstract, void *out) } dc_usb_desc_t usb = {dev.idVendor, dev.idProduct}; - if (iterator->filter && !iterator->filter (DC_TRANSPORT_USBHID, &usb)) { + if (!dc_descriptor_filter (iterator->descriptor, DC_TRANSPORT_USBHID, &usb)) { continue; } @@ -518,7 +518,7 @@ dc_usbhid_iterator_next (dc_iterator_t *abstract, void *out) iterator->current = current->next; dc_usb_desc_t usb = {current->vendor_id, current->product_id}; - if (iterator->filter && !iterator->filter (DC_TRANSPORT_USBHID, &usb)) { + if (!dc_descriptor_filter (iterator->descriptor, DC_TRANSPORT_USBHID, &usb)) { continue; } From 57f0ce6d7902117cfc4d0d6b401b516fc93ca488 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 29 Apr 2020 18:56:13 +0200 Subject: [PATCH 06/11] Add support for filter parameters The filter parameter provides a mechanism to pass some additional information, needed to configure the I/O stream, back to the caller. --- src/bluetooth.c | 2 +- src/descriptor-private.h | 2 +- src/descriptor.c | 59 ++++++++++++++++++++++++---------------- src/irda.c | 2 +- src/serial_posix.c | 2 +- src/serial_win32.c | 2 +- src/usbhid.c | 2 +- 7 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/bluetooth.c b/src/bluetooth.c index e6fafd5..1889bf7 100644 --- a/src/bluetooth.c +++ b/src/bluetooth.c @@ -456,7 +456,7 @@ dc_bluetooth_iterator_next (dc_iterator_t *abstract, void *out) INFO (abstract->context, "Discover: address=" DC_ADDRESS_FORMAT ", name=%s", address, name ? name : ""); - if (!dc_descriptor_filter (iterator->descriptor, DC_TRANSPORT_BLUETOOTH, name)) { + if (!dc_descriptor_filter (iterator->descriptor, DC_TRANSPORT_BLUETOOTH, name, NULL)) { continue; } diff --git a/src/descriptor-private.h b/src/descriptor-private.h index 06c2980..eaf7303 100644 --- a/src/descriptor-private.h +++ b/src/descriptor-private.h @@ -34,7 +34,7 @@ typedef struct dc_usb_desc_t { } dc_usb_desc_t; int -dc_descriptor_filter (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); +dc_descriptor_filter (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata, void *params); #ifdef __cplusplus } diff --git a/src/descriptor.c b/src/descriptor.c index 183d3d5..5acab3f 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -36,21 +36,31 @@ values, \ C_ARRAY_SIZE(values) - isnullterminated, \ C_ARRAY_ITEMSIZE(values), \ - match) + match, \ + NULL, NULL, 0) + +#define DC_FILTER_INTERNAL_WITH_PARAMS(key, values, isnullterminated, match, params_dst, params_src) \ + dc_filter_internal( \ + key, \ + values, \ + C_ARRAY_SIZE(values) - isnullterminated, \ + C_ARRAY_ITEMSIZE(values), \ + match, \ + params_dst, params_src, sizeof *(params_src)) typedef int (*dc_match_t)(const void *, const void *); -typedef int (*dc_filter_t) (dc_transport_t transport, const void *userdata); +typedef int (*dc_filter_t) (dc_transport_t transport, const void *userdata, void *params); -static int dc_filter_uwatec (dc_transport_t transport, const void *userdata); -static int dc_filter_suunto (dc_transport_t transport, const void *userdata); -static int dc_filter_shearwater (dc_transport_t transport, const void *userdata); -static int dc_filter_hw (dc_transport_t transport, const void *userdata); -static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata); -static int dc_filter_mares (dc_transport_t transport, const void *userdata); -static int dc_filter_divesystem (dc_transport_t transport, const void *userdata); -static int dc_filter_oceanic (dc_transport_t transport, const void *userdata); -static int dc_filter_mclean (dc_transport_t transport, const void *userdata); +static int dc_filter_uwatec (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_suunto (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_shearwater (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_hw (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_mares (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_divesystem (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_oceanic (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_mclean (dc_transport_t transport, const void *userdata, void *params); static dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item); @@ -463,13 +473,16 @@ dc_match_oceanic (const void *key, const void *value) } static int -dc_filter_internal (const void *key, const void *values, size_t count, size_t size, dc_match_t match) +dc_filter_internal (const void *key, const void *values, size_t count, size_t size, dc_match_t match, void *params_dst, const void *params_src, size_t params_size) { if (key == NULL) return 0; for (size_t i = 0; i < count; ++i) { if (match (key, (const unsigned char *) values + i * size)) { + if (params_src && params_dst) { + memcpy (params_dst, params_src, params_size); + } return 1; } } @@ -484,7 +497,7 @@ static const char * const rfcomm[] = { NULL }; -static int dc_filter_uwatec (dc_transport_t transport, const void *userdata) +static int dc_filter_uwatec (dc_transport_t transport, const void *userdata, void *params) { static const char * const irda[] = { "Aladin Smart Com", @@ -519,7 +532,7 @@ static int dc_filter_uwatec (dc_transport_t transport, const void *userdata) return 1; } -static int dc_filter_suunto (dc_transport_t transport, const void *userdata) +static int dc_filter_suunto (dc_transport_t transport, const void *userdata, void *params) { static const dc_usb_desc_t usbhid[] = { {0x1493, 0x0030}, // Eon Steel @@ -541,7 +554,7 @@ static int dc_filter_suunto (dc_transport_t transport, const void *userdata) return 1; } -static int dc_filter_hw (dc_transport_t transport, const void *userdata) +static int dc_filter_hw (dc_transport_t transport, const void *userdata, void *params) { static const char * const bluetooth[] = { "OSTC", @@ -557,7 +570,7 @@ static int dc_filter_hw (dc_transport_t transport, const void *userdata) return 1; } -static int dc_filter_shearwater (dc_transport_t transport, const void *userdata) +static int dc_filter_shearwater (dc_transport_t transport, const void *userdata, void *params) { static const char * const bluetooth[] = { "Predator", @@ -577,7 +590,7 @@ static int dc_filter_shearwater (dc_transport_t transport, const void *userdata) return 1; } -static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata) +static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata, void *params) { static const char * const bluetooth[] = { "DiveComputer", @@ -592,7 +605,7 @@ static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata) return 1; } -static int dc_filter_mares (dc_transport_t transport, const void *userdata) +static int dc_filter_mares (dc_transport_t transport, const void *userdata, void *params) { static const char * const bluetooth[] = { "Mares bluelink pro", @@ -606,7 +619,7 @@ static int dc_filter_mares (dc_transport_t transport, const void *userdata) return 1; } -static int dc_filter_divesystem (dc_transport_t transport, const void *userdata) +static int dc_filter_divesystem (dc_transport_t transport, const void *userdata, void *params) { static const char * const bluetooth[] = { "DS", @@ -619,7 +632,7 @@ static int dc_filter_divesystem (dc_transport_t transport, const void *userdata) return 1; } -static int dc_filter_oceanic (dc_transport_t transport, const void *userdata) +static int dc_filter_oceanic (dc_transport_t transport, const void *userdata, void *params) { static const unsigned int model[] = { 0x4552, // Oceanic Pro Plus X @@ -641,7 +654,7 @@ static int dc_filter_oceanic (dc_transport_t transport, const void *userdata) return 1; } -static int dc_filter_mclean(dc_transport_t transport, const void *userdata) +static int dc_filter_mclean(dc_transport_t transport, const void *userdata, void *params) { static const char * const bluetooth[] = { "McLean Extreme", @@ -748,10 +761,10 @@ dc_descriptor_get_transports (dc_descriptor_t *descriptor) } int -dc_descriptor_filter (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +dc_descriptor_filter (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata, void *params) { if (descriptor == NULL || descriptor->filter == NULL) return 1; - return descriptor->filter (transport, userdata); + return descriptor->filter (transport, userdata, params); } diff --git a/src/irda.c b/src/irda.c index 89b7e50..448c57c 100644 --- a/src/irda.c +++ b/src/irda.c @@ -231,7 +231,7 @@ dc_irda_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descriptor_ INFO (context, "Discover: address=%08x, name=%s, charset=%02x, hints=%04x", address, name, charset, hints); - if (!dc_descriptor_filter (descriptor, DC_TRANSPORT_IRDA, name)) { + if (!dc_descriptor_filter (descriptor, DC_TRANSPORT_IRDA, name, NULL)) { continue; } diff --git a/src/serial_posix.c b/src/serial_posix.c index cca501e..4fbcb96 100644 --- a/src/serial_posix.c +++ b/src/serial_posix.c @@ -230,7 +230,7 @@ dc_serial_iterator_next (dc_iterator_t *abstract, void *out) return DC_STATUS_NOMEMORY; } - if (!dc_descriptor_filter (iterator->descriptor, DC_TRANSPORT_SERIAL, filename)) { + if (!dc_descriptor_filter (iterator->descriptor, DC_TRANSPORT_SERIAL, filename, NULL)) { continue; } diff --git a/src/serial_win32.c b/src/serial_win32.c index b51aafd..8427a25 100644 --- a/src/serial_win32.c +++ b/src/serial_win32.c @@ -226,7 +226,7 @@ dc_serial_iterator_next (dc_iterator_t *abstract, void *out) // Null terminate the string. data[data_len] = 0; - if (!dc_descriptor_filter (iterator->descriptor, DC_TRANSPORT_SERIAL, data)) { + if (!dc_descriptor_filter (iterator->descriptor, DC_TRANSPORT_SERIAL, data, NULL)) { continue; } diff --git a/src/usbhid.c b/src/usbhid.c index 9ed6648..b16bcc3 100644 --- a/src/usbhid.c +++ b/src/usbhid.c @@ -435,7 +435,7 @@ dc_usbhid_iterator_next (dc_iterator_t *abstract, void *out) } dc_usb_desc_t usb = {dev.idVendor, dev.idProduct}; - if (!dc_descriptor_filter (iterator->descriptor, DC_TRANSPORT_USBHID, &usb)) { + if (!dc_descriptor_filter (iterator->descriptor, DC_TRANSPORT_USBHID, &usb, NULL)) { continue; } From c84bbd93a3ed898d45482d5c22ce0acee9701033 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 30 Apr 2020 19:10:34 +0200 Subject: [PATCH 07/11] Add an I/O implementation for USB communication The USB communication is now also implemented as an I/O stream transport. Unlike most I/O devices, USB communication supports multiple interfaces and endpoints. This requires some some special care: In the general case, autodetection isn't really possible without additional knowledge. Hence the need for the filter parameters to pass this kind of information. The implementation assumes two bulk endpoints for the standard read/write interface. Communication with the control endpoint is supported through the new DC_IOCTL_USB_CONTROL_{READ,WRITE} ioctl's. --- include/libdivecomputer/Makefile.am | 1 + include/libdivecomputer/usb.h | 145 +++++++ msvc/libdivecomputer.vcproj | 8 + src/Makefile.am | 1 + src/descriptor-private.h | 6 + src/libdivecomputer.symbols | 6 + src/usb.c | 585 ++++++++++++++++++++++++++++ 7 files changed, 752 insertions(+) create mode 100644 include/libdivecomputer/usb.h create mode 100644 src/usb.c diff --git a/include/libdivecomputer/Makefile.am b/include/libdivecomputer/Makefile.am index bffc849..6f679c7 100644 --- a/include/libdivecomputer/Makefile.am +++ b/include/libdivecomputer/Makefile.am @@ -12,6 +12,7 @@ libdivecomputer_HEADERS = \ bluetooth.h \ ble.h \ irda.h \ + usb.h \ usbhid.h \ custom.h \ device.h \ diff --git a/include/libdivecomputer/usb.h b/include/libdivecomputer/usb.h new file mode 100644 index 0000000..6d13813 --- /dev/null +++ b/include/libdivecomputer/usb.h @@ -0,0 +1,145 @@ +/* + * libdivecomputer + * + * Copyright (C) 2020 Jef Driesen + * + * 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 DC_USB_H +#define DC_USB_H + +#include "common.h" +#include "context.h" +#include "iostream.h" +#include "iterator.h" +#include "descriptor.h" +#include "ioctl.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * Perform a USB control transfer. + * + * The parameters for the control transfer are specified in the + * #dc_usb_control_t data structure. If the control transfer requires + * additional data as in- or output, the buffer must be located + * immediately after the #dc_usb_control_t data structure, and the + * length of the buffer must be indicated in the #wLength field. The + * size of the ioctl request is the total size, including the size of + * the #dc_usb_control_t structure. + */ +#define DC_IOCTL_USB_CONTROL_READ DC_IOCTL_IOR('u', 0, DC_IOCTL_SIZE_VARIABLE) +#define DC_IOCTL_USB_CONTROL_WRITE DC_IOCTL_IOW('u', 0, DC_IOCTL_SIZE_VARIABLE) + +/** + * USB control transfer. + */ +typedef struct dc_usb_control_t { + unsigned char bmRequestType; + unsigned char bRequest; + unsigned short wValue; + unsigned short wIndex; + unsigned short wLength; +} dc_usb_control_t; + +/** + * Endpoint direction bits of the USB control transfer. + */ +typedef enum dc_usb_endpoint_t { + DC_USB_ENDPOINT_OUT = 0x00, + DC_USB_ENDPOINT_IN = 0x80 +} dc_usb_endpoint_t; + +/** + * Request type bits of the USB control transfer. + */ +typedef enum dc_usb_request_t { + DC_USB_REQUEST_STANDARD = 0x00, + DC_USB_REQUEST_CLASS = 0x20, + DC_USB_REQUEST_VENDOR = 0x40, + DC_USB_REQUEST_RESERVED = 0x60 +} dc_usb_request_t; + +/** + * Recipient bits of the USB control transfer. + */ +typedef enum dc_usb_recipient_t { + DC_USB_RECIPIENT_DEVICE = 0x00, + DC_USB_RECIPIENT_INTERFACE = 0x01, + DC_USB_RECIPIENT_ENDPOINT = 0x02, + DC_USB_RECIPIENT_OTHER = 0x03, +} dc_usb_recipient_t; + +/** + * Opaque object representing a USB device. + */ +typedef struct dc_usb_device_t dc_usb_device_t; + +/** + * Get the vendor id (VID) of the USB device. + * + * @param[in] device A valid USB device. + */ +unsigned int +dc_usb_device_get_vid (dc_usb_device_t *device); + +/** + * Get the product id (PID) of the USB device. + * + * @param[in] device A valid USB device. + */ +unsigned int +dc_usb_device_get_pid (dc_usb_device_t *device); + +/** + * Destroy the USB device and free all resources. + * + * @param[in] device A valid USB device. + */ +void +dc_usb_device_free(dc_usb_device_t *device); + +/** + * Create an iterator to enumerate the USB devices. + * + * @param[out] iterator A location to store the iterator. + * @param[in] context A valid context object. + * @param[in] descriptor A valid device descriptor or NULL. + * @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code + * on failure. + */ +dc_status_t +dc_usb_iterator_new (dc_iterator_t **iterator, dc_context_t *context, dc_descriptor_t *descriptor); + +/** + * Open a USB connection. + * + * @param[out] iostream A location to store the USB connection. + * @param[in] context A valid context object. + * @param[in] device A valid USB device. + * @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code + * on failure. + */ +dc_status_t +dc_usb_open (dc_iostream_t **iostream, dc_context_t *context, dc_usb_device_t *device); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* DC_USB_H */ diff --git a/msvc/libdivecomputer.vcproj b/msvc/libdivecomputer.vcproj index 0580e56..5dcad41 100644 --- a/msvc/libdivecomputer.vcproj +++ b/msvc/libdivecomputer.vcproj @@ -518,6 +518,10 @@ RelativePath="..\src\timer.c" > + + @@ -872,6 +876,10 @@ RelativePath="..\include\libdivecomputer\units.h" > + + diff --git a/src/Makefile.am b/src/Makefile.am index 7b18286..b33acd0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -76,6 +76,7 @@ libdivecomputer_la_SOURCES = \ liquivision_lynx.h liquivision_lynx.c liquivision_lynx_parser.c \ socket.h socket.c \ irda.c \ + usb.c \ usbhid.c \ bluetooth.c \ custom.c diff --git a/src/descriptor-private.h b/src/descriptor-private.h index eaf7303..d829422 100644 --- a/src/descriptor-private.h +++ b/src/descriptor-private.h @@ -33,6 +33,12 @@ typedef struct dc_usb_desc_t { unsigned short pid; } dc_usb_desc_t; +typedef struct dc_usb_params_t { + unsigned int interface; + unsigned char endpoint_in; + unsigned char endpoint_out; +} dc_usb_params_t; + int dc_descriptor_filter (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata, void *params); diff --git a/src/libdivecomputer.symbols b/src/libdivecomputer.symbols index b17d60b..85caae5 100644 --- a/src/libdivecomputer.symbols +++ b/src/libdivecomputer.symbols @@ -70,6 +70,12 @@ dc_irda_device_free dc_irda_iterator_new dc_irda_open +dc_usb_device_get_vid +dc_usb_device_get_pid +dc_usb_device_free +dc_usb_iterator_new +dc_usb_open + dc_usbhid_device_get_vid dc_usbhid_device_get_pid dc_usbhid_device_free diff --git a/src/usb.c b/src/usb.c new file mode 100644 index 0000000..87a1061 --- /dev/null +++ b/src/usb.c @@ -0,0 +1,585 @@ +/* + * libdivecomputer + * + * Copyright (C) 2020 Jef Driesen + * + * 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#ifdef HAVE_LIBUSB +#ifdef _WIN32 +#define NOGDI +#endif +#include +#endif + +#include + +#include "common-private.h" +#include "context-private.h" +#include "iostream-private.h" +#include "descriptor-private.h" +#include "iterator-private.h" +#include "platform.h" + +#define ISINSTANCE(device) dc_iostream_isinstance((device), &dc_usb_vtable) + +typedef struct dc_usb_session_t { + size_t refcount; +#ifdef HAVE_LIBUSB + libusb_context *handle; +#endif +} dc_usb_session_t; + +struct dc_usb_device_t { + unsigned short vid, pid; + dc_usb_session_t *session; +#ifdef HAVE_LIBUSB + struct libusb_device *handle; + int interface; + unsigned char endpoint_in; + unsigned char endpoint_out; +#endif +}; + +#ifdef HAVE_LIBUSB +static dc_status_t dc_usb_iterator_next (dc_iterator_t *iterator, void *item); +static dc_status_t dc_usb_iterator_free (dc_iterator_t *iterator); + +static dc_status_t dc_usb_set_timeout (dc_iostream_t *iostream, int timeout); +static dc_status_t dc_usb_poll (dc_iostream_t *iostream, int timeout); +static dc_status_t dc_usb_read (dc_iostream_t *iostream, void *data, size_t size, size_t *actual); +static dc_status_t dc_usb_write (dc_iostream_t *iostream, const void *data, size_t size, size_t *actual); +static dc_status_t dc_usb_ioctl (dc_iostream_t *iostream, unsigned int request, void *data, size_t size); +static dc_status_t dc_usb_close (dc_iostream_t *iostream); + +typedef struct dc_usb_iterator_t { + dc_iterator_t base; + dc_descriptor_t *descriptor; + dc_usb_session_t *session; + struct libusb_device **devices; + size_t count; + size_t current; +} dc_usb_iterator_t; + +typedef struct dc_usb_t { + /* Base class. */ + dc_iostream_t base; + /* Internal state. */ + dc_usb_session_t *session; + libusb_device_handle *handle; + int interface; + unsigned char endpoint_in; + unsigned char endpoint_out; + unsigned int timeout; +} dc_usb_t; + +static const dc_iterator_vtable_t dc_usb_iterator_vtable = { + sizeof(dc_usb_iterator_t), + dc_usb_iterator_next, + dc_usb_iterator_free, +}; + +static const dc_iostream_vtable_t dc_usb_vtable = { + sizeof(dc_usb_t), + dc_usb_set_timeout, /* set_timeout */ + NULL, /* set_break */ + NULL, /* set_dtr */ + NULL, /* set_rts */ + NULL, /* get_lines */ + NULL, /* get_available */ + NULL, /* configure */ + dc_usb_poll, /* poll */ + dc_usb_read, /* read */ + dc_usb_write, /* write */ + dc_usb_ioctl, /* ioctl */ + NULL, /* flush */ + NULL, /* purge */ + NULL, /* sleep */ + dc_usb_close, /* close */ +}; + +static dc_status_t +syserror(int errcode) +{ + switch (errcode) { + case LIBUSB_ERROR_INVALID_PARAM: + return DC_STATUS_INVALIDARGS; + case LIBUSB_ERROR_NO_MEM: + return DC_STATUS_NOMEMORY; + case LIBUSB_ERROR_NO_DEVICE: + case LIBUSB_ERROR_NOT_FOUND: + return DC_STATUS_NODEVICE; + case LIBUSB_ERROR_ACCESS: + case LIBUSB_ERROR_BUSY: + return DC_STATUS_NOACCESS; + case LIBUSB_ERROR_TIMEOUT: + return DC_STATUS_TIMEOUT; + default: + return DC_STATUS_IO; + } +} + +static dc_status_t +dc_usb_session_new (dc_usb_session_t **out, dc_context_t *context) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_usb_session_t *session = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + session = (dc_usb_session_t *) malloc (sizeof(dc_usb_session_t)); + if (session == NULL) { + ERROR (context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_unlock; + } + + session->refcount = 1; + + int rc = libusb_init (&session->handle); + if (rc != LIBUSB_SUCCESS) { + ERROR (context, "Failed to initialize usb support (%s).", + libusb_error_name (rc)); + status = syserror (rc); + goto error_free; + } + + *out = session; + + return status; + +error_free: + free (session); +error_unlock: + return status; +} + +static dc_usb_session_t * +dc_usb_session_ref (dc_usb_session_t *session) +{ + if (session == NULL) + return NULL; + + session->refcount++; + + return session; +} + +static dc_status_t +dc_usb_session_unref (dc_usb_session_t *session) +{ + if (session == NULL) + return DC_STATUS_SUCCESS; + + if (--session->refcount == 0) { + libusb_exit (session->handle); + free (session); + } + + return DC_STATUS_SUCCESS; +} +#endif + +unsigned int +dc_usb_device_get_vid (dc_usb_device_t *device) +{ + if (device == NULL) + return 0; + + return device->vid; +} + +unsigned int +dc_usb_device_get_pid (dc_usb_device_t *device) +{ + if (device == NULL) + return 0; + + return device->pid; +} + +void +dc_usb_device_free(dc_usb_device_t *device) +{ + if (device == NULL) + return; + +#ifdef HAVE_LIBUSB + libusb_unref_device (device->handle); + dc_usb_session_unref (device->session); +#endif + free (device); +} + +dc_status_t +dc_usb_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descriptor_t *descriptor) +{ +#ifdef HAVE_LIBUSB + dc_status_t status = DC_STATUS_SUCCESS; + dc_usb_iterator_t *iterator = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + iterator = (dc_usb_iterator_t *) dc_iterator_allocate (context, &dc_usb_iterator_vtable); + if (iterator == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Initialize the usb library. + status = dc_usb_session_new (&iterator->session, context); + if (status != DC_STATUS_SUCCESS) { + goto error_free; + } + + // Enumerate the USB devices. + struct libusb_device **devices = NULL; + ssize_t ndevices = libusb_get_device_list (iterator->session->handle, &devices); + if (ndevices < 0) { + ERROR (context, "Failed to enumerate the usb devices (%s).", + libusb_error_name (ndevices)); + status = syserror (ndevices); + goto error_session_unref; + } + + iterator->devices = devices; + iterator->count = ndevices; + iterator->current = 0; + iterator->descriptor = descriptor; + + *out = (dc_iterator_t *) iterator; + + return DC_STATUS_SUCCESS; + +error_session_unref: + dc_usb_session_unref (iterator->session); +error_free: + dc_iterator_deallocate ((dc_iterator_t *) iterator); + return status; +#else + return DC_STATUS_UNSUPPORTED; +#endif +} + +#ifdef HAVE_LIBUSB +static dc_status_t +dc_usb_iterator_next (dc_iterator_t *abstract, void *out) +{ + dc_usb_iterator_t *iterator = (dc_usb_iterator_t *) abstract; + dc_usb_device_t *device = NULL; + + while (iterator->current < iterator->count) { + struct libusb_device *current = iterator->devices[iterator->current++]; + + // Get the device descriptor. + struct libusb_device_descriptor dev; + int rc = libusb_get_device_descriptor (current, &dev); + if (rc < 0) { + ERROR (abstract->context, "Failed to get the device descriptor (%s).", + libusb_error_name (rc)); + return syserror (rc); + } + + dc_usb_desc_t usb = {dev.idVendor, dev.idProduct}; + dc_usb_params_t params = {0, 0, 0}; + if (!dc_descriptor_filter (iterator->descriptor, DC_TRANSPORT_USB, &usb, ¶ms)) { + continue; + } + + // Get the active configuration descriptor. + struct libusb_config_descriptor *config = NULL; + rc = libusb_get_active_config_descriptor (current, &config); + if (rc != LIBUSB_SUCCESS) { + ERROR (abstract->context, "Failed to get the configuration descriptor (%s).", + libusb_error_name (rc)); + return syserror (rc); + } + + // Find the first matching interface. + const struct libusb_interface_descriptor *interface = NULL; + for (unsigned int i = 0; i < config->bNumInterfaces; i++) { + const struct libusb_interface *iface = &config->interface[i]; + for (int j = 0; j < iface->num_altsetting; j++) { + const struct libusb_interface_descriptor *desc = &iface->altsetting[j]; + if (interface == NULL && desc->bInterfaceNumber == params.interface) { + interface = desc; + } + } + } + + if (interface == NULL) { + libusb_free_config_descriptor (config); + continue; + } + + // Find the first matching input and output bulk endpoints. + const struct libusb_endpoint_descriptor *ep_in = NULL, *ep_out = NULL; + for (unsigned int i = 0; i < interface->bNumEndpoints; i++) { + const struct libusb_endpoint_descriptor *desc = &interface->endpoint[i]; + + unsigned int type = desc->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK; + unsigned int direction = desc->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK; + + if (type != LIBUSB_TRANSFER_TYPE_BULK) { + continue; + } + + if (ep_in == NULL && direction == LIBUSB_ENDPOINT_IN && + (params.endpoint_in == 0 || params.endpoint_in == desc->bEndpointAddress)) { + ep_in = desc; + } + + if (ep_out == NULL && direction == LIBUSB_ENDPOINT_OUT && + (params.endpoint_out == 0 || params.endpoint_out == desc->bEndpointAddress)) { + ep_out = desc; + } + } + + if (ep_in == NULL || ep_out == NULL) { + libusb_free_config_descriptor (config); + continue; + } + + device = (dc_usb_device_t *) malloc (sizeof(dc_usb_device_t)); + if (device == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + libusb_free_config_descriptor (config); + return DC_STATUS_NOMEMORY; + } + + device->session = dc_usb_session_ref (iterator->session); + device->vid = dev.idVendor; + device->pid = dev.idProduct; + device->handle = libusb_ref_device (current); + device->interface = interface->bInterfaceNumber; + device->endpoint_in = ep_in->bEndpointAddress; + device->endpoint_out = ep_out->bEndpointAddress; + + *(dc_usb_device_t **) out = device; + + libusb_free_config_descriptor (config); + + return DC_STATUS_SUCCESS; + } + + return DC_STATUS_DONE; +} + +static dc_status_t +dc_usb_iterator_free (dc_iterator_t *abstract) +{ + dc_usb_iterator_t *iterator = (dc_usb_iterator_t *) abstract; + + libusb_free_device_list (iterator->devices, 1); + dc_usb_session_unref (iterator->session); + + return DC_STATUS_SUCCESS; +} +#endif + +dc_status_t +dc_usb_open (dc_iostream_t **out, dc_context_t *context, dc_usb_device_t *device) +{ +#ifdef HAVE_LIBUSB + dc_status_t status = DC_STATUS_SUCCESS; + dc_usb_t *usb = NULL; + + if (out == NULL || device == NULL) + return DC_STATUS_INVALIDARGS; + + INFO (context, "Open: vid=%04x, pid=%04x, interface=%u, endpoints=%02x,%02x", + device->vid, device->pid, device->interface, device->endpoint_in, device->endpoint_out); + + // Allocate memory. + usb = (dc_usb_t *) dc_iostream_allocate (context, &dc_usb_vtable, DC_TRANSPORT_USB); + if (usb == NULL) { + ERROR (context, "Out of memory."); + return DC_STATUS_NOMEMORY; + } + + // Initialize the usb library. + usb->session = dc_usb_session_ref (device->session); + if (usb->session == NULL) { + goto error_free; + } + + // Open the USB device. + int rc = libusb_open (device->handle, &usb->handle); + if (rc != LIBUSB_SUCCESS) { + ERROR (context, "Failed to open the usb device (%s).", + libusb_error_name (rc)); + status = syserror (rc); + goto error_session_unref; + } + +#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000102) + libusb_set_auto_detach_kernel_driver (usb->handle, 1); +#endif + + // Claim the interface. + rc = libusb_claim_interface (usb->handle, device->interface); + if (rc != LIBUSB_SUCCESS) { + ERROR (context, "Failed to claim the usb interface (%s).", + libusb_error_name (rc)); + status = syserror (rc); + goto error_usb_close; + } + + usb->interface = device->interface; + usb->endpoint_in = device->endpoint_in; + usb->endpoint_out = device->endpoint_out; + usb->timeout = 0; + + *out = (dc_iostream_t *) usb; + + return DC_STATUS_SUCCESS; + +error_usb_close: + libusb_close (usb->handle); +error_session_unref: + dc_usb_session_unref (usb->session); +error_free: + dc_iostream_deallocate ((dc_iostream_t *) usb); + return status; +#else + return DC_STATUS_UNSUPPORTED; +#endif +} + +#ifdef HAVE_LIBUSB +static dc_status_t +dc_usb_close (dc_iostream_t *abstract) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_usb_t *usb = (dc_usb_t *) abstract; + + libusb_release_interface (usb->handle, usb->interface); + libusb_close (usb->handle); + dc_usb_session_unref (usb->session); + + return status; +} + +static dc_status_t +dc_usb_set_timeout (dc_iostream_t *abstract, int timeout) +{ + dc_usb_t *usb = (dc_usb_t *) abstract; + + if (timeout < 0) { + usb->timeout = 0; + } else if (timeout == 0) { + return DC_STATUS_UNSUPPORTED; + } else { + usb->timeout = timeout; + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +dc_usb_poll (dc_iostream_t *abstract, int timeout) +{ + return DC_STATUS_UNSUPPORTED; +} + +static dc_status_t +dc_usb_read (dc_iostream_t *abstract, void *data, size_t size, size_t *actual) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_usb_t *usb = (dc_usb_t *) abstract; + int nbytes = 0; + + int rc = libusb_bulk_transfer (usb->handle, usb->endpoint_in, data, size, &nbytes, usb->timeout); + if (rc != LIBUSB_SUCCESS) { + ERROR (abstract->context, "Usb read bulk transfer failed (%s).", + libusb_error_name (rc)); + status = syserror (rc); + goto out; + } + +out: + if (actual) + *actual = nbytes; + + return status; +} + +static dc_status_t +dc_usb_write (dc_iostream_t *abstract, const void *data, size_t size, size_t *actual) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_usb_t *usb = (dc_usb_t *) abstract; + int nbytes = 0; + + int rc = libusb_bulk_transfer (usb->handle, usb->endpoint_out, (void *) data, size, &nbytes, 0); + if (rc != LIBUSB_SUCCESS) { + ERROR (abstract->context, "Usb write bulk transfer failed (%s).", + libusb_error_name (rc)); + status = syserror (rc); + goto out; + } + +out: + if (actual) + *actual = nbytes; + + return status; +} + +static dc_status_t +dc_usb_ioctl_control (dc_iostream_t *abstract, void *data, size_t size) +{ + dc_usb_t *usb = (dc_usb_t *) abstract; + const dc_usb_control_t *control = (const dc_usb_control_t *) data; + + if (size < sizeof(control) || control->wLength > size - sizeof(control)) { + return DC_STATUS_INVALIDARGS; + } + + int rc = libusb_control_transfer (usb->handle, + control->bmRequestType, control->bRequest, control->wValue, control->wIndex, + (unsigned char *) data + sizeof(control), control->wLength, usb->timeout); + if (rc != LIBUSB_SUCCESS) { + ERROR (abstract->context, "Usb control transfer failed (%s).", + libusb_error_name (rc)); + return syserror (rc); + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +dc_usb_ioctl (dc_iostream_t *abstract, unsigned int request, void *data, size_t size) +{ + switch (request) { + case DC_IOCTL_USB_CONTROL_READ: + case DC_IOCTL_USB_CONTROL_WRITE: + return dc_usb_ioctl_control (abstract, data, size); + default: + return DC_STATUS_UNSUPPORTED; + } +} +#endif From c72dc4aa738915292917a538f02a61b928233383 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 30 Apr 2020 19:17:23 +0200 Subject: [PATCH 08/11] Use the new USB transport for the Atomic Aquatics Cobalt Replace the hardcoded libusb based code with the new USB I/O transport. This enables the use of a custom I/O on platforms where libusb is not available. --- src/atomics_cobalt.c | 152 +++++++++++++------------------------------ src/atomics_cobalt.h | 3 +- src/descriptor.c | 22 ++++++- src/device.c | 2 +- 4 files changed, 70 insertions(+), 109 deletions(-) diff --git a/src/atomics_cobalt.c b/src/atomics_cobalt.c index e907b77..a67ef82 100644 --- a/src/atomics_cobalt.c +++ b/src/atomics_cobalt.c @@ -19,19 +19,10 @@ * MA 02110-1301 USA */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - #include // memcmp, memcpy #include // malloc, free -#ifdef HAVE_LIBUSB -#ifdef _WIN32 -#define NOGDI -#endif -#include -#endif +#include #include "atomics_cobalt.h" #include "context-private.h" @@ -41,8 +32,6 @@ #define ISINSTANCE(device) dc_device_isinstance((device), &atomics_cobalt_device_vtable) -#define EXITCODE(rc) (rc == LIBUSB_ERROR_TIMEOUT ? DC_STATUS_TIMEOUT : DC_STATUS_IO) - #define COBALT1 0 #define COBALT2 2 @@ -58,10 +47,7 @@ typedef struct atomics_cobalt_device_t { dc_device_t base; -#ifdef HAVE_LIBUSB - libusb_context *context; - libusb_device_handle *handle; -#endif + dc_iostream_t *iostream; unsigned int simulation; unsigned char fingerprint[6]; unsigned char version[SZ_VERSION]; @@ -69,7 +55,6 @@ typedef struct atomics_cobalt_device_t { static dc_status_t atomics_cobalt_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); static dc_status_t atomics_cobalt_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); -static dc_status_t atomics_cobalt_device_close (dc_device_t *abstract); static const dc_device_vtable_t atomics_cobalt_device_vtable = { sizeof(atomics_cobalt_device_t), @@ -80,22 +65,19 @@ static const dc_device_vtable_t atomics_cobalt_device_vtable = { NULL, /* dump */ atomics_cobalt_device_foreach, /* foreach */ NULL, /* timesync */ - atomics_cobalt_device_close /* close */ + NULL /* close */ }; dc_status_t -atomics_cobalt_device_open (dc_device_t **out, dc_context_t *context) +atomics_cobalt_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) { -#ifdef HAVE_LIBUSB dc_status_t status = DC_STATUS_SUCCESS; atomics_cobalt_device_t *device = NULL; -#endif if (out == NULL) return DC_STATUS_INVALIDARGS; -#ifdef HAVE_LIBUSB // Allocate memory. device = (atomics_cobalt_device_t *) dc_device_allocate (context, &atomics_cobalt_device_vtable); if (device == NULL) { @@ -104,67 +86,30 @@ atomics_cobalt_device_open (dc_device_t **out, dc_context_t *context) } // Set the default values. - device->context = NULL; - device->handle = NULL; + device->iostream = iostream; device->simulation = 0; memset (device->fingerprint, 0, sizeof (device->fingerprint)); - int rc = libusb_init (&device->context); - if (rc < 0) { - ERROR (context, "Failed to initialize usb support."); - status = DC_STATUS_IO; + // Set the timeout for receiving data (2000 ms). + status = dc_iostream_set_timeout (device->iostream, TIMEOUT); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the timeout."); goto error_free; } - device->handle = libusb_open_device_with_vid_pid (device->context, VID, PID); - if (device->handle == NULL) { - ERROR (context, "Failed to open the usb device."); - status = DC_STATUS_IO; - goto error_usb_exit; - } - - rc = libusb_claim_interface (device->handle, 0); - if (rc < 0) { - ERROR (context, "Failed to claim the usb interface."); - status = DC_STATUS_IO; - goto error_usb_close; - } - status = atomics_cobalt_device_version ((dc_device_t *) device, device->version, sizeof (device->version)); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to identify the dive computer."); - goto error_usb_close; + goto error_free; } *out = (dc_device_t*) device; return DC_STATUS_SUCCESS; -error_usb_close: - libusb_close (device->handle); -error_usb_exit: - libusb_exit (device->context); error_free: dc_device_deallocate ((dc_device_t *) device); return status; -#else - return DC_STATUS_UNSUPPORTED; -#endif -} - - -static dc_status_t -atomics_cobalt_device_close (dc_device_t *abstract) -{ - atomics_cobalt_device_t *device = (atomics_cobalt_device_t *) abstract; - -#ifdef HAVE_LIBUSB - libusb_release_interface(device->handle, 0); - libusb_close (device->handle); - libusb_exit (device->context); -#endif - - return DC_STATUS_SUCCESS; } @@ -202,6 +147,7 @@ atomics_cobalt_device_set_simulation (dc_device_t *abstract, unsigned int simula dc_status_t atomics_cobalt_device_version (dc_device_t *abstract, unsigned char data[], unsigned int size) { + dc_status_t status = DC_STATUS_SUCCESS; atomics_cobalt_device_t *device = (atomics_cobalt_device_t *) abstract; if (!ISINSTANCE (abstract)) @@ -210,31 +156,31 @@ atomics_cobalt_device_version (dc_device_t *abstract, unsigned char data[], unsi if (size < SZ_VERSION) return DC_STATUS_INVALIDARGS; -#ifdef HAVE_LIBUSB // Send the command to the dive computer. - uint8_t bRequest = 0x01; - int rc = libusb_control_transfer (device->handle, - LIBUSB_RECIPIENT_DEVICE | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT, - bRequest, 0, 0, NULL, 0, TIMEOUT); - if (rc != LIBUSB_SUCCESS) { - ERROR (abstract->context, "Failed to send the command."); - return EXITCODE(rc); - } + unsigned char bRequest = 0x01; + dc_usb_control_t control = { + DC_USB_REQUEST_VENDOR | DC_USB_RECIPIENT_DEVICE | DC_USB_ENDPOINT_OUT, /* bmRequestType */ + bRequest, /* bRequest */ + 0, /* wValue */ + 0, /* wIndex */ + 0, /* wLength */ + }; - HEXDUMP (abstract->context, DC_LOGLEVEL_INFO, "Write", &bRequest, 1); + status = dc_iostream_ioctl (device->iostream, DC_IOCTL_USB_CONTROL_WRITE, &control, sizeof(control)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the command."); + return status; + } // Receive the answer from the dive computer. - int length = 0; + size_t length = 0; unsigned char packet[SZ_VERSION + 2] = {0}; - rc = libusb_bulk_transfer (device->handle, 0x82, - packet, sizeof (packet), &length, TIMEOUT); - if (rc != LIBUSB_SUCCESS || length != sizeof (packet)) { + status = dc_iostream_read (device->iostream, packet, sizeof(packet), &length); + if (status != DC_STATUS_SUCCESS || length != sizeof (packet)) { ERROR (abstract->context, "Failed to receive the answer."); - return EXITCODE(rc); + return status; } - HEXDUMP (abstract->context, DC_LOGLEVEL_INFO, "Read", packet, length); - // Verify the checksum of the packet. unsigned short crc = array_uint16_le (packet + SZ_VERSION); unsigned short ccrc = checksum_add_uint16 (packet, SZ_VERSION, 0x0); @@ -246,16 +192,13 @@ atomics_cobalt_device_version (dc_device_t *abstract, unsigned char data[], unsi memcpy (data, packet, SZ_VERSION); return DC_STATUS_SUCCESS; -#else - return DC_STATUS_UNSUPPORTED; -#endif } static dc_status_t atomics_cobalt_read_dive (dc_device_t *abstract, dc_buffer_t *buffer, int init, dc_event_progress_t *progress) { -#ifdef HAVE_LIBUSB + dc_status_t status = DC_STATUS_SUCCESS; atomics_cobalt_device_t *device = (atomics_cobalt_device_t *) abstract; if (device_is_cancelled (abstract)) @@ -268,35 +211,37 @@ atomics_cobalt_read_dive (dc_device_t *abstract, dc_buffer_t *buffer, int init, } // Send the command to the dive computer. - uint8_t bRequest = 0; + unsigned char bRequest = 0; if (device->simulation) bRequest = init ? 0x02 : 0x03; else bRequest = init ? 0x09 : 0x0A; - int rc = libusb_control_transfer (device->handle, - LIBUSB_RECIPIENT_DEVICE | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT, - bRequest, 0, 0, NULL, 0, TIMEOUT); - if (rc != LIBUSB_SUCCESS) { - ERROR (abstract->context, "Failed to send the command."); - return EXITCODE(rc); - } - HEXDUMP (abstract->context, DC_LOGLEVEL_INFO, "Write", &bRequest, 1); + dc_usb_control_t control = { + DC_USB_REQUEST_VENDOR | DC_USB_RECIPIENT_DEVICE | DC_USB_ENDPOINT_OUT, /* bmRequestType */ + bRequest, /* bRequest */ + 0, /* wValue */ + 0, /* wIndex */ + 0, /* wLength */ + }; + + status = dc_iostream_ioctl (device->iostream, DC_IOCTL_USB_CONTROL_WRITE, &control, sizeof(control)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the command."); + return status; + } unsigned int nbytes = 0; while (1) { // Receive the answer from the dive computer. - int length = 0; + size_t length = 0; unsigned char packet[8 * 1024] = {0}; - rc = libusb_bulk_transfer (device->handle, 0x82, - packet, sizeof (packet), &length, TIMEOUT); - if (rc != LIBUSB_SUCCESS && rc != LIBUSB_ERROR_TIMEOUT) { + status = dc_iostream_read (device->iostream, packet, sizeof(packet), &length); + if (status != DC_STATUS_SUCCESS && status != DC_STATUS_TIMEOUT) { ERROR (abstract->context, "Failed to receive the answer."); - return EXITCODE(rc); + return status; } - HEXDUMP (abstract->context, DC_LOGLEVEL_INFO, "Read", packet, length); - // Update and emit a progress event. if (progress) { progress->current += length; @@ -343,9 +288,6 @@ atomics_cobalt_read_dive (dc_device_t *abstract, dc_buffer_t *buffer, int init, dc_buffer_slice (buffer, 0, nbytes - 2); return DC_STATUS_SUCCESS; -#else - return DC_STATUS_UNSUPPORTED; -#endif } diff --git a/src/atomics_cobalt.h b/src/atomics_cobalt.h index 0308350..09f0226 100644 --- a/src/atomics_cobalt.h +++ b/src/atomics_cobalt.h @@ -23,6 +23,7 @@ #define ATOMICS_COBALT_H #include +#include #include #include #include @@ -32,7 +33,7 @@ extern "C" { #endif /* __cplusplus */ dc_status_t -atomics_cobalt_device_open (dc_device_t **device, dc_context_t *context); +atomics_cobalt_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); dc_status_t atomics_cobalt_parser_create (dc_parser_t **parser, dc_context_t *context); diff --git a/src/descriptor.c b/src/descriptor.c index 5acab3f..cc8bb9a 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -61,6 +61,7 @@ static int dc_filter_mares (dc_transport_t transport, const void *userdata, void static int dc_filter_divesystem (dc_transport_t transport, const void *userdata, void *params); static int dc_filter_oceanic (dc_transport_t transport, const void *userdata, void *params); static int dc_filter_mclean (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_atomic (dc_transport_t transport, const void *userdata, void *params); static dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item); @@ -326,8 +327,8 @@ static const dc_descriptor_t g_descriptors[] = { {"Dive Rite", "NiTek Trio", DC_FAMILY_ZEAGLE_N2ITION3, 0, DC_TRANSPORT_SERIAL, NULL}, {"Scubapro", "XTender 5", DC_FAMILY_ZEAGLE_N2ITION3, 0, DC_TRANSPORT_SERIAL, NULL}, /* Atomic Aquatics Cobalt */ - {"Atomic Aquatics", "Cobalt", DC_FAMILY_ATOMICS_COBALT, 0, DC_TRANSPORT_USB, NULL}, - {"Atomic Aquatics", "Cobalt 2", DC_FAMILY_ATOMICS_COBALT, 2, DC_TRANSPORT_USB, NULL}, + {"Atomic Aquatics", "Cobalt", DC_FAMILY_ATOMICS_COBALT, 0, DC_TRANSPORT_USB, dc_filter_atomic}, + {"Atomic Aquatics", "Cobalt 2", DC_FAMILY_ATOMICS_COBALT, 2, DC_TRANSPORT_USB, dc_filter_atomic}, /* Shearwater Predator */ {"Shearwater", "Predator", DC_FAMILY_SHEARWATER_PREDATOR, 2, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_shearwater}, /* Shearwater Petrel */ @@ -669,6 +670,23 @@ static int dc_filter_mclean(dc_transport_t transport, const void *userdata, void return 1; } +static int dc_filter_atomic (dc_transport_t transport, const void *userdata, void *params) +{ + static const dc_usb_desc_t usb[] = { + {0x0471, 0x0888}, // Atomic Aquatics Cobalt + }; + + static const dc_usb_params_t usb_params = { + 0, 0x82, 0x02 + }; + + if (transport == DC_TRANSPORT_USB) { + return DC_FILTER_INTERNAL_WITH_PARAMS (userdata, usb, 0, dc_match_usb, params, &usb_params); + } + + return 1; +} + dc_status_t dc_descriptor_iterator (dc_iterator_t **out) { diff --git a/src/device.c b/src/device.c index 271c2f4..62914dc 100644 --- a/src/device.c +++ b/src/device.c @@ -190,7 +190,7 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr rc = zeagle_n2ition3_device_open (&device, context, iostream); break; case DC_FAMILY_ATOMICS_COBALT: - rc = atomics_cobalt_device_open (&device, context); + rc = atomics_cobalt_device_open (&device, context, iostream); break; case DC_FAMILY_SHEARWATER_PREDATOR: rc = shearwater_predator_device_open (&device, context, iostream); From 5380b247af47dea198aaf2c68ac7aaf0cc8ef69e Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 30 Apr 2020 19:23:59 +0200 Subject: [PATCH 09/11] Update the example application The application is now responsible for setting up the USB based I/O stream. --- examples/common.c | 38 +++++++++++++++++++++++++++++++++++++- examples/dctool_scan.c | 8 ++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/examples/common.c b/examples/common.c index cfabc03..d4e4ad4 100644 --- a/examples/common.c +++ b/examples/common.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include "common.h" @@ -398,6 +399,41 @@ dctool_file_read (const char *filename) return buffer; } +static dc_status_t +dctool_usb_open (dc_iostream_t **out, dc_context_t *context, dc_descriptor_t *descriptor) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_iostream_t *iostream = NULL; + + // Discover the usb device. + dc_iterator_t *iterator = NULL; + dc_usb_device_t *device = NULL; + dc_usb_iterator_new (&iterator, context, descriptor); + while (dc_iterator_next (iterator, &device) == DC_STATUS_SUCCESS) { + break; + } + dc_iterator_free (iterator); + + if (device == NULL) { + ERROR ("No dive computer found."); + status = DC_STATUS_NODEVICE; + goto cleanup; + } + + // Open the usb device. + status = dc_usb_open (&iostream, context, device); + if (status != DC_STATUS_SUCCESS) { + ERROR ("Failed to open the usb device."); + goto cleanup; + } + + *out = iostream; + +cleanup: + dc_usb_device_free (device); + return status; +} + static dc_status_t dctool_usbhid_open (dc_iostream_t **out, dc_context_t *context, dc_descriptor_t *descriptor) { @@ -532,7 +568,7 @@ dctool_iostream_open (dc_iostream_t **iostream, dc_context_t *context, dc_descri case DC_TRANSPORT_SERIAL: return dc_serial_open (iostream, context, devname); case DC_TRANSPORT_USB: - return DC_STATUS_SUCCESS; + return dctool_usb_open(iostream, context, descriptor); case DC_TRANSPORT_USBHID: return dctool_usbhid_open(iostream, context, descriptor); case DC_TRANSPORT_IRDA: diff --git a/examples/dctool_scan.c b/examples/dctool_scan.c index b96d014..a68d46b 100644 --- a/examples/dctool_scan.c +++ b/examples/dctool_scan.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include "dctool.h" @@ -59,6 +60,9 @@ scan (dc_context_t *context, dc_descriptor_t *descriptor, dc_transport_t transpo case DC_TRANSPORT_BLUETOOTH: status = dc_bluetooth_iterator_new (&iterator, context, descriptor); break; + case DC_TRANSPORT_USB: + status = dc_usb_iterator_new (&iterator, context, descriptor); + break; case DC_TRANSPORT_USBHID: status = dc_usbhid_iterator_new (&iterator, context, descriptor); break; @@ -90,6 +94,10 @@ scan (dc_context_t *context, dc_descriptor_t *descriptor, dc_transport_t transpo dc_bluetooth_device_get_name (device)); dc_bluetooth_device_free (device); break; + case DC_TRANSPORT_USB: + printf ("%04x:%04x\n", dc_usb_device_get_vid (device), dc_usb_device_get_pid (device)); + dc_usb_device_free (device); + break; case DC_TRANSPORT_USBHID: printf ("%04x:%04x\n", dc_usbhid_device_get_vid (device), dc_usbhid_device_get_pid (device)); dc_usbhid_device_free (device); From c77551b366531950fb9ed889a606c70e56b35849 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 17 Jul 2020 20:52:48 +0200 Subject: [PATCH 10/11] Handle a negative number of bytes as an error There is no reason for libusb to ever return a negative number of bytes, but due to the use of a signed integer, it's technically possible. And because libdivecomputer returns an unsigned integer, such a negative number would be reported as a very large size. --- src/usb.c | 8 ++++++-- src/usbhid.c | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/usb.c b/src/usb.c index 87a1061..e39bc48 100644 --- a/src/usb.c +++ b/src/usb.c @@ -513,10 +513,12 @@ dc_usb_read (dc_iostream_t *abstract, void *data, size_t size, size_t *actual) int nbytes = 0; int rc = libusb_bulk_transfer (usb->handle, usb->endpoint_in, data, size, &nbytes, usb->timeout); - if (rc != LIBUSB_SUCCESS) { + if (rc != LIBUSB_SUCCESS || nbytes < 0) { ERROR (abstract->context, "Usb read bulk transfer failed (%s).", libusb_error_name (rc)); status = syserror (rc); + if (nbytes < 0) + nbytes = 0; goto out; } @@ -535,10 +537,12 @@ dc_usb_write (dc_iostream_t *abstract, const void *data, size_t size, size_t *ac int nbytes = 0; int rc = libusb_bulk_transfer (usb->handle, usb->endpoint_out, (void *) data, size, &nbytes, 0); - if (rc != LIBUSB_SUCCESS) { + if (rc != LIBUSB_SUCCESS || nbytes < 0) { ERROR (abstract->context, "Usb write bulk transfer failed (%s).", libusb_error_name (rc)); status = syserror (rc); + if (nbytes < 0) + nbytes = 0; goto out; } diff --git a/src/usbhid.c b/src/usbhid.c index b16bcc3..735ceef 100644 --- a/src/usbhid.c +++ b/src/usbhid.c @@ -703,10 +703,12 @@ dc_usbhid_read (dc_iostream_t *abstract, void *data, size_t size, size_t *actual #if defined(USE_LIBUSB) int rc = libusb_interrupt_transfer (usbhid->handle, usbhid->endpoint_in, data, size, &nbytes, usbhid->timeout); - if (rc != LIBUSB_SUCCESS) { + if (rc != LIBUSB_SUCCESS || nbytes < 0) { ERROR (abstract->context, "Usb read interrupt transfer failed (%s).", libusb_error_name (rc)); status = syserror (rc); + if (nbytes < 0) + nbytes = 0; goto out; } #elif defined(USE_HIDAPI) @@ -749,10 +751,12 @@ dc_usbhid_write (dc_iostream_t *abstract, const void *data, size_t size, size_t } int rc = libusb_interrupt_transfer (usbhid->handle, usbhid->endpoint_out, (void *) buffer, length, &nbytes, 0); - if (rc != LIBUSB_SUCCESS) { + if (rc != LIBUSB_SUCCESS || nbytes < 0) { ERROR (abstract->context, "Usb write interrupt transfer failed (%s).", libusb_error_name (rc)); status = syserror (rc); + if (nbytes < 0) + nbytes = 0; goto out; } From 9dace57814767c4272a350aae58a533903614a97 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 14 Aug 2020 15:14:05 +0200 Subject: [PATCH 11/11] Fix the OSTC4 firmware upgrade Commit d1b865d192afc9efde337b5cff8a239366f15565 breaks the OSTC4 firmware upgrade because the OSTC4 expects to receive the service init command and the service key all at once, before sending any response. The hwOS firmware still reads the service init command one byte at a time, and sends the echo immediately after each byte. But in the meantime, the hwos firmware has also been optimized. The processing time for an incoming byte is now always faster then the time it takes for the next byte to physically arrive via the serial line between the USB/BT chip and the processor. Thus, even without any buffering, sending all bytes at once should no longer be a problem. This partially reverts commit d1b865d192afc9efde337b5cff8a239366f15565. Reported-by: Anton Lundin Suggested-by: Ralph Lembcke --- src/hw_ostc3.c | 44 +++++++++++++++----------------------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 3e01f88..c669ea2 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -468,39 +468,25 @@ hw_ostc3_device_init_service (hw_ostc3_device_t *device) const unsigned char command[] = {S_INIT, 0xAB, 0xCD, 0xEF}; unsigned char answer[5] = {0}; - for (size_t i = 0; i < 4; ++i) { - // Send the command. - status = hw_ostc3_write (device, NULL, command + i, 1); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to send the command."); - return status; - } - - // Read the answer. - status = hw_ostc3_read (device, NULL, answer + i, 1); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to receive the answer."); - return status; - } - - // Verify the answer. - const unsigned char expected = (i == 0 ? 0x4B : command[i]); - if (answer[i] != expected) { - ERROR (abstract->context, "Unexpected answer byte."); - return DC_STATUS_PROTOCOL; - } - } - - // Read the ready byte. - status = hw_ostc3_read (device, NULL, answer + 4, 1); + // Send the command and service key. + status = hw_ostc3_write (device, NULL, command, sizeof (command)); if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to receive the ready byte."); + ERROR (abstract->context, "Failed to send the command."); return status; } - // Verify the ready byte. - if (answer[4] != S_READY) { - ERROR (abstract->context, "Unexpected ready byte."); + // Read the response. + status = hw_ostc3_read (device, NULL, answer, sizeof (answer)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the answer."); + return status; + } + + // Verify the response to service mode. + if (answer[0] != 0x4B || answer[1] != 0xAB || + answer[2] != 0xCD || answer[3] != 0xEF || + answer[4] != S_READY) { + ERROR (abstract->context, "Failed to verify the answer."); return DC_STATUS_PROTOCOL; }