Add support for the McLean Extreme dive computer

Initial support for McLean Extreme

Signed-off by: David McLean Carron <david_de_carron@hotmail.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
David Carron 2020-05-17 14:28:24 -07:00 committed by Linus Torvalds
parent 48e46cf777
commit ee84202b2b
11 changed files with 962 additions and 0 deletions

View File

@ -0,0 +1,102 @@
COMPUTER CONFIGURATION
----------------------
The computer configuration (including gradient factors, gasses etc) is read and written as a single, fixed size block.
Config section
--------------
Computer configuration and individual dive configurations both start with identical generic configuration sections of length 0x002d bytes
0x0000 uint8_t format ZERO
0x0001 uint8_t gas[0]_pO2 oxygen content [0, 100] (percent)
0x0002 uint8_t gas[0]_pHe helium content [0, 100] (percent)
0x0003 uint8_t gas[1]_pO2 oxygen content [0, 100] (percent)
0x0004 uint8_t gas[1]_pHe helium content [0, 100] (percent)
0x0005 uint8_t gas[2]_pO2 oxygen content [0, 100] (percent)
0x0006 uint8_t gas[2]_pHe helium content [0, 100] (percent)
0x0007 uint8_t gas[3]_pO2 oxygen content [0, 100] (percent)
0x0008 uint8_t gas[3]_pHe helium content [0, 100] (percent)
0x0009 uint8_t gas[4]_pO2 oxygen content [0, 100] (percent)
0x000a uint8_t gas[4]_pHe helium content [0, 100] (percent)
0x000b uint8_t gas[5]_pO2 oxygen content [0, 100] (percent)
0x000c uint8_t gas[5]_pHe helium content [0, 100] (percent)
0x000d uint8_t gas[6]_pO2 oxygen content [0, 100] (percent)
0x000e uint8_t gas[6]_pHe helium content [0, 100] (percent)
0x000f uint8_t gas[7]_pO2 oxygen content [0, 100] (percent)
0x0010 uint8_t gas[7]_pHe helium content [0, 100] (percent)
0x0011 uint8_t gas_mask bitwise mask of enabled gasses
0x0012 uint8_t gas_active current gas index
0x0013 uint8_t setpoint[0] (centibar)
0x0014 uint8_t setpoint[1] (centibar)
0x0015 uint8_t setpoint[2] (centibar)
0x0016 uint8_t setpoint_mask bitwise mask of enabled setpoints
0x0017 uint8_t setpoint_active current setpoint index
0x0018 bool metric display units [true: metric, false: imperial]
0x0019 uint16_t name dive number
0x001b uint8_t laststop_index last stop depth enumeration [0, 3] (metric: 3m, 4m, 5m, 6m imperial: 10ft, 13ft, 16ft, 18ft)
0x001c uint16_t Vasc ascent speed limit (millibar/minute)
0x001e uint16_t Psurf surface pressure (millibar)
0x0020 uint8_t gfs_index predefined gf enumeration [0, 5] (only used in rec mode)
0x0021 uint8_t gf lo custom gf low [0, 255] (ignored in rec mode)
0x0022 uint8_t gf hi custom gf high [0, 255] (ignored in rec mode)
0x0023 uint8_t density_index predefined water density enumeration [0: 1.000, 1: 1.020, 2: 1.030]
0x0024 uint16_t ppN2_limit nitrogen partial pressure alert (millibars)
0x0026 uint16_t ppO2_limit oxygen partial pressure alert (millibars)
0x0028 uint16_t ppO2_bottomlimit oxygen partial pressure alert (millibars)
0x002a uint16_t density_limit (units ??)
0x002c uint8_t operatingmode operating mode enumeration [0: OC rec, 1: OC tec, 2: CC, 3: Gauge]
Computer configuration
----------------------
The computer configuration is read and written as a single block of 0x006a bytes formatted thusly:
<config section as above>
0x002d +
0x0000 int64_t epoch internal adjustment used to set computer time
0x0008 uint16_t inactive_timeout number of seconds of inactivity before the computer turns itself off
0x000a uint16_t dive_timeout number of seconds on surface before declaring dive finished
0x000c uint16_t log_period number of seconds between dive samples
0x000e uint16_t log_timeout not used
0x0010 uint16_t brightness_timeout number of seconds of inactivity before the computer dims the screen
0x0012 uint8_t brightness dimmed screen brightness [0, 10]
0x0013 uint8_t colorscheme computer color scheme enumeration [0, 12]
0x0014 uint8_t language computer display language enumeration [0, 1]
0x0015 uint8_t batterytype user-defined battery type enumeration [0, 4]
0x0016 uint16_t batterytime computer up time since last battery change
0x0018 <48 bytes> compass calibration data (cannot be written)
0x0048 uint8_t button_sensitivity piezo button sensitivity [0, 9]
0x0049 uint8_t orientation screen and button orientation
0x004a char[32] diver_name diver name (guaranteed at least one nul terminator)
0x006a
Dive configuration
------------------
Individual dives are read as a single block of 0x002d bytes formatted thusly:
<config section as above>
0x002d +
0x0000 uint32_t log_start log start (seconds since midnight 1 jan 2000)
0x0004 uint32_t dive_start dive start (seconds since midnight 1 jan 2000)
0x0008 uint32_t dive_end dive end (seconds since midnight 1 jan 2000)
0x000c uint32_t log_end log end (seconds since midnight 1 jan 2000)
0x0010 uint8_t temp_min minimum ambient temperature (degrees centigrade)
0x0011 uint8_t temp_max maximum ambient temperature (degrees centigrade)
0x0012 uint16_t pO2_min minimum inspired oxygen partial pressure during the dive (millibars)
0x0014 uint16_t pO2_max maximum inspired oxygen partial pressure during the dive (millibars)
0x0016 uint16_t Pmax maximum ambient pressure during the dive (millibars)
0x0018 uint16_t Pav average ambient pressure during the dive (millibars)
0x001a uint32_t ISS integral supersaturation of dive mbar*minutes
0x001e uint16_t CNS_start CNS at start of dive (percent)
0x0020 uint16_t CNS_max maximum CNS encountered during the dive (percent)
0x0022 uint16_t OTU OTU dive
0x0024 uint16_t tndl shortest NDL calculated during dive (seconds)
0x0026 uint32_t tdeco longest decompression time calculated during dive (seconds)
0x002a uint8_t ndeco deepest decompression stop index calculated during dive (seconds)
0x002b uint16_t tdesat desaturation time calculated at end of dive (seconds)
0x002d

