Compare commits
118 Commits
Subsurface
...
test_mares
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9dd81852e | ||
|
|
426a39fc73 | ||
|
|
d3ccb88a44 | ||
|
|
ce6d9896a7 | ||
|
|
2d40cf46b1 | ||
|
|
d532be187a | ||
|
|
df7aeeef01 | ||
|
|
aab3d7a68e | ||
|
|
32e6ae4efa | ||
|
|
ccef003d47 | ||
|
|
4dff291a1a | ||
|
|
eacfe4011a | ||
|
|
2efa83eeee | ||
|
|
4ce0ae9e4f | ||
|
|
9812bf0828 | ||
|
|
ffbb472975 | ||
|
|
1bbd386959 | ||
|
|
b9d72607a8 | ||
|
|
e38406b353 | ||
|
|
8fb2b75c25 | ||
|
|
1194585595 | ||
|
|
92cf2f8fa1 | ||
|
|
e650acc052 | ||
|
|
2971cc20ff | ||
|
|
c7112237b3 | ||
|
|
e4698c4844 | ||
|
|
d0ec5cf760 | ||
|
|
1953d7a5a1 | ||
|
|
072bef1666 | ||
|
|
391c4095f2 | ||
|
|
1d09635a58 | ||
|
|
e4c96e93ca | ||
|
|
33253b2f8c | ||
|
|
b082a96ebb | ||
|
|
e34e211c3e | ||
|
|
abde311d3a | ||
|
|
f0fe141373 | ||
|
|
e61b7c64f9 | ||
|
|
54ba49b1b3 | ||
|
|
65ef99981f | ||
|
|
419b0e0498 | ||
|
|
5456475fd5 | ||
|
|
fac3c8ff14 | ||
|
|
17ff3e0667 | ||
|
|
2518231577 | ||
|
|
c6c29f6e9a | ||
|
|
07a33e88c3 | ||
|
|
5be8c17ea1 | ||
|
|
f57c53470b | ||
|
|
e97886a994 | ||
|
|
8120b11258 | ||
|
|
32d62d6269 | ||
|
|
91766d1ed0 | ||
|
|
f705ddefa8 | ||
|
|
0d1e8f1803 | ||
|
|
6f377182f5 | ||
|
|
fb70928c83 | ||
|
|
8f2ac8f61e | ||
|
|
348387c6f6 | ||
|
|
5ad97bd6bb | ||
|
|
ee7c14ecc3 | ||
|
|
d0a3336c82 | ||
|
|
437cc3e0cc | ||
|
|
9379004b2d | ||
|
|
34785f55ff | ||
|
|
0cbcc0518c | ||
|
|
f80024ed59 | ||
|
|
12b90c693a | ||
|
|
ec0029c4ce | ||
|
|
a9c582e26f | ||
|
|
c1c0303e04 | ||
|
|
7779bdf581 | ||
|
|
416bf35977 | ||
|
|
e02037bf84 | ||
|
|
ac25976258 | ||
|
|
41303bbc70 | ||
|
|
8f7e29e1f9 | ||
|
|
740222d216 | ||
|
|
4a43392c78 | ||
|
|
86540206db | ||
|
|
f6ea5f514a | ||
|
|
fe2a43e798 | ||
|
|
63cd80c560 | ||
|
|
22a96bf395 | ||
|
|
6a6e60c9bb | ||
|
|
994efff75a | ||
|
|
6b7c269c9c | ||
|
|
c8e52081cd | ||
|
|
00a90e2822 | ||
|
|
6d470d8430 | ||
|
|
3dbe5353f5 | ||
|
|
6d53e31cba | ||
|
|
bc2ba57302 | ||
|
|
b65b2318f1 | ||
|
|
2c7479ad1c | ||
|
|
a726a38cbb | ||
|
|
8f790b52e4 | ||
|
|
8735156e89 | ||
|
|
bb985eedbb | ||
|
|
8f4945dc1e | ||
|
|
02560a7e7f | ||
|
|
5255ba5448 | ||
|
|
503d934c19 | ||
|
|
902dbf4d6d | ||
|
|
e0761561e9 | ||
|
|
d264349676 | ||
|
|
e97a47cca5 | ||
|
|
14490a462a | ||
|
|
97c8bb908e | ||
|
|
f248a95d64 | ||
|
|
df1e97c471 | ||
|
|
a38d640df4 | ||
|
|
6c51ace384 | ||
|
|
db08a534bf | ||
|
|
167848aa59 | ||
|
|
362fe3f936 | ||
|
|
49f89d2205 | ||
|
|
063041ddca |
2
.gitignore
vendored
2
.gitignore
vendored
@ -82,3 +82,5 @@ Makefile.in
|
||||
/src/libdivecomputer.la
|
||||
/src/libdivecomputer.rc
|
||||
/src/revision.h
|
||||
|
||||
/build
|
||||
|
||||
10
configure.ac
10
configure.ac
@ -2,7 +2,7 @@
|
||||
m4_define([dc_version_major],[0])
|
||||
m4_define([dc_version_minor],[7])
|
||||
m4_define([dc_version_micro],[0])
|
||||
m4_define([dc_version_suffix],[devel])
|
||||
m4_define([dc_version_suffix],[devel-Subsurface-NG])
|
||||
m4_define([dc_version],dc_version_major.dc_version_minor.dc_version_micro[]m4_ifset([dc_version_suffix],-[dc_version_suffix]))
|
||||
|
||||
# Libtool versioning.
|
||||
@ -175,19 +175,21 @@ AC_CHECK_FUNCS([getopt_long])
|
||||
|
||||
# Checks for supported compiler options.
|
||||
AX_APPEND_COMPILE_FLAGS([ \
|
||||
-pedantic \
|
||||
-Wall \
|
||||
-Wextra \
|
||||
-Wshadow \
|
||||
-Wrestrict \
|
||||
-Wformat=2 \
|
||||
-Wwrite-strings \
|
||||
-Wcast-qual \
|
||||
-Wpointer-arith \
|
||||
-Wstrict-prototypes \
|
||||
-Wmissing-prototypes \
|
||||
-Wmissing-declarations \
|
||||
-Wno-unused-parameter \
|
||||
-Wno-unused-function \
|
||||
-Wno-unused-variable \
|
||||
-Wno-unused-but-set-variable \
|
||||
-Wno-pointer-sign \
|
||||
-Wno-shadow \
|
||||
])
|
||||
|
||||
# Windows specific compiler options.
|
||||
|
||||
@ -90,6 +90,8 @@ static const backend_table_t g_backends[] = {
|
||||
{"idive", DC_FAMILY_DIVESYSTEM_IDIVE, 0x03},
|
||||
{"cochran", DC_FAMILY_COCHRAN_COMMANDER, 0},
|
||||
{"divecomputereu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0},
|
||||
{"descentmk1", DC_FAMILY_GARMIN, 0},
|
||||
{"cosmiq", DC_FAMILY_DEEPBLU, 0},
|
||||
};
|
||||
|
||||
static const transport_table_t g_transports[] = {
|
||||
@ -99,6 +101,7 @@ static const transport_table_t g_transports[] = {
|
||||
{"irda", DC_TRANSPORT_IRDA},
|
||||
{"bluetooth", DC_TRANSPORT_BLUETOOTH},
|
||||
{"ble", DC_TRANSPORT_BLE},
|
||||
{"usbstorage",DC_TRANSPORT_USBSTORAGE},
|
||||
};
|
||||
|
||||
const char *
|
||||
@ -537,6 +540,8 @@ dctool_iostream_open (dc_iostream_t **iostream, dc_context_t *context, dc_descri
|
||||
return dctool_irda_open (iostream, context, descriptor, devname);
|
||||
case DC_TRANSPORT_BLUETOOTH:
|
||||
return dctool_bluetooth_open (iostream, context, descriptor, devname);
|
||||
case DC_TRANSPORT_USBSTORAGE:
|
||||
return dc_usb_storage_open (iostream, context, devname);
|
||||
default:
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
}
|
||||
|
||||
@ -414,6 +414,24 @@ dctool_xml_output_write (dctool_output_t *abstract, dc_parser_t *parser, const u
|
||||
convert_pressure(atmospheric, output->units));
|
||||
}
|
||||
|
||||
message ("Parsing strings.\n");
|
||||
int idx;
|
||||
for (idx = 0; idx < 100; idx++) {
|
||||
dc_field_string_t str = { NULL };
|
||||
status = dc_parser_get_field(parser, DC_FIELD_STRING, idx, &str);
|
||||
if (status != DC_STATUS_SUCCESS && status != DC_STATUS_UNSUPPORTED) {
|
||||
ERROR ("Error parsing strings");
|
||||
goto cleanup;
|
||||
}
|
||||
if (status == DC_STATUS_UNSUPPORTED)
|
||||
break;
|
||||
if (!str.desc || !str.value)
|
||||
break;
|
||||
fprintf (output->ostream, "<extradata key='%s' value='%s' />\n",
|
||||
str.desc, str.value);
|
||||
|
||||
}
|
||||
|
||||
// Parse the sample data.
|
||||
message ("Parsing the sample data.\n");
|
||||
status = dc_parser_samples_foreach (parser, sample_cb, &sampledata);
|
||||
|
||||
@ -48,9 +48,13 @@ typedef enum dc_transport_t {
|
||||
DC_TRANSPORT_USBHID = (1 << 2),
|
||||
DC_TRANSPORT_IRDA = (1 << 3),
|
||||
DC_TRANSPORT_BLUETOOTH = (1 << 4),
|
||||
DC_TRANSPORT_BLE = (1 << 5)
|
||||
DC_TRANSPORT_BLE = (1 << 5),
|
||||
DC_TRANSPORT_USBSTORAGE= (1 << 6),
|
||||
} dc_transport_t;
|
||||
|
||||
// Idiotic enums can't be queried
|
||||
#define DC_TRANSPORT_USBSTORAGE DC_TRANSPORT_USBSTORAGE
|
||||
|
||||
typedef enum dc_family_t {
|
||||
DC_FAMILY_NULL = 0,
|
||||
/* Suunto */
|
||||
@ -104,6 +108,10 @@ typedef enum dc_family_t {
|
||||
DC_FAMILY_COCHRAN_COMMANDER = (14 << 16),
|
||||
/* Tecdiving */
|
||||
DC_FAMILY_TECDIVING_DIVECOMPUTEREU = (15 << 16),
|
||||
/* Garmin */
|
||||
DC_FAMILY_GARMIN = (16 << 16),
|
||||
/* Deepblu */
|
||||
DC_FAMILY_DEEPBLU = (17 << 16),
|
||||
} dc_family_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@ -45,6 +45,7 @@ typedef struct dc_custom_cbs_t {
|
||||
dc_status_t (*purge) (void *userdata, dc_direction_t direction);
|
||||
dc_status_t (*sleep) (void *userdata, unsigned int milliseconds);
|
||||
dc_status_t (*close) (void *userdata);
|
||||
const char *(*get_name) (void *userdata);
|
||||
} dc_custom_cbs_t;
|
||||
|
||||
/**
|
||||
|
||||
@ -283,6 +283,22 @@ dc_iostream_sleep (dc_iostream_t *iostream, unsigned int milliseconds);
|
||||
dc_status_t
|
||||
dc_iostream_close (dc_iostream_t *iostream);
|
||||
|
||||
/**
|
||||
* Get the name of the device
|
||||
*
|
||||
* @param[in] iostream A valid I/O stream.
|
||||
* @returns a NUL-terminated constant C string that has a lifetime
|
||||
* until the close of the iostream, or NUL if no name exists.
|
||||
*
|
||||
* The name depends on the iostream transport. For a BLE device, it
|
||||
* is the name of the device during device discovery.
|
||||
*/
|
||||
const char *
|
||||
dc_iostream_get_name (dc_iostream_t *iostream);
|
||||
|
||||
dc_status_t
|
||||
dc_usb_storage_open (dc_iostream_t **out, dc_context_t *context, const char *name);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
@ -46,9 +46,13 @@ typedef enum dc_sample_type_t {
|
||||
DC_SAMPLE_PPO2,
|
||||
DC_SAMPLE_CNS,
|
||||
DC_SAMPLE_DECO,
|
||||
DC_SAMPLE_GASMIX
|
||||
DC_SAMPLE_GASMIX,
|
||||
DC_SAMPLE_TTS, // time to surface in seconds
|
||||
} dc_sample_type_t;
|
||||
|
||||
// Make it easy to test support compile-time with "#ifdef DC_SAMPLE_TTS"
|
||||
#define DC_SAMPLE_TTS DC_SAMPLE_TTS
|
||||
|
||||
typedef enum dc_field_type_t {
|
||||
DC_FIELD_DIVETIME,
|
||||
DC_FIELD_MAXDEPTH,
|
||||
@ -62,9 +66,13 @@ typedef enum dc_field_type_t {
|
||||
DC_FIELD_TEMPERATURE_MAXIMUM,
|
||||
DC_FIELD_TANK_COUNT,
|
||||
DC_FIELD_TANK,
|
||||
DC_FIELD_DIVEMODE
|
||||
DC_FIELD_DIVEMODE,
|
||||
DC_FIELD_STRING,
|
||||
} dc_field_type_t;
|
||||
|
||||
// Make it easy to test support compile-time with "#ifdef DC_FIELD_STRING"
|
||||
#define DC_FIELD_STRING DC_FIELD_STRING
|
||||
|
||||
typedef enum parser_sample_event_t {
|
||||
SAMPLE_EVENT_NONE,
|
||||
SAMPLE_EVENT_DECOSTOP,
|
||||
@ -92,17 +100,41 @@ typedef enum parser_sample_event_t {
|
||||
SAMPLE_EVENT_HEADING,
|
||||
SAMPLE_EVENT_TISSUELEVEL,
|
||||
SAMPLE_EVENT_GASCHANGE2, /* Deprecated: replaced by DC_SAMPLE_GASMIX. */
|
||||
SAMPLE_EVENT_STRING,
|
||||
} parser_sample_event_t;
|
||||
|
||||
/* To let the compile know we have this */
|
||||
#define SAMPLE_EVENT_STRING SAMPLE_EVENT_STRING
|
||||
|
||||
/* For backwards compatibility */
|
||||
#define SAMPLE_EVENT_UNKNOWN SAMPLE_EVENT_FLOOR
|
||||
|
||||
typedef enum parser_sample_flags_t {
|
||||
SAMPLE_FLAGS_NONE = 0,
|
||||
SAMPLE_FLAGS_BEGIN = (1 << 0),
|
||||
SAMPLE_FLAGS_END = (1 << 1)
|
||||
SAMPLE_FLAGS_END = (1 << 1),
|
||||
SAMPLE_FLAGS_SEVERITY_MASK = (7 << 2),
|
||||
} parser_sample_flags_t;
|
||||
|
||||
#define SAMPLE_FLAGS_SEVERITY_SHIFT 2
|
||||
|
||||
#define SAMPLE_FLAGS_SEVERITY_MISSING (0 << SAMPLE_FLAGS_SEVERITY_SHIFT)
|
||||
#define SAMPLE_FLAGS_SEVERITY_STATE (1 << SAMPLE_FLAGS_SEVERITY_SHIFT)
|
||||
#define SAMPLE_FLAGS_SEVERITY_INFO (2 << SAMPLE_FLAGS_SEVERITY_SHIFT)
|
||||
#define SAMPLE_FLAGS_SEVERITY_WARN (3 << SAMPLE_FLAGS_SEVERITY_SHIFT)
|
||||
#define SAMPLE_FLAGS_SEVERITY_ALARM (4 << SAMPLE_FLAGS_SEVERITY_SHIFT)
|
||||
|
||||
/* these are used for the types of TAGs in Shearwater PNF info events */
|
||||
#define SAMPLE_FLAGS_TYPE_SHIFT 5
|
||||
#define SAMPLE_FLAGS_TYPE_MASK (7 << SAMPLE_FLAGS_TYPE_SHIFT)
|
||||
#define SAMPLE_FLAGS_TYPE_NONE (0 << SAMPLE_FLAGS_TYPE_SHIFT)
|
||||
#define SAMPLE_FLAGS_TYPE_INTEREST (1 << SAMPLE_FLAGS_TYPE_SHIFT)
|
||||
#define SAMPLE_FLAGS_TYPE_NAVPOINT (2 << SAMPLE_FLAGS_TYPE_SHIFT)
|
||||
#define SAMPLE_FLAGS_TYPE_DANGER (3 << SAMPLE_FLAGS_TYPE_SHIFT)
|
||||
#define SAMPLE_FLAGS_TYPE_ANIMAL (4 << SAMPLE_FLAGS_TYPE_SHIFT)
|
||||
#define SAMPLE_FLAGS_TYPE_ISSUE (5 << SAMPLE_FLAGS_TYPE_SHIFT)
|
||||
#define SAMPLE_FLAGS_TYPE_INJURY (6 << SAMPLE_FLAGS_TYPE_SHIFT)
|
||||
|
||||
typedef enum parser_sample_vendor_t {
|
||||
SAMPLE_VENDOR_NONE,
|
||||
SAMPLE_VENDOR_UWATEC_ALADIN,
|
||||
@ -148,11 +180,16 @@ typedef struct dc_gasmix_t {
|
||||
|
||||
#define DC_GASMIX_UNKNOWN 0xFFFFFFFF
|
||||
|
||||
typedef enum dc_tankvolume_t {
|
||||
DC_TANKVOLUME_NONE,
|
||||
DC_TANKVOLUME_METRIC,
|
||||
DC_TANKVOLUME_IMPERIAL,
|
||||
} dc_tankvolume_t;
|
||||
typedef unsigned int dc_tankinfo_t;
|
||||
#define DC_TANKINFO_METRIC 1
|
||||
#define DC_TANKINFO_IMPERIAL 2
|
||||
#define DC_TANKINFO_CC_DILUENT 4
|
||||
#define DC_TANKINFO_CC_O2 8
|
||||
|
||||
// For backwards compatibility
|
||||
#define DC_TANKVOLUME_NONE 0
|
||||
#define DC_TANKVOLUME_METRIC DC_TANKINFO_METRIC
|
||||
#define DC_TANKVOLUME_IMPERIAL DC_TANKINFO_IMPERIAL
|
||||
|
||||
/*
|
||||
* Tank volume
|
||||
@ -179,13 +216,18 @@ typedef enum dc_tankvolume_t {
|
||||
|
||||
typedef struct dc_tank_t {
|
||||
unsigned int gasmix; /* Gas mix index, or DC_GASMIX_UNKNOWN */
|
||||
dc_tankvolume_t type; /* Tank type */
|
||||
dc_tankinfo_t type; /* Tank type - metric/imperial and oc/cc */
|
||||
double volume; /* Volume (liter) */
|
||||
double workpressure; /* Work pressure (bar) */
|
||||
double beginpressure; /* Begin pressure (bar) */
|
||||
double endpressure; /* End pressure (bar) */
|
||||
} dc_tank_t;
|
||||
|
||||
typedef struct dc_field_string_t {
|
||||
const char *desc;
|
||||
const char *value;
|
||||
} dc_field_string_t;
|
||||
|
||||
typedef union dc_sample_value_t {
|
||||
unsigned int time;
|
||||
double depth;
|
||||
@ -199,6 +241,7 @@ typedef union dc_sample_value_t {
|
||||
unsigned int time;
|
||||
unsigned int flags;
|
||||
unsigned int value;
|
||||
const char *name;
|
||||
} event;
|
||||
unsigned int rbt;
|
||||
unsigned int heartbeat;
|
||||
|
||||
@ -498,6 +498,22 @@
|
||||
RelativePath="..\src\tecdiving_divecomputereu_parser.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\src\garmin.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\src\garmin_parser.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\src\deepblu.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\src\deepblu_parser.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\src\timer.c"
|
||||
>
|
||||
@ -840,6 +856,14 @@
|
||||
RelativePath="..\src\tecdiving_divecomputereu.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\src\garmin.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\src\deepblu.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\src\timer.h"
|
||||
>
|
||||
|
||||
@ -72,10 +72,13 @@ libdivecomputer_la_SOURCES = \
|
||||
buffer.c \
|
||||
cochran_commander.h cochran_commander.c cochran_commander_parser.c \
|
||||
tecdiving_divecomputereu.h tecdiving_divecomputereu.c tecdiving_divecomputereu_parser.c \
|
||||
garmin.h garmin.c garmin_parser.c \
|
||||
deepblu.h deepblu.c deepblu_parser.c \
|
||||
socket.h socket.c \
|
||||
irda.c \
|
||||
usbhid.c \
|
||||
bluetooth.c \
|
||||
usb_storage.c \
|
||||
custom.c
|
||||
|
||||
if OS_WIN32
|
||||
|
||||
@ -20,6 +20,12 @@
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
#include <libdivecomputer/units.h>
|
||||
|
||||
@ -129,6 +135,9 @@ atomics_cobalt_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dateti
|
||||
}
|
||||
|
||||
|
||||
#define BUFLEN 16
|
||||
|
||||
|
||||
static dc_status_t
|
||||
atomics_cobalt_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value)
|
||||
{
|
||||
@ -143,6 +152,9 @@ atomics_cobalt_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, un
|
||||
dc_tank_t *tank = (dc_tank_t *) value;
|
||||
|
||||
double atmospheric = 0.0;
|
||||
char buf[BUFLEN];
|
||||
dc_field_string_t *string = (dc_field_string_t *) value;
|
||||
|
||||
if (parser->atmospheric)
|
||||
atmospheric = parser->atmospheric;
|
||||
else
|
||||
@ -208,6 +220,29 @@ atomics_cobalt_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, un
|
||||
return DC_STATUS_DATAFORMAT;
|
||||
}
|
||||
break;
|
||||
case DC_FIELD_STRING:
|
||||
switch(flags) {
|
||||
case 0: // Serialnr
|
||||
string->desc = "Serial";
|
||||
snprintf(buf, BUFLEN, "%c%c%c%c-%c%c%c%c", p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11]);
|
||||
break;
|
||||
case 1: // Program Version
|
||||
string->desc = "Program Version";
|
||||
snprintf(buf, BUFLEN, "%.2f", array_uint16_le(p + 30) / 100.0);
|
||||
break;
|
||||
case 2: // Boot Version
|
||||
string->desc = "Boot Version";
|
||||
snprintf(buf, BUFLEN, "%.2f", array_uint16_le(p + 32) / 100.0);
|
||||
break;
|
||||
case 3: // Nofly
|
||||
string->desc = "NoFly Time";
|
||||
snprintf(buf, BUFLEN, "%0u:%02u", p[0x52], p[0x53]);
|
||||
break;
|
||||
default:
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
}
|
||||
string->value = strdup(buf);
|
||||
break;
|
||||
default:
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
}
|
||||
|
||||
@ -334,6 +334,7 @@ dc_context_get_transports (dc_context_t *context)
|
||||
#elif defined(HAVE_LIBUSB) && !defined(__APPLE__)
|
||||
| DC_TRANSPORT_USBHID
|
||||
#endif
|
||||
| DC_TRANSPORT_USBSTORAGE
|
||||
#ifdef _WIN32
|
||||
#ifdef HAVE_AF_IRDA_H
|
||||
| DC_TRANSPORT_IRDA
|
||||
|
||||
13
src/custom.c
13
src/custom.c
@ -41,6 +41,7 @@ static dc_status_t dc_custom_flush (dc_iostream_t *abstract);
|
||||
static dc_status_t dc_custom_purge (dc_iostream_t *abstract, dc_direction_t direction);
|
||||
static dc_status_t dc_custom_sleep (dc_iostream_t *abstract, unsigned int milliseconds);
|
||||
static dc_status_t dc_custom_close (dc_iostream_t *abstract);
|
||||
static const char *dc_custom_get_name (dc_iostream_t *abstract);
|
||||
|
||||
typedef struct dc_custom_t {
|
||||
/* Base class. */
|
||||
@ -66,6 +67,7 @@ static const dc_iostream_vtable_t dc_custom_vtable = {
|
||||
dc_custom_purge, /* purge */
|
||||
dc_custom_sleep, /* sleep */
|
||||
dc_custom_close, /* close */
|
||||
dc_custom_get_name, /* get_name */
|
||||
};
|
||||
|
||||
dc_status_t
|
||||
@ -246,3 +248,14 @@ dc_custom_close (dc_iostream_t *abstract)
|
||||
|
||||
return custom->callbacks.close (custom->userdata);
|
||||
}
|
||||
|
||||
static const char *
|
||||
dc_custom_get_name (dc_iostream_t *abstract)
|
||||
{
|
||||
dc_custom_t *custom = (dc_custom_t *) abstract;
|
||||
|
||||
if (custom->callbacks.get_name == NULL)
|
||||
return NULL;
|
||||
|
||||
return custom->callbacks.get_name (custom->userdata);
|
||||
}
|
||||
|
||||
503
src/deepblu.c
Normal file
503
src/deepblu.c
Normal file
@ -0,0 +1,503 @@
|
||||
/*
|
||||
* Deepblu Cosmiq+ downloading
|
||||
*
|
||||
* Copyright (C) 2019 Linus Torvalds
|
||||
*
|
||||
* 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 "deepblu.h"
|
||||
#include "context-private.h"
|
||||
#include "device-private.h"
|
||||
#include "array.h"
|
||||
|
||||
// "Write state"
|
||||
#define CMD_SETTIME 0x20 // Send 6 byte date-time, get single-byte 00x00 ack
|
||||
#define CMD_23 0x23 // Send 00/01 byte, get ack back? Some metric/imperial setting?
|
||||
|
||||
// "Read dives"?
|
||||
#define CMD_GETDIVENR 0x40 // Send empty byte, get single-byte number of dives back
|
||||
#define CMD_GETDIVE 0x41 // Send dive number (1-nr) byte, get dive stat length byte back
|
||||
#define RSP_DIVESTAT 0x42 // .. followed by packets of dive stat for that dive of that length
|
||||
#define CMD_GETPROFILE 0x43 // Send dive number (1-nr) byte, get dive profile length BE word back
|
||||
#define RSP_DIVEPROF 0x44 // .. followed by packets of dive profile of that length
|
||||
|
||||
// "Read state"
|
||||
#define CMD_GETTIME 0x50 // Send empty byte, get six-byte bcd date-time back
|
||||
#define CMD_51 0x51 // Send empty byte, get four bytes back (03 dc 00 e3)
|
||||
#define CMD_52 0x52 // Send empty byte, get two bytes back (bf 8d)
|
||||
#define CMD_53 0x53 // Send empty byte, get six bytes back (0e 81 00 03 00 00)
|
||||
#define CMD_54 0x54 // Send empty byte, get byte back (00)
|
||||
#define CMD_55 0x55 // Send empty byte, get byte back (00)
|
||||
#define CMD_56 0x56 // Send empty byte, get byte back (00)
|
||||
#define CMD_57 0x57 // Send empty byte, get byte back (00)
|
||||
#define CMD_58 0x58 // Send empty byte, get byte back (52)
|
||||
#define CMD_59 0x59 // Send empty byte, get six bytes back (00 00 07 00 00 00)
|
||||
// (00 00 00 00 00 00)
|
||||
#define CMD_5a 0x5a // Send empty byte, get six bytes back (23 1b 09 d8 37 c0)
|
||||
#define CMD_5b 0x5b // Send empty byte, get six bytes back (00 21 00 14 00 01)
|
||||
// (00 00 00 14 00 01)
|
||||
#define CMD_5c 0x5c // Send empty byte, get six bytes back (13 88 00 46 20 00)
|
||||
// (13 88 00 3c 15 00)
|
||||
#define CMD_5d 0x5d // Send empty byte, get six bytes back (19 00 23 0C 02 0E)
|
||||
// (14 14 14 0c 01 0e)
|
||||
#define CMD_5f 0x5f // Send empty byte, get six bytes back (00 00 07 00 00 00)
|
||||
|
||||
#define COSMIQ_HDR_SIZE 36
|
||||
|
||||
typedef struct deepblu_device_t {
|
||||
dc_device_t base;
|
||||
dc_iostream_t *iostream;
|
||||
unsigned char fingerprint[COSMIQ_HDR_SIZE];
|
||||
} deepblu_device_t;
|
||||
|
||||
static dc_status_t deepblu_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size);
|
||||
static dc_status_t deepblu_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata);
|
||||
static dc_status_t deepblu_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime);
|
||||
static dc_status_t deepblu_device_close (dc_device_t *abstract);
|
||||
|
||||
static const dc_device_vtable_t deepblu_device_vtable = {
|
||||
sizeof(deepblu_device_t),
|
||||
DC_FAMILY_DEEPBLU,
|
||||
deepblu_device_set_fingerprint, /* set_fingerprint */
|
||||
NULL, /* read */
|
||||
NULL, /* write */
|
||||
NULL, /* dump */
|
||||
deepblu_device_foreach, /* foreach */
|
||||
deepblu_device_timesync, /* timesync */
|
||||
deepblu_device_close, /* close */
|
||||
};
|
||||
|
||||
// Maximum data in a packet. It's actually much
|
||||
// less than this, since BLE packets are small and
|
||||
// with the 7 bytes of headers and final newline
|
||||
// and the HEX encoding, the actual maximum is
|
||||
// just something like 6 bytes.
|
||||
//
|
||||
// But in theory the data could be done over
|
||||
// multiple packets. That doesn't seem to be
|
||||
// the case in anything I've seen so far.
|
||||
//
|
||||
// Pick something small and easy to use for
|
||||
// stack buffers.
|
||||
#define MAX_DATA 20
|
||||
|
||||
static char *
|
||||
write_hex_byte(unsigned char data, char *p)
|
||||
{
|
||||
static const char hex[16] = "0123456789ABCDEF";
|
||||
*p++ = hex[data >> 4];
|
||||
*p++ = hex[data & 0xf];
|
||||
return p;
|
||||
}
|
||||
|
||||
//
|
||||
// Send a cmd packet.
|
||||
//
|
||||
// The format of the cmd on the "wire" is:
|
||||
// - byte '#'
|
||||
// - HEX char of cmd
|
||||
// - HEX char two's complement modular sum of packet data (including cmd/size)
|
||||
// - HEX char size of data as encoded in HEX
|
||||
// - n * HEX char data
|
||||
// - byte '\n'
|
||||
// so you end up having 8 bytes of header/trailer overhead, and two bytes
|
||||
// for every byte of data sent due to the HEX encoding.
|
||||
//
|
||||
static dc_status_t
|
||||
deepblu_send_cmd(deepblu_device_t *device, const unsigned char cmd, const unsigned char data[], size_t size)
|
||||
{
|
||||
char buffer[8+2*MAX_DATA], *p;
|
||||
unsigned char csum;
|
||||
int i;
|
||||
|
||||
if (size > MAX_DATA)
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
|
||||
// Calculate packet csum
|
||||
csum = cmd + 2*size;
|
||||
for (i = 0; i < size; i++)
|
||||
csum += data[i];
|
||||
csum = -csum;
|
||||
|
||||
// Fill the data buffer
|
||||
p = buffer;
|
||||
*p++ = '#';
|
||||
p = write_hex_byte(cmd, p);
|
||||
p = write_hex_byte(csum, p);
|
||||
p = write_hex_byte(size*2, p);
|
||||
for (i = 0; i < size; i++)
|
||||
p = write_hex_byte(data[i], p);
|
||||
*p++ = '\n';
|
||||
|
||||
// .. and send it out
|
||||
return dc_iostream_write(device->iostream, buffer, p-buffer, NULL);
|
||||
}
|
||||
|
||||
//
|
||||
// Receive one 'line' of data
|
||||
//
|
||||
// The deepblu BLE protocol is ASCII line based and packetized.
|
||||
// Normally one packet is one line, but it looks like the Nordic
|
||||
// Semi BLE chip will sometimes send packets early (some internal
|
||||
// serial buffer timeout?) with incompete data.
|
||||
//
|
||||
// So read packets until you get newline.
|
||||
static dc_status_t
|
||||
deepblu_recv_line(deepblu_device_t *device, unsigned char *buf, size_t size)
|
||||
{
|
||||
while (1) {
|
||||
unsigned char buffer[20];
|
||||
size_t transferred = 0;
|
||||
dc_status_t status;
|
||||
|
||||
status = dc_iostream_read(device->iostream, buffer, sizeof(buffer), &transferred);
|
||||
if (status != DC_STATUS_SUCCESS) {
|
||||
ERROR(device->base.context, "Failed to receive Deepblu reply packet.");
|
||||
return status;
|
||||
}
|
||||
if (transferred > size) {
|
||||
ERROR(device->base.context, "Deepblu reply packet with too much data (got %zu, expected %zu)", transferred, size);
|
||||
return DC_STATUS_IO;
|
||||
}
|
||||
if (!transferred) {
|
||||
ERROR(device->base.context, "Empty Deepblu reply packet");
|
||||
return DC_STATUS_IO;
|
||||
}
|
||||
memcpy(buf, buffer, transferred);
|
||||
buf += transferred;
|
||||
size -= transferred;
|
||||
if (buf[-1] == '\n')
|
||||
break;
|
||||
}
|
||||
buf[-1] = 0;
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static int
|
||||
hex_nibble(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
if (c >= 'a' && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
if (c >= 'A' && c <= 'F')
|
||||
return c - 'A' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
read_hex_byte(char *p)
|
||||
{
|
||||
// This is negative if either of the nibbles is invalid
|
||||
return (hex_nibble(p[0]) << 4) | hex_nibble(p[1]);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Receive a reply packet
|
||||
//
|
||||
// The reply packet has the same format as the cmd packet we
|
||||
// send, except the first byte is '$' instead of '#'.
|
||||
static dc_status_t
|
||||
deepblu_recv_data(deepblu_device_t *device, const unsigned char expected, unsigned char *buf, size_t size, size_t *received)
|
||||
{
|
||||
int len, i;
|
||||
dc_status_t status;
|
||||
char buffer[8+2*MAX_DATA];
|
||||
int cmd, csum, ndata;
|
||||
|
||||
status = deepblu_recv_line(device, buffer, sizeof(buffer));
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
return status;
|
||||
|
||||
// deepblu_recv_line() always zero-terminates the result
|
||||
// if it returned success, and has removed the final newline.
|
||||
len = strlen(buffer);
|
||||
HEXDUMP(device->base.context, DC_LOGLEVEL_DEBUG, "rcv", buffer, len);
|
||||
|
||||
// A valid reply should always be at least 7 characters: the
|
||||
// initial '$' and the three header HEX bytes.
|
||||
if (len < 8 || buffer[0] != '$') {
|
||||
ERROR(device->base.context, "Invalid Deepblu reply packet");
|
||||
return DC_STATUS_IO;
|
||||
}
|
||||
|
||||
cmd = read_hex_byte(buffer+1);
|
||||
csum = read_hex_byte(buffer+3);
|
||||
ndata = read_hex_byte(buffer+5);
|
||||
if ((cmd | csum | ndata) < 0) {
|
||||
ERROR(device->base.context, "non-hex Deepblu reply packet header");
|
||||
return DC_STATUS_IO;
|
||||
}
|
||||
|
||||
// Verify the data length: it's the size of the HEX data,
|
||||
// and should also match the line length we got (the 7
|
||||
// is for the header data we already decoded above).
|
||||
if ((ndata & 1) || ndata != len - 7) {
|
||||
ERROR(device->base.context, "Deepblu reply packet data length does not match (claimed %d, got %d)", ndata, len-7);
|
||||
return DC_STATUS_IO;
|
||||
}
|
||||
|
||||
if (ndata >> 1 > size) {
|
||||
ERROR(device->base.context, "Deepblu reply packet too big for buffer (ndata=%d, size=%zu)", ndata, size);
|
||||
return DC_STATUS_IO;
|
||||
}
|
||||
|
||||
csum += cmd + ndata;
|
||||
|
||||
for (i = 7; i < len; i += 2) {
|
||||
int byte = read_hex_byte(buffer + i);
|
||||
if (byte < 0) {
|
||||
ERROR(device->base.context, "Deepblu reply packet data not valid hex");
|
||||
return DC_STATUS_IO;
|
||||
}
|
||||
*buf++ = byte;
|
||||
csum += byte;
|
||||
}
|
||||
|
||||
if (csum & 255) {
|
||||
ERROR(device->base.context, "Deepblu reply packet csum not valid (%x)", csum);
|
||||
return DC_STATUS_IO;
|
||||
}
|
||||
|
||||
*received = ndata >> 1;
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// Common communication pattern: send a command, expect data back with the same
|
||||
// command byte.
|
||||
static dc_status_t
|
||||
deepblu_send_recv(deepblu_device_t *device, const unsigned char cmd,
|
||||
const unsigned char *data, size_t data_size,
|
||||
unsigned char *result, size_t result_size)
|
||||
{
|
||||
dc_status_t status;
|
||||
size_t got;
|
||||
|
||||
status = deepblu_send_cmd(device, cmd, data, data_size);
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
return status;
|
||||
status = deepblu_recv_data(device, cmd, result, result_size, &got);
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
return status;
|
||||
if (got != result_size) {
|
||||
ERROR(device->base.context, "Deepblu result size didn't match expected (expected %zu, got %zu)",
|
||||
result_size, got);
|
||||
return DC_STATUS_IO;
|
||||
}
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
deepblu_recv_bulk(deepblu_device_t *device, const unsigned char cmd, unsigned char *buf, size_t len)
|
||||
{
|
||||
while (len) {
|
||||
dc_status_t status;
|
||||
size_t got;
|
||||
|
||||
status = deepblu_recv_data(device, cmd, buf, len, &got);
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
return status;
|
||||
if (got > len) {
|
||||
ERROR(device->base.context, "Deepblu bulk receive overflow");
|
||||
return DC_STATUS_IO;
|
||||
}
|
||||
buf += got;
|
||||
len -= got;
|
||||
}
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
dc_status_t
|
||||
deepblu_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream)
|
||||
{
|
||||
deepblu_device_t *device;
|
||||
|
||||
if (out == NULL)
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
|
||||
// Allocate memory.
|
||||
device = (deepblu_device_t *) dc_device_allocate (context, &deepblu_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));
|
||||
|
||||
*out = (dc_device_t *) device;
|
||||
|
||||
ERROR (context, "Deepblu Cosmiq+ open called");
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
deepblu_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size)
|
||||
{
|
||||
deepblu_device_t *device = (deepblu_device_t *)abstract;
|
||||
|
||||
ERROR (device->base.context, "Deepblu Cosmiq+ set_fingerprint called");
|
||||
HEXDUMP(device->base.context, DC_LOGLEVEL_DEBUG, "set_fingerprint", data, size);
|
||||
|
||||
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 unsigned char bcd(int val)
|
||||
{
|
||||
if (val >= 0 && val < 100) {
|
||||
int high = val / 10;
|
||||
int low = val % 10;
|
||||
return (high << 4) | low;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
deepblu_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime)
|
||||
{
|
||||
deepblu_device_t *device = (deepblu_device_t *)abstract;
|
||||
unsigned char result[1], data[6];
|
||||
dc_status_t status;
|
||||
size_t len;
|
||||
|
||||
data[0] = bcd(datetime->year - 2000);
|
||||
data[1] = bcd(datetime->month);
|
||||
data[2] = bcd(datetime->day);
|
||||
data[3] = bcd(datetime->hour);
|
||||
data[4] = bcd(datetime->minute);
|
||||
data[5] = bcd(datetime->second);
|
||||
|
||||
// Maybe also check that we received one zero byte (ack?)
|
||||
return deepblu_send_recv(device, CMD_SETTIME,
|
||||
data, sizeof(data),
|
||||
result, sizeof(result));
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
deepblu_device_close (dc_device_t *abstract)
|
||||
{
|
||||
deepblu_device_t *device = (deepblu_device_t *) abstract;
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static const char zero[MAX_DATA];
|
||||
|
||||
static dc_status_t
|
||||
deepblu_download_dive(deepblu_device_t *device, unsigned char nr, dc_dive_callback_t callback, void *userdata)
|
||||
{
|
||||
unsigned char header_len;
|
||||
unsigned char profilebytes[2];
|
||||
unsigned int profile_len;
|
||||
dc_status_t status;
|
||||
char header[256];
|
||||
unsigned char *profile;
|
||||
|
||||
status = deepblu_send_recv(device, CMD_GETDIVE, &nr, 1, &header_len, 1);
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
return status;
|
||||
status = deepblu_recv_bulk(device, RSP_DIVESTAT, header, header_len);
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
return status;
|
||||
memset(header + header_len, 0, 256 - header_len);
|
||||
|
||||
/* The header is the fingerprint. If we've already seen this header, we're done */
|
||||
if (memcmp(header, device->fingerprint, sizeof (device->fingerprint)) == 0)
|
||||
return DC_STATUS_DONE;
|
||||
|
||||
status = deepblu_send_recv(device, CMD_GETPROFILE, &nr, 1, profilebytes, sizeof(profilebytes));
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
return status;
|
||||
profile_len = (profilebytes[0] << 8) | profilebytes[1];
|
||||
|
||||
profile = malloc(256 + profile_len);
|
||||
if (!profile) {
|
||||
ERROR (device->base.context, "Insufficient buffer space available.");
|
||||
return DC_STATUS_NOMEMORY;
|
||||
}
|
||||
|
||||
// We make the dive data be 256 bytes of header, followed by the profile data
|
||||
memcpy(profile, header, 256);
|
||||
|
||||
status = deepblu_recv_bulk(device, RSP_DIVEPROF, profile+256, profile_len);
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
return status;
|
||||
|
||||
if (callback) {
|
||||
if (!callback(profile, profile_len+256, header, header_len, userdata))
|
||||
return DC_STATUS_DONE;
|
||||
}
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
deepblu_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
|
||||
{
|
||||
dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
|
||||
deepblu_device_t *device = (deepblu_device_t *) abstract;
|
||||
unsigned char nrdives, val;
|
||||
dc_status_t status;
|
||||
int i;
|
||||
|
||||
val = 0;
|
||||
status = deepblu_send_recv(device, CMD_GETDIVENR, &val, 1, &nrdives, 1);
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
return status;
|
||||
|
||||
if (!nrdives)
|
||||
return DC_STATUS_SUCCESS;
|
||||
|
||||
progress.maximum = nrdives;
|
||||
progress.current = 0;
|
||||
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
|
||||
|
||||
for (i = 1; i <= nrdives; i++) {
|
||||
if (device_is_cancelled(abstract)) {
|
||||
dc_status_set_error(&status, DC_STATUS_CANCELLED);
|
||||
break;
|
||||
}
|
||||
|
||||
status = deepblu_download_dive(device, i, callback, userdata);
|
||||
switch (status) {
|
||||
case DC_STATUS_DONE:
|
||||
i = nrdives;
|
||||
break;
|
||||
case DC_STATUS_SUCCESS:
|
||||
break;
|
||||
default:
|
||||
return status;
|
||||
}
|
||||
progress.current = i;
|
||||
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
|
||||
}
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
43
src/deepblu.h
Normal file
43
src/deepblu.h
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Deepblu Cosmiq+ downloading/parsing
|
||||
*
|
||||
* Copyright (C) 2018 Linus Torvalds
|
||||
*
|
||||
* 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 DEEPBLU_H
|
||||
#define DEEPBLU_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
|
||||
deepblu_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream);
|
||||
|
||||
dc_status_t
|
||||
deepblu_parser_create (dc_parser_t **parser, dc_context_t *context);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
#endif /* DEEPBLU_H */
|
||||
361
src/deepblu_parser.c
Normal file
361
src/deepblu_parser.c
Normal file
@ -0,0 +1,361 @@
|
||||
/*
|
||||
* Deeplu Cosmiq+ parsing
|
||||
*
|
||||
* Copyright (C) 2019 Linus Torvalds
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "deepblu.h"
|
||||
#include "context-private.h"
|
||||
#include "parser-private.h"
|
||||
#include "array.h"
|
||||
|
||||
#define C_ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
|
||||
|
||||
#define MAXFIELDS 128
|
||||
|
||||
struct msg_desc;
|
||||
|
||||
#define MAXTYPE 16
|
||||
#define MAXGASES 16
|
||||
#define MAXSTRINGS 32
|
||||
|
||||
typedef struct deepblu_parser_t {
|
||||
dc_parser_t base;
|
||||
|
||||
dc_sample_callback_t callback;
|
||||
void *userdata;
|
||||
|
||||
// 20 sec for scuba, 1 sec for freedives
|
||||
int sample_interval;
|
||||
|
||||
// Field cache
|
||||
struct {
|
||||
unsigned int initialized;
|
||||
|
||||
// dc_get_field() data
|
||||
unsigned int DIVETIME;
|
||||
double MAXDEPTH;
|
||||
double AVGDEPTH;
|
||||
double ATMOSPHERIC;
|
||||
dc_divemode_t DIVEMODE;
|
||||
unsigned int GASMIX_COUNT;
|
||||
dc_salinity_t SALINITY;
|
||||
dc_gasmix_t gasmix[MAXGASES];
|
||||
|
||||
dc_field_string_t strings[MAXSTRINGS];
|
||||
} cache;
|
||||
} deepblu_parser_t;
|
||||
|
||||
// I *really* need to make this generic
|
||||
static void add_string(deepblu_parser_t *deepblu, const char *desc, const char *data);
|
||||
static void add_string_fmt(deepblu_parser_t *deepblu, const char *desc, const char *fmt, ...);
|
||||
|
||||
/*
|
||||
* Macro to make it easy to set DC_FIELD_xyz values
|
||||
*/
|
||||
#define ASSIGN_FIELD(name, value) do { \
|
||||
deepblu->cache.initialized |= 1u << DC_FIELD_##name; \
|
||||
deepblu->cache.name = (value); \
|
||||
} while (0)
|
||||
|
||||
|
||||
static dc_status_t deepblu_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size);
|
||||
static dc_status_t deepblu_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime);
|
||||
static dc_status_t deepblu_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value);
|
||||
static dc_status_t deepblu_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata);
|
||||
|
||||
static const dc_parser_vtable_t deepblu_parser_vtable = {
|
||||
sizeof(deepblu_parser_t),
|
||||
DC_FAMILY_DEEPBLU,
|
||||
deepblu_parser_set_data, /* set_data */
|
||||
deepblu_parser_get_datetime, /* datetime */
|
||||
deepblu_parser_get_field, /* fields */
|
||||
deepblu_parser_samples_foreach, /* samples_foreach */
|
||||
NULL /* destroy */
|
||||
};
|
||||
|
||||
dc_status_t
|
||||
deepblu_parser_create (dc_parser_t **out, dc_context_t *context)
|
||||
{
|
||||
deepblu_parser_t *parser = NULL;
|
||||
|
||||
if (out == NULL)
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
|
||||
// Allocate memory.
|
||||
parser = (deepblu_parser_t *) dc_parser_allocate (context, &deepblu_parser_vtable);
|
||||
if (parser == NULL) {
|
||||
ERROR (context, "Failed to allocate memory.");
|
||||
return DC_STATUS_NOMEMORY;
|
||||
}
|
||||
|
||||
*out = (dc_parser_t *) parser;
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* FIXME! This should all be generic.
|
||||
*
|
||||
* Now it's just copied between all the different
|
||||
* dive computers that support the strings..
|
||||
*/
|
||||
static void add_string(deepblu_parser_t *deepblu, const char *desc, const char *value)
|
||||
{
|
||||
int i;
|
||||
|
||||
deepblu->cache.initialized |= 1 << DC_FIELD_STRING;
|
||||
for (i = 0; i < MAXSTRINGS; i++) {
|
||||
dc_field_string_t *str = deepblu->cache.strings+i;
|
||||
if (str->desc)
|
||||
continue;
|
||||
str->desc = desc;
|
||||
str->value = strdup(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void add_string_fmt(deepblu_parser_t *deepblu, const char *desc, const char *fmt, ...)
|
||||
{
|
||||
char buffer[256];
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
buffer[sizeof(buffer)-1] = 0;
|
||||
(void) vsnprintf(buffer, sizeof(buffer)-1, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
add_string(deepblu, desc, buffer);
|
||||
}
|
||||
|
||||
|
||||
static double
|
||||
pressure_to_depth(unsigned int mbar)
|
||||
{
|
||||
// Specific weight of seawater (millibar to cm)
|
||||
const double specific_weight = 1.024 * 0.980665;
|
||||
|
||||
// Absolute pressure, subtract surface pressure
|
||||
if (mbar < 1013)
|
||||
return 0.0;
|
||||
mbar -= 1013;
|
||||
return mbar / specific_weight / 100.0;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
deepblu_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size)
|
||||
{
|
||||
deepblu_parser_t *deepblu = (deepblu_parser_t *) abstract;
|
||||
const unsigned char *hdr = data;
|
||||
const unsigned char *profile = data + 256;
|
||||
unsigned int divetime, maxpressure;
|
||||
|
||||
if (size < 256)
|
||||
return DC_STATUS_IO;
|
||||
|
||||
deepblu->callback = NULL;
|
||||
deepblu->userdata = NULL;
|
||||
memset(&deepblu->cache, 0, sizeof(deepblu->cache));
|
||||
|
||||
// LE16 at 0 is 'dive number'
|
||||
|
||||
// LE16 at 12 is the dive time
|
||||
// It's in seconds for freedives, minutes for scuba/gauge
|
||||
divetime = hdr[12] + 256*hdr[13];
|
||||
|
||||
// Byte at 2 is 'activity type' (2 = scuba, 3 = gauge, 4 = freedive)
|
||||
// Byte at 3 is O2 percentage
|
||||
switch (data[2]) {
|
||||
case 2:
|
||||
// SCUBA - divetime in minutes
|
||||
divetime *= 60;
|
||||
deepblu->cache.gasmix[0].oxygen = data[3] / 100.0;
|
||||
deepblu->cache.initialized |= 1u << DC_FIELD_GASMIX;
|
||||
ASSIGN_FIELD(GASMIX_COUNT, 1);
|
||||
ASSIGN_FIELD(DIVEMODE, DC_DIVEMODE_OC);
|
||||
break;
|
||||
case 3:
|
||||
// GAUGE - divetime in minutes
|
||||
divetime *= 60;
|
||||
ASSIGN_FIELD(DIVEMODE, DC_DIVEMODE_GAUGE);
|
||||
break;
|
||||
case 4:
|
||||
// FREEDIVE - divetime in seconds
|
||||
ASSIGN_FIELD(DIVEMODE, DC_DIVEMODE_FREEDIVE);
|
||||
deepblu->sample_interval = 1;
|
||||
break;
|
||||
default:
|
||||
ERROR (abstract->context, "Deepblu: unknown activity type '%02x'", data[2]);
|
||||
break;
|
||||
}
|
||||
|
||||
// Seems to be fixed at 20s for scuba, 1s for freedive
|
||||
deepblu->sample_interval = hdr[26];
|
||||
|
||||
maxpressure = hdr[22] + 256*hdr[23]; // Maxpressure in millibar
|
||||
|
||||
ASSIGN_FIELD(DIVETIME, divetime);
|
||||
ASSIGN_FIELD(MAXDEPTH, pressure_to_depth(maxpressure));
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// The layout of the header in the 'data' is
|
||||
// 0: LE16 dive number
|
||||
// 2: dive type byte?
|
||||
// 3: O2 percentage byte
|
||||
// 4: unknown
|
||||
// 5: unknown
|
||||
// 6: LE16 year
|
||||
// 8: day of month
|
||||
// 9: month
|
||||
// 10: minute
|
||||
// 11: hour
|
||||
// 12: LE16 dive time
|
||||
// 14: LE16 ??
|
||||
// 16: LE16 surface pressure?
|
||||
// 18: LE16 ??
|
||||
// 20: LE16 ??
|
||||
// 22: LE16 max depth pressure
|
||||
// 24: LE16 water temp
|
||||
// 26: LE16 ??
|
||||
// 28: LE16 ??
|
||||
// 30: LE16 ??
|
||||
// 32: LE16 ??
|
||||
// 34: LE16 ??
|
||||
static dc_status_t
|
||||
deepblu_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime)
|
||||
{
|
||||
deepblu_parser_t *deepblu = (deepblu_parser_t *) abstract;
|
||||
const unsigned char *data = deepblu->base.data;
|
||||
int len = deepblu->base.size;
|
||||
|
||||
if (len < 256)
|
||||
return DC_STATUS_IO;
|
||||
datetime->year = data[6] + (data[7] << 8);
|
||||
datetime->day = data[8];
|
||||
datetime->month = data[9];
|
||||
datetime->minute = data[10];
|
||||
datetime->hour = data[11];
|
||||
datetime->second = 0;
|
||||
datetime->timezone = DC_TIMEZONE_NONE;
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static dc_status_t get_string_field(dc_field_string_t *strings, unsigned idx, dc_field_string_t *value)
|
||||
{
|
||||
if (idx < MAXSTRINGS) {
|
||||
dc_field_string_t *res = strings+idx;
|
||||
if (res->desc && res->value) {
|
||||
*value = *res;
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
}
|
||||
|
||||
// Ugly define thing makes the code much easier to read
|
||||
// I'd love to use __typeof__, but that's a gcc'ism
|
||||
#define field_value(p, NAME) \
|
||||
(memcpy((p), &deepblu->cache.NAME, sizeof(deepblu->cache.NAME)), DC_STATUS_SUCCESS)
|
||||
// Hacky hack hack
|
||||
#define GASMIX gasmix[flags]
|
||||
|
||||
static dc_status_t
|
||||
deepblu_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value)
|
||||
{
|
||||
deepblu_parser_t *deepblu = (deepblu_parser_t *) abstract;
|
||||
|
||||
if (!value)
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
|
||||
/* This whole sequence should be standardized */
|
||||
if (!(deepblu->cache.initialized & (1 << type)))
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
|
||||
switch (type) {
|
||||
case DC_FIELD_DIVETIME:
|
||||
return field_value(value, DIVETIME);
|
||||
case DC_FIELD_MAXDEPTH:
|
||||
return field_value(value, MAXDEPTH);
|
||||
case DC_FIELD_AVGDEPTH:
|
||||
return field_value(value, AVGDEPTH);
|
||||
case DC_FIELD_GASMIX_COUNT:
|
||||
case DC_FIELD_TANK_COUNT:
|
||||
return field_value(value, GASMIX_COUNT);
|
||||
case DC_FIELD_GASMIX:
|
||||
if (flags >= MAXGASES)
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
return field_value(value, GASMIX);
|
||||
case DC_FIELD_SALINITY:
|
||||
return field_value(value, SALINITY);
|
||||
case DC_FIELD_ATMOSPHERIC:
|
||||
return field_value(value, ATMOSPHERIC);
|
||||
case DC_FIELD_DIVEMODE:
|
||||
return field_value(value, DIVEMODE);
|
||||
case DC_FIELD_TANK:
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
case DC_FIELD_STRING:
|
||||
return get_string_field(deepblu->cache.strings, flags, (dc_field_string_t *)value);
|
||||
default:
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
}
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
deepblu_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
|
||||
{
|
||||
deepblu_parser_t *deepblu = (deepblu_parser_t *) abstract;
|
||||
const unsigned char *data = deepblu->base.data;
|
||||
int len = deepblu->base.size, i;
|
||||
|
||||
deepblu->callback = callback;
|
||||
deepblu->userdata = userdata;
|
||||
|
||||
// Skip the header information
|
||||
if (len < 256)
|
||||
return DC_STATUS_IO;
|
||||
data += 256;
|
||||
len -= 256;
|
||||
|
||||
// The rest should be samples every 20s with temperature and depth
|
||||
for (i = 0; i < len/4; i++) {
|
||||
dc_sample_value_t sample = {0};
|
||||
unsigned int temp = data[0]+256*data[1];
|
||||
unsigned int pressure = data[2]+256*data[3];
|
||||
|
||||
data += 4;
|
||||
sample.time = (i+1)*deepblu->sample_interval;
|
||||
if (callback) callback (DC_SAMPLE_TIME, sample, userdata);
|
||||
|
||||
sample.depth = pressure_to_depth(pressure);
|
||||
if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
|
||||
|
||||
sample.temperature = temp / 10.0;
|
||||
if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
|
||||
}
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
@ -45,8 +45,10 @@ static int dc_filter_suunto (dc_transport_t transport, const void *userdata);
|
||||
static int dc_filter_shearwater (dc_transport_t transport, const void *userdata);
|
||||
static int dc_filter_hw (dc_transport_t transport, const void *userdata);
|
||||
static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata);
|
||||
static int dc_filter_garmin (dc_transport_t transport, const void *userdata);
|
||||
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_deepblu (dc_transport_t transport, const void *userdata);
|
||||
|
||||
static dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item);
|
||||
|
||||
@ -225,23 +227,23 @@ static const dc_descriptor_t g_descriptors[] = {
|
||||
{"Aeris", "A300CS", DC_FAMILY_OCEANIC_ATOM2, 0x454C, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Tusa", "Talis", DC_FAMILY_OCEANIC_ATOM2, 0x454E, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Beuchat", "Mundial 3", DC_FAMILY_OCEANIC_ATOM2, 0x4550, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Oceanic", "Pro Plus X", DC_FAMILY_OCEANIC_ATOM2, 0x4552, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Oceanic", "Pro Plus X", DC_FAMILY_OCEANIC_ATOM2, 0x4552, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
|
||||
{"Oceanic", "F10", DC_FAMILY_OCEANIC_ATOM2, 0x4553, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Oceanic", "F11", DC_FAMILY_OCEANIC_ATOM2, 0x4554, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Subgear", "XP-Air", DC_FAMILY_OCEANIC_ATOM2, 0x4555, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Sherwood", "Vision", DC_FAMILY_OCEANIC_ATOM2, 0x4556, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Oceanic", "VTX", DC_FAMILY_OCEANIC_ATOM2, 0x4557, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Aqualung", "i300", DC_FAMILY_OCEANIC_ATOM2, 0x4559, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Aqualung", "i750TC", DC_FAMILY_OCEANIC_ATOM2, 0x455A, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Aqualung", "i750TC", DC_FAMILY_OCEANIC_ATOM2, 0x455A, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, NULL},
|
||||
{"Aqualung", "i450T", DC_FAMILY_OCEANIC_ATOM2, 0x4641, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Aqualung", "i550", DC_FAMILY_OCEANIC_ATOM2, 0x4642, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Aqualung", "i200", DC_FAMILY_OCEANIC_ATOM2, 0x4646, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Aqualung", "i300C", DC_FAMILY_OCEANIC_ATOM2, 0x4648, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Aqualung", "i200C", DC_FAMILY_OCEANIC_ATOM2, 0x4649, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Aqualung", "i300C", DC_FAMILY_OCEANIC_ATOM2, 0x4648, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
|
||||
{"Aqualung", "i200C", DC_FAMILY_OCEANIC_ATOM2, 0x4649, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
|
||||
{"Aqualung", "i100", DC_FAMILY_OCEANIC_ATOM2, 0x464E, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Aqualung", "i770R", DC_FAMILY_OCEANIC_ATOM2, 0x4651, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Aqualung", "i550C", DC_FAMILY_OCEANIC_ATOM2, 0x4652, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Oceanic", "Geo 4.0", DC_FAMILY_OCEANIC_ATOM2, 0x4653, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Aqualung", "i770R", DC_FAMILY_OCEANIC_ATOM2, 0x4651, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
|
||||
{"Aqualung", "i550C", DC_FAMILY_OCEANIC_ATOM2, 0x4652, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
|
||||
{"Oceanic", "Geo 4.0", DC_FAMILY_OCEANIC_ATOM2, 0x4653, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
|
||||
/* Mares Nemo */
|
||||
{"Mares", "Nemo", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL},
|
||||
{"Mares", "Nemo Steel", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL},
|
||||
@ -371,6 +373,10 @@ static const dc_descriptor_t g_descriptors[] = {
|
||||
{"Cochran", "EMC-20H", DC_FAMILY_COCHRAN_COMMANDER, 5, DC_TRANSPORT_SERIAL, NULL},
|
||||
/* Tecdiving DiveComputer.eu */
|
||||
{"Tecdiving", "DiveComputer.eu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_tecdiving},
|
||||
/* Garmin */
|
||||
{"Garmin", "Descent Mk1", DC_FAMILY_GARMIN, 2859, DC_TRANSPORT_USBSTORAGE, dc_filter_garmin},
|
||||
/* Deepblu */
|
||||
{"Deepblu", "Cosmiq+", DC_FAMILY_DEEPBLU, 0, DC_TRANSPORT_BLE, dc_filter_deepblu},
|
||||
};
|
||||
|
||||
static int
|
||||
@ -561,6 +567,19 @@ static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int dc_filter_garmin (dc_transport_t transport, const void *userdata)
|
||||
{
|
||||
static const dc_usb_desc_t usbhid[] = {
|
||||
{0x091e, 0x2b2b}, // Garmin Descent Mk1
|
||||
};
|
||||
|
||||
if (transport == DC_TRANSPORT_USBSTORAGE) {
|
||||
return DC_FILTER_INTERNAL (userdata, usbhid, 0, dc_match_usb);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int dc_filter_mares (dc_transport_t transport, const void *userdata)
|
||||
{
|
||||
static const char * const bluetooth[] = {
|
||||
@ -587,6 +606,19 @@ static int dc_filter_divesystem (dc_transport_t transport, const void *userdata)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int dc_filter_deepblu (dc_transport_t transport, const void *userdata)
|
||||
{
|
||||
static const char * const bluetooth[] = {
|
||||
"COSMIQ",
|
||||
};
|
||||
|
||||
if (transport == DC_TRANSPORT_BLE) {
|
||||
return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_name);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
dc_status_t
|
||||
dc_descriptor_iterator (dc_iterator_t **out)
|
||||
{
|
||||
|
||||
@ -57,6 +57,8 @@
|
||||
#include "divesystem_idive.h"
|
||||
#include "cochran_commander.h"
|
||||
#include "tecdiving_divecomputereu.h"
|
||||
#include "garmin.h"
|
||||
#include "deepblu.h"
|
||||
|
||||
#include "device-private.h"
|
||||
#include "context-private.h"
|
||||
@ -211,6 +213,12 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr
|
||||
case DC_FAMILY_TECDIVING_DIVECOMPUTEREU:
|
||||
rc = tecdiving_divecomputereu_device_open (&device, context, iostream);
|
||||
break;
|
||||
case DC_FAMILY_GARMIN:
|
||||
rc = garmin_device_open (&device, context, iostream);
|
||||
break;
|
||||
case DC_FAMILY_DEEPBLU:
|
||||
rc = deepblu_device_open (&device, context, iostream);
|
||||
break;
|
||||
default:
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
}
|
||||
|
||||
338
src/garmin.c
Normal file
338
src/garmin.c
Normal file
@ -0,0 +1,338 @@
|
||||
/*
|
||||
* Garmin Descent Mk1 USB storage downloading
|
||||
*
|
||||
* Copyright (C) 2018 Linus Torvalds
|
||||
*
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "garmin.h"
|
||||
#include "context-private.h"
|
||||
#include "device-private.h"
|
||||
#include "array.h"
|
||||
|
||||
typedef struct garmin_device_t {
|
||||
dc_device_t base;
|
||||
dc_iostream_t *iostream;
|
||||
unsigned char fingerprint[FIT_NAME_SIZE];
|
||||
} garmin_device_t;
|
||||
|
||||
static dc_status_t garmin_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size);
|
||||
static dc_status_t garmin_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata);
|
||||
static dc_status_t garmin_device_close (dc_device_t *abstract);
|
||||
|
||||
static const dc_device_vtable_t garmin_device_vtable = {
|
||||
sizeof(garmin_device_t),
|
||||
DC_FAMILY_GARMIN,
|
||||
garmin_device_set_fingerprint, /* set_fingerprint */
|
||||
NULL, /* read */
|
||||
NULL, /* write */
|
||||
NULL, /* dump */
|
||||
garmin_device_foreach, /* foreach */
|
||||
NULL, /* timesync */
|
||||
garmin_device_close, /* close */
|
||||
};
|
||||
|
||||
dc_status_t
|
||||
garmin_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream)
|
||||
{
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
garmin_device_t *device = NULL;
|
||||
|
||||
if (out == NULL)
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
|
||||
// Allocate memory.
|
||||
device = (garmin_device_t *) dc_device_allocate (context, &garmin_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));
|
||||
|
||||
*out = (dc_device_t *) device;
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
garmin_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size)
|
||||
{
|
||||
garmin_device_t *device = (garmin_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
|
||||
garmin_device_close (dc_device_t *abstract)
|
||||
{
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
garmin_device_t *device = (garmin_device_t *) abstract;
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
struct file_list {
|
||||
int nr, allocated;
|
||||
struct fit_name *array;
|
||||
};
|
||||
|
||||
static int name_cmp(const void *a, const void *b)
|
||||
{
|
||||
// Sort reverse string ordering (newest first), so use 'b,a'
|
||||
return strcmp(b,a);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the FIT file list and sort it.
|
||||
*
|
||||
* Return number of files found.
|
||||
*/
|
||||
static int get_file_list(dc_device_t *abstract, DIR *dir, struct file_list *files)
|
||||
{
|
||||
struct dirent *de;
|
||||
|
||||
DEBUG (abstract->context, "Iterating over Garmin files");
|
||||
while ((de = readdir(dir)) != NULL) {
|
||||
int len = strlen(de->d_name);
|
||||
struct fit_name *entry;
|
||||
const char *explain = NULL;
|
||||
|
||||
DEBUG (abstract->context, " %s", de->d_name);
|
||||
|
||||
if (len < 5)
|
||||
explain = "name too short";
|
||||
if (len >= FIT_NAME_SIZE)
|
||||
explain = "name too long";
|
||||
if (strncasecmp(de->d_name + len - 4, ".FIT", 4))
|
||||
explain = "name lacks FIT suffix";
|
||||
|
||||
DEBUG (abstract->context, " %s - %s", de->d_name, explain ? explain : "adding to list");
|
||||
if (explain)
|
||||
continue;
|
||||
|
||||
if (files->nr == files->allocated) {
|
||||
struct fit_name *array;
|
||||
int n = 3*(files->allocated + 8)/2;
|
||||
size_t new_size;
|
||||
|
||||
new_size = n * sizeof(array[0]);
|
||||
array = realloc(files->array, new_size);
|
||||
if (!array)
|
||||
return DC_STATUS_NOMEMORY;
|
||||
|
||||
files->array = array;
|
||||
files->allocated = n;
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE! This depends on the zero-padding that strncpy does.
|
||||
*
|
||||
* strncpy() doesn't just limit the size of the copy, it
|
||||
* will zero-pad the end of the result buffer.
|
||||
*/
|
||||
entry = files->array + files->nr++;
|
||||
strncpy(entry->name, de->d_name, FIT_NAME_SIZE);
|
||||
}
|
||||
DEBUG (abstract->context, "Found %d files", files->nr);
|
||||
|
||||
qsort(files->array, files->nr, sizeof(struct fit_name), name_cmp);
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
#ifndef O_BINARY
|
||||
#define O_BINARY 0
|
||||
#endif
|
||||
|
||||
static dc_status_t
|
||||
read_file(char *pathname, int pathlen, const char *name, dc_buffer_t *file)
|
||||
{
|
||||
int fd, rc;
|
||||
|
||||
pathname[pathlen] = '/';
|
||||
memcpy(pathname+pathlen+1, name, FIT_NAME_SIZE);
|
||||
fd = open(pathname, O_RDONLY | O_BINARY);
|
||||
|
||||
if (fd < 0)
|
||||
return DC_STATUS_IO;
|
||||
|
||||
rc = DC_STATUS_SUCCESS;
|
||||
for (;;) {
|
||||
char buffer[4096];
|
||||
int n;
|
||||
|
||||
n = read(fd, buffer, sizeof(buffer));
|
||||
if (!n)
|
||||
break;
|
||||
if (n > 0) {
|
||||
dc_buffer_append(file, buffer, n);
|
||||
continue;
|
||||
}
|
||||
rc = DC_STATUS_IO;
|
||||
break;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
garmin_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
|
||||
{
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
garmin_device_t *device = (garmin_device_t *) abstract;
|
||||
dc_parser_t *parser;
|
||||
char pathname[PATH_MAX];
|
||||
size_t pathlen;
|
||||
struct file_list files = { 0, 0, NULL };
|
||||
dc_buffer_t *file;
|
||||
DIR *dir;
|
||||
int rc;
|
||||
|
||||
// Read the directory name from the iostream
|
||||
rc = dc_iostream_read(device->iostream, &pathname, sizeof(pathname)-1, &pathlen);
|
||||
if (rc != DC_STATUS_SUCCESS)
|
||||
return rc;
|
||||
pathname[pathlen] = 0;
|
||||
|
||||
// The actual dives are under the "Garmin/Activity/" directory
|
||||
// as FIT files, with names like "2018-08-20-10-23-30.fit".
|
||||
// Make sure our buffer is big enough.
|
||||
if (pathlen + strlen("/Garmin/Activity/") + FIT_NAME_SIZE + 2 > PATH_MAX) {
|
||||
ERROR (abstract->context, "Invalid Garmin base directory '%s'", pathname);
|
||||
return DC_STATUS_IO;
|
||||
}
|
||||
|
||||
if (pathlen && pathname[pathlen-1] != '/')
|
||||
pathname[pathlen++] = '/';
|
||||
strcpy(pathname + pathlen, "Garmin/Activity");
|
||||
pathlen += strlen("Garmin/Activity");
|
||||
|
||||
dir = opendir(pathname);
|
||||
if (!dir) {
|
||||
ERROR (abstract->context, "Failed to open directory '%s'.", pathname);
|
||||
return DC_STATUS_IO;
|
||||
}
|
||||
|
||||
// Get the list of FIT files
|
||||
rc = get_file_list(abstract, dir, &files);
|
||||
closedir(dir);
|
||||
if (rc != DC_STATUS_SUCCESS || !files.nr) {
|
||||
free(files.array);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Can we find the fingerprint entry?
|
||||
for (int i = 0; i < files.nr; i++) {
|
||||
const char *name = files.array[i].name;
|
||||
|
||||
if (memcmp(name, device->fingerprint, sizeof (device->fingerprint)))
|
||||
continue;
|
||||
|
||||
// Found fingerprint, just cut the array short here
|
||||
files.nr = i;
|
||||
DEBUG (abstract->context, "Ignoring '%s' and older", name);
|
||||
break;
|
||||
}
|
||||
|
||||
// Enable progress notifications.
|
||||
dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
|
||||
progress.maximum = files.nr;
|
||||
progress.current = 0;
|
||||
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
|
||||
|
||||
file = dc_buffer_new (16384);
|
||||
if (file == NULL) {
|
||||
ERROR (abstract->context, "Insufficient buffer space available.");
|
||||
free(files.array);
|
||||
return DC_STATUS_NOMEMORY;
|
||||
}
|
||||
if ((rc = garmin_parser_create(&parser, abstract->context) != DC_STATUS_SUCCESS)) {
|
||||
ERROR (abstract->context, "Failed to create parser for dive verification.");
|
||||
free(files.array);
|
||||
return rc;
|
||||
}
|
||||
|
||||
dc_event_devinfo_t devinfo;
|
||||
dc_event_devinfo_t *devinfo_p = &devinfo;
|
||||
for (int i = 0; i < files.nr; i++) {
|
||||
const char *name = files.array[i].name;
|
||||
const unsigned char *data;
|
||||
unsigned int size;
|
||||
short is_dive = 0;
|
||||
|
||||
if (device_is_cancelled(abstract)) {
|
||||
status = DC_STATUS_CANCELLED;
|
||||
break;
|
||||
}
|
||||
|
||||
// Reset the membuffer, read the data
|
||||
dc_buffer_clear(file);
|
||||
dc_buffer_append(file, name, FIT_NAME_SIZE);
|
||||
|
||||
status = read_file(pathname, pathlen, name, file);
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
break;
|
||||
|
||||
data = dc_buffer_get_data(file);
|
||||
size = dc_buffer_get_size(file);
|
||||
|
||||
is_dive = garmin_parser_is_dive(parser, data, size, devinfo_p);
|
||||
if (devinfo_p) {
|
||||
// first time we came through here, let's emit the
|
||||
// devinfo and vendor events
|
||||
device_event_emit (abstract, DC_EVENT_DEVINFO, devinfo_p);
|
||||
devinfo_p = NULL;
|
||||
}
|
||||
if (!is_dive) {
|
||||
DEBUG (abstract->context, "decided %s isn't a dive.", name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (callback && !callback(data, size, name, FIT_NAME_SIZE, userdata))
|
||||
break;
|
||||
|
||||
progress.current++;
|
||||
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
|
||||
}
|
||||
|
||||
free(files.array);
|
||||
dc_parser_destroy(parser);
|
||||
return status;
|
||||
}
|
||||
59
src/garmin.h
Normal file
59
src/garmin.h
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Garmin Descent Mk1
|
||||
*
|
||||
* Copyright (C) 2018 Linus Torvalds
|
||||
*
|
||||
* 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 GARMIN_H
|
||||
#define GARMIN_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
|
||||
garmin_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream);
|
||||
|
||||
dc_status_t
|
||||
garmin_parser_create (dc_parser_t **parser, dc_context_t *context);
|
||||
|
||||
// we need to be able to call into the parser to check if the
|
||||
// files that we find are actual dives
|
||||
int
|
||||
garmin_parser_is_dive (dc_parser_t *abstract, const unsigned char *data, unsigned int size, dc_event_devinfo_t *devinfo_p);
|
||||
|
||||
// The dive names are of the form "2018-08-20-10-23-30.fit"
|
||||
// With the terminating zero, that's 24 bytes.
|
||||
//
|
||||
// We use this as the fingerprint, but it ends up being a
|
||||
// special fixed header in the parser data too.
|
||||
#define FIT_NAME_SIZE 24
|
||||
|
||||
struct fit_name {
|
||||
char name[FIT_NAME_SIZE];
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
#endif /* GARMIN_H */
|
||||
1385
src/garmin_parser.c
Normal file
1385
src/garmin_parser.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -36,7 +36,7 @@ dc_status_t
|
||||
hw_ostc_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream);
|
||||
|
||||
dc_status_t
|
||||
hw_ostc_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int hwos);
|
||||
hw_ostc_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int serial, unsigned int hwos);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ dc_status_t
|
||||
hw_ostc3_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream);
|
||||
|
||||
dc_status_t
|
||||
hw_ostc3_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model);
|
||||
hw_ostc3_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int serial, unsigned int model);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@ -20,6 +20,12 @@
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
#include "libdivecomputer/units.h"
|
||||
|
||||
@ -61,8 +67,14 @@
|
||||
#define OSTC3_APNEA 3
|
||||
#define OSTC3_PSCR 4
|
||||
|
||||
#define OSTC3_ZHL16 0
|
||||
#define OSTC3_ZHL16_GF 1
|
||||
#define OSTC4_VPM 2
|
||||
|
||||
#define OSTC4 0x3B
|
||||
|
||||
#define UNSUPPORTED 0xFFFFFFFF
|
||||
|
||||
typedef struct hw_ostc_sample_info_t {
|
||||
unsigned int type;
|
||||
unsigned int divisor;
|
||||
@ -78,7 +90,13 @@ typedef struct hw_ostc_layout_t {
|
||||
unsigned int salinity;
|
||||
unsigned int duration;
|
||||
unsigned int temperature;
|
||||
unsigned int battery;
|
||||
unsigned int desat;
|
||||
unsigned int firmware;
|
||||
unsigned int deco_info1;
|
||||
unsigned int deco_info2;
|
||||
unsigned int decomode;
|
||||
unsigned int battery_percentage;
|
||||
} hw_ostc_layout_t;
|
||||
|
||||
typedef struct hw_ostc_gasmix_t {
|
||||
@ -90,6 +108,7 @@ typedef struct hw_ostc_parser_t {
|
||||
dc_parser_t base;
|
||||
unsigned int hwos;
|
||||
unsigned int model;
|
||||
unsigned int serial;
|
||||
// Cached fields.
|
||||
unsigned int cached;
|
||||
unsigned int version;
|
||||
@ -127,7 +146,13 @@ static const hw_ostc_layout_t hw_ostc_layout_ostc = {
|
||||
43, /* salinity */
|
||||
47, /* duration */
|
||||
13, /* temperature */
|
||||
34, /* battery volt after dive */
|
||||
17, /* desat */
|
||||
32, /* firmware */
|
||||
49, /* deco_info1 */
|
||||
50, /* deco_info1 */
|
||||
51, /* decomode */
|
||||
0, /* battery percentage TBD */
|
||||
};
|
||||
|
||||
static const hw_ostc_layout_t hw_ostc_layout_frog = {
|
||||
@ -139,7 +164,13 @@ static const hw_ostc_layout_t hw_ostc_layout_frog = {
|
||||
43, /* salinity */
|
||||
47, /* duration */
|
||||
19, /* temperature */
|
||||
34, /* battery volt after dive */
|
||||
23, /* desat */
|
||||
32, /* firmware */
|
||||
49, /* deco_info1 */
|
||||
50, /* deco_info2 */
|
||||
51, /* decomode */
|
||||
0, /* battery percentage TBD */
|
||||
};
|
||||
|
||||
static const hw_ostc_layout_t hw_ostc_layout_ostc3 = {
|
||||
@ -151,7 +182,13 @@ static const hw_ostc_layout_t hw_ostc_layout_ostc3 = {
|
||||
70, /* salinity */
|
||||
75, /* duration */
|
||||
22, /* temperature */
|
||||
50, /* battery volt after dive */
|
||||
26, /* desat */
|
||||
48, /* firmware */
|
||||
77, /* deco_info1 */
|
||||
78, /* deco_info2 */
|
||||
79, /* decomode */
|
||||
59, /* battery percentage */
|
||||
};
|
||||
|
||||
static unsigned int
|
||||
@ -294,7 +331,7 @@ hw_ostc_parser_cache (hw_ostc_parser_t *parser)
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
hw_ostc_parser_create_internal (dc_parser_t **out, dc_context_t *context, unsigned int hwos, unsigned int model)
|
||||
hw_ostc_parser_create_internal (dc_parser_t **out, dc_context_t *context, unsigned int serial, unsigned int hwos, unsigned int model)
|
||||
{
|
||||
hw_ostc_parser_t *parser = NULL;
|
||||
|
||||
@ -324,6 +361,7 @@ hw_ostc_parser_create_internal (dc_parser_t **out, dc_context_t *context, unsign
|
||||
parser->gasmix[i].oxygen = 0;
|
||||
parser->gasmix[i].helium = 0;
|
||||
}
|
||||
parser->serial = serial;
|
||||
|
||||
*out = (dc_parser_t *) parser;
|
||||
|
||||
@ -332,15 +370,15 @@ hw_ostc_parser_create_internal (dc_parser_t **out, dc_context_t *context, unsign
|
||||
|
||||
|
||||
dc_status_t
|
||||
hw_ostc_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int hwos)
|
||||
hw_ostc_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int serial, unsigned int hwos)
|
||||
{
|
||||
return hw_ostc_parser_create_internal (out, context, hwos, 0);
|
||||
return hw_ostc_parser_create_internal (out, context, serial, hwos, 0);
|
||||
}
|
||||
|
||||
dc_status_t
|
||||
hw_ostc3_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model)
|
||||
hw_ostc3_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int serial, unsigned int model)
|
||||
{
|
||||
return hw_ostc_parser_create_internal (out, context, 1, model);
|
||||
return hw_ostc_parser_create_internal (out, context, serial, 1, model);
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
@ -428,6 +466,8 @@ hw_ostc_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime)
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
#define BUFLEN 32
|
||||
|
||||
static dc_status_t
|
||||
hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value)
|
||||
{
|
||||
@ -452,9 +492,12 @@ hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned
|
||||
|
||||
dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
|
||||
dc_salinity_t *water = (dc_salinity_t *) value;
|
||||
dc_field_string_t *string = (dc_field_string_t *) value;
|
||||
|
||||
unsigned int salinity = data[layout->salinity];
|
||||
if (version == 0x23 || version == 0x24)
|
||||
salinity += 100;
|
||||
char buf[BUFLEN];
|
||||
|
||||
if (value) {
|
||||
switch (type) {
|
||||
@ -552,6 +595,79 @@ hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
}
|
||||
break;
|
||||
case DC_FIELD_STRING:
|
||||
switch(flags) {
|
||||
case 0: /* serial */
|
||||
string->desc = "Serial";
|
||||
snprintf(buf, BUFLEN, "%u", parser->serial);
|
||||
break;
|
||||
case 1: /* battery */
|
||||
string->desc = "Battery at end";
|
||||
unsigned int percentage = (unsigned int) data[layout->battery_percentage];
|
||||
if (percentage != 0xFF && (version == 0x23 || version == 0x24)) {
|
||||
percentage = percentage>100? 100: percentage;
|
||||
snprintf(buf, BUFLEN, "%.2fV, %u%% remaining",
|
||||
array_uint16_le (data + layout->battery) / 1000.0,
|
||||
percentage);
|
||||
} else {
|
||||
snprintf(buf, BUFLEN, "%.2fV", array_uint16_le (data + layout->battery) / 1000.0);
|
||||
}
|
||||
break;
|
||||
case 2: /* desat */
|
||||
string->desc = "Desat time";
|
||||
snprintf(buf, BUFLEN, "%0u:%02u", array_uint16_le (data + layout->desat) / 60,
|
||||
array_uint16_le (data + layout->desat) % 60);
|
||||
break;
|
||||
case 3: /* firmware */
|
||||
string->desc = "FW Version";
|
||||
/* OSTC4 stores firmware as XXXX XYYY YYZZ ZZZB, -> X.Y.Z beta? */
|
||||
if (parser->model == OSTC4) {
|
||||
int firmwareOnDevice = array_uint16_le (data + layout->firmware);
|
||||
unsigned char X = 0, Y = 0, Z = 0, beta = 0;
|
||||
X = (firmwareOnDevice & 0xF800) >> 11;
|
||||
Y = (firmwareOnDevice & 0x07C0) >> 6;
|
||||
Z = (firmwareOnDevice & 0x003E) >> 1;
|
||||
beta = firmwareOnDevice & 0x0001;
|
||||
|
||||
snprintf(buf, BUFLEN, "%u.%u.%u%s\n", X, Y, Z, beta? "beta": "");
|
||||
} else {
|
||||
snprintf(buf, BUFLEN, "%0u.%02u", data[layout->firmware], data[layout->firmware + 1]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 4: /* Deco model */
|
||||
string->desc = "Deco model";
|
||||
if (((version == 0x23 || version == 0x24) && data[layout->decomode] == OSTC3_ZHL16) ||
|
||||
(version == 0x22 && data[layout->decomode] == FROG_ZHL16) ||
|
||||
(version == 0x21 && (data[layout->decomode] == OSTC_ZHL16_OC || data[layout->decomode] == OSTC_ZHL16_CC)))
|
||||
strncpy(buf, "ZH-L16", BUFLEN);
|
||||
else if (((version == 0x23 || version == 0x24) && data[layout->decomode] == OSTC3_ZHL16_GF) ||
|
||||
(version == 0x22 && data[layout->decomode] == FROG_ZHL16_GF) ||
|
||||
(version == 0x21 && (data[layout->decomode] == OSTC_ZHL16_OC_GF || data[layout->decomode] == OSTC_ZHL16_CC_GF)))
|
||||
strncpy(buf, "ZH-L16-GF", BUFLEN);
|
||||
else if (((version == 0x24) && data[layout->decomode] == OSTC4_VPM))
|
||||
strncpy(buf, "VPM", BUFLEN);
|
||||
else
|
||||
return DC_STATUS_DATAFORMAT;
|
||||
break;
|
||||
case 5: /* Deco model info */
|
||||
string->desc = "Deco model info";
|
||||
if (((version == 0x23 || version == 0x24) && data[layout->decomode] == OSTC3_ZHL16) ||
|
||||
(version == 0x22 && data[layout->decomode] == FROG_ZHL16) ||
|
||||
(version == 0x21 && (data[layout->decomode] == OSTC_ZHL16_OC || data[layout->decomode] == OSTC_ZHL16_CC)))
|
||||
snprintf(buf, BUFLEN, "Saturation %u, Desaturation %u", layout->deco_info1, layout->deco_info2);
|
||||
else if (((version == 0x23 || version == 0x24) && data[layout->decomode] == OSTC3_ZHL16_GF) ||
|
||||
(version == 0x22 && data[layout->decomode] == FROG_ZHL16_GF) ||
|
||||
(version == 0x21 && (data[layout->decomode] == OSTC_ZHL16_OC_GF || data[layout->decomode] == OSTC_ZHL16_CC_GF)))
|
||||
snprintf(buf, BUFLEN, "GF %u/%u", data[layout->deco_info1], data[layout->deco_info2]);
|
||||
else
|
||||
return DC_STATUS_DATAFORMAT;
|
||||
break;
|
||||
default:
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
}
|
||||
string->value = strdup(buf);
|
||||
break;
|
||||
default:
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
}
|
||||
|
||||
@ -68,6 +68,8 @@ struct dc_iostream_vtable_t {
|
||||
dc_status_t (*sleep) (dc_iostream_t *iostream, unsigned int milliseconds);
|
||||
|
||||
dc_status_t (*close) (dc_iostream_t *iostream);
|
||||
|
||||
const char *(*get_name) (dc_iostream_t *iostream);
|
||||
};
|
||||
|
||||
dc_iostream_t *
|
||||
|
||||
@ -186,43 +186,74 @@ dc_iostream_configure (dc_iostream_t *iostream, unsigned int baudrate, unsigned
|
||||
dc_status_t
|
||||
dc_iostream_read (dc_iostream_t *iostream, void *data, size_t size, size_t *actual)
|
||||
{
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
if (actual)
|
||||
*actual = 0;
|
||||
|
||||
if (iostream == NULL || iostream->vtable->read == NULL)
|
||||
return DC_STATUS_IO;
|
||||
|
||||
while (size) {
|
||||
dc_status_t status;
|
||||
size_t nbytes = 0;
|
||||
|
||||
if (iostream == NULL || iostream->vtable->read == NULL) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
status = iostream->vtable->read (iostream, data, size, &nbytes);
|
||||
|
||||
HEXDUMP (iostream->context, DC_LOGLEVEL_INFO, "Read", (unsigned char *) data, nbytes);
|
||||
|
||||
out:
|
||||
if (actual)
|
||||
/*
|
||||
* If the reader is able to handle partial results,
|
||||
* return them as such. NOTE! No need to add up a
|
||||
* total, we will go through this loop only once
|
||||
* in this case.
|
||||
*/
|
||||
if (actual) {
|
||||
*actual = nbytes;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
return status;
|
||||
|
||||
/*
|
||||
* Continue reading to fill up the whole buffer,
|
||||
* since the reader is not able to handle a
|
||||
* partial result.
|
||||
*/
|
||||
data = (void *)(nbytes + (char *)data);
|
||||
size -= nbytes;
|
||||
}
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
dc_status_t
|
||||
dc_iostream_write (dc_iostream_t *iostream, const void *data, size_t size, size_t *actual)
|
||||
{
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
if (actual)
|
||||
*actual = 0;
|
||||
|
||||
if (iostream == NULL || iostream->vtable->write == NULL)
|
||||
return DC_STATUS_IO;
|
||||
|
||||
while (size) {
|
||||
dc_status_t status;
|
||||
size_t nbytes = 0;
|
||||
|
||||
if (iostream == NULL || iostream->vtable->write == NULL) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
status = iostream->vtable->write (iostream, data, size, &nbytes);
|
||||
|
||||
HEXDUMP (iostream->context, DC_LOGLEVEL_INFO, "Write", (const unsigned char *) data, nbytes);
|
||||
|
||||
out:
|
||||
if (actual)
|
||||
if (actual) {
|
||||
*actual = nbytes;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
return status;
|
||||
|
||||
data = (void *)(nbytes + (char *)data);
|
||||
size -= nbytes;
|
||||
}
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
dc_status_t
|
||||
@ -274,3 +305,15 @@ dc_iostream_close (dc_iostream_t *iostream)
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
const char *
|
||||
dc_iostream_get_name (dc_iostream_t *iostream)
|
||||
{
|
||||
if (iostream == NULL)
|
||||
return NULL;
|
||||
|
||||
if (iostream->vtable->get_name)
|
||||
return iostream->vtable->get_name (iostream);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -75,6 +75,8 @@ dc_usbhid_device_free
|
||||
dc_usbhid_iterator_new
|
||||
dc_usbhid_open
|
||||
|
||||
dc_usb_storage_open
|
||||
|
||||
dc_custom_open
|
||||
|
||||
dc_parser_new
|
||||
|
||||
@ -482,8 +482,8 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_
|
||||
goto error_free;
|
||||
}
|
||||
|
||||
// Set the timeout for receiving data (1000 ms).
|
||||
status = dc_iostream_set_timeout (device->iostream, 1000);
|
||||
// Set the timeout for receiving data (3 sec = 3000 ms).
|
||||
status = dc_iostream_set_timeout (device->iostream, 3000);
|
||||
if (status != DC_STATUS_SUCCESS) {
|
||||
ERROR (context, "Failed to set the timeout.");
|
||||
goto error_free;
|
||||
|
||||
@ -58,6 +58,7 @@
|
||||
typedef struct oceanic_atom2_device_t {
|
||||
oceanic_common_device_t base;
|
||||
dc_iostream_t *iostream;
|
||||
unsigned int sequence;
|
||||
unsigned int delay;
|
||||
unsigned int bigpage;
|
||||
unsigned char cache[256];
|
||||
@ -123,7 +124,7 @@ static const oceanic_common_version_t oceanic_atom2b_version[] = {
|
||||
{"AQUAI300 \0\0 512K"},
|
||||
{"HOLLDG03 \0\0 512K"},
|
||||
{"AQUAI100 \0\0 512K"},
|
||||
{"AQUA300C \0\0 512K"},
|
||||
{"AQUA300C \0\0 \0\0\0\0"},
|
||||
};
|
||||
|
||||
static const oceanic_common_version_t oceanic_atom2c_version[] = {
|
||||
@ -195,12 +196,13 @@ static const oceanic_common_version_t oceanic_reactpro_version[] = {
|
||||
{"REACPRO2 \0\0 512K"},
|
||||
};
|
||||
|
||||
// Like the i770R, there's some extended pattern for the last
|
||||
// four digits. The serial communication apparently says "2048"
|
||||
// for this, but the BLE version says "0001".
|
||||
//
|
||||
// The middle two digits are the FW version or something,
|
||||
static const oceanic_common_version_t oceanic_proplusx_version[] = {
|
||||
{"OCEANOCX \0\0 2048"},
|
||||
};
|
||||
|
||||
static const oceanic_common_version_t aqualung_i770r_version[] = {
|
||||
{"AQUA770R \0\0 2048"},
|
||||
{"OCEANOCX \0\0 \0\0\0\0"},
|
||||
};
|
||||
|
||||
static const oceanic_common_version_t aeris_a300cs_version[] = {
|
||||
@ -209,6 +211,19 @@ static const oceanic_common_version_t aeris_a300cs_version[] = {
|
||||
{"AQUAI750 \0\0 2048"},
|
||||
};
|
||||
|
||||
// Not 100% sure what the pattern is.
|
||||
// I've seen:
|
||||
//
|
||||
// "AQUA770R 1A 0001"
|
||||
// "AQUA770R 1A 0090"
|
||||
//
|
||||
// from the same dive computer. On other ones, it's
|
||||
// apparently the two middle digits that change, on
|
||||
// the i770R it might be all of them.
|
||||
static const oceanic_common_version_t aqualung_i770r_version[] = {
|
||||
{"AQUA770R \0\0 \0\0\0\0"},
|
||||
};
|
||||
|
||||
static const oceanic_common_version_t aqualung_i450t_version[] = {
|
||||
{"AQUAI450 \0\0 2048"},
|
||||
};
|
||||
@ -596,7 +611,7 @@ oceanic_atom2_packet (oceanic_atom2_device_t *device, const unsigned char comman
|
||||
|
||||
|
||||
static dc_status_t
|
||||
oceanic_atom2_transfer (oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, unsigned int crc_size)
|
||||
oceanic_atom2_serial_transfer (oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, unsigned int crc_size)
|
||||
{
|
||||
// Send the command to the device. If the device responds with an
|
||||
// ACK byte, the command was received successfully and the answer
|
||||
@ -626,6 +641,200 @@ oceanic_atom2_transfer (oceanic_atom2_device_t *device, const unsigned char comm
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* The BLE GATT packet size is up to 20 bytes and the format is:
|
||||
*
|
||||
* byte 0: <0xCD>
|
||||
* Seems to always have this value. Don't ask what it means
|
||||
* byte 1: <d 1 c s s s s s>
|
||||
* d=0 means "command", d=1 means "reply from dive computer"
|
||||
* 1 is always set, afaik
|
||||
* c=0 means "last packet" in sequence, c=1 means "more packets coming"
|
||||
* sssss is a 5-bit sequence number for packets
|
||||
* byte 2: <cmd seq>
|
||||
* starts at 0 for the connection, incremented for each command
|
||||
* byte 3: <length of data>
|
||||
* 1-16 bytes of data per packet.
|
||||
* byte 4..n: <data>
|
||||
*/
|
||||
static dc_status_t
|
||||
oceanic_atom2_ble_write(oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize)
|
||||
{
|
||||
unsigned char buf[20];
|
||||
unsigned char cmd_seq = device->sequence;
|
||||
unsigned char pkt_seq;
|
||||
|
||||
pkt_seq = 0;
|
||||
while (csize) {
|
||||
dc_status_t ret;
|
||||
unsigned char status = 0x40;
|
||||
unsigned int cpartial = csize;
|
||||
if (cpartial > 16) {
|
||||
cpartial = 16;
|
||||
status |= 0x20;
|
||||
}
|
||||
buf[0] = 0xcd;
|
||||
buf[1] = status | (pkt_seq & 31);
|
||||
buf[2] = cmd_seq;
|
||||
buf[3] = cpartial;
|
||||
memcpy(buf+4, command, cpartial);
|
||||
command += cpartial;
|
||||
csize -= cpartial;
|
||||
ret = dc_iostream_write(device->iostream, buf, 4+cpartial, NULL);
|
||||
if (ret != DC_STATUS_SUCCESS)
|
||||
return ret;
|
||||
pkt_seq++;
|
||||
}
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
oceanic_atom2_ble_read(oceanic_atom2_device_t *device, unsigned char **result_p, unsigned int *size_p)
|
||||
{
|
||||
unsigned char *result = NULL;
|
||||
unsigned int size = 0, allocated = 0;
|
||||
unsigned char buf[20];
|
||||
unsigned char cmd_seq = device->sequence;
|
||||
unsigned char pkt_seq;
|
||||
dc_status_t ret = DC_STATUS_SUCCESS;
|
||||
|
||||
pkt_seq = 0;
|
||||
for (;;) {
|
||||
unsigned char status, expect;
|
||||
size_t transferred = 0;
|
||||
ret = dc_iostream_read(device->iostream, buf, sizeof(buf), &transferred);
|
||||
if (ret != DC_STATUS_SUCCESS)
|
||||
break;
|
||||
|
||||
ret = DC_STATUS_IO;
|
||||
if (transferred < 5 || transferred > 20) {
|
||||
ERROR(device->base.base.context, "Odd BLE packet size %zd", transferred);
|
||||
break;
|
||||
}
|
||||
if (buf[0] != 0xcd)
|
||||
ERROR(device->base.base.context, "Odd first byte (got '%02x', expected 'cd'", buf[0]);
|
||||
|
||||
// Verify status byte
|
||||
expect = 0xc0;
|
||||
expect |= (pkt_seq & 31);
|
||||
status = buf[1];
|
||||
if ((status & ~0x20) != expect)
|
||||
ERROR(device->base.base.context, "Odd status byte (got '%02x', expected '%02x'", buf[1], expect);
|
||||
|
||||
// Verify command sequence byte
|
||||
expect = cmd_seq;
|
||||
if (buf[2] != expect)
|
||||
ERROR(device->base.base.context, "Odd cmd sequence byte (got '%02x', expected '%02x'", buf[2], expect);
|
||||
|
||||
// Verify length byte
|
||||
expect = buf[3];
|
||||
if (expect < 1 || expect > 16) {
|
||||
ERROR(device->base.base.context, "Odd reply size byte (got %d, expected 1..16", buf[3]);
|
||||
break;
|
||||
}
|
||||
|
||||
if (transferred < 4+expect) {
|
||||
ERROR(device->base.base.context, "Packet too small (got %zd bytes, expected at least %d bytes)", transferred, 4+expect);
|
||||
break;
|
||||
}
|
||||
|
||||
if (size + expect > allocated) {
|
||||
unsigned int newsize = size + expect + 100;
|
||||
unsigned char *newalloc = realloc(result, newsize);
|
||||
if (!newalloc) {
|
||||
ret = DC_STATUS_NOMEMORY;
|
||||
break;
|
||||
}
|
||||
result = newalloc;
|
||||
allocated = newsize;
|
||||
}
|
||||
|
||||
memcpy(result + size, buf+4, expect);
|
||||
size += expect;
|
||||
pkt_seq++;
|
||||
|
||||
/* More packets? */
|
||||
if (status & 0x20)
|
||||
continue;
|
||||
|
||||
ret = DC_STATUS_SUCCESS;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret != DC_STATUS_SUCCESS) {
|
||||
free(result);
|
||||
size = 0;
|
||||
result = NULL;
|
||||
}
|
||||
*result_p = result;
|
||||
*size_p = size;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Transfer a command and optionally read return data.
|
||||
*
|
||||
* NOTE! The NUL byte at the end of a command is a serial transfer thing,
|
||||
* and we remove it. The correct thing to do would be to add it on the
|
||||
* serial transfer side instead (or perhaps not send it at all, Jef says
|
||||
* it may be historical), but right now I've tried to minimize the changes
|
||||
* that the BLE transfer code made to the code, so instead this tries to
|
||||
* just skip the extraneous byte.
|
||||
*/
|
||||
static dc_status_t
|
||||
oceanic_atom2_ble_transfer (oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, unsigned int crc_size)
|
||||
{
|
||||
unsigned char buf[20];
|
||||
unsigned char cmd_seq = device->sequence;
|
||||
unsigned char pkt_seq;
|
||||
dc_status_t ret = DC_STATUS_SUCCESS;
|
||||
int retry = 3;
|
||||
|
||||
/*
|
||||
* The serial commands have a NUL byte at the end. It's bogus.
|
||||
* It should be added on the serial transfer side, not removed
|
||||
* here.
|
||||
*/
|
||||
if (csize > 1 && csize < 8 && !command[csize-1])
|
||||
csize--;
|
||||
|
||||
retry:
|
||||
if (--retry < 0)
|
||||
return ret;
|
||||
|
||||
ret = oceanic_atom2_ble_write(device, command, csize);
|
||||
if (ret != DC_STATUS_SUCCESS)
|
||||
return ret;
|
||||
|
||||
pkt_seq = 0;
|
||||
if (answer) {
|
||||
unsigned char *buf;
|
||||
unsigned int size;
|
||||
ret = oceanic_atom2_ble_read(device, &buf, &size);
|
||||
if (ret != DC_STATUS_SUCCESS)
|
||||
goto retry;
|
||||
if (size > asize && buf[0] == ACK) {
|
||||
memcpy(answer, buf+1, asize);
|
||||
device->sequence++;
|
||||
} else {
|
||||
ERROR(device->base.base.context, "Result too small: got %d bytes, expected at least %d bytes", size, asize+1);
|
||||
ret = DC_STATUS_IO;
|
||||
goto retry;
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
oceanic_atom2_transfer (oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, unsigned int crc_size)
|
||||
{
|
||||
if (dc_iostream_get_transport(device->iostream) == DC_TRANSPORT_BLE)
|
||||
return oceanic_atom2_ble_transfer(device, command, csize, answer, asize, crc_size);
|
||||
|
||||
return oceanic_atom2_serial_transfer(device, command, csize, answer, asize, crc_size);
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
oceanic_atom2_quit (oceanic_atom2_device_t *device)
|
||||
@ -662,6 +871,7 @@ oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream
|
||||
// Set the default values.
|
||||
device->iostream = iostream;
|
||||
device->delay = 0;
|
||||
device->sequence = 0;
|
||||
device->bigpage = 1; // no big pages
|
||||
device->cached_page = INVALID;
|
||||
device->cached_highmem = INVALID;
|
||||
@ -783,7 +993,7 @@ oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream
|
||||
} else if (OCEANIC_COMMON_MATCH (device->base.version, oceanic_default_version)) {
|
||||
device->base.layout = &oceanic_default_layout;
|
||||
} else {
|
||||
WARNING (context, "Unsupported device detected!");
|
||||
WARNING (context, "Unsupported device detected (%s)!", device->base.version);
|
||||
device->base.layout = &oceanic_default_layout;
|
||||
if (memcmp(device->base.version + 12, "256K", 4) == 0) {
|
||||
device->base.layout = &oceanic_atom1_layout;
|
||||
@ -839,9 +1049,55 @@ oceanic_atom2_device_keepalive (dc_device_t *abstract)
|
||||
if (rc != DC_STATUS_SUCCESS)
|
||||
return rc;
|
||||
|
||||
/* No answer: increment sequence number manually */
|
||||
device->sequence++;
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* The BLE communication sends a handshake packet that seems
|
||||
* to be a passphrase based on the BLE name of the device
|
||||
* (more specifically the serial number encoded in the name).
|
||||
*
|
||||
* The packet format is:
|
||||
* 0xe5
|
||||
* < 8 bytes of passphrase >
|
||||
* one-byte checksum of the passphrase.
|
||||
*/
|
||||
static dc_status_t
|
||||
oceanic_atom2_send_ble_handshake(oceanic_atom2_device_t *device)
|
||||
{
|
||||
unsigned char handshake[10] = { 0xe5, }, ack[1];
|
||||
const char *bt_name = dc_iostream_get_name(device->iostream);
|
||||
|
||||
/*
|
||||
* Allow skipping the handshake if no name. But the download will
|
||||
* likely fail.
|
||||
*
|
||||
* The format of the name is something like 'FQ001124', where the
|
||||
* two first letters indicate the kind of device it is, and the
|
||||
* six digits are the serial number.
|
||||
*
|
||||
* Jef theorizes that 'FQ' in hexadecimal is 0x4651, which is
|
||||
* the model number of the i770R.
|
||||
*/
|
||||
if (!bt_name || strlen(bt_name) < 8)
|
||||
return DC_STATUS_SUCCESS;
|
||||
|
||||
/* Turn ASCII numbers into just raw byte values */
|
||||
for (int i = 0; i < 6; i++)
|
||||
handshake[i+1] = bt_name[i+2] - '0';
|
||||
|
||||
/* Add simple checksum */
|
||||
handshake[9] = checksum_add_uint8(handshake+1, 8, 0x00);
|
||||
|
||||
/*
|
||||
* .. and send it off.
|
||||
*
|
||||
* NOTE! We don't expect any data back, but we do want the ACK.
|
||||
*/
|
||||
return oceanic_atom2_ble_transfer(device, handshake, sizeof(handshake), ack, 0, 0);
|
||||
}
|
||||
|
||||
dc_status_t
|
||||
oceanic_atom2_device_version (dc_device_t *abstract, unsigned char data[], unsigned int size)
|
||||
@ -862,7 +1118,10 @@ oceanic_atom2_device_version (dc_device_t *abstract, unsigned char data[], unsig
|
||||
|
||||
memcpy (data, answer, PAGESIZE);
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
if (dc_iostream_get_transport(device->iostream) == DC_TRANSPORT_BLE)
|
||||
rc = oceanic_atom2_send_ble_handshake(device);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
@ -983,6 +1242,9 @@ oceanic_atom2_device_write (dc_device_t *abstract, unsigned int address, const u
|
||||
if (rc != DC_STATUS_SUCCESS)
|
||||
return rc;
|
||||
|
||||
/* No answer, increment sequence number manually */
|
||||
device->sequence++;
|
||||
|
||||
nbytes += PAGESIZE;
|
||||
address += PAGESIZE;
|
||||
data += PAGESIZE;
|
||||
|
||||
@ -37,7 +37,7 @@ dc_status_t
|
||||
oceanic_atom2_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream, unsigned int model);
|
||||
|
||||
dc_status_t
|
||||
oceanic_atom2_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int model);
|
||||
oceanic_atom2_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int model, unsigned int serial);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@ -20,6 +20,8 @@
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <libdivecomputer/units.h>
|
||||
|
||||
@ -110,6 +112,7 @@ struct oceanic_atom2_parser_t {
|
||||
unsigned int model;
|
||||
unsigned int headersize;
|
||||
unsigned int footersize;
|
||||
unsigned int serial;
|
||||
// Cached fields.
|
||||
unsigned int cached;
|
||||
unsigned int header;
|
||||
@ -139,7 +142,7 @@ static const dc_parser_vtable_t oceanic_atom2_parser_vtable = {
|
||||
|
||||
|
||||
dc_status_t
|
||||
oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model)
|
||||
oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model, unsigned int serial)
|
||||
{
|
||||
oceanic_atom2_parser_t *parser = NULL;
|
||||
|
||||
@ -190,6 +193,7 @@ oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, unsigned
|
||||
parser->headersize = 5 * PAGESIZE / 2;
|
||||
}
|
||||
|
||||
parser->serial = serial;
|
||||
parser->cached = 0;
|
||||
parser->header = 0;
|
||||
parser->footer = 0;
|
||||
@ -388,6 +392,7 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
#define BUF_LEN 16
|
||||
|
||||
static dc_status_t
|
||||
oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser)
|
||||
@ -532,6 +537,9 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns
|
||||
|
||||
dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
|
||||
dc_salinity_t *water = (dc_salinity_t *) value;
|
||||
dc_field_string_t *string = (dc_field_string_t *) value;
|
||||
|
||||
char buf[BUF_LEN];
|
||||
|
||||
if (value) {
|
||||
switch (type) {
|
||||
@ -561,7 +569,7 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns
|
||||
break;
|
||||
case DC_FIELD_SALINITY:
|
||||
if (parser->model == A300CS || parser->model == VTX ||
|
||||
parser->model == I750TC) {
|
||||
parser->model == I750TC || parser->model == I770R) {
|
||||
if (data[0x18] & 0x80) {
|
||||
water->type = DC_WATER_FRESH;
|
||||
} else {
|
||||
@ -587,6 +595,17 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns
|
||||
return DC_STATUS_DATAFORMAT;
|
||||
}
|
||||
break;
|
||||
case DC_FIELD_STRING:
|
||||
switch(flags) {
|
||||
case 0: /* Serial */
|
||||
string->desc = "Serial";
|
||||
snprintf(buf, BUF_LEN, "%06u", parser->serial);
|
||||
break;
|
||||
default:
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
}
|
||||
string->value = strdup(buf);
|
||||
break;
|
||||
default:
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
}
|
||||
@ -740,7 +759,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
|
||||
if (have_pressure) {
|
||||
unsigned int idx = 2;
|
||||
if (parser->model == A300CS || parser->model == VTX ||
|
||||
parser->model == I750TC)
|
||||
parser->model == I750TC || parser->model == I770R)
|
||||
idx = 16;
|
||||
pressure = array_uint16_le(data + parser->header + idx);
|
||||
if (pressure == 10000)
|
||||
@ -793,7 +812,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
|
||||
tank = 0;
|
||||
pressure = (((data[offset + 7] << 8) + data[offset + 6]) & 0x0FFF);
|
||||
} else if (parser->model == A300CS || parser->model == VTX ||
|
||||
parser->model == I750TC) {
|
||||
parser->model == I750TC || parser->model == I770R) {
|
||||
// Tank pressure (1 psi) and number (one based index)
|
||||
tank = (data[offset + 1] & 0x03) - 1;
|
||||
pressure = ((data[offset + 7] << 8) + data[offset + 6]) & 0x0FFF;
|
||||
|
||||
@ -507,13 +507,14 @@ oceanic_common_device_profile (dc_device_t *abstract, dc_event_progress_t *progr
|
||||
|
||||
// Remove padding from the profile.
|
||||
if (layout->highmem) {
|
||||
// The logbook entry contains the total number of pages containing
|
||||
// profile data, excluding the footer page. Limit the profile size
|
||||
// to this size.
|
||||
unsigned int value = array_uint16_le (profiles + offset + 12);
|
||||
unsigned int value_hi = value & 0xE000;
|
||||
unsigned int value_lo = value & 0x0FFF;
|
||||
unsigned int npages = ((value_hi >> 1) | value_lo) + 1;
|
||||
unsigned char *profile = profiles + offset;
|
||||
// profile+12 and the bottom 4 bits of profile+13 and the top 3 bits of profile+13
|
||||
// is the number of pages of profile data until the start of the footer
|
||||
// (not including the prepended logbook entry).
|
||||
unsigned int high_part = array_uint16_le (profile + 12) & 0xE000;
|
||||
unsigned int low_part = array_uint16_le (profile + 12) & 0x0FFF;
|
||||
unsigned int npages = ((high_part >> 1) | low_part) + 1;
|
||||
// INFO (abstract->context, "profile npages: 0x%X (%d)", npages, npages);
|
||||
unsigned int length = npages * PAGESIZE;
|
||||
if (rb_entry_size > length) {
|
||||
rb_entry_size = length;
|
||||
|
||||
30
src/parser.c
30
src/parser.c
@ -57,6 +57,8 @@
|
||||
#include "divesystem_idive.h"
|
||||
#include "cochran_commander.h"
|
||||
#include "tecdiving_divecomputereu.h"
|
||||
#include "garmin.h"
|
||||
#include "deepblu.h"
|
||||
|
||||
#include "context-private.h"
|
||||
#include "parser-private.h"
|
||||
@ -65,7 +67,7 @@
|
||||
#define REACTPROWHITE 0x4354
|
||||
|
||||
static dc_status_t
|
||||
dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t family, unsigned int model, unsigned int devtime, dc_ticks_t systime)
|
||||
dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t family, unsigned int model, unsigned int serial, unsigned int devtime, dc_ticks_t systime)
|
||||
{
|
||||
dc_status_t rc = DC_STATUS_SUCCESS;
|
||||
dc_parser_t *parser = NULL;
|
||||
@ -88,7 +90,7 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa
|
||||
break;
|
||||
case DC_FAMILY_SUUNTO_VYPER2:
|
||||
case DC_FAMILY_SUUNTO_D9:
|
||||
rc = suunto_d9_parser_create (&parser, context, model);
|
||||
rc = suunto_d9_parser_create (&parser, context, model, serial);
|
||||
break;
|
||||
case DC_FAMILY_SUUNTO_EONSTEEL:
|
||||
rc = suunto_eonsteel_parser_create(&parser, context, model);
|
||||
@ -119,7 +121,7 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa
|
||||
if (model == REACTPROWHITE)
|
||||
rc = oceanic_veo250_parser_create (&parser, context, model);
|
||||
else
|
||||
rc = oceanic_atom2_parser_create (&parser, context, model);
|
||||
rc = oceanic_atom2_parser_create (&parser, context, model, serial);
|
||||
break;
|
||||
case DC_FAMILY_MARES_NEMO:
|
||||
case DC_FAMILY_MARES_PUCK:
|
||||
@ -132,11 +134,11 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa
|
||||
rc = mares_iconhd_parser_create (&parser, context, model);
|
||||
break;
|
||||
case DC_FAMILY_HW_OSTC:
|
||||
rc = hw_ostc_parser_create (&parser, context, 0);
|
||||
rc = hw_ostc_parser_create (&parser, context, serial, 0);
|
||||
break;
|
||||
case DC_FAMILY_HW_FROG:
|
||||
case DC_FAMILY_HW_OSTC3:
|
||||
rc = hw_ostc3_parser_create (&parser, context, model);
|
||||
rc = hw_ostc3_parser_create (&parser, context, serial, model);
|
||||
break;
|
||||
case DC_FAMILY_CRESSI_EDY:
|
||||
case DC_FAMILY_ZEAGLE_N2ITION3:
|
||||
@ -152,10 +154,10 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa
|
||||
rc = atomics_cobalt_parser_create (&parser, context);
|
||||
break;
|
||||
case DC_FAMILY_SHEARWATER_PREDATOR:
|
||||
rc = shearwater_predator_parser_create (&parser, context, model);
|
||||
rc = shearwater_predator_parser_create (&parser, context, model, serial);
|
||||
break;
|
||||
case DC_FAMILY_SHEARWATER_PETREL:
|
||||
rc = shearwater_petrel_parser_create (&parser, context, model);
|
||||
rc = shearwater_petrel_parser_create (&parser, context, model, serial);
|
||||
break;
|
||||
case DC_FAMILY_DIVERITE_NITEKQ:
|
||||
rc = diverite_nitekq_parser_create (&parser, context);
|
||||
@ -172,6 +174,12 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa
|
||||
case DC_FAMILY_TECDIVING_DIVECOMPUTEREU:
|
||||
rc = tecdiving_divecomputereu_parser_create (&parser, context);
|
||||
break;
|
||||
case DC_FAMILY_GARMIN:
|
||||
rc = garmin_parser_create (&parser, context);
|
||||
break;
|
||||
case DC_FAMILY_DEEPBLU:
|
||||
rc = deepblu_parser_create (&parser, context);
|
||||
break;
|
||||
default:
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
}
|
||||
@ -188,7 +196,9 @@ dc_parser_new (dc_parser_t **out, dc_device_t *device)
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
|
||||
return dc_parser_new_internal (out, device->context,
|
||||
dc_device_get_type (device), device->devinfo.model,
|
||||
dc_device_get_type (device),
|
||||
device->devinfo.model,
|
||||
device->devinfo.serial,
|
||||
device->clock.devtime, device->clock.systime);
|
||||
}
|
||||
|
||||
@ -196,7 +206,9 @@ dc_status_t
|
||||
dc_parser_new2 (dc_parser_t **out, dc_context_t *context, dc_descriptor_t *descriptor, unsigned int devtime, dc_ticks_t systime)
|
||||
{
|
||||
return dc_parser_new_internal (out, context,
|
||||
dc_descriptor_get_type (descriptor), dc_descriptor_get_model (descriptor),
|
||||
dc_descriptor_get_type (descriptor),
|
||||
dc_descriptor_get_model (descriptor),
|
||||
0,
|
||||
devtime, systime);
|
||||
}
|
||||
|
||||
|
||||
@ -316,6 +316,27 @@ done:
|
||||
return status;
|
||||
}
|
||||
|
||||
dc_status_t
|
||||
shearwater_common_command (shearwater_common_device_t *device, const unsigned char input[], unsigned int isize)
|
||||
{
|
||||
dc_status_t status = DC_STATUS_SUCCESS;
|
||||
dc_device_t *abstract = (dc_device_t *) device;
|
||||
|
||||
if (isize > SZ_PACKET)
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
|
||||
if (device_is_cancelled (abstract))
|
||||
return DC_STATUS_CANCELLED;
|
||||
|
||||
// Send the command packet.
|
||||
status = shearwater_common_slip_write (device, input, isize);
|
||||
if (status != DC_STATUS_SUCCESS)
|
||||
ERROR (abstract->context, "Failed to send the command packet.");
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
|
||||
dc_status_t
|
||||
shearwater_common_transfer (shearwater_common_device_t *device, const unsigned char input[], unsigned int isize, unsigned char output[], unsigned int osize, unsigned int *actual)
|
||||
@ -379,7 +400,6 @@ shearwater_common_transfer (shearwater_common_device_t *device, const unsigned c
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
dc_status_t
|
||||
shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buffer, unsigned int address, unsigned int size, unsigned int compression, dc_event_progress_t *progress)
|
||||
{
|
||||
|
||||
@ -54,6 +54,9 @@ typedef struct shearwater_common_device_t {
|
||||
dc_status_t
|
||||
shearwater_common_setup (shearwater_common_device_t *device, dc_context_t *context, dc_iostream_t *iostream);
|
||||
|
||||
dc_status_t
|
||||
shearwater_common_command (shearwater_common_device_t *device, const unsigned char input[], unsigned int isize);
|
||||
|
||||
dc_status_t
|
||||
shearwater_common_transfer (shearwater_common_device_t *device, const unsigned char input[], unsigned int isize, unsigned char output[], unsigned int osize, unsigned int *actual);
|
||||
|
||||
|
||||
@ -212,28 +212,41 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call
|
||||
unsigned int hardware = array_uint_be (dc_buffer_get_data (buffer), dc_buffer_get_size (buffer));
|
||||
unsigned int model = 0;
|
||||
switch (hardware) {
|
||||
case 0x0808: // Petrel 2
|
||||
case 0x0909: // Petrel 1
|
||||
case 0x0B0B: // Petrel 1 (newer hardware)
|
||||
model = PETREL;
|
||||
case 0x0101:
|
||||
case 0x0202:
|
||||
model = PREDATOR;
|
||||
break;
|
||||
case 0x0606:
|
||||
case 0x0A0A: // Nerd 1
|
||||
model = NERD;
|
||||
break;
|
||||
case 0x0E0D: // Nerd 2
|
||||
model = NERD2;
|
||||
break;
|
||||
case 0x0707:
|
||||
case 0x0404:
|
||||
case 0x0909: // Petrel 1
|
||||
case 0x0B0B: // Petrel 1 (newer hardware)
|
||||
model = PETREL;
|
||||
break;
|
||||
case 0x0505:
|
||||
case 0x0808: // Petrel 2
|
||||
model = PETREL;
|
||||
break;
|
||||
case 0x0707: // documentation list 0C0D for both Perdix and Perdix AI :-(
|
||||
model = PERDIX;
|
||||
break;
|
||||
case 0x0C0C:
|
||||
case 0x0C0D:
|
||||
case 0x0D0D:
|
||||
model = PERDIXAI;
|
||||
break;
|
||||
case 0x0F0F:
|
||||
case 0x1F0A:
|
||||
model = TERIC;
|
||||
break;
|
||||
default:
|
||||
WARNING (abstract->context, "Unknown hardware type %04x.", hardware);
|
||||
// return a model of 0 which is unknown
|
||||
WARNING (abstract->context, "Unknown hardware type %04x. Assuming Petrel.", hardware);
|
||||
}
|
||||
|
||||
// Emit a device info event.
|
||||
|
||||
@ -35,7 +35,7 @@ dc_status_t
|
||||
shearwater_petrel_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream);
|
||||
|
||||
dc_status_t
|
||||
shearwater_petrel_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int model);
|
||||
shearwater_petrel_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int model, unsigned int serial);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ dc_status_t
|
||||
shearwater_predator_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream);
|
||||
|
||||
dc_status_t
|
||||
shearwater_predator_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int model);
|
||||
shearwater_predator_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int model, unsigned int serial);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@ -20,6 +20,13 @@
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
#include <libdivecomputer/units.h>
|
||||
|
||||
@ -51,6 +58,7 @@
|
||||
#define LOG_RECORD_CLOSING_5 0x25
|
||||
#define LOG_RECORD_CLOSING_6 0x26
|
||||
#define LOG_RECORD_CLOSING_7 0x27
|
||||
#define LOG_RECORD_INFO_EVENT 0x30
|
||||
#define LOG_RECORD_FINAL 0xFF
|
||||
|
||||
#define SZ_BLOCK 0x80
|
||||
@ -68,12 +76,15 @@
|
||||
#define IMPERIAL 1
|
||||
|
||||
#define NGASMIXES 10
|
||||
#define MAXSTRINGS 32
|
||||
#define NTANKS 2
|
||||
#define NRECORDS 8
|
||||
|
||||
#define PREDATOR 2
|
||||
#define PETREL 3
|
||||
|
||||
#define INFO_EVENT_TAG_LOG 38
|
||||
|
||||
#define UNDEFINED 0xFFFFFFFF
|
||||
|
||||
typedef struct shearwater_predator_parser_t shearwater_predator_parser_t;
|
||||
@ -87,6 +98,7 @@ typedef struct shearwater_predator_tank_t {
|
||||
unsigned int enabled;
|
||||
unsigned int beginpressure;
|
||||
unsigned int endpressure;
|
||||
unsigned int battery;
|
||||
} shearwater_predator_tank_t;
|
||||
|
||||
struct shearwater_predator_parser_t {
|
||||
@ -110,10 +122,14 @@ struct shearwater_predator_parser_t {
|
||||
unsigned int tankidx[NTANKS];
|
||||
unsigned int calibrated;
|
||||
double calibration[3];
|
||||
unsigned int serial;
|
||||
dc_divemode_t mode;
|
||||
unsigned int units;
|
||||
unsigned int atmospheric;
|
||||
unsigned int density;
|
||||
|
||||
/* String fields */
|
||||
dc_field_string_t strings[MAXSTRINGS];
|
||||
};
|
||||
|
||||
static dc_status_t shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size);
|
||||
@ -159,7 +175,7 @@ shearwater_predator_find_gasmix (shearwater_predator_parser_t *parser, unsigned
|
||||
|
||||
|
||||
static dc_status_t
|
||||
shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model, unsigned int petrel)
|
||||
shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model, unsigned int serial, unsigned int petrel)
|
||||
{
|
||||
shearwater_predator_parser_t *parser = NULL;
|
||||
const dc_parser_vtable_t *vtable = NULL;
|
||||
@ -187,6 +203,9 @@ shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsig
|
||||
parser->model = model;
|
||||
parser->petrel = petrel;
|
||||
parser->samplesize = samplesize;
|
||||
parser->serial = serial;
|
||||
|
||||
// Set the default values.
|
||||
parser->cached = 0;
|
||||
parser->pnf = 0;
|
||||
parser->logversion = 0;
|
||||
@ -207,6 +226,7 @@ shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsig
|
||||
parser->tank[i].enabled = 0;
|
||||
parser->tank[i].beginpressure = 0;
|
||||
parser->tank[i].endpressure = 0;
|
||||
parser->tank[i].battery = 0;
|
||||
parser->tankidx[i] = i;
|
||||
}
|
||||
parser->calibrated = 0;
|
||||
@ -225,16 +245,16 @@ shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsig
|
||||
|
||||
|
||||
dc_status_t
|
||||
shearwater_predator_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model)
|
||||
shearwater_predator_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model, unsigned int serial)
|
||||
{
|
||||
return shearwater_common_parser_create (out, context, model, 0);
|
||||
return shearwater_common_parser_create (out, context, model, serial, 0);
|
||||
}
|
||||
|
||||
|
||||
dc_status_t
|
||||
shearwater_petrel_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model)
|
||||
shearwater_petrel_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model, unsigned int serial)
|
||||
{
|
||||
return shearwater_common_parser_create (out, context, model, 1);
|
||||
return shearwater_common_parser_create (out, context, model, serial, 1);
|
||||
}
|
||||
|
||||
|
||||
@ -300,6 +320,126 @@ shearwater_predator_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *d
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* These string cache interfaces should be some generic
|
||||
* library rather than copied for all the dive computers.
|
||||
*
|
||||
* This is just copied from the EON Steel code.
|
||||
*/
|
||||
static void
|
||||
add_string(shearwater_predator_parser_t *parser, const char *desc, const char *value)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAXSTRINGS; i++) {
|
||||
dc_field_string_t *str = parser->strings+i;
|
||||
if (str->desc)
|
||||
continue;
|
||||
str->desc = desc;
|
||||
str->value = strdup(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
add_string_fmt(shearwater_predator_parser_t *parser, const char *desc, const char *fmt, ...)
|
||||
{
|
||||
char buffer[256];
|
||||
va_list ap;
|
||||
|
||||
/*
|
||||
* We ignore the return value from vsnprintf, and we
|
||||
* always NUL-terminate the destination buffer ourselves.
|
||||
*
|
||||
* That way we don't have to worry about random bad legacy
|
||||
* implementations.
|
||||
*/
|
||||
va_start(ap, fmt);
|
||||
buffer[sizeof(buffer)-1] = 0;
|
||||
(void) vsnprintf(buffer, sizeof(buffer)-1, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
return add_string(parser, desc, buffer);
|
||||
}
|
||||
|
||||
// Show the battery state
|
||||
//
|
||||
// NOTE! Right now it only shows the most serious bit
|
||||
// but the code is set up so that we could perhaps
|
||||
// indicate that the battery is on the edge (ie it
|
||||
// reported both "normal" _and_ "warning" during the
|
||||
// dive - maybe that would be a "starting to warn")
|
||||
//
|
||||
// We could also report unpaired and comm errors.
|
||||
static void
|
||||
add_battery_info(shearwater_predator_parser_t *parser, const char *desc, unsigned int state)
|
||||
{
|
||||
// We don't know what other state bits than 0-2 mean
|
||||
state &= 7;
|
||||
if (state >= 1 && state <= 7) {
|
||||
static const char *states[8] = {
|
||||
"", // 000 - No state bits, not used
|
||||
"normal", // 001 - only normal
|
||||
"critical", // 010 - only critical
|
||||
"critical", // 011 - both normal and critical
|
||||
"warning", // 100 - only warning
|
||||
"warning", // 101 - normal and warning
|
||||
"critical", // 110 - warning and critical
|
||||
"critical", // 111 - normal, warning and critical
|
||||
};
|
||||
add_string(parser, desc, states[state]);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
add_deco_model(shearwater_predator_parser_t *parser, const unsigned char *data)
|
||||
{
|
||||
unsigned int idx_deco_model = parser->pnf ? parser->opening[2] + 18 : 67;
|
||||
unsigned int idx_gfs = parser->pnf ? parser->opening[3] + 5 : 85;
|
||||
|
||||
switch (data[idx_deco_model]) {
|
||||
case 0:
|
||||
add_string_fmt(parser, "Deco model", "GF %u/%u", data[4], data[5]);
|
||||
break;
|
||||
case 1:
|
||||
add_string_fmt(parser, "Deco model", "VPM-B +%u", data[idx_deco_model + 1]);
|
||||
break;
|
||||
case 2:
|
||||
add_string_fmt(parser, "Deco model", "VPM-B/GFS +%u %u%%", data[idx_deco_model + 1], data[idx_gfs]);
|
||||
break;
|
||||
default:
|
||||
add_string_fmt(parser, "Deco model", "Unknown model %d", data[idx_deco_model]);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
add_battery_type(shearwater_predator_parser_t *parser, const unsigned char *data)
|
||||
{
|
||||
if (parser->logversion < 7)
|
||||
return;
|
||||
|
||||
unsigned int idx_battery_type = parser->pnf ? parser->opening[4] + 9 : 120;
|
||||
switch (data[idx_battery_type]) {
|
||||
case 1:
|
||||
add_string(parser, "Battery type", "1.5V Alkaline");
|
||||
break;
|
||||
case 2:
|
||||
add_string(parser, "Battery type", "1.5V Lithium");
|
||||
break;
|
||||
case 3:
|
||||
add_string(parser, "Battery type", "1.2V NiMH");
|
||||
break;
|
||||
case 4:
|
||||
add_string(parser, "Battery type", "3.6V Saft");
|
||||
break;
|
||||
case 5:
|
||||
add_string(parser, "Battery type", "3.7V Li-Ion");
|
||||
break;
|
||||
default:
|
||||
add_string_fmt(parser, "Battery type", "unknown type %d", data[idx_battery_type]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
|
||||
@ -311,6 +451,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
|
||||
if (parser->cached) {
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
memset(parser->strings, 0, sizeof(parser->strings));
|
||||
|
||||
// Log versions before 6 weren't reliably stored in the data, but
|
||||
// 6 is also the oldest version that we assume in our code
|
||||
@ -432,13 +573,16 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
|
||||
// bits the tank pressure in units of 2 psi.
|
||||
unsigned int pressure = array_uint16_be (data + offset + pnf + idx[i]);
|
||||
if (pressure < 0xFFF0) {
|
||||
unsigned int battery = 1u << (pressure >> 12);
|
||||
pressure &= 0x0FFF;
|
||||
if (!tank[i].enabled) {
|
||||
tank[i].enabled = 1;
|
||||
tank[i].beginpressure = pressure;
|
||||
tank[i].endpressure = pressure;
|
||||
tank[i].battery = 0;
|
||||
}
|
||||
tank[i].endpressure = pressure;
|
||||
tank[i].battery |= battery;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -472,6 +616,8 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
|
||||
}
|
||||
}
|
||||
|
||||
add_string_fmt(parser, "Logversion", "%d%s", logversion, pnf ? "(PNF)" : "");
|
||||
|
||||
// Cache sensor calibration for later use
|
||||
unsigned int nsensors = 0, ndefaults = 0;
|
||||
unsigned int base = parser->opening[3] + (pnf ? 6 : 86);
|
||||
@ -500,8 +646,12 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
|
||||
// uncalibrated).
|
||||
WARNING (abstract->context, "Disabled all O2 sensors due to a default calibration value.");
|
||||
parser->calibrated = 0;
|
||||
if (mode != DC_DIVEMODE_OC)
|
||||
add_string(parser, "PPO2 source", "voted/averaged");
|
||||
} else {
|
||||
parser->calibrated = data[base];
|
||||
if (mode != DC_DIVEMODE_OC)
|
||||
add_string(parser, "PPO2 source", "cells");
|
||||
}
|
||||
|
||||
// Cache the data for later use.
|
||||
@ -529,6 +679,15 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
|
||||
parser->density = array_uint16_be (data + parser->opening[3] + (parser->pnf ? 3 : 83));
|
||||
parser->cached = 1;
|
||||
|
||||
add_string_fmt(parser, "Serial", "%08x", parser->serial);
|
||||
// bytes 1-31 are identical in all formats
|
||||
add_string_fmt(parser, "FW Version", "%2x", data[19]);
|
||||
add_deco_model(parser, data);
|
||||
add_battery_type(parser, data);
|
||||
add_string_fmt(parser, "Battery at end", "%.1f V", data[9] / 10.0);
|
||||
add_battery_info(parser, "T1 battery", tank[0].battery);
|
||||
add_battery_info(parser, "T2 battery", tank[1].battery);
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
@ -547,6 +706,7 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ
|
||||
dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
|
||||
dc_tank_t *tank = (dc_tank_t *) value;
|
||||
dc_salinity_t *water = (dc_salinity_t *) value;
|
||||
dc_field_string_t *string = (dc_field_string_t *) value;
|
||||
|
||||
if (value) {
|
||||
switch (type) {
|
||||
@ -596,6 +756,15 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ
|
||||
case DC_FIELD_DIVEMODE:
|
||||
*((dc_divemode_t *) value) = parser->mode;
|
||||
break;
|
||||
case DC_FIELD_STRING:
|
||||
if (flags < MAXSTRINGS) {
|
||||
dc_field_string_t *p = parser->strings + flags;
|
||||
if (p->desc) {
|
||||
*string = *p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
default:
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
}
|
||||
@ -639,6 +808,41 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
|
||||
while (offset + parser->samplesize <= length) {
|
||||
dc_sample_value_t sample = {0};
|
||||
|
||||
// stop parsing if we see the end block
|
||||
if (pnf && data[offset] == LOG_RECORD_FINAL && data[offset + 1] == 0xFD)
|
||||
break;
|
||||
|
||||
if (pnf && data[offset] == LOG_RECORD_INFO_EVENT) {
|
||||
// additional events defined in PNF
|
||||
INFO(abstract->context, "PNF INFO_EVENT ID %u time %u W1 %u W2 %u", data[offset + 1],
|
||||
array_uint32_be (data + offset + 4),
|
||||
array_uint32_be (data + offset + 8),
|
||||
array_uint32_be (data + offset + 12));
|
||||
if (data[offset + 1] == INFO_EVENT_TAG_LOG) {
|
||||
// this is a TAG
|
||||
// its time is a unix timestamp, so we need to subtract the dive start time
|
||||
unsigned int tag_time = array_uint32_be (data + offset + 4) - array_uint32_be (data + parser->opening[0] + 12);
|
||||
unsigned int tag_heading = array_uint32_be (data + offset + 8);
|
||||
unsigned int tag_type = array_uint32_be (data + offset + 12);
|
||||
// heading is only valid if 0..360
|
||||
// type is only valid if 0..5
|
||||
if (tag_heading <= 360 && tag_type <= 5) {
|
||||
// encode this as a bookmark event, using the flags to capture the type and value for heading
|
||||
sample.event.type = SAMPLE_EVENT_BOOKMARK;
|
||||
sample.event.time = tag_time;
|
||||
sample.event.flags = (tag_type + 1) << SAMPLE_FLAGS_TYPE_SHIFT; // 0 means it isn't a tag
|
||||
sample.event.value = tag_heading;
|
||||
if (callback) callback (DC_SAMPLE_EVENT, sample, userdata);
|
||||
}
|
||||
}
|
||||
offset += parser->samplesize;
|
||||
continue;
|
||||
}
|
||||
// Ignore blocks that aren't dive samples
|
||||
if (pnf && data[offset] != LOG_RECORD_DIVE_SAMPLE) {
|
||||
offset += parser->samplesize;
|
||||
continue;
|
||||
}
|
||||
// Ignore empty samples.
|
||||
if (array_isequal (data + offset, parser->samplesize, 0x00)) {
|
||||
offset += parser->samplesize;
|
||||
@ -683,10 +887,10 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
|
||||
if ((status & OC) == 0) {
|
||||
// PPO2
|
||||
if ((status & PPO2_EXTERNAL) == 0) {
|
||||
#ifdef SENSOR_AVERAGE
|
||||
if (!parser->calibrated) {
|
||||
sample.ppo2 = data[offset + pnf + 6] / 100.0;
|
||||
if (callback) callback (DC_SAMPLE_PPO2, sample, userdata);
|
||||
#else
|
||||
} else {
|
||||
sample.ppo2 = data[offset + pnf + 12] * parser->calibration[0];
|
||||
if (callback && (parser->calibrated & 0x01)) callback (DC_SAMPLE_PPO2, sample, userdata);
|
||||
|
||||
@ -695,7 +899,7 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
|
||||
|
||||
sample.ppo2 = data[offset + pnf + 15] * parser->calibration[2];
|
||||
if (callback && (parser->calibrated & 0x04)) callback (DC_SAMPLE_PPO2, sample, userdata);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Setpoint
|
||||
@ -815,6 +1019,5 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
|
||||
|
||||
offset += parser->samplesize;
|
||||
}
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ dc_status_t
|
||||
suunto_d9_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream, unsigned int model);
|
||||
|
||||
dc_status_t
|
||||
suunto_d9_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int model);
|
||||
suunto_d9_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int model, unsigned int serial);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@ -20,7 +20,8 @@
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h> // memcmp
|
||||
#include <string.h> // memcmp, strdup
|
||||
#include <stdio.h> // snprintf
|
||||
|
||||
#include "suunto_d9.h"
|
||||
#include "context-private.h"
|
||||
@ -72,6 +73,7 @@ typedef struct suunto_d9_parser_t suunto_d9_parser_t;
|
||||
struct suunto_d9_parser_t {
|
||||
dc_parser_t base;
|
||||
unsigned int model;
|
||||
unsigned int serial;
|
||||
// Cached fields.
|
||||
unsigned int cached;
|
||||
unsigned int id;
|
||||
@ -244,7 +246,7 @@ suunto_d9_parser_cache (suunto_d9_parser_t *parser)
|
||||
}
|
||||
|
||||
dc_status_t
|
||||
suunto_d9_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model)
|
||||
suunto_d9_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model, unsigned int serial)
|
||||
{
|
||||
suunto_d9_parser_t *parser = NULL;
|
||||
|
||||
@ -260,6 +262,7 @@ suunto_d9_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int
|
||||
|
||||
// Set the default values.
|
||||
parser->model = model;
|
||||
parser->serial = serial;
|
||||
parser->cached = 0;
|
||||
parser->id = 0;
|
||||
parser->mode = AIR;
|
||||
@ -343,6 +346,7 @@ suunto_d9_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime)
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
#define BUFLEN 16
|
||||
|
||||
static dc_status_t
|
||||
suunto_d9_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value)
|
||||
@ -358,6 +362,9 @@ suunto_d9_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigne
|
||||
return rc;
|
||||
|
||||
dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
|
||||
dc_field_string_t *string = (dc_field_string_t *) value;
|
||||
|
||||
char buf[BUFLEN];
|
||||
|
||||
if (value) {
|
||||
switch (type) {
|
||||
@ -405,6 +412,17 @@ suunto_d9_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigne
|
||||
return DC_STATUS_DATAFORMAT;
|
||||
}
|
||||
break;
|
||||
case DC_FIELD_STRING:
|
||||
switch (flags) {
|
||||
case 0: /* serial */
|
||||
string->desc = "Serial";
|
||||
snprintf(buf, BUFLEN, "%08u", parser->serial);
|
||||
break;
|
||||
default:
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
}
|
||||
string->value = strdup(buf);
|
||||
break;
|
||||
default:
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
}
|
||||
|
||||
@ -641,12 +641,35 @@ read_file(suunto_eonsteel_device_t *eon, const char *filename, dc_buffer_t *buf)
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert a directory entry in the sorted list, most recent entry
|
||||
* first.
|
||||
*
|
||||
* The directory entry names are the timestamps as hex, so ordering
|
||||
* in alphabetical order ends up also ordering in date order!
|
||||
*/
|
||||
static struct directory_entry *insert_dirent(struct directory_entry *entry, struct directory_entry *list)
|
||||
{
|
||||
struct directory_entry **pos = &list, *next;
|
||||
|
||||
while ((next = *pos) != NULL) {
|
||||
/* Is this bigger (more recent) than the next entry? We're good! */
|
||||
if (strcmp(entry->name, next->name) > 0)
|
||||
break;
|
||||
pos = &next->next;
|
||||
}
|
||||
entry->next = next;
|
||||
*pos = entry;
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE! This will create the list of dirent's in reverse order,
|
||||
* with the last dirent first. That's intentional: for dives,
|
||||
* we will want to look up the last dive first.
|
||||
*/
|
||||
static struct directory_entry *parse_dirent(suunto_eonsteel_device_t *eon, int nr, const unsigned char *p, unsigned int len, struct directory_entry *old)
|
||||
static struct directory_entry *parse_dirent(suunto_eonsteel_device_t *eon, int nr, const unsigned char *p, unsigned int len, struct directory_entry *list)
|
||||
{
|
||||
while (len > 8) {
|
||||
unsigned int type = array_uint32_le(p);
|
||||
@ -667,10 +690,9 @@ static struct directory_entry *parse_dirent(suunto_eonsteel_device_t *eon, int n
|
||||
ERROR(eon->base.context, "out of memory");
|
||||
break;
|
||||
}
|
||||
entry->next = old;
|
||||
old = entry;
|
||||
list = insert_dirent(entry, list);
|
||||
}
|
||||
return old;
|
||||
return list;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
@ -731,6 +753,19 @@ get_file_list(suunto_eonsteel_device_t *eon, struct directory_entry **res)
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static int
|
||||
count_file_list(struct directory_entry *list)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
while (list) {
|
||||
count++;
|
||||
list = list->next;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
dc_status_t
|
||||
suunto_eonsteel_device_open(dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream, unsigned int model)
|
||||
{
|
||||
@ -802,7 +837,6 @@ suunto_eonsteel_device_foreach(dc_device_t *abstract, dc_dive_callback_t callbac
|
||||
dc_buffer_t *file;
|
||||
char pathname[64];
|
||||
unsigned int time;
|
||||
unsigned int count = 0;
|
||||
dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
|
||||
|
||||
// Emit a device info event.
|
||||
@ -820,46 +854,17 @@ suunto_eonsteel_device_foreach(dc_device_t *abstract, dc_dive_callback_t callbac
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// Locate the most recent dive.
|
||||
// The filename represent the time of the dive, encoded as a hexadecimal
|
||||
// number. Thus the most recent dive can be found by simply sorting the
|
||||
// filenames alphabetically.
|
||||
struct directory_entry *head = de, *tail = de, *latest = de;
|
||||
while (de) {
|
||||
if (strcmp (de->name, latest->name) > 0) {
|
||||
latest = de;
|
||||
}
|
||||
tail = de;
|
||||
count++;
|
||||
de = de->next;
|
||||
}
|
||||
|
||||
// Make the most recent dive the head of the list.
|
||||
// The linked list is made circular, by attaching the head to the tail and
|
||||
// then cut open again just before the most recent dive.
|
||||
de = head;
|
||||
while (de) {
|
||||
if (de->next == latest) {
|
||||
de->next = NULL;
|
||||
tail->next = head;
|
||||
break;
|
||||
}
|
||||
|
||||
de = de->next;
|
||||
}
|
||||
|
||||
file = dc_buffer_new (16384);
|
||||
if (file == NULL) {
|
||||
ERROR (abstract->context, "Insufficient buffer space available.");
|
||||
file_list_free (latest);
|
||||
file_list_free(de);
|
||||
return DC_STATUS_NOMEMORY;
|
||||
}
|
||||
|
||||
progress.maximum = count;
|
||||
progress.maximum = count_file_list(de);
|
||||
progress.current = 0;
|
||||
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
|
||||
|
||||
de = latest;
|
||||
while (de) {
|
||||
int len;
|
||||
struct directory_entry *next = de->next;
|
||||
|
||||
@ -21,8 +21,16 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
/* Wow. MSC is truly crap */
|
||||
#ifdef _MSC_VER
|
||||
#define snprintf _snprintf
|
||||
#define vsnprintf _vsnprintf
|
||||
#endif
|
||||
|
||||
#include "suunto_eonsteel.h"
|
||||
#include "context-private.h"
|
||||
@ -71,6 +79,7 @@ struct type_desc {
|
||||
|
||||
#define MAXTYPE 512
|
||||
#define MAXGASES 16
|
||||
#define MAXSTRINGS 32
|
||||
|
||||
typedef struct suunto_eonsteel_parser_t {
|
||||
dc_parser_t base;
|
||||
@ -89,7 +98,8 @@ typedef struct suunto_eonsteel_parser_t {
|
||||
double lowsetpoint;
|
||||
double highsetpoint;
|
||||
double customsetpoint;
|
||||
dc_tankvolume_t tankinfo[MAXGASES];
|
||||
dc_field_string_t strings[MAXSTRINGS];
|
||||
dc_tankinfo_t tankinfo[MAXGASES];
|
||||
double tanksize[MAXGASES];
|
||||
double tankworkingpressure[MAXGASES];
|
||||
} cache;
|
||||
@ -97,11 +107,6 @@ typedef struct suunto_eonsteel_parser_t {
|
||||
|
||||
typedef int (*eon_data_cb_t)(unsigned short type, const struct type_desc *desc, const unsigned char *data, int len, void *user);
|
||||
|
||||
typedef struct eon_event_t {
|
||||
const char *name;
|
||||
parser_sample_event_t type;
|
||||
} eon_event_t;
|
||||
|
||||
static const struct {
|
||||
const char *name;
|
||||
enum eon_sample type;
|
||||
@ -167,16 +172,6 @@ static enum eon_sample lookup_descriptor_type(suunto_eonsteel_parser_t *eon, str
|
||||
return ES_none;
|
||||
}
|
||||
|
||||
static parser_sample_event_t lookup_event(const char *name, const eon_event_t events[], size_t n)
|
||||
{
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
if (!strcasecmp(name, events[i].name))
|
||||
return events[i].type;
|
||||
}
|
||||
|
||||
return SAMPLE_EVENT_NONE;
|
||||
}
|
||||
|
||||
static const char *desc_type_name(enum eon_sample type)
|
||||
{
|
||||
int i;
|
||||
@ -468,8 +463,6 @@ struct sample_data {
|
||||
|
||||
/* We gather up deco and cylinder pressure information */
|
||||
int gasnr;
|
||||
int tts, ndl;
|
||||
double ceiling;
|
||||
};
|
||||
|
||||
static void sample_time(struct sample_data *info, unsigned short time_delta)
|
||||
@ -507,7 +500,6 @@ static void sample_ndl(struct sample_data *info, short ndl)
|
||||
{
|
||||
dc_sample_value_t sample = {0};
|
||||
|
||||
info->ndl = ndl;
|
||||
if (ndl < 0)
|
||||
return;
|
||||
|
||||
@ -518,14 +510,27 @@ static void sample_ndl(struct sample_data *info, short ndl)
|
||||
|
||||
static void sample_tts(struct sample_data *info, unsigned short tts)
|
||||
{
|
||||
if (tts != 0xffff)
|
||||
info->tts = tts;
|
||||
if (tts != 0xffff) {
|
||||
dc_sample_value_t sample = {0};
|
||||
sample.time = tts;
|
||||
if (info->callback) info->callback(DC_SAMPLE_TTS, sample, info->userdata);
|
||||
}
|
||||
}
|
||||
|
||||
static void sample_ceiling(struct sample_data *info, unsigned short ceiling)
|
||||
{
|
||||
if (ceiling != 0xffff)
|
||||
info->ceiling = ceiling / 100.0;
|
||||
if (ceiling != 0xffff) {
|
||||
dc_sample_value_t sample = {0};
|
||||
|
||||
// We don't actually have a time for the
|
||||
// deco stop, we just have a ceiling.
|
||||
//
|
||||
// We'll just say it's one minute.
|
||||
sample.deco.type = DC_DECO_DECOSTOP;
|
||||
sample.deco.time = ceiling ? 60 : 0;
|
||||
sample.deco.depth = ceiling / 100.0;
|
||||
if (info->callback) info->callback(DC_SAMPLE_DECO, sample, info->userdata);
|
||||
}
|
||||
}
|
||||
|
||||
static void sample_heading(struct sample_data *info, unsigned short heading)
|
||||
@ -681,26 +686,17 @@ static void sample_event_state_type(const struct type_desc *desc, struct sample_
|
||||
static void sample_event_state_value(const struct type_desc *desc, struct sample_data *info, unsigned char value)
|
||||
{
|
||||
dc_sample_value_t sample = {0};
|
||||
static const eon_event_t states[] = {
|
||||
{"Wet Outside", SAMPLE_EVENT_NONE},
|
||||
{"Below Wet Activation Depth", SAMPLE_EVENT_NONE},
|
||||
{"Below Surface", SAMPLE_EVENT_NONE},
|
||||
{"Dive Active", SAMPLE_EVENT_NONE},
|
||||
{"Surface Calculation", SAMPLE_EVENT_NONE},
|
||||
{"Tank pressure available", SAMPLE_EVENT_NONE},
|
||||
{"Closed Circuit Mode", SAMPLE_EVENT_NONE},
|
||||
};
|
||||
const char *name;
|
||||
|
||||
name = info->state_type;
|
||||
if (!name)
|
||||
return;
|
||||
|
||||
sample.event.type = lookup_event(name, states, C_ARRAY_SIZE(states));
|
||||
if (sample.event.type == SAMPLE_EVENT_NONE)
|
||||
return;
|
||||
|
||||
sample.event.type = SAMPLE_EVENT_STRING;
|
||||
sample.event.name = name;
|
||||
sample.event.flags = value ? SAMPLE_FLAGS_BEGIN : SAMPLE_FLAGS_END;
|
||||
sample.event.flags |= 1 << SAMPLE_FLAGS_SEVERITY_SHIFT;
|
||||
|
||||
if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata);
|
||||
}
|
||||
|
||||
@ -712,25 +708,6 @@ static void sample_event_notify_type(const struct type_desc *desc, struct sample
|
||||
|
||||
static void sample_event_notify_value(const struct type_desc *desc, struct sample_data *info, unsigned char value)
|
||||
{
|
||||
static const eon_event_t notifications[] = {
|
||||
{"NoFly Time", SAMPLE_EVENT_NONE},
|
||||
{"Depth", SAMPLE_EVENT_NONE},
|
||||
{"Surface Time", SAMPLE_EVENT_NONE},
|
||||
{"Tissue Level", SAMPLE_EVENT_TISSUELEVEL},
|
||||
{"Deco", SAMPLE_EVENT_NONE},
|
||||
{"Deco Window", SAMPLE_EVENT_NONE},
|
||||
{"Safety Stop Ahead", SAMPLE_EVENT_NONE},
|
||||
{"Safety Stop", SAMPLE_EVENT_SAFETYSTOP},
|
||||
{"Safety Stop Broken", SAMPLE_EVENT_CEILING_SAFETYSTOP},
|
||||
{"Deep Stop Ahead", SAMPLE_EVENT_NONE},
|
||||
{"Deep Stop", SAMPLE_EVENT_DEEPSTOP},
|
||||
{"Dive Time", SAMPLE_EVENT_DIVETIME},
|
||||
{"Gas Available", SAMPLE_EVENT_NONE},
|
||||
{"SetPoint Switch", SAMPLE_EVENT_NONE},
|
||||
{"Diluent Hypoxia", SAMPLE_EVENT_NONE},
|
||||
{"Air Time", SAMPLE_EVENT_NONE},
|
||||
{"Tank Pressure", SAMPLE_EVENT_NONE},
|
||||
};
|
||||
dc_sample_value_t sample = {0};
|
||||
const char *name;
|
||||
|
||||
@ -738,11 +715,11 @@ static void sample_event_notify_value(const struct type_desc *desc, struct sampl
|
||||
if (!name)
|
||||
return;
|
||||
|
||||
sample.event.type = lookup_event(name, notifications, C_ARRAY_SIZE(notifications));
|
||||
if (sample.event.type == SAMPLE_EVENT_NONE)
|
||||
return;
|
||||
|
||||
sample.event.type = SAMPLE_EVENT_STRING;
|
||||
sample.event.name = name;
|
||||
sample.event.flags = value ? SAMPLE_FLAGS_BEGIN : SAMPLE_FLAGS_END;
|
||||
sample.event.flags |= 2 << SAMPLE_FLAGS_SEVERITY_SHIFT;
|
||||
|
||||
if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata);
|
||||
}
|
||||
|
||||
@ -755,22 +732,6 @@ static void sample_event_warning_type(const struct type_desc *desc, struct sampl
|
||||
|
||||
static void sample_event_warning_value(const struct type_desc *desc, struct sample_data *info, unsigned char value)
|
||||
{
|
||||
static const eon_event_t warnings[] = {
|
||||
{"ICD Penalty", SAMPLE_EVENT_NONE},
|
||||
{"Deep Stop Penalty", SAMPLE_EVENT_VIOLATION},
|
||||
{"Mandatory Safety Stop", SAMPLE_EVENT_SAFETYSTOP_MANDATORY},
|
||||
{"OTU250", SAMPLE_EVENT_NONE},
|
||||
{"OTU300", SAMPLE_EVENT_NONE},
|
||||
{"CNS80%", SAMPLE_EVENT_NONE},
|
||||
{"CNS100%", SAMPLE_EVENT_NONE},
|
||||
{"Max.Depth", SAMPLE_EVENT_MAXDEPTH},
|
||||
{"Air Time", SAMPLE_EVENT_AIRTIME},
|
||||
{"Tank Pressure", SAMPLE_EVENT_NONE},
|
||||
{"Safety Stop Broken", SAMPLE_EVENT_CEILING_SAFETYSTOP},
|
||||
{"Deep Stop Broken", SAMPLE_EVENT_CEILING_SAFETYSTOP},
|
||||
{"Ceiling Broken", SAMPLE_EVENT_CEILING},
|
||||
{"PO2 High", SAMPLE_EVENT_PO2},
|
||||
};
|
||||
dc_sample_value_t sample = {0};
|
||||
const char *name;
|
||||
|
||||
@ -778,11 +739,11 @@ static void sample_event_warning_value(const struct type_desc *desc, struct samp
|
||||
if (!name)
|
||||
return;
|
||||
|
||||
sample.event.type = lookup_event(name, warnings, C_ARRAY_SIZE(warnings));
|
||||
if (sample.event.type == SAMPLE_EVENT_NONE)
|
||||
return;
|
||||
|
||||
sample.event.type = SAMPLE_EVENT_STRING;
|
||||
sample.event.name = name;
|
||||
sample.event.flags = value ? SAMPLE_FLAGS_BEGIN : SAMPLE_FLAGS_END;
|
||||
sample.event.flags |= 3 << SAMPLE_FLAGS_SEVERITY_SHIFT;
|
||||
|
||||
if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata);
|
||||
}
|
||||
|
||||
@ -795,27 +756,18 @@ static void sample_event_alarm_type(const struct type_desc *desc, struct sample_
|
||||
|
||||
static void sample_event_alarm_value(const struct type_desc *desc, struct sample_data *info, unsigned char value)
|
||||
{
|
||||
static const eon_event_t alarms[] = {
|
||||
{"Mandatory Safety Stop Broken", SAMPLE_EVENT_CEILING_SAFETYSTOP},
|
||||
{"Ascent Speed", SAMPLE_EVENT_ASCENT},
|
||||
{"Diluent Hyperoxia", SAMPLE_EVENT_NONE},
|
||||
{"Violated Deep Stop", SAMPLE_EVENT_VIOLATION},
|
||||
{"Ceiling Broken", SAMPLE_EVENT_CEILING},
|
||||
{"PO2 High", SAMPLE_EVENT_PO2},
|
||||
{"PO2 Low", SAMPLE_EVENT_PO2},
|
||||
};
|
||||
dc_sample_value_t sample = {0};
|
||||
const char *name;
|
||||
dc_sample_value_t sample = {0};
|
||||
|
||||
name = info->alarm_type;
|
||||
if (!name)
|
||||
return;
|
||||
|
||||
sample.event.type = lookup_event(name, alarms, C_ARRAY_SIZE(alarms));
|
||||
if (sample.event.type == SAMPLE_EVENT_NONE)
|
||||
return;
|
||||
|
||||
sample.event.type = SAMPLE_EVENT_STRING;
|
||||
sample.event.name = name;
|
||||
sample.event.flags = value ? SAMPLE_FLAGS_BEGIN : SAMPLE_FLAGS_END;
|
||||
sample.event.flags |= 4 << SAMPLE_FLAGS_SEVERITY_SHIFT;
|
||||
|
||||
if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata);
|
||||
}
|
||||
|
||||
@ -976,10 +928,6 @@ static int traverse_samples(unsigned short type, const struct type_desc *desc, c
|
||||
if (desc->size > len)
|
||||
ERROR(eon->base.context, "Got %d bytes of data for '%s' that wants %d bytes", len, desc->desc, desc->size);
|
||||
|
||||
info->ndl = -1;
|
||||
info->tts = 0;
|
||||
info->ceiling = 0.0;
|
||||
|
||||
for (i = 0; i < EON_MAX_GROUP; i++) {
|
||||
enum eon_sample type = desc->type[i];
|
||||
int bytes = handle_sample_type(desc, info, type, data);
|
||||
@ -995,15 +943,6 @@ static int traverse_samples(unsigned short type, const struct type_desc *desc, c
|
||||
used += bytes;
|
||||
}
|
||||
|
||||
if (info->ndl < 0 && (info->tts || info->ceiling)) {
|
||||
dc_sample_value_t sample = {0};
|
||||
|
||||
sample.deco.type = DC_DECO_DECOSTOP;
|
||||
sample.deco.time = info->tts;
|
||||
sample.deco.depth = info->ceiling;
|
||||
if (info->callback) info->callback(DC_SAMPLE_DECO, sample, info->userdata);
|
||||
}
|
||||
|
||||
// Warn if there are left-over bytes for something we did use part of
|
||||
if (used && len)
|
||||
ERROR(eon->base.context, "Entry for '%s' had %d bytes, only used %d", desc->desc, len+used, used);
|
||||
@ -1026,6 +965,19 @@ suunto_eonsteel_parser_samples_foreach(dc_parser_t *abstract, dc_sample_callback
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static dc_status_t get_string_field(suunto_eonsteel_parser_t *eon, unsigned idx, dc_field_string_t *value)
|
||||
{
|
||||
if (idx < MAXSTRINGS) {
|
||||
dc_field_string_t *res = eon->cache.strings+idx;
|
||||
if (res->desc && res->value) {
|
||||
*value = *res;
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
}
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
}
|
||||
|
||||
// Ugly define thing makes the code much easier to read
|
||||
// I'd love to use __typeof__, but that's a gcc'ism
|
||||
#define field_value(p, set) \
|
||||
@ -1094,11 +1046,13 @@ suunto_eonsteel_parser_get_field(dc_parser_t *parser, dc_field_type_t type, unsi
|
||||
* We need to have workpressure and a valid tank. In that case,
|
||||
* a fractional tank size implies imperial.
|
||||
*/
|
||||
if (tank->workpressure && (tank->type == DC_TANKVOLUME_METRIC)) {
|
||||
if (tank->workpressure && (tank->type & DC_TANKINFO_METRIC)) {
|
||||
if (fabs(tank->volume - rint(tank->volume)) > 0.001)
|
||||
tank->type = DC_TANKVOLUME_IMPERIAL;
|
||||
tank->type += DC_TANKINFO_IMPERIAL - DC_TANKINFO_METRIC;
|
||||
}
|
||||
break;
|
||||
case DC_FIELD_STRING:
|
||||
return get_string_field(eon, flags, (dc_field_string_t *)value);
|
||||
default:
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
}
|
||||
@ -1149,7 +1103,7 @@ static void set_depth_field(suunto_eonsteel_parser_t *eon, unsigned short d)
|
||||
// "enum:0=Off,1=Primary,2=?,3=Diluent"
|
||||
// "enum:0=Off,1=Primary,3=Diluent,4=Oxygen"
|
||||
//
|
||||
// We turn that into the DC_TANKVOLUME data here, but
|
||||
// We turn that into the DC_TANKINFO data here, but
|
||||
// initially consider all non-off tanks to me METRIC.
|
||||
//
|
||||
// We may later turn the METRIC tank size into IMPERIAL if we
|
||||
@ -1157,7 +1111,7 @@ static void set_depth_field(suunto_eonsteel_parser_t *eon, unsigned short d)
|
||||
static int add_gas_type(suunto_eonsteel_parser_t *eon, const struct type_desc *desc, unsigned char type)
|
||||
{
|
||||
int idx = eon->cache.ngases;
|
||||
dc_tankvolume_t tankinfo = DC_TANKVOLUME_METRIC;
|
||||
dc_tankinfo_t tankinfo = DC_TANKINFO_METRIC;
|
||||
char *name;
|
||||
|
||||
if (idx >= MAXGASES)
|
||||
@ -1168,9 +1122,9 @@ static int add_gas_type(suunto_eonsteel_parser_t *eon, const struct type_desc *d
|
||||
if (!name)
|
||||
DEBUG(eon->base.context, "Unable to look up gas type %u in %s", type, desc->format);
|
||||
else if (!strcasecmp(name, "Diluent"))
|
||||
;
|
||||
tankinfo |= DC_TANKINFO_CC_DILUENT;
|
||||
else if (!strcasecmp(name, "Oxygen"))
|
||||
;
|
||||
tankinfo |= DC_TANKINFO_CC_O2;
|
||||
else if (!strcasecmp(name, "None"))
|
||||
tankinfo = DC_TANKVOLUME_NONE;
|
||||
else if (strcasecmp(name, "Primary"))
|
||||
@ -1223,6 +1177,42 @@ static int add_gas_workpressure(suunto_eonsteel_parser_t *eon, float wp)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_string(suunto_eonsteel_parser_t *eon, const char *desc, const char *value)
|
||||
{
|
||||
int i;
|
||||
|
||||
eon->cache.initialized |= 1 << DC_FIELD_STRING;
|
||||
for (i = 0; i < MAXSTRINGS; i++) {
|
||||
dc_field_string_t *str = eon->cache.strings+i;
|
||||
if (str->desc)
|
||||
continue;
|
||||
str->desc = desc;
|
||||
str->value = strdup(value);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_string_fmt(suunto_eonsteel_parser_t *eon, const char *desc, const char *fmt, ...)
|
||||
{
|
||||
char buffer[256];
|
||||
va_list ap;
|
||||
|
||||
/*
|
||||
* We ignore the return value from vsnprintf, and we
|
||||
* always NUL-terminate the destination buffer ourselves.
|
||||
*
|
||||
* That way we don't have to worry about random bad legacy
|
||||
* implementations.
|
||||
*/
|
||||
va_start(ap, fmt);
|
||||
buffer[sizeof(buffer)-1] = 0;
|
||||
(void) vsnprintf(buffer, sizeof(buffer)-1, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
return add_string(eon, desc, buffer);
|
||||
}
|
||||
|
||||
static float get_le32_float(const unsigned char *src)
|
||||
{
|
||||
union {
|
||||
@ -1246,7 +1236,16 @@ static int traverse_device_fields(suunto_eonsteel_parser_t *eon, const struct ty
|
||||
const unsigned char *data, int len)
|
||||
{
|
||||
const char *name = desc->desc + strlen("sml.DeviceLog.Device.");
|
||||
|
||||
if (!strcmp(name, "SerialNumber"))
|
||||
return add_string(eon, "Serial", data);
|
||||
if (!strcmp(name, "Info.HW"))
|
||||
return add_string(eon, "HW Version", data);
|
||||
if (!strcmp(name, "Info.SW"))
|
||||
return add_string(eon, "FW Version", data);
|
||||
if (!strcmp(name, "Info.BatteryAtStart"))
|
||||
return add_string(eon, "Battery at start", data);
|
||||
if (!strcmp(name, "Info.BatteryAtEnd"))
|
||||
return add_string(eon, "Battery at end", data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1277,12 +1276,30 @@ static int traverse_gas_fields(suunto_eonsteel_parser_t *eon, const struct type_
|
||||
if (!strcmp(name, ".Gas.Helium"))
|
||||
return add_gas_he(eon, data[0]);
|
||||
|
||||
if (!strcmp(name, ".Gas.TransmitterID"))
|
||||
return add_string(eon, "Transmitter ID", data);
|
||||
|
||||
if (!strcmp(name, ".Gas.TankSize"))
|
||||
return add_gas_size(eon, get_le32_float(data));
|
||||
|
||||
if (!strcmp(name, ".Gas.TankFillPressure"))
|
||||
return add_gas_workpressure(eon, get_le32_float(data));
|
||||
|
||||
// There is a bug with older transmitters, where the transmitter
|
||||
// battery charge returns zero. Rather than returning that bogus
|
||||
// data, just don't return any battery charge information at all.
|
||||
//
|
||||
// Make sure to add all non-battery-charge field checks above this
|
||||
// test, so that it doesn't trigger for anything else.
|
||||
if (!data[0])
|
||||
return 0;
|
||||
|
||||
if (!strcmp(name, ".Gas.TransmitterStartBatteryCharge"))
|
||||
return add_string_fmt(eon, "Transmitter Battery at start", "%d %%", data[0]);
|
||||
|
||||
if (!strcmp(name, ".Gas.TransmitterEndBatteryCharge"))
|
||||
return add_string_fmt(eon, "Transmitter Battery at end", "%d %%", data[0]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1339,12 +1356,22 @@ static int traverse_diving_fields(suunto_eonsteel_parser_t *eon, const struct ty
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp(name, "Algorithm"))
|
||||
return add_string(eon, "Deco algorithm", data);
|
||||
|
||||
if (!strcmp(name, "DiveMode")) {
|
||||
if (!strncmp((const char *)data, "CCR", 3)) {
|
||||
eon->cache.divemode = DC_DIVEMODE_CCR;
|
||||
eon->cache.initialized |= 1 << DC_FIELD_DIVEMODE;
|
||||
}
|
||||
return 0;
|
||||
return add_string(eon, "Dive Mode", data);
|
||||
}
|
||||
|
||||
/* Signed byte of conservatism (-2 .. +2) */
|
||||
if (!strcmp(name, "Conservatism")) {
|
||||
int val = *(signed char *)data;
|
||||
|
||||
return add_string_fmt(eon, "Personal Adjustment", "P%d", val);
|
||||
}
|
||||
|
||||
if (!strcmp(name, "LowSetPoint")) {
|
||||
@ -1359,6 +1386,18 @@ static int traverse_diving_fields(suunto_eonsteel_parser_t *eon, const struct ty
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Time recoded in seconds.
|
||||
// Let's just agree to ignore seconds
|
||||
if (!strcmp(name, "DesaturationTime")) {
|
||||
unsigned int time = array_uint32_le(data) / 60;
|
||||
return add_string_fmt(eon, "Desaturation Time", "%d:%02d", time / 60, time % 60);
|
||||
}
|
||||
|
||||
if (!strcmp(name, "SurfaceTime")) {
|
||||
unsigned int time = array_uint32_le(data) / 60;
|
||||
return add_string_fmt(eon, "Surface Time", "%d:%02d", time / 60, time % 60);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1385,6 +1424,8 @@ static int traverse_header_fields(suunto_eonsteel_parser_t *eon, const struct ty
|
||||
eon->cache.maxdepth = d;
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(name, "DateTime"))
|
||||
return add_string(eon, "Dive ID", data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1428,6 +1469,8 @@ static int traverse_sample_fields(suunto_eonsteel_parser_t *eon, const struct ty
|
||||
set_depth_field(eon, array_uint16_le(data));
|
||||
data += 2;
|
||||
continue;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
109
src/usb_storage.c
Normal file
109
src/usb_storage.c
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Dummy "stream" operations for USB storage
|
||||
*
|
||||
* Copyright (C) 2018 Linus Torvalds
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <limits.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "common-private.h"
|
||||
#include "context-private.h"
|
||||
#include "iostream-private.h"
|
||||
#include "iterator-private.h"
|
||||
#include "descriptor-private.h"
|
||||
#include "timer.h"
|
||||
|
||||
// Fake "device" that just contains the directory name that
|
||||
// you can read out of the iostream. All the actual IO is
|
||||
// up to you.
|
||||
typedef struct dc_usbstorage_t {
|
||||
dc_iostream_t base;
|
||||
char pathname[PATH_MAX];
|
||||
} dc_usbstorage_t;
|
||||
|
||||
static dc_status_t
|
||||
dc_usb_storage_read (dc_iostream_t *iostream, void *data, size_t size, size_t *actual);
|
||||
|
||||
static const dc_iostream_vtable_t dc_usbstorage_vtable = {
|
||||
sizeof(dc_usbstorage_t),
|
||||
NULL, /* set_timeout */
|
||||
NULL, /* set_latency */
|
||||
NULL, /* set_break */
|
||||
NULL, /* set_dtr */
|
||||
NULL, /* set_rts */
|
||||
NULL, /* get_lines */
|
||||
NULL, /* get_available */
|
||||
NULL, /* configure */
|
||||
dc_usb_storage_read, /* read */
|
||||
NULL, /* write */
|
||||
NULL, /* flush */
|
||||
NULL, /* purge */
|
||||
NULL, /* sleep */
|
||||
NULL, /* close */
|
||||
};
|
||||
|
||||
dc_status_t
|
||||
dc_usb_storage_open (dc_iostream_t **out, dc_context_t *context, const char *name)
|
||||
{
|
||||
dc_usbstorage_t *device = NULL;
|
||||
struct stat st;
|
||||
|
||||
if (out == NULL || name == NULL)
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
|
||||
INFO (context, "Open: name=%s", name);
|
||||
if (stat(name, &st) < 0 || !S_ISDIR(st.st_mode))
|
||||
return DC_STATUS_NODEVICE;
|
||||
|
||||
// Allocate memory.
|
||||
device = (dc_usbstorage_t *) dc_iostream_allocate (context, &dc_usbstorage_vtable, DC_TRANSPORT_USBSTORAGE);
|
||||
if (device == NULL) {
|
||||
SYSERROR (context, ENOMEM);
|
||||
return DC_STATUS_NOMEMORY;
|
||||
}
|
||||
|
||||
strncpy(device->pathname, name, PATH_MAX);
|
||||
device->pathname[PATH_MAX-1] = 0;
|
||||
|
||||
*out = (dc_iostream_t *) device;
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
dc_usb_storage_read (dc_iostream_t *iostream, void *data, size_t size, size_t *actual)
|
||||
{
|
||||
dc_usbstorage_t *device = (dc_usbstorage_t *) iostream;
|
||||
size_t len = strlen(device->pathname);
|
||||
|
||||
if (size <= len)
|
||||
return DC_STATUS_IO;
|
||||
memcpy(data, device->pathname, len+1);
|
||||
if (actual)
|
||||
*actual = len;
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user