Merge git://github.com/libdivecomputer/libdivecomputer into Subsurface-NG

Merge upstream updates from Jef:

 - better Mares Bluelink Pro downloading

 - Suunto DX CCR and gas mix fixes

* 'master' of git://github.com/libdivecomputer/libdivecomputer:
  Add support for the Mares Bluelink Pro interface
  Check the correct vtable pointer
  Report the setpoint data
  Take the CCR diluents into account
  Implement the initial gas mix
  Get the gas mix index directly from the event data
This commit is contained in:
Linus Torvalds 2019-04-15 08:53:33 -07:00
commit c7112237b3
3 changed files with 155 additions and 51 deletions

View File

@ -35,6 +35,7 @@ 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_garmin (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);
@ -242,16 +243,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 | DC_TRANSPORT_BLE, 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 | DC_TRANSPORT_BLE, 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 | DC_TRANSPORT_BLE, NULL},
{"Mares", "Smart Air", DC_FAMILY_MARES_ICONHD , 0x24, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
{"Mares", "Quad", DC_FAMILY_MARES_ICONHD , 0x29, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, 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},
@ -518,6 +519,19 @@ static int dc_filter_garmin (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,7 +74,9 @@ typedef struct mares_iconhd_device_t {
unsigned char version[140];
unsigned int model;
unsigned int packetsize;
unsigned int splitcommand;
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);
@ -147,23 +151,92 @@ 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)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_device_t *abstract = (dc_device_t *) device;
unsigned int split_csize;
assert (csize >= 2);
if (device_is_cancelled (abstract))
return DC_STATUS_CANCELLED;
split_csize = device->splitcommand ? 2 : csize;
// Send the command header to the dive computer.
status = dc_iostream_write (device->iostream, command, split_csize, NULL);
status = mares_iconhd_write (device, command, 2);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to send the command.");
return status;
@ -171,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;
@ -183,9 +256,9 @@ mares_iconhd_transfer (mares_iconhd_device_t *device,
return DC_STATUS_PROTOCOL;
}
// Send any remaining command payload to the dive computer.
if (csize > split_csize) {
status = dc_iostream_write (device->iostream, command + split_csize, csize - split_csize, NULL);
// Send the command payload to the dive computer.
if (csize > 2) {
status = mares_iconhd_write (device, command + 2, csize - 2);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to send the command.");
return status;
@ -193,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;
@ -201,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;
@ -216,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)
@ -240,15 +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;
/*
* At least the Mares Matrix needs the command to be split into
* base and argument, with a wait for the ACK byte in between.
*
* See commit 59bfb0f3189b ("Add support for the Mares Matrix")
* for details.
*/
device->splitcommand = 1;
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);
@ -323,26 +411,6 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_
break;
}
if (dc_iostream_get_transport(device->iostream) == DC_TRANSPORT_BLE) {
/*
* Don't ask for larger amounts of data with the BLE
* transport - it will fail. I suspect there is a buffer
* overflow in the BlueLink Pro dongle when bluetooth is
* slower than the serial protocol that the dongle talks to
* the dive computer.
*/
if (device->packetsize > 128)
device->packetsize = 128;
/*
* With BLE, don't wait for ACK before sending the arguments
* to a command.
*
* There is some timing issue that makes that take too long
* and causes the command to be aborted.
*/
device->splitcommand = 0;
}
*out = (dc_device_t *) device;
return DC_STATUS_SUCCESS;

View File

