Merge git://github.com/libdivecomputer/libdivecomputer into Subsurface-DS9

Merge Jef's upstream into the Subsurface branch:

 - support for new dive computers: Mares Pick Pro+, Deep Six Excursion,
   Crest CR-4, Genesis Centauri and Tusa TC1.

 - support freedive mode on Mares Smart Air

 - work with Oceanic dive computers regardless of whether they need the
   BLE handshake or not

 - OSTC updates: support bigger BLE packets in newer versions, fix
   setpoint in SCR mode

 - Shearwater updates: full dive mode parsing, correct timezone handling
   on Teric, support up to four transmitters on newer log versions.

* git://github.com/libdivecomputer/libdivecomputer: (26 commits)
  Read the extra tank information
  Add support for transmitter T3 and T4
  Limit the number of records for the Predator
  Report the timezone offset for the Teric
  Use the correct model number from the final block
  Use the dive mode stored in the header
  Report the correct dive mode for SCR dives
  Increase the size of the BLE packet cache
  Add support for the Genesis Centauri and Tusa TC1
  Read the hardware and software version
  Report the initial setpoint in SCR mode
  Add the divemode to the layout descriptor
  Re-order the fields in the layout descriptor
  Show the correct help message for the scan command
  Add support for the Crest CR-4
  Add udev rule for the Suunto EON Steel Black
  Ignore unsupported BLE handshake
  Detect NAK response packets
  Remove the initial gas switch
  Restore the original standard gravity factor
  ...
This commit is contained in:
Linus Torvalds 2022-02-27 12:20:09 -08:00
commit 39dbb275cc
20 changed files with 1233 additions and 168 deletions

View File

@ -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"

View File

@ -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},

View File

@ -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;
}

View File

@ -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 */

View File

@ -182,6 +182,8 @@
<ClCompile Include="..\src\cressi_leonardo_parser.c" />
<ClCompile Include="..\src\custom.c" />
<ClCompile Include="..\src\datetime.c" />
<ClCompile Include="..\src\deepsix_excursion.c" />
<ClCompile Include="..\src\deepsix_excursion_parser.c" />
<ClCompile Include="..\src\descriptor.c" />
<ClCompile Include="..\src\device.c" />
<ClCompile Include="..\src\diverite_nitekq.c" />
@ -304,6 +306,7 @@
<ClInclude Include="..\src\cressi_edy.h" />
<ClInclude Include="..\src\cressi_goa.h" />
<ClInclude Include="..\src\cressi_leonardo.h" />
<ClInclude Include="..\src\deepsix_excursion.h" />
<ClInclude Include="..\src\descriptor-private.h" />
<ClInclude Include="..\src\device-private.h" />
<ClInclude Include="..\src\diverite_nitekq.h" />

View File

@ -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 \

View File

@ -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)
{

View File

@ -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);

View File

@ -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) {

456
src/deepsix_excursion.c Normal file
View File

@ -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 <string.h>
#include <stdlib.h>
#include <stdio.h>
#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;
}

43
src/deepsix_excursion.h Normal file
View File

@ -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 <libdivecomputer/context.h>
#include <libdivecomputer/iostream.h>
#include <libdivecomputer/device.h>
#include <libdivecomputer/parser.h>
#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 */

View File

@ -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 <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <libdivecomputer/units.h>
#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;
}

View File

@ -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)
{

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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.");

View File

@ -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;

View File

@ -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;

View File

@ -20,6 +20,7 @@
*/
#include <stdlib.h>
#include <string.h>
#include <libdivecomputer/units.h>
@ -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