View File

@ -92,6 +92,7 @@ static const backend_table_t g_backends[] = {
{"divecomputereu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0}, {"divecomputereu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0},
{"descentmk1", DC_FAMILY_GARMIN, 0}, {"descentmk1", DC_FAMILY_GARMIN, 0},
{"cosmiq", DC_FAMILY_DEEPBLU, 0}, {"cosmiq", DC_FAMILY_DEEPBLU, 0},
{"mclean", DC_FAMILY_MCLEAN_EXTREME, 0},
}; };
static const transport_table_t g_transports[] = { static const transport_table_t g_transports[] = {

View File

@ -112,6 +112,8 @@ typedef enum dc_family_t {
DC_FAMILY_GARMIN = (16 << 16), DC_FAMILY_GARMIN = (16 << 16),
/* Deepblu */ /* Deepblu */
DC_FAMILY_DEEPBLU = (17 << 16), DC_FAMILY_DEEPBLU = (17 << 16),
/* McLean */
DC_FAMILY_MCLEAN_EXTREME = (18 << 16),
} dc_family_t; } dc_family_t;
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -514,6 +514,14 @@
RelativePath="..\src\deepblu_parser.c" RelativePath="..\src\deepblu_parser.c"
> >
</File> </File>
<File
RelativePath="..\src\mclean_extreme.c"
>
</File>
<File
RelativePath="..\src\mclean_extreme_parser.c"
>
</File>
<File <File
RelativePath="..\src\field-cache.c" RelativePath="..\src\field-cache.c"
> >
@ -868,6 +876,10 @@
RelativePath="..\src\deepblu.h" RelativePath="..\src\deepblu.h"
> >
</File> </File>
<File
RelativePath="..\src\mclean_extreme.h"
>
</File>
<File <File
RelativePath="..\src\timer.h" RelativePath="..\src\timer.h"
> >

View File

@ -75,6 +75,7 @@ libdivecomputer_la_SOURCES = \
tecdiving_divecomputereu.h tecdiving_divecomputereu.c tecdiving_divecomputereu_parser.c \ tecdiving_divecomputereu.h tecdiving_divecomputereu.c tecdiving_divecomputereu_parser.c \
garmin.h garmin.c garmin_parser.c \ garmin.h garmin.c garmin_parser.c \
deepblu.h deepblu.c deepblu_parser.c \ deepblu.h deepblu.c deepblu_parser.c \
mclean_extreme.h mclean_extreme.c mclean_extreme_parser.c \
socket.h socket.c \ socket.h socket.c \
irda.c \ irda.c \
usbhid.c \ usbhid.c \

View File

@ -50,6 +50,7 @@ static int dc_filter_mares (dc_transport_t transport, const void *userdata);
static int dc_filter_divesystem (dc_transport_t transport, const void *userdata); static int dc_filter_divesystem (dc_transport_t transport, const void *userdata);
static int dc_filter_oceanic (dc_transport_t transport, const void *userdata); static int dc_filter_oceanic (dc_transport_t transport, const void *userdata);
static int dc_filter_deepblu (dc_transport_t transport, const void *userdata); static int dc_filter_deepblu (dc_transport_t transport, const void *userdata);
static int dc_filter_mclean(dc_transport_t transport, const void *userdata);
static dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item); static dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item);
@ -382,6 +383,8 @@ static const dc_descriptor_t g_descriptors[] = {
{"Garmin", "Descent Mk1", DC_FAMILY_GARMIN, 2859, DC_TRANSPORT_USBSTORAGE, dc_filter_garmin}, {"Garmin", "Descent Mk1", DC_FAMILY_GARMIN, 2859, DC_TRANSPORT_USBSTORAGE, dc_filter_garmin},
/* Deepblu */ /* Deepblu */
{"Deepblu", "Cosmiq+", DC_FAMILY_DEEPBLU, 0, DC_TRANSPORT_BLE, dc_filter_deepblu}, {"Deepblu", "Cosmiq+", DC_FAMILY_DEEPBLU, 0, DC_TRANSPORT_BLE, dc_filter_deepblu},
/* McLean Extreme */
{ "McLean", "Extreme", DC_FAMILY_MCLEAN_EXTREME, 0, DC_TRANSPORT_BLUETOOTH, dc_filter_mclean },
}; };
static int static int
@ -661,6 +664,22 @@ static int dc_filter_deepblu (dc_transport_t transport, const void *userdata)
return 1; return 1;
} }
static int dc_filter_mclean(dc_transport_t transport, const void* userdata)
{
static const char* const bluetooth[] = {
"Extreme",
};
if (transport == DC_TRANSPORT_BLUETOOTH) {
return DC_FILTER_INTERNAL(userdata, bluetooth, 0, dc_match_name);
}
else if (transport == DC_TRANSPORT_SERIAL) {
return DC_FILTER_INTERNAL(userdata, rfcomm, 1, dc_match_devname);
}
return 1;
}
dc_status_t dc_status_t
dc_descriptor_iterator (dc_iterator_t **out) dc_descriptor_iterator (dc_iterator_t **out)
{ {

View File

@ -59,6 +59,7 @@
#include "tecdiving_divecomputereu.h" #include "tecdiving_divecomputereu.h"
#include "garmin.h" #include "garmin.h"
#include "deepblu.h" #include "deepblu.h"
#include "mclean_extreme.h"
#include "device-private.h" #include "device-private.h"
#include "context-private.h" #include "context-private.h"
@ -219,6 +220,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr
case DC_FAMILY_DEEPBLU: case DC_FAMILY_DEEPBLU:
rc = deepblu_device_open (&device, context, iostream); rc = deepblu_device_open (&device, context, iostream);
break; break;
case DC_FAMILY_MCLEAN_EXTREME:
rc = mclean_extreme_device_open(&device, context, iostream);
break;
default: default:
return DC_STATUS_INVALIDARGS; return DC_STATUS_INVALIDARGS;
} }

496
src/mclean_extreme.c Normal file
View File

@ -0,0 +1,496 @@
/*
* libdivecomputer
*
* Copyright (C) 2018 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> // memcmp, memcpy
#include <stdlib.h> // malloc, free
#include <stdint.h>
#include "mclean_extreme.h"
#include "context-private.h"
#include "device-private.h"
#include "array.h"
#define ISINSTANCE(device) dc_device_isinstance((device), &mclean_extreme_device_vtable)
#define MAXRETRIES 14
#define STX 0x7E
#define CMD_COMPUTER 0xa0 // download computer configuration
#define CMD_SETCOMPUTER 0xa1 // upload computer configuration
#define CMD_DIVE 0xa3 // download specified dive configuration and samples
#define CMD_CLOSE 0xaa // close connexion and turn off bluetooth
#define SZ_PACKET 512 // maximum packe payload length
#define SZ_SUMMARY 7 // size of the device fingerprint
#define SZ_CFG 0x002d // size of the common dive/computer header
#define SZ_COMPUTER 0x0097 // size of the computer state dump
#define SZ_DIVE 0x005e // size of the dive state dump
#define SZ_SAMPLE 0x0004 // size of the sample state dump
// private device parsing functions //////////////////////////////////////////////////////////////////////////////////////////
static uint16_t uint16(const unsigned char* buffer, int addr) { return (buffer[0 + addr] << 0) | (buffer[1 + addr] << 8); }
static uint32_t uint32(const unsigned char* buffer, int addr) { return (uint16(buffer, addr) << 0) | (uint16(buffer, addr + 2) << 16); }
static uint8_t device_format(const unsigned char* device) { return device[0x0000]; }
// static uint8_t device_gas_pO2(const unsigned char* device, int value) { return device[0x0001 + value * 2]; }
// static uint8_t device_gas_pHe(const unsigned char* device, int value) { return device[0x0001 + 1 + value * 2]; }
// static bool device_gas_enabled(const unsigned char* device, int value) { return (device[0x0011] & (1 << value)) != 0; }
// static uint8_t device_setpoint(const unsigned char* device, int value) { return device[0x0013 + value]; }
// static bool device_setpoint_enabled(const unsigned char* device, int value) { return (device[device, 0x0016] & (1 << value)) != 0; }
// static bool device_metric(unsigned char* device) { return device[0x0018] != 0; }
static uint16_t device_name(const unsigned char* device) { return uint16(device, 0x0019); }
// static uint16_t device_Vasc(const unsigned char* device) { return uint16(device, 0x001c); }
// static uint16_t device_Psurf(const unsigned char* device) { return uint16(device, 0x001e); }
// static uint8_t device_gfs_index(const unsigned char* device) { return device[0x0020]; }
// static uint8_t device_gflo(const unsigned char* device) { return device[0x0021]; }
// static uint8_t device_gfhi(const unsigned char* device) { return device[0x0022]; }
// static uint8_t device_density_index(const unsigned char* device) { return device[0x0023]; }
// static uint16_t device_ppN2_limit(const unsigned char* device) { return uint16(device, 0x0024); }
// static uint16_t device_ppO2_limit(const unsigned char* device) { return uint16(device, 0x0026); }
// static uint16_t device_ppO2_bottomlimit(const unsigned char* device) { return uint16(device, 0x0028); }
// static uint16_t device_density_limit(const unsigned char* device) { return uint16(device, 0x002a); }
// static uint8_t device_operatingmode(const unsigned char* device) { return device[0x002c]; }
// static uint16_t device_inactive_timeout(const unsigned char* device) { return uint16(device, SZ_CFG + 0x0008); }
// static uint16_t device_dive_timeout(const unsigned char* device) { return uint16(device, SZ_CFG + 0x000a); }
// static uint16_t device_log_period(const unsigned char* device) { return device[SZ_CFG + 0x000c]; }
// static uint16_t device_log_timeout(const unsigned char* device) { return uint16(device, SZ_CFG + 0x000e); }
// static uint8_t device_brightness_timeout(const unsigned char* device) { return device[SZ_CFG + 0x0010]; }
// static uint8_t device_brightness(const unsigned char* device) { return device[SZ_CFG + 0x0012]; }
// static uint8_t device_colorscheme(const unsigned char* device) { return device[SZ_CFG + 0x0013]; }
// static uint8_t device_language(const unsigned char* device) { return device[SZ_CFG + 0x0014]; }
// static uint8_t device_batterytype(const unsigned char* device) { return device[SZ_CFG + 0x0015]; }
// static uint16_t device_batterytime(const unsigned char* device) { return uint16(device, SZ_CFG + 0x0014); }
// static uint8_t device_button_sensitivity(const unsigned char* device) { return device[SZ_CFG + 0x0016]; }
// static uint8_t device_orientation(const unsigned char* device) { return device[SZ_CFG + 0x0049]; }
// static const char* device_owner(const unsigned char* device) { return (const char*)& device[SZ_CFG + 0x004a]; }
// private dive parsing functions //////////////////////////////////////////////////////////////////////////////////////////
static uint8_t dive_format(const unsigned char* dive) { return dive[0x0000]; }
static uint16_t dive_samples_cnt(const unsigned char* dive) { return uint16(dive, 0x005c); }
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
typedef struct mclean_extreme_device_t {
dc_device_t base;
dc_iostream_t* iostream;
unsigned char fingerprint[SZ_SUMMARY];
uint8_t data[SZ_COMPUTER];
} mclean_extreme_device_t;
static dc_status_t mclean_extreme_device_set_fingerprint(dc_device_t* abstract, const unsigned char data[], unsigned int size);
static dc_status_t mclean_extreme_device_foreach(dc_device_t* abstract, dc_dive_callback_t callback, void* userdata);
static dc_status_t mclean_extreme_device_close(dc_device_t* abstract);
static const dc_device_vtable_t mclean_extreme_device_vtable = {
sizeof(mclean_extreme_device_t),
DC_FAMILY_MCLEAN_EXTREME,
mclean_extreme_device_set_fingerprint, /* set_fingerprint */
NULL, /* read */
NULL, /* write */
NULL, /* dump */
mclean_extreme_device_foreach, /* foreach */
NULL, /* timesync */
mclean_extreme_device_close, /* close */
};
static unsigned short
checksum_crc(const unsigned char data[], unsigned int size, unsigned short init)
{
unsigned short crc = init;
for (unsigned int i = 0; i < size; ++i) {
crc ^= data[i] << 8;
if (crc & 0x8000) {
crc <<= 1;
crc ^= 0x1021;
}
else {
crc <<= 1;
}
}
return crc;
}
static dc_status_t
mclean_extreme_send(mclean_extreme_device_t* device, unsigned char cmd, const unsigned char data[], size_t size)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_device_t* abstract = (dc_device_t*)device;
unsigned short crc = 0;
if (device_is_cancelled(abstract))
return DC_STATUS_CANCELLED;
if (size > SZ_PACKET)
return DC_STATUS_INVALIDARGS;
// Setup the data packet
unsigned char packet[SZ_PACKET + 11] = {
STX,
0x00,
(size >> 0) & 0xFF,
(size >> 8) & 0xFF,
(size >> 16) & 0xFF,
(size >> 24) & 0xFF,
cmd,
};
if (size) {
memcpy(packet + 7, data, size);
}
crc = checksum_crc(packet + 1, size + 6, 0);
packet[size + 7] = (crc >> 8) & 0xFF;
packet[size + 8] = (crc) & 0xFF;
packet[size + 9] = 0x00;
packet[size + 10] = 0x00;
// Give the dive computer some extra time.
dc_iostream_sleep(device->iostream, 300);
// Send the data packet.
status = dc_iostream_write(device->iostream, packet, size + 11, NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR(abstract->context, "Failed to send the command.");
return status;
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
mclean_extreme_receive(mclean_extreme_device_t* device, unsigned char rsp, unsigned char data[], size_t max_size, size_t* actual_size)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_device_t* abstract = (dc_device_t*)device;
unsigned char header[7];
unsigned int nretries = 0;
// Read the packet start byte.
// Unfortunately it takes a relative long time, about 6-8 seconds,
// before the STX byte arrives. Hence the standard timeout of one
// second is not sufficient, and we need to retry a few times on
// timeout. The advantage over using a single read operation with a
// large timeout is that we can give the user a chance to cancel the
// operation.
while (1) {
status = dc_iostream_read(device->iostream, header + 0, 1, NULL);
if (status != DC_STATUS_SUCCESS) {
if (status != DC_STATUS_TIMEOUT) {
ERROR(abstract->context, "Failed to receive the packet start byte.");
return status;
}
// Abort if the maximum number of retries is reached.
if (nretries++ >= MAXRETRIES)
return status;
// Cancel if requested by the user.
if (device_is_cancelled(abstract))
return DC_STATUS_CANCELLED;
// Try again.
continue;
}
if (header[0] == STX)
break;
// Reset the retry counter.
nretries = 0;
}
// Read the packet header.
status = dc_iostream_read(device->iostream, header + 1, sizeof(header) - 1, NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR(abstract->context, "Failed to receive the packet header.");
return status;
}
// Verify the type byte.
unsigned int type = header[1];
if (type != 0x00) {
ERROR(abstract->context, "Unexpected type byte (%02x).", type);
return DC_STATUS_PROTOCOL;
}
// Verify the length.
unsigned int length = array_uint32_le(header + 2);
if (length > max_size) {
ERROR(abstract->context, "Unexpected packet length (%u for %zu).", length, max_size);
return DC_STATUS_PROTOCOL;
}
// Get the command type.
unsigned int cmd = header[6];
if (cmd != rsp) {
ERROR(abstract->context, "Unexpected command byte (%02x).", cmd);
return DC_STATUS_PROTOCOL;
}
size_t nbytes = 0;
while (nbytes < length) {
// Set the maximum packet size.
size_t len = 1000;
// Limit the packet size to the total size.
if (nbytes + len > length)
len = length - nbytes;
// Read the packet payload.
status = dc_iostream_read(device->iostream, data + nbytes, len, NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR(abstract->context, "Failed to receive the packet payload.");
return status;
}
nbytes += len;
}
// Read the packet checksum.
unsigned char checksum[4];
status = dc_iostream_read(device->iostream, checksum, sizeof(checksum), NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR(abstract->context, "Failed to receive the packet checksum.");
return status;
}
// Verify the checksum.
unsigned short crc = array_uint16_be(checksum);
unsigned short ccrc = 0;
ccrc = checksum_crc(header + 1, sizeof(header) - 1, ccrc);
ccrc = checksum_crc(data, length, ccrc);
if (crc != ccrc || checksum[2] != 0x00 || checksum[3] != 0) {
ERROR(abstract->context, "Unexpected packet checksum.");
return DC_STATUS_PROTOCOL;
}
if (actual_size != NULL) {
// Return the actual length.
*actual_size = length;
}
return DC_STATUS_SUCCESS;
}
dc_status_t
mclean_extreme_device_open(dc_device_t** out, dc_context_t* context, dc_iostream_t* iostream)
{
dc_status_t status = DC_STATUS_SUCCESS;
mclean_extreme_device_t* device = NULL;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
// Allocate memory.
device = (mclean_extreme_device_t*)dc_device_allocate(context, &mclean_extreme_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;
}
// Send the init command.
status = mclean_extreme_send(device, CMD_COMPUTER, NULL, 0);
if (status != DC_STATUS_SUCCESS) {
ERROR(context, "Failed to send the init command.");
goto error_free;
}
// Read the device info.
status = mclean_extreme_receive(device, CMD_COMPUTER, device->data, sizeof(device->data), NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR(context, "Failed to receive the device info.");
goto error_free;
}
if (device_format(device->data) != 0) { /* bad device format */
status = DC_STATUS_DATAFORMAT;
ERROR(context, "Unsupported device format.");
goto error_free;
}
*out = (dc_device_t*)device;
return DC_STATUS_SUCCESS;
error_free:
dc_device_deallocate((dc_device_t*)device);
return status;
}
static dc_status_t
mclean_extreme_device_close(dc_device_t* abstract)
{
dc_status_t status = DC_STATUS_SUCCESS;
mclean_extreme_device_t* device = (mclean_extreme_device_t*)abstract;
status = mclean_extreme_send(device, CMD_CLOSE, NULL, 0);
if (status != DC_STATUS_SUCCESS) {
ERROR(abstract->context, "Failed to send the exit command.");
return status;
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
mclean_extreme_device_set_fingerprint(dc_device_t* abstract, const unsigned char data[], unsigned int size)
{
mclean_extreme_device_t* device = (mclean_extreme_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
mclean_extreme_device_readsamples(dc_device_t* abstract, uint8_t* dive)
{
dc_status_t status = DC_STATUS_SUCCESS;
mclean_extreme_device_t* device = (mclean_extreme_device_t*)abstract;
int samples_cnt = (dive[0x005c] << 0) + (dive[0x005d] << 8); // number of samples to follow
dive = &dive[SZ_DIVE];
while (samples_cnt != 0) {
unsigned char data[SZ_PACKET]; // buffer for read packet data
size_t length; // buffer for read packet length
status = mclean_extreme_receive(device, CMD_DIVE, data, SZ_PACKET, &length);
if (status != DC_STATUS_SUCCESS) {
ERROR(abstract->context, "Failed to receive dive samples.");
break;
}
int packet_cnt = length / SZ_SAMPLE; // number of samples in the packet
if (packet_cnt > samples_cnt) { /* too many samples received */
status = DC_STATUS_DATAFORMAT;
ERROR(abstract->context, "Too many dive samples received.");
break;
}
if (length != packet_cnt * SZ_SAMPLE) { /* not an integer number of samples */
status = DC_STATUS_DATAFORMAT;
ERROR(abstract->context, "Partial samples received.");
break;
}
memcpy(dive, data, packet_cnt * SZ_SAMPLE); // append samples to dive buffer
dive = &dive[packet_cnt * SZ_SAMPLE]; // move buffer write cursor
samples_cnt -= packet_cnt;
}
return status;
}
static dc_status_t
mclean_extreme_device_foreach(dc_device_t* abstract, dc_dive_callback_t callback, void* userdata)
{
dc_status_t status = DC_STATUS_SUCCESS;
mclean_extreme_device_t* device = (mclean_extreme_device_t*)abstract;
dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
int dives_cnt = device_name(device->data);
progress.current = 0;
progress.maximum = dives_cnt;
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
for (int i = dives_cnt - 1; i >= 0; --i) {
unsigned char data[512]; // buffer for read packet data
size_t length; // buffer for read packet length
unsigned char id[] = { (unsigned char)i, (unsigned char)(i >> 8) }; // payload for CMD_DIVE request
status = mclean_extreme_send(device, CMD_DIVE, id, sizeof(id));
if (status != DC_STATUS_SUCCESS) {
ERROR(abstract->context, "Failed to send the get dive command.");
break;
}
status = mclean_extreme_receive(device, CMD_DIVE, data, 512, &length);
if (status != DC_STATUS_SUCCESS) {
ERROR(abstract->context, "Failed to receive dive header.");
break;
}
if (dive_format(data) != 0) { /* can't understand the format */
INFO(abstract->context, "Skipping unsupported dive format");
break;
}
int cnt_samples = dive_samples_cnt(data); // number of samples to follow
size_t size = SZ_DIVE + cnt_samples * SZ_SAMPLE; // total buffer size required for this dive
uint8_t* dive = (uint8_t *)malloc(size); // buffer for this dive
if (dive == NULL) {
status = DC_STATUS_NOMEMORY;
break;
}
memcpy(dive, data, SZ_DIVE); // copy data to dive buffer
status = mclean_extreme_device_readsamples(abstract, dive); // append samples to buffer
if (status != DC_STATUS_SUCCESS) { /* failed to read dive samples */
free(dive); // cleanup
break; // stop downloading
}
if (callback && !callback(dive, size, dive, sizeof(device->fingerprint), userdata)) { /* cancelled by callback */
free(dive); // cleanup
break; // stop downloading
}
free(dive);
progress.current = dives_cnt - 1 - i;
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
}
return status;
}

43
src/mclean_extreme.h Normal file
View File

@ -0,0 +1,43 @@
/*
* libdivecomputer
*
* Copyright (C) 2018 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 MCLEAN_EXTREME_H
#define MCLEAN_EXTREME_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
mclean_extreme_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream);
dc_status_t
mclean_extreme_parser_create (dc_parser_t **parser, dc_context_t *context);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* mclean_extreme_H */

278
src/mclean_extreme_parser.c Normal file
View File

@ -0,0 +1,278 @@
/*
* libdivecomputer
*
* Copyright (C) 2018 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 <stdint.h>
#include <stdbool.h>
#include "mclean_extreme.h"
#include "context-private.h"
#include "parser-private.h"
#include "array.h"
#define ISINSTANCE(parser) dc_device_isinstance((parser), &mclean_extreme_parser_vtable)
#define SZ_CFG 0x002d // size of the common dive/computer header
#define SZ_COMPUTER 0x006a // size of the computer state dump
#define SZ_DIVE 0x005e // size of the dive state dump
#define SZ_SAMPLE 0x0004 // size of the sample state dump
// private dive parsing functions //////////////////////////////////////////////////////////////////////////////////////////
static uint16_t uint16(const unsigned char* buffer, int addr) { return (buffer[0 + addr] << 0) | (buffer[1 + addr] << 8); }
static uint32_t uint32(const unsigned char* buffer, int addr) { return (buffer[0 + addr] << 0) | (buffer[1 + addr] << 8) | (buffer[2 + addr] << 16) | (buffer[3 + addr] << 24); }
static uint8_t dive_format(const unsigned char* dive) { return dive[0x0000]; }
static uint8_t dive_gas_pO2(const unsigned char* dive, int value) { return dive[0x0001 + value * 2]; }
static uint8_t dive_gas_pHe(const unsigned char* dive, int value) { return dive[0x0001 + 1 + value * 2]; }
// static bool dive_gas_enabled(const unsigned char* dive, int value) { return (dive[0x0011] & (1 << value)) != 0; }
static uint8_t dive_setpoint(const unsigned char* dive, int value) { return dive[0x0013 + value]; }
// static bool dive_setpoint_enabled(const unsigned char* dive, int value) { return (dive[dive, 0x0016] & (1 << value)) != 0; }
// static bool dive_metric(const unsigned char* dive) { return dive[0x0018] != 0; }
// static uint16_t dive_name(const unsigned char* dive) { return uint16(dive, 0x0019); }
// static uint16_t dive_Vasc(const unsigned char* dive) { return uint16(dive, 0x001c); }
static uint16_t dive_Psurf(const unsigned char* dive) { return uint16(dive, 0x001e); }
// static uint8_t dive_gfs_index(const unsigned char* dive) { return dive[0x0020]; }
// static uint8_t dive_gflo(const unsigned char* dive) { return dive[0x0021]; }
// static uint8_t dive_gfhi(const unsigned char* dive) { return dive[0x0022]; }
static uint8_t dive_density_index(const unsigned char* dive) { return dive[0x0023]; }
// static uint16_t dive_ppN2_limit(const unsigned char* dive) { return uint16(dive, 0x0024); }
// static uint16_t dive_ppO2_limit(const unsigned char* dive) { return uint16(dive, 0x0026); }
// static uint16_t dive_ppO2_bottomlimit(const unsigned char* dive) { return uint16(dive, 0x0028); }
// static uint16_t dive_density_limit(const unsigned char* dive) { return uint16(dive, 0x002a); }
static uint8_t dive_operatingmode(const unsigned char* dive) { return dive[0x002c]; }
static uint32_t dive_logstart(const unsigned char* dive) { return uint32(dive, SZ_CFG + 0x0000); }
static uint32_t dive_divestart(const unsigned char* dive) { return uint32(dive, SZ_CFG + 0x0004); }
static uint32_t dive_diveend(const unsigned char* dive) { return uint32(dive, SZ_CFG + 0x0008); }
static uint32_t dive_logend(const unsigned char* dive) { return uint32(dive, SZ_CFG + 0x000c); }
static uint8_t dive_temp_min(const unsigned char* dive) { return dive[SZ_CFG + 0x0010]; }
static uint8_t dive_temp_max(const unsigned char* dive) { return dive[SZ_CFG + 0x0011]; }
// static uint16_t dive_pO2_min(const unsigned char* dive) { return uint16(dive, SZ_CFG + 0x0012); }
// static uint16_t dive_pO2_max(const unsigned char* dive) { return uint16(dive, SZ_CFG + 0x0014); }
static uint16_t dive_Pmax(const unsigned char* dive) { return uint16(dive, SZ_CFG + 0x0016); }
static uint16_t dive_Pav(const unsigned char* dive) { return uint16(dive, SZ_CFG + 0x0018); }
// static uint32_t ISS(const unsigned char* dive) { return uint16(dive, SZ_CFG + 0x001a); }
// static uint16_t CNS_start(const unsigned char* dive) { return uint16(dive, SZ_CFG + 0x001e); }
// static uint16_t CNS_max(const unsigned char* dive) { return uint16(dive, SZ_CFG + 0x0020); }
// static uint16_t OTU(const unsigned char* dive) { return uint16(dive, SZ_CFG + 0x0022); }
// static uint16_t tndl(const unsigned char* dive) { return uint16(dive, SZ_CFG + 0x0024); }
// static uint32_t tdeco(const unsigned char* dive) { return uint32(dive, SZ_CFG + 0x0026); }
// static uint8_t ndeco(const unsigned char* dive) { return dive[SZ_CFG + 0x002a]; }
// static uint32_t tdesat(const unsigned char* dive) { return uint32(dive, SZ_CFG + 0x002b); }
static uint16_t dive_samples_cnt(const unsigned char* dive) { return uint16(dive, 0x005c); }
// private sample parsing functions //////////////////////////////////////////////////////////////////////////////////////////
static uint16_t sample_depth(const unsigned char* dive, int n) { return uint16(dive, SZ_DIVE + n * SZ_SAMPLE + 0); }
static uint8_t sample_temperature(const unsigned char* dive, int n) { return dive[SZ_DIVE + n * SZ_SAMPLE + 2]; }
static bool sample_ccr(const unsigned char* dive, int n) { return dive[SZ_DIVE + n * SZ_SAMPLE + 3] & 0b10000000; }
static uint8_t sample_sp_index(const unsigned char* dive, int n) { return (dive[SZ_DIVE + n * SZ_SAMPLE + 3] & 0b01100000) >> 5; }
static uint8_t sample_gas_index(const unsigned char* dive, int n) { return (dive[SZ_DIVE + n * SZ_SAMPLE + 3] & 0b00011100) >> 2; }
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
typedef struct mclean_extreme_parser_t mclean_extreme_parser_t;
struct mclean_extreme_parser_t {
dc_parser_t base;
};
static dc_status_t mclean_extreme_parser_set_data(dc_parser_t* abstract, const unsigned char* data, unsigned int size);
static dc_status_t mclean_extreme_parser_get_datetime(dc_parser_t* abstract, dc_datetime_t* datetime);
static dc_status_t mclean_extreme_parser_get_field(dc_parser_t* abstract, dc_field_type_t type, unsigned int flags, void* value);
static dc_status_t mclean_extreme_parser_samples_foreach(dc_parser_t* abstract, dc_sample_callback_t callback, void* userdata);
static const dc_parser_vtable_t mclean_extreme_parser_vtable = {
sizeof(mclean_extreme_parser_t),
DC_FAMILY_MCLEAN_EXTREME,
mclean_extreme_parser_set_data, /* set_data */
mclean_extreme_parser_get_datetime, /* datetime */
mclean_extreme_parser_get_field, /* fields */
mclean_extreme_parser_samples_foreach, /* samples_foreach */
NULL /* destroy */
};
dc_status_t
mclean_extreme_parser_create(dc_parser_t** out, dc_context_t* context)
{
mclean_extreme_parser_t* parser = NULL;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
// Allocate memory.
parser = (mclean_extreme_parser_t*)dc_parser_allocate(context, &mclean_extreme_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
mclean_extreme_parser_set_data(dc_parser_t* abstract, const unsigned char* data, unsigned int size)
{
dc_status_t status = DC_STATUS_SUCCESS;
if (dive_format(data) != 0) {
status = DC_STATUS_DATAFORMAT;
ERROR(abstract->context, "Unsupported dive format.");
}
const int samples_cnt = dive_samples_cnt(data);
if (size != SZ_DIVE + samples_cnt * SZ_SAMPLE) {
ERROR(abstract->context, "Corrupt dive in memory.");
return DC_STATUS_DATAFORMAT;
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
mclean_extreme_parser_get_datetime(dc_parser_t* abstract, dc_datetime_t* datetime)
{
if (datetime) {
const unsigned char* dive = abstract->data;
dc_ticks_t dc_ticks = 946684800 + dive_logstart(dive); // raw times are offsets for 1/1/2000
dc_datetime_gmtime(datetime, dc_ticks);
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
mclean_extreme_parser_get_field(dc_parser_t* abstract, dc_field_type_t type, unsigned int flags, void* value)
{
static const double densities[] = { 1.000, 1.020, 1.030 };
static const dc_divemode_t divemodes[] = { DC_DIVEMODE_OC, DC_DIVEMODE_OC, DC_DIVEMODE_CCR, DC_DIVEMODE_GAUGE };
const unsigned char* dive = abstract->data;
dc_gasmix_t* gasmix = (dc_gasmix_t*)value;
if (value) {
switch (type) {
case DC_FIELD_DIVETIME:
*((unsigned int*)value) = dive_logend(dive) - dive_logstart(dive);
break;
case DC_FIELD_MAXDEPTH:
*((double*)value) = 0.01 * (dive_Pmax(dive) - dive_Psurf(dive)) / densities[dive_density_index(dive)];
break;
case DC_FIELD_AVGDEPTH:
*((double*)value) = 0.01 * (dive_Pav(dive) - dive_Psurf(dive)) / densities[dive_density_index(dive)];
break;
case DC_FIELD_SALINITY:
switch (dive_density_index(dive)) {
case 0: ((dc_salinity_t*)value)->type = DC_WATER_FRESH; ((dc_salinity_t*)value)->density = densities[dive_density_index(dive)]; break;
case 1: ((dc_salinity_t*)value)->type = DC_WATER_SALT; ((dc_salinity_t*)value)->density = densities[dive_density_index(dive)]; break;
case 2: ((dc_salinity_t*)value)->type = DC_WATER_SALT; ((dc_salinity_t*)value)->density = densities[dive_density_index(dive)]; break;
default: /* that's an error */ break;
}
break;
case DC_FIELD_ATMOSPHERIC:
*((double*)value) = dive_Psurf(dive) / 1000.0;
break;
// case DC_FIELD_TEMPERATURE_SURFACE:
case DC_FIELD_TEMPERATURE_MINIMUM:
*((double*)value) = dive_temp_min(dive);
break;
case DC_FIELD_TEMPERATURE_MAXIMUM:
*((double*)value) = dive_temp_max(dive);
break;
case DC_FIELD_DIVEMODE:
*((dc_divemode_t*)value) = divemodes[dive_operatingmode(dive)];
break;
//case DC_FIELD_TANK:
//case DC_FIELD_TANK_COUNT:
case DC_FIELD_GASMIX_COUNT:
*((unsigned int*)value) = 8;
break;
case DC_FIELD_GASMIX:
gasmix->helium = 0.01 * dive_gas_pHe(dive, flags);
gasmix->oxygen = 0.01 * dive_gas_pO2(dive, flags);
gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium;
break;
default:
return DC_STATUS_UNSUPPORTED;
}
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
mclean_extreme_parser_samples_foreach(dc_parser_t* abstract, dc_sample_callback_t callback, void* userdata)
{
const unsigned char* dive = abstract->data;
const unsigned int size = abstract->size;
const unsigned int interval = 20; // this should be dive[log end] - dive[log start]?
const int samples_cnt = dive_samples_cnt(dive); // number of samples to follow
if (callback) {
unsigned int time = 0;
for (int i = 0; i < samples_cnt; ++i) {
dc_sample_value_t sample = { 0 };
sample.time = time;
callback(DC_SAMPLE_TIME, sample, userdata);
sample.depth = sample_depth(dive, i) * 0.1;
callback(DC_SAMPLE_DEPTH, sample, userdata);
sample.temperature = sample_temperature(dive, i);
callback(DC_SAMPLE_TEMPERATURE, sample, userdata);
sample.gasmix = sample_gas_index(dive, i);
callback(DC_SAMPLE_GASMIX, sample, userdata);
if (sample_ccr(dive, i)) {
const uint8_t sp_index = sample_sp_index(dive, i);
sample.setpoint = 100.0 * dive_setpoint(dive, sp_index);
if (callback) callback(DC_SAMPLE_SETPOINT, sample, userdata);
}
time += interval;
}
}
return DC_STATUS_SUCCESS;
}

View File

@ -59,6 +59,7 @@
#include "tecdiving_divecomputereu.h" #include "tecdiving_divecomputereu.h"
#include "garmin.h" #include "garmin.h"
#include "deepblu.h" #include "deepblu.h"
#include "mclean_extreme.h"
#include "context-private.h" #include "context-private.h"
#include "parser-private.h" #include "parser-private.h"
@ -180,6 +181,9 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa
case DC_FAMILY_DEEPBLU: case DC_FAMILY_DEEPBLU:
rc = deepblu_parser_create (&parser, context); rc = deepblu_parser_create (&parser, context);
break; break;
case DC_FAMILY_MCLEAN_EXTREME:
rc = mclean_extreme_parser_create(&parser, context);
break;
default: default:
return DC_STATUS_INVALIDARGS; return DC_STATUS_INVALIDARGS;
} }