diff --git a/contrib/android/Android.mk b/contrib/android/Android.mk index 5c1c6f93..19702b81 100644 --- a/contrib/android/Android.mk +++ b/contrib/android/Android.mk @@ -9,6 +9,7 @@ LOCAL_SRC_FILES := \ src/array.c \ src/atomics_cobalt.c \ src/atomics_cobalt_parser.c \ + src/ble.c \ src/bluetooth.c \ src/buffer.c \ src/checksum.c \ diff --git a/contrib/msvc/libdivecomputer.vcxproj b/contrib/msvc/libdivecomputer.vcxproj index 991f1ac8..d9176e7a 100644 --- a/contrib/msvc/libdivecomputer.vcxproj +++ b/contrib/msvc/libdivecomputer.vcxproj @@ -177,6 +177,7 @@ + diff --git a/examples/dctool_timesync.c b/examples/dctool_timesync.c index eeff0bbc..cdd02bc5 100644 --- a/examples/dctool_timesync.c +++ b/examples/dctool_timesync.c @@ -85,10 +85,10 @@ do_timesync (dc_context_t *context, dc_descriptor_t *descriptor, dc_transport_t } // Syncronize the device clock. - message ("Syncronize the device clock.\n"); + message ("Synchronize the device clock.\n"); rc = dc_device_timesync (device, datetime); if (rc != DC_STATUS_SUCCESS) { - ERROR ("Error syncronizing the device clock."); + ERROR ("Error synchronizing the device clock."); goto cleanup; } diff --git a/include/libdivecomputer/ble.h b/include/libdivecomputer/ble.h index a13fe87c..6529747f 100644 --- a/include/libdivecomputer/ble.h +++ b/include/libdivecomputer/ble.h @@ -48,6 +48,59 @@ extern "C" { #define DC_IOCTL_BLE_GET_ACCESSCODE DC_IOCTL_IOR('b', 2, DC_IOCTL_SIZE_VARIABLE) #define DC_IOCTL_BLE_SET_ACCESSCODE DC_IOCTL_IOW('b', 2, DC_IOCTL_SIZE_VARIABLE) +/** + * Perform a BLE characteristic read/write operation. + * + * The UUID of the characteristic must be specified as a #dc_ble_uuid_t + * data structure. If the operation requires additional data as in- or + * output, the buffer must be located immediately after the + * #dc_ble_uuid_t data structure. The size of the ioctl request is the + * total size, including the size of the #dc_ble_uuid_t structure. + */ +#define DC_IOCTL_BLE_CHARACTERISTIC_READ DC_IOCTL_IOR('b', 3, DC_IOCTL_SIZE_VARIABLE) +#define DC_IOCTL_BLE_CHARACTERISTIC_WRITE DC_IOCTL_IOW('b', 3, DC_IOCTL_SIZE_VARIABLE) + +/** + * The minimum number of bytes (including the terminating null byte) for + * formatting a bluetooth UUID as a string. + */ +#define DC_BLE_UUID_SIZE 37 + +/** + * Bluetooth UUID (128 bits). + */ +typedef unsigned char dc_ble_uuid_t[16]; + +/** + * Convert a bluetooth UUID to a string. + * + * The bluetooth UUID is formatted as + * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, where each XX pair is a + * hexadecimal number specifying an octet of the UUID. + * The minimum size for the buffer is #DC_BLE_UUID_SIZE bytes. + * + * @param[in] uuid A bluetooth UUID. + * @param[in] str The memory buffer to store the result. + * @param[in] size The size of the memory buffer. + * @returns The null-terminated string on success, or NULL on failure. + */ +char * +dc_ble_uuid2str (const dc_ble_uuid_t uuid, char *str, size_t size); + +/** + * Convert a string to a bluetooth UUID. + * + * The string is expected to be in the format + * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, where each XX pair is a + * hexadecimal number specifying an octet of the UUID. + * + * @param[in] str A null-terminated string. + * @param[in] uuid The memory buffer to store the result. + * @returns Non-zero on success, or zero on failure. + */ +int +dc_ble_str2uuid (const char *str, dc_ble_uuid_t uuid); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/Makefile.am b/src/Makefile.am index 495d25e5..888e49da 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -88,6 +88,7 @@ libdivecomputer_la_SOURCES = \ irda.c \ usb.c \ usbhid.c \ + ble.c \ bluetooth.c \ custom.c diff --git a/src/ble.c b/src/ble.c new file mode 100644 index 00000000..c8944c64 --- /dev/null +++ b/src/ble.c @@ -0,0 +1,97 @@ +/* + * libdivecomputer + * + * Copyright (C) 2024 Jef Driesen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include + +#include "platform.h" + +char * +dc_ble_uuid2str (const dc_ble_uuid_t uuid, char *str, size_t size) +{ + if (str == NULL || size < DC_BLE_UUID_SIZE) + return NULL; + + int n = dc_platform_snprintf(str, size, + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + uuid[0], uuid[1], uuid[2], uuid[3], + uuid[4], uuid[5], + uuid[6], uuid[7], + uuid[8], uuid[9], + uuid[10], uuid[11], uuid[12], + uuid[13], uuid[14], uuid[15]); + if (n < 0 || (size_t) n >= size) + return NULL; + + return str; +} + +int +dc_ble_str2uuid (const char *str, dc_ble_uuid_t uuid) +{ + dc_ble_uuid_t tmp = {0}; + + if (str == NULL || uuid == NULL) + return 0; + + unsigned int i = 0; + unsigned char c = 0; + while ((c = *str++) != '\0') { + if (c == '-') { + if (i != 8 && i != 12 && i != 16 && i != 20) { + return 0; /* Invalid character! */ + } + continue; + } else if (c >= '0' && c <= '9') { + c -= '0'; + } else if (c >= 'A' && c <= 'F') { + c -= 'A' - 10; + } else if (c >= 'a' && c <= 'f') { + c -= 'a' - 10; + } else { + return 0; /* Invalid character! */ + } + + if ((i & 1) == 0) { + c <<= 4; + } + + if (i >= 2 * sizeof(tmp)) { + return 0; /* Too many characters! */ + } + + tmp[i / 2] |= c; + i++; + } + + if (i != 2 * sizeof(tmp)) { + return 0; /* Not enough characters! */ + } + + memcpy (uuid, tmp, sizeof(tmp)); + + return 1; +} diff --git a/src/cressi_goa.c b/src/cressi_goa.c index 9df10e8d..2b977767 100644 --- a/src/cressi_goa.c +++ b/src/cressi_goa.c @@ -23,6 +23,8 @@ #include // malloc, free #include // assert +#include + #include "cressi_goa.h" #include "context-private.h" #include "device-private.h" @@ -32,9 +34,15 @@ #define ISINSTANCE(device) dc_device_isinstance((device), &cressi_goa_device_vtable) -#define CMD_VERSION 0x00 -#define CMD_LOGBOOK 0x21 -#define CMD_DIVE 0x22 +#define CMD_VERSION 0x00 +#define CMD_SET_TIME 0x13 +#define CMD_EXIT_PCLINK 0x1D +#define CMD_LOGBOOK 0x21 +#define CMD_DIVE 0x22 +#define CMD_LOGBOOK_V4 0x23 + +#define CMD_LOGBOOK_BLE 0x02 +#define CMD_DIVE_BLE 0x03 #define HEADER 0xAA #define TRAILER 0x55 @@ -42,10 +50,8 @@ #define ACK 0x06 #define SZ_DATA 512 -#define SZ_PACKET 10 -#define SZ_HEADER 23 +#define SZ_PACKET 12 -#define FP_OFFSET 0x11 #define FP_SIZE 6 #define NSTEPS 1000 @@ -57,8 +63,17 @@ typedef struct cressi_goa_device_t { unsigned char fingerprint[FP_SIZE]; } cressi_goa_device_t; +typedef struct cressi_goa_conf_t { + unsigned int logbook_cmd; + unsigned int logbook_len; + unsigned int logbook_fp_offset; + unsigned int dive_fp_offset; +} cressi_goa_conf_t; + static dc_status_t cressi_goa_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); static dc_status_t cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +static dc_status_t cressi_goa_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime); +static dc_status_t cressi_goa_device_close (dc_device_t *abstract); static const dc_device_vtable_t cressi_goa_device_vtable = { sizeof(cressi_goa_device_t), @@ -68,8 +83,13 @@ static const dc_device_vtable_t cressi_goa_device_vtable = { NULL, /* write */ NULL, /* dump */ cressi_goa_device_foreach, /* foreach */ - NULL, /* timesync */ - NULL /* close */ + cressi_goa_device_timesync, /* timesync */ + cressi_goa_device_close /* close */ +}; + +static const cressi_goa_conf_t version_conf[] = { + { CMD_LOGBOOK, 23, 17, 12 }, + { CMD_LOGBOOK_V4, 15, 3, 4 }, }; static dc_status_t @@ -77,6 +97,7 @@ cressi_goa_device_send (cressi_goa_device_t *device, unsigned char cmd, const un { 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); if (size > SZ_PACKET) { ERROR (abstract->context, "Unexpected payload size (%u).", size); @@ -100,10 +121,15 @@ cressi_goa_device_send (cressi_goa_device_t *device, unsigned char cmd, const un // Wait a small amount of time before sending the command. Without // this delay, the transfer will fail most of the time. - dc_iostream_sleep (device->iostream, 100); + unsigned int delay = transport == DC_TRANSPORT_BLE ? 2000 : 100; + dc_iostream_sleep (device->iostream, delay); // Send the command to the device. - status = dc_iostream_write (device->iostream, packet, size + 8, NULL); + if (transport == DC_TRANSPORT_BLE) { + status = dc_iostream_write (device->iostream, packet + 4, size + 1, NULL); + } else { + status = dc_iostream_write (device->iostream, packet, size + 8, NULL); + } if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the command."); return status; @@ -113,13 +139,25 @@ cressi_goa_device_send (cressi_goa_device_t *device, unsigned char cmd, const un } static dc_status_t -cressi_goa_device_receive (cressi_goa_device_t *device, unsigned char data[], unsigned int size) +cressi_goa_device_receive (cressi_goa_device_t *device, dc_buffer_t *output) { 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); unsigned char packet[SZ_PACKET + 8]; + if (transport == DC_TRANSPORT_BLE) { + if (output) { + return DC_STATUS_INVALIDARGS; + } else { + return DC_STATUS_SUCCESS; + } + } + + // Clear the output buffer. + dc_buffer_clear (output); + // Read the header of the data packet. status = dc_iostream_read (device->iostream, packet, 4, NULL); if (status != DC_STATUS_SUCCESS) { @@ -161,14 +199,11 @@ cressi_goa_device_receive (cressi_goa_device_t *device, unsigned char data[], un return DC_STATUS_PROTOCOL; } - // Verify the payload length. - if (length != size) { - ERROR (abstract->context, "Unexpected payload size (%u).", length); - return DC_STATUS_PROTOCOL; - } - - if (length) { - memcpy (data, packet + 5, length); + if (length && output) { + if (!dc_buffer_append (output, packet + 5, length)) { + ERROR (abstract->context, "Could not append received data."); + return DC_STATUS_NOMEMORY; + } } return status; @@ -179,6 +214,7 @@ cressi_goa_device_download (cressi_goa_device_t *device, dc_buffer_t *buffer, dc { 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 char ack[] = {ACK}; const unsigned int initial = progress ? progress->current : 0; @@ -193,27 +229,43 @@ cressi_goa_device_download (cressi_goa_device_t *device, dc_buffer_t *buffer, dc unsigned int size = 2; unsigned int nbytes = 0; while (nbytes < size) { - // Read the data packet. unsigned char packet[3 + SZ_DATA + 2]; - status = dc_iostream_read (device->iostream, packet, sizeof(packet), NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to receive the answer."); - return status; - } - - // Verify the checksum of the packet. - unsigned short crc = array_uint16_le (packet + sizeof(packet) - 2); - unsigned short ccrc = checksum_crc16_ccitt (packet + 3, sizeof(packet) - 5, 0x0000, 0x0000); - if (crc != ccrc) { - ERROR (abstract->context, "Unexpected answer checksum."); - return DC_STATUS_PROTOCOL; - } - // Send the ack byte to the device. - status = dc_iostream_write (device->iostream, ack, sizeof(ack), NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to send the ack byte."); - return status; + if (transport == DC_TRANSPORT_BLE) { + // Read the data packet. + unsigned int packetsize = 0; + while (packetsize < SZ_DATA) { + size_t len = 0; + status = dc_iostream_read (device->iostream, packet + 3 + packetsize, SZ_DATA - packetsize, &len); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the answer."); + return status; + } + + packetsize += len; + } + } else { + // Read the data packet. + status = dc_iostream_read (device->iostream, packet, sizeof(packet), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the answer."); + return status; + } + + // Verify the checksum of the packet. + unsigned short crc = array_uint16_le (packet + sizeof(packet) - 2); + unsigned short ccrc = checksum_crc16_ccitt (packet + 3, sizeof(packet) - 5, 0x0000, 0x0000); + if (crc != ccrc) { + ERROR (abstract->context, "Unexpected answer checksum."); + return DC_STATUS_PROTOCOL; + } + + // Send the ack byte to the device. + status = dc_iostream_write (device->iostream, ack, sizeof(ack), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the ack byte."); + return status; + } } // Get the total size from the first data packet. @@ -243,25 +295,45 @@ cressi_goa_device_download (cressi_goa_device_t *device, dc_buffer_t *buffer, dc } } - // Read the end byte. - unsigned char end = 0; - status = dc_iostream_read (device->iostream, &end, 1, NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to receive the end byte."); - return status; - } + if (transport == DC_TRANSPORT_BLE) { + // Read the end bytes. + unsigned char end[16] = {0}; + size_t len = 0; + status = dc_iostream_read (device->iostream, end, sizeof(end), &len); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the end bytes."); + return status; + } - // Verify the end byte. - if (end != END) { - ERROR (abstract->context, "Unexpected end byte (%02x).", end); - return DC_STATUS_PROTOCOL; - } + // Verify the end bytes ("EOT xmodem"). + const unsigned char validate[16] = { + 0x45, 0x4F, 0x54, 0x20, 0x78, 0x6D, 0x6F, 0x64, + 0x65, 0x6D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + if (memcmp (end, validate, sizeof(validate)) != 0) { + ERROR (abstract->context, "Unexpected end bytes."); + return DC_STATUS_PROTOCOL; + } + } else { + // Read the end byte. + unsigned char end = 0; + status = dc_iostream_read (device->iostream, &end, 1, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the end byte."); + return status; + } - // Send the ack byte to the device. - status = dc_iostream_write (device->iostream, ack, sizeof(ack), NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to send the ack byte."); - return status; + // Verify the end byte. + if (end != END) { + ERROR (abstract->context, "Unexpected end byte (%02x).", end); + return DC_STATUS_PROTOCOL; + } + + // Send the ack byte to the device. + status = dc_iostream_write (device->iostream, ack, sizeof(ack), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the ack byte."); + return status; + } } return status; @@ -271,7 +343,7 @@ static dc_status_t cressi_goa_device_transfer (cressi_goa_device_t *device, unsigned char cmd, const unsigned char input[], unsigned int isize, - unsigned char output[], unsigned int osize, + dc_buffer_t *output, dc_buffer_t *buffer, dc_event_progress_t *progress) { @@ -284,7 +356,7 @@ cressi_goa_device_transfer (cressi_goa_device_t *device, } // Receive the answer from the dive computer. - status = cressi_goa_device_receive (device, output, osize); + status = cressi_goa_device_receive (device, output); if (status != DC_STATUS_SUCCESS) { return status; } @@ -328,8 +400,10 @@ cressi_goa_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t goto error_free; } - // Set the timeout for receiving data (3000 ms). - status = dc_iostream_set_timeout (device->iostream, 3000); + // Set the timeout for receiving data (3000 - 5000 ms). + dc_transport_t transport = dc_iostream_get_transport (device->iostream); + int timeout = transport == DC_TRANSPORT_BLE ? 5000 : 3000; + status = dc_iostream_set_timeout (device->iostream, timeout); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to set the timeout."); goto error_free; @@ -382,6 +456,7 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v { dc_status_t status = DC_STATUS_SUCCESS; cressi_goa_device_t *device = (cressi_goa_device_t *) abstract; + dc_transport_t transport = dc_iostream_get_transport (device->iostream); dc_buffer_t *logbook = NULL; dc_buffer_t *dive = NULL; @@ -389,36 +464,129 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - // Read the version information. - unsigned char id[9] = {0}; - status = cressi_goa_device_transfer (device, CMD_VERSION, NULL, 0, id, sizeof(id), NULL, NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the version information."); + dc_buffer_t *id = dc_buffer_new(11); + if (id == NULL) { + ERROR (abstract->context, "Failed to allocate memory for the ID."); + status = DC_STATUS_NOMEMORY; goto error_exit; } + // Read the version information. + if (transport == DC_TRANSPORT_BLE) { + /* + * With the BLE communication, there is no variant of the CMD_VERSION + * command available. The corresponding information must be obtained by + * reading some secondary characteristics instead: + * 6E400003-B5A3-F393-E0A9-E50E24DC10B8 - 5 bytes + * 6E400004-B5A3-F393-E0A9-E50E24DC10B8 - 2 bytes + * 6E400005-B5A3-F393-E0A9-E50E24DC10B8 - 2 bytes + */ + const dc_ble_uuid_t characteristics[] = { + {0x6E, 0x40, 0x00, 0x03, 0xB5, 0xA3, 0xF3, 0x93, 0xE0, 0xA9, 0xE5, 0x0E, 0x24, 0xDC, 0x10, 0xB8}, + {0x6E, 0x40, 0x00, 0x04, 0xB5, 0xA3, 0xF3, 0x93, 0xE0, 0xA9, 0xE5, 0x0E, 0x24, 0xDC, 0x10, 0xB8}, + {0x6E, 0x40, 0x00, 0x05, 0xB5, 0xA3, 0xF3, 0x93, 0xE0, 0xA9, 0xE5, 0x0E, 0x24, 0xDC, 0x10, 0xB8}, + }; + const size_t sizes[] = {5, 2, 2}; + + for (size_t i = 0; i < C_ARRAY_SIZE(characteristics); ++i) { + unsigned char request[sizeof(dc_ble_uuid_t) + 5] = {0}; + + // Setup the request. + memcpy (request, characteristics[i], sizeof(dc_ble_uuid_t)); + memset (request + sizeof(dc_ble_uuid_t), 0, sizes[i]); + + // Read the characteristic. + status = dc_iostream_ioctl (device->iostream, DC_IOCTL_BLE_CHARACTERISTIC_READ, request, sizeof(dc_ble_uuid_t) + sizes[i]); + if (status != DC_STATUS_SUCCESS) { + char uuidstr[DC_BLE_UUID_SIZE] = {0}; + ERROR (abstract->context, "Failed to read the characteristic '%s'.", + dc_ble_uuid2str(characteristics[i], uuidstr, sizeof(uuidstr))); + goto error_free_id; + } + + // Copy the payload data. + if (!dc_buffer_append(id, request + sizeof(dc_ble_uuid_t), sizes[i])) { + ERROR (abstract->context, "Insufficient buffer space available."); + status = DC_STATUS_NOMEMORY; + goto error_free_id; + } + } + } else { + status = cressi_goa_device_transfer (device, CMD_VERSION, NULL, 0, id, NULL, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the version information."); + goto error_free_id; + } + } + + const unsigned char *id_data = dc_buffer_get_data(id); + size_t id_size = dc_buffer_get_size(id); + + if (id_size < 9) { + ERROR (abstract->context, "Unexpected version length (" DC_PRINTF_SIZE ").", id_size); + status = DC_STATUS_DATAFORMAT; + goto error_free_id; + } + + // Get the device info. + unsigned int model = id_data[4]; + unsigned int firmware = array_uint16_le (id_data + 5); + unsigned int serial = array_uint32_le (id_data + 0); + + // Get the data format version. + unsigned int version = 0; + if (id_size == 11) { + version = array_uint16_le (id_data + 9); + } else { + if (firmware >= 161 && firmware <= 165) { + version = 0; + } else if (firmware >= 166 && firmware <= 169) { + version = 1; + } else if (firmware >= 170 && firmware <= 179) { + version = 2; + } else if (firmware >= 100 && firmware <= 110) { + version = 3; + } else if (firmware >= 200 && firmware <= 205) { + version = 4; + } else if (firmware >= 300) { + version = 5; + } else { + ERROR (abstract->context, "Unknown firmware version (%u).", firmware); + status = DC_STATUS_DATAFORMAT; + goto error_free_id; + } + } + + const cressi_goa_conf_t *conf = &version_conf[version >= 4]; + // Emit a vendor event. dc_event_vendor_t vendor; - vendor.data = id; - vendor.size = sizeof (id); + vendor.data = id_data; + vendor.size = id_size; device_event_emit (abstract, DC_EVENT_VENDOR, &vendor); // Emit a device info event. dc_event_devinfo_t devinfo; - devinfo.model = id[4]; - devinfo.firmware = array_uint16_le (id + 5); - devinfo.serial = array_uint32_le (id + 0); + devinfo.model = model; + devinfo.firmware = firmware; + devinfo.serial = serial; device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); // Allocate memory for the logbook data. logbook = dc_buffer_new(4096); if (logbook == NULL) { - ERROR (abstract->context, "Failed to allocate memory."); - goto error_exit; + ERROR (abstract->context, "Failed to allocate memory for the logbook."); + status = DC_STATUS_NOMEMORY; + goto error_free_id; } // Read the logbook data. - status = cressi_goa_device_transfer (device, CMD_LOGBOOK, NULL, 0, NULL, 0, logbook, &progress); + if (transport == DC_TRANSPORT_BLE) { + unsigned char args[] = {0x00}; + status = cressi_goa_device_transfer (device, CMD_LOGBOOK_BLE, args, sizeof(args), NULL, logbook, &progress); + } else { + status = cressi_goa_device_transfer (device, conf->logbook_cmd, NULL, 0, NULL, logbook, &progress); + } if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the logbook data."); goto error_free_logbook; @@ -430,9 +598,9 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v // Count the number of dives. unsigned int count = 0; unsigned int offset = logbook_size; - while (offset > SZ_HEADER) { + while (offset >= conf->logbook_len) { // Move to the start of the logbook entry. - offset -= SZ_HEADER; + offset -= conf->logbook_len; // Get the dive number. unsigned int number= array_uint16_le (logbook_data + offset); @@ -440,7 +608,7 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v break; // Compare the fingerprint to identify previously downloaded entries. - if (memcmp (logbook_data + offset + FP_OFFSET, device->fingerprint, sizeof(device->fingerprint)) == 0) { + if (memcmp (logbook_data + offset + conf->logbook_fp_offset, device->fingerprint, sizeof(device->fingerprint)) == 0) { break; } @@ -454,7 +622,8 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v // Allocate memory for the dive data. dive = dc_buffer_new(4096); if (dive == NULL) { - ERROR (abstract->context, "Failed to allocate memory."); + ERROR (abstract->context, "Failed to allocate memory for the dive."); + status = DC_STATUS_NOMEMORY; goto error_free_logbook; } @@ -462,10 +631,17 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v offset = logbook_size; for (unsigned int i = 0; i < count; ++i) { // Move to the start of the logbook entry. - offset -= SZ_HEADER; + offset -= conf->logbook_len; // Read the dive data. - status = cressi_goa_device_transfer (device, CMD_DIVE, logbook_data + offset, 2, NULL, 0, dive, &progress); + if (transport == DC_TRANSPORT_BLE) { + unsigned char number[2] = { + logbook_data[offset + 1], + logbook_data[offset + 0]}; + status = cressi_goa_device_transfer (device, CMD_DIVE_BLE, number, 2, NULL, dive, &progress); + } else { + status = cressi_goa_device_transfer (device, CMD_DIVE, logbook_data + offset, 2, NULL, dive, &progress); + } if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the dive data."); goto error_free_dive; @@ -474,21 +650,29 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v const unsigned char *dive_data = dc_buffer_get_data (dive); size_t dive_size = dc_buffer_get_size (dive); - // Verify the header in the logbook and dive data are identical. - // After the 2 byte dive number, the logbook header has 5 bytes - // extra, which are not present in the dive header. - if (dive_size < SZ_HEADER - 5 || - memcmp (dive_data + 0, logbook_data + offset + 0, 2) != 0 || - memcmp (dive_data + 2, logbook_data + offset + 7, SZ_HEADER - 7) != 0) { + // Verify the dive number and the fingerprint in the logbook and dive + // data are identical. + if (dive_size < conf->dive_fp_offset + FP_SIZE || + memcmp (dive_data, logbook_data + offset, 2) != 0 || + memcmp (dive_data + conf->dive_fp_offset, logbook_data + offset + conf->logbook_fp_offset, FP_SIZE) != 0) { ERROR (abstract->context, "Unexpected dive header."); status = DC_STATUS_DATAFORMAT; goto error_free_dive; } - // Those 5 extra bytes contain the dive mode, which is required for - // parsing the dive data. Therefore, insert all 5 bytes again. The - // remaining 4 bytes appear to be some 32 bit address. - if (!dc_buffer_insert (dive, 2, logbook_data + offset + 2, 5)) { + // The dive computer id data and the logbook entry contain important + // information that is required for parsing the dive data, but is + // not present in the dive data itself. Therefore, both pieces of data + // are prepended to the dive data, along with a small header containing + // their size. + const unsigned char header[] = { + id_size, + conf->logbook_len, + }; + unsigned int headersize = sizeof(header) + id_size + conf->logbook_len; + if (!dc_buffer_prepend(dive, logbook_data + offset, conf->logbook_len) || + !dc_buffer_prepend(dive, id_data, id_size) || + !dc_buffer_prepend(dive, header, sizeof(header))) { ERROR (abstract->context, "Out of memory."); status = DC_STATUS_NOMEMORY; goto error_free_dive; @@ -497,7 +681,7 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v dive_data = dc_buffer_get_data (dive); dive_size = dc_buffer_get_size (dive); - if (callback && !callback(dive_data, dive_size, dive_data + FP_OFFSET, sizeof(device->fingerprint), userdata)) + if (callback && !callback(dive_data, dive_size, dive_data + headersize + conf->dive_fp_offset, sizeof(device->fingerprint), userdata)) break; } @@ -505,6 +689,55 @@ cressi_goa_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v dc_buffer_free(dive); error_free_logbook: dc_buffer_free(logbook); +error_free_id: + dc_buffer_free(id); error_exit: return status; } + +static dc_status_t +cressi_goa_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) +{ + dc_status_t status = DC_STATUS_SUCCESS; + cressi_goa_device_t *device = (cressi_goa_device_t *) abstract; + dc_transport_t transport = dc_iostream_get_transport (device->iostream); + + if (transport == DC_TRANSPORT_BLE) { + return DC_STATUS_UNSUPPORTED; + } + + unsigned char new_time[7]; + array_uint16_le_set(new_time, datetime->year); + new_time[2] = datetime->month; + new_time[3] = datetime->day; + new_time[4] = datetime->hour; + new_time[5] = datetime->minute; + new_time[6] = datetime->second; + status = cressi_goa_device_transfer (device, CMD_SET_TIME, new_time, sizeof(new_time), NULL, NULL, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to set the new time."); + return status; + } + + return status; +} + +static dc_status_t +cressi_goa_device_close (dc_device_t *abstract) +{ + dc_status_t status = DC_STATUS_SUCCESS; + cressi_goa_device_t *device = (cressi_goa_device_t *) abstract; + dc_transport_t transport = dc_iostream_get_transport (device->iostream); + + if (transport == DC_TRANSPORT_BLE) { + return DC_STATUS_SUCCESS; + } + + status = cressi_goa_device_transfer (device, CMD_EXIT_PCLINK, NULL, 0, NULL, NULL, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to exit PC Link."); + return status; + } + + return status; +} diff --git a/src/cressi_goa.h b/src/cressi_goa.h index 63283a07..b7c79de1 100644 --- a/src/cressi_goa.h +++ b/src/cressi_goa.h @@ -35,7 +35,7 @@ dc_status_t cressi_goa_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); dc_status_t -cressi_goa_parser_create (dc_parser_t **parser, dc_context_t *context, const unsigned char data[], size_t size, unsigned int model); +cressi_goa_parser_create (dc_parser_t **parser, dc_context_t *context, const unsigned char data[], size_t size); #ifdef __cplusplus } diff --git a/src/cressi_goa_parser.c b/src/cressi_goa_parser.c index 28986622..22e9c659 100644 --- a/src/cressi_goa_parser.c +++ b/src/cressi_goa_parser.c @@ -28,40 +28,46 @@ #define ISINSTANCE(parser) dc_device_isinstance((parser), &cressi_goa_parser_vtable) -#define SZ_HEADER 23 - -#define DEPTH 0 -#define DEPTH2 1 -#define TIME 2 +#define DEPTH_SCUBA 0 +#define DEPTH_FREE 1 +#define SURFACE 2 #define TEMPERATURE 3 -#define SCUBA 0 -#define NITROX 1 -#define FREEDIVE 2 -#define GAUGE 3 +#define SCUBA 0 +#define NITROX 1 +#define FREEDIVE 2 +#define GAUGE 3 +#define FREEDIVE_ADV 5 -#define NGASMIXES 2 +#define NGASMIXES 3 +#define NVERSIONS 6 +#define NDIVEMODES 6 #define UNDEFINED 0xFFFFFFFF typedef struct cressi_goa_parser_t cressi_goa_parser_t; -struct cressi_goa_parser_t { - dc_parser_t base; - unsigned int model; -}; - typedef struct cressi_goa_layout_t { unsigned int headersize; + unsigned int nsamples; + unsigned int samplerate; unsigned int datetime; unsigned int divetime; - unsigned int gasmix; + unsigned int gasmix[NGASMIXES]; unsigned int atmospheric; unsigned int maxdepth; unsigned int avgdepth; unsigned int temperature; } cressi_goa_layout_t; +struct cressi_goa_parser_t { + dc_parser_t base; + const cressi_goa_layout_t *layout; + unsigned int headersize; + unsigned int version; + unsigned int divemode; +}; + static dc_status_t cressi_goa_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); static dc_status_t cressi_goa_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); static dc_status_t cressi_goa_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); @@ -78,56 +84,300 @@ static const dc_parser_vtable_t cressi_goa_parser_vtable = { NULL /* destroy */ }; -static const cressi_goa_layout_t layouts[] = { - /* SCUBA */ +static const cressi_goa_layout_t scuba_nitrox_layout_v0 = { + 90, /* headersize */ + 10, /* nsamples */ + UNDEFINED, /* samplerate */ + 12, /* datetime */ + 20, /* divetime */ + { 26, 28, UNDEFINED }, /* gasmix */ + 30, /* atmospheric */ + 73, /* maxdepth */ + 75, /* avgdepth */ + 77, /* temperature */ +}; + +static const cressi_goa_layout_t scuba_nitrox_layout_v1v2 = { + 92, /* headersize */ + 10, /* nsamples */ + UNDEFINED, /* samplerate */ + 12, /* datetime */ + 20, /* divetime */ + { 26, 28, UNDEFINED }, /* gasmix */ + 30, /* atmospheric */ + 73, /* maxdepth */ + 75, /* avgdepth */ + 77, /* temperature */ +}; + +static const cressi_goa_layout_t scuba_nitrox_layout_v3 = { + 92, /* headersize */ + 10, /* nsamples */ + UNDEFINED, /* samplerate */ + 12, /* datetime */ + 20, /* divetime */ + { 26, 28, 87 }, /* gasmix */ + 30, /* atmospheric */ + 73, /* maxdepth */ + 75, /* avgdepth */ + 77, /* temperature */ +}; + +static const cressi_goa_layout_t scuba_nitrox_layout_v4 = { + 82, /* headersize */ + 10, /* nsamples */ + 2, /* samplerate */ + 4, /* datetime */ + 11, /* divetime */ + { 17, 19, 21 }, /* gasmix */ + 23, /* atmospheric */ + 66, /* maxdepth */ + 68, /* avgdepth */ + 70, /* temperature */ +}; + +static const cressi_goa_layout_t scuba_nitrox_layout_v5 = { + 83, /* headersize */ + 2, /* nsamples */ + UNDEFINED, /* samplerate */ + 4, /* datetime */ + 11, /* divetime */ + { 17, 19, 21 }, /* gasmix */ + 23, /* atmospheric */ + 66, /* maxdepth */ + 68, /* avgdepth */ + 70, /* temperature */ +}; + +static const cressi_goa_layout_t freedive_layout_v0 = { + 34, /* headersize */ + 10, /* nsamples */ + UNDEFINED, /* samplerate */ + 12, /* datetime */ + 20, /* divetime */ + { UNDEFINED, UNDEFINED, UNDEFINED }, /* gasmix */ + UNDEFINED, /* atmospheric */ + 23, /* maxdepth */ + UNDEFINED, /* avgdepth */ + 25, /* temperature */ +}; + +static const cressi_goa_layout_t freedive_layout_v1v2v3 = { + 38, /* headersize */ + 10, /* nsamples */ + UNDEFINED, /* samplerate */ + 12, /* datetime */ + 20, /* divetime */ + { UNDEFINED, UNDEFINED, UNDEFINED }, /* gasmix */ + UNDEFINED, /* atmospheric */ + 23, /* maxdepth */ + UNDEFINED, /* avgdepth */ + 25, /* temperature */ +}; + +static const cressi_goa_layout_t freedive_layout_v4 = { + 27, /* headersize */ + 2, /* nsamples */ + 10, /* samplerate */ + 4, /* datetime */ + 11, /* divetime */ + { UNDEFINED, UNDEFINED, UNDEFINED }, /* gasmix */ + UNDEFINED, /* atmospheric */ + 15, /* maxdepth */ + UNDEFINED, /* avgdepth */ + 17, /* temperature */ +}; + +static const cressi_goa_layout_t gauge_layout_v0 = { + 38, /* headersize */ + 10, /* nsamples */ + UNDEFINED, /* samplerate */ + 12, /* datetime */ + 20, /* divetime */ + { UNDEFINED, UNDEFINED, UNDEFINED }, /* gasmix */ + 22, /* atmospheric */ + 24, /* maxdepth */ + 26, /* avgdepth */ + 28, /* temperature */ +}; + +static const cressi_goa_layout_t gauge_layout_v1v2v3 = { + 40, /* headersize */ + 10, /* nsamples */ + UNDEFINED, /* samplerate */ + 12, /* datetime */ + 20, /* divetime */ + { UNDEFINED, UNDEFINED, UNDEFINED }, /* gasmix */ + 22, /* atmospheric */ + 24, /* maxdepth */ + 26, /* avgdepth */ + 28, /* temperature */ +}; + +static const cressi_goa_layout_t gauge_layout_v4 = { + 28, /* headersize */ + 2, /* nsamples */ + 10, /* samplerate */ + 4, /* datetime */ + 11, /* divetime */ + { UNDEFINED, UNDEFINED, UNDEFINED }, /* gasmix */ + 13, /* atmospheric */ + 15, /* maxdepth */ + 17, /* avgdepth */ + 19, /* temperature */ +}; + +static const cressi_goa_layout_t advanced_freedive_layout_v4 = { + 28, /* headersize */ + 2, /* nsamples */ + 10, /* samplerate */ + 4, /* datetime */ + 22, /* divetime */ + { UNDEFINED, UNDEFINED, UNDEFINED }, /* gasmix */ + UNDEFINED, /* atmospheric */ + 16, /* maxdepth */ + UNDEFINED, /* avgdepth */ + 18, /* temperature */ +}; + +static const cressi_goa_layout_t * const layouts[NVERSIONS][NDIVEMODES] = { + { + &scuba_nitrox_layout_v0, /* SCUBA */ + &scuba_nitrox_layout_v0, /* NITROX */ + &freedive_layout_v0, /* FREEDIVE */ + &gauge_layout_v0, /* GAUGE */ + NULL, /* UNUSED */ + NULL, /* FREEDIVE_ADV */ + }, + { + &scuba_nitrox_layout_v1v2, /* SCUBA */ + &scuba_nitrox_layout_v1v2, /* NITROX */ + &freedive_layout_v1v2v3, /* FREEDIVE */ + &gauge_layout_v1v2v3, /* GAUGE */ + NULL, /* UNUSED */ + NULL, /* FREEDIVE_ADV */ + }, { - 0x61, /* headersize */ - 0x11, /* datetime */ - 0x19, /* divetime */ - 0x1F, /* gasmix */ - 0x23, /* atmospheric */ - 0x4E, /* maxdepth */ - 0x50, /* avgdepth */ - 0x52, /* temperature */ + &scuba_nitrox_layout_v1v2, /* SCUBA */ + &scuba_nitrox_layout_v1v2, /* NITROX */ + &freedive_layout_v1v2v3, /* FREEDIVE */ + &gauge_layout_v1v2v3, /* GAUGE */ + NULL, /* UNUSED */ + NULL, /* FREEDIVE_ADV */ }, - /* NITROX */ { - 0x61, /* headersize */ - 0x11, /* datetime */ - 0x19, /* divetime */ - 0x1F, /* gasmix */ - 0x23, /* atmospheric */ - 0x4E, /* maxdepth */ - 0x50, /* avgdepth */ - 0x52, /* temperature */ + &scuba_nitrox_layout_v3, /* SCUBA */ + &scuba_nitrox_layout_v3, /* NITROX */ + &freedive_layout_v1v2v3, /* FREEDIVE */ + &gauge_layout_v1v2v3, /* GAUGE */ + NULL, /* UNUSED */ + NULL, /* FREEDIVE_ADV */ }, - /* FREEDIVE */ { - 0x2B, /* headersize */ - 0x11, /* datetime */ - 0x19, /* divetime */ - UNDEFINED, /* gasmix */ - UNDEFINED, /* atmospheric */ - 0x1C, /* maxdepth */ - UNDEFINED, /* avgdepth */ - 0x1E, /* temperature */ + &scuba_nitrox_layout_v4, /* SCUBA */ + &scuba_nitrox_layout_v4, /* NITROX */ + &freedive_layout_v4, /* FREEDIVE */ + &gauge_layout_v4, /* GAUGE */ + NULL, /* UNUSED */ + &advanced_freedive_layout_v4, /* FREEDIVE_ADV */ }, - /* GAUGE */ { - 0x2D, /* headersize */ - 0x11, /* datetime */ - 0x19, /* divetime */ - UNDEFINED, /* gasmix */ - 0x1B, /* atmospheric */ - 0x1D, /* maxdepth */ - 0x1F, /* avgdepth */ - 0x21, /* temperature */ + &scuba_nitrox_layout_v5, /* SCUBA */ + &scuba_nitrox_layout_v5, /* NITROX */ + &freedive_layout_v4, /* FREEDIVE */ + &gauge_layout_v4, /* GAUGE */ + NULL, /* UNUSED */ + NULL, /* FREEDIVE_ADV */ }, }; +static dc_status_t +cressi_goa_init(cressi_goa_parser_t *parser) +{ + dc_parser_t *abstract = (dc_parser_t *) parser; + const unsigned char *data = abstract->data; + unsigned int size = abstract->size; + + if (size < 2) { + ERROR (abstract->context, "Invalid dive length (%u).", size); + return DC_STATUS_DATAFORMAT; + } + + unsigned int id_len = data[0]; + unsigned int logbook_len = data[1]; + if (id_len < 9 || logbook_len < 15) { + ERROR (abstract->context, "Invalid id or logbook length (%u %u).", id_len, logbook_len); + return DC_STATUS_DATAFORMAT; + } + + if (size < 2 + id_len + logbook_len) { + ERROR (abstract->context, "Invalid dive length (%u).", size); + return DC_STATUS_DATAFORMAT; + } + + const unsigned char *id = data + 2; + const unsigned char *logbook = data + 2 + id_len; + + // Get the data format version. + unsigned int version = 0; + unsigned int firmware = array_uint16_le (id + 5); + if (id_len == 11) { + version = array_uint16_le (id + 9); + } else { + if (firmware >= 161 && firmware <= 165) { + version = 0; + } else if (firmware >= 166 && firmware <= 169) { + version = 1; + } else if (firmware >= 170 && firmware <= 179) { + version = 2; + } else if (firmware >= 100 && firmware <= 110) { + version = 3; + } else if (firmware >= 200 && firmware <= 205) { + version = 4; + } else if (firmware >= 300) { + version = 5; + } else { + ERROR (abstract->context, "Unknown firmware version (%u).", firmware); + return DC_STATUS_DATAFORMAT; + } + } + if (version >= NVERSIONS) { + ERROR (abstract->context, "Invalid data format version (%u).", version); + return DC_STATUS_DATAFORMAT; + } + + // Get the dive mode. + unsigned int divemode = logbook[2]; + if (divemode >= NDIVEMODES) { + ERROR (abstract->context, "Invalid dive mode (%u).", divemode); + return DC_STATUS_DATAFORMAT; + } + + // Get the layout. + const cressi_goa_layout_t *layout = layouts[version][divemode]; + if (layout == NULL) { + ERROR (abstract->context, "Unsupported dive mode for data format version %u (%u).", version, divemode); + return DC_STATUS_DATAFORMAT; + } + + unsigned int headersize = 2 + id_len + logbook_len; + if (size < headersize + layout->headersize) { + ERROR (abstract->context, "Invalid dive length (%u).", size); + return DC_STATUS_DATAFORMAT; + } + + parser->layout = layout; + parser->headersize = headersize; + parser->divemode = divemode; + parser->version = version; + + return DC_STATUS_SUCCESS; +} + dc_status_t -cressi_goa_parser_create (dc_parser_t **out, dc_context_t *context, const unsigned char data[], size_t size, unsigned int model) +cressi_goa_parser_create (dc_parser_t **out, dc_context_t *context, const unsigned char data[], size_t size) { + dc_status_t status = DC_STATUS_SUCCESS; cressi_goa_parser_t *parser = NULL; if (out == NULL) @@ -140,33 +390,25 @@ cressi_goa_parser_create (dc_parser_t **out, dc_context_t *context, const unsign return DC_STATUS_NOMEMORY; } - parser->model = model; + status = cressi_goa_init(parser); + if (status != DC_STATUS_SUCCESS) + goto error_free; *out = (dc_parser_t*) parser; return DC_STATUS_SUCCESS; + +error_free: + dc_parser_deallocate ((dc_parser_t *) parser); + return status; } static dc_status_t cressi_goa_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) { - const unsigned char *data = abstract->data; - unsigned int size = abstract->size; - - if (size < SZ_HEADER) - return DC_STATUS_DATAFORMAT; - - unsigned int divemode = data[2]; - if (divemode >= C_ARRAY_SIZE(layouts)) { - return DC_STATUS_DATAFORMAT; - } + cressi_goa_parser_t *parser = (cressi_goa_parser_t *) abstract; - const cressi_goa_layout_t *layout = &layouts[divemode]; - - if (size < layout->headersize) - return DC_STATUS_DATAFORMAT; - - const unsigned char *p = abstract->data + layout->datetime; + const unsigned char *p = abstract->data + parser->headersize + parser->layout->datetime; if (datetime) { datetime->year = array_uint16_le(p); @@ -184,29 +426,17 @@ cressi_goa_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) static dc_status_t cressi_goa_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) { - const unsigned char *data = abstract->data; - unsigned int size = abstract->size; - - if (size < SZ_HEADER) - return DC_STATUS_DATAFORMAT; - - unsigned int divemode = data[2]; - if (divemode >= C_ARRAY_SIZE(layouts)) { - return DC_STATUS_DATAFORMAT; - } - - const cressi_goa_layout_t *layout = &layouts[divemode]; - - if (size < layout->headersize) - return DC_STATUS_DATAFORMAT; + cressi_goa_parser_t *parser = (cressi_goa_parser_t *) abstract; + const cressi_goa_layout_t *layout = parser->layout; + const unsigned char *data = abstract->data + parser->headersize; unsigned int ngasmixes = 0; - if (layout->gasmix != UNDEFINED) { - for (unsigned int i = 0; i < NGASMIXES; ++i) { - if (data[layout->gasmix + 2 * i + 1] == 0) - break; - ngasmixes++; - } + for (unsigned int i = 0; i < NGASMIXES; ++i) { + if (layout->gasmix[i] == UNDEFINED) + break; + if (data[layout->gasmix[i] + 1] == 0) + break; + ngasmixes++; } dc_gasmix_t *gasmix = (dc_gasmix_t *) value; @@ -242,13 +472,15 @@ cressi_goa_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsign *((unsigned int *) value) = ngasmixes; break; case DC_FIELD_GASMIX: + if (ngasmixes <= flags) + return DC_STATUS_INVALIDARGS; gasmix->usage = DC_USAGE_NONE; gasmix->helium = 0.0; - gasmix->oxygen = data[layout->gasmix + 2 * flags + 1] / 100.0; + gasmix->oxygen = data[layout->gasmix[flags] + 1] / 100.0; gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; break; case DC_FIELD_DIVEMODE: - switch (divemode) { + switch (parser->divemode) { case SCUBA: case NITROX: *((dc_divemode_t *) value) = DC_DIVEMODE_OC; @@ -257,6 +489,7 @@ cressi_goa_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsign *((dc_divemode_t *) value) = DC_DIVEMODE_GAUGE; break; case FREEDIVE: + case FREEDIVE_ADV: *((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE; break; default: @@ -274,55 +507,86 @@ cressi_goa_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsign static dc_status_t cressi_goa_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) { - const unsigned char *data = abstract->data; - unsigned int size = abstract->size; - - if (size < SZ_HEADER) - return DC_STATUS_DATAFORMAT; - - unsigned int divemode = data[2]; - if (divemode >= C_ARRAY_SIZE(layouts)) { - return DC_STATUS_DATAFORMAT; + cressi_goa_parser_t *parser = (cressi_goa_parser_t *) abstract; + const cressi_goa_layout_t *layout = parser->layout; + const unsigned char *data = abstract->data + parser->headersize; + unsigned int size = abstract->size - parser->headersize; + + unsigned int interval = parser->divemode == FREEDIVE ? 2000 : 5000; + if (layout->samplerate != UNDEFINED) { + const unsigned int samplerates[] = { + 500, 1000, 2000, 5000 + }; + unsigned int samplerate = data[layout->samplerate]; + if (samplerate == 0 || samplerate > C_ARRAY_SIZE(samplerates)) { + ERROR (abstract->context, "Unknown sample rate index (%u).", samplerate); + return DC_STATUS_DATAFORMAT; + } + interval = samplerates[samplerate - 1]; } - const cressi_goa_layout_t *layout = &layouts[divemode]; - - if (size < layout->headersize) - return DC_STATUS_DATAFORMAT; + // In advanced freedive mode, there is an extra header present after the + // samples containing the advanced freedive dip stats. + unsigned int trailer = parser->divemode == FREEDIVE_ADV ? 13 : 0; - unsigned int interval = divemode == FREEDIVE ? 2 : 5; + unsigned int nsamples = array_uint16_le (data + layout->nsamples); unsigned int time = 0; unsigned int depth = 0; + unsigned int depth_mask = ((parser->version < 4) ? 0x07FF : 0x0FFF); unsigned int gasmix = 0, gasmix_previous = 0xFFFFFFFF; + unsigned int gasmix_mask = ((parser->version < 3) ? 0x0800 : 0x1800); unsigned int temperature = 0; unsigned int have_temperature = 0; unsigned int complete = 0; unsigned int offset = layout->headersize; - while (offset + 2 <= size) { + for (unsigned int i = 0; i < nsamples; ++i) { dc_sample_value_t sample = {0}; + if (offset + 2 + trailer > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + // Get the sample type and value. unsigned int raw = array_uint16_le (data + offset); unsigned int type = (raw & 0x0003); unsigned int value = (raw & 0xFFFC) >> 2; - if (type == DEPTH || type == DEPTH2) { - depth = (value & 0x07FF); - gasmix = (value & 0x0800) >> 11; + if (type == DEPTH_SCUBA) { + depth = value & 0x07FF; + gasmix = (value & gasmix_mask) >> 11; + time += interval; + complete = 1; + } else if (type == DEPTH_FREE) { + depth = value & depth_mask; time += interval; complete = 1; } else if (type == TEMPERATURE) { temperature = value; have_temperature = 1; - } else if (type == TIME) { - time += value; + } else if (type == SURFACE) { + unsigned int surftime = value * 1000; + if (surftime > interval) { + surftime -= interval; + time += interval; + + // Time (seconds). + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, &sample, userdata); + // Depth (1/10 m). + sample.depth = 0.0; + if (callback) callback (DC_SAMPLE_DEPTH, &sample, userdata); + } + time += surftime; + depth = 0; + complete = 1; } if (complete) { // Time (seconds). - sample.time = time * 1000; + sample.time = time; if (callback) callback (DC_SAMPLE_TIME, &sample, userdata); // Temperature (1/10 °C). @@ -337,7 +601,7 @@ cressi_goa_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t c if (callback) callback (DC_SAMPLE_DEPTH, &sample, userdata); // Gas change - if (divemode == SCUBA || divemode == NITROX) { + if (parser->divemode == SCUBA || parser->divemode == NITROX) { if (gasmix != gasmix_previous) { sample.gasmix = gasmix; if (callback) callback (DC_SAMPLE_GASMIX, &sample, userdata); diff --git a/src/deepsix_excursion_parser.c b/src/deepsix_excursion_parser.c index 52d06af9..e197891e 100644 --- a/src/deepsix_excursion_parser.c +++ b/src/deepsix_excursion_parser.c @@ -334,6 +334,9 @@ deepsix_excursion_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, case 2: *((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE; break; + case 3: + *((dc_divemode_t *) value) = DC_DIVEMODE_CCR; + break; default: return DC_STATUS_DATAFORMAT; } @@ -550,6 +553,12 @@ deepsix_excursion_parser_samples_foreach_v1 (dc_parser_t *abstract, dc_sample_ca return DC_STATUS_DATAFORMAT; } break; + case EVENT_CHANGE_SETPOINT: + if (event_info[i].size != 4 && event_info[i].size != 2) { + ERROR(abstract->context, "Unexpected event size (%u).", event_info[i].size); + return DC_STATUS_DATAFORMAT; + } + break; case EVENT_SAMPLES_MISSED: if (event_info[i].size != 6) { ERROR(abstract->context, "Unexpected event size (%u).", event_info[i].size); @@ -670,6 +679,14 @@ deepsix_excursion_parser_samples_foreach_v1 (dc_parser_t *abstract, dc_sample_ca sample.gasmix = mix_idx; if (callback) callback(DC_SAMPLE_GASMIX, &sample, userdata); break; + case EVENT_CHANGE_SETPOINT: + // Ignore the 4 byte variant because it's not + // supposed to be present in the sample data. + if (event_info[i].size == 2) { + sample.setpoint = data[offset + event_offset] / 10.0; + if (callback) callback(DC_SAMPLE_SETPOINT, &sample, userdata); + } + break; case EVENT_SAMPLES_MISSED: count = array_uint16_le(data + offset + event_offset); timestamp = array_uint32_le(data + offset + event_offset + 2); diff --git a/src/descriptor.c b/src/descriptor.c index 8d31e01c..0fcdd9a2 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -59,6 +59,7 @@ static int dc_filter_deepsix (dc_descriptor_t *descriptor, dc_transport_t transp static int dc_filter_deepblu (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); static int dc_filter_oceans (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); static int dc_filter_divesoft (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); +static int dc_filter_cressi (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); // Not merged upstream yet static int dc_filter_garmin (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata); @@ -149,6 +150,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Uwatec", "Galileo Terra", DC_FAMILY_UWATEC_SMART, 0x11, DC_TRANSPORT_IRDA, dc_filter_uwatec}, {"Uwatec", "Aladin Tec", DC_FAMILY_UWATEC_SMART, 0x12, DC_TRANSPORT_IRDA, dc_filter_uwatec}, {"Uwatec", "Aladin Prime", DC_FAMILY_UWATEC_SMART, 0x12, DC_TRANSPORT_IRDA, dc_filter_uwatec}, + {"Uwatec", "Aladin One", DC_FAMILY_UWATEC_SMART, 0x12, DC_TRANSPORT_IRDA, dc_filter_uwatec}, {"Uwatec", "Aladin Tec 2G", DC_FAMILY_UWATEC_SMART, 0x13, DC_TRANSPORT_IRDA, dc_filter_uwatec}, {"Uwatec", "Aladin 2G", DC_FAMILY_UWATEC_SMART, 0x13, DC_TRANSPORT_IRDA, dc_filter_uwatec}, {"Subgear", "XP-10", DC_FAMILY_UWATEC_SMART, 0x13, DC_TRANSPORT_IRDA, dc_filter_uwatec}, @@ -273,7 +275,8 @@ static const dc_descriptor_t g_descriptors[] = { {"Sherwood", "Amphos Air 2.0", DC_FAMILY_OCEANIC_ATOM2, 0x4658, DC_TRANSPORT_SERIAL, NULL}, {"Sherwood", "Beacon", DC_FAMILY_OCEANIC_ATOM2, 0x4742, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, {"Aqualung", "i470TC", DC_FAMILY_OCEANIC_ATOM2, 0x4743, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, - {"Aqualung", "i200Cv2", DC_FAMILY_OCEANIC_ATOM2, 0x4749, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, + {"Aqualung", "i100", DC_FAMILY_OCEANIC_ATOM2, 0x4745, DC_TRANSPORT_SERIAL, NULL}, + {"Aqualung", "i200C", DC_FAMILY_OCEANIC_ATOM2, 0x4749, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, {"Oceanic", "Geo Air", DC_FAMILY_OCEANIC_ATOM2, 0x474B, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, /* Pelagic I330R */ // The pairing sequence for these was intentionally broken by Pelagic Pressure Systems @@ -281,6 +284,7 @@ static const dc_descriptor_t g_descriptors[] = { // Pelagic should fix this on their side if they want their customers to be able to use Subsurface //{"Apeks", "DSX", DC_FAMILY_PELAGIC_I330R, 0x4741, DC_TRANSPORT_BLE, dc_filter_oceanic}, //{"Aqualung", "i330R", DC_FAMILY_PELAGIC_I330R, 0x4744, DC_TRANSPORT_BLE, dc_filter_oceanic}, + //{"Aqualung", "i330R Console", DC_FAMILY_PELAGIC_I330R, 0x474D, DC_TRANSPORT_BLE, dc_filter_oceanic}, /* Mares Nemo */ {"Mares", "Nemo", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Nemo Steel", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL}, @@ -344,11 +348,13 @@ static const dc_descriptor_t g_descriptors[] = { {"Cressi", "Newton", DC_FAMILY_CRESSI_LEONARDO, 5, DC_TRANSPORT_SERIAL, NULL}, {"Cressi", "Drake", DC_FAMILY_CRESSI_LEONARDO, 6, DC_TRANSPORT_SERIAL, NULL}, /* Cressi Goa */ - {"Cressi", "Cartesio", DC_FAMILY_CRESSI_GOA, 1, DC_TRANSPORT_SERIAL, NULL}, - {"Cressi", "Goa", DC_FAMILY_CRESSI_GOA, 2, DC_TRANSPORT_SERIAL, NULL}, - {"Cressi", "Donatello", DC_FAMILY_CRESSI_GOA, 4, DC_TRANSPORT_SERIAL, NULL}, - {"Cressi", "Michelangelo", DC_FAMILY_CRESSI_GOA, 5, DC_TRANSPORT_SERIAL, NULL}, - {"Cressi", "Neon", DC_FAMILY_CRESSI_GOA, 9, DC_TRANSPORT_SERIAL, NULL}, + {"Cressi", "Cartesio", DC_FAMILY_CRESSI_GOA, 1, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, + {"Cressi", "Goa", DC_FAMILY_CRESSI_GOA, 2, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, + {"Cressi", "Leonardo 2.0", DC_FAMILY_CRESSI_GOA, 3, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, + {"Cressi", "Donatello", DC_FAMILY_CRESSI_GOA, 4, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, + {"Cressi", "Michelangelo", DC_FAMILY_CRESSI_GOA, 5, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, + {"Cressi", "Neon", DC_FAMILY_CRESSI_GOA, 9, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, + {"Cressi", "Nepto", DC_FAMILY_CRESSI_GOA, 10, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_cressi}, /* Zeagle N2iTiON3 */ {"Zeagle", "N2iTiON3", DC_FAMILY_ZEAGLE_N2ITION3, 0, DC_TRANSPORT_SERIAL, NULL}, {"Apeks", "Quantum X", DC_FAMILY_ZEAGLE_N2ITION3, 0, DC_TRANSPORT_SERIAL, NULL}, @@ -559,6 +565,31 @@ dc_match_number_with_prefix (const void *key, const void *value) return 1; } +static int +dc_match_hex_with_prefix (const void *key, const void *value) +{ + const char *str = (const char *) key; + const char *prefix = *(const char * const *) value; + + size_t n = strlen (prefix); + + if (strncmp (str, prefix, n) != 0) { + return 0; + } + + while (str[n] != 0) { + const char c = str[n]; + if ((c < '0' || c > '9') && + (c < 'A' || c > 'F') && + (c < 'a' || c > 'f')) { + return 0; + } + n++; + } + + return 1; +} + static int dc_match_oceanic (const void *key, const void *value) { @@ -575,6 +606,20 @@ dc_match_oceanic (const void *key, const void *value) return dc_match_number_with_prefix (key, &p); } +static int +dc_match_cressi (const void *key, const void *value) +{ + unsigned int model = *(const unsigned int *) value; + + char prefix[16] = {0}; + + dc_platform_snprintf(prefix, sizeof(prefix), "%u_", model); + + const char *p = prefix; + + return dc_match_hex_with_prefix (key, &p); +} + static int dc_filter_internal (const void *key, const void *values, size_t count, size_t size, dc_match_t match) { @@ -777,6 +822,7 @@ dc_filter_oceanic (dc_descriptor_t *descriptor, dc_transport_t transport, const 0x4744, // Aqualung i330R 0x4749, // Aqualung i200C (newer model) 0x474B, // Oceanic Geo Air + 0x474D, // Aqualung i330R Console }; if (transport == DC_TRANSPORT_BLE) { @@ -876,6 +922,26 @@ dc_filter_divesoft (dc_descriptor_t *descriptor, dc_transport_t transport, const return 1; } +static int +dc_filter_cressi (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata) +{ + static const unsigned int model[] = { + 1, // Cartesio + 2, // Goa + 3, // Leonardo 2.0 + 4, // Donatello + 5, // Michelangelo + 9, // Neon + 10, // Nepto + }; + + if (transport == DC_TRANSPORT_BLE) { + return DC_FILTER_INTERNAL (userdata, model, 0, dc_match_cressi); + } + + return 1; +} + dc_status_t dc_descriptor_iterator (dc_iterator_t **out) { diff --git a/src/libdivecomputer.symbols b/src/libdivecomputer.symbols index b346ef01..c04e6270 100644 --- a/src/libdivecomputer.symbols +++ b/src/libdivecomputer.symbols @@ -66,6 +66,9 @@ dc_bluetooth_device_free dc_bluetooth_iterator_new dc_bluetooth_open +dc_ble_uuid2str +dc_ble_str2uuid + dc_irda_device_get_address dc_irda_device_get_name dc_irda_device_free diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index 6c7da77c..0601c15b 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -513,6 +513,7 @@ static const oceanic_common_version_t versions[] = { {"AERISAIR \0\0 1024", 0, A300AI, &oceanic_vt4_layout}, {"SWVISION \0\0 1024", 0, VISION, &oceanic_vt4_layout}, {"XPSUBAIR \0\0 1024", 0, XPAIR, &oceanic_vt4_layout}, + {"AQUAI100 \0\0 1024", 0, I100V2, &oceanic_vt4_layout}, {"HOLLDG04 \0\0 2048", 0, TX1, &hollis_tx1_layout}, diff --git a/src/oceanic_atom2_parser.c b/src/oceanic_atom2_parser.c index a8304471..05a135c7 100644 --- a/src/oceanic_atom2_parser.c +++ b/src/oceanic_atom2_parser.c @@ -145,9 +145,9 @@ oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, const uns } else if (model == PROPLUSX) { parser->headersize = 3 * PAGESIZE; } else if (model == I550C || model == WISDOM4 || - model == I200CV2) { + model == I200CV2|| model == I100V2) { parser->headersize = 5 * PAGESIZE / 2; - } else if (model == I330R) { + } else if (model == I330R || model == I330R_C) { parser->logbooksize = 64; parser->headersize = parser->logbooksize + 80; parser->footersize = 48; @@ -199,6 +199,7 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim switch (parser->model) { case I330R: + case I330R_C: case DSX: datetime->year = p[7] + 2000; datetime->month = p[6]; @@ -225,6 +226,7 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim case I470TC: case I200CV2: case GEOAIR: + case I100V2: datetime->year = ((p[5] & 0xE0) >> 5) + ((p[7] & 0xE0) >> 2) + 2000; datetime->month = (p[3] & 0x0F); datetime->day = ((p[0] & 0x80) >> 3) + ((p[3] & 0xF0) >> 4); @@ -395,7 +397,7 @@ oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser) } else if (parser->model == VEO20 || parser->model == VEO30 || parser->model == OCS) { mode = (data[1] & 0x60) >> 5; - } else if (parser->model == I330R) { + } else if (parser->model == I330R || parser->model == I330R_C) { mode = data[2]; } else if (parser->model == DSX) { mode = data[45]; @@ -456,7 +458,7 @@ oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser) } else if (parser->model == WISDOM4) { o2_offset = header + 4; ngasmixes = 1; - } else if (parser->model == I330R) { + } else if (parser->model == I330R || parser->model == I330R_C) { ngasmixes = 3; o2_offset = parser->logbooksize + 16; } else if (parser->model == DSX) { @@ -481,7 +483,8 @@ oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser) if (data[o2_offset + i * o2_step]) { parser->oxygen[i] = data[o2_offset + i * o2_step]; // The i330R uses 20 as "Air" and 21 as 21% Nitrox - if (parser->model == I330R && parser->oxygen[i] == 20) { + if ((parser->model == I330R || parser->model == I330R_C) && + parser->oxygen[i] == 20) { parser->oxygen[i] = 21; } } else { @@ -545,13 +548,13 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns parser->model == F11A || parser->model == F11B || parser->model == MUNDIAL2 || parser->model == MUNDIAL3) *((double *) value) = array_uint16_le (data + 4) / 16.0 * FEET; - else if (parser->model == I330R || parser->model == DSX) + else if (parser->model == I330R || parser->model == I330R_C || parser->model == DSX) *((double *) value) = array_uint16_le (data + parser->footer + 10) / 10.0 * FEET; else *((double *) value) = (array_uint16_le (data + parser->footer + 4) & 0x0FFF) / 16.0 * FEET; break; case DC_FIELD_AVGDEPTH: - if (parser->model == I330R || parser->model == DSX) { + if (parser->model == I330R || parser->model == I330R_C || parser->model == DSX) { *((double *) value) = array_uint16_le (data + parser->footer + 12) / 10.0 * FEET; } else { return DC_STATUS_UNSUPPORTED; @@ -575,7 +578,7 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns water->type = DC_WATER_SALT; } water->density = 0.0; - } else if (parser->model == I330R || parser->model == DSX) { + } else if (parser->model == I330R || parser->model == I330R_C || parser->model == DSX) { unsigned int settings = array_uint32_le (data + parser->logbooksize + 12); if (settings & 0x10000) { water->type = DC_WATER_FRESH; @@ -693,7 +696,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ unsigned int time = 0; unsigned int interval = 1000; if (!is_freedive (parser->mode, parser->model)) { - if (parser->model == I330R || parser->model == DSX) { + if (parser->model == I330R || parser->model == I330R_C || parser->model == DSX) { interval = data[parser->logbooksize + 36] * 1000; } else { unsigned int offset = 0x17; @@ -728,7 +731,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I750TC || parser->model == PROPLUSX || parser->model == I770R || parser->model == I470TC || parser->model == SAGE || parser->model == BEACON || - parser->model == GEOAIR || parser->model == I330R) { + parser->model == GEOAIR || parser->model == I330R || + parser->model == I330R_C) { samplesize = PAGESIZE; } else if (parser->model == DSX) { samplesize = 32; @@ -747,7 +751,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I300C || parser->model == TALIS || parser->model == I200C || parser->model == I200CV2 || parser->model == GEO40 || parser->model == VEO40 || - parser->model == I330R) { + parser->model == I330R || parser->model == I330R_C || + parser->model == I100V2) { have_pressure = 0; } @@ -902,7 +907,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I300C || parser->model == I200C || parser->model == GEO40 || parser->model == VEO40 || parser->model == I470TC || parser->model == I200CV2 || - parser->model == GEOAIR) { + parser->model == GEOAIR || parser->model == I100V2) { temperature = data[offset + 3]; } else if (parser->model == OCS || parser->model == TX1) { temperature = data[offset + 1]; @@ -916,7 +921,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I770R|| parser->model == SAGE || parser->model == BEACON) { temperature = data[offset + 11]; - } else if (parser->model == I330R || parser->model == DSX) { + } else if (parser->model == I330R || parser->model == I330R_C || parser->model == DSX) { temperature = array_uint16_le(data + offset + 10); } else { unsigned int sign; @@ -940,7 +945,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ else temperature += (data[offset + 7] & 0x0C) >> 2; } - if (parser->model == I330R || parser->model == DSX) { + if (parser->model == I330R || parser->model == I330R_C || parser->model == DSX) { sample.temperature = ((temperature / 10.0) - 32.0) * (5.0 / 9.0); } else { sample.temperature = (temperature - 32.0) * (5.0 / 9.0); @@ -995,15 +1000,15 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I300C || parser->model == I200C || parser->model == GEO40 || parser->model == VEO40 || parser->model == I470TC || parser->model == I200CV2 || - parser->model == GEOAIR) + parser->model == GEOAIR || parser->model == I100V2) depth = (data[offset + 4] + (data[offset + 5] << 8)) & 0x0FFF; - else if (parser->model == I330R || parser->model == DSX) + else if (parser->model == I330R || parser->model == I330R_C || parser->model == DSX) depth = array_uint16_le (data + offset + 2); else if (parser->model == ATOM1) depth = data[offset + 3] * 16; else depth = (data[offset + 2] + (data[offset + 3] << 8)) & 0x0FFF; - if (parser->model == I330R || parser->model == DSX) { + if (parser->model == I330R || parser->model == I330R_C || parser->model == DSX) { sample.depth = depth / 10.0 * FEET; } else { sample.depth = depth / 16.0 * FEET; @@ -1062,11 +1067,11 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I450T || parser->model == I200C || parser->model == GEO40 || parser->model == VEO40 || parser->model == I470TC || parser->model == I200CV2 || - parser->model == GEOAIR) { + parser->model == GEOAIR || parser->model == I100V2) { decostop = (data[offset + 7] & 0xF0) >> 4; decotime = array_uint16_le(data + offset + 6) & 0x0FFF; have_deco = 1; - } else if (parser->model == I330R || parser->model == DSX) { + } else if (parser->model == I330R || parser->model == I330R_C || parser->model == DSX) { decostop = data[offset + 8]; if (decostop) { // Deco time @@ -1080,7 +1085,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ if (have_deco) { if (decostop) { sample.deco.type = DC_DECO_DECOSTOP; - if (parser->model == I330R || parser->model == DSX) { + if (parser->model == I330R || parser->model == I330R_C || parser->model == DSX) { sample.deco.depth = decostop * FEET; } else { sample.deco.depth = decostop * 10 * FEET; @@ -1119,7 +1124,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ } // PPO2 - if (parser->model == I330R) { + if (parser->model == I330R || parser->model == I330R_C) { sample.ppo2.sensor = DC_SENSOR_NONE; sample.ppo2.value = data[offset + 9] / 100.0; if (callback) callback (DC_SAMPLE_PPO2, &sample, userdata); diff --git a/src/oceanic_common.h b/src/oceanic_common.h index 43254a96..0e184ba9 100644 --- a/src/oceanic_common.h +++ b/src/oceanic_common.h @@ -122,12 +122,14 @@ extern "C" { #define AMPHOSAIR2 0x4658 #define BEACON 0x4742 #define I470TC 0x4743 +#define I100V2 0x4745 #define I200CV2 0x4749 #define GEOAIR 0x474B // i330r #define DSX 0x4741 #define I330R 0x4744 +#define I330R_C 0x474D #define PAGESIZE 0x10 #define FPMAXSIZE 0x200 diff --git a/src/parser.c b/src/parser.c index ba78399d..4c9dbbec 100644 --- a/src/parser.c +++ b/src/parser.c @@ -158,7 +158,7 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, const unsigned rc = cressi_leonardo_parser_create (&parser, context, data, size, model); break; case DC_FAMILY_CRESSI_GOA: - rc = cressi_goa_parser_create (&parser, context, data, size, model); + rc = cressi_goa_parser_create (&parser, context, data, size); break; case DC_FAMILY_ATOMICS_COBALT: rc = atomics_cobalt_parser_create (&parser, context, data, size); diff --git a/src/shearwater_common.c b/src/shearwater_common.c index 83527b62..c6192239 100644 --- a/src/shearwater_common.c +++ b/src/shearwater_common.c @@ -738,6 +738,7 @@ dc_status_t shearwater_common_get_model(shearwater_common_device_t *device, unsi *model = PERDIXAI; break; case 0xC407: + case 0xC964: *model = PERDIX2; break; case 0x0F0F: