diff --git a/examples/universal.c b/examples/universal.c
index 2b0fc81..56b1946 100644
--- a/examples/universal.c
+++ b/examples/universal.c
@@ -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},
diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h
index 983ec07..4bcc700 100644
--- a/include/libdivecomputer/common.h
+++ b/include/libdivecomputer/common.h
@@ -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,
diff --git a/include/libdivecomputer/suunto.h b/include/libdivecomputer/suunto.h
index 337dffd..3ff7c88 100644
--- a/include/libdivecomputer/suunto.h
+++ b/include/libdivecomputer/suunto.h
@@ -27,5 +27,6 @@
#include "suunto_vyper.h"
#include "suunto_vyper2.h"
#include "suunto_d9.h"
+#include "suunto_eonsteel.h"
#endif /* SUUNTO_H */
diff --git a/include/libdivecomputer/suunto_eonsteel.h b/include/libdivecomputer/suunto_eonsteel.h
new file mode 100644
index 0000000..d12387c
--- /dev/null
+++ b/include/libdivecomputer/suunto_eonsteel.h
@@ -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 EON_STEEL_H
+#define EON_STEEL_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 /* EON_STEEL_H */
diff --git a/msvc/libdivecomputer.vcproj b/msvc/libdivecomputer.vcproj
index 78a8190..ff22d90 100644
--- a/msvc/libdivecomputer.vcproj
+++ b/msvc/libdivecomputer.vcproj
@@ -402,6 +402,14 @@
RelativePath="..\src\suunto_eon_parser.c"
>
+
+
+
+
diff --git a/src/Makefile.am b/src/Makefile.am
index 595f0c7..458f1dd 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -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 \
diff --git a/src/descriptor.c b/src/descriptor.c
index fba9874..22ebe7c 100644
--- a/src/descriptor.c
+++ b/src/descriptor.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
diff --git a/src/device.c b/src/device.c
index 9fad7a2..edc1cf2 100644
--- a/src/device.c
+++ b/src/device.c
@@ -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;
diff --git a/src/libdivecomputer.symbols b/src/libdivecomputer.symbols
index 14a96a8..55852ae 100644
--- a/src/libdivecomputer.symbols
+++ b/src/libdivecomputer.symbols
@@ -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
diff --git a/src/parser.c b/src/parser.c
index 7cc7f4e..a17a586 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -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);
diff --git a/src/suunto_eonsteel.c b/src/suunto_eonsteel.c
new file mode 100644
index 0000000..aeff089
--- /dev/null
+++ b/src/suunto_eonsteel.c
@@ -0,0 +1,613 @@
+/*
+ * 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "context-private.h"
+#include "device-private.h"
+#include "array.h"
+
+#ifdef HAVE_LIBUSB
+
+#include
+
+struct eonsteel {
+ dc_device_t base;
+
+ libusb_context *ctx;
+ libusb_device_handle *handle;
+ unsigned int magic;
+ unsigned short seq;
+};
+
+// 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 INIT_LEN 4
+#define INIT_DATA "\x02\x00\x2a\x00"
+
+#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 const char *dive_directory = "0:/dives";
+
+static struct directory_entry *alloc_dirent(int type, int len, const char *name)
+{
+ struct directory_entry *res;
+
+ res = 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 int report_error(const char *fmt, ...)
+{
+ char buffer[128];
+ va_list args;
+
+ va_start(args, fmt);
+ vsnprintf(buffer, sizeof(buffer)-1, fmt, args);
+ va_end(args);
+ fprintf(stderr, "%.*s\n", (int)sizeof(buffer), buffer);
+ return -1;
+}
+
+static void put_le16(unsigned short val, void *dst)
+{
+ unsigned char *p = (unsigned char *)dst;
+ p[0] = val;
+ p[1] = val >> 8;
+}
+
+static void put_le32(unsigned int val, void *dst)
+{
+ unsigned char *p = (unsigned char *)dst;
+ p[0] = val;
+ p[1] = val >> 8;
+ p[2] = val >> 16;
+ p[3] = val >> 24;
+}
+
+static void debug(const char *name, const char *buf, int len)
+{
+ int i;
+
+ fprintf(stderr, "%4d %s:", len, name);
+ for (i = 0; i < len; i++)
+ fprintf(stderr, " %02x", (unsigned char) buf[i]);
+ fprintf(stderr, "\n");
+}
+
+static void debug_text(const char *name, const char *buf, int len)
+{
+ int i;
+
+ printf("text of %s:\n", name);
+ for (i = 0; i < len; i++) {
+ unsigned char c = buf[i];
+ if (c > 31 && c < 127)
+ putchar(c);
+ else if (c == '\n') {
+ putchar('\\');
+ putchar('n');
+ } else {
+ static const char hex[16]="0123456789abcdef";
+ putchar('\\');
+ putchar('x');
+ putchar(hex[c>>4]);
+ putchar(hex[c&15]);
+ }
+ }
+ printf("\nend of text\n");
+}
+
+static int receive_data(struct eonsteel *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))
+ return report_error("incomplete read interrupt transfer");
+// dump every incoming packet?
+// debug("rcv", buf, transferred);
+ if (buf[0] != 0x3f)
+ return report_error("read interrupt transfer returns wrong report type");
+ len = buf[1];
+ if (len > sizeof(buf)-2)
+ return report_error("read interrupt transfer reports short length");
+ if (len > size)
+ return report_error("read interrupt transfer reports excessive length");
+ memcpy(buffer+ret, buf+2, len);
+ size -= len;
+ ret += len;
+ if (len < sizeof(buf)-2)
+ break;
+ }
+
+ return ret;
+}
+
+static int send_cmd(struct eonsteel *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)
+ return report_error("send command with too much long");
+
+ 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
+ memcpy(buf+14, buffer, len);
+
+ rc = libusb_interrupt_transfer(eon->handle, OutEndpoint, buf, sizeof(buf), &transferred, 5000);
+ if (rc < 0)
+ return report_error("write interrupt transfer failed");
+
+// dump every outgoing packet?
+// 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(struct eonsteel *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)
+ return report_error("short command reply (%d)", len);
+ if (array_uint16_le(buf) != cmd)
+ return report_error("command reply doesn't match command");
+ if (array_uint32_le(buf+2) != eon->magic + 5)
+ return report_error("command reply doesn't match magic (got %08x, expected %08x)", array_uint32_le(buf+2), eon->magic + 5);
+ if (array_uint16_le(buf+6) != eon->seq)
+ return report_error("command reply doesn't match sequence number");
+ actual = array_uint32_le(buf+8);
+ if (actual + 12 != len)
+ return report_error("command reply length mismatch (got %d, claimed %d)", len-12, actual);
+ if (len_in < actual)
+ return report_error("command reply returned too much data (got %d, had %d)", actual, len_in);
+
+ // Successful command - increment sequence number
+ eon->seq++;
+ memcpy(in, buf+12, actual);
+ return actual;
+}
+
+static int read_file(struct eonsteel *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))
+ return report_error("too long filename: %s", filename);
+ memcpy(cmdbuf+4, filename, len);
+ rc = send_receive(eon, FILE_LOOKUP_CMD,
+ len+4, cmdbuf,
+ sizeof(result), result);
+ if (rc < 0)
+ return report_error("unable to look up %s", filename);
+// debug("lookup", result, rc);
+
+ rc = send_receive(eon, FILE_STAT_CMD,
+ 0, "",
+ sizeof(result), result);
+ if (rc < 0)
+ return report_error("unable to stat %s", filename);
+// 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)
+ return report_error("unable to read %s", filename);
+ if (rc < 8)
+ return report_error("got short read reply for %s", filename);
+
+ // Not file offset, just stays unmodified.
+ at = array_uint32_le(result);
+ if (at != 1234)
+ return report_error("read of %s returned different offset than asked for (%d vs %d)", filename, at, offset);
+
+ // Number of bytes actually read
+ got = array_uint32_le(result+4);
+ if (!got)
+ break;
+ if (rc < 8 + got)
+ return report_error("odd read size reply for offset %d of file %s", offset, filename);
+
+ if (got > size)
+ got = size;
+ dc_buffer_append(buf, result+8, got);
+ offset += got;
+ size -= got;
+ }
+
+ rc = send_receive(eon, FILE_CLOSE_CMD,
+ 0, "",
+ sizeof(result), result);
+ if (rc < 0)
+ report_error("cmd 0510 failed");
+// 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(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) {
+ report_error("corrupt dirent entry");
+ break;
+ }
+// debug("dir entry", p, 8);
+ p += 8 + namelen + 1;
+ len -= 8 + namelen + 1;
+ entry = alloc_dirent(type, namelen, name);
+ entry->next = old;
+ old = entry;
+ }
+ return old;
+}
+
+static int get_file_list(struct eonsteel *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);
+ strcpy(cmd+4, dive_directory);
+ cmdlen = 4+strlen(dive_directory)+1;
+ rc = send_receive(eon, DIR_LOOKUP_CMD,
+ cmdlen, cmd,
+ sizeof(result), result);
+ if (rc < 0)
+ report_error("cmd DIR_LOOKUP failed");
+// debug("DIR_LOOKUP", result, rc);
+
+ for (;;) {
+ unsigned int nr, last;
+
+ rc = send_receive(eon, READDIR_CMD,
+ 0, "",
+ sizeof(result), result);
+ if (rc < 0)
+ return report_error("readdir failed");
+ if (rc < 8)
+ return report_error("short readdir result");
+ nr = array_uint32_le(result);
+ last = array_uint32_le(result+4);
+// debug("dir packet", result, 8);
+
+ de = parse_dirent(nr, result+8, rc-8, de);
+ if (last)
+ break;
+ }
+
+ rc = send_receive(eon, DIR_CLOSE_CMD,
+ 0, "",
+ sizeof(result), result);
+ if (rc < 0)
+ report_error("dir close failed");
+
+ *res = de;
+ return 0;
+}
+
+
+static dc_status_t eonsteel_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback, void *userdata);
+static dc_status_t eonsteel_device_close(dc_device_t *abstract);
+
+static const dc_device_vtable_t eonsteel_device_vtable = {
+ DC_FAMILY_SUUNTO_EONSTEEL,
+ NULL, /* set_fingerprint */
+ NULL, /* read */
+ NULL, /* write */
+ NULL, /* dump */
+ eonsteel_device_foreach, /* foreach */
+ eonsteel_device_close /* close */
+};
+
+static int initialize_eonsteel(struct eonsteel *eon)
+{
+ const int InEndpoint = 0x82;
+ 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, INIT_LEN, INIT_DATA))
+ return report_error("Failed to send initialization command");
+ if (receive_data(eon, buf, sizeof(buf)) < 0)
+ return report_error("Failed to receive initial reply");
+
+ // 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)
+{
+ struct eonsteel *eon;
+
+ if (out == NULL)
+ return DC_STATUS_INVALIDARGS;
+
+ eon = calloc(1, sizeof(struct eonsteel));
+ 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, &eonsteel_device_vtable);
+
+ if (libusb_init(&eon->ctx)) {
+ ERROR(context, "libusb_init() failed");
+ return DC_STATUS_IO;
+ }
+
+ eon->handle = libusb_open_device_with_vid_pid(eon->ctx, 0x1493, 0x0030);
+ if (!eon->handle) {
+ ERROR(context, "unable to open EON Steel device (%s)", strerror(errno));
+ libusb_exit(eon->ctx);
+ return DC_STATUS_IO;
+ }
+
+ libusb_set_auto_detach_kernel_driver(eon->handle, 1);
+ libusb_claim_interface(eon->handle, 0);
+
+ if (initialize_eonsteel(eon) < 0) {
+ ERROR(context, "unable to initialize EON Steel device (%s)", strerror(errno));
+ libusb_exit(eon->ctx);
+ 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 eonsteel_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
+{
+ int skip = 0, rc;
+ struct directory_entry *de;
+ struct eonsteel *eon = (struct eonsteel *) 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 eonsteel_device_close(dc_device_t *abstract)
+{
+ struct eonsteel *eon = (struct eonsteel *) 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
diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c
new file mode 100644
index 0000000..1f75395
--- /dev/null
+++ b/src/suunto_eonsteel_parser.c
@@ -0,0 +1,532 @@
+/*
+ * 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#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
+
+struct eon_parser {
+ 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;
+};
+
+
+static int report_error(const char *fmt, ...)
+{
+ char buffer[128];
+ va_list args;
+
+ va_start(args, fmt);
+ vsnprintf(buffer, sizeof(buffer)-1, fmt, args);
+ va_end(args);
+ fprintf(stderr, "%.*s\n", (int)sizeof(buffer), buffer);
+ return -1;
+}
+
+static unsigned char get_u8(const void *src)
+{
+ return *(const unsigned char *)src;
+}
+
+static void debug(const char *name, const char *buf, int len)
+{
+ int i;
+
+ fprintf(stderr, "%4d %s:", len, name);
+ for (i = 0; i < len; i++)
+ fprintf(stderr, " %02x", (unsigned char) buf[i]);
+ fprintf(stderr, "\n");
+}
+
+static void debug_text(const char *name, const char *buf, int len)
+{
+ int i;
+
+ printf("text of %s:\n", name);
+ for (i = 0; i < len; i++) {
+ unsigned char c = buf[i];
+ if (c > 31 && c < 127)
+ putchar(c);
+ else if (c == '\n') {
+ putchar('\\');
+ putchar('n');
+ } else {
+ static const char hex[16]="0123456789abcdef";
+ putchar('\\');
+ putchar('x');
+ putchar(hex[c>>4]);
+ putchar(hex[c&15]);
+ }
+ }
+ printf("\nend of text\n");
+}
+
+typedef int (*eon_data_cb_t)(unsigned short type, const struct type_desc *desc, const void *data, int len, void *user);
+
+static int record_type(struct eon_parser *eon, unsigned short type, const char *name, int namelen)
+{
+ struct type_desc desc;
+ const char *next;
+
+ desc.desc = desc.format = desc.mod = "";
+ 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] != '>')
+ return report_error("Unexpected type description: %.*s", len, name);
+ p = malloc(len-4);
+ if (!p)
+ return report_error("out of memory");
+ 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:
+ return report_error("Unknown type descriptor: %.*s", len, name);
+ }
+ } while ((name = next) != NULL);
+
+ if (type > MAXTYPE)
+ return report_error("Type out of range (%04x: '%s' '%s' '%s')",
+ type, desc.desc, desc.format, desc.mod);
+
+ eon->type_desc[type] = desc;
+ return 0;
+}
+
+static int traverse_entry(struct eon_parser *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]) {
+ debug("next", p, 8);
+ return report_error("Bad dive entry (%02x)", p[0]);
+ }
+ 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 != '<') {
+ fflush(NULL);
+ debug("bad", p, 16);
+ exit(1);
+ }
+
+ record_type(eon, type, 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) {
+ debug("len-ff", end, 8);
+ len = array_uint32_le(end);
+ end += 4;
+ }
+
+ if (type > MAXTYPE || !eon->type_desc[type].desc) {
+ debug("last", last, 16);
+ 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(struct eon_parser *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 {
+ struct eon_parser *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 void *data, int len, void *user)
+{
+ struct sample_data *info = 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, get_u8(data), array_uint16_le(data+1));
+ break;
+ }
+ return 0;
+}
+
+static dc_status_t eonsteel_parser_samples_foreach(dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
+{
+ struct eon_parser *eon = (void *)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 eonsteel_parser_get_field(dc_parser_t *parser, dc_field_type_t type, unsigned int flags, void *value)
+{
+ struct eon_parser *eon = (struct eon_parser *)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;
+ }
+ 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 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(struct eon_parser *eon, unsigned short time_delta_ms)
+{
+ eon->cache.divetime += time_delta_ms;
+}
+
+// depth in cm
+static void set_depth_field(struct eon_parser *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(struct eon_parser *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(struct eon_parser *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(struct eon_parser *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 void *data, int len, void *user)
+{
+ struct eon_parser *eon = 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, get_u8(data));
+ break;
+ case 0x000e: // Oxygen percentage in first byte
+ add_gas_o2(eon, get_u8(data));
+ break;
+ case 0x000f: // Helium percentage in first byte
+ add_gas_he(eon, get_u8(data));
+ break;
+ }
+ return 0;
+}
+
+
+static void initialize_field_caches(struct eon_parser *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 eonsteel_parser_set_data(dc_parser_t *parser, const unsigned char *data, unsigned int size)
+{
+ struct eon_parser *eon = (void *)parser;
+ memset(eon->type_desc, 0, sizeof(eon->type_desc));
+ initialize_field_caches(eon);
+ return DC_STATUS_SUCCESS;
+}
+
+static dc_status_t eonsteel_parser_destroy(dc_parser_t *parser)
+{
+ free(parser);
+ return DC_STATUS_SUCCESS;
+}
+
+static const dc_parser_vtable_t eonsteel_parser_vtable = {
+ DC_FAMILY_SUUNTO_EONSTEEL,
+ eonsteel_parser_set_data, /* set_data */
+ eonsteel_parser_get_datetime, /* datetime */
+ eonsteel_parser_get_field, /* fields */
+ eonsteel_parser_samples_foreach, /* samples_foreach */
+ eonsteel_parser_destroy /* destroy */
+};
+
+dc_status_t suunto_eonsteel_parser_create(dc_parser_t **out, dc_context_t *context, unsigned int model)
+{
+ struct eon_parser *eon;
+
+ if (out == NULL)
+ return DC_STATUS_INVALIDARGS;
+
+ eon = calloc(1, sizeof(*eon));
+
+ parser_init(&eon->base, context, &eonsteel_parser_vtable);
+
+ *out = (dc_parser_t *) eon;
+
+ return DC_STATUS_SUCCESS;
+}