From 75f260a941db39b111e7c01a78915520e2cf9bb9 Mon Sep 17 00:00:00 2001 From: Ryan Gardner Date: Fri, 21 May 2021 14:23:49 -0400 Subject: [PATCH 01/26] Add support for the Deep Six Excursion Based on original work and code by Ryan Gardner, with some additional improvements and integration into libdivecomputer by Jef Driesen. --- examples/common.c | 1 + include/libdivecomputer/common.h | 2 + msvc/libdivecomputer.vcxproj | 3 + src/Makefile.am | 1 + src/deepsix_excursion.c | 438 +++++++++++++++++++++++++++++++ src/deepsix_excursion.h | 43 +++ src/deepsix_excursion_parser.c | 251 ++++++++++++++++++ src/descriptor.c | 17 ++ src/device.c | 4 + src/parser.c | 4 + 10 files changed, 764 insertions(+) create mode 100644 src/deepsix_excursion.c create mode 100644 src/deepsix_excursion.h create mode 100644 src/deepsix_excursion_parser.c diff --git a/examples/common.c b/examples/common.c index 6c1c131..10eb496 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}, }; static const transport_table_t g_transports[] = { diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h index 62f62f3..f6ca9fe 100644 --- a/include/libdivecomputer/common.h +++ b/include/libdivecomputer/common.h @@ -110,6 +110,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), } dc_family_t; #ifdef __cplusplus 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 19bca52..5cec346 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/deepsix_excursion.c b/src/deepsix_excursion.c new file mode 100644 index 0000000..687f6f6 --- /dev/null +++ b/src/deepsix_excursion.c @@ -0,0 +1,438 @@ +/* + * 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_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 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 = 0; + 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 06b0243..67b4a25 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); static dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item); @@ -421,6 +422,8 @@ 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}, }; static int @@ -720,6 +723,20 @@ 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", + }; + + if (transport == DC_TRANSPORT_BLE) { + return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_name); + } + + return 1; +} + + dc_status_t dc_descriptor_iterator (dc_iterator_t **out) { diff --git a/src/device.c b/src/device.c index 6c6732d..3cd3e3a 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" #include "device-private.h" #include "context-private.h" @@ -223,6 +224,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/parser.c b/src/parser.c index 2596de4..9a0f71c 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" #include "context-private.h" #include "parser-private.h" @@ -184,6 +185,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; } From c4b694fdb1876b72143c465c933517d27eac4ab0 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 12 Sep 2021 19:07:44 +0200 Subject: [PATCH 02/26] Fix the salinity parsing The parser->mode field is only initialized at the end of the function. The result is that the current code always used the default value (zero). Inside the function itself, the local variable should be used instead. --- src/mares_iconhd_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index a9ca390..15fab95 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -266,7 +266,7 @@ mares_iconhd_cache (mares_iconhd_parser_t *parser) unsigned int settings = 0; if (parser->model == SMARTAPNEA) { settings = array_uint16_le (p + 0x1C); - } else if (parser->mode == ICONHD_FREEDIVE) { + } else if (mode == ICONHD_FREEDIVE) { settings = array_uint16_le (p + 0x08); } else { settings = array_uint16_le (p + 0x0C); From 1a4798792e30b0d22d5d826e2691bfdd14bed908 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 12 Sep 2021 19:43:02 +0200 Subject: [PATCH 03/26] Use the divetime stored in the header There is no need to calculate the total time from the pseudo profile, because the dive time is also stored in the header. --- src/mares_iconhd_parser.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index 15fab95..52b56cb 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -697,13 +697,7 @@ mares_iconhd_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi } 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; + *((unsigned int *) value) = array_uint16_le (p + 0x0C); } else { *((unsigned int *) value) = parser->nsamples * parser->interval - parser->surftime; } From e52468e0c3d3349be2fb5a3a46da8bc468076969 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 12 Sep 2021 19:56:36 +0200 Subject: [PATCH 04/26] Use a layout descriptor Replace hardcoded constants with a layout descriptor. This reduces the amount of model specific conditions, and makes it easier to add support for new models. --- src/mares_iconhd_parser.c | 227 ++++++++++++++++++++++++-------------- 1 file changed, 146 insertions(+), 81 deletions(-) diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index 52b56cb..917e44e 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -35,6 +35,8 @@ (((major) & 0xFF) << 8) | \ ((minor) & 0xFF)) +#define UNSUPPORTED 0xFFFFFFFF + #define SMART 0x000010 #define SMARTAPNEA 0x010010 #define ICONHD 0x14 @@ -120,6 +122,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; @@ -150,6 +165,91 @@ struct mares_iconhd_parser_t { unsigned int ngasmixes; 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 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); @@ -233,23 +333,29 @@ 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; + layout = &iconhdnet; } else if (parser->model == QUADAIR || parser->model == SMARTAIR) { 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; } if (length < 4 + headersize) { @@ -263,14 +369,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 (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; @@ -288,7 +387,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); @@ -302,31 +401,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); @@ -359,6 +461,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; @@ -391,8 +494,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. @@ -412,7 +517,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; @@ -449,7 +554,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); @@ -505,6 +610,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; @@ -563,6 +669,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; @@ -598,6 +705,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; } @@ -623,15 +731,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) { @@ -692,25 +792,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 *) value) = array_uint16_le (p + 0x0C); + 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; @@ -745,14 +834,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) { @@ -791,24 +873,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) { @@ -881,9 +949,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) { @@ -1099,7 +1164,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."); From 7e075eb959a0e932d9fa55a00ece182e425123eb Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 31 Aug 2021 18:01:26 +0200 Subject: [PATCH 05/26] Add support for Mares Smart Air freedives The Mares Smart Air supports a freedive mode, which uses a different data format compared to the scuba dives. --- src/mares_iconhd.c | 14 ++++++++++---- src/mares_iconhd_parser.c | 24 +++++++++++++++++++++++- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index 2a70be3..21f88fa 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -790,9 +790,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; @@ -811,7 +817,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 917e44e..93cc5f2 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -228,6 +228,18 @@ static const mares_iconhd_layout_t smart_freedive = { 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 */ @@ -338,7 +350,7 @@ mares_iconhd_cache (mares_iconhd_parser_t *parser) headersize = 0x80; samplesize = 12; layout = &iconhdnet; - } else if (parser->model == QUADAIR || parser->model == SMARTAIR) { + } else if (parser->model == QUADAIR) { headersize = 0x84; samplesize = 12; layout = &smartair; @@ -356,6 +368,16 @@ mares_iconhd_cache (mares_iconhd_parser_t *parser) 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) { From 1c39c68203268985034de9c1214277c802178f24 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 27 Sep 2021 20:14:14 +0200 Subject: [PATCH 06/26] Add support for the Mares Puck Pro + The Mares Puck Pro + is compatible with the previous Puck Pro. Both models can't even be distinguished because they share the same model number and use the same product name in the version packet. --- src/descriptor.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/descriptor.c b/src/descriptor.c index 67b4a25..c462de1 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -293,6 +293,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}, From a8bcfb998b9c400e42b98db437cbf1aba6c13bfc Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 28 Sep 2021 12:28:14 +0200 Subject: [PATCH 07/26] Restore the original standard gravity factor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Uwatec/Scubapro dive computers are confirmed to internally use the approximated standard gravity (10.0 m/s²) for the depth conversion. Allthough this is technically wrong, users expect to get the same depth values as their dive computer shows. This partially reverts commit cfc9ddc380bdc5616893fc2af4e05204b5500ea2. --- src/uwatec_smart_parser.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uwatec_smart_parser.c b/src/uwatec_smart_parser.c index 7a12a4e..3e4ed09 100644 --- a/src/uwatec_smart_parser.c +++ b/src/uwatec_smart_parser.c @@ -801,7 +801,7 @@ uwatec_smart_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi *((unsigned int *) value) = array_uint16_le (data + table->divetime) * 60; break; case DC_FIELD_MAXDEPTH: - *((double *) value) = array_uint16_le (data + table->maxdepth) * (BAR / 1000.0) / (density * GRAVITY); + *((double *) value) = array_uint16_le (data + table->maxdepth) * (BAR / 1000.0) / (density * 10.0); break; case DC_FIELD_GASMIX_COUNT: *((unsigned int *) value) = parser->ngasmixes; @@ -1233,7 +1233,7 @@ uwatec_smart_parse (uwatec_smart_parser_t *parser, dc_sample_callback_t callback } if (have_depth) { - sample.depth = (signed int)(depth - depth_calibration) * (2.0 * BAR / 1000.0) / (density * GRAVITY); + sample.depth = (signed int)(depth - depth_calibration) * (2.0 * BAR / 1000.0) / (density * 10.0); if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); } From 9ff6e5caadbacb40b041da259ee9bf2563f6defa Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 12 Oct 2021 19:48:47 +0200 Subject: [PATCH 08/26] Remove the initial gas switch The Cressi Drake is a freedive computer and does not support gas switches. --- src/cressi_leonardo_parser.c | 3 +++ 1 file changed, 3 insertions(+) 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) { From ba4a119a4f2bb45d97d0c31adbbd1fa6e1b635f4 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 7 Oct 2021 19:56:52 +0200 Subject: [PATCH 09/26] Detect NAK response packets When the dive computer receives a command it doesn't support, it sends back a single byte NAK (0xA5) packet instead of the expected ACK byte (0x5A) at the start of the packet. Retrying is pointless in this case, because the next attempt will also fail. Instead, return immediately with an appropriate error code, and let the upper layers handle the unsupported command. Note that the detection is currently only enabled for BLE communication. --- src/oceanic_atom2.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index 060a1cc..c327ceb 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; From cd0f42804af70845c08e6b0d589b45feaef53f1c Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 7 Oct 2021 19:57:23 +0200 Subject: [PATCH 10/26] Ignore unsupported BLE handshake For some BLE enabled models, like the Oceanic Pro Plus X, Aqualung i750TC, Sherwood Sage and Sherwood Beacon, the BLE handshake command is not supported and therefore disabled. However, based on a bug report by a user with two Aqualung i770R dive computers, this detection mechanism based on the dive computer model number isn't sufficient. Allthough the two devices have the exact same firmware version (2A), the handshake command only works on the newest unit, and fails with a NAK response on the oldest unit. Remove the model based detection and always try to send the handshake command and rely on the NAK response to ignore the failure instead. An additional advantage is that we no longer have to (manually) maintain a list with the model numbers where the handshaking is known to fail. --- src/oceanic_atom2.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index c327ceb..16cf43e 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -819,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; } @@ -916,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; From b2040d9adb36d88b255d64ce0fcf8b69ffe0a518 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 10 Nov 2021 23:37:06 +0100 Subject: [PATCH 11/26] Add udev rule for the Suunto EON Steel Black --- contrib/udev/libdivecomputer.rules | 3 +++ 1 file changed, 3 insertions(+) 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" From af03e39383c5431b08ab474cb652e948c7465ca4 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 17 Nov 2021 19:10:32 +0100 Subject: [PATCH 12/26] Add support for the Crest CR-4 The Crest CR-4 is compatible with the Deep Six Excursion. --- src/descriptor.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/descriptor.c b/src/descriptor.c index c462de1..7108429 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -425,6 +425,7 @@ static const dc_descriptor_t g_descriptors[] = { {"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}, }; static int @@ -728,6 +729,7 @@ static int dc_filter_deepsix (dc_transport_t transport, const void *userdata, vo { static const char * const bluetooth[] = { "EXCURSION", + "Crest-CR4", }; if (transport == DC_TRANSPORT_BLE) { From 331bcbdaf711dfbd8e70b1ce04b14b99f47eb10b Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 10 Dec 2021 19:24:45 +0100 Subject: [PATCH 13/26] Show the correct help message for the scan command --- examples/dctool_scan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; } From 70411048e5be207d2a68a5bdce897baa8d672a22 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 19 Nov 2021 09:08:25 +0100 Subject: [PATCH 14/26] Re-order the fields in the layout descriptor The new order of the fields matches the physical memory layout more closely. --- src/hw_ostc_parser.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index 714318b..5d9ac35 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -90,13 +90,13 @@ 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 atmospheric; unsigned int firmware; + unsigned int salinity; + unsigned int avgdepth; + unsigned int duration; } hw_ostc_layout_t; typedef struct hw_ostc_gasmix_t { @@ -139,37 +139,37 @@ 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 */ + 15, /* atmospheric */ 32, /* firmware */ + 43, /* salinity */ + 45, /* avgdepth */ + 47, /* duration */ }; 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 */ + 21, /* atmospheric */ 32, /* firmware */ + 43, /* salinity */ + 45, /* avgdepth */ + 47, /* duration */ }; 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 */ + 24, /* atmospheric */ 48, /* firmware */ + 70, /* salinity */ + 73, /* avgdepth */ + 75, /* duration */ }; static unsigned int From 16e49eee6d441a9e494ab70d035579348a74090a Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 19 Nov 2021 09:12:51 +0100 Subject: [PATCH 15/26] Add the divemode to the layout descriptor --- src/hw_ostc_parser.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index 5d9ac35..29edcfd 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -97,6 +97,7 @@ typedef struct hw_ostc_layout_t { unsigned int salinity; unsigned int avgdepth; unsigned int duration; + unsigned int divemode; } hw_ostc_layout_t; typedef struct hw_ostc_gasmix_t { @@ -146,6 +147,7 @@ static const hw_ostc_layout_t hw_ostc_layout_ostc = { 43, /* salinity */ 45, /* avgdepth */ 47, /* duration */ + 51, /* divemode */ }; static const hw_ostc_layout_t hw_ostc_layout_frog = { @@ -158,6 +160,7 @@ static const hw_ostc_layout_t hw_ostc_layout_frog = { 43, /* salinity */ 45, /* avgdepth */ 47, /* duration */ + 51, /* divemode */ }; static const hw_ostc_layout_t hw_ostc_layout_ostc3 = { @@ -170,6 +173,7 @@ static const hw_ostc_layout_t hw_ostc_layout_ostc3 = { 70, /* salinity */ 73, /* avgdepth */ 75, /* duration */ + 82, /* divemode */ }; static unsigned int @@ -269,7 +273,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) { initial_setpoint = data[60]; } // Initial CNS @@ -511,7 +515,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; @@ -533,7 +537,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; @@ -545,7 +549,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; From 5e2d376627714c0786e964c53b47fb6d5bfad757 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 29 Nov 2021 14:26:57 +0100 Subject: [PATCH 16/26] Report the initial setpoint in SCR mode --- src/hw_ostc_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index 29edcfd..f1092c0 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -273,7 +273,7 @@ hw_ostc_parser_cache (hw_ostc_parser_t *parser) } } // The first fixed setpoint is the initial setpoint in CCR mode. - if (data[layout->divemode] == OSTC3_CC) { + if (data[layout->divemode] == OSTC3_CC || data[layout->divemode] == OSTC3_PSCR) { initial_setpoint = data[60]; } // Initial CNS From c5dced237a054adf2ed5862f7fe40c1efedc4c2e Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 18 Nov 2021 10:10:58 +0100 Subject: [PATCH 17/26] Read the hardware and software version --- src/deepsix_excursion.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/deepsix_excursion.c b/src/deepsix_excursion.c index 687f6f6..378e20f 100644 --- a/src/deepsix_excursion.c +++ b/src/deepsix_excursion.c @@ -44,6 +44,8 @@ #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 @@ -265,6 +267,22 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call 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); @@ -276,7 +294,7 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call // Emit a device info event. dc_event_devinfo_t devinfo; devinfo.model = 0; - devinfo.firmware = 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); From f6df075d511361ea94914cb82e23042960947988 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sat, 1 Jan 2022 11:46:36 +0100 Subject: [PATCH 18/26] Add support for the Genesis Centauri and Tusa TC1 Both models are compatible with the Deep Six Excursion. --- src/descriptor.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/descriptor.c b/src/descriptor.c index 7108429..fefef66 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -426,6 +426,8 @@ static const dc_descriptor_t g_descriptors[] = { /* 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}, }; static int @@ -730,6 +732,8 @@ static int dc_filter_deepsix (dc_transport_t transport, const void *userdata, vo static const char * const bluetooth[] = { "EXCURSION", "Crest-CR4", + "CENTAURI", + "TC1", }; if (transport == DC_TRANSPORT_BLE) { From d960a37e120757a7459c23a5bb3cc058e7269563 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 18 Jan 2022 18:50:53 +0100 Subject: [PATCH 19/26] Increase the size of the BLE packet cache In the latest OSTC hardware, the Telit/Stollman bluetooth module has been replaced with a u-Blox Nina B2 bluetooth module. This new module supports BLE data packets of up to 244 bytes (corresponding to an ATT MTU of 247 bytes and a LL PDU payload size of 251 bytes). --- src/hw_ostc3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 119af09..ff821c6 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -106,7 +106,7 @@ typedef struct hw_ostc3_device_t { unsigned int firmware; unsigned char fingerprint[5]; hw_ostc3_state_t state; - unsigned char cache[20]; + unsigned char cache[244]; unsigned int available; unsigned int offset; } hw_ostc3_device_t; From 811a9b4f89498a337301429b8e8578e61f34a6a9 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 7 Feb 2022 19:03:46 +0100 Subject: [PATCH 20/26] Report the correct dive mode for SCR dives The status field contains an extra bit for SCR dives, so there is no reason to report them as CCR dives. --- src/shearwater_predator_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index 290001a..9fd9656 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -391,7 +391,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; + mode = status & SC ? DC_DIVEMODE_SCR : DC_DIVEMODE_CCR; } // Gaschange. From ccd37d4fa310ef0b598300f7935a1c29e454afa7 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 7 Feb 2022 20:15:41 +0100 Subject: [PATCH 21/26] Use the dive mode stored in the header Starting with log version 8, the dive mode is stored in one of the opening records. For backwards compatibility with older firmware versions, the autodetection based on the status field in the sample data is kept as a fallback mechanism. --- src/shearwater_predator_parser.c | 51 +++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index 9fd9656..b6f223b 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -67,6 +67,15 @@ #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 @@ -113,7 +122,7 @@ struct shearwater_predator_parser_t { unsigned int tankidx[NTANKS]; unsigned int calibrated; double calibration[3]; - dc_divemode_t mode; + unsigned int divemode; unsigned int units; unsigned int atmospheric; unsigned int density; @@ -216,7 +225,7 @@ shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsig for (unsigned int i = 0; i < 3; ++i) { parser->calibration[i] = 0.0; } - parser->mode = DC_DIVEMODE_OC; + parser->divemode = M_OC_TEC; parser->units = METRIC; parser->density = 1025; parser->atmospheric = ATM / (BAR / 1000); @@ -273,7 +282,7 @@ shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char for (unsigned int i = 0; i < 3; ++i) { parser->calibration[i] = 0.0; } - parser->mode = DC_DIVEMODE_OC; + parser->divemode = M_OC_TEC; parser->units = METRIC; parser->density = 1025; parser->atmospheric = ATM / (BAR / 1000); @@ -367,7 +376,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; @@ -391,7 +400,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) // Status flags. unsigned int status = data[offset + 11 + pnf]; if ((status & OC) == 0) { - mode = status & SC ? DC_DIVEMODE_SCR : DC_DIVEMODE_CCR; + divemode = status & SC ? M_SC : M_CC; } // Gaschange. @@ -447,7 +456,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) } } 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; @@ -510,6 +519,11 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) parser->calibrated = data[base]; } + // Get the dive mode from the header (if available). + if (logversion >= 8) { + divemode = data[parser->opening[4] + (pnf ? 1 : 112)]; + } + // Cache the data for later use. parser->pnf = pnf; parser->logversion = logversion; @@ -529,7 +543,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) parser->tankidx[i] = UNDEFINED; } } - parser->mode = mode; + parser->divemode = divemode; parser->units = data[parser->opening[0] + 8]; parser->atmospheric = array_uint16_be (data + parser->opening[1] + (parser->pnf ? 16 : 47)); parser->density = array_uint16_be (data + parser->opening[3] + (parser->pnf ? 3 : 83)); @@ -600,7 +614,28 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ *((double *) value) = parser->atmospheric / 1000.0; break; case DC_FIELD_DIVEMODE: - *((dc_divemode_t *) value) = parser->mode; + switch (parser->divemode) { + case M_CC: + case M_CC2: + *((dc_divemode_t *) value) = DC_DIVEMODE_CCR; + break; + case M_SC: + *((dc_divemode_t *) value) = DC_DIVEMODE_SCR; + break; + case M_OC_TEC: + case M_OC_REC: + *((dc_divemode_t *) value) = DC_DIVEMODE_OC; + break; + case M_GAUGE: + case M_PPO2: + *((dc_divemode_t *) value) = DC_DIVEMODE_GAUGE; + break; + case M_FREEDIVE: + *((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE; + break; + default: + return DC_STATUS_DATAFORMAT; + } break; default: return DC_STATUS_UNSUPPORTED; From d1242a28cfb2f69115486ce491c5165e1fe9cde8 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 7 Feb 2022 21:36:59 +0100 Subject: [PATCH 22/26] Use the correct model number from the final block During the download, the model number is obtained from the hardware type because the model number isn't available before downloading the first dive. Since the list with available hardware types is incomplete, the correct model number is not always available. However, during parsing the correct model number is available in the final block. --- src/shearwater_predator_parser.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index b6f223b..d7a8ad8 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -524,6 +524,11 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) 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]; + } + // Cache the data for later use. parser->pnf = pnf; parser->logversion = logversion; From 54fa676e75a5b7df804468a2dca72ca018bd577c Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 7 Feb 2022 21:41:34 +0100 Subject: [PATCH 23/26] Report the timezone offset for the Teric The Shearwater Teric supports a UTC offset and DST setting. --- src/shearwater_predator_parser.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index d7a8ad8..a79677f 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -85,6 +85,7 @@ #define PREDATOR 2 #define PETREL 3 +#define TERIC 8 #define UNDEFINED 0xFFFFFFFF @@ -307,7 +308,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; } From 86e1d59a6ad1dcf3965de917520e541844c6bd56 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 7 Feb 2022 22:12:25 +0100 Subject: [PATCH 24/26] Limit the number of records for the Predator In the older Predator-like data format, the 4th opening/closing record is the last one. To avoid accidental use of the higher ones, leave them undefined. --- src/shearwater_predator_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index a79677f..360b978 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -373,7 +373,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; } From 7a650f940cab0f575dd9ae5f98402e247f58ce1e Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 8 Feb 2022 09:13:47 +0100 Subject: [PATCH 25/26] Add support for transmitter T3 and T4 In recent firmware versions (log version 13), Shearwater added support for two more tank transmittors: T3 and T4. --- src/shearwater_predator_parser.c | 40 ++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index 360b978..b7e7b32 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -52,6 +52,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 @@ -80,7 +81,7 @@ #define IMPERIAL 1 #define NGASMIXES 10 -#define NTANKS 2 +#define NTANKS 4 #define NRECORDS 8 #define PREDATOR 2 @@ -439,8 +440,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+ @@ -461,6 +462,22 @@ 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].enabled) { + tank[i + 2].enabled = 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 divemode = M_FREEDIVE; @@ -805,8 +822,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 @@ -837,6 +854,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 From c6640aa7d319506b3ece645361e6f1d9fb1ac0fb Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 8 Feb 2022 20:24:59 +0100 Subject: [PATCH 26/26] Read the extra tank information The latest firmware does store some additional information for each tank. Right now it's not really used for anything yet, but it's available for future use. --- src/array.c | 12 +++++ src/array.h | 3 ++ src/shearwater_predator_parser.c | 88 +++++++++++++++++++++++++++++--- 3 files changed, 97 insertions(+), 6 deletions(-) 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/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index b7e7b32..28c4915 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -20,6 +20,7 @@ */ #include +#include #include @@ -99,8 +100,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]; } shearwater_predator_tank_t; struct shearwater_predator_parser_t { @@ -219,8 +225,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->tankidx[i] = i; } parser->calibrated = 0; @@ -276,8 +287,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; @@ -453,8 +469,8 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) unsigned int pressure = array_uint16_be (data + offset + pnf + idx[i]); if (pressure < 0xFFF0) { 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; } @@ -469,8 +485,8 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) unsigned int pressure = array_uint16_be (data + offset + pnf + i * 2); if (pressure < 0xFFF0) { pressure &= 0x0FFF; - if (!tank[i + 2].enabled) { - tank[i + 2].enabled = 1; + if (!tank[i + 2].active) { + tank[i + 2].active = 1; tank[i + 2].beginpressure = pressure; tank[i + 2].endpressure = pressure; } @@ -485,9 +501,59 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) // 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 @@ -553,6 +619,16 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) 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. parser->pnf = pnf; parser->logversion = logversion; @@ -564,7 +640,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++;