Add support for the Mares Bluelink Pro interface

The main difference with the serial communication is that the BLE
communication uses data packets (with a maximum size of 20 bytes)
instead of a continuous data stream.

Occasionally, the device responds with an empty packet (with just the
ACK and EOF byte) or with a short packet where one or more payload bytes
are missing. The empty packet is most likely caused because the device
didn't receive the second part of the command (with the parameters) in
time. The missing bytes might be caused by a buffer overflow on the
Bluelink Pro, when the data from the dive computer arrives faster over
the serial link than the device can forward it over the slower bluetooth
link to the host system.

One way to address this problem is to lower the packet size from 256 bytes
to only 128 bytes. The main disadvantage is that this also impacts the
download speed considerably. In one particular example, the total
download time increased from about 135 to 210 seconds (which is already
slow compared to the 62 seconds the same download takes over usb)!

An alternative solution is to simply retry the failed command. That way
there is only a performance penalty for the few bad packets, and not for
every single packet. In the above example, only two packets needed a
retransmission.

Note that the iconhd communication protocol uses no checksums. Hence
it's not possible to detect corrupt data packets. Only short packets can
be detected, because they result in a timeout.
This commit is contained in:
Jef Driesen 2018-12-07 14:42:43 +01:00
parent 88a814a616
commit 019a98f80c
2 changed files with 127 additions and 12 deletions

View File

