From 07a33e88c3d3414557e66adc3455488739070a4c Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Fri, 28 Sep 2018 18:52:16 -0700 Subject: [PATCH] First attempt at supporting the Aqualung i770R This works over BLE, although the end result of a dive download is still a bit wonky. There remains some parsing problem that Jef says are likely be common with the Pro Plus too. The serial download doesn't work at all, for unknown reasons. That *should* be the easy part that "just works" and acts the way previous similar dive computers have acted. It's a standard FTDI chip (FT231X) that exposes a serial connection, but there may be some setup required to choose between USB and BLE that we do not know about right now. Signed-off-by: Linus Torvalds --- src/descriptor.c | 1 + src/oceanic_atom2.c | 254 ++++++++++++++++++++++++++++++++++++- src/oceanic_atom2_parser.c | 25 ++-- 3 files changed, 266 insertions(+), 14 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index c8eb62a..a8ee301 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -221,6 +221,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Aqualung", "i550", DC_FAMILY_OCEANIC_ATOM2, 0x4642, DC_TRANSPORT_SERIAL, NULL}, {"Aqualung", "i200", DC_FAMILY_OCEANIC_ATOM2, 0x4646, DC_TRANSPORT_SERIAL, NULL}, {"Aqualung", "i100", DC_FAMILY_OCEANIC_ATOM2, 0x464E, DC_TRANSPORT_SERIAL, NULL}, + {"Aqualung", "i770R", DC_FAMILY_OCEANIC_ATOM2, 0x4651, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL}, /* 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}, diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index 8837e09..0ca6f53 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -35,6 +35,7 @@ #define PROPLUSX 0x4552 #define VTX 0x4557 #define I750TC 0x455A +#define I770R 0x4651 #define MAXRETRIES 2 #define MAXDELAY 16 @@ -56,6 +57,7 @@ typedef struct oceanic_atom2_device_t { oceanic_common_device_t base; dc_iostream_t *iostream; + unsigned int sequence; unsigned int delay; unsigned int bigpage; unsigned char cache[256]; @@ -198,6 +200,19 @@ static const oceanic_common_version_t aeris_a300cs_version[] = { {"AQUAI750 \0\0 2048"}, }; +// Not 100% sure what the pattern is. +// I've seen: +// +// "AQUA770R 1A 0001" +// "AQUA770R 1A 0090" +// +// from the same dive computer. On other ones, it's +// apparently the two middle digits that change, on +// the i770R it might be all of them. +static const oceanic_common_version_t aqualung_i770r_version[] = { + {"AQUA770R \0\0 \0\0\0\0"}, +}; + static const oceanic_common_version_t aqualung_i450t_version[] = { {"AQUAI450 \0\0 2048"}, }; @@ -487,6 +502,21 @@ static const oceanic_common_layout_t aeris_a300cs_layout = { 0, /* pt_mode_serial */ }; +static const oceanic_common_layout_t aqualung_i770r_layout = { + 0x440000, /* memsize */ + 0x40000, /* highmem */ + 0x0000, /* cf_devinfo */ + 0x0040, /* cf_pointers */ + 0x1000, /* rb_logbook_begin */ + 0x10000, /* rb_logbook_end */ + 16, /* rb_logbook_entry_size */ + 0x40000, /* rb_profile_begin */ + 0x440000, /* rb_profile_end */ + 0, /* pt_mode_global */ + 1, /* pt_mode_logbook */ + 0, /* pt_mode_serial */ +}; + static const oceanic_common_layout_t aqualung_i450t_layout = { 0x40000, /* memsize */ 0, /* highmem */ @@ -570,7 +600,7 @@ oceanic_atom2_packet (oceanic_atom2_device_t *device, const unsigned char comman static dc_status_t -oceanic_atom2_transfer (oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, unsigned int crc_size) +oceanic_atom2_serial_transfer (oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, unsigned int crc_size) { // Send the command to the device. If the device responds with an // ACK byte, the command was received successfully and the answer @@ -600,6 +630,193 @@ oceanic_atom2_transfer (oceanic_atom2_device_t *device, const unsigned char comm return DC_STATUS_SUCCESS; } +/* + * The BLE GATT packet size is up to 20 bytes and the format is: + * + * byte 0: <0xCD> + * Seems to always have this value. Don't ask what it means + * byte 1: + * d=0 means "command", d=1 means "reply from dive computer" + * 1 is always set, afaik + * c=0 means "last packet" in sequence, c=1 means "more packets coming" + * sssss is a 5-bit sequence number for packets + * byte 2: + * starts at 0 for the connection, incremented for each command + * byte 3: + * 1-16 bytes of data per packet. + * byte 4..n: + */ +static dc_status_t +oceanic_atom2_ble_write(oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize) +{ + unsigned char buf[20]; + unsigned char cmd_seq = device->sequence; + unsigned char pkt_seq; + + pkt_seq = 0; + while (csize) { + dc_status_t ret; + unsigned char status = 0x40; + unsigned int cpartial = csize; + if (cpartial > 16) { + cpartial = 16; + status |= 0x20; + } + buf[0] = 0xcd; + buf[1] = status | (pkt_seq & 31); + buf[2] = cmd_seq; + buf[3] = cpartial; + memcpy(buf+4, command, cpartial); + command += cpartial; + csize -= cpartial; + ret = dc_iostream_write(device->iostream, buf, 4+cpartial, NULL); + if (ret != DC_STATUS_SUCCESS) + return ret; + pkt_seq++; + } + return DC_STATUS_SUCCESS; +} + +static dc_status_t +oceanic_atom2_ble_read(oceanic_atom2_device_t *device, unsigned char **result_p, unsigned int *size_p) +{ + unsigned char *result = NULL; + unsigned int size = 0, allocated = 0; + unsigned char buf[20]; + unsigned char cmd_seq = device->sequence; + unsigned char pkt_seq; + dc_status_t ret = DC_STATUS_SUCCESS; + + pkt_seq = 0; + for (;;) { + unsigned char status, expect; + size_t transferred = 0; + ret = dc_iostream_read(device->iostream, buf, sizeof(buf), &transferred); + if (ret != DC_STATUS_SUCCESS) + break; + + ret = DC_STATUS_IO; + if (transferred < 5 || transferred > 20) { + ERROR(device->base.base.context, "Odd BLE packet size %zd", transferred); + break; + } + if (buf[0] != 0xcd) + ERROR(device->base.base.context, "Odd first byte (got '%02x', expected 'cd'", buf[0]); + + // Verify status byte + expect = 0xc0; + expect |= (pkt_seq & 31); + status = buf[1]; + if ((status & ~0x20) != expect) + ERROR(device->base.base.context, "Odd status byte (got '%02x', expected '%02x'", buf[1], expect); + + // Verify command sequence byte + expect = cmd_seq; + if (buf[2] != expect) + ERROR(device->base.base.context, "Odd cmd sequence byte (got '%02x', expected '%02x'", buf[2], expect); + + // Verify length byte + expect = buf[3]; + if (expect < 1 || expect > 16) { + ERROR(device->base.base.context, "Odd reply size byte (got %d, expected 1..16", buf[3]); + break; + } + + if (transferred < 4+expect) { + ERROR(device->base.base.context, "Packet too small (got %zd bytes, expected at least %d bytes)", transferred, 4+expect); + break; + } + + if (size + expect > allocated) { + unsigned int newsize = size + expect + 100; + unsigned char *newalloc = realloc(result, newsize); + if (!newalloc) { + ret = DC_STATUS_NOMEMORY; + break; + } + result = newalloc; + allocated = newsize; + } + + memcpy(result + size, buf+4, expect); + size += expect; + pkt_seq++; + + /* More packets? */ + if (status & 0x20) + continue; + + ret = DC_STATUS_SUCCESS; + break; + } + + if (ret != DC_STATUS_SUCCESS) { + free(result); + size = 0; + result = NULL; + } + *result_p = result; + *size_p = size; + return ret; +} + +/* + * Transfer a command and optionally read return data. + * + * NOTE! The NUL byte at the end of a command is a serial transfer thing, + * and we remove it. The correct thing to do would be to add it on the + * serial transfer side instead (or perhaps not send it at all, Jef says + * it may be historical), but right now I've tried to minimize the changes + * that the BLE transfer code made to the code, so instead this tries to + * just skip the extraneous byte. + */ +static dc_status_t +oceanic_atom2_ble_transfer (oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, unsigned int crc_size) +{ + unsigned char buf[20]; + unsigned char cmd_seq = device->sequence; + unsigned char pkt_seq; + dc_status_t ret = DC_STATUS_SUCCESS; + + /* + * The serial commands have a NUL byte at the end. It's bogus. + * It should be added on the serial transfer side, not removed + * here. + */ + if (csize > 1 && csize < 8 && !command[csize-1]) + csize--; + + ret = oceanic_atom2_ble_write(device, command, csize); + if (ret != DC_STATUS_SUCCESS) + return ret; + + pkt_seq = 0; + if (asize) { + unsigned char *buf; + unsigned int size; + ret = oceanic_atom2_ble_read(device, &buf, &size); + if (ret != DC_STATUS_SUCCESS) + return ret; + if (size > asize && buf[0] == ACK) { + memcpy(answer, buf+1, asize); + } else { + ERROR(device->base.base.context, "Result too small: got %d bytes, expected at least %d bytes", size, asize+1); + ret = DC_STATUS_IO; + } + free(buf); + } + + return ret; +} + +static dc_status_t +oceanic_atom2_transfer (oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, unsigned int crc_size) +{ + if (dc_iostream_get_transport(device->iostream) == DC_TRANSPORT_BLE) + return oceanic_atom2_ble_transfer(device, command, csize, answer, asize, crc_size); + + return oceanic_atom2_serial_transfer(device, command, csize, answer, asize, crc_size); +} static dc_status_t oceanic_atom2_quit (oceanic_atom2_device_t *device) @@ -636,15 +853,26 @@ oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream // Set the default values. device->iostream = iostream; device->delay = 0; + device->sequence = 0; device->bigpage = 1; // no big pages device->cached_page = INVALID; device->cached_highmem = INVALID; memset(device->cache, 0, sizeof(device->cache)); // Get the correct baudrate. - unsigned int baudrate = 38400; - if (model == VTX || model == I750TC || model == PROPLUSX) { + unsigned int baudrate; + switch (model) { + case VTX: + case I750TC: + case PROPLUSX: baudrate = 115200; + break; + case I770R: + baudrate = 1000000; + break; + default: + baudrate = 38400; + break; } // Set the serial communication protocol (38400 8N1). @@ -733,6 +961,9 @@ oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream } else if (OCEANIC_COMMON_MATCH (device->base.version, aeris_a300cs_version)) { device->base.layout = &aeris_a300cs_layout; device->bigpage = 16; + } else if (OCEANIC_COMMON_MATCH (device->base.version, aqualung_i770r_version)) { + device->base.layout = &aqualung_i770r_layout; + device->bigpage = 16; } else if (OCEANIC_COMMON_MATCH (device->base.version, aqualung_i450t_version)) { device->base.layout = &aqualung_i450t_layout; } else if (OCEANIC_COMMON_MATCH (device->base.version, oceanic_default_version)) { @@ -793,7 +1024,7 @@ oceanic_atom2_device_keepalive (dc_device_t *abstract) dc_status_t rc = oceanic_atom2_transfer (device, command, sizeof (command), NULL, 0, 0); if (rc != DC_STATUS_SUCCESS) return rc; - + device->sequence++; return DC_STATUS_SUCCESS; } @@ -816,6 +1047,17 @@ oceanic_atom2_device_version (dc_device_t *abstract, unsigned char data[], unsig return rc; memcpy (data, answer, PAGESIZE); + device->sequence++; + + if (dc_iostream_get_transport(device->iostream) == DC_TRANSPORT_BLE) { + /* I have NO IDEA! */ + unsigned char unk[10] = "\xe5\x00\x00\x01\x01\x02\x04\x00\x00\x08"; + rc = oceanic_atom2_ble_write(device, unk, sizeof (unk)); + unsigned char *ret; + unsigned int ret_size; + rc = oceanic_atom2_ble_read(device, &ret, &ret_size); + device->sequence++; + } return DC_STATUS_SUCCESS; } @@ -883,6 +1125,8 @@ oceanic_atom2_device_read (dc_device_t *abstract, unsigned int address, unsigned if (rc != DC_STATUS_SUCCESS) return rc; + device->sequence++; + // Cache the page. memcpy (device->cache, answer, pagesize); device->cached_page = page; @@ -938,6 +1182,8 @@ oceanic_atom2_device_write (dc_device_t *abstract, unsigned int address, const u if (rc != DC_STATUS_SUCCESS) return rc; + device->sequence++; + nbytes += PAGESIZE; address += PAGESIZE; data += PAGESIZE; diff --git a/src/oceanic_atom2_parser.c b/src/oceanic_atom2_parser.c index 308f201..5b822d8 100644 --- a/src/oceanic_atom2_parser.c +++ b/src/oceanic_atom2_parser.c @@ -89,6 +89,7 @@ #define I550 0x4642 #define I200 0x4646 #define I100 0x464E +#define I770R 0x4651 #define NORMAL 0 #define GAUGE 1 @@ -177,7 +178,8 @@ oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, unsigned parser->headersize = 5 * PAGESIZE; parser->footersize = 0; } else if (model == A300CS || model == VTX || - model == I450T || model == I750TC) { + model == I450T || model == I750TC || + model == I770R) { parser->headersize = 5 * PAGESIZE; } else if (model == PROPLUSX) { parser->headersize = 3 * PAGESIZE; @@ -319,6 +321,7 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim case I450T: case I750TC: case PROPLUSX: + case I770R: datetime->year = (p[10]) + 2000; datetime->month = (p[8]); datetime->day = (p[9]); @@ -439,7 +442,7 @@ oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser) he_offset = 0x48; ngasmixes = 6; } else if (parser->model == A300CS || parser->model == VTX || - parser->model == I750TC) { + parser->model == I750TC || parser->model == I770R) { o2_offset = 0x2A; if (data[0x39] & 0x04) { ngasmixes = 1; @@ -545,7 +548,7 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns break; case DC_FIELD_SALINITY: if (parser->model == A300CS || parser->model == VTX || - parser->model == I750TC) { + parser->model == I750TC || parser->model == I770R) { if (data[0x18] & 0x80) { water->type = DC_WATER_FRESH; } else { @@ -648,7 +651,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ unsigned int idx = 0x17; if (parser->model == A300CS || parser->model == VTX || parser->model == I450T || parser->model == I750TC || - parser->model == PROPLUSX) + parser->model == PROPLUSX || parser->model == I770R) idx = 0x1f; switch (data[idx] & 0x03) { case 0: @@ -703,7 +706,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == OC1C || parser->model == OCI || parser->model == TX1 || parser->model == A300CS || parser->model == VTX || parser->model == I450T || - parser->model == I750TC || parser->model == PROPLUSX) { + parser->model == I750TC || parser->model == PROPLUSX || + parser->model == I770R) { samplesize = PAGESIZE; } @@ -732,7 +736,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ if (have_pressure) { unsigned int idx = 2; if (parser->model == A300CS || parser->model == VTX || - parser->model == I750TC) + parser->model == I750TC || parser->model == I770R) idx = 16; pressure = array_uint16_le(data + parser->header + idx); if (pressure == 10000) @@ -785,7 +789,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ tank = 0; pressure = (((data[offset + 7] << 8) + data[offset + 6]) & 0x0FFF); } else if (parser->model == A300CS || parser->model == VTX || - parser->model == I750TC) { + parser->model == I750TC || parser->model == I770R) { // Tank pressure (1 psi) and number (one based index) tank = (data[offset + 1] & 0x03) - 1; pressure = ((data[offset + 7] << 8) + data[offset + 6]) & 0x0FFF; @@ -881,7 +885,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == XPAIR) { temperature = ((data[offset + 7] & 0xF0) >> 4) | ((data[offset + 7] & 0x0C) << 2) | ((data[offset + 5] & 0x0C) << 4); } else if (parser->model == A300CS || parser->model == VTX || - parser->model == I750TC || parser->model == PROPLUSX) { + parser->model == I750TC || parser->model == PROPLUSX || + parser->model == I770R) { temperature = data[offset + 11]; } else { unsigned int sign; @@ -922,7 +927,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ pressure = (((data[offset + 0] & 0x03) << 8) + data[offset + 1]) * 5; else if (parser->model == TX1 || parser->model == A300CS || parser->model == VTX || parser->model == I750TC || - parser->model == PROPLUSX) + parser->model == PROPLUSX || parser->model == I770R) pressure = array_uint16_le (data + offset + 4); else pressure -= data[offset + 1]; @@ -971,7 +976,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ unsigned int decostop = 0, decotime = 0; if (parser->model == A300CS || parser->model == VTX || parser->model == I450T || parser->model == I750TC || - parser->model == PROPLUSX) { + parser->model == PROPLUSX || parser->model == I770R) { decostop = (data[offset + 15] & 0x70) >> 4; decotime = array_uint16_le(data + offset + 6) & 0x03FF; have_deco = 1;