Ok, this is a re-done set of patches that uses DC_FIELD_STRING instead
of DC_FIELD_DEVINFO.

I've also changed "eon_steel" to "eonsteel" throughout (unless I
missed something) as per Jef.

Also, to show how DC_FIELD_STRING allows other things, I added "Deco
algorithm", "Battery before dive" and "Battery after dive" parsing to
the EON Steel backend, although right now my subsurface patch just
simply ignores that extra data. We'll need a way to show it, or at
least save/restore it sanely, so that will take a bit of effort. But
it's there, and it works (tested by printing the values out to stderr
- that "debugging" is not in this series of patches, though).

Anyway, I think Dirk was right, the DC_FIELD_STRING model is nicer
than showing it as samples, although it did make the subsurface
interface a bit different: since we don't get all the version and dive
ID data together as one "devinfo", we actually need to save them off
individually and then at the end add it to our dive computer database.
But that's probably a good idea anyway, since we very likely *should*
save at least fw version per-dive rather than per divecomputer (since
the same dive computer can have multiple firmware versions over time).

Whatever. The subsurface patch is still pretty small and
straightforward, although to save/show generic strings will definitely
take more effort. But battery information is apparently something
people have been asking for, so I think it's all good.

I could have done inter-diffs from the previous version, but with the
big renames of "eon_steel" to "eonsteel", that diff wouldn't really
have been very useful anyway, and it's not like the previous version
was applied, so this is an all-new series. Three patches for
libdivecomputer, and one patch for subsurface.

                  Linus
From b317a599e09a997d0cffe924463a45ad82a0d90d Mon Sep 17 00:00:00 2001
From: Linus Torvalds <torva...@linux-foundation.org>
Date: Mon, 20 Oct 2014 10:34:37 -0700
Subject: [PATCH 1/3] Suunto EON Steel: support downloading of core dive
 profile data

Basic Suunto EON Steel downloading copied from my test application.
This parses all the core dive data, including sample data (time, depth,
cylinder pressure, deco information etc).

The deco information returns ceiling and TTS rather than ceiling and
"time at ceiling", because that's what the dive computer has, and I
don't see any other way to return the information.

We don't report any events yet, though.

Signed-off-by: Linus Torvalds <torva...@linux-foundation.org>
---
 examples/universal.c                      |   1 +
 include/libdivecomputer/common.h          |   1 +
 include/libdivecomputer/suunto.h          |   1 +
 include/libdivecomputer/suunto_eonsteel.h |  39 ++
 msvc/libdivecomputer.vcproj               |   8 +
 src/Makefile.am                           |   1 +
 src/descriptor.c                          |   6 +
 src/device.c                              |   3 +
 src/parser.c                              |   3 +
 src/suunto_eonsteel.c                     | 614 ++++++++++++++++++++++++++++++
 src/suunto_eonsteel_parser.c              | 532 ++++++++++++++++++++++++++
 11 files changed, 1209 insertions(+)
 create mode 100644 include/libdivecomputer/suunto_eonsteel.h
 create mode 100644 src/suunto_eonsteel.c
 create mode 100644 src/suunto_eonsteel_parser.c

diff --git a/examples/universal.c b/examples/universal.c
index 1e0307b25576..315259708a0e 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 983ec0716861..4bcc7009986b 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 337dffd6f42f..3ff7c887ecee 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 000000000000..d12387c184f0
--- /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 78a8190ccda2..b1aaa2b0bd75 100644
--- a/msvc/libdivecomputer.vcproj
+++ b/msvc/libdivecomputer.vcproj
@@ -395,6 +395,14 @@
 				>
 			</File>
 			<File
+				RelativePath="..\src\suunto_eonsteel.c"
+				>
+			</File>
+			<File
+				RelativePath="..\src\suunto_eonsteel_parser.c"
+				>
+			</File>
+			<File
 				RelativePath="..\src\suunto_eon.c"
 				>
 			</File>
