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 <torvalds@linux-foundation.org>
This commit is contained in:
Linus Torvalds 2018-09-28 18:52:16 -07:00
parent 5be8c17ea1
commit 07a33e88c3
3 changed files with 266 additions and 14 deletions

View File

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

View File

@ -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 1 c s s s s s>
* 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: <cmd seq>
* starts at 0 for the connection, incremented for each command
* byte 3: <length of data>
* 1-16 bytes of data per packet.
* byte 4..n: <data>
*/
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;

View File

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