Add support for the Mares Sirius (and compatible models)

The Mares Sirius uses the same communication protocol as the Genius,
except for the fact that it uses a newer BLE version which supports
larger data packets. The actual MTU is likely negotiated because we see
different sizes like 244 and 182 bytes. We don't have access to this MTU
size because not all BLE implementations can expose this information.

Unfortunately not only the BLE packet size is variable, but also the
size of the higher level data frames (used for downloading the content
of the objects) is no longer fixed. The frame size appears to adapt to
the BLE MTU size. This is most likely done to reduce the overhead and
maximize the throughput.

Although each frame ends with an OxEA byte, we can't rely on this
knowledge to detect the end of the frame. The END byte is not escaped in
the payload, and thus can also appear anywhere in the frame. As a
workaround, we rely on the fact that each frame appears to be send as a
single BLE packet. The only exception is the ACK byte, which gets send
as a separate BLE packet if the command requires parameter data.

Compatible models: Quad Ci, Puck 4 and Puck Air 2
This commit is contained in:
Jef Driesen 2023-07-31 19:39:49 +02:00
parent f20d9cb972
commit 1663997111
5 changed files with 177 additions and 20 deletions

View File

@ -307,6 +307,10 @@ static const dc_descriptor_t g_descriptors[] = {
{"Mares", "Smart Air", DC_FAMILY_MARES_ICONHD , 0x24, 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}, {"Mares", "Quad", DC_FAMILY_MARES_ICONHD , 0x29, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_mares},
{"Mares", "Horizon", DC_FAMILY_MARES_ICONHD , 0x2C, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Horizon", DC_FAMILY_MARES_ICONHD , 0x2C, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Puck Air 2", DC_FAMILY_MARES_ICONHD , 0x2D, DC_TRANSPORT_BLE, dc_filter_mares},
{"Mares", "Sirius", DC_FAMILY_MARES_ICONHD , 0x2F, DC_TRANSPORT_BLE, dc_filter_mares},
{"Mares", "Quad Ci", DC_FAMILY_MARES_ICONHD , 0x31, DC_TRANSPORT_BLE, dc_filter_mares},
{"Mares", "Puck 4", DC_FAMILY_MARES_ICONHD , 0x35, 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},
@ -706,6 +710,9 @@ dc_filter_mares (dc_descriptor_t *descriptor, dc_transport_t transport, const vo
static const char * const bluetooth[] = { static const char * const bluetooth[] = {
"Mares bluelink pro", "Mares bluelink pro",
"Mares Genius", "Mares Genius",
"Sirius",
"Quad Ci",
"Puck4",
}; };
if (transport == DC_TRANSPORT_BLE) { if (transport == DC_TRANSPORT_BLE) {

View File

@ -176,7 +176,7 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr
rc = mares_darwin_device_open (&device, context, iostream, dc_descriptor_get_model (descriptor)); rc = mares_darwin_device_open (&device, context, iostream, dc_descriptor_get_model (descriptor));
break; break;
case DC_FAMILY_MARES_ICONHD: case DC_FAMILY_MARES_ICONHD:
rc = mares_iconhd_device_open (&device, context, iostream); rc = mares_iconhd_device_open (&device, context, iostream, dc_descriptor_get_model (descriptor));
break; break;
case DC_FAMILY_HW_OSTC: case DC_FAMILY_HW_OSTC:
rc = hw_ostc_device_open (&device, context, iostream); rc = hw_ostc_device_open (&device, context, iostream);

View File

@ -48,6 +48,10 @@
#define SMARTAIR 0x24 #define SMARTAIR 0x24
#define QUAD 0x29 #define QUAD 0x29
#define HORIZON 0x2C #define HORIZON 0x2C
#define PUCKAIR2 0x2D
#define SIRIUS 0x2F
#define QUADCI 0x31
#define PUCK4 0x35
#define ISSMART(model) ( \ #define ISSMART(model) ( \
(model) == SMART || \ (model) == SMART || \
@ -56,10 +60,25 @@
#define ISGENIUS(model) ( \ #define ISGENIUS(model) ( \
(model) == GENIUS || \ (model) == GENIUS || \
(model) == HORIZON) (model) == HORIZON || \
(model) == PUCKAIR2 || \
(model) == SIRIUS || \
(model) == QUADCI || \
(model) == PUCK4)
#define ISSIRIUS(model) ( \
(model) == PUCKAIR2 || \
(model) == SIRIUS || \
(model) == QUADCI || \
(model) == PUCK4)
#define MAXRETRIES 4 #define MAXRETRIES 4
#define MAXPACKET 244
#define FIXED 0
#define VARIABLE 1
#define ACK 0xAA #define ACK 0xAA
#define END 0xEA #define END 0xEA
#define XOR 0xA5 #define XOR 0xA5
@ -104,6 +123,7 @@ 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 int ble;
} 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);
@ -171,6 +191,10 @@ mares_iconhd_get_model (mares_iconhd_device_t *device)
{"Smart Air", SMARTAIR}, {"Smart Air", SMARTAIR},
{"Quad", QUAD}, {"Quad", QUAD},
{"Horizon", HORIZON}, {"Horizon", HORIZON},
{"Puck Air 2", PUCKAIR2},
{"Sirius", SIRIUS},
{"Quad Ci", QUADCI},
{"Puck4", PUCK4},
}; };
// Check the product name in the version packet against the list // Check the product name in the version packet against the list
@ -187,10 +211,11 @@ mares_iconhd_get_model (mares_iconhd_device_t *device)
} }
static dc_status_t static dc_status_t
mares_iconhd_packet (mares_iconhd_device_t *device, mares_iconhd_packet_fixed (mares_iconhd_device_t *device,
unsigned char cmd, unsigned char cmd,
const unsigned char data[], unsigned int size, const unsigned char data[], unsigned int size,
unsigned char answer[], unsigned int asize) unsigned char answer[], unsigned int asize,
unsigned int *actual)
{ {
dc_status_t status = DC_STATUS_SUCCESS; dc_status_t status = DC_STATUS_SUCCESS;
dc_device_t *abstract = (dc_device_t *) device; dc_device_t *abstract = (dc_device_t *) device;
@ -262,15 +287,120 @@ mares_iconhd_packet (mares_iconhd_device_t *device,
return DC_STATUS_PROTOCOL; return DC_STATUS_PROTOCOL;
} }
if (actual) {
*actual = asize;
}
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }
static dc_status_t static dc_status_t
mares_iconhd_transfer (mares_iconhd_device_t *device, unsigned char cmd, const unsigned char data[], unsigned int size, unsigned char answer[], unsigned int asize) mares_iconhd_packet_variable (mares_iconhd_device_t *device,
unsigned char cmd,
const unsigned char data[], unsigned int size,
unsigned char answer[], unsigned int asize,
unsigned int *actual)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_device_t *abstract = (dc_device_t *) device;
unsigned char packet[MAXPACKET] = {0};
size_t length = 0;
if (device_is_cancelled (abstract))
return DC_STATUS_CANCELLED;
// Send the command header to the dive computer.
const unsigned char command[2] = {
cmd, cmd ^ XOR,
};
status = dc_iostream_write (device->iostream, command, sizeof(command), NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to send the command header.");
return status;
}
// Read either the entire data packet (if there is no command data to send),
// or only the header byte (if there is also command data to send).
status = dc_iostream_read (device->iostream, packet, sizeof (packet), &length);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to receive the packet header.");
return status;
}
if (size) {
// Send the command payload to the dive computer.
status = dc_iostream_write (device->iostream, data, size, NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to send the command data.");
return status;
}
// Read the data packet.
size_t len = 0;
status = dc_iostream_read (device->iostream, packet + length, sizeof (packet) - length, &len);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to receive the packet data.");
return status;
}
length += len;
}
if (length < 2 || length - 2 > asize) {
ERROR (abstract->context, "Unexpected packet length (" DC_PRINTF_SIZE ").", length);
return DC_STATUS_PROTOCOL;
}
// Verify the header byte.
if (packet[0] != ACK) {
ERROR (abstract->context, "Unexpected packet header byte (%02x).", packet[0]);
return DC_STATUS_PROTOCOL;
}
// Verify the trailer byte.
if (packet[length - 1] != END) {
ERROR (abstract->context, "Unexpected packet trailer byte (%02x).", packet[length - 1]);
return DC_STATUS_PROTOCOL;
}
if (actual == NULL) {
// Verify the actual length.
if (length - 2 != asize) {
ERROR (abstract->context, "Unexpected packet length (" DC_PRINTF_SIZE ").", length - 2);
return DC_STATUS_PROTOCOL;
}
} else {
// Return the actual length.
*actual = length - 2;
}
memcpy (answer, packet + 1, length - 2);
return DC_STATUS_SUCCESS;
}
static dc_status_t
mares_iconhd_packet (mares_iconhd_device_t *device,
unsigned char cmd,
const unsigned char data[], unsigned int size,
unsigned char answer[], unsigned int asize,
unsigned int *actual)
{
dc_transport_t transport = dc_iostream_get_transport (device->iostream);
if (transport == DC_TRANSPORT_BLE && device->ble == VARIABLE) {
return mares_iconhd_packet_variable (device, cmd, data, size, answer, asize, actual);
} else {
return mares_iconhd_packet_fixed (device, cmd, data, size, answer, asize, actual);
}
}
static dc_status_t
mares_iconhd_transfer (mares_iconhd_device_t *device, unsigned char cmd, const unsigned char data[], unsigned int size, unsigned char answer[], unsigned int asize, unsigned int *actual)
{ {
unsigned int nretries = 0; unsigned int nretries = 0;
dc_status_t rc = DC_STATUS_SUCCESS; dc_status_t rc = DC_STATUS_SUCCESS;
while ((rc = mares_iconhd_packet (device, cmd, data, size, answer, asize)) != DC_STATUS_SUCCESS) { while ((rc = mares_iconhd_packet (device, cmd, data, size, answer, asize, actual)) != DC_STATUS_SUCCESS) {
// Automatically discard a corrupted packet, // Automatically discard a corrupted packet,
// and request a new one. // and request a new one.
if (rc != DC_STATUS_PROTOCOL && rc != DC_STATUS_TIMEOUT) if (rc != DC_STATUS_PROTOCOL && rc != DC_STATUS_TIMEOUT)
@ -294,7 +424,9 @@ mares_iconhd_read_object(mares_iconhd_device_t *device, dc_event_progress_t *pro
dc_status_t status = DC_STATUS_SUCCESS; dc_status_t status = DC_STATUS_SUCCESS;
dc_device_t *abstract = (dc_device_t *) device; dc_device_t *abstract = (dc_device_t *) device;
dc_transport_t transport = dc_iostream_get_transport (device->iostream); dc_transport_t transport = dc_iostream_get_transport (device->iostream);
const unsigned int maxpacket = (transport == DC_TRANSPORT_BLE) ? 124 : 504; const unsigned int maxpacket = (transport == DC_TRANSPORT_BLE) ?
(device->ble == VARIABLE ? MAXPACKET - 3 : 124) :
504;
// Update and emit a progress event. // Update and emit a progress event.
unsigned int initial = 0; unsigned int initial = 0;
@ -312,7 +444,7 @@ mares_iconhd_read_object(mares_iconhd_device_t *device, dc_event_progress_t *pro
subindex & 0xFF subindex & 0xFF
}; };
memset (cmd_init + 6, 0x00, sizeof(cmd_init) - 6); memset (cmd_init + 6, 0x00, sizeof(cmd_init) - 6);
status = mares_iconhd_transfer (device, CMD_OBJ_INIT, cmd_init, sizeof (cmd_init), rsp_init, sizeof (rsp_init)); status = mares_iconhd_transfer (device, CMD_OBJ_INIT, cmd_init, sizeof (cmd_init), rsp_init, sizeof (rsp_init), NULL);
if (status != DC_STATUS_SUCCESS) { if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to transfer the init packet."); ERROR (abstract->context, "Failed to transfer the init packet.");
return status; return status;
@ -366,13 +498,21 @@ mares_iconhd_read_object(mares_iconhd_device_t *device, dc_event_progress_t *pro
} }
// Transfer the segment packet. // Transfer the segment packet.
unsigned int length = 0;
unsigned char rsp_segment[1 + 504]; unsigned char rsp_segment[1 + 504];
status = mares_iconhd_transfer (device, cmd, NULL, 0, rsp_segment, len + 1); status = mares_iconhd_transfer (device, cmd, NULL, 0, rsp_segment, len + 1, &length);
if (status != DC_STATUS_SUCCESS) { if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to transfer the segment packet."); ERROR (abstract->context, "Failed to transfer the segment packet.");
return status; return status;
} }
if (length < 1) {
ERROR (abstract->context, "Unexpected packet length (%u).", length);
return DC_STATUS_PROTOCOL;
}
length--;
// Verify the packet header. // Verify the packet header.
if ((rsp_segment[0] & 0xF0) >> 4 != toggle) { if ((rsp_segment[0] & 0xF0) >> 4 != toggle) {
ERROR (abstract->context, "Unexpected packet header (%02x).", rsp_segment[0]); ERROR (abstract->context, "Unexpected packet header (%02x).", rsp_segment[0]);
@ -380,12 +520,12 @@ mares_iconhd_read_object(mares_iconhd_device_t *device, dc_event_progress_t *pro
} }
// Append the payload to the output buffer. // Append the payload to the output buffer.
if (!dc_buffer_append (buffer, rsp_segment + 1, len)) { if (!dc_buffer_append (buffer, rsp_segment + 1, length)) {
ERROR (abstract->context, "Insufficient buffer space available."); ERROR (abstract->context, "Insufficient buffer space available.");
return DC_STATUS_NOMEMORY; return DC_STATUS_NOMEMORY;
} }
nbytes += len; nbytes += length;
npackets++; npackets++;
// Update and emit the progress events. // Update and emit the progress events.
@ -399,7 +539,7 @@ mares_iconhd_read_object(mares_iconhd_device_t *device, dc_event_progress_t *pro
} }
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, unsigned int model)
{ {
dc_status_t status = DC_STATUS_SUCCESS; dc_status_t status = DC_STATUS_SUCCESS;
mares_iconhd_device_t *device = NULL; mares_iconhd_device_t *device = NULL;
@ -422,9 +562,10 @@ 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;
device->ble = ISSIRIUS(model) ? VARIABLE : FIXED;
// Create the packet stream. // Create the packet stream.
if (transport == DC_TRANSPORT_BLE) { if (transport == DC_TRANSPORT_BLE && device->ble == FIXED) {
status = dc_packet_open (&device->iostream, context, iostream, 244, 20); status = dc_packet_open (&device->iostream, context, iostream, 244, 20);
if (status != DC_STATUS_SUCCESS) { if (status != DC_STATUS_SUCCESS) {
ERROR (context, "Failed to create the packet stream."); ERROR (context, "Failed to create the packet stream.");
@ -467,7 +608,7 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_
// Send the version command. // Send the version command.
status = mares_iconhd_transfer (device, CMD_VERSION, NULL, 0, status = mares_iconhd_transfer (device, CMD_VERSION, NULL, 0,
device->version, sizeof (device->version)); device->version, sizeof (device->version), NULL);
if (status != DC_STATUS_SUCCESS) { if (status != DC_STATUS_SUCCESS) {
goto error_free_iostream; goto error_free_iostream;
} }
@ -479,7 +620,7 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_
unsigned int memsize = 0; unsigned int memsize = 0;
if (device->model == QUAD) { if (device->model == QUAD) {
unsigned char rsp_flash[4] = {0}; unsigned char rsp_flash[4] = {0};
status = mares_iconhd_transfer (device, CMD_FLASHSIZE, NULL, 0, rsp_flash, sizeof (rsp_flash)); status = mares_iconhd_transfer (device, CMD_FLASHSIZE, NULL, 0, rsp_flash, sizeof (rsp_flash), NULL);
if (status != DC_STATUS_SUCCESS) { if (status != DC_STATUS_SUCCESS) {
WARNING (context, "Failed to read the flash memory size."); WARNING (context, "Failed to read the flash memory size.");
} else { } else {
@ -538,7 +679,7 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_
error_free_iostream: error_free_iostream:
if (transport == DC_TRANSPORT_BLE) { if (transport == DC_TRANSPORT_BLE && device->ble == FIXED) {
dc_iostream_close (device->iostream); dc_iostream_close (device->iostream);
} }
error_free: error_free:
@ -551,9 +692,10 @@ static dc_status_t
mares_iconhd_device_close (dc_device_t *abstract) mares_iconhd_device_close (dc_device_t *abstract)
{ {
mares_iconhd_device_t *device = (mares_iconhd_device_t *) abstract; mares_iconhd_device_t *device = (mares_iconhd_device_t *) abstract;
dc_transport_t transport = dc_iostream_get_transport (device->iostream);
// Close the packet stream. // Close the packet stream.
if (dc_iostream_get_transport (device->iostream) == DC_TRANSPORT_BLE) { if (transport == DC_TRANSPORT_BLE && device->ble == FIXED) {
return dc_iostream_close (device->iostream); return dc_iostream_close (device->iostream);
} }
@ -601,7 +743,7 @@ mares_iconhd_device_read (dc_device_t *abstract, unsigned int address, unsigned
(len >> 8) & 0xFF, (len >> 8) & 0xFF,
(len >> 16) & 0xFF, (len >> 16) & 0xFF,
(len >> 24) & 0xFF}; (len >> 24) & 0xFF};
rc = mares_iconhd_transfer (device, CMD_READ, command, sizeof (command), data, len); rc = mares_iconhd_transfer (device, CMD_READ, command, sizeof (command), data, len, NULL);
if (rc != DC_STATUS_SUCCESS) if (rc != DC_STATUS_SUCCESS)
return rc; return rc;

View File

@ -32,7 +32,7 @@ extern "C" {
#endif /* __cplusplus */ #endif /* __cplusplus */
dc_status_t dc_status_t
mares_iconhd_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); mares_iconhd_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream, unsigned int model);
dc_status_t dc_status_t
mares_iconhd_parser_create (dc_parser_t **parser, dc_context_t *context, const unsigned char data[], size_t size, unsigned int model); mares_iconhd_parser_create (dc_parser_t **parser, dc_context_t *context, const unsigned char data[], size_t size, unsigned int model);

View File

@ -45,6 +45,10 @@
#define QUADAIR 0x23 #define QUADAIR 0x23
#define SMARTAIR 0x24 #define SMARTAIR 0x24
#define HORIZON 0x2C #define HORIZON 0x2C
#define PUCKAIR2 0x2D
#define SIRIUS 0x2F
#define QUADCI 0x31
#define PUCK4 0x35
#define ISSMART(model) ( \ #define ISSMART(model) ( \
(model) == SMART || \ (model) == SMART || \
@ -53,7 +57,11 @@
#define ISGENIUS(model) ( \ #define ISGENIUS(model) ( \
(model) == GENIUS || \ (model) == GENIUS || \
(model) == HORIZON) (model) == HORIZON || \
(model) == PUCKAIR2 || \
(model) == SIRIUS || \
(model) == QUADCI || \
(model) == PUCK4)
#define NGASMIXES_ICONHD 3 #define NGASMIXES_ICONHD 3
#define NGASMIXES_GENIUS 5 #define NGASMIXES_GENIUS 5