diff --git a/src/Makefile.am b/src/Makefile.am
index 595f0c7dc1c0..458f1dd911e8 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 2845af10e74b..4209318f4821 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},
@@ -339,6 +343,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 9fad7a238bc4..edc1cf2d397c 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/parser.c b/src/parser.c
index 7cc7f4ed2cfd..a17a58619f73 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 000000000000..328a8e949088
--- /dev/null
+++ b/src/suunto_eonsteel.c
@@ -0,0 +1,614 @@
+/*
+ * 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 <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libdivecomputer/suunto_eonsteel.h>
+
+#include "context-private.h"
+#include "device-private.h"
+#include "array.h"
+
+#ifdef HAVE_LIBUSB
+
+#include <libusb-1.0/libusb.h>
+
+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)
+{
+	int rc;
+	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);
+	*out = (void *) eon;
+
+	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;
+	}
+
+	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;
+		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 000000000000..1d035b93f7e9
--- /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 <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.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
+
+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 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)
+{
+	unsigned int t;
+
+	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 = &eon->base;
+
+	return DC_STATUS_SUCCESS;
+}
-- 
2.1.2.452.gd5ca7ae

From 869440d6b464707c506824ed92b3efe7d9ea6eb1 Mon Sep 17 00:00:00 2001
From: Linus Torvalds <torva...@linux-foundation.org>
Date: Wed, 22 Oct 2014 11:23:09 -0700
Subject: [PATCH 2/3] parser: add DC_FIELD_STRING field type for parse-time
 information

This can be used to return almost arbitrary information to the dive log
application at dive parse time, by returning a number of strings (with a
descriptor) for dive state.

NOTE! The strings are supposed to be human-readable, so that the dive
log application can just show them unedited - and without understanding
them - to the user, together with the description.  So if your dive
computer supports returning a battery voltage, for example, you can
return it as a

  { "Voltage", "4.449V" }

descriptor/value string pair, and the application could then put these
string pairs together and show (somewhere) an informational line like
"Voltage: 4.449V" along with the other information you return.

Some dive log applications migth recognize particular descriptor strings
and use them specially to fill in other information (ie serial numbers,
weight and suit information etc), but in general the interface is very
much meant to be informational free-form for a human user.

So do *not* use this interface to encode things that are not human-
readable.  Serial numbers or version information that is meaningful to
humans are fine. But random encoded data structures are not.

Signed-off-by: Linus Torvalds <torva...@linux-foundation.org>
---
 include/libdivecomputer/parser.h | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/include/libdivecomputer/parser.h b/include/libdivecomputer/parser.h
index 65b18c9bb2fe..a3e6a35994ab 100644
--- a/include/libdivecomputer/parser.h
+++ b/include/libdivecomputer/parser.h
@@ -53,9 +53,13 @@ typedef enum dc_field_type_t {
 	DC_FIELD_GASMIX_COUNT,
 	DC_FIELD_GASMIX,
 	DC_FIELD_SALINITY,
-	DC_FIELD_ATMOSPHERIC
+	DC_FIELD_ATMOSPHERIC,
+	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,
@@ -128,6 +132,11 @@ typedef struct dc_gasmix_t {
 	double nitrogen;
 } dc_gasmix_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;
-- 
2.1.2.452.gd5ca7ae

From 248d9fed29638d79cd9e31a6f924939ecddcbe99 Mon Sep 17 00:00:00 2001
From: Linus Torvalds <torva...@linux-foundation.org>
Date: Mon, 27 Oct 2014 17:19:48 -0700
Subject: [PATCH 3/3] Suunto EON Steel: populate dive surface pressure, and
 various string data

The EON Steel saves the dive computer firmware versions etc per dive,
which is really nice for upgrades: old dives done with older firmware
still show the firmware at the time of the dive.  That, in turn, is nice
because we can use it for a reliable dive ID - dive time with serial
number etc.

This uses the new DC_FIELD_STRING model to feed the hw/sw information to
the application, since we need the parser to access it.  It also returns
battery state and deco model information.

Signed-off-by: Linus Torvalds <torva...@linux-foundation.org>
---
 src/suunto_eonsteel_parser.c | 153 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 152 insertions(+), 1 deletion(-)

diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c
index 1d035b93f7e9..29be4ee1e3df 100644
--- a/src/suunto_eonsteel_parser.c
+++ b/src/suunto_eonsteel_parser.c
@@ -41,6 +41,7 @@ struct type_desc {
 
 #define MAXTYPE 512
 #define MAXGASES 16
+#define MAXSTRINGS 16
 
 struct eon_parser {
 	dc_parser_t base;
@@ -55,6 +56,7 @@ struct eon_parser {
 		dc_gasmix_t gasmix[MAXGASES];
 		dc_salinity_t salinity;
 		double surface_pressure;
+		dc_field_string_t strings[MAXSTRINGS];
 	} cache;
 };
 
@@ -76,6 +78,17 @@ static unsigned char get_u8(const void *src)
 	return *(const unsigned char *)src;
 }
 
+static float get_le32_float(const void *src)
+{
+	union {
+		unsigned int val;
+		float result;
+	} u;
+
+	u.val = array_uint32_le(src);
+	return u.result;
+}
+
 static void debug(const char *name, const char *buf, int len)
 {
 	int i;
@@ -349,11 +362,23 @@ static dc_status_t eonsteel_parser_samples_foreach(dc_parser_t *abstract, dc_sam
 {
 	struct eon_parser *eon = (void *)abstract;
 	struct sample_data data = { eon, callback, userdata, 0 };
-
 	traverse_data(eon, traverse_samples, &data);
 	return DC_STATUS_SUCCESS;
 }
 
+static dc_status_t get_string_field(struct eon_parser *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) \
@@ -390,6 +415,8 @@ static dc_status_t eonsteel_parser_get_field(dc_parser_t *parser, dc_field_type_
 	case DC_FIELD_ATMOSPHERIC:
 		field_value(value, eon->cache.surface_pressure);
 		break;
+	case DC_FIELD_STRING:
+		return get_string_field(eon, flags, (dc_field_string_t *)value);
 	}
 	return DC_STATUS_SUCCESS;
 }
@@ -453,6 +480,123 @@ static void add_gas_he(struct eon_parser *eon, unsigned char he)
 	eon->cache.initialized |= 1 << DC_FIELD_GASMIX;
 }
 
+static int add_string(struct eon_parser *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;
+}
+
+// "Device" fields are all utf8:
+//   Info.BatteryAtEnd
+//   Info.BatteryAtStart
+//   Info.BSL
+//   Info.HW
+//   Info.SW
+//   Name
+//   SerialNumber
+static int traverse_device_fields(struct eon_parser *eon, const char *name, const void *data, int len)
+{
+	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;
+}
+
+// "Header" fields are:
+//   Activity (utf8)
+//   DateTime (utf8)
+//   Depth.Avg (float32,precision=2)
+//   Depth.Max (float32,precision=2)
+//   Diving.AlgorithmAscentTime (uint32)
+//   Diving.AlgorithmBottomMixture.Helium (uint8,precision=2) (0.01*x,100*x)
+//   Diving.AlgorithmBottomMixture.Oxygen (uint8,precision=2) (0.01*x,100*x)
+//   Diving.AlgorithmBottomTime (uint32)
+//   Diving.AlgorithmTransitionDepth (uint8)
+//   Diving.Algorithm (utf8)
+//   Diving.Altitude (uint16)
+//   Diving.Conservatism (int8)
+//   Diving.DaysInSeries (uint32)
+//   Diving.DesaturationTime (uint32)
+//   Diving.DiveMode (utf8)
+//   Diving.EndTissue.CNS (float32,precision=3)
+//   Diving.EndTissue.Helium+Pressure (uint32)
+//   Diving.EndTissue.Nitrogen+Pressure (uint32)
+//   Diving.EndTissue.OLF (float32,precision=3)
+//   Diving.EndTissue.OTU (float32)
+//   Diving.EndTissue.RgbmHelium (float32,precision=3)
+//   Diving.EndTissue.RgbmNitrogen (float32,precision=3)
+//   Diving.NumberInSeries (uint32)
+//   Diving.PreviousDiveDepth (float32,precision=2)
+//   Diving.StartTissue.CNS (float32,precision=3)
+//   Diving.StartTissue.Helium+Pressure (uint32)
+//   Diving.StartTissue.Nitrogen+Pressure (uint32)
+//   Diving.StartTissue.OLF (float32,precision=3)
+//   Diving.StartTissue.OTU (float32)
+//   Diving.StartTissue.RgbmHelium (float32,precision=3)
+//   Diving.StartTissue.RgbmNitrogen (float32,precision=3)
+//   Diving.SurfacePressure (uint32)
+//   Diving.SurfaceTime (uint32)
+//   Duration (uint32)
+//   PauseDuration (uint32)
+//   SampleInterval (uint8)
+static int traverse_header_fields(struct eon_parser *eon, const char *name, const void *data, int len)
+{
+	if (!strcmp(name, "Depth.Max")) {
+		double d = get_le32_float(data);
+		if (d > eon->cache.maxdepth)
+			eon->cache.maxdepth = d;
+		return 0;
+	}
+	if (!strcmp(name, "Diving.SurfacePressure")) {
+		unsigned int pressure = array_uint32_le(data); // in SI units - Pascal
+		eon->cache.surface_pressure = pressure / 100000.0; // bar
+		eon->cache.initialized |= 1 << DC_FIELD_ATMOSPHERIC;
+		return 0;
+	}
+	if (!strcmp(name, "DateTime"))
+		return add_string(eon, "Dive ID", data);
+
+	if (!strcmp(name, "Diving.Algorithm"))
+		return add_string(eon, "Deco algorithm", data);
+
+	return 0;
+}
+
+static int traverse_dynamic_fields(struct eon_parser *eon, const struct type_desc *desc, const void *data, int len)
+{
+	const char *name = desc->desc;
+
+	if (!strncmp(name, "sml.", 4)) {
+		name += 4;
+		if (!strncmp(name, "DeviceLog.", 10)) {
+			name += 10;
+			if (!strncmp(name, "Device.", 7))
+				return traverse_device_fields(eon, name+7, data, len);
+			if (!strncmp(name, "Header.", 7)) {
+				return traverse_header_fields(eon, name+7, data, len);
+			}
+		}
+	}
+	return 0;
+}
+
 static int traverse_fields(unsigned short type, const struct type_desc *desc, const void *data, int len, void *user)
 {
 	struct eon_parser *eon = user;
@@ -477,6 +621,13 @@ static int traverse_fields(unsigned short type, const struct type_desc *desc, co
 	case 0x000f: // Helium percentage in first byte
 		add_gas_he(eon, get_u8(data));
 		break;
+	default:
+	// The types with the high byte set seem to be dynamic
+	// although not all of them seem to change. But let's
+	// just check the descriptor name for them.
+		if (type > 255)
+			traverse_dynamic_fields(eon, desc, data, len);
+		break;
 	}
 	return 0;
 }
-- 
2.1.2.452.gd5ca7ae

From c5ba078d1aa828dc977cb4fcbe669ce81f29ce6a Mon Sep 17 00:00:00 2001
From: Linus Torvalds <torva...@linux-foundation.org>
Date: Wed, 22 Oct 2014 12:11:12 -0700
Subject: [PATCH] Use the new DC_FIELD_STRING callback if it exists

Right now we don't have any good way to save the results, but we
recognize some strigns (serial number and firmware version), and we show
the rest as "informational errors".

Signed-off-by: Linus Torvalds <torva...@linux-foundation.org>
---
 dive.c            |  4 ++++
 dive.h            |  2 +-
 libdivecomputer.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 52 insertions(+), 1 deletion(-)

diff --git a/dive.c b/dive.c
index 9f4df56dc7d9..d412481185f8 100644
--- a/dive.c
+++ b/dive.c
@@ -1110,6 +1110,10 @@ static void fixup_dive_dc(struct dive *dive, struct divecomputer *dc)
 	int pressure_delta[MAX_CYLINDERS] = { INT_MAX, };
 	int first_cylinder;
 
+	/* Add device information to table */
+	if (dc->deviceid && (dc->serial || dc->fw_version))
+		create_device_node(dc->model, dc->deviceid, dc->serial, dc->fw_version, "");
+
 	/* Fixup duration and mean depth */
 	fixup_dc_duration(dc);
 	update_min_max_temperatures(dive, dc->watertemp);
diff --git a/dive.h b/dive.h
index 6809d83844f6..25103df462b2 100644
--- a/dive.h
+++ b/dive.h
@@ -253,7 +253,7 @@ struct divecomputer {
 	enum dive_comp_type dctype;	// dive computer type: OC(default) or CCR
 	uint8_t no_o2sensors;		// rebreathers: number of O2 sensors used
 	int salinity; 			// kg per 10000 l
-	const char *model;
+	const char *model, *serial, *fw_version;
 	uint32_t deviceid, diveid;
 	int samples, alloc_samples;
 	struct sample *sample;
diff --git a/libdivecomputer.c b/libdivecomputer.c
index ff8d0eb93bda..4675fa2acbf7 100644
--- a/libdivecomputer.c
+++ b/libdivecomputer.c
@@ -397,6 +397,39 @@ static uint32_t calculate_diveid(const unsigned char *fingerprint, unsigned int
 	return csum[0];
 }
 
+static uint32_t calculate_string_hash(const char *str)
+{
+	return calculate_diveid(str, strlen(str));
+}
+
+#ifdef DC_FIELD_STRING
+static void parse_string_field(struct dive *dive, dc_field_string_t *str)
+{
+	// Our dive ID is the string hash of the "Dive ID" string
+	if (!strcmp(str->desc, "Dive ID")) {
+		if (!dive->dc.diveid)
+			dive->dc.diveid = calculate_string_hash(str->value);
+		return;
+	}
+
+	if (!strcmp(str->desc, "Serial")) {
+		if (!dive->dc.serial)
+			dive->dc.serial = strdup(str->value);
+		if (!dive->dc.deviceid)
+			dive->dc.deviceid = calculate_string_hash(str->value);
+		return;
+	}
+
+	if (!strcmp(str->desc, "FW Version")) {
+		if (!dive->dc.fw_version)
+			dive->dc.fw_version = strdup(str->value);
+		return;
+	}
+
+	// FIXME - we don't actually save the battery info etc rigth now
+}
+#endif
+
 /* returns true if we want libdivecomputer's dc_device_foreach() to continue,
  *  false otherwise */
 static int dive_cb(const unsigned char *data, unsigned int size,
@@ -494,6 +527,20 @@ static int dive_cb(const unsigned char *data, unsigned int size,
 	dive->dc.surface_pressure.mbar = rint(surface_pressure * 1000.0);
 #endif
 
+#ifdef DC_FIELD_STRING
+	// The dive parsing may give us more device information
+	int idx;
+	for (idx = 0; idx < 100; idx++) {
+		dc_field_string_t str = { NULL };
+		rc = dc_parser_get_field(parser, DC_FIELD_STRING, idx, &str);
+		if (rc != DC_STATUS_SUCCESS)
+			break;
+		if (!str.desc || !str.value)
+			break;
+		parse_string_field(dive, &str);
+	}
+#endif
+
 	rc = parse_gasmixes(devdata, dive, parser, ngases, data);
 	if (rc != DC_STATUS_SUCCESS) {
 		dev_info(devdata, translate("gettextFromC", "Error parsing the gas mix"));
-- 
2.1.2.452.gd5ca7ae

_______________________________________________
subsurface mailing list
subsurface@subsurface-divelog.org
http://lists.subsurface-divelog.org/cgi-bin/mailman/listinfo/subsurface

Reply via email to