@ -34,6 +34,7 @@ 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 dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item);
@ -241,16 +242,16 @@ static const dc_descriptor_t g_descriptors[] = {
{"Mares", "Airlab", DC_FAMILY_MARES_DARWIN , 1, DC_TRANSPORT_SERIAL, NULL},
/* Mares Icon HD */
{"Mares", "Matrix", DC_FAMILY_MARES_ICONHD , 0x0F, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Smart", DC_FAMILY_MARES_ICONHD , 0x000010, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Smart Apnea", DC_FAMILY_MARES_ICONHD , 0x010010, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Smart", DC_FAMILY_MARES_ICONHD , 0x000010, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_mares},
{"Mares", "Smart Apnea", DC_FAMILY_MARES_ICONHD , 0x010010, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_mares},
{"Mares", "Icon HD", DC_FAMILY_MARES_ICONHD , 0x14, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Icon HD Net Ready", DC_FAMILY_MARES_ICONHD , 0x15, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Puck Pro", DC_FAMILY_MARES_ICONHD , 0x18, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Puck Pro", DC_FAMILY_MARES_ICONHD , 0x18, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_mares},
{"Mares", "Nemo Wide 2", DC_FAMILY_MARES_ICONHD , 0x19, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Puck 2", DC_FAMILY_MARES_ICONHD , 0x1F, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Quad Air", DC_FAMILY_MARES_ICONHD , 0x23, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Smart Air", DC_FAMILY_MARES_ICONHD , 0x24, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Quad", DC_FAMILY_MARES_ICONHD , 0x29, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Quad Air", DC_FAMILY_MARES_ICONHD , 0x23, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_mares},
{"Mares", "Smart Air", DC_FAMILY_MARES_ICONHD , 0x24, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_mares},
{"Mares", "Quad", DC_FAMILY_MARES_ICONHD , 0x29, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_mares},
/* Heinrichs Weikamp */
{"Heinrichs Weikamp", "OSTC", DC_FAMILY_HW_OSTC, 0, DC_TRANSPORT_SERIAL, NULL},
{"Heinrichs Weikamp", "OSTC Mk2", DC_FAMILY_HW_OSTC, 1, DC_TRANSPORT_SERIAL, NULL},
@ -502,6 +503,19 @@ 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 const char *bluetooth[] = {
"Mares bluelink pro",
};
if (transport == DC_TRANSPORT_BLE) {
return dc_filter_internal_name ((const char *) userdata, bluetooth, C_ARRAY_SIZE(bluetooth));
}
return 1;
}
dc_status_t
dc_descriptor_iterator (dc_iterator_t **out)
{

View File

@ -45,6 +45,8 @@
#define SMARTAIR 0x24
#define QUAD 0x29
#define MAXRETRIES 4
#define ACK 0xAA
#define EOF 0xEA
@ -72,6 +74,9 @@ typedef struct mares_iconhd_device_t {
unsigned char version[140];
unsigned int model;
unsigned int packetsize;
unsigned char cache[20];
unsigned int available;
unsigned int offset;
} mares_iconhd_device_t;
static dc_status_t mares_iconhd_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size);
@ -146,7 +151,79 @@ mares_iconhd_get_model (mares_iconhd_device_t *device)
}
static dc_status_t
mares_iconhd_transfer (mares_iconhd_device_t *device,
mares_iconhd_read (mares_iconhd_device_t *device, unsigned char data[], size_t size)
{
dc_status_t rc = DC_STATUS_SUCCESS;
dc_transport_t transport = dc_iostream_get_transport(device->iostream);
size_t nbytes = 0;
while (nbytes < size) {
if (transport == DC_TRANSPORT_BLE) {
if (device->available == 0) {
// Read a packet into the cache.
size_t len = 0;
rc = dc_iostream_read (device->iostream, device->cache, sizeof(device->cache), &len);
if (rc != DC_STATUS_SUCCESS)
return rc;
device->available = len;
device->offset = 0;
}
}
// Set the minimum packet size.
size_t length = (transport == DC_TRANSPORT_BLE) ? device->available : size - nbytes;
// Limit the packet size to the total size.
if (nbytes + length > size)
length = size - nbytes;
if (transport == DC_TRANSPORT_BLE) {
// Copy the data from the cached packet.
memcpy (data + nbytes, device->cache + device->offset, length);
device->available -= length;
device->offset += length;
} else {
// Read the packet.
rc = dc_iostream_read (device->iostream, data + nbytes, length, &length);
if (rc != DC_STATUS_SUCCESS)
return rc;
}
nbytes += length;
}
return rc;
}
static dc_status_t
mares_iconhd_write (mares_iconhd_device_t *device, const unsigned char data[], size_t size)
{
dc_status_t rc = DC_STATUS_SUCCESS;
dc_transport_t transport = dc_iostream_get_transport(device->iostream);
size_t nbytes = 0;
while (nbytes < size) {
// Set the maximum packet size.
size_t length = (transport == DC_TRANSPORT_BLE) ? sizeof(device->cache) : size - nbytes;
// Limit the packet size to the total size.
if (nbytes + length > size)
length = size - nbytes;
// Write the packet.
rc = dc_iostream_write (device->iostream, data + nbytes, length, &length);
if (rc != DC_STATUS_SUCCESS)
return rc;
nbytes += length;
}
return rc;
}
static dc_status_t
mares_iconhd_packet (mares_iconhd_device_t *device,
const unsigned char command[], unsigned int csize,
unsigned char answer[], unsigned int asize)
{
@ -159,7 +236,7 @@ mares_iconhd_transfer (mares_iconhd_device_t *device,
return DC_STATUS_CANCELLED;
// Send the command header to the dive computer.
status = dc_iostream_write (device->iostream, command, 2, NULL);
status = mares_iconhd_write (device, command, 2);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to send the command.");
return status;
@ -167,7 +244,7 @@ mares_iconhd_transfer (mares_iconhd_device_t *device,
// Receive the header byte.
unsigned char header[1] = {0};
status = dc_iostream_read (device->iostream, header, sizeof (header), NULL);
status = mares_iconhd_read (device, header, sizeof (header));
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to receive the answer.");
return status;
@ -181,7 +258,7 @@ mares_iconhd_transfer (mares_iconhd_device_t *device,
// Send the command payload to the dive computer.
if (csize > 2) {
status = dc_iostream_write (device->iostream, command + 2, csize - 2, NULL);
status = mares_iconhd_write (device, command + 2, csize - 2);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to send the command.");
return status;
@ -189,7 +266,7 @@ mares_iconhd_transfer (mares_iconhd_device_t *device,
}
// Read the packet.
status = dc_iostream_read (device->iostream, answer, asize, NULL);
status = mares_iconhd_read (device, answer, asize);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to receive the answer.");
return status;
@ -197,7 +274,7 @@ mares_iconhd_transfer (mares_iconhd_device_t *device,
// Receive the trailer byte.
unsigned char trailer[1] = {0};
status = dc_iostream_read (device->iostream, trailer, sizeof (trailer), NULL);
status = mares_iconhd_read (device, trailer, sizeof (trailer));
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to receive the answer.");
return status;
@ -212,6 +289,27 @@ mares_iconhd_transfer (mares_iconhd_device_t *device,
return DC_STATUS_SUCCESS;
}
static dc_status_t
mares_iconhd_transfer (mares_iconhd_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 = mares_iconhd_packet (device, command, csize, answer, asize)) != DC_STATUS_SUCCESS) {
// Automatically discard a corrupted packet,
// and request a new one.
if (rc != DC_STATUS_PROTOCOL && rc != DC_STATUS_TIMEOUT)
return rc;
// Abort if the maximum number of retries is reached.
if (nretries++ >= MAXRETRIES)
return rc;
// Discard any garbage bytes.
dc_iostream_purge (device->iostream, DC_DIRECTION_INPUT);
}
return DC_STATUS_SUCCESS;
}
dc_status_t
mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream)
@ -236,6 +334,9 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_
memset (device->version, 0, sizeof (device->version));
device->model = 0;
device->packetsize = 0;
memset (device->cache, 0, sizeof (device->cache));
device->available = 0;
device->offset = 0;
// Set the serial communication protocol (115200 8E1).
status = dc_iostream_configure (device->iostream, 115200, 8, DC_PARITY_EVEN, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE);