diff --git a/contrib/udev/libdivecomputer.rules b/contrib/udev/libdivecomputer.rules index e4e6fdc..02ef7da 100644 --- a/contrib/udev/libdivecomputer.rules +++ b/contrib/udev/libdivecomputer.rules @@ -10,6 +10,9 @@ SUBSYSTEM=="usb", ATTR{idVendor}=="1493", ATTR{idProduct}=="0033", GROUP="plugde # Suunto D5 SUBSYSTEM=="usb", ATTR{idVendor}=="1493", ATTR{idProduct}=="0035", GROUP="plugdev" +# Suunto EON Steel Black +SUBSYSTEM=="usb", ATTR{idVendor}=="1493", ATTR{idProduct}=="0036", GROUP="plugdev" + # Scubapro G2 SUBSYSTEM=="usb", ATTR{idVendor}=="2e6c", ATTR{idProduct}=="3201", GROUP="plugdev" diff --git a/examples/common.c b/examples/common.c index 345dfec..ef5aa74 100644 --- a/examples/common.c +++ b/examples/common.c @@ -94,6 +94,7 @@ static const backend_table_t g_backends[] = { {"extreme", DC_FAMILY_MCLEAN_EXTREME, 0}, {"lynx", DC_FAMILY_LIQUIVISION_LYNX, 0}, {"sp2", DC_FAMILY_SPORASUB_SP2, 0}, + {"excursion", DC_FAMILY_DEEPSIX_EXCURSION, 0}, // Not merged upstream yet {"descentmk1", DC_FAMILY_GARMIN, 0}, diff --git a/examples/dctool_scan.c b/examples/dctool_scan.c index a68d46b..868a9a7 100644 --- a/examples/dctool_scan.c +++ b/examples/dctool_scan.c @@ -158,7 +158,7 @@ dctool_scan_run (int argc, char *argv[], dc_context_t *context, dc_descriptor_t // Show help message. if (help) { - dctool_command_showhelp (&dctool_list); + dctool_command_showhelp (&dctool_scan); return EXIT_SUCCESS; } diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h index 6e2c499..c8c7439 100644 --- a/include/libdivecomputer/common.h +++ b/include/libdivecomputer/common.h @@ -114,6 +114,8 @@ typedef enum dc_family_t { DC_FAMILY_LIQUIVISION_LYNX = (17 << 16), /* Sporasub */ DC_FAMILY_SPORASUB_SP2 = (18 << 16), + /* Deep Six */ + DC_FAMILY_DEEPSIX_EXCURSION = (19 << 16), // Not merged upstream yet /* Garmin */ diff --git a/msvc/libdivecomputer.vcxproj b/msvc/libdivecomputer.vcxproj index 2f99a43..5c8fe3a 100644 --- a/msvc/libdivecomputer.vcxproj +++ b/msvc/libdivecomputer.vcxproj @@ -182,6 +182,8 @@ + + @@ -304,6 +306,7 @@ + diff --git a/src/Makefile.am b/src/Makefile.am index dd88eac..978a9e6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -75,6 +75,7 @@ libdivecomputer_la_SOURCES = \ mclean_extreme.h mclean_extreme.c mclean_extreme_parser.c \ liquivision_lynx.h liquivision_lynx.c liquivision_lynx_parser.c \ sporasub_sp2.h sporasub_sp2.c sporasub_sp2_parser.c \ + deepsix_excursion.h deepsix_excursion.c deepsix_excursion_parser.c \ socket.h socket.c \ irda.c \ usb.c \ diff --git a/src/array.c b/src/array.c index de15bc1..1777a01 100644 --- a/src/array.c +++ b/src/array.c @@ -160,6 +160,18 @@ array_convert_str2num (const unsigned char data[], unsigned int size) return value; } +unsigned int +array_convert_bcd2dec (const unsigned char data[], unsigned int size) +{ + unsigned int value = 0; + for (unsigned int i = 0; i < size; ++i) { + value *= 100; + value += bcd2dec(data[i]); + } + + return value; +} + unsigned int array_uint_be (const unsigned char data[], unsigned int n) { diff --git a/src/array.h b/src/array.h index da70efa..d69fa32 100644 --- a/src/array.h +++ b/src/array.h @@ -52,6 +52,9 @@ array_convert_hex2bin (const unsigned char input[], unsigned int isize, unsigned unsigned int array_convert_str2num (const unsigned char data[], unsigned int size); +unsigned int +array_convert_bcd2dec (const unsigned char data[], unsigned int size); + unsigned int array_uint_be (const unsigned char data[], unsigned int n); diff --git a/src/cressi_leonardo_parser.c b/src/cressi_leonardo_parser.c index d7cf0de..d4f37f5 100644 --- a/src/cressi_leonardo_parser.c +++ b/src/cressi_leonardo_parser.c @@ -178,6 +178,9 @@ cressi_leonardo_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac unsigned int gasmix_previous = 0xFFFFFFFF; unsigned int gasmix = 0; + if (parser->model == DRAKE) { + gasmix = gasmix_previous; + } unsigned int offset = SZ_HEADER; while (offset + 2 <= size) { diff --git a/src/deepsix_excursion.c b/src/deepsix_excursion.c new file mode 100644 index 0000000..378e20f --- /dev/null +++ b/src/deepsix_excursion.c @@ -0,0 +1,456 @@ +/* + * libdivecomputer + * + * Copyright (C) 2021 Ryan Gardner, 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 + */ + +#include +#include +#include + +#include "deepsix_excursion.h" +#include "context-private.h" +#include "device-private.h" +#include "platform.h" +#include "checksum.h" +#include "array.h" + +#define MAXPACKET 255 + +#define HEADERSIZE 156 + +#define NSTEPS 1000 +#define STEP(i,n) (NSTEPS * (i) / (n)) + +#define FP_SIZE 6 +#define FP_OFFSET 12 + +#define DIR_WRITE 0x00 +#define DIR_READ 0x01 + +#define GRP_INFO 0xA0 +#define CMD_INFO_HARDWARE 0x01 +#define CMD_INFO_SOFTWARE 0x02 +#define CMD_INFO_SERIAL 0x03 +#define CMD_INFO_LASTDIVE 0x04 + +#define GRP_SETTINGS 0xB0 +#define CMD_SETTINGS_DATE 0x01 +#define CMD_SETTINGS_TIME 0x03 +#define CMD_SETTINGS_STORE 0x27 +#define CMD_SETTINGS_LOAD 0x28 + +#define GRP_DIVE 0xC0 +#define CMD_DIVE_HEADER 0x02 +#define CMD_DIVE_PROFILE 0x03 + +typedef struct deepsix_excursion_device_t { + dc_device_t base; + dc_iostream_t *iostream; + unsigned char fingerprint[FP_SIZE]; +} deepsix_excursion_device_t; + +static dc_status_t deepsix_excursion_device_set_fingerprint (dc_device_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +static dc_status_t deepsix_excursion_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime); + +static const dc_device_vtable_t deepsix_excursion_device_vtable = { + sizeof(deepsix_excursion_device_t), + DC_FAMILY_DEEPSIX_EXCURSION, + deepsix_excursion_device_set_fingerprint, /* set_fingerprint */ + NULL, /* read */ + NULL, /* write */ + NULL, /* dump */ + deepsix_excursion_device_foreach, /* foreach */ + deepsix_excursion_device_timesync, /* timesync */ + NULL, /* close */ +}; + +static dc_status_t +deepsix_excursion_send (deepsix_excursion_device_t *device, unsigned char grp, unsigned char cmd, unsigned char dir, const unsigned char data[], unsigned int size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + unsigned char packet[4 + MAXPACKET + 1]; + + if (device_is_cancelled (abstract)) + return DC_STATUS_CANCELLED; + + if (size > MAXPACKET) + return DC_STATUS_INVALIDARGS; + + // Setup the data packet + packet[0] = grp; + packet[1] = cmd; + packet[2] = dir; + packet[3] = size; + if (size) { + memcpy(packet + 4, data, size); + } + packet[size + 4] = checksum_add_uint8 (packet, size + 4, 0) ^ 0xFF; + + // Send the data packet. + status = dc_iostream_write (device->iostream, packet, 4 + size + 1, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the command."); + return status; + } + + return status; +} + +static dc_status_t +deepsix_excursion_recv (deepsix_excursion_device_t *device, unsigned char grp, unsigned char cmd, unsigned char dir, unsigned char data[], unsigned int size, unsigned int *actual) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + unsigned char packet[4 + MAXPACKET + 1]; + size_t transferred = 0; + + // Read the packet header, payload and checksum. + status = dc_iostream_read (device->iostream, packet, sizeof(packet), &transferred); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the packet."); + return status; + } + + if (transferred < 4) { + ERROR (abstract->context, "Packet header too short ("DC_PRINTF_SIZE").", transferred); + return DC_STATUS_PROTOCOL; + } + + // Verify the packet header. + if (packet[0] != grp || packet[1] != cmd || packet[2] != dir) { + ERROR (device->base.context, "Unexpected packet header."); + return DC_STATUS_PROTOCOL; + } + + unsigned int len = packet[3]; + if (len > MAXPACKET) { + ERROR (abstract->context, "Packet header length too large (%u).", len); + return DC_STATUS_PROTOCOL; + } + + if (transferred < 4 + len + 1) { + ERROR (abstract->context, "Packet data too short ("DC_PRINTF_SIZE").", transferred); + return DC_STATUS_PROTOCOL; + } + + // Verify the checksum. + unsigned char csum = checksum_add_uint8 (packet, len + 4, 0) ^ 0xFF; + if (packet[len + 4] != csum) { + ERROR (abstract->context, "Unexpected packet checksum (%02x)", csum); + return DC_STATUS_PROTOCOL; + } + + if (len > size) { + ERROR (abstract->context, "Unexpected packet length (%u).", len); + return DC_STATUS_PROTOCOL; + } + + memcpy(data, packet + 4, len); + + if (actual) + *actual = len; + + return status; +} + +static dc_status_t +deepsix_excursion_transfer (deepsix_excursion_device_t *device, unsigned char grp, unsigned char cmd, unsigned char dir, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, unsigned int *actual) +{ + dc_status_t status = DC_STATUS_SUCCESS; + + status = deepsix_excursion_send (device, grp, cmd, dir, command, csize); + if (status != DC_STATUS_SUCCESS) + return status; + + status = deepsix_excursion_recv (device, grp + 1, cmd, dir, answer, asize, actual); + if (status != DC_STATUS_SUCCESS) + return status; + + return status; +} + +dc_status_t +deepsix_excursion_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) +{ + dc_status_t status = DC_STATUS_SUCCESS; + deepsix_excursion_device_t *device = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + device = (deepsix_excursion_device_t *) dc_device_allocate (context, &deepsix_excursion_device_vtable); + if (device == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Set the default values. + device->iostream = iostream; + memset(device->fingerprint, 0, sizeof(device->fingerprint)); + + // Set the serial communication protocol (115200 8N1). + status = dc_iostream_configure (device->iostream, 115200, 8, DC_PARITY_NONE, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the terminal attributes."); + goto error_free; + } + + // Set the timeout for receiving data (1000ms). + status = dc_iostream_set_timeout (device->iostream, 1000); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the timeout."); + goto error_free; + } + + // Make sure everything is in a sane state. + dc_iostream_sleep (device->iostream, 300); + dc_iostream_purge (device->iostream, DC_DIRECTION_ALL); + + *out = (dc_device_t *) device; + + return DC_STATUS_SUCCESS; + +error_free: + dc_device_deallocate ((dc_device_t *) device); + return status; +} + +static dc_status_t +deepsix_excursion_device_set_fingerprint (dc_device_t *abstract, const unsigned char *data, unsigned int size) +{ + deepsix_excursion_device_t *device = (deepsix_excursion_device_t *)abstract; + + if (size && size != sizeof (device->fingerprint)) + return DC_STATUS_INVALIDARGS; + + if (size) + memcpy (device->fingerprint, data, sizeof (device->fingerprint)); + else + memset (device->fingerprint, 0, sizeof (device->fingerprint)); + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) +{ + dc_status_t status = DC_STATUS_SUCCESS; + deepsix_excursion_device_t *device = (deepsix_excursion_device_t *) abstract; + + // Enable progress notifications. + dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Load the settings into memory. + status = deepsix_excursion_transfer (device, GRP_SETTINGS, CMD_SETTINGS_LOAD, DIR_WRITE, NULL, 0, NULL, 0, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to load the settings."); + return status; + } + + // Read the hardware version. + unsigned char rsp_hardware[6] = {0}; + status = deepsix_excursion_transfer (device, GRP_INFO, CMD_INFO_HARDWARE, DIR_READ, NULL, 0, rsp_hardware, sizeof(rsp_hardware), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the hardware version."); + return status; + } + + // Read the software version. + unsigned char rsp_software[6] = {0}; + status = deepsix_excursion_transfer (device, GRP_INFO, CMD_INFO_SOFTWARE, DIR_READ, NULL, 0, rsp_software, sizeof(rsp_software), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the software version."); + return status; + } + + // Read the serial number + unsigned char rsp_serial[12] = {0}; + status = deepsix_excursion_transfer (device, GRP_INFO, CMD_INFO_SERIAL, DIR_READ, NULL, 0, rsp_serial, sizeof(rsp_serial), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the serial number."); + return status; + } + + // Emit a device info event. + dc_event_devinfo_t devinfo; + devinfo.model = 0; + devinfo.firmware = array_uint16_be (rsp_software + 4); + devinfo.serial = array_convert_str2num (rsp_serial + 3, sizeof(rsp_serial) - 3); + device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); + + // Read the index of the last dive. + const unsigned char cmd_index[2] = {0}; + unsigned char rsp_index[2] = {0}; + status = deepsix_excursion_transfer (device, GRP_INFO, CMD_INFO_LASTDIVE, DIR_READ, cmd_index, sizeof(cmd_index), rsp_index, sizeof(rsp_index), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the last dive index."); + return status; + } + + // Calculate the number of dives. + unsigned int ndives = array_uint16_le (rsp_index); + + // Update and emit a progress event. + progress.maximum = ndives * NSTEPS; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + dc_buffer_t *buffer = dc_buffer_new(0); + if (buffer == NULL) { + ERROR (abstract->context, "Insufficient buffer space available."); + return DC_STATUS_NOMEMORY; + } + + for (unsigned int i = 0; i < ndives; ++i) { + unsigned int number = ndives - i; + + const unsigned char cmd_header[] = { + (number ) & 0xFF, + (number >> 8) & 0xFF}; + unsigned char rsp_header[HEADERSIZE] = {0}; + status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_HEADER, DIR_READ, cmd_header, sizeof(cmd_header), rsp_header, sizeof(rsp_header), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the dive header."); + goto error_free; + } + + if (memcmp(rsp_header + FP_OFFSET, device->fingerprint, sizeof(device->fingerprint)) == 0) + break; + + unsigned int length = array_uint32_le (rsp_header + 8); + + // Update and emit a progress event. + progress.current = i * NSTEPS + STEP(sizeof(rsp_header), sizeof(rsp_header) + length); + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + dc_buffer_clear(buffer); + dc_buffer_reserve(buffer, sizeof(rsp_header) + length); + + if (!dc_buffer_append(buffer, rsp_header, sizeof(rsp_header))) { + ERROR (abstract->context, "Insufficient buffer space available."); + status = DC_STATUS_NOMEMORY; + goto error_free; + } + + unsigned offset = 0; + while (offset < length) { + unsigned int len = 0; + const unsigned char cmd_profile[] = { + (number ) & 0xFF, + (number >> 8) & 0xFF, + (offset ) & 0xFF, + (offset >> 8) & 0xFF, + (offset >> 16) & 0xFF, + (offset >> 24) & 0xFF}; + unsigned char rsp_profile[MAXPACKET] = {0}; + status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_PROFILE, DIR_READ, cmd_profile, sizeof(cmd_profile), rsp_profile, sizeof(rsp_profile), &len); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the dive profile."); + goto error_free; + } + + // Remove padding from the last packet. + unsigned int n = len; + if (offset + n > length) { + n = length - offset; + } + + // Update and emit a progress event. + progress.current = i * NSTEPS + STEP(sizeof(rsp_header) + offset + n, sizeof(rsp_header) + length); + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + if (!dc_buffer_append(buffer, rsp_profile, n)) { + ERROR (abstract->context, "Insufficient buffer space available."); + status = DC_STATUS_NOMEMORY; + goto error_free; + } + + offset += n; + } + + unsigned char *data = dc_buffer_get_data(buffer); + unsigned int size = dc_buffer_get_size(buffer); + if (callback && !callback (data, size, data + FP_OFFSET, sizeof(device->fingerprint), userdata)) { + break; + } + } + +error_free: + dc_buffer_free(buffer); + return status; +} + +static dc_status_t +deepsix_excursion_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) +{ + dc_status_t status = DC_STATUS_SUCCESS; + deepsix_excursion_device_t *device = (deepsix_excursion_device_t *) abstract; + + if (datetime == NULL || datetime->year < 2000) { + ERROR (abstract->context, "Invalid date/time value specified."); + return DC_STATUS_INVALIDARGS; + } + + const unsigned char cmd_date[] = { + datetime->year - 2000, + datetime->month, + datetime->day}; + + const unsigned char cmd_time[] = { + datetime->hour, + datetime->minute, + datetime->second}; + + const unsigned char cmd_store[] = {0x00}; + + unsigned char rsp_date[sizeof(cmd_date)] = {0}; + status = deepsix_excursion_transfer (device, GRP_SETTINGS, CMD_SETTINGS_DATE, DIR_WRITE, cmd_date, sizeof(cmd_date), rsp_date, sizeof(rsp_date), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to set the date."); + return status; + } + + if (memcmp(rsp_date, cmd_date, sizeof(cmd_date)) != 0) { + ERROR (abstract->context, "Failed to verify the date."); + return DC_STATUS_PROTOCOL; + } + + unsigned char rsp_time[sizeof(cmd_time)] = {0}; + status = deepsix_excursion_transfer (device, GRP_SETTINGS, CMD_SETTINGS_TIME, DIR_WRITE, cmd_time, sizeof(cmd_time), rsp_time, sizeof(rsp_time), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to set the time."); + return status; + } + + if (memcmp(rsp_time, cmd_time, sizeof(cmd_time)) != 0) { + ERROR (abstract->context, "Failed to verify the time."); + return DC_STATUS_PROTOCOL; + } + + status = deepsix_excursion_transfer (device, GRP_SETTINGS, CMD_SETTINGS_STORE, DIR_WRITE, cmd_store, sizeof(cmd_store), NULL, 0, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to store the settings."); + return status; + } + + return status; +} diff --git a/src/deepsix_excursion.h b/src/deepsix_excursion.h new file mode 100644 index 0000000..42ecffc --- /dev/null +++ b/src/deepsix_excursion.h @@ -0,0 +1,43 @@ +/* + * libdivecomputer + * + * Copyright (C) 2021 Ryan Gardner, 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 + * + */ +#ifndef DEEPSIX_EXCURSION_H +#define DEEPSIX_EXCURSION_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +dc_status_t +deepsix_excursion_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream); + +dc_status_t +deepsix_excursion_parser_create (dc_parser_t **parser, dc_context_t *context); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* DEEPSIX_EXCURSION_H */ diff --git a/src/deepsix_excursion_parser.c b/src/deepsix_excursion_parser.c new file mode 100644 index 0000000..a5d312d --- /dev/null +++ b/src/deepsix_excursion_parser.c @@ -0,0 +1,251 @@ +/* + * libdivecomputer + * + * Copyright (C) 2021 Ryan Gardner, 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 + */ + +#include +#include +#include +#include + +#include + +#include "deepsix_excursion.h" +#include "context-private.h" +#include "parser-private.h" +#include "array.h" + +#define HEADERSIZE 156 + +#define ALARM 0x0001 +#define TEMPERATURE 0x0002 +#define DECO 0x0003 +#define CEILING 0x0004 +#define CNS 0x0005 + +#define DENSITY 1024.0 + +typedef struct deepsix_excursion_parser_t { + dc_parser_t base; +} deepsix_excursion_parser_t; + +static dc_status_t deepsix_excursion_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t deepsix_excursion_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); +static dc_status_t deepsix_excursion_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); +static dc_status_t deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); + +static const dc_parser_vtable_t deepsix_parser_vtable = { + sizeof(deepsix_excursion_parser_t), + DC_FAMILY_DEEPSIX_EXCURSION, + deepsix_excursion_parser_set_data, /* set_data */ + deepsix_excursion_parser_get_datetime, /* datetime */ + deepsix_excursion_parser_get_field, /* fields */ + deepsix_excursion_parser_samples_foreach, /* samples_foreach */ + NULL /* destroy */ +}; + +dc_status_t +deepsix_excursion_parser_create (dc_parser_t **out, dc_context_t *context) +{ + deepsix_excursion_parser_t *parser = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + parser = (deepsix_excursion_parser_t *) dc_parser_allocate (context, &deepsix_parser_vtable); + if (parser == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + *out = (dc_parser_t *) parser; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepsix_excursion_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) +{ + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepsix_excursion_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) +{ + const unsigned char *data = abstract->data; + unsigned int size = abstract->size; + + if (size < HEADERSIZE) + return DC_STATUS_DATAFORMAT; + + if (datetime) { + datetime->year = data[12] + 2000; + datetime->month = data[13]; + datetime->day = data[14]; + datetime->hour = data[15]; + datetime->minute = data[16]; + datetime->second = data[17]; + datetime->timezone = DC_TIMEZONE_NONE; + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepsix_excursion_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 < HEADERSIZE) + return DC_STATUS_DATAFORMAT; + + unsigned int atmospheric = array_uint32_le(data + 56); + + dc_salinity_t *water = (dc_salinity_t *) value; + + if (value) { + switch (type) { + case DC_FIELD_DIVETIME: + *((unsigned int *) value) = array_uint32_le(data + 20); + break; + case DC_FIELD_MAXDEPTH: + *((double *) value) = (signed int)(array_uint32_le(data + 28) - atmospheric) * (BAR / 1000.0) / (DENSITY * GRAVITY); + break; + break; + case DC_FIELD_TEMPERATURE_MINIMUM: + *((double *) value) = (signed int) array_uint32_le(data + 32) / 10.0; + break; + case DC_FIELD_ATMOSPHERIC: + *((double *) value) = atmospheric / 1000.0; + break; + case DC_FIELD_SALINITY: + water->type = DC_WATER_SALT; + water->density = DENSITY; + break; + case DC_FIELD_DIVEMODE: + switch (array_uint32_le(data + 4)) { + case 0: + *((dc_divemode_t *) value) = DC_DIVEMODE_OC; + break; + case 1: + *((dc_divemode_t *) value) = DC_DIVEMODE_GAUGE; + break; + case 2: + *((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE; + break; + default: + return DC_STATUS_DATAFORMAT; + } + break; + default: + return DC_STATUS_UNSUPPORTED; + } + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepsix_excursion_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 < HEADERSIZE) + return DC_STATUS_DATAFORMAT; + + int firmware4c = memcmp(data + 48, "D01-4C", 6) == 0; + + unsigned int maxtype = firmware4c ? TEMPERATURE : CNS; + + unsigned int interval = array_uint32_le(data + 24); + unsigned int atmospheric = array_uint32_le(data + 56); + + unsigned int time = 0; + unsigned int offset = HEADERSIZE; + while (offset + 1 < size) { + dc_sample_value_t sample = {0}; + + // Get the sample type. + unsigned int type = data[offset]; + if (type < 1 || type > maxtype) { + ERROR (abstract->context, "Unknown sample type (%u).", type); + return DC_STATUS_DATAFORMAT; + } + + // Get the sample length. + unsigned int length = 1; + if (type == ALARM || type == CEILING) { + length = 8; + } else if (type == TEMPERATURE || type == DECO || type == CNS) { + length = 6; + } + + // Verify the length. + if (offset + length > size) { + WARNING (abstract->context, "Unexpected end of data."); + break; + } + + unsigned int misc = data[offset + 1]; + unsigned int depth = array_uint16_le(data + offset + 2); + + if (type == TEMPERATURE) { + time += interval; + sample.time = time; + if (callback) callback(DC_SAMPLE_TIME, sample, userdata); + + sample.depth = (signed int)(depth - atmospheric) * (BAR / 1000.0) / (DENSITY * GRAVITY); + if (callback) callback(DC_SAMPLE_DEPTH, sample, userdata); + } + + if (type == ALARM) { + unsigned int alarm_time = array_uint16_le(data + offset + 4); + unsigned int alarm_value = array_uint16_le(data + offset + 6); + } else if (type == TEMPERATURE) { + unsigned int temperature = array_uint16_le(data + offset + 4); + if (firmware4c) { + if (temperature > 1300) { + length = 8; + } else if (temperature >= 10) { + sample.temperature = temperature / 10.0; + if (callback) callback(DC_SAMPLE_TEMPERATURE, sample, userdata); + } + } else { + sample.temperature = temperature / 10.0; + if (callback) callback(DC_SAMPLE_TEMPERATURE, sample, userdata); + } + } else if (type == DECO) { + unsigned int deco = array_uint16_le(data + offset + 4); + } else if (type == CEILING) { + unsigned int ceiling_depth = array_uint16_le(data + offset + 4); + unsigned int ceiling_time = array_uint16_le(data + offset + 6); + } else if (type == CNS) { + unsigned int cns = array_uint16_le(data + offset + 4); + sample.cns = cns; + if (callback) callback(DC_SAMPLE_CNS, sample, userdata); + } + + offset += length; + } + + return DC_STATUS_SUCCESS; +} diff --git a/src/descriptor.c b/src/descriptor.c index 6980867..10860e8 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -62,6 +62,7 @@ static int dc_filter_divesystem (dc_transport_t transport, const void *userdata, static int dc_filter_oceanic (dc_transport_t transport, const void *userdata, void *params); static int dc_filter_mclean (dc_transport_t transport, const void *userdata, void *params); static int dc_filter_atomic (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_deepsix (dc_transport_t transport, const void *userdata, void *params); // Not merged upstream yet static int dc_filter_garmin (dc_transport_t transport, const void *userdata, void *params); @@ -297,6 +298,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Mares", "Icon HD", DC_FAMILY_MARES_ICONHD , 0x14, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Icon HD Net Ready", DC_FAMILY_MARES_ICONHD , 0x15, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Puck Pro", DC_FAMILY_MARES_ICONHD , 0x18, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_mares}, + {"Mares", "Puck Pro +", DC_FAMILY_MARES_ICONHD , 0x18, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_mares}, {"Mares", "Nemo Wide 2", DC_FAMILY_MARES_ICONHD , 0x19, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Genius", DC_FAMILY_MARES_ICONHD , 0x1C, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_mares}, {"Mares", "Puck 2", DC_FAMILY_MARES_ICONHD , 0x1F, DC_TRANSPORT_SERIAL, NULL}, @@ -426,6 +428,11 @@ static const dc_descriptor_t g_descriptors[] = { {"Liquivision", "Kaon", DC_FAMILY_LIQUIVISION_LYNX, 3, DC_TRANSPORT_SERIAL, NULL}, /* Sporasub */ {"Sporasub", "SP2", DC_FAMILY_SPORASUB_SP2, 0, DC_TRANSPORT_SERIAL, NULL}, + /* Deep Six Excursion */ + {"Deep Six", "Excursion", DC_FAMILY_DEEPSIX_EXCURSION, 0, DC_TRANSPORT_BLE, dc_filter_deepsix}, + {"Crest", "CR-4", DC_FAMILY_DEEPSIX_EXCURSION, 0, DC_TRANSPORT_BLE, dc_filter_deepsix}, + {"Genesis", "Centauri", DC_FAMILY_DEEPSIX_EXCURSION, 0, DC_TRANSPORT_BLE, dc_filter_deepsix}, + {"Tusa", "TC1", DC_FAMILY_DEEPSIX_EXCURSION, 0, DC_TRANSPORT_BLE, dc_filter_deepsix}, // Not merged upstream yet /* Garmin -- model numbers as defined in FIT format; USB product id is (0x4000 | model) */ @@ -735,6 +742,22 @@ static int dc_filter_atomic (dc_transport_t transport, const void *userdata, voi return 1; } +static int dc_filter_deepsix (dc_transport_t transport, const void *userdata, void *params) +{ + static const char * const bluetooth[] = { + "EXCURSION", + "Crest-CR4", + "CENTAURI", + "TC1", + }; + + if (transport == DC_TRANSPORT_BLE) { + return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_name); + } + + return 1; +} + // Not merged upstream yet static int dc_filter_garmin (dc_transport_t transport, const void *userdata, void *params) { diff --git a/src/device.c b/src/device.c index 991937c..6ac4ac4 100644 --- a/src/device.c +++ b/src/device.c @@ -60,6 +60,7 @@ #include "mclean_extreme.h" #include "liquivision_lynx.h" #include "sporasub_sp2.h" +#include "deepsix_excursion.h" // Not merged upstream yet #include "garmin.h" @@ -228,6 +229,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr case DC_FAMILY_SPORASUB_SP2: rc = sporasub_sp2_device_open (&device, context, iostream); break; + case DC_FAMILY_DEEPSIX_EXCURSION: + rc = deepsix_excursion_device_open (&device, context, iostream); + break; default: return DC_STATUS_INVALIDARGS; diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index a975699..81b5a62 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -102,19 +102,19 @@ typedef struct hw_ostc_sample_info_t { typedef struct hw_ostc_layout_t { unsigned int datetime; unsigned int maxdepth; - unsigned int avgdepth; unsigned int divetime; - unsigned int atmospheric; - unsigned int salinity; - unsigned int duration; unsigned int temperature; - unsigned int battery; + unsigned int atmospheric; unsigned int desat; unsigned int firmware; + unsigned int battery; + unsigned int battery_percentage; + unsigned int salinity; + unsigned int avgdepth; + unsigned int duration; unsigned int deco_info1; unsigned int deco_info2; - unsigned int decomode; - unsigned int battery_percentage; + unsigned int divemode; } hw_ostc_layout_t; typedef struct hw_ostc_gasmix_t { @@ -158,55 +158,55 @@ static const dc_parser_vtable_t hw_ostc_parser_vtable = { static const hw_ostc_layout_t hw_ostc_layout_ostc = { 3, /* datetime */ 8, /* maxdepth */ - 45, /* avgdepth */ 10, /* divetime */ - 15, /* atmospheric */ - 43, /* salinity */ - 47, /* duration */ 13, /* temperature */ - 34, /* battery volt after dive */ + 15, /* atmospheric */ 17, /* desat */ 32, /* firmware */ + 34, /* battery volt after dive */ + 0, /* battery percentage TBD */ + 43, /* salinity */ + 45, /* avgdepth */ + 47, /* duration */ 49, /* deco_info1 */ 50, /* deco_info1 */ - 51, /* decomode */ - 0, /* battery percentage TBD */ + 51, /* divemode */ }; static const hw_ostc_layout_t hw_ostc_layout_frog = { 9, /* datetime */ 14, /* maxdepth */ - 45, /* avgdepth */ 16, /* divetime */ - 21, /* atmospheric */ - 43, /* salinity */ - 47, /* duration */ 19, /* temperature */ - 34, /* battery volt after dive */ + 21, /* atmospheric */ 23, /* desat */ 32, /* firmware */ + 34, /* battery volt after dive */ + 0, /* battery percentage TBD */ + 43, /* salinity */ + 45, /* avgdepth */ + 47, /* duration */ 49, /* deco_info1 */ 50, /* deco_info2 */ - 51, /* decomode */ - 0, /* battery percentage TBD */ + 51, /* divemode */ }; static const hw_ostc_layout_t hw_ostc_layout_ostc3 = { 12, /* datetime */ 17, /* maxdepth */ - 73, /* avgdepth */ 19, /* divetime */ - 24, /* atmospheric */ - 70, /* salinity */ - 75, /* duration */ 22, /* temperature */ - 50, /* battery volt after dive */ + 24, /* atmospheric */ 26, /* desat */ 48, /* firmware */ + 50, /* battery volt after dive */ + 59, /* battery percentage */ + 70, /* salinity */ + 73, /* avgdepth */ + 75, /* duration */ 77, /* deco_info1 */ 78, /* deco_info2 */ - 79, /* decomode */ - 59, /* battery percentage */ + 82, /* divemode */ }; static unsigned int @@ -306,7 +306,7 @@ hw_ostc_parser_cache (hw_ostc_parser_t *parser) } } // The first fixed setpoint is the initial setpoint in CCR mode. - if (data[82] == OSTC3_CC) { + if (data[layout->divemode] == OSTC3_CC || data[layout->divemode] == OSTC3_PSCR) { initial_setpoint = data[60]; } // Initial CNS @@ -554,7 +554,7 @@ hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned break; case DC_FIELD_DIVEMODE: if (version == 0x21) { - switch (data[51]) { + switch (data[layout->divemode]) { case OSTC_APNEA: *((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE; break; @@ -576,7 +576,7 @@ hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned return DC_STATUS_DATAFORMAT; } } else if (version == 0x22) { - switch (data[51]) { + switch (data[layout->divemode]) { case FROG_ZHL16: case FROG_ZHL16_GF: *((dc_divemode_t *) value) = DC_DIVEMODE_OC; @@ -588,7 +588,7 @@ hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned return DC_STATUS_DATAFORMAT; } } else if (version == 0x23 || version == 0x24) { - switch (data[82]) { + switch (data[layout->divemode]) { case OSTC3_OC: *((dc_divemode_t *) value) = DC_DIVEMODE_OC; break; @@ -653,28 +653,28 @@ hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned case 4: /* Deco model */ string->desc = "Deco model"; - if (((version == 0x23 || version == 0x24) && data[layout->decomode] == OSTC3_ZHL16) || - (version == 0x22 && data[layout->decomode] == FROG_ZHL16) || - (version == 0x21 && (data[layout->decomode] == OSTC_ZHL16_OC || data[layout->decomode] == OSTC_ZHL16_CC))) + if (((version == 0x23 || version == 0x24) && data[layout->divemode] == OSTC3_ZHL16) || + (version == 0x22 && data[layout->divemode] == FROG_ZHL16) || + (version == 0x21 && (data[layout->divemode] == OSTC_ZHL16_OC || data[layout->divemode] == OSTC_ZHL16_CC))) strncpy(buf, "ZH-L16", BUFLEN); - else if (((version == 0x23 || version == 0x24) && data[layout->decomode] == OSTC3_ZHL16_GF) || - (version == 0x22 && data[layout->decomode] == FROG_ZHL16_GF) || - (version == 0x21 && (data[layout->decomode] == OSTC_ZHL16_OC_GF || data[layout->decomode] == OSTC_ZHL16_CC_GF))) + else if (((version == 0x23 || version == 0x24) && data[layout->divemode] == OSTC3_ZHL16_GF) || + (version == 0x22 && data[layout->divemode] == FROG_ZHL16_GF) || + (version == 0x21 && (data[layout->divemode] == OSTC_ZHL16_OC_GF || data[layout->divemode] == OSTC_ZHL16_CC_GF))) strncpy(buf, "ZH-L16-GF", BUFLEN); - else if (((version == 0x24) && data[layout->decomode] == OSTC4_VPM)) + else if (((version == 0x24) && data[layout->divemode] == OSTC4_VPM)) strncpy(buf, "VPM", BUFLEN); else return DC_STATUS_DATAFORMAT; break; case 5: /* Deco model info */ string->desc = "Deco model info"; - if (((version == 0x23 || version == 0x24) && data[layout->decomode] == OSTC3_ZHL16) || - (version == 0x22 && data[layout->decomode] == FROG_ZHL16) || - (version == 0x21 && (data[layout->decomode] == OSTC_ZHL16_OC || data[layout->decomode] == OSTC_ZHL16_CC))) + if (((version == 0x23 || version == 0x24) && data[layout->divemode] == OSTC3_ZHL16) || + (version == 0x22 && data[layout->divemode] == FROG_ZHL16) || + (version == 0x21 && (data[layout->divemode] == OSTC_ZHL16_OC || data[layout->divemode] == OSTC_ZHL16_CC))) snprintf(buf, BUFLEN, "Saturation %u, Desaturation %u", layout->deco_info1, layout->deco_info2); - else if (((version == 0x23 || version == 0x24) && data[layout->decomode] == OSTC3_ZHL16_GF) || - (version == 0x22 && data[layout->decomode] == FROG_ZHL16_GF) || - (version == 0x21 && (data[layout->decomode] == OSTC_ZHL16_OC_GF || data[layout->decomode] == OSTC_ZHL16_CC_GF))) + else if (((version == 0x23 || version == 0x24) && data[layout->divemode] == OSTC3_ZHL16_GF) || + (version == 0x22 && data[layout->divemode] == FROG_ZHL16_GF) || + (version == 0x21 && (data[layout->divemode] == OSTC_ZHL16_OC_GF || data[layout->divemode] == OSTC_ZHL16_CC_GF))) snprintf(buf, BUFLEN, "GF %u/%u", data[layout->deco_info1], data[layout->deco_info2]); else return DC_STATUS_DATAFORMAT; diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index 7ef528e..330b771 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -823,9 +823,15 @@ mares_iconhd_device_foreach_raw (dc_device_t *abstract, dc_dive_callback_t callb samplesize = 14; fingerprint = 0x40; } else if (model == SMARTAIR) { - headersize = 0x84; - samplesize = 12; - fingerprint = 2; + if (mode == FREEDIVE) { + headersize = 0x30; + samplesize = 6; + fingerprint = 0x22; + } else { + headersize = 0x84; + samplesize = 12; + fingerprint = 2; + } } if (offset < headersize) break; @@ -844,7 +850,7 @@ mares_iconhd_device_foreach_raw (dc_device_t *abstract, dc_dive_callback_t callb // end of the ringbuffer. The current dive is incomplete (partially // overwritten with newer data), and processing should stop. unsigned int nbytes = 4 + headersize + nsamples * samplesize; - if (model == ICONHDNET || model == QUADAIR || model == SMARTAIR) { + if (model == ICONHDNET || model == QUADAIR || (model == SMARTAIR && mode != FREEDIVE)) { nbytes += (nsamples / 4) * 8; } else if (model == SMARTAPNEA) { unsigned int settings = array_uint16_le (buffer + offset - headersize + 0x1C); diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index e4023bb..87440c3 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -37,6 +37,8 @@ (((major) & 0xFF) << 8) | \ ((minor) & 0xFF)) +#define UNSUPPORTED 0xFFFFFFFF + #define SMART 0x000010 #define SMARTAPNEA 0x010010 #define ICONHD 0x14 @@ -122,6 +124,19 @@ typedef struct mares_iconhd_parser_t mares_iconhd_parser_t; +typedef struct mares_iconhd_layout_t { + unsigned int settings; + unsigned int datetime; + unsigned int divetime; + unsigned int maxdepth; + unsigned int atmospheric; + unsigned int atmospheric_divisor; + unsigned int temperature_min; + unsigned int temperature_max; + unsigned int gasmixes; + unsigned int tanks; +} mares_iconhd_layout_t; + typedef struct mares_iconhd_gasmix_t { unsigned int oxygen; unsigned int helium; @@ -153,6 +168,103 @@ struct mares_iconhd_parser_t { unsigned int serial; mares_iconhd_gasmix_t gasmix[NGASMIXES]; mares_iconhd_tank_t tank[NTANKS]; + const mares_iconhd_layout_t *layout; +}; + +static const mares_iconhd_layout_t iconhd = { + 0x0C, /* settings */ + 0x02, /* datetime */ + UNSUPPORTED, /* divetime */ + 0x00, /* maxdepth */ + 0x22, 8, /* atmospheric */ + 0x42, /* temperature_min */ + 0x44, /* temperature_max */ + 0x10, /* gasmixes */ + UNSUPPORTED, /* tanks */ +}; + +static const mares_iconhd_layout_t iconhdnet = { + 0x0C, /* settings */ + 0x02, /* datetime */ + UNSUPPORTED, /* divetime */ + 0x00, /* maxdepth */ + 0x22, 8, /* atmospheric */ + 0x42, /* temperature_min */ + 0x44, /* temperature_max */ + 0x10, /* gasmixes */ + 0x58, /* tanks */ +}; + +static const mares_iconhd_layout_t smartair = { + 0x0C, /* settings */ + 0x02, /* datetime */ + UNSUPPORTED, /* divetime */ + 0x00, /* maxdepth */ + 0x22, 8, /* atmospheric */ + 0x42, /* temperature_min */ + 0x44, /* temperature_max */ + 0x10, /* gasmixes */ + 0x5C, /* tanks */ +}; + +static const mares_iconhd_layout_t smartapnea = { + 0x1C, /* settings */ + 0x40, /* datetime */ + 0x24, /* divetime */ + 0x3A, /* maxdepth */ + 0x38, 1, /* atmospheric */ + 0x3E, /* temperature_min */ + 0x3C, /* temperature_max */ + UNSUPPORTED, /* gasmixes */ + UNSUPPORTED, /* tanks */ +}; + +static const mares_iconhd_layout_t smart_freedive = { + 0x08, /* settings */ + 0x20, /* datetime */ + 0x0C, /* divetime */ + 0x1A, /* maxdepth */ + 0x18, 1, /* atmospheric */ + 0x1C, /* temperature_min */ + 0x1E, /* temperature_max */ + UNSUPPORTED, /* gasmixes */ + UNSUPPORTED, /* tanks */ +}; + +static const mares_iconhd_layout_t smartair_freedive = { + 0x08, /* settings */ + 0x22, /* datetime */ + 0x0E, /* divetime */ + 0x1C, /* maxdepth */ + 0x1A, 1, /* atmospheric */ + 0x20, /* temperature_min */ + 0x1E, /* temperature_max */ + UNSUPPORTED, /* gasmixes */ + UNSUPPORTED, /* tanks */ +}; + +static const mares_iconhd_layout_t genius = { + 0x0C, /* settings */ + 0x08, /* datetime */ + UNSUPPORTED, /* divetime */ + 0x22, /* maxdepth */ + 0x3E, 1, /* atmospheric */ + 0x28, /* temperature_min */ + 0x26, /* temperature_max */ + 0x54, /* gasmixes */ + 0x54, /* tanks */ +}; + +static const mares_iconhd_layout_t horizon = { + 0x0C, /* settings */ + 0x08, /* datetime */ + UNSUPPORTED, /* divetime */ + 0x22 + 8, /* maxdepth */ + 0x3E + 8, 1, /* atmospheric */ + 0x28 + 8, /* temperature_min */ + 0x26 + 8, /* temperature_max */ + 0x54 + 8, /* gasmixes */ + 0x54 + 8, /* tanks */ }; static dc_status_t mares_iconhd_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); @@ -236,23 +348,39 @@ mares_iconhd_cache (mares_iconhd_parser_t *parser) // Get the header and sample size. unsigned int headersize = 0x5C; unsigned int samplesize = 8; + const mares_iconhd_layout_t *layout = &iconhd; if (parser->model == ICONHDNET) { headersize = 0x80; samplesize = 12; - } else if (parser->model == QUADAIR || parser->model == SMARTAIR) { + layout = &iconhdnet; + } else if (parser->model == QUADAIR) { headersize = 0x84; samplesize = 12; + layout = &smartair; } else if (parser->model == SMART) { if (mode == ICONHD_FREEDIVE) { headersize = 0x2E; samplesize = 6; + layout = &smart_freedive; } else { headersize = 0x5C; samplesize = 8; + layout = &iconhd; } } else if (parser->model == SMARTAPNEA) { headersize = 0x50; samplesize = 14; + layout = &smartapnea; + } else if (parser->model == SMARTAIR) { + if (mode == ICONHD_FREEDIVE) { + headersize = 0x30; + samplesize = 6; + layout = &smartair_freedive; + } else { + headersize = 0x84; + samplesize = 12; + layout = &smartair; + } } if (length < 4 + headersize) { @@ -266,14 +394,7 @@ mares_iconhd_cache (mares_iconhd_parser_t *parser) } // Get the dive settings. - unsigned int settings = 0; - if (parser->model == SMARTAPNEA) { - settings = array_uint16_le (p + 0x1C); - } else if (parser->mode == ICONHD_FREEDIVE) { - settings = array_uint16_le (p + 0x08); - } else { - settings = array_uint16_le (p + 0x0C); - } + unsigned int settings = array_uint16_le (p + layout->settings); // Get the sample interval. unsigned int interval = 0; @@ -291,7 +412,7 @@ mares_iconhd_cache (mares_iconhd_parser_t *parser) // Calculate the total number of bytes for this dive. unsigned int nbytes = 4 + headersize + nsamples * samplesize; - if (parser->model == ICONHDNET || parser->model == QUADAIR || parser->model == SMARTAIR) { + if (layout->tanks != UNSUPPORTED) { nbytes += (nsamples / 4) * 8; } else if (parser->model == SMARTAPNEA) { unsigned int divetime = array_uint32_le (p + 0x24); @@ -305,31 +426,34 @@ mares_iconhd_cache (mares_iconhd_parser_t *parser) // Gas mixes unsigned int ngasmixes = 0; mares_iconhd_gasmix_t gasmix[NGASMIXES_ICONHD] = {0}; - if (mode == ICONHD_GAUGE || mode == ICONHD_FREEDIVE) { - ngasmixes = 0; - } else if (mode == ICONHD_AIR) { - gasmix[0].oxygen = 21; - gasmix[0].helium = 0; - ngasmixes = 1; - } else { - // Count the number of active gas mixes. The active gas - // mixes are always first, so we stop counting as soon - // as the first gas marked as disabled is found. - ngasmixes = 0; - while (ngasmixes < NGASMIXES_ICONHD) { - if (p[0x10 + ngasmixes * 4 + 1] & 0x80) - break; - gasmix[ngasmixes].oxygen = p[0x10 + ngasmixes * 4]; - gasmix[ngasmixes].helium = 0; - ngasmixes++; + if (layout->gasmixes != UNSUPPORTED) { + if (mode == ICONHD_GAUGE || mode == ICONHD_FREEDIVE) { + ngasmixes = 0; + } else if (mode == ICONHD_AIR) { + gasmix[0].oxygen = 21; + gasmix[0].helium = 0; + ngasmixes = 1; + } else { + // Count the number of active gas mixes. The active gas + // mixes are always first, so we stop counting as soon + // as the first gas marked as disabled is found. + ngasmixes = 0; + while (ngasmixes < NGASMIXES_ICONHD) { + unsigned int offset = layout->gasmixes + ngasmixes * 4; + if (p[offset + 1] & 0x80) + break; + gasmix[ngasmixes].oxygen = p[offset]; + gasmix[ngasmixes].helium = 0; + ngasmixes++; + } } } // Tanks unsigned int ntanks = 0; mares_iconhd_tank_t tank[NTANKS_ICONHD] = {0}; - if (parser->model == ICONHDNET || parser->model == QUADAIR || parser->model == SMARTAIR) { - unsigned int tankoffset = (parser->model == ICONHDNET) ? 0x58 : 0x5C; + if (layout->tanks != UNSUPPORTED) { + unsigned int tankoffset = layout->tanks; while (ntanks < NTANKS_ICONHD) { tank[ntanks].volume = array_uint16_le (p + tankoffset + 0x0C + ntanks * 8 + 0); tank[ntanks].workpressure = array_uint16_le (p + tankoffset + 0x0C + ntanks * 8 + 2); @@ -362,6 +486,7 @@ mares_iconhd_cache (mares_iconhd_parser_t *parser) for (unsigned int i = 0; i < ntanks; ++i) { parser->tank[i] = tank[i]; } + parser->layout = layout; parser->cached = 1; return DC_STATUS_SUCCESS; @@ -394,8 +519,10 @@ mares_genius_cache (mares_iconhd_parser_t *parser) // The Horizon header has 8 bytes extra at offset 0x18. unsigned int extra = 0; + const mares_iconhd_layout_t * layout = &genius; if (logformat == 1) { extra = 8; + layout = &horizon; } // The Genius header (v1.x) has 10 bytes more at the end. @@ -415,7 +542,7 @@ mares_genius_cache (mares_iconhd_parser_t *parser) unsigned int nsamples = array_uint16_le (data + 0x20 + extra); // Get the dive settings. - unsigned int settings = array_uint32_le (data + 0x0C); + unsigned int settings = array_uint32_le (data + layout->settings); // Get the dive mode. unsigned int mode = settings & 0xF; @@ -452,7 +579,7 @@ mares_genius_cache (mares_iconhd_parser_t *parser) mares_iconhd_gasmix_t gasmix[NGASMIXES_GENIUS] = {0}; mares_iconhd_tank_t tank[NTANKS_GENIUS] = {0}; for (unsigned int i = 0; i < NGASMIXES_GENIUS; i++) { - unsigned int offset = 0x54 + extra + i * 20; + unsigned int offset = layout->tanks + i * 20; unsigned int gasmixparams = array_uint32_le(data + offset + 0); unsigned int beginpressure = array_uint16_le(data + offset + 4); unsigned int endpressure = array_uint16_le(data + offset + 6); @@ -508,6 +635,7 @@ mares_genius_cache (mares_iconhd_parser_t *parser) for (unsigned int i = 0; i < ntanks; ++i) { parser->tank[i] = tank[i]; } + parser->layout = layout; parser->cached = 1; return DC_STATUS_SUCCESS; @@ -567,6 +695,7 @@ mares_iconhd_parser_create (dc_parser_t **out, dc_context_t *context, unsigned i parser->tank[i].beginpressure = 0; parser->tank[i].endpressure = 0; } + parser->layout = NULL; *out = (dc_parser_t*) parser; @@ -602,6 +731,7 @@ mares_iconhd_parser_set_data (dc_parser_t *abstract, const unsigned char *data, parser->tank[i].beginpressure = 0; parser->tank[i].endpressure = 0; } + parser->layout = NULL; return DC_STATUS_SUCCESS; } @@ -627,15 +757,7 @@ mares_iconhd_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime } // Offset to the date/time field. - if (parser->model == GENIUS || parser->model == HORIZON) { - p += 0x08; - } else if (parser->model == SMARTAPNEA) { - p += 0x40; - } else if (parser->mode == ICONHD_FREEDIVE) { - p += 0x20; - } else { - p += 2; - } + p += parser->layout->datetime; if (datetime) { if (parser->model == GENIUS || parser->model == HORIZON) { @@ -699,31 +821,14 @@ mares_iconhd_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi if (value) { switch (type) { case DC_FIELD_DIVETIME: - if (parser->model == GENIUS || parser->model == HORIZON) { - *((unsigned int *) value) = parser->nsamples * parser->interval - parser->surftime; - } else if (parser->model == SMARTAPNEA) { - *((unsigned int *) value) = array_uint16_le (p + 0x24); - } else if (parser->mode == ICONHD_FREEDIVE) { - unsigned int divetime = 0; - unsigned int offset = 4; - for (unsigned int i = 0; i < parser->nsamples; ++i) { - divetime += array_uint16_le (abstract->data + offset + 2); - offset += parser->samplesize; - } - *((unsigned int *) value) = divetime; + if (parser->layout->divetime != UNSUPPORTED) { + *((unsigned int *) value) = array_uint16_le (p + parser->layout->divetime); } else { *((unsigned int *) value) = parser->nsamples * parser->interval - parser->surftime; } break; case DC_FIELD_MAXDEPTH: - if (parser->model == GENIUS || parser->model == HORIZON) - *((double *) value) = array_uint16_le (p + 0x22 + extra) / 10.0; - else if (parser->model == SMARTAPNEA) - *((double *) value) = array_uint16_le (p + 0x3A) / 10.0; - else if (parser->mode == ICONHD_FREEDIVE) - *((double *) value) = array_uint16_le (p + 0x1A) / 10.0; - else - *((double *) value) = array_uint16_le (p + 0x00) / 10.0; + *((double *) value) = array_uint16_le (p + parser->layout->maxdepth) / 10.0; break; case DC_FIELD_GASMIX_COUNT: *((unsigned int *) value) = parser->ngasmixes; @@ -758,14 +863,7 @@ mares_iconhd_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi } break; case DC_FIELD_ATMOSPHERIC: - if (parser->model == GENIUS || parser->model == HORIZON) - *((double *) value) = array_uint16_le (p + 0x3E + extra) / 1000.0; - else if (parser->model == SMARTAPNEA) - *((double *) value) = array_uint16_le (p + 0x38) / 1000.0; - else if (parser->mode == ICONHD_FREEDIVE) - *((double *) value) = array_uint16_le (p + 0x18) / 1000.0; - else - *((double *) value) = array_uint16_le (p + 0x22) / 8000.0; + *((double *) value) = array_uint16_le (p + parser->layout->atmospheric) / (1000.0 * parser->layout->atmospheric_divisor); break; case DC_FIELD_SALINITY: if (parser->model == GENIUS || parser->model == HORIZON) { @@ -804,24 +902,10 @@ mares_iconhd_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi } break; case DC_FIELD_TEMPERATURE_MINIMUM: - if (parser->model == GENIUS || parser->model == HORIZON) - *((double *) value) = (signed short) array_uint16_le (p + 0x28 + extra) / 10.0; - else if (parser->model == SMARTAPNEA) - *((double *) value) = (signed short) array_uint16_le (p + 0x3E) / 10.0; - else if (parser->mode == ICONHD_FREEDIVE) - *((double *) value) = (signed short) array_uint16_le (p + 0x1C) / 10.0; - else - *((double *) value) = (signed short) array_uint16_le (p + 0x42) / 10.0; + *((double *) value) = (signed short) array_uint16_le (p + parser->layout->temperature_min) / 10.0; break; case DC_FIELD_TEMPERATURE_MAXIMUM: - if (parser->model == GENIUS || parser->model == HORIZON) - *((double *) value) = (signed short) array_uint16_le (p + 0x26 + extra) / 10.0; - else if (parser->model == SMARTAPNEA) - *((double *) value) = (signed short) array_uint16_le (p + 0x3C) / 10.0; - else if (parser->mode == ICONHD_FREEDIVE) - *((double *) value) = (signed short) array_uint16_le (p + 0x1E) / 10.0; - else - *((double *) value) = (signed short) array_uint16_le (p + 0x44) / 10.0; + *((double *) value) = (signed short) array_uint16_le (p + parser->layout->temperature_max) / 10.0; break; case DC_FIELD_DIVEMODE: if (parser->model == GENIUS || parser->model == HORIZON) { @@ -905,9 +989,6 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t // Previous gas mix - initialize with impossible value unsigned int gasmix_previous = 0xFFFFFFFF; - unsigned int isairintegrated = (parser->model == ICONHDNET || parser->model == QUADAIR || - parser->model == SMARTAIR || parser->model == GENIUS || parser->model == HORIZON); - unsigned int offset = 4; unsigned int marker = 0; if (parser->model == GENIUS || parser->model == HORIZON) { @@ -1123,7 +1204,7 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t nsamples++; // Some extra data. - if (isairintegrated && (nsamples % 4) == 0) { + if (parser->layout->tanks != UNSUPPORTED && (nsamples % 4) == 0) { if ((parser->model == GENIUS || parser->model == HORIZON) && !mares_genius_isvalid (data + offset, AIRS_SIZE, AIRS_TYPE)) { ERROR (abstract->context, "Invalid AIRS record."); diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index 607b80f..8af753b 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -682,6 +682,22 @@ oceanic_atom2_packet (oceanic_atom2_device_t *device, const unsigned char comman return status; } + // Verify the number of bytes. + if (nbytes < 1) { + ERROR (abstract->context, "Invalid packet size (%u).", nbytes); + return DC_STATUS_PROTOCOL; + } + + // Verify the ACK byte of the answer. + if (packet[0] != ack) { + ERROR (abstract->context, "Unexpected answer start byte(s)."); + if (packet[0] == (unsigned char) ~ack) { + return DC_STATUS_UNSUPPORTED; + } else { + return DC_STATUS_PROTOCOL; + } + } + // Verify the number of bytes. if (nbytes < 1 + asize + crc_size) { ERROR (abstract->context, "Unexpected number of bytes received (%u %u).", nbytes, 1 + asize + crc_size); @@ -690,12 +706,6 @@ oceanic_atom2_packet (oceanic_atom2_device_t *device, const unsigned char comman nbytes -= 1 + crc_size; - // Verify the ACK byte of the answer. - if (packet[0] != ack) { - ERROR (abstract->context, "Unexpected answer start byte(s)."); - return DC_STATUS_PROTOCOL; - } - if (asize) { // Verify the checksum of the answer. unsigned short crc, ccrc; @@ -809,8 +819,14 @@ oceanic_atom2_ble_handshake(oceanic_atom2_device_t *device) // Send the command to the dive computer. rc = oceanic_atom2_transfer (device, handshake, sizeof(handshake), ACK, NULL, 0, 0); - if (rc != DC_STATUS_SUCCESS) - return rc; + if (rc != DC_STATUS_SUCCESS) { + if (rc == DC_STATUS_UNSUPPORTED) { + WARNING (abstract->context, "Bluetooth handshake not supported."); + return DC_STATUS_SUCCESS; + } else { + return rc; + } + } return DC_STATUS_SUCCESS; } @@ -906,8 +922,7 @@ oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream goto error_free; } - if (dc_iostream_get_transport (device->iostream) == DC_TRANSPORT_BLE && - model != PROPLUSX && model != I750TC && model != SAGE && model != BEACON) { + if (dc_iostream_get_transport (device->iostream) == DC_TRANSPORT_BLE) { status = oceanic_atom2_ble_handshake(device); if (status != DC_STATUS_SUCCESS) { goto error_free; diff --git a/src/parser.c b/src/parser.c index 61bad59..c97e825 100644 --- a/src/parser.c +++ b/src/parser.c @@ -60,6 +60,7 @@ #include "mclean_extreme.h" #include "liquivision_lynx.h" #include "sporasub_sp2.h" +#include "deepsix_excursion.h" // Not merged upstream yet #include "garmin.h" @@ -189,6 +190,9 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa case DC_FAMILY_SPORASUB_SP2: rc = sporasub_sp2_parser_create (&parser, context); break; + case DC_FAMILY_DEEPSIX_EXCURSION: + rc = deepsix_excursion_parser_create (&parser, context); + break; default: return DC_STATUS_INVALIDARGS; diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index 348ad28..624669b 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -20,6 +20,7 @@ */ #include +#include #include @@ -53,6 +54,7 @@ #define LOG_RECORD_CLOSING_6 0x26 #define LOG_RECORD_CLOSING_7 0x27 #define LOG_RECORD_INFO_EVENT 0x30 +#define LOG_RECORD_DIVE_SAMPLE_EXT 0xE1 #define LOG_RECORD_FINAL 0xFF #define INFO_EVENT_TAG_LOG 38 @@ -68,16 +70,26 @@ #define SC 0x08 #define OC 0x10 +#define M_CC 0 +#define M_OC_TEC 1 +#define M_GAUGE 2 +#define M_PPO2 3 +#define M_SC 4 +#define M_CC2 5 +#define M_OC_REC 6 +#define M_FREEDIVE 7 + #define METRIC 0 #define IMPERIAL 1 #define NGASMIXES 10 #define MAXSTRINGS 32 -#define NTANKS 2 +#define NTANKS 4 #define NRECORDS 8 #define PREDATOR 2 #define PETREL 3 +#define TERIC 8 #define UNDEFINED 0xFFFFFFFF @@ -90,8 +102,13 @@ typedef struct shearwater_predator_gasmix_t { typedef struct shearwater_predator_tank_t { unsigned int enabled; + unsigned int active; unsigned int beginpressure; unsigned int endpressure; + unsigned int pressure_max; + unsigned int pressure_reserve; + unsigned int serial; + char name[2]; unsigned int battery; } shearwater_predator_tank_t; @@ -217,8 +234,13 @@ shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsig parser->ntanks = 0; for (unsigned int i = 0; i < NTANKS; ++i) { parser->tank[i].enabled = 0; + parser->tank[i].active = 0; parser->tank[i].beginpressure = 0; parser->tank[i].endpressure = 0; + parser->tank[i].pressure_max = 0; + parser->tank[i].pressure_reserve = 0; + parser->tank[i].serial = 0; + memset (parser->tank[i].name, 0, sizeof (parser->tank[i].name)); parser->tank[i].battery = 0; parser->tankidx[i] = i; } @@ -276,8 +298,13 @@ shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char parser->ntanks = 0; for (unsigned int i = 0; i < NTANKS; ++i) { parser->tank[i].enabled = 0; + parser->tank[i].active = 0; parser->tank[i].beginpressure = 0; parser->tank[i].endpressure = 0; + parser->tank[i].pressure_max = 0; + parser->tank[i].pressure_reserve = 0; + parser->tank[i].serial = 0; + memset (parser->tank[i].name, 0, sizeof (parser->tank[i].name)); parser->tankidx[i] = i; } parser->calibrated = 0; @@ -310,7 +337,13 @@ shearwater_predator_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *d if (!dc_datetime_gmtime (datetime, ticks)) return DC_STATUS_DATAFORMAT; - datetime->timezone = DC_TIMEZONE_NONE; + if (parser->model == TERIC && parser->logversion >= 9 && parser->opening[5] != UNDEFINED) { + int utc_offset = (int) array_uint32_be (data + parser->opening[5] + 26); + int dst = data[parser->opening[5] + 30]; + datetime->timezone = utc_offset * 60 + dst * 3600; + } else { + datetime->timezone = DC_TIMEZONE_NONE; + } return DC_STATUS_SUCCESS; } @@ -400,6 +433,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) dc_parser_t *abstract = (dc_parser_t *) parser; const unsigned char *data = parser->base.data; unsigned int size = parser->base.size; + const char *ppo2_source = NULL; if (parser->cached) { return DC_STATUS_SUCCESS; @@ -448,7 +482,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) // byte opening and closing block. To minimize the differences // with the PNF format, all record offsets are assigned the same // value here. - for (unsigned int i = 0; i < NRECORDS; ++i) { + for (unsigned int i = 0; i <= 4; ++i) { parser->opening[i] = 0; parser->closing[i] = size - footersize; } @@ -458,7 +492,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) } // Default dive mode. - dc_divemode_t mode = DC_DIVEMODE_OC; + unsigned int divemode = M_OC_TEC; // Get the gas mixes. unsigned int ngasmixes = 0; @@ -482,7 +516,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) // Status flags. unsigned int status = data[offset + 11 + pnf]; if ((status & OC) == 0) { - mode = DC_DIVEMODE_CCR; + divemode = status & SC ? M_SC : M_CC; } // Gaschange. @@ -514,8 +548,8 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) // Tank pressure if (logversion >= 7) { - const unsigned int idx[NTANKS] = {27, 19}; - for (unsigned int i = 0; i < NTANKS; ++i) { + const unsigned int idx[2] = {27, 19}; + for (unsigned int i = 0; i < 2; ++i) { // Values above 0xFFF0 are special codes: // 0xFFFF AI is off // 0xFFFE No comms for 90 seconds+ @@ -528,8 +562,8 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) if (pressure < 0xFFF0) { unsigned int battery = 1u << (pressure >> 12); pressure &= 0x0FFF; - if (!tank[i].enabled) { - tank[i].enabled = 1; + if (!tank[i].active) { + tank[i].active = 1; tank[i].beginpressure = pressure; tank[i].endpressure = pressure; tank[i].battery = 0; @@ -539,16 +573,82 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) } } } + } else if (type == LOG_RECORD_DIVE_SAMPLE_EXT) { + // Tank pressure + if (logversion >= 13) { + for (unsigned int i = 0; i < 2; ++i) { + unsigned int pressure = array_uint16_be (data + offset + pnf + i * 2); + if (pressure < 0xFFF0) { + pressure &= 0x0FFF; + if (!tank[i + 2].active) { + tank[i + 2].active = 1; + tank[i + 2].beginpressure = pressure; + tank[i + 2].endpressure = pressure; + } + tank[i + 2].endpressure = pressure; + } + } + } } else if (type == LOG_RECORD_FREEDIVE_SAMPLE) { // Freedive record - mode = DC_DIVEMODE_FREEDIVE; + divemode = M_FREEDIVE; } else if (type >= LOG_RECORD_OPENING_0 && type <= LOG_RECORD_OPENING_7) { // Opening record parser->opening[type - LOG_RECORD_OPENING_0] = offset; - // Log version if (type == LOG_RECORD_OPENING_4) { + // Log version logversion = data[offset + 16]; + + // Air integration mode + if (logversion >= 7) { + unsigned int airmode = data[offset + 28]; + if (logversion < 13) { + if (airmode == 1 || airmode == 2) { + tank[airmode - 1].enabled = 1; + } else if (airmode == 3) { + tank[0].enabled = 1; + tank[1].enabled = 1; + } + } + if (airmode == 4) { + tank[0].enabled = 1; + tank[1].enabled = 1; + } + } + } else if (type == LOG_RECORD_OPENING_5) { + if (logversion >= 9) { + tank[0].serial = array_convert_bcd2dec (data + offset + 1, 3); + tank[0].pressure_max = array_uint16_be(data + offset + 6); + tank[0].pressure_reserve = array_uint16_be(data + offset + 8); + + tank[1].serial = array_convert_bcd2dec(data + offset + 10, 3); + tank[1].pressure_max = array_uint16_be(data + offset + 15); + tank[1].pressure_reserve = array_uint16_be(data + offset + 17); + } + } else if (type == LOG_RECORD_OPENING_6) { + if (logversion >= 13) { + tank[0].enabled = data[offset + 19]; + memcpy (tank[0].name, data + offset + 20, sizeof (tank[0].name)); + + tank[1].enabled = data[offset + 22]; + memcpy (tank[1].name, data + offset + 23, sizeof (tank[1].name)); + + tank[2].serial = array_convert_bcd2dec(data + offset + 25, 3); + tank[2].pressure_max = array_uint16_be(data + offset + 28); + tank[2].pressure_reserve = array_uint16_be(data + offset + 30); + } + } else if (type == LOG_RECORD_OPENING_7) { + if (logversion >= 13) { + tank[2].enabled = data[offset + 1]; + memcpy (tank[2].name, data + offset + 2, sizeof (tank[2].name)); + + tank[3].serial = array_convert_bcd2dec(data + offset + 4, 3); + tank[3].pressure_max = array_uint16_be(data + offset + 7); + tank[3].pressure_reserve = array_uint16_be(data + offset + 9); + tank[3].enabled = data[offset + 11]; + memcpy (tank[3].name, data + offset + 12, sizeof (tank[3].name)); + } } } else if (type >= LOG_RECORD_CLOSING_0 && type <= LOG_RECORD_CLOSING_7) { // Closing record @@ -602,12 +702,30 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) // uncalibrated). WARNING (abstract->context, "Disabled all O2 sensors due to a default calibration value."); parser->calibrated = 0; - if (mode != DC_DIVEMODE_OC) - dc_field_add_string(&parser->cache, "PPO2 source", "voted/averaged"); + ppo2_source = "voted/averaged"; } else { parser->calibrated = data[base]; - if (mode != DC_DIVEMODE_OC) - dc_field_add_string(&parser->cache, "PPO2 source", "cells"); + ppo2_source = "cells"; + } + + // Get the dive mode from the header (if available). + if (logversion >= 8) { + divemode = data[parser->opening[4] + (pnf ? 1 : 112)]; + } + + // Get the correct model number from the final block. + if (parser->final != UNDEFINED) { + parser->model = data[parser->final + 13]; + } + + // Fix the Teric tank serial number. + if (parser->model == TERIC) { + for (unsigned int i = 0; i < NTANKS; ++i) { + tank[i].serial = + ((tank[i].serial / 10000) % 100) + + ((tank[i].serial / 100) % 100) * 100 + + ((tank[i].serial ) % 100) * 10000; + } } // Cache the data for later use. @@ -621,7 +739,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) } parser->ntanks = 0; for (unsigned int i = 0; i < NTANKS; ++i) { - if (tank[i].enabled) { + if (tank[i].active) { parser->tankidx[i] = parser->ntanks; parser->tank[parser->ntanks] = tank[i]; parser->ntanks++; @@ -634,8 +752,6 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) parser->density = array_uint16_be (data + parser->opening[3] + (parser->pnf ? 3 : 83)); parser->cached = 1; - DC_ASSIGN_FIELD(parser->cache, DIVEMODE, mode); - dc_field_add_string_fmt(&parser->cache, "Serial", "%08x", parser->serial); // bytes 1-31 are identical in all formats dc_field_add_string_fmt(&parser->cache, "FW Version", "%2x", data[19]); @@ -645,6 +761,31 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) add_battery_info(parser, "T1 battery", tank[0].battery); add_battery_info(parser, "T2 battery", tank[1].battery); + switch (divemode) { + case M_CC: + case M_CC2: + DC_ASSIGN_FIELD(parser->cache, DIVEMODE, DC_DIVEMODE_CCR); + if (ppo2_source) + dc_field_add_string(&parser->cache, "PPO2 source", ppo2_source); + break; + case M_SC: + DC_ASSIGN_FIELD(parser->cache, DIVEMODE, DC_DIVEMODE_SCR); + break; + case M_OC_TEC: + case M_OC_REC: + DC_ASSIGN_FIELD(parser->cache, DIVEMODE, DC_DIVEMODE_OC); + break; + case M_GAUGE: + case M_PPO2: + DC_ASSIGN_FIELD(parser->cache, DIVEMODE, DC_DIVEMODE_GAUGE); + break; + case M_FREEDIVE: + DC_ASSIGN_FIELD(parser->cache, DIVEMODE, DC_DIVEMODE_FREEDIVE); + break; + default: + break; + } + return DC_STATUS_SUCCESS; } @@ -874,8 +1015,8 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal // for logversion 7 and newer (introduced for Perdix AI) // detect tank pressure if (parser->logversion >= 7) { - const unsigned int idx[NTANKS] = {27, 19}; - for (unsigned int i = 0; i < NTANKS; ++i) { + const unsigned int idx[2] = {27, 19}; + for (unsigned int i = 0; i < 2; ++i) { // Tank pressure // Values above 0xFFF0 are special codes: // 0xFFFF AI is off @@ -906,6 +1047,19 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal if (callback) callback (DC_SAMPLE_RBT, sample, userdata); } } + } else if (type == LOG_RECORD_DIVE_SAMPLE_EXT) { + // Tank pressure + if (parser->logversion >= 13) { + for (unsigned int i = 0; i < 2; ++i) { + unsigned int pressure = array_uint16_be (data + offset + pnf + i * 2); + if (pressure < 0xFFF0) { + pressure &= 0x0FFF; + sample.pressure.tank = parser->tankidx[i + 2]; + sample.pressure.value = pressure * 2 * PSI / BAR; + if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata); + } + } + } } else if (type == LOG_RECORD_FREEDIVE_SAMPLE) { // A freedive record is actually 4 samples, each 8-bytes, // packed into a standard 32-byte sized record. At the end