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_shearwater (dc_transport_t transport, const void *userdata);
static int dc_filter_hw (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_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); 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", "Airlab", DC_FAMILY_MARES_DARWIN , 1, DC_TRANSPORT_SERIAL, NULL},
/* Mares Icon HD */ /* Mares Icon HD */
{"Mares", "Matrix", DC_FAMILY_MARES_ICONHD , 0x0F, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Matrix", DC_FAMILY_MARES_ICONHD , 0x0F, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Smart", DC_FAMILY_MARES_ICONHD , 0x000010, 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, NULL}, {"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", DC_FAMILY_MARES_ICONHD , 0x14, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Icon HD Net Ready", DC_FAMILY_MARES_ICONHD , 0x15, 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", "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", "Puck 2", DC_FAMILY_MARES_ICONHD , 0x1F, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Quad Air", DC_FAMILY_MARES_ICONHD , 0x23, 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, NULL}, {"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, NULL}, {"Mares", "Quad", DC_FAMILY_MARES_ICONHD , 0x29, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_mares},
/* Heinrichs Weikamp */ /* Heinrichs Weikamp */
{"Heinrichs Weikamp", "OSTC", DC_FAMILY_HW_OSTC, 0, DC_TRANSPORT_SERIAL, NULL}, {"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}, {"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; 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_status_t
dc_descriptor_iterator (dc_iterator_t **out) dc_descriptor_iterator (dc_iterator_t **out)
{ {

View File

@ -45,6 +45,8 @@
#define SMARTAIR 0x24 #define SMARTAIR 0x24
#define QUAD 0x29 #define QUAD 0x29
#define MAXRETRIES 4
#define ACK 0xAA #define ACK 0xAA
#define EOF 0xEA #define EOF 0xEA
@ -72,6 +74,9 @@ typedef struct mares_iconhd_device_t {
unsigned char version[140]; unsigned char version[140];
unsigned int model; unsigned int model;
unsigned int packetsize; unsigned int packetsize;
unsigned char cache[20];
unsigned int available;
unsigned int offset;
} mares_iconhd_device_t; } mares_iconhd_device_t;
static dc_status_t mares_iconhd_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); 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 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, const unsigned char command[], unsigned int csize,
unsigned char answer[], unsigned int asize) unsigned char answer[], unsigned int asize)
{ {
@ -159,7 +236,7 @@ mares_iconhd_transfer (mares_iconhd_device_t *device,
return DC_STATUS_CANCELLED; return DC_STATUS_CANCELLED;
// Send the command header to the dive computer. // 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) { if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to send the command."); ERROR (abstract->context, "Failed to send the command.");
return status; return status;
@ -167,7 +244,7 @@ mares_iconhd_transfer (mares_iconhd_device_t *device,
// Receive the header byte. // Receive the header byte.
unsigned char header[1] = {0}; 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) { if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to receive the answer."); ERROR (abstract->context, "Failed to receive the answer.");
return status; return status;
@ -181,7 +258,7 @@ mares_iconhd_transfer (mares_iconhd_device_t *device,
// Send the command payload to the dive computer. // Send the command payload to the dive computer.
if (csize > 2) { 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) { if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to send the command."); ERROR (abstract->context, "Failed to send the command.");
return status; return status;
@ -189,7 +266,7 @@ mares_iconhd_transfer (mares_iconhd_device_t *device,
} }
// Read the packet. // Read the packet.
status = dc_iostream_read (device->iostream, answer, asize, NULL); status = mares_iconhd_read (device, answer, asize);
if (status != DC_STATUS_SUCCESS) { if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to receive the answer."); ERROR (abstract->context, "Failed to receive the answer.");
return status; return status;
@ -197,7 +274,7 @@ mares_iconhd_transfer (mares_iconhd_device_t *device,
// Receive the trailer byte. // Receive the trailer byte.
unsigned char trailer[1] = {0}; 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) { if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to receive the answer."); ERROR (abstract->context, "Failed to receive the answer.");
return status; return status;
@ -212,6 +289,27 @@ mares_iconhd_transfer (mares_iconhd_device_t *device,
return DC_STATUS_SUCCESS; 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 dc_status_t
mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) 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)); memset (device->version, 0, sizeof (device->version));
device->model = 0; device->model = 0;
device->packetsize = 0; device->packetsize = 0;
memset (device->cache, 0, sizeof (device->cache));
device->available = 0;
device->offset = 0;
// Set the serial communication protocol (115200 8E1). // Set the serial communication protocol (115200 8E1).
status = dc_iostream_configure (device->iostream, 115200, 8, DC_PARITY_EVEN, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE); status = dc_iostream_configure (device->iostream, 115200, 8, DC_PARITY_EVEN, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE);