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:
parent
48e46cf777
commit
ee84202b2b
102
doc/McLeanExtreme-wire-format.txt
Normal file
102
doc/McLeanExtreme-wire-format.txt
Normal 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
|
||||||
|
|
||||||
|
|
||||||
@ -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[] = {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -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 \
|
||||||
|
|||||||
@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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
496
src/mclean_extreme.c
Normal 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
43
src/mclean_extreme.h
Normal 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
278
src/mclean_extreme_parser.c
Normal 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;
|
||||||
|
}
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user