@ -79,6 +79,7 @@ struct suunto_d9_parser_t {
unsigned int id;
unsigned int mode;
unsigned int ngasmixes;
unsigned int nccr;
unsigned int oxygen[NGASMIXES];
unsigned int helium[NGASMIXES];
unsigned int gasmix;
@ -111,7 +112,7 @@ static unsigned int
suunto_d9_parser_find_gasmix (suunto_d9_parser_t *parser, unsigned int o2, unsigned int he)
{
// Find the gasmix in the list.
unsigned int i = 0;
unsigned int i = parser->nccr;
while (i < parser->ngasmixes) {
if (o2 == parser->oxygen[i] && he == parser->helium[i])
break;
@ -138,6 +139,7 @@ suunto_d9_parser_cache (suunto_d9_parser_t *parser)
unsigned int gasmode_offset = 0x19;
unsigned int gasmix_offset = 0x21;
unsigned int gasmix_count = 3;
unsigned int ccr_count = 0;
if (parser->model == HELO2) {
gasmode_offset = 0x1F;
gasmix_offset = 0x54;
@ -171,6 +173,7 @@ suunto_d9_parser_cache (suunto_d9_parser_t *parser)
else
gasmix_offset = 0xC1;
gasmix_count = 11;
ccr_count = 3;
}
// Offset to the configuration data.
@ -191,12 +194,15 @@ suunto_d9_parser_cache (suunto_d9_parser_t *parser)
parser->gasmix = 0;
if (parser->mode == GAUGE || parser->mode == FREEDIVE) {
parser->ngasmixes = 0;
parser->nccr = 0;
} else if (parser->mode == AIR) {
parser->oxygen[0] = 21;
parser->helium[0] = 0;
parser->ngasmixes = 1;
parser->nccr = 0;
} else {
parser->ngasmixes = 0;
parser->nccr = ccr_count;
for (unsigned int i = 0; i < gasmix_count; ++i) {
if (parser->model == HELO2 || parser->model == D4i ||
parser->model == D6i || parser->model == D9tx ||
@ -225,6 +231,11 @@ suunto_d9_parser_cache (suunto_d9_parser_t *parser)
} else {
parser->gasmix = data[0x28];
}
} else if (parser->model == DX) {
parser->gasmix = data[0x31] & 0x7F;
if ((data[0x31] & 0x80) == 0) {
parser->gasmix += parser->nccr;
}
}
}
parser->config = config;
@ -256,6 +267,7 @@ suunto_d9_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int
parser->id = 0;
parser->mode = AIR;
parser->ngasmixes = 0;
parser->nccr = 0;
for (unsigned int i = 0; i < NGASMIXES; ++i) {
parser->oxygen[i] = 0;
parser->helium[i] = 0;
@ -279,6 +291,7 @@ suunto_d9_parser_set_data (dc_parser_t *abstract, const unsigned char *data, uns
parser->id = 0;
parser->mode = AIR;
parser->ngasmixes = 0;
parser->nccr = 0;
for (unsigned int i = 0; i < NGASMIXES; ++i) {
parser->oxygen[i] = 0;
parser->helium[i] = 0;
@ -560,7 +573,7 @@ suunto_d9_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t ca
unsigned int event = data[offset++];
unsigned int seconds, type, unknown, heading;
unsigned int current, next;
unsigned int he, o2, idx;
unsigned int he, o2, ppo2, idx;
unsigned int length;
sample.event.type = SAMPLE_EVENT_NONE;
@ -747,22 +760,31 @@ suunto_d9_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t ca
ERROR (abstract->context, "Buffer overflow detected!");
return DC_STATUS_DATAFORMAT;
}
unknown = data[offset + 0];
type = data[offset + 0];
he = data[offset + 1];
o2 = data[offset + 2];
if (parser->model == DX || parser->model == VYPERNOVO ||
(parser->model == D6i && parser->id == ID_D6I_V2)) {
ppo2 = data[offset + 3];
seconds = data[offset + 4];
} else {
ppo2 = 0;
seconds = data[offset + 3];
}
idx = suunto_d9_parser_find_gasmix(parser, o2, he);
idx = type & 0x0F;
if ((type & 0x80) == 0) {
idx += parser->nccr;
}
if (idx >= parser->ngasmixes) {
ERROR (abstract->context, "Invalid gas mix.");
return DC_STATUS_DATAFORMAT;
}
sample.gasmix = idx;
if (callback) callback (DC_SAMPLE_GASMIX, sample, userdata);
if (type & 0x80) {
sample.setpoint = ppo2 / 10.0;
if (callback) callback (DC_SAMPLE_SETPOINT, sample, userdata);
}
offset += length;
break;
default: