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: