Merge branch 'eonsteel'
This commit is contained in:
commit
d68cf82432
@ -78,6 +78,7 @@ static const backend_table_t g_backends[] = {
|
||||
{"vyper", DC_FAMILY_SUUNTO_VYPER},
|
||||
{"vyper2", DC_FAMILY_SUUNTO_VYPER2},
|
||||
{"d9", DC_FAMILY_SUUNTO_D9},
|
||||
{"eonsteel", DC_FAMILY_SUUNTO_EONSTEEL},
|
||||
{"aladin", DC_FAMILY_UWATEC_ALADIN},
|
||||
{"memomouse", DC_FAMILY_UWATEC_MEMOMOUSE},
|
||||
{"smart", DC_FAMILY_UWATEC_SMART},
|
||||
|
||||
@ -49,6 +49,7 @@ typedef enum dc_family_t {
|
||||
DC_FAMILY_SUUNTO_VYPER,
|
||||
DC_FAMILY_SUUNTO_VYPER2,
|
||||
DC_FAMILY_SUUNTO_D9,
|
||||
DC_FAMILY_SUUNTO_EONSTEEL,
|
||||
/* Reefnet */
|
||||
DC_FAMILY_REEFNET_SENSUS = (2 << 16),
|
||||
DC_FAMILY_REEFNET_SENSUSPRO,
|
||||
|
||||
@ -27,5 +27,6 @@
|
||||
#include "suunto_vyper.h"
|
||||
#include "suunto_vyper2.h"
|
||||
#include "suunto_d9.h"
|
||||
#include "suunto_eonsteel.h"
|
||||
|
||||
#endif /* SUUNTO_H */
|
||||
|
||||
39
include/libdivecomputer/suunto_eonsteel.h
Normal file
39
include/libdivecomputer/suunto_eonsteel.h
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* libdivecomputer
|
||||
*
|
||||
* Copyright (C) 2014 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 SUUNTO_EONSTEEL_H
|
||||
#define SUUNTO_EONSTEEL_H
|
||||
|
||||
#include "context.h"
|
||||
#include "device.h"
|
||||
#include "parser.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
dc_status_t suunto_eonsteel_device_open(dc_device_t **device, dc_context_t *context, const char *name, unsigned int model);
|
||||
dc_status_t suunto_eonsteel_parser_create(dc_parser_t **parser, dc_context_t *context, unsigned int model);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
#endif /* SUUNTO_EONSTEEL_H */
|
||||
@ -402,6 +402,14 @@
|
||||
RelativePath="..\src\suunto_eon_parser.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\src\suunto_eonsteel.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\src\suunto_eonsteel_parser.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\src\suunto_solution.c"
|
||||
>
|
||||
|
||||
@ -24,6 +24,7 @@ libdivecomputer_la_SOURCES = \
|
||||
suunto_vyper.c suunto_vyper_parser.c \
|
||||
suunto_vyper2.c \
|
||||
suunto_d9.c suunto_d9_parser.c \
|
||||
suunto_eonsteel.c suunto_eonsteel_parser.c \
|
||||
reefnet_sensus.c reefnet_sensus_parser.c \
|
||||
reefnet_sensuspro.c reefnet_sensuspro_parser.c \
|
||||
reefnet_sensusultra.c reefnet_sensusultra_parser.c \
|
||||
|
||||
@ -77,6 +77,10 @@ static const dc_descriptor_t g_descriptors[] = {
|
||||
{"Suunto", "D6i", DC_FAMILY_SUUNTO_D9, 0x1A},
|
||||
{"Suunto", "D9tx", DC_FAMILY_SUUNTO_D9, 0x1B},
|
||||
{"Suunto", "DX", DC_FAMILY_SUUNTO_D9, 0x1C},
|
||||
/* Suunto EON Steel */
|
||||
#ifdef HAVE_LIBUSB
|
||||
{"Suunto", "EON Steel", DC_FAMILY_SUUNTO_EONSTEEL, 0},
|
||||
#endif
|
||||
/* Uwatec Aladin */
|
||||
{"Uwatec", "Aladin Air Twin", DC_FAMILY_UWATEC_ALADIN, 0x1C},
|
||||
{"Uwatec", "Aladin Sport Plus", DC_FAMILY_UWATEC_ALADIN, 0x3E},
|
||||
@ -340,6 +344,8 @@ dc_descriptor_get_transport (dc_descriptor_t *descriptor)
|
||||
|
||||
if (descriptor->type == DC_FAMILY_ATOMICS_COBALT)
|
||||
return DC_TRANSPORT_USB;
|
||||
else if (descriptor->type == DC_FAMILY_SUUNTO_EONSTEEL)
|
||||
return DC_TRANSPORT_USB;
|
||||
else if (descriptor->type == DC_FAMILY_UWATEC_SMART)
|
||||
return DC_TRANSPORT_IRDA;
|
||||
else
|
||||
|
||||
@ -82,6 +82,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr
|
||||
case DC_FAMILY_SUUNTO_D9:
|
||||
rc = suunto_d9_device_open (&device, context, name, dc_descriptor_get_model (descriptor));
|
||||
break;
|
||||
case DC_FAMILY_SUUNTO_EONSTEEL:
|
||||
rc = suunto_eonsteel_device_open (&device, context, name, dc_descriptor_get_model (descriptor));
|
||||
break;
|
||||
case DC_FAMILY_UWATEC_ALADIN:
|
||||
rc = uwatec_aladin_device_open (&device, context, name);
|
||||
break;
|
||||
|
||||
@ -53,6 +53,7 @@ suunto_vyper_parser_create
|
||||
suunto_solution_parser_create
|
||||
suunto_eon_parser_create
|
||||
suunto_d9_parser_create
|
||||
suunto_eonsteel_parser_create
|
||||
mares_nemo_parser_create
|
||||
mares_darwin_parser_create
|
||||
mares_iconhd_parser_create
|
||||
@ -127,6 +128,7 @@ suunto_vyper2_device_reset_maxdepth
|
||||
suunto_vyper_device_open
|
||||
suunto_vyper_device_read_dive
|
||||
suunto_vyper_extract_dives
|
||||
suunto_eonsteel_device_open
|
||||
uwatec_aladin_device_open
|
||||
uwatec_aladin_extract_dives
|
||||
uwatec_memomouse_device_open
|
||||
|
||||
@ -66,6 +66,9 @@ dc_parser_new (dc_parser_t **out, dc_device_t *device)
|
||||
case DC_FAMILY_SUUNTO_D9:
|
||||
rc = suunto_d9_parser_create (&parser, context, device->devinfo.model);
|
||||
break;
|
||||
case DC_FAMILY_SUUNTO_EONSTEEL:
|
||||
rc = suunto_eonsteel_parser_create(&parser, context, device->devinfo.model);
|
||||
break;
|
||||
case DC_FAMILY_UWATEC_ALADIN:
|
||||
case DC_FAMILY_UWATEC_MEMOMOUSE:
|
||||
rc = uwatec_memomouse_parser_create (&parser, context, device->clock.devtime, device->clock.systime);
|
||||
|
||||
634
src/suunto_eonsteel.c
Normal file
634
src/suunto_eonsteel.c
Normal file
@ -0,0 +1,634 @@
|
||||
/*
|
||||
* libdivecomputer
|
||||
*
|
||||
* Copyright (C) 2014 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <libdivecomputer/suunto_eonsteel.h>
|
||||
|
||||
#include "context-private.h"
|
||||
#include "device-private.h"
|
||||
#include "array.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBUSB
|
||||
|
||||
#ifdef _WIN32
|
||||
#define NOGDI
|
||||
#endif
|
||||
|
||||
#include <libusb-1.0/libusb.h>
|
||||
|
||||
typedef struct suunto_eonsteel_device_t {
|
||||
dc_device_t base;
|
||||
|
||||
libusb_context *ctx;
|
||||
libusb_device_handle *handle;
|
||||
unsigned int magic;
|
||||
unsigned short seq;
|
||||
} suunto_eonsteel_device_t;
|
||||
|
||||
// The EON Steel implements a small filesystem
|
||||
#define DIRTYPE_FILE 0x0001
|
||||
#define DIRTYPE_DIR 0x0002
|
||||
|
||||
struct directory_entry {
|
||||
struct directory_entry *next;
|
||||
int type;
|
||||
int namelen;
|
||||
char name[1];
|
||||
};
|
||||
|
||||
// EON Steel command numbers and other magic field values
|
||||
#define INIT_CMD 0x00
|
||||
#define INIT_MAGIC 0x0001
|
||||
#define INIT_SEQ 0
|
||||
|
||||
#define READ_STRING_CMD 0x0411
|
||||
|
||||
#define FILE_LOOKUP_CMD 0x0010
|
||||
#define FILE_READ_CMD 0x0110
|
||||
#define FILE_STAT_CMD 0x0710
|
||||
#define FILE_CLOSE_CMD 0x0510
|
||||
|
||||
#define DIR_LOOKUP_CMD 0x0810
|
||||
#define READDIR_CMD 0x0910
|
||||
#define DIR_CLOSE_CMD 0x0a10
|
||||
|
||||
static dc_status_t suunto_eonsteel_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback, void *userdata);
|
||||
static dc_status_t suunto_eonsteel_device_close(dc_device_t *abstract);
|
||||
|
||||
static const dc_device_vtable_t suunto_eonsteel_device_vtable = {
|
||||
DC_FAMILY_SUUNTO_EONSTEEL,
|
||||
NULL, /* set_fingerprint */
|
||||
NULL, /* read */
|
||||
NULL, /* write */
|
||||
NULL, /* dump */
|
||||
suunto_eonsteel_device_foreach, /* foreach */
|
||||
suunto_eonsteel_device_close /* close */
|
||||
};
|
||||
|
||||
static const char dive_directory[] = "0:/dives";
|
||||
|
||||
static struct directory_entry *alloc_dirent(int type, int len, const char *name)
|
||||
{
|
||||
struct directory_entry *res;
|
||||
|
||||
res = (struct directory_entry *) malloc(offsetof(struct directory_entry, name) + len + 1);
|
||||
if (res) {
|
||||
res->next = NULL;
|
||||
res->type = type;
|
||||
res->namelen = len;
|
||||
memcpy(res->name, name, len);
|
||||
res->name[len] = 0;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static void put_le16(unsigned short val, unsigned char *p)
|
||||
{
|
||||
p[0] = val;
|
||||
p[1] = val >> 8;
|
||||
}
|
||||
|
||||
static void put_le32(unsigned int val, unsigned char *p)
|
||||
{
|
||||
p[0] = val;
|
||||
p[1] = val >> 8;
|
||||
p[2] = val >> 16;
|
||||
p[3] = val >> 24;
|
||||
}
|
||||
|
||||
static int receive_data(suunto_eonsteel_device_t *eon, unsigned char *buffer, int size)
|
||||
{
|
||||
const int InEndpoint = 0x82;
|
||||
unsigned char buf[64];
|
||||
int ret = 0;
|
||||
|
||||
for (;;) {
|
||||
int rc, transferred, len;
|
||||
|
||||
rc = libusb_interrupt_transfer(eon->handle, InEndpoint, buf, sizeof(buf), &transferred, 5000);
|
||||
if (rc || transferred != sizeof(buf)) {
|
||||
ERROR(eon->base.context, "incomplete read interrupt transfer");
|
||||
return -1;
|
||||
}
|
||||
// dump every incoming packet?
|
||||
HEXDUMP (eon->base.context, DC_LOGLEVEL_DEBUG, "rcv", buf, transferred);
|
||||
if (buf[0] != 0x3f) {
|
||||
ERROR(eon->base.context, "read interrupt transfer returns wrong report type");
|
||||
return -1;
|
||||
}
|
||||
len = buf[1];
|
||||
if (len > sizeof(buf)-2) {
|
||||
ERROR(eon->base.context, "read interrupt transfer reports short length");
|
||||
return -1;
|
||||
}
|
||||
if (len > size) {
|
||||
ERROR(eon->base.context, "read interrupt transfer reports excessive length");
|
||||
return -1;
|
||||
}
|
||||
memcpy(buffer+ret, buf+2, len);
|
||||
size -= len;
|
||||
ret += len;
|
||||
if (len < sizeof(buf)-2)
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int send_cmd(suunto_eonsteel_device_t *eon,
|
||||
unsigned short cmd,
|
||||
unsigned int len,
|
||||
const unsigned char *buffer)
|
||||
{
|
||||
const int OutEndpoint = 0x02;
|
||||
unsigned char buf[64];
|
||||
int transferred, rc;
|
||||
unsigned short seq = eon->seq;
|
||||
unsigned int magic = eon->magic;
|
||||
|
||||
// Two-byte packet header, followed by 12 bytes of extended header
|
||||
if (len > sizeof(buf)-2-12) {
|
||||
ERROR(eon->base.context, "send command with too much long");
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
|
||||
buf[0] = 0x3f;
|
||||
buf[1] = len + 12;
|
||||
|
||||
// 2-byte LE command word
|
||||
put_le16(cmd, buf+2);
|
||||
|
||||
// 4-byte LE magic value (starts at 1)
|
||||
put_le32(magic, buf+4);
|
||||
|
||||
// 2-byte LE sequence number;
|
||||
put_le16(seq, buf+8);
|
||||
|
||||
// 4-byte LE length
|
||||
put_le32(len, buf+10);
|
||||
|
||||
// .. followed by actual data
|
||||
if (len) {
|
||||
memcpy(buf+14, buffer, len);
|
||||
}
|
||||
|
||||
rc = libusb_interrupt_transfer(eon->handle, OutEndpoint, buf, sizeof(buf), &transferred, 5000);
|
||||
if (rc < 0) {
|
||||
ERROR(eon->base.context, "write interrupt transfer failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// dump every outgoing packet?
|
||||
HEXDUMP (eon->base.context, DC_LOGLEVEL_DEBUG, "cmd", buf, sizeof(buf));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Send a command, receive a reply
|
||||
*
|
||||
* This carefully checks the data fields in the reply for a match
|
||||
* against the command, and then only returns the actual reply
|
||||
* data itself.
|
||||
*
|
||||
* Also note that "receive_data()" itself will have removed the
|
||||
* per-packet handshake bytes, so unlike "send_cmd()", this does
|
||||
* not see the two initial 0x3f 0x?? bytes, and this the offsets
|
||||
* for the cmd/magic/seq/len are off by two compared to the
|
||||
* send_cmd() side. The offsets are the same in the actual raw
|
||||
* packet.
|
||||
*/
|
||||
static int send_receive(suunto_eonsteel_device_t *eon,
|
||||
unsigned short cmd,
|
||||
unsigned int len_out, const unsigned char *out,
|
||||
unsigned int len_in, unsigned char *in)
|
||||
{
|
||||
int len, actual;
|
||||
unsigned char buf[2048];
|
||||
|
||||
if (send_cmd(eon, cmd, len_out, out) < 0)
|
||||
return -1;
|
||||
len = receive_data(eon, buf, sizeof(buf));
|
||||
if (len < 10) {
|
||||
ERROR(eon->base.context, "short command reply (%d)", len);
|
||||
return -1;
|
||||
}
|
||||
if (array_uint16_le(buf) != cmd) {
|
||||
ERROR(eon->base.context, "command reply doesn't match command");
|
||||
return -1;
|
||||
}
|
||||
if (array_uint32_le(buf+2) != eon->magic + 5) {
|
||||
ERROR(eon->base.context, "command reply doesn't match magic (got %08x, expected %08x)", array_uint32_le(buf+2), eon->magic + 5);
|
||||
return -1;
|
||||
}
|
||||
if (array_uint16_le(buf+6) != eon->seq) {
|
||||
ERROR(eon->base.context, "command reply doesn't match sequence number");
|
||||
return -1;
|
||||
}
|
||||
actual = array_uint32_le(buf+8);
|
||||
if (actual + 12 != len) {
|
||||
ERROR(eon->base.context, "command reply length mismatch (got %d, claimed %d)", len-12, actual);
|
||||
return -1;
|
||||
}
|
||||
if (len_in < actual) {
|
||||
ERROR(eon->base.context, "command reply returned too much data (got %d, had %d)", actual, len_in);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Successful command - increment sequence number
|
||||
eon->seq++;
|
||||
memcpy(in, buf+12, actual);
|
||||
return actual;
|
||||
}
|
||||
|
||||
static int read_file(suunto_eonsteel_device_t *eon, const char *filename, dc_buffer_t *buf)
|
||||
{
|
||||
unsigned char result[2560];
|
||||
unsigned char cmdbuf[64];
|
||||
unsigned int size, offset;
|
||||
int rc, len;
|
||||
|
||||
memset(cmdbuf, 0, sizeof(cmdbuf));
|
||||
len = strlen(filename) + 1;
|
||||
if (len + 4 > sizeof(cmdbuf)) {
|
||||
ERROR(eon->base.context, "too long filename: %s", filename);
|
||||
return -1;
|
||||
}
|
||||
memcpy(cmdbuf+4, filename, len);
|
||||
rc = send_receive(eon, FILE_LOOKUP_CMD,
|
||||
len+4, cmdbuf,
|
||||
sizeof(result), result);
|
||||
if (rc < 0) {
|
||||
ERROR(eon->base.context, "unable to look up %s", filename);
|
||||
return -1;
|
||||
}
|
||||
HEXDUMP (eon->base.context, DC_LOGLEVEL_DEBUG, "lookup", result, rc);
|
||||
|
||||
rc = send_receive(eon, FILE_STAT_CMD,
|
||||
0, NULL,
|
||||
sizeof(result), result);
|
||||
if (rc < 0) {
|
||||
ERROR(eon->base.context, "unable to stat %s", filename);
|
||||
return -1;
|
||||
}
|
||||
HEXDUMP (eon->base.context, DC_LOGLEVEL_DEBUG, "stat", result, rc);
|
||||
|
||||
size = array_uint32_le(result+4);
|
||||
offset = 0;
|
||||
|
||||
while (size > 0) {
|
||||
unsigned int ask, got, at;
|
||||
|
||||
ask = size;
|
||||
if (ask > 1024)
|
||||
ask = 1024;
|
||||
put_le32(1234, cmdbuf+0); // Not file offset, after all
|
||||
put_le32(ask, cmdbuf+4); // Size of read
|
||||
rc = send_receive(eon, FILE_READ_CMD,
|
||||
8, cmdbuf,
|
||||
sizeof(result), result);
|
||||
if (rc < 0) {
|
||||
ERROR(eon->base.context, "unable to read %s", filename);
|
||||
return -1;
|
||||
}
|
||||
if (rc < 8) {
|
||||
ERROR(eon->base.context, "got short read reply for %s", filename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Not file offset, just stays unmodified.
|
||||
at = array_uint32_le(result);
|
||||
if (at != 1234) {
|
||||
ERROR(eon->base.context, "read of %s returned different offset than asked for (%d vs %d)", filename, at, offset);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Number of bytes actually read
|
||||
got = array_uint32_le(result+4);
|
||||
if (!got)
|
||||
break;
|
||||
if (rc < 8 + got) {
|
||||
ERROR(eon->base.context, "odd read size reply for offset %d of file %s", offset, filename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (got > size)
|
||||
got = size;
|
||||
dc_buffer_append(buf, result+8, got);
|
||||
offset += got;
|
||||
size -= got;
|
||||
}
|
||||
|
||||
rc = send_receive(eon, FILE_CLOSE_CMD,
|
||||
0, NULL,
|
||||
sizeof(result), result);
|
||||
if (rc < 0) {
|
||||
ERROR(eon->base.context, "cmd FILE_CLOSE_CMD failed");
|
||||
return -1;
|
||||
}
|
||||
HEXDUMP(eon->base.context, DC_LOGLEVEL_DEBUG, "close", result, rc);
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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, int len, struct directory_entry *old)
|
||||
{
|
||||
while (len > 8) {
|
||||
unsigned int type = array_uint32_le(p);
|
||||
unsigned int namelen = array_uint32_le(p+4);
|
||||
const unsigned char *name = p+8;
|
||||
struct directory_entry *entry;
|
||||
|
||||
if (namelen + 8 + 1 > len || name[namelen] != 0) {
|
||||
ERROR(eon->base.context, "corrupt dirent entry");
|
||||
break;
|
||||
}
|
||||
HEXDUMP(eon->base.context, DC_LOGLEVEL_DEBUG, "dir entry", p, 8);
|
||||
|
||||
p += 8 + namelen + 1;
|
||||
len -= 8 + namelen + 1;
|
||||
entry = alloc_dirent(type, namelen, (const char *) name);
|
||||
if (!entry) {
|
||||
ERROR(eon->base.context, "out of memory");
|
||||
break;
|
||||
}
|
||||
entry->next = old;
|
||||
old = entry;
|
||||
}
|
||||
return old;
|
||||
}
|
||||
|
||||
static int get_file_list(suunto_eonsteel_device_t *eon, struct directory_entry **res)
|
||||
{
|
||||
struct directory_entry *de = NULL;
|
||||
unsigned char cmd[64];
|
||||
unsigned char result[2048];
|
||||
int rc, cmdlen;
|
||||
|
||||
|
||||
*res = NULL;
|
||||
put_le32(0, cmd);
|
||||
memcpy(cmd + 4, dive_directory, sizeof(dive_directory));
|
||||
cmdlen = 4 + sizeof(dive_directory);
|
||||
rc = send_receive(eon, DIR_LOOKUP_CMD,
|
||||
cmdlen, cmd,
|
||||
sizeof(result), result);
|
||||
if (rc < 0) {
|
||||
ERROR(eon->base.context, "cmd DIR_LOOKUP failed");
|
||||
}
|
||||
HEXDUMP(eon->base.context, DC_LOGLEVEL_DEBUG, "DIR_LOOKUP", result, rc);
|
||||
|
||||
for (;;) {
|
||||
unsigned int nr, last;
|
||||
|
||||
rc = send_receive(eon, READDIR_CMD,
|
||||
0, NULL,
|
||||
sizeof(result), result);
|
||||
if (rc < 0) {
|
||||
ERROR(eon->base.context, "readdir failed");
|
||||
return -1;
|
||||
}
|
||||
if (rc < 8) {
|
||||
ERROR(eon->base.context, "short readdir result");
|
||||
return -1;
|
||||
}
|
||||
nr = array_uint32_le(result);
|
||||
last = array_uint32_le(result+4);
|
||||
HEXDUMP(eon->base.context, DC_LOGLEVEL_DEBUG, "dir packet", result, 8);
|
||||
|
||||
de = parse_dirent(eon, nr, result+8, rc-8, de);
|
||||
if (last)
|
||||
break;
|
||||
}
|
||||
|
||||
rc = send_receive(eon, DIR_CLOSE_CMD,
|
||||
0, NULL,
|
||||
sizeof(result), result);
|
||||
if (rc < 0) {
|
||||
ERROR(eon->base.context, "dir close failed");
|
||||
}
|
||||
|
||||
*res = de;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int initialize_eonsteel(suunto_eonsteel_device_t *eon)
|
||||
{
|
||||
const int InEndpoint = 0x82;
|
||||
const unsigned char init[] = {0x02, 0x00, 0x2a, 0x00};
|
||||
unsigned char buf[64];
|
||||
|
||||
/* Get rid of any pending stale input first */
|
||||
for (;;) {
|
||||
int transferred;
|
||||
|
||||
int rc = libusb_interrupt_transfer(eon->handle, InEndpoint, buf, sizeof(buf), &transferred, 10);
|
||||
if (rc < 0)
|
||||
break;
|
||||
if (!transferred)
|
||||
break;
|
||||
}
|
||||
|
||||
if (send_cmd(eon, INIT_CMD, sizeof(init), init)) {
|
||||
ERROR(eon->base.context, "Failed to send initialization command");
|
||||
return -1;
|
||||
}
|
||||
if (receive_data(eon, buf, sizeof(buf)) < 0) {
|
||||
ERROR(eon->base.context, "Failed to receive initial reply");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Don't ask
|
||||
eon->magic = 0x00000005 | (buf[4] << 16) | (buf[5] << 24);
|
||||
// Increment the sequence number for every command sent
|
||||
eon->seq++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
dc_status_t
|
||||
suunto_eonsteel_device_open(dc_device_t **out, dc_context_t *context, const char *name, unsigned int model)
|
||||
{
|
||||
suunto_eonsteel_device_t *eon;
|
||||
|
||||
if (out == NULL)
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
|
||||
eon = (suunto_eonsteel_device_t *) calloc(1, sizeof(suunto_eonsteel_device_t));
|
||||
if (!eon)
|
||||
return DC_STATUS_NOMEMORY;
|
||||
|
||||
// Set up the magic handshake fields
|
||||
eon->magic = INIT_MAGIC;
|
||||
eon->seq = INIT_SEQ;
|
||||
|
||||
// Set up the libdivecomputer interfaces
|
||||
device_init(&eon->base, context, &suunto_eonsteel_device_vtable);
|
||||
|
||||
if (libusb_init(&eon->ctx)) {
|
||||
ERROR(context, "libusb_init() failed");
|
||||
free(eon);
|
||||
return DC_STATUS_IO;
|
||||
}
|
||||
|
||||
eon->handle = libusb_open_device_with_vid_pid(eon->ctx, 0x1493, 0x0030);
|
||||
if (!eon->handle) {
|
||||
ERROR(context, "unable to open device");
|
||||
libusb_exit(eon->ctx);
|
||||
free(eon);
|
||||
return DC_STATUS_IO;
|
||||
}
|
||||
|
||||
#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000102)
|
||||
libusb_set_auto_detach_kernel_driver(eon->handle, 1);
|
||||
#endif
|
||||
|
||||
libusb_claim_interface(eon->handle, 0);
|
||||
|
||||
if (initialize_eonsteel(eon) < 0) {
|
||||
ERROR(context, "unable to initialize device");
|
||||
libusb_close(eon->handle);
|
||||
libusb_exit(eon->ctx);
|
||||
free(eon);
|
||||
return DC_STATUS_IO;
|
||||
}
|
||||
|
||||
*out = (dc_device_t *) eon;
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static int count_dir_entries(struct directory_entry *de)
|
||||
{
|
||||
int count = 0;
|
||||
while (de) {
|
||||
count++;
|
||||
de = de->next;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
suunto_eonsteel_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
|
||||
{
|
||||
int skip = 0, rc;
|
||||
struct directory_entry *de;
|
||||
suunto_eonsteel_device_t *eon = (suunto_eonsteel_device_t *) abstract;
|
||||
dc_buffer_t *file;
|
||||
char pathname[64];
|
||||
unsigned int time;
|
||||
dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
|
||||
|
||||
if (get_file_list(eon, &de) < 0)
|
||||
return DC_STATUS_IO;
|
||||
|
||||
file = dc_buffer_new(0);
|
||||
progress.maximum = count_dir_entries(de);
|
||||
progress.current = 0;
|
||||
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
|
||||
|
||||
while (de) {
|
||||
int len;
|
||||
struct directory_entry *next = de->next;
|
||||
unsigned char buf[4];
|
||||
|
||||
if (device_is_cancelled(abstract))
|
||||
skip = 1;
|
||||
|
||||
switch (de->type) {
|
||||
case DIRTYPE_DIR:
|
||||
/* Ignore subdirectories in the dive directory */
|
||||
break;
|
||||
case DIRTYPE_FILE:
|
||||
if (skip)
|
||||
break;
|
||||
if (sscanf(de->name, "%x.LOG", &time) != 1)
|
||||
break;
|
||||
len = snprintf(pathname, sizeof(pathname), "%s/%s", dive_directory, de->name);
|
||||
if (len >= sizeof(pathname))
|
||||
break;
|
||||
|
||||
// Reset the membuffer, put the 4-byte length at the head.
|
||||
dc_buffer_clear(file);
|
||||
put_le32(time, buf);
|
||||
dc_buffer_append(file, buf, 4);
|
||||
|
||||
// Then read the filename into the rest of the buffer
|
||||
rc = read_file(eon, pathname, file);
|
||||
if (rc < 0)
|
||||
break;
|
||||
if (!callback)
|
||||
break;
|
||||
if (!callback(dc_buffer_get_data(file), dc_buffer_get_size(file), NULL, 0, userdata))
|
||||
skip = 1;
|
||||
|
||||
// We've used up the buffer, so create a new one
|
||||
file = dc_buffer_new(0);
|
||||
}
|
||||
progress.current++;
|
||||
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
|
||||
|
||||
free(de);
|
||||
de = next;
|
||||
}
|
||||
dc_buffer_free(file);
|
||||
|
||||
return device_is_cancelled(abstract) ? DC_STATUS_CANCELLED : DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
suunto_eonsteel_device_close(dc_device_t *abstract)
|
||||
{
|
||||
suunto_eonsteel_device_t *eon = (suunto_eonsteel_device_t *) abstract;
|
||||
|
||||
libusb_close(eon->handle);
|
||||
libusb_exit(eon->ctx);
|
||||
free(eon);
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
#else // no LIBUSB support
|
||||
|
||||
dc_status_t
|
||||
suunto_eonsteel_device_open(dc_device_t **out, dc_context_t *context, const char *name, unsigned int model)
|
||||
{
|
||||
ERROR(context, "The Suunto EON Steel backend needs libusb-1.0");
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
}
|
||||
|
||||
#endif
|
||||
515
src/suunto_eonsteel_parser.c
Normal file
515
src/suunto_eonsteel_parser.c
Normal file
@ -0,0 +1,515 @@
|
||||
/*
|
||||
* libdivecomputer
|
||||
*
|
||||
* Copyright (C) 2014 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 <string.h>
|
||||
|
||||
#include <libdivecomputer/suunto_eonsteel.h>
|
||||
|
||||
#include "context-private.h"
|
||||
#include "parser-private.h"
|
||||
#include "array.h"
|
||||
|
||||
struct type_desc {
|
||||
const char *desc, *format, *mod;
|
||||
};
|
||||
|
||||
#define MAXTYPE 512
|
||||
#define MAXGASES 16
|
||||
|
||||
typedef struct suunto_eonsteel_parser_t {
|
||||
dc_parser_t base;
|
||||
struct type_desc type_desc[MAXTYPE];
|
||||
// field cache
|
||||
struct {
|
||||
unsigned int initialized;
|
||||
unsigned int divetime;
|
||||
double maxdepth;
|
||||
double avgdepth;
|
||||
unsigned int ngases;
|
||||
dc_gasmix_t gasmix[MAXGASES];
|
||||
dc_salinity_t salinity;
|
||||
double surface_pressure;
|
||||
} cache;
|
||||
} 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);
|
||||
|
||||
static void
|
||||
desc_free (struct type_desc desc[], unsigned int count)
|
||||
{
|
||||
for (unsigned int i = 0; i < count; ++i) {
|
||||
free((void *)desc[i].desc);
|
||||
free((void *)desc[i].format);
|
||||
free((void *)desc[i].mod);
|
||||
}
|
||||
}
|
||||
|
||||
static int record_type(suunto_eonsteel_parser_t *eon, unsigned short type, const char *name, int namelen)
|
||||
{
|
||||
struct type_desc desc;
|
||||
const char *next;
|
||||
|
||||
desc.desc = desc.format = desc.mod = NULL;
|
||||
do {
|
||||
int len;
|
||||
char *p;
|
||||
|
||||
next = strchr(name, '\n');
|
||||
if (next) {
|
||||
len = next - name;
|
||||
next++;
|
||||
} else {
|
||||
len = strlen(name);
|
||||
}
|
||||
|
||||
if (len < 5 || name[0] != '<' || name[4] != '>') {
|
||||
ERROR(eon->base.context, "Unexpected type description: %.*s", len, name);
|
||||
return -1;
|
||||
}
|
||||
p = (char *) malloc(len-4);
|
||||
if (!p) {
|
||||
ERROR(eon->base.context, "out of memory");
|
||||
desc_free(&desc, 1);
|
||||
return -1;
|
||||
}
|
||||
memcpy(p, name+5, len-5);
|
||||
p[len-5] = 0;
|
||||
|
||||
// PTH, GRP, FRM, MOD
|
||||
switch (name[1]) {
|
||||
case 'P':
|
||||
case 'G':
|
||||
desc.desc = p;
|
||||
break;
|
||||
case 'F':
|
||||
desc.format = p;
|
||||
break;
|
||||
case 'M':
|
||||
desc.mod = p;
|
||||
break;
|
||||
default:
|
||||
ERROR(eon->base.context, "Unknown type descriptor: %.*s", len, name);
|
||||
desc_free(&desc, 1);
|
||||
free(p);
|
||||
return -1;
|
||||
}
|
||||
} while ((name = next) != NULL);
|
||||
|
||||
if (type > MAXTYPE) {
|
||||
ERROR(eon->base.context, "Type out of range (%04x: '%s' '%s' '%s')",
|
||||
type,
|
||||
desc.desc ? desc.desc : "",
|
||||
desc.format ? desc.format : "",
|
||||
desc.mod ? desc.mod : "");
|
||||
desc_free(&desc, 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
desc_free(eon->type_desc + type, 1);
|
||||
eon->type_desc[type] = desc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int traverse_entry(suunto_eonsteel_parser_t *eon, const unsigned char *p, int len, eon_data_cb_t callback, void *user)
|
||||
{
|
||||
const unsigned char *name, *data, *end, *last, *one_past_end = p + len;
|
||||
int textlen, type;
|
||||
int rc;
|
||||
|
||||
// First two bytes: zero and text length
|
||||
if (p[0]) {
|
||||
HEXDUMP(eon->base.context, DC_LOGLEVEL_DEBUG, "next", p, 8);
|
||||
ERROR(eon->base.context, "Bad dive entry (%02x)", p[0]);
|
||||
return -1;
|
||||
}
|
||||
textlen = p[1];
|
||||
|
||||
name = p + 2;
|
||||
if (textlen == 0xff) {
|
||||
textlen = array_uint32_le(name);
|
||||
name += 4;
|
||||
}
|
||||
|
||||
// Two bytes of 'type' followed by the name/descriptor, followed by the data
|
||||
data = name + textlen;
|
||||
type = array_uint16_le(name);
|
||||
name += 2;
|
||||
|
||||
if (*name != '<') {
|
||||
HEXDUMP(eon->base.context, DC_LOGLEVEL_DEBUG, "bad", p, 16);
|
||||
return -1;
|
||||
}
|
||||
|
||||
record_type(eon, type, (const char *) name, textlen-3);
|
||||
|
||||
end = data;
|
||||
last = data;
|
||||
while (end < one_past_end && *end) {
|
||||
const unsigned char *begin = end;
|
||||
unsigned int type = *end++;
|
||||
unsigned int len;
|
||||
if (type == 0xff) {
|
||||
type = array_uint16_le(end);
|
||||
end += 2;
|
||||
}
|
||||
len = *end++;
|
||||
|
||||
// I've never actually seen this case yet..
|
||||
// Just assuming from the other cases.
|
||||
if (len == 0xff) {
|
||||
HEXDUMP(eon->base.context, DC_LOGLEVEL_DEBUG, "len-ff", end, 8);
|
||||
len = array_uint32_le(end);
|
||||
end += 4;
|
||||
}
|
||||
|
||||
if (type > MAXTYPE || !eon->type_desc[type].desc) {
|
||||
HEXDUMP(eon->base.context, DC_LOGLEVEL_DEBUG, "last", last, 16);
|
||||
HEXDUMP(eon->base.context, DC_LOGLEVEL_DEBUG, "this", begin, 16);
|
||||
} else {
|
||||
rc = callback(type, eon->type_desc+type, end, len, user);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
}
|
||||
|
||||
last = begin;
|
||||
end += len;
|
||||
}
|
||||
|
||||
return end - p;
|
||||
}
|
||||
|
||||
static int traverse_data(suunto_eonsteel_parser_t *eon, eon_data_cb_t callback, void *user)
|
||||
{
|
||||
const unsigned char *data = eon->base.data;
|
||||
int len = eon->base.size;
|
||||
|
||||
// Dive files start with "SBEM" and four NUL characters
|
||||
// Additionally, we've prepended the time as an extra
|
||||
// 4-byte pre-header
|
||||
if (len < 12 || memcmp(data+4, "SBEM", 4))
|
||||
return 0;
|
||||
|
||||
data += 12;
|
||||
len -= 12;
|
||||
|
||||
while (len > 4) {
|
||||
int i = traverse_entry(eon, data, len, callback, user);
|
||||
if (i < 0)
|
||||
return 1;
|
||||
len -= i;
|
||||
data += i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct sample_data {
|
||||
suunto_eonsteel_parser_t *eon;
|
||||
dc_sample_callback_t callback;
|
||||
void *userdata;
|
||||
unsigned int time;
|
||||
};
|
||||
|
||||
static void sample_time(struct sample_data *info, unsigned short time_delta)
|
||||
{
|
||||
dc_sample_value_t sample = {0};
|
||||
|
||||
info->time += time_delta;
|
||||
sample.time = info->time / 1000;
|
||||
if (info->callback) info->callback(DC_SAMPLE_TIME, sample, info->userdata);
|
||||
}
|
||||
|
||||
static void sample_depth(struct sample_data *info, unsigned short depth)
|
||||
{
|
||||
dc_sample_value_t sample = {0};
|
||||
|
||||
if (depth == 0xffff)
|
||||
return;
|
||||
|
||||
sample.depth = depth / 100.0;
|
||||
if (info->callback) info->callback(DC_SAMPLE_DEPTH, sample, info->userdata);
|
||||
}
|
||||
|
||||
static void sample_temp(struct sample_data *info, short temp)
|
||||
{
|
||||
dc_sample_value_t sample = {0};
|
||||
|
||||
if (temp < -3000)
|
||||
return;
|
||||
|
||||
sample.temperature = temp / 10.0;
|
||||
if (info->callback) info->callback(DC_SAMPLE_TEMPERATURE, sample, info->userdata);
|
||||
}
|
||||
|
||||
static void sample_deco(struct sample_data *info, short ndl, unsigned short tts, unsigned ceiling)
|
||||
{
|
||||
dc_sample_value_t sample = {0};
|
||||
|
||||
/* Are we in deco? */
|
||||
if (ndl < 0) {
|
||||
sample.deco.type = DC_DECO_DECOSTOP;
|
||||
if (tts != 0xffff)
|
||||
sample.deco.time = tts;
|
||||
if (ceiling != 0xffff)
|
||||
sample.deco.depth = ceiling / 100.0;
|
||||
} else {
|
||||
sample.deco.type = DC_DECO_NDL;
|
||||
sample.deco.time = ndl;
|
||||
}
|
||||
if (info->callback) info->callback(DC_SAMPLE_DECO, sample, info->userdata);
|
||||
}
|
||||
|
||||
static void sample_cylinder_pressure(struct sample_data *info, unsigned char idx, unsigned short pressure)
|
||||
{
|
||||
dc_sample_value_t sample = {0};
|
||||
|
||||
if (pressure == 0xffff)
|
||||
return;
|
||||
|
||||
sample.pressure.tank = idx-1;
|
||||
sample.pressure.value = pressure / 100.0;
|
||||
if (info->callback) info->callback(DC_SAMPLE_PRESSURE, sample, info->userdata);
|
||||
}
|
||||
|
||||
static int traverse_samples(unsigned short type, const struct type_desc *desc, const unsigned char *data, int len, void *user)
|
||||
{
|
||||
struct sample_data *info = (struct sample_data *) user;
|
||||
|
||||
switch (type) {
|
||||
case 0x0001: // group: time in first word, depth in second
|
||||
sample_time(info, array_uint16_le(data));
|
||||
sample_depth(info, array_uint16_le(data+2));
|
||||
sample_temp(info, array_uint16_le(data+4));
|
||||
sample_deco(info, array_uint16_le(data+8), array_uint16_le(data+10), array_uint16_le(data+12));
|
||||
break;
|
||||
case 0x0002: // time in first word
|
||||
sample_time(info, array_uint16_le(data));
|
||||
break;
|
||||
case 0x0003: // depth in first word
|
||||
sample_depth(info, array_uint16_le(data));
|
||||
break;
|
||||
case 0x000a: // cylinder idx in first byte, pressure in next word
|
||||
sample_cylinder_pressure(info, data[0], array_uint16_le(data+1));
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
suunto_eonsteel_parser_samples_foreach(dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
|
||||
{
|
||||
suunto_eonsteel_parser_t *eon = (suunto_eonsteel_parser_t *) abstract;
|
||||
struct sample_data data = { eon, callback, userdata, 0 };
|
||||
|
||||
traverse_data(eon, traverse_samples, &data);
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// 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) \
|
||||
memcpy((p), &(set), sizeof(set))
|
||||
|
||||
static dc_status_t
|
||||
suunto_eonsteel_parser_get_field(dc_parser_t *parser, dc_field_type_t type, unsigned int flags, void *value)
|
||||
{
|
||||
suunto_eonsteel_parser_t *eon = (suunto_eonsteel_parser_t *)parser;
|
||||
|
||||
if (!(eon->cache.initialized >> type))
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
|
||||
switch (type) {
|
||||
case DC_FIELD_DIVETIME:
|
||||
field_value(value, eon->cache.divetime);
|
||||
break;
|
||||
case DC_FIELD_MAXDEPTH:
|
||||
field_value(value, eon->cache.maxdepth);
|
||||
break;
|
||||
case DC_FIELD_AVGDEPTH:
|
||||
field_value(value, eon->cache.avgdepth);
|
||||
break;
|
||||
case DC_FIELD_GASMIX_COUNT:
|
||||
field_value(value, eon->cache.ngases);
|
||||
break;
|
||||
case DC_FIELD_GASMIX:
|
||||
if (flags >= MAXGASES)
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
field_value(value, eon->cache.gasmix[flags]);
|
||||
break;
|
||||
case DC_FIELD_SALINITY:
|
||||
field_value(value, eon->cache.salinity);
|
||||
break;
|
||||
case DC_FIELD_ATMOSPHERIC:
|
||||
field_value(value, eon->cache.surface_pressure);
|
||||
break;
|
||||
default:
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
}
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* The time of the dive is encoded in the filename,
|
||||
* and we've saved it off as the four first bytes
|
||||
* of the dive data (in little-endian format).
|
||||
*/
|
||||
static dc_status_t
|
||||
suunto_eonsteel_parser_get_datetime(dc_parser_t *parser, dc_datetime_t *datetime)
|
||||
{
|
||||
if (parser->size < 4)
|
||||
return DC_STATUS_UNSUPPORTED;
|
||||
|
||||
dc_datetime_gmtime(datetime, array_uint32_le(parser->data));
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// time in ms
|
||||
static void add_time_field(suunto_eonsteel_parser_t *eon, unsigned short time_delta_ms)
|
||||
{
|
||||
eon->cache.divetime += time_delta_ms;
|
||||
}
|
||||
|
||||
// depth in cm
|
||||
static void set_depth_field(suunto_eonsteel_parser_t *eon, unsigned short d)
|
||||
{
|
||||
if (d != 0xffff) {
|
||||
double depth = d / 100.0;
|
||||
if (depth > eon->cache.maxdepth)
|
||||
eon->cache.maxdepth = depth;
|
||||
eon->cache.initialized |= 1 << DC_FIELD_MAXDEPTH;
|
||||
}
|
||||
}
|
||||
|
||||
// gas type: 0=Off,1=Primary,2=?,3=Diluent
|
||||
static void add_gas_type(suunto_eonsteel_parser_t *eon, unsigned char type)
|
||||
{
|
||||
if (eon->cache.ngases < MAXGASES)
|
||||
eon->cache.ngases++;
|
||||
eon->cache.initialized |= 1 << DC_FIELD_GASMIX_COUNT;
|
||||
}
|
||||
|
||||
// O2 percentage as a byte
|
||||
static void add_gas_o2(suunto_eonsteel_parser_t *eon, unsigned char o2)
|
||||
{
|
||||
int idx = eon->cache.ngases-1;
|
||||
if (idx >= 0)
|
||||
eon->cache.gasmix[idx].oxygen = o2 / 100.0;
|
||||
eon->cache.initialized |= 1 << DC_FIELD_GASMIX;
|
||||
}
|
||||
|
||||
// He percentage as a byte
|
||||
static void add_gas_he(suunto_eonsteel_parser_t *eon, unsigned char he)
|
||||
{
|
||||
int idx = eon->cache.ngases-1;
|
||||
if (idx >= 0)
|
||||
eon->cache.gasmix[idx].helium = he / 100.0;
|
||||
eon->cache.initialized |= 1 << DC_FIELD_GASMIX;
|
||||
}
|
||||
|
||||
static int traverse_fields(unsigned short type, const struct type_desc *desc, const unsigned char *data, int len, void *user)
|
||||
{
|
||||
suunto_eonsteel_parser_t *eon = (suunto_eonsteel_parser_t *) user;
|
||||
|
||||
switch (type) {
|
||||
case 0x0001: // group: time in first word, depth in second
|
||||
add_time_field(eon, array_uint16_le(data));
|
||||
set_depth_field(eon, array_uint16_le(data+2));
|
||||
break;
|
||||
case 0x0002: // time in first word
|
||||
add_time_field(eon, array_uint16_le(data));
|
||||
break;
|
||||
case 0x0003: // depth in first word
|
||||
set_depth_field(eon, array_uint16_le(data));
|
||||
break;
|
||||
case 0x000d: // gas state in first byte
|
||||
add_gas_type(eon, data[0]);
|
||||
break;
|
||||
case 0x000e: // Oxygen percentage in first byte
|
||||
add_gas_o2(eon, data[0]);
|
||||
break;
|
||||
case 0x000f: // Helium percentage in first byte
|
||||
add_gas_he(eon, data[0]);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void initialize_field_caches(suunto_eonsteel_parser_t *eon)
|
||||
{
|
||||
memset(&eon->cache, 0, sizeof(eon->cache));
|
||||
eon->cache.initialized = 1 << DC_FIELD_DIVETIME;
|
||||
|
||||
traverse_data(eon, traverse_fields, eon);
|
||||
|
||||
// The internal time fields are in ms and have to be added up
|
||||
// like that. At the end, we translate it back to seconds.
|
||||
eon->cache.divetime /= 1000;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
suunto_eonsteel_parser_set_data(dc_parser_t *parser, const unsigned char *data, unsigned int size)
|
||||
{
|
||||
suunto_eonsteel_parser_t *eon = (suunto_eonsteel_parser_t *) parser;
|
||||
|
||||
desc_free(eon->type_desc, MAXTYPE);
|
||||
memset(eon->type_desc, 0, sizeof(eon->type_desc));
|
||||
initialize_field_caches(eon);
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static dc_status_t
|
||||
suunto_eonsteel_parser_destroy(dc_parser_t *parser)
|
||||
{
|
||||
suunto_eonsteel_parser_t *eon = (suunto_eonsteel_parser_t *) parser;
|
||||
|
||||
desc_free(eon->type_desc, MAXTYPE);
|
||||
free(parser);
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static const dc_parser_vtable_t suunto_eonsteel_parser_vtable = {
|
||||
DC_FAMILY_SUUNTO_EONSTEEL,
|
||||
suunto_eonsteel_parser_set_data, /* set_data */
|
||||
suunto_eonsteel_parser_get_datetime, /* datetime */
|
||||
suunto_eonsteel_parser_get_field, /* fields */
|
||||
suunto_eonsteel_parser_samples_foreach, /* samples_foreach */
|
||||
suunto_eonsteel_parser_destroy /* destroy */
|
||||
};
|
||||
|
||||
dc_status_t
|
||||
suunto_eonsteel_parser_create(dc_parser_t **out, dc_context_t *context, unsigned int model)
|
||||
{
|
||||
suunto_eonsteel_parser_t *eon;
|
||||
|
||||
if (out == NULL)
|
||||
return DC_STATUS_INVALIDARGS;
|
||||
|
||||
eon = (suunto_eonsteel_parser_t *) calloc(1, sizeof(*eon));
|
||||
if (!eon)
|
||||
return DC_STATUS_NOMEMORY;
|
||||
|
||||
parser_init(&eon->base, context, &suunto_eonsteel_parser_vtable);
|
||||
|
||||
*out = (dc_parser_t *) eon;
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user