From 16639971111e35fd636f4cd448fbd28fb5c0057d Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 31 Jul 2023 19:39:49 +0200 Subject: [PATCH] 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 --- src/descriptor.c | 7 ++ src/device.c | 2 +- src/mares_iconhd.c | 176 ++++++++++++++++++++++++++++++++++---- src/mares_iconhd.h | 2 +- src/mares_iconhd_parser.c | 10 ++- 5 files changed, 177 insertions(+), 20 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index 096c8ee..83fd3a9 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -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", "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", "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", "OSTC", DC_FAMILY_HW_OSTC, 0, 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[] = { "Mares bluelink pro", "Mares Genius", + "Sirius", + "Quad Ci", + "Puck4", }; if (transport == DC_TRANSPORT_BLE) { diff --git a/src/device.c b/src/device.c index ff6a198..a7e60b7 100644 --- a/src/device.c +++ b/src/device.c @@ -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)); break; 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; case DC_FAMILY_HW_OSTC: rc = hw_ostc_device_open (&device, context, iostream); diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index db6d20b..c968685 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -48,6 +48,10 @@ #define SMARTAIR 0x24 #define QUAD 0x29 #define HORIZON 0x2C +#define PUCKAIR2 0x2D +#define SIRIUS 0x2F +#define QUADCI 0x31 +#define PUCK4 0x35 #define ISSMART(model) ( \ (model) == SMART || \ @@ -56,10 +60,25 @@ #define ISGENIUS(model) ( \ (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 MAXPACKET 244 + +#define FIXED 0 +#define VARIABLE 1 + #define ACK 0xAA #define END 0xEA #define XOR 0xA5 @@ -104,6 +123,7 @@ typedef struct mares_iconhd_device_t { unsigned char version[140]; unsigned int model; unsigned int packetsize; + unsigned int ble; } mares_iconhd_device_t; 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}, {"Quad", QUAD}, {"Horizon", HORIZON}, + {"Puck Air 2", PUCKAIR2}, + {"Sirius", SIRIUS}, + {"Quad Ci", QUADCI}, + {"Puck4", PUCK4}, }; // 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 -mares_iconhd_packet (mares_iconhd_device_t *device, +mares_iconhd_packet_fixed (mares_iconhd_device_t *device, unsigned char cmd, 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_device_t *abstract = (dc_device_t *) device; @@ -262,15 +287,120 @@ mares_iconhd_packet (mares_iconhd_device_t *device, return DC_STATUS_PROTOCOL; } + if (actual) { + *actual = asize; + } + return DC_STATUS_SUCCESS; } 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; 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, // and request a new one. 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_device_t *abstract = (dc_device_t *) device; 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. unsigned int initial = 0; @@ -312,7 +444,7 @@ mares_iconhd_read_object(mares_iconhd_device_t *device, dc_event_progress_t *pro subindex & 0xFF }; 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) { ERROR (abstract->context, "Failed to transfer the init packet."); return status; @@ -366,13 +498,21 @@ mares_iconhd_read_object(mares_iconhd_device_t *device, dc_event_progress_t *pro } // Transfer the segment packet. + unsigned int length = 0; 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) { ERROR (abstract->context, "Failed to transfer the segment packet."); return status; } + if (length < 1) { + ERROR (abstract->context, "Unexpected packet length (%u).", length); + return DC_STATUS_PROTOCOL; + } + + length--; + // Verify the packet header. if ((rsp_segment[0] & 0xF0) >> 4 != toggle) { 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. - 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."); return DC_STATUS_NOMEMORY; } - nbytes += len; + nbytes += length; npackets++; // 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 -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; 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)); device->model = 0; device->packetsize = 0; + device->ble = ISSIRIUS(model) ? VARIABLE : FIXED; // 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); if (status != DC_STATUS_SUCCESS) { 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. 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) { 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; if (device->model == QUAD) { 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) { WARNING (context, "Failed to read the flash memory size."); } else { @@ -538,7 +679,7 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_ error_free_iostream: - if (transport == DC_TRANSPORT_BLE) { + if (transport == DC_TRANSPORT_BLE && device->ble == FIXED) { dc_iostream_close (device->iostream); } error_free: @@ -551,9 +692,10 @@ static dc_status_t mares_iconhd_device_close (dc_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. - if (dc_iostream_get_transport (device->iostream) == DC_TRANSPORT_BLE) { + if (transport == DC_TRANSPORT_BLE && device->ble == FIXED) { 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 >> 16) & 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) return rc; diff --git a/src/mares_iconhd.h b/src/mares_iconhd.h index 1b00c54..a6cb6a8 100644 --- a/src/mares_iconhd.h +++ b/src/mares_iconhd.h @@ -32,7 +32,7 @@ extern "C" { #endif /* __cplusplus */ 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 mares_iconhd_parser_create (dc_parser_t **parser, dc_context_t *context, const unsigned char data[], size_t size, unsigned int model); diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index 223d7f8..855008a 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -45,6 +45,10 @@ #define QUADAIR 0x23 #define SMARTAIR 0x24 #define HORIZON 0x2C +#define PUCKAIR2 0x2D +#define SIRIUS 0x2F +#define QUADCI 0x31 +#define PUCK4 0x35 #define ISSMART(model) ( \ (model) == SMART || \ @@ -53,7 +57,11 @@ #define ISGENIUS(model) ( \ (model) == GENIUS || \ - (model) == HORIZON) + (model) == HORIZON || \ + (model) == PUCKAIR2 || \ + (model) == SIRIUS || \ + (model) == QUADCI || \ + (model) == PUCK4) #define NGASMIXES_ICONHD 3 #define NGASMIXES_GENIUS 5