diff --git a/examples/common.c b/examples/common.c
index d8b4d0d..a4927cd 100644
--- a/examples/common.c
+++ b/examples/common.c
@@ -93,6 +93,7 @@ static const backend_table_t g_backends[] = {
{"descentmk1", DC_FAMILY_GARMIN, 0},
{"cosmiq", DC_FAMILY_DEEPBLU, 0},
{"mclean", DC_FAMILY_MCLEAN_EXTREME, 0},
+ {"oceans", DC_FAMILY_OCEANS_S1, 0},
};
static const transport_table_t g_transports[] = {
diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h
index d09cd47..065925e 100644
--- a/include/libdivecomputer/common.h
+++ b/include/libdivecomputer/common.h
@@ -114,6 +114,8 @@ typedef enum dc_family_t {
DC_FAMILY_GARMIN = (17 << 16),
/* Deepblu */
DC_FAMILY_DEEPBLU = (18 << 16),
+ /* Oceans S1 */
+ DC_FAMILY_OCEANS_S1 = (19 << 16),
} dc_family_t;
#ifdef __cplusplus
diff --git a/msvc/libdivecomputer.vcproj b/msvc/libdivecomputer.vcproj
index daac0ef..91f015d 100644
--- a/msvc/libdivecomputer.vcproj
+++ b/msvc/libdivecomputer.vcproj
@@ -530,6 +530,14 @@
RelativePath="..\src\mclean_extreme_parser.c"
>
+
+
+
+
@@ -892,6 +900,10 @@
RelativePath="..\src\mclean_extreme.h"
>
+
+
diff --git a/src/Makefile.am b/src/Makefile.am
index 8c6ec5e..37da7e8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -76,6 +76,7 @@ libdivecomputer_la_SOURCES = \
garmin.h garmin.c garmin_parser.c \
deepblu.h deepblu.c deepblu_parser.c \
mclean_extreme.h mclean_extreme.c mclean_extreme_parser.c \
+ oceans_s1.h oceans_s1.c oceans_s1_parser.c \
socket.h socket.c \
irda.c \
usbhid.c \
diff --git a/src/descriptor.c b/src/descriptor.c
index 3cec2fe..bd4458d 100644
--- a/src/descriptor.c
+++ b/src/descriptor.c
@@ -51,6 +51,7 @@ static int dc_filter_divesystem (dc_transport_t transport, const void *userdata)
static int dc_filter_oceanic (dc_transport_t transport, const void *userdata);
static int dc_filter_deepblu (dc_transport_t transport, const void *userdata);
static int dc_filter_mclean (dc_transport_t transport, const void *userdata);
+static int dc_filter_oceans(dc_transport_t transport, const void *userdata);
static dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item);
@@ -385,6 +386,8 @@ static const dc_descriptor_t g_descriptors[] = {
{"Deepblu", "Cosmiq+", DC_FAMILY_DEEPBLU, 0, DC_TRANSPORT_BLE, dc_filter_deepblu},
/* McLean Extreme */
{ "McLean", "Extreme", DC_FAMILY_MCLEAN_EXTREME, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_mclean},
+ /* Oceans S1 */
+ { "Oceans", "S1", DC_FAMILY_OCEANS_S1, 0, DC_TRANSPORT_BLE, dc_filter_oceans },
};
static int
@@ -679,6 +682,19 @@ static int dc_filter_mclean(dc_transport_t transport, const void *userdata)
return 1;
}
+static int dc_filter_oceans(dc_transport_t transport, const void* userdata)
+{
+ static const char* const ble[] = {
+ "S1",
+ };
+
+ if (transport == DC_TRANSPORT_BLE) {
+ return DC_FILTER_INTERNAL(userdata, ble, 0, dc_match_prefix);
+ }
+
+ return 1;
+}
+
dc_status_t
dc_descriptor_iterator (dc_iterator_t **out)
{
diff --git a/src/device.c b/src/device.c
index 0a3d4e4..17cc4ed 100644
--- a/src/device.c
+++ b/src/device.c
@@ -60,6 +60,7 @@
#include "garmin.h"
#include "deepblu.h"
#include "mclean_extreme.h"
+#include "oceans_s1.h"
#include "device-private.h"
#include "context-private.h"
@@ -223,6 +224,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr
case DC_FAMILY_MCLEAN_EXTREME:
rc = mclean_extreme_device_open (&device, context, iostream);
break;
+ case DC_FAMILY_OCEANS_S1:
+ rc = oceans_s1_device_open(&device, context, iostream);
+ break;
default:
return DC_STATUS_INVALIDARGS;
}
diff --git a/src/field-cache.c b/src/field-cache.c
index 890320a..ddb706a 100644
--- a/src/field-cache.c
+++ b/src/field-cache.c
@@ -62,3 +62,41 @@ dc_status_t dc_field_get_string(dc_field_cache_t *cache, unsigned idx, dc_field_
}
return DC_STATUS_UNSUPPORTED;
}
+
+
+/*
+ * Use this generic "pick fields from the field cache" helper
+ * after you've handled all the ones you do differently
+ */
+dc_status_t
+dc_field_get(dc_field_cache_t *cache, dc_field_type_t type, unsigned int flags, void* value)
+{
+ if (!(cache->initialized & (1 << type)))
+ return DC_STATUS_UNSUPPORTED;
+
+ switch (type) {
+ case DC_FIELD_DIVETIME:
+ return DC_FIELD_VALUE(*cache, value, DIVETIME);
+ case DC_FIELD_MAXDEPTH:
+ return DC_FIELD_VALUE(*cache, value, MAXDEPTH);
+ case DC_FIELD_AVGDEPTH:
+ return DC_FIELD_VALUE(*cache, value, AVGDEPTH);
+ case DC_FIELD_GASMIX_COUNT:
+ case DC_FIELD_TANK_COUNT:
+ return DC_FIELD_VALUE(*cache, value, GASMIX_COUNT);
+ case DC_FIELD_GASMIX:
+ if (flags >= MAXGASES)
+ break;
+ return DC_FIELD_INDEX(*cache, value, GASMIX, flags);
+ case DC_FIELD_SALINITY:
+ return DC_FIELD_VALUE(*cache, value, SALINITY);
+ case DC_FIELD_DIVEMODE:
+ return DC_FIELD_VALUE(*cache, value, DIVEMODE);
+ case DC_FIELD_STRING:
+ return dc_field_get_string(cache, flags, (dc_field_string_t *)value);
+ default:
+ break;
+ }
+
+ return DC_STATUS_UNSUPPORTED;
+}
diff --git a/src/field-cache.h b/src/field-cache.h
index ec8a7f4..6f5c487 100644
--- a/src/field-cache.h
+++ b/src/field-cache.h
@@ -36,6 +36,7 @@ typedef struct dc_field_cache {
dc_status_t dc_field_add_string(dc_field_cache_t *, const char *desc, const char *data);
dc_status_t dc_field_add_string_fmt(dc_field_cache_t *, const char *desc, const char *fmt, ...);
dc_status_t dc_field_get_string(dc_field_cache_t *, unsigned idx, dc_field_string_t *value);
+dc_status_t dc_field_get(dc_field_cache_t *, dc_field_type_t, unsigned int, void *);
/*
* Macro to make it easy to set DC_FIELD_xyz values.
diff --git a/src/oceans_s1.c b/src/oceans_s1.c
new file mode 100644
index 0000000..e70e6ee
--- /dev/null
+++ b/src/oceans_s1.c
@@ -0,0 +1,649 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// Copyright (C) 2020 Linus Torvalds
+
+#include // memcmp, memcpy
+#include // malloc, free
+#include
+#include
+#include
+#include
+
+#include "oceans_s1.h"
+#include "context-private.h"
+#include "device-private.h"
+#include "array.h"
+
+#define S1_FINGERPRINT 32
+
+typedef struct oceans_s1_device_t {
+ dc_device_t base;
+ dc_iostream_t* iostream;
+ unsigned char fingerprint[S1_FINGERPRINT];
+} oceans_s1_device_t;
+
+static dc_status_t oceans_s1_device_set_fingerprint(dc_device_t *abstract, const unsigned char data[], unsigned int size);
+static dc_status_t oceans_s1_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback, void *userdata);
+static dc_status_t oceans_s1_device_close(dc_device_t *abstract);
+static dc_status_t oceans_s1_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime);
+
+static const dc_device_vtable_t oceans_s1_device_vtable = {
+ sizeof(oceans_s1_device_t),
+ DC_FAMILY_OCEANS_S1,
+ oceans_s1_device_set_fingerprint, /* set_fingerprint */
+ NULL, /* read */
+ NULL, /* write */
+ NULL, /* dump */
+ oceans_s1_device_foreach, /* foreach */
+ oceans_s1_device_timesync, /* timesync */
+ oceans_s1_device_close, /* close */
+};
+
+static dc_status_t
+oceans_s1_write(oceans_s1_device_t *s1, const char *msg)
+{
+ return dc_iostream_write(s1->iostream, msg, strlen(msg), NULL);
+}
+
+static dc_status_t
+oceans_s1_read(oceans_s1_device_t *s1, char *buf, size_t bufsz)
+{
+ size_t nbytes;
+ dc_status_t status;
+
+ status = dc_iostream_read(s1->iostream, buf, bufsz, &nbytes);
+ if (status != DC_STATUS_SUCCESS)
+ return status;
+ if (nbytes < bufsz)
+ buf[nbytes] = 0;
+ return status;
+}
+
+#define BUFSZ 64
+
+// Note how we don't rely on the return value of 'vsnprintf(), or on
+// NUL termination because it's not portable.
+static dc_status_t oceans_s1_printf(oceans_s1_device_t *s1, const char *fmt, ...)
+{
+ va_list ap;
+ char buffer[BUFSZ];
+
+ va_start(ap, fmt);
+ vsnprintf(buffer, BUFSZ, fmt, ap);
+ va_end(ap);
+ buffer[BUFSZ-1] = 0;
+
+ return oceans_s1_write(s1, buffer);
+}
+
+static dc_status_t oceans_s1_expect(oceans_s1_device_t *s1, const char *result)
+{
+ char buffer[BUFSZ];
+ dc_status_t status;
+
+ status = oceans_s1_read(s1, buffer, BUFSZ);
+ if (status != DC_STATUS_SUCCESS)
+ return status;
+
+ if (strncmp(buffer, result, strlen(result))) {
+ ERROR(s1->base.context, "Expected '%s' got '%s'", result, buffer);
+ return DC_STATUS_IO;
+ }
+
+ return DC_STATUS_SUCCESS;
+}
+
+/*
+ * The "blob mode" is sends stuff in bigger chunks with some binary
+ * header and trailer.
+ *
+ * It seems to be a sequence of packets with 517 bytes of payload:
+ * three bytes of header, 512 bytes of ASCII data, and two bytes of
+ * trailer (data checksum?).
+ *
+ * We're supposed to start the sequence with a 'C' packet, and reply
+ * to each 517-byte packet sequence with a '\006' packet.
+ *
+ * When there is no more data, the S1 will send us a '\004' packet,
+ * which we'll ack with a final '\006' packet.
+ *
+ * The header is '\001' followed by block number (starting at 1),
+ * followed by (255-block) number. So we can get a sequence of
+ *
+ * 01 01 fe <512 bytes> xx xx
+ * 01 02 fd <512 bytes> xx xx
+ * 01 03 fc <512 bytes> xx xx
+ * 01 04 fb <512 bytes> xx xx
+ * 01 05 fa <512 bytes> xx xx
+ * 01 06 f9 <512 bytes> xx xx
+ * 01 07 f8 <512 bytes> xx xx
+ * 04
+ *
+ * And we should reply with that '\006' packet for each of those
+ * entries.
+ *
+ * NOTE! The above is not in single BLE packets, although the
+ * sequence blocks always start at a packet boundary.
+ */
+#define BLOB_BUFSZ 256
+static dc_status_t oceans_s1_get_sequence(oceans_s1_device_t *s1, unsigned char seq, dc_buffer_t *res)
+{
+ unsigned char buffer[BLOB_BUFSZ];
+ dc_status_t status;
+ size_t nbytes;
+
+ status = dc_iostream_read(s1->iostream, buffer, BLOB_BUFSZ, &nbytes);
+ if (status != DC_STATUS_SUCCESS)
+ return status;
+ if (!nbytes)
+ return DC_STATUS_IO;
+
+ if (buffer[0] == 4)
+ return DC_STATUS_DONE;
+
+ if (nbytes <= 3 || buffer[0] != 1)
+ return DC_STATUS_IO;
+
+ if (buffer[1] != seq || buffer[2]+seq != 255)
+ return DC_STATUS_IO;
+
+ nbytes -= 3;
+ dc_buffer_append(res, buffer+3, nbytes);
+ while (nbytes < 512) {
+ size_t got;
+
+ status = dc_iostream_read(s1->iostream, buffer, BLOB_BUFSZ, &got);
+ if (status != DC_STATUS_SUCCESS)
+ return status;
+
+ if (!got)
+ return DC_STATUS_IO;
+
+ // We should check the checksum if it is that?
+ if (got + nbytes > 512)
+ got = 512-nbytes;
+ dc_buffer_append(res, buffer, got);
+ nbytes += got;
+ }
+ return DC_STATUS_SUCCESS;
+}
+
+static dc_status_t oceans_s1_get_blob(oceans_s1_device_t *s1, const unsigned char **result)
+{
+ dc_status_t status;
+ dc_buffer_t *res;
+ unsigned char *data;
+ size_t size;
+ unsigned char seq;
+
+ res = dc_buffer_new(0);
+ if (!res)
+ return DC_STATUS_NOMEMORY;
+
+ // Tell the Oceans S1 to into some kind of block mode..
+ //
+ // The Oceans Android app uses a "Write Command" rather than
+ // a "Write Request" for this, but it seems to not matter
+
+ status = dc_iostream_write(s1->iostream, "C", 1, NULL);
+ if (status != DC_STATUS_SUCCESS)
+ return status;
+
+ seq = 1;
+ for (;;) {
+ status = oceans_s1_get_sequence(s1, seq, res);
+ if (status == DC_STATUS_DONE)
+ break;
+
+ if (status != DC_STATUS_SUCCESS) {
+ dc_buffer_free(res);
+ return status;
+ }
+
+ // Ack the packet sequence, and go look for the next one
+ status = dc_iostream_write(s1->iostream, "\006", 1, NULL);
+ if (status != DC_STATUS_SUCCESS)
+ return status;
+ seq++;
+ }
+
+
+
+ // Tell the Oceans S1 to exit block mode (??)
+ status = dc_iostream_write(s1->iostream, "\006", 1, NULL);
+ if (status != DC_STATUS_SUCCESS)
+ return status;
+
+ size = dc_buffer_get_size(res);
+
+ // NUL-terminate before getting buffer
+ dc_buffer_append(res, "", 1);
+ data = dc_buffer_get_data(res);
+
+ /* Remove trailing whitespace */
+ while (size && isspace(data[size-1]))
+ data[--size] = 0;
+
+ *result = data;
+ return DC_STATUS_SUCCESS;
+}
+
+/*
+ * The Oceans S1 uses the normal UNIX epoch time format: seconds
+ * since 1-1-1970. In UTC format (so converting local time to UTC).
+ */
+static dc_status_t oceans_s1_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime)
+{
+ oceans_s1_device_t *s1 = (oceans_s1_device_t *) abstract;
+ dc_ticks_t timestamp;
+ dc_status_t status;
+
+ timestamp = dc_datetime_mktime(datetime);
+ if (timestamp < 0)
+ return DC_STATUS_INVALIDARGS;
+
+ timestamp += datetime->timezone;
+
+ status = oceans_s1_printf(s1, "utc %lld\n", (long long) timestamp);
+ if (status != DC_STATUS_SUCCESS)
+ return status;
+
+ return oceans_s1_expect(s1, "utc>ok");
+}
+
+
+/*
+ * Oceans S1 initial sequence (all ASCII text with newlines):
+ *
+ * Cmd Reply Comments
+ *
+ * "utc" "utc>ok 1592912375" // TZ=UTC date -d"@1592912375"
+ * "battery" "battery>ok 59%"
+ * "version" "version>ok 1.1 42a7e564" // Odd hex contant. Device ID?
+ * "utc 1592912364" "utc>ok" TZ=UTC date -d"@1592912364"
+ * "units 0" "units>ok"
+ * "name TGludXM=" "name>ok" // WTF?
+ * "dllist" "dllist>xmr"
+ *
+ * At this point, the sequence changed and is no longer single packets
+ * with a full line with newline termination.
+ *
+ * We send a single 'C' character as a GATT "Write Command" - 0x53 (so
+ * not "Write Request" - 0x12).
+ *
+ * The dive computer replies with GATT packets that contains:
+ *
+ * - binary three bytes: "\x01\x01\xfe"
+ *
+ * - followed by ASCII text blob (note the single space indentation):
+ *
+ * divelog v1,10s/sample
+ * dive 1,0,21,1591372057
+ * continue 612,10
+ * enddive 3131,496
+ * dive 2,0,21,1591372925
+ * enddive 1535,277
+ * dive 3,0,32,1591463368
+ * enddive 1711,4515
+ * dive 4,0,32,1591961688
+ * continue 300,45
+ * continue 391,238
+ * continue 420,126
+ * continue 236,17
+ * enddive 1087,2636
+ * endlog
+ *
+ * Followed by a lot of newlines to pad out the packets.
+ *
+ * NOTE! The newlines are probably because the way the Nordic Semi UART
+ * buffering works: it will buffer the packets until they are full, or
+ * until a newline.
+ *
+ * Then some odd data: write a single '\x06' character and get a single
+ * character reply of '\x04' (!?). Repeat, get a '\x13' byte back.
+ *
+ * NOTE! Again these single-byte things are GATT "write command", not
+ * GATT "write request" things. They may be commands to the UART, not
+ * data. Some kind of flow control? Or UART buffer control?
+ *
+ * Then it seems to go back to line-mode with the usual Write Request:
+ *
+ * "dlget 4 5" "dlget>xmr"
+ *
+ * which puts us in that "blob" mode again, and we send a singler 'C'
+ * character again, and now get that same '\x01\x01\xfe' binary data
+ * followed by ASCII text blob (note the space indentation again):
+ *
+ * divelog v1,10s/sample
+ * dive 4,0,32,1591961688
+ * 365,13,1
+ * 382,13,51456
+ * 367,13,16640
+ * 381,13,49408
+ * 375,13,24576
+ * 355,13,16384
+ * 346,13,16384
+ * 326,14,16384
+ * 355,14,16384
+ * 394,14,24576
+ * 397,14,16384
+ * 434,14,49152
+ * 479,14,49152
+ * 488,14,16384
+ * 556,14,57344
+ * 616,14,49152
+ * 655,14,49152
+ * 738,14,49152
+ * 800,14,57344
+ * 800,14,49408
+ * 834,14,16640
+ * 871,14,24832
+ * 860,14,16640
+ * 860,14,16640
+ * 815,14,24832
+ * 738,14,16640
+ * 707,14,16640
+ * 653,14,24832
+ * 647,13,16640
+ * 670,13,16640
+ * 653,13,24832
+ * ...
+ * continue 236,17
+ * 227,13,57600
+ * 238,14,16640
+ * 267,14,24832
+ * 283,14,16384
+ * 272,14,16384
+ * 303,14,24576
+ * 320,14,16384
+ * 318,14,16384
+ * 318,14,16384
+ * 335,14,24576
+ * 332,14,16384
+ * 386,14,16384
+ * 417,14,24576
+ * 244,14,16640
+ * 71,14,16640
+ * enddive 1087,2636
+ * endlog
+ *
+ * Where the samples seem to be
+ * - 'depth in cm'
+ * - 'temperature in °C' (??)
+ * - 'hex value flags' (??)
+ *
+ * Repeat with 'dlget 3 4', 'dlget 2 3', 'dlget 1 2'.
+ *
+ * Done.
+ */
+dc_status_t
+oceans_s1_device_open(dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream)
+{
+ char buffer[128];
+ dc_status_t status = DC_STATUS_SUCCESS;
+ oceans_s1_device_t *s1 = NULL;
+
+ if (out == NULL)
+ return DC_STATUS_INVALIDARGS;
+
+ // Allocate memory.
+ s1 = (oceans_s1_device_t*)dc_device_allocate(context, &oceans_s1_device_vtable);
+ if (s1 == NULL) {
+ ERROR(context, "Failed to allocate memory.");
+ return DC_STATUS_NOMEMORY;
+ }
+
+ // Set the default values.
+ s1->iostream = iostream;
+ memset(s1->fingerprint, 0, sizeof(s1->fingerprint));
+
+ *out = (dc_device_t*)s1;
+
+ // Do minimal verification that we can talk to it
+ // as part of the open.
+ status = oceans_s1_write(s1, "utc\n");
+ if (status != DC_STATUS_SUCCESS)
+ return status;
+
+ status = oceans_s1_read(s1, buffer, sizeof(buffer));
+ if (status != DC_STATUS_SUCCESS)
+ return status;
+ if (memcmp(buffer, "utc>ok", 6))
+ return DC_STATUS_IO;
+
+ return DC_STATUS_SUCCESS;
+}
+
+static dc_status_t
+oceans_s1_device_close(dc_device_t *abstract)
+{
+ dc_status_t status = DC_STATUS_SUCCESS;
+ oceans_s1_device_t *s1 = (oceans_s1_device_t*)abstract;
+
+ // Fill in
+
+ return DC_STATUS_SUCCESS;
+}
+
+static dc_status_t
+oceans_s1_device_set_fingerprint(dc_device_t *abstract, const unsigned char data[], unsigned int size)
+{
+ oceans_s1_device_t *s1 = (oceans_s1_device_t*)abstract;
+
+ if (size > sizeof(s1->fingerprint))
+ return DC_STATUS_INVALIDARGS;
+
+ memset(s1->fingerprint, 0, sizeof(s1->fingerprint));
+ memcpy(s1->fingerprint, data, size);
+
+ return DC_STATUS_SUCCESS;
+}
+
+static dc_status_t
+get_dive_list(oceans_s1_device_t *s1, const unsigned char **list)
+{
+ dc_status_t status;
+
+ status = oceans_s1_write(s1, "dllist\n");
+ if (status != DC_STATUS_SUCCESS)
+ return status;
+
+ status = oceans_s1_expect(s1, "dllist>xmr");
+ if (status != DC_STATUS_SUCCESS)
+ return status;
+
+ return oceans_s1_get_blob(s1, list);
+}
+
+static dc_status_t
+get_one_dive(oceans_s1_device_t *s1, int nr, const unsigned char **dive)
+{
+ dc_status_t status;
+
+ status = oceans_s1_printf(s1, "dlget %d %d\n", nr, nr+1);
+ if (status != DC_STATUS_SUCCESS)
+ return status;
+
+ status = oceans_s1_expect(s1, "dlget>xmr");
+ if (status != DC_STATUS_SUCCESS)
+ return status;
+
+ return oceans_s1_get_blob(s1, dive);
+}
+
+static const unsigned char *get_string_line(const unsigned char **blob)
+{
+ const unsigned char *in = *blob;
+ const unsigned char *line;
+ unsigned char c;
+
+ *blob = NULL;
+ if (!in)
+ return NULL;
+
+ while (isspace(*in))
+ in++;
+
+ if (!*in)
+ return NULL;
+
+ line = in;
+ while ((c = *in) != 0) {
+ if (c == '\r' || c == '\n')
+ break;
+ in++;
+ }
+ *blob = in;
+ return line;
+}
+
+// The 'unknown' field is probably the dive mode
+// 'date' is seconds since UNIX epoch (the usual "local time as GMT")
+// depth and duration are in cm and seconds
+// The fingerprint is just the 'dive' line padded with NUL characters
+struct s1_dive {
+ int nr, unknown, o2;
+ long long date;
+ unsigned maxdepth, duration;
+ unsigned char fingerprint[S1_FINGERPRINT];
+ struct s1_dive *next;
+};
+
+/*
+ * React to the "dive x,y,z,date" line.
+ *
+ * Allocate the dive.
+ */
+static struct s1_dive *
+s1_alloc_dive(const unsigned char *line, size_t len)
+{
+ struct s1_dive *dive;
+ int nr, unknown, o2;
+ long long date;
+
+ if (sscanf(line, "dive %d,%d,%d,%lld", &nr, &unknown, &o2, &date) != 4)
+ return NULL;
+
+ dive = malloc(sizeof(*dive));
+ if (dive) {
+ memset(dive, 0, sizeof(*dive));
+
+ dive->nr = nr;
+ dive->unknown = unknown;
+ dive->o2 = o2;
+ dive->date = date;
+ dive->next = NULL;
+
+ if (len >= S1_FINGERPRINT)
+ len = S1_FINGERPRINT-1;
+ memcpy(dive->fingerprint, line, len);
+ }
+ return dive;
+}
+
+/*
+ * React to the "enddive x,y" line
+ *
+ * Add a dive to the dive list, sorted with newest dive first
+ *
+ * I'm not sure if the dive list is always presented sorted by the
+ * Oceans S1, but it arrives in the reverse order of what we want
+ * (we want newest first, it lists them oldest first). So we need
+ * to switch the order, and we might as well make sure it's sorted
+ * while doing that.
+ *
+ * If it always comes sorted from the Oceans S1, the while () loop
+ * here won't ever actually loop, so there's no real cost to this
+ * (not that CPU time here matters).
+ */
+static int
+s1_add_dive(struct s1_dive *dive, struct s1_dive **list, const unsigned char *line, size_t len)
+{
+ unsigned maxdepth, duration;
+ struct s1_dive *next;
+
+ if (!dive)
+ return 0;
+
+ if (sscanf(line, "enddive %u,%u", &maxdepth, &duration) != 2)
+ return 0;
+
+ dive->maxdepth = maxdepth;
+ dive->duration = duration;
+ while ((next = *list) != NULL) {
+ if (dive->nr >= next->nr)
+ break;
+ list = &next->next;
+ }
+ dive->next = next;
+ *list = dive;
+ return 1;
+}
+
+static dc_status_t
+oceans_s1_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
+{
+ int nr;
+ struct s1_dive *divelist, *current_dive;
+ const unsigned char *blob, *line;
+ dc_status_t status = DC_STATUS_SUCCESS;
+ oceans_s1_device_t *s1 = (oceans_s1_device_t*)abstract;
+
+ dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
+ device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
+
+ status = get_dive_list(s1, &blob);
+ if (status != DC_STATUS_SUCCESS)
+ return status;
+
+ nr = 0;
+ divelist = NULL;
+ current_dive = NULL;
+ while ((line = get_string_line(&blob)) != NULL) {
+ int linelen = blob - line;
+ const unsigned char *dive;
+
+ /* We only care about 'dive' and 'enddive' lines */
+ if (linelen < 8)
+ continue;
+
+ if (!memcmp(line, "dive ", 5)) {
+ current_dive = s1_alloc_dive(line, linelen);
+ continue;
+ }
+
+ if (memcmp(line, "enddive ", 8))
+ continue;
+
+ if (s1_add_dive(current_dive, &divelist, line, linelen))
+ nr++;
+ current_dive = NULL;
+ }
+ if (!nr)
+ return DC_STATUS_SUCCESS;
+
+ progress.current = 0;
+ progress.maximum = nr;
+ device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
+
+ for (current_dive = divelist; current_dive; current_dive = current_dive->next) {
+ const unsigned char *blob;
+
+ if (!memcmp(current_dive->fingerprint, s1->fingerprint, S1_FINGERPRINT))
+ break;
+
+ if (device_is_cancelled(&s1->base))
+ break;
+
+ status = get_one_dive(s1, current_dive->nr, &blob);
+ if (status != DC_STATUS_SUCCESS)
+ return status;
+
+ progress.current++;
+ device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
+
+ if (callback && !callback(blob, strlen(blob), current_dive->fingerprint, S1_FINGERPRINT, userdata))
+ break;
+ }
+
+ return status;
+}
diff --git a/src/oceans_s1.h b/src/oceans_s1.h
new file mode 100644
index 0000000..483b144
--- /dev/null
+++ b/src/oceans_s1.h
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// Copyright (C) 2020 Linus Torvalds
+
+#ifndef OCEANS_S1_H
+#define OCEANS_S1_H
+
+#include
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+dc_status_t
+oceans_s1_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream);
+
+dc_status_t
+oceans_s1_parser_create (dc_parser_t **parser, dc_context_t *context);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* OCEANS_S1_H */
diff --git a/src/oceans_s1_parser.c b/src/oceans_s1_parser.c
new file mode 100644
index 0000000..b2e012f
--- /dev/null
+++ b/src/oceans_s1_parser.c
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// Copyright (C) 2020 Linus Torvalds
+
+#include
+#include
+#include
+#include
+#include
+
+#include "oceans_s1.h"
+#include "context-private.h"
+#include "parser-private.h"
+#include "field-cache.h"
+#include "array.h"
+
+typedef struct oceans_s1_parser_t oceans_s1_parser_t;
+
+struct oceans_s1_parser_t {
+ dc_parser_t base;
+ int divenr;
+ unsigned int maxdepth, duration;
+ long long date;
+ struct dc_field_cache cache;
+};
+
+static dc_status_t oceans_s1_parser_set_data(dc_parser_t *abstract, const unsigned char *data, unsigned int size);
+static dc_status_t oceans_s1_parser_get_datetime(dc_parser_t *abstract, dc_datetime_t *datetime);
+static dc_status_t oceans_s1_parser_get_field(dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value);
+static dc_status_t oceans_s1_parser_samples_foreach(dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata);
+
+static const dc_parser_vtable_t oceans_s1_parser_vtable = {
+ sizeof(oceans_s1_parser_t),
+ DC_FAMILY_OCEANS_S1,
+ oceans_s1_parser_set_data, /* set_data */
+ oceans_s1_parser_get_datetime, /* datetime */
+ oceans_s1_parser_get_field, /* fields */
+ oceans_s1_parser_samples_foreach, /* samples_foreach */
+ NULL /* destroy */
+};
+
+dc_status_t
+oceans_s1_parser_create(dc_parser_t **out, dc_context_t *context)
+{
+ oceans_s1_parser_t *parser = NULL;
+
+ if (out == NULL)
+ return DC_STATUS_INVALIDARGS;
+
+ // Allocate memory.
+ parser = (oceans_s1_parser_t*)dc_parser_allocate(context, &oceans_s1_parser_vtable);
+ if (parser == NULL) {
+ ERROR(context, "Failed to allocate memory.");
+ return DC_STATUS_NOMEMORY;
+ }
+
+ *out = (dc_parser_t*)parser;
+
+ return DC_STATUS_SUCCESS;
+}
+
+static const unsigned char *get_string_line(const unsigned char *in, const unsigned char **next)
+{
+ const unsigned char *line;
+ unsigned char c;
+
+ if (!in) {
+ *next = NULL;
+ return NULL;
+ }
+
+ while (isspace(*in))
+ in++;
+
+ if (!*in) {
+ *next = NULL;
+ return NULL;
+ }
+
+ line = in;
+ while ((c = *in) != 0) {
+ if (c == '\r' || c == '\n')
+ break;
+ in++;
+ }
+ *next = in;
+ return line;
+}
+
+static dc_status_t
+oceans_s1_parse_dive(struct oceans_s1_parser_t *s1, const unsigned char *data, dc_sample_callback_t callback, void *userdata)
+{
+ const unsigned char *line;
+ unsigned int sample_interval = 10;
+ unsigned int sample_time = 0;
+
+ memset(&s1->cache, 0, sizeof(s1->cache));
+
+ while ((line = get_string_line(data, &data)) != NULL) {
+ dc_sample_value_t sample = {0};
+ int depth = 0, temp = 0, flags = 0;
+
+ if (!strncmp(line, "divelog ", 8)) {
+ sscanf(line, "divelog v1,%us/sample", &sample_interval);
+ continue;
+ }
+ if (!strncmp(line, "dive ", 5)) {
+ int nr, unknown, o2;
+ long long date;
+
+ sscanf(line, "dive %d,%d,%d,%lld", &nr, &unknown, &o2, &date);
+ s1->divenr = nr;
+ s1->date = date;
+ // I think "unknown" is dive mode
+ if (o2) {
+ dc_gasmix_t mix = { 0 };
+ mix.oxygen = o2 / 100.0;
+ DC_ASSIGN_FIELD(s1->cache, GASMIX_COUNT, 1);
+ DC_ASSIGN_IDX(s1->cache, GASMIX, 0, mix);
+ }
+ continue;
+ }
+ if (!strncmp(line, "continue ", 9)) {
+ int depth = 0, seconds = 0;
+ sscanf(line, "continue %d,%d", &depth, &seconds);
+
+ // Create surface samples for the surface time,
+ // and then a depth sample at the stated depth
+ if (callback) {
+ if (seconds >= sample_interval*2) {
+ dc_sample_value_t sample = {0};
+ sample.time = sample_time + sample_interval;
+ callback(DC_SAMPLE_TIME, sample, userdata);
+ sample.depth = 0;
+ callback(DC_SAMPLE_DEPTH, sample, userdata);
+
+ sample.time = sample_time + seconds - sample_interval;
+ callback(DC_SAMPLE_TIME, sample, userdata);
+ sample.depth = 0;
+ callback(DC_SAMPLE_DEPTH, sample, userdata);
+ }
+ sample.time = sample_time + seconds;
+ callback(DC_SAMPLE_TIME, sample, userdata);
+ sample.depth = depth / 100.0;
+ callback(DC_SAMPLE_DEPTH, sample, userdata);
+ }
+ sample_time += seconds;
+ continue;
+ }
+ if (!strncmp(line, "enddive ", 8)) {
+ int maxdepth = 0, duration = 0;
+ sscanf(line, "enddive %d,%d", &maxdepth, &duration);
+ DC_ASSIGN_FIELD(s1->cache, MAXDEPTH, maxdepth / 100.0);
+ DC_ASSIGN_FIELD(s1->cache, DIVETIME, duration);
+ s1->maxdepth = maxdepth;
+ s1->duration = duration;
+ continue;
+ }
+ if (sscanf(line, "%d,%d,%d", &depth, &temp, &flags) != 3)
+ continue;
+
+ sample_time += sample_interval;
+ if (callback) {
+ dc_sample_value_t sample = {0};
+ sample.time = sample_time;
+ callback(DC_SAMPLE_TIME, sample, userdata);
+ sample.depth = depth / 100.0;
+ callback(DC_SAMPLE_DEPTH, sample, userdata);
+ sample.temperature = temp;
+ callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
+ }
+ }
+ return DC_STATUS_SUCCESS;
+}
+
+static dc_status_t
+oceans_s1_parser_set_data(dc_parser_t *abstract, const unsigned char *data, unsigned int size)
+{
+ struct oceans_s1_parser_t *s1 = (struct oceans_s1_parser_t *)abstract;
+
+ return oceans_s1_parse_dive(s1, data, NULL, NULL);
+}
+
+static dc_status_t
+oceans_s1_parser_get_datetime(dc_parser_t *abstract, dc_datetime_t *datetime)
+{
+ oceans_s1_parser_t *s1 = (oceans_s1_parser_t *)abstract;
+
+ dc_datetime_gmtime(datetime, s1->date);
+ return DC_STATUS_SUCCESS;
+}
+
+static dc_status_t
+oceans_s1_parser_get_field(dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value)
+{
+ oceans_s1_parser_t *s1 = (oceans_s1_parser_t *)abstract;
+
+ return dc_field_get(&s1->cache, type, flags, value);
+}
+
+static dc_status_t
+oceans_s1_parser_samples_foreach(dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
+{
+ struct oceans_s1_parser_t *s1 = (struct oceans_s1_parser_t *)abstract;
+
+ return oceans_s1_parse_dive(s1, s1->base.data, callback, userdata);
+}
diff --git a/src/parser.c b/src/parser.c
index dffb517..e76cb63 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -60,6 +60,7 @@
#include "garmin.h"
#include "deepblu.h"
#include "mclean_extreme.h"
+#include "oceans_s1.h"
#include "context-private.h"
#include "parser-private.h"
@@ -184,6 +185,9 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa
case DC_FAMILY_MCLEAN_EXTREME:
rc = mclean_extreme_parser_create (&parser, context);
break;
+ case DC_FAMILY_OCEANS_S1:
+ rc = oceans_s1_parser_create(&parser, context);
+ break;
default:
return DC_STATUS_INVALIDARGS;
}