From e9129ff45a2b0128814a2fd13861932492cbd31a Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 22 Jun 2020 14:55:16 -0700 Subject: [PATCH 1/7] Add generic dc_field_get() helper This generic helper just gets everything from the field cache. Dive computers can do their own things for any field they handle differently, and then at the end fall back to this for all of the common cases that are purely described by the field cache structure. Signed-off-by: Linus Torvalds --- src/field-cache.c | 38 ++++++++++++++++++++++++++++++++++++++ src/field-cache.h | 1 + 2 files changed, 39 insertions(+) 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. From d58b8f6928d528e6d356b9a002047da0a38ef4a4 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 22 Jun 2020 14:57:40 -0700 Subject: [PATCH 2/7] Add skeleton for Oceans S1 downloader This does nothing, but fills in all the basic boiler plate code and data structures. Signed-off-by: Linus Torvalds --- examples/common.c | 1 + include/libdivecomputer/common.h | 2 + msvc/libdivecomputer.vcproj | 12 ++++ src/Makefile.am | 1 + src/descriptor.c | 16 +++++ src/device.c | 4 ++ src/oceans_s1.c | 108 +++++++++++++++++++++++++++++++ src/oceans_s1.h | 25 +++++++ src/oceans_s1_parser.c | 85 ++++++++++++++++++++++++ src/parser.c | 4 ++ 10 files changed, 258 insertions(+) create mode 100644 src/oceans_s1.c create mode 100644 src/oceans_s1.h create mode 100644 src/oceans_s1_parser.c diff --git a/examples/common.c b/examples/common.c index ebc1542..1458e88 100644 --- a/examples/common.c +++ b/examples/common.c @@ -92,6 +92,7 @@ static const backend_table_t g_backends[] = { {"divecomputereu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0}, {"descentmk1", DC_FAMILY_GARMIN, 0}, {"cosmiq", DC_FAMILY_DEEPBLU, 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 0819843..8581dbb 100644 --- a/include/libdivecomputer/common.h +++ b/include/libdivecomputer/common.h @@ -112,6 +112,8 @@ typedef enum dc_family_t { DC_FAMILY_GARMIN = (16 << 16), /* Deepblu */ DC_FAMILY_DEEPBLU = (17 << 16), + /* Oceans S1 */ + DC_FAMILY_OCEANS_S1 = (18 << 16), } dc_family_t; #ifdef __cplusplus diff --git a/msvc/libdivecomputer.vcproj b/msvc/libdivecomputer.vcproj index 0ae0ed6..a752c83 100644 --- a/msvc/libdivecomputer.vcproj +++ b/msvc/libdivecomputer.vcproj @@ -514,6 +514,14 @@ RelativePath="..\src\deepblu_parser.c" > + + + + @@ -868,6 +876,10 @@ RelativePath="..\src\deepblu.h" > + + diff --git a/src/Makefile.am b/src/Makefile.am index af2bc6d..38b7b27 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -75,6 +75,7 @@ libdivecomputer_la_SOURCES = \ tecdiving_divecomputereu.h tecdiving_divecomputereu.c tecdiving_divecomputereu_parser.c \ garmin.h garmin.c garmin_parser.c \ deepblu.h deepblu.c deepblu_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 9836d52..cba294a 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -50,6 +50,7 @@ static int dc_filter_mares (dc_transport_t transport, const void *userdata); static int dc_filter_divesystem (dc_transport_t transport, const void *userdata); static int dc_filter_oceanic (dc_transport_t transport, const void *userdata); static int dc_filter_deepblu (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); @@ -382,6 +383,8 @@ static const dc_descriptor_t g_descriptors[] = { {"Garmin", "Descent Mk1", DC_FAMILY_GARMIN, 2859, DC_TRANSPORT_USBSTORAGE, dc_filter_garmin}, /* Deepblu */ {"Deepblu", "Cosmiq+", DC_FAMILY_DEEPBLU, 0, DC_TRANSPORT_BLE, dc_filter_deepblu}, + /* Oceans S1 */ + { "Oceans", "S1", DC_FAMILY_OCEANS_S1, 0, DC_TRANSPORT_BLE, dc_filter_oceans }, }; static int @@ -661,6 +664,19 @@ static int dc_filter_deepblu (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 15bfcf3..f8d1e04 100644 --- a/src/device.c +++ b/src/device.c @@ -59,6 +59,7 @@ #include "tecdiving_divecomputereu.h" #include "garmin.h" #include "deepblu.h" +#include "oceans_s1.h" #include "device-private.h" #include "context-private.h" @@ -219,6 +220,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr case DC_FAMILY_DEEPBLU: rc = deepblu_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/oceans_s1.c b/src/oceans_s1.c new file mode 100644 index 0000000..dafbcc7 --- /dev/null +++ b/src/oceans_s1.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2020 Linus Torvalds + +#include // memcmp, memcpy +#include // malloc, free +#include + +#include "oceans_s1.h" +#include "context-private.h" +#include "device-private.h" +#include "array.h" + +#define ISINSTANCE(device) dc_device_isinstance((device), &oceans_s1_device_vtable) + +typedef struct oceans_s1_device_t { + dc_device_t base; + dc_iostream_t* iostream; + unsigned char fingerprint[4]; +} 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 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 */ + NULL, /* timesync */ + oceans_s1_device_close, /* close */ +}; + +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; + + // Fill in + + return DC_STATUS_IO; +} + +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 && size != sizeof(s1->fingerprint)) + return DC_STATUS_INVALIDARGS; + + if (size) + memcpy(s1->fingerprint, data, sizeof(s1->fingerprint)); + else + memset(s1->fingerprint, 0, sizeof(s1->fingerprint)); + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +oceans_s1_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) +{ + 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); + + progress.current = 0; + progress.maximum = 0; + device_event_emit(abstract, DC_EVENT_PROGRESS, &progress); + + // Fill in + + 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..f545c15 --- /dev/null +++ b/src/oceans_s1_parser.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2020 Linus Torvalds + +#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; + 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 dc_status_t +oceans_s1_parser_set_data(dc_parser_t *abstract, const unsigned char *data, unsigned int size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + // Fill me + return DC_STATUS_SUCCESS; +} + +static dc_status_t +oceans_s1_parser_get_datetime(dc_parser_t* abstract, dc_datetime_t* datetime) +{ + // Fill me + 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) +{ + // Fill me + return DC_STATUS_SUCCESS; +} diff --git a/src/parser.c b/src/parser.c index 8fd16ee..453797c 100644 --- a/src/parser.c +++ b/src/parser.c @@ -59,6 +59,7 @@ #include "tecdiving_divecomputereu.h" #include "garmin.h" #include "deepblu.h" +#include "oceans_s1.h" #include "context-private.h" #include "parser-private.h" @@ -180,6 +181,9 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa case DC_FAMILY_DEEPBLU: rc = deepblu_parser_create (&parser, context); break; + case DC_FAMILY_OCEANS_S1: + rc = oceans_s1_parser_create(&parser, context); + break; default: return DC_STATUS_INVALIDARGS; } From 7018206f56dedfbf2cf935c78def7356faf44919 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 23 Jun 2020 14:28:44 -0700 Subject: [PATCH 3/7] Oceans S1: start documenting the download format and first packets The communication seems to be mainly ASCII strings, with the main complexity probably being that the Nordic Semi UART has some side channel for switching between line-buffered and "bulk data" modes. That part might end up being painful and needing more interfaces to the Subsurface BLE code. We'll likely need to add more special BLE code. The Bluetooth SIG really is a horrible disgrace. Signed-off-by: Linus Torvalds --- src/oceans_s1.c | 152 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/src/oceans_s1.c b/src/oceans_s1.c index dafbcc7..40a27fb 100644 --- a/src/oceans_s1.c +++ b/src/oceans_s1.c @@ -34,6 +34,150 @@ static const dc_device_vtable_t oceans_s1_device_vtable = { 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; +} + +/* + * 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) { @@ -57,6 +201,14 @@ oceans_s1_device_open(dc_device_t **out, dc_context_t *context, dc_iostream_t *i *out = (dc_device_t*)s1; + 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; + // Fill in return DC_STATUS_IO; From ae58a66d9a37677ada71487a408bd322c4535c00 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 23 Jun 2020 15:45:27 -0700 Subject: [PATCH 4/7] Oceans S1: start filling in protocol details This does the time sync command, which is particularly simple. Signed-off-by: Linus Torvalds --- src/oceans_s1.c | 70 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 5 deletions(-) diff --git a/src/oceans_s1.c b/src/oceans_s1.c index 40a27fb..0640f25 100644 --- a/src/oceans_s1.c +++ b/src/oceans_s1.c @@ -4,6 +4,8 @@ #include // memcmp, memcpy #include // malloc, free #include +#include +#include #include "oceans_s1.h" #include "context-private.h" @@ -21,6 +23,7 @@ typedef struct 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), @@ -30,7 +33,7 @@ static const dc_device_vtable_t oceans_s1_device_vtable = { NULL, /* write */ NULL, /* dump */ oceans_s1_device_foreach, /* foreach */ - NULL, /* timesync */ + oceans_s1_device_timesync, /* timesync */ oceans_s1_device_close, /* close */ }; @@ -54,6 +57,61 @@ oceans_s1_read(oceans_s1_device_t *s1, char *buf, size_t bufsz) 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))) + return DC_STATUS_IO; + + 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): * @@ -201,6 +259,8 @@ oceans_s1_device_open(dc_device_t **out, dc_context_t *context, dc_iostream_t *i *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; @@ -208,10 +268,10 @@ oceans_s1_device_open(dc_device_t **out, dc_context_t *context, dc_iostream_t *i 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; - // Fill in - - return DC_STATUS_IO; + return DC_STATUS_SUCCESS; } static dc_status_t @@ -251,7 +311,7 @@ oceans_s1_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback, voi device_event_emit(abstract, DC_EVENT_PROGRESS, &progress); progress.current = 0; - progress.maximum = 0; + progress.maximum = 100; device_event_emit(abstract, DC_EVENT_PROGRESS, &progress); // Fill in From 91658dd167ff28ab11fda999bcbbaf9dd0f7b0ed Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 24 Jun 2020 14:18:57 -0700 Subject: [PATCH 5/7] Oceans S1: fill out core download protocol details All of the Oceans S1 protocol seems to be basically ASCII data, but the bigger chunks of data (the dive list, and the actual dive profiles) are chunked in 512-byte pieces with sequence numbers and what looks like some checksum. This doesn't check the checksum yet, but the basic "download data" seems to work. Note that the code doesn't actually _parse_ said data yet, nor create an actual dive list. So this is very much only very incremental progress, but this seems to have been the nastiest part of the actual protocol. Knock wood. Signed-off-by: Linus Torvalds --- src/oceans_s1.c | 185 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 184 insertions(+), 1 deletion(-) diff --git a/src/oceans_s1.c b/src/oceans_s1.c index 0640f25..3cd7496 100644 --- a/src/oceans_s1.c +++ b/src/oceans_s1.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "oceans_s1.h" #include "context-private.h" @@ -83,9 +84,146 @@ static dc_status_t oceans_s1_expect(oceans_s1_device_t *s1, const char *result) if (status != DC_STATUS_SUCCESS) return status; - if (strncmp(buffer, result, strlen(result))) + 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, 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; } @@ -112,6 +250,7 @@ static dc_status_t oceans_s1_device_timesync(dc_device_t *abstract, const dc_dat return oceans_s1_expect(s1, "utc>ok"); } + /* * Oceans S1 initial sequence (all ASCII text with newlines): * @@ -301,19 +440,63 @@ oceans_s1_device_set_fingerprint(dc_device_t *abstract, const unsigned char data return DC_STATUS_SUCCESS; } +static dc_status_t +get_dive_list(oceans_s1_device_t *s1, 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, 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 dc_status_t oceans_s1_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) { + unsigned char *divelist, *dive; 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, &divelist); + if (status != DC_STATUS_SUCCESS) + return status; + fprintf(stderr, "divelist = %s\n", divelist); + progress.current = 0; progress.maximum = 100; device_event_emit(abstract, DC_EVENT_PROGRESS, &progress); + // Just force dive 4 for now + status = get_one_dive(s1, 4, &dive); + if (status != DC_STATUS_SUCCESS) + return status; + fprintf(stderr, "dive 4 = %s\n", dive); + // Fill in return status; From da8ae52b50111dca4bd89e92b5e330225eb976a5 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 24 Jun 2020 17:09:34 -0700 Subject: [PATCH 6/7] Oceans S1: actually download all dives and parse them This isn't perfect - we don't do the whole dive fingerprint etc, so right now it always downloads all dives. To make matters worse, it downloads dives oldest first, which then confuses the subsurface downloader that expects newest first. So there's stuff to clean up, but the basic profile data is all there. Signed-off-by: Linus Torvalds --- src/oceans_s1.c | 107 +++++++++++++++++++++++++++---- src/oceans_s1_parser.c | 142 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 227 insertions(+), 22 deletions(-) diff --git a/src/oceans_s1.c b/src/oceans_s1.c index 3cd7496..7e126a2 100644 --- a/src/oceans_s1.c +++ b/src/oceans_s1.c @@ -167,7 +167,7 @@ static dc_status_t oceans_s1_get_sequence(oceans_s1_device_t *s1, unsigned char return DC_STATUS_SUCCESS; } -static dc_status_t oceans_s1_get_blob(oceans_s1_device_t *s1, unsigned char **result) +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; @@ -441,7 +441,7 @@ oceans_s1_device_set_fingerprint(dc_device_t *abstract, const unsigned char data } static dc_status_t -get_dive_list(oceans_s1_device_t *s1, unsigned char **list) +get_dive_list(oceans_s1_device_t *s1, const unsigned char **list) { dc_status_t status; @@ -457,7 +457,7 @@ get_dive_list(oceans_s1_device_t *s1, unsigned char **list) } static dc_status_t -get_one_dive(oceans_s1_device_t *s1, int nr, unsigned char **dive) +get_one_dive(oceans_s1_device_t *s1, int nr, const unsigned char **dive) { dc_status_t status; @@ -472,10 +472,52 @@ get_one_dive(oceans_s1_device_t *s1, int nr, unsigned char **dive) return oceans_s1_get_blob(s1, dive); } +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 int count_dives(const unsigned char *divelist) +{ + int dives = 0; + const unsigned char *line; + + while ((line = get_string_line(divelist, &divelist)) != NULL) { + if (strncmp(line, "dive ", 5)) + continue; + dives++; + } + return dives; +} + static dc_status_t oceans_s1_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) { - unsigned char *divelist, *dive; + int nr; + const unsigned char *divelist, *line; dc_status_t status = DC_STATUS_SUCCESS; oceans_s1_device_t *s1 = (oceans_s1_device_t*)abstract; @@ -485,19 +527,60 @@ oceans_s1_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback, voi status = get_dive_list(s1, &divelist); if (status != DC_STATUS_SUCCESS) return status; - fprintf(stderr, "divelist = %s\n", divelist); + + nr = count_dives(divelist); + if (!nr) + return DC_STATUS_SUCCESS; progress.current = 0; - progress.maximum = 100; + progress.maximum = nr; device_event_emit(abstract, DC_EVENT_PROGRESS, &progress); - // Just force dive 4 for now - status = get_one_dive(s1, 4, &dive); - if (status != DC_STATUS_SUCCESS) - return status; - fprintf(stderr, "dive 4 = %s\n", dive); + int dive_nr = 0, dive_unknown = 0, dive_o2 = 0; + int dive_depth, dive_time; + long long dive_date = -1; + char fingerprint[32]; - // Fill in + while ((line = get_string_line(divelist, &divelist)) != NULL) { + int linelen = divelist - line; + const unsigned char *dive; + + /* We only care about 'dive' and 'enddive' lines */ + if (linelen < 8 || linelen >= 32) + continue; + + if (!memcmp(line, "dive ", 5)) { + int nr, unknown, o2; + long long date; + + if (sscanf(line, "dive %d,%d,%d,%lld", &nr, &unknown, &o2, &date) != 4) + continue; + dive_nr = nr; + dive_unknown = unknown; + dive_o2 = o2; + dive_date = date; + + memset(fingerprint, 0, sizeof(fingerprint)); + memcpy(fingerprint, line, linelen); + continue; + } + + if (memcmp(line, "enddive ", 8)) + continue; + + if (sscanf(line, "enddive %d,%d", &dive_depth, &dive_time) != 2) + continue; + + status = get_one_dive(s1, dive_nr, &dive); + if (status != DC_STATUS_SUCCESS) + return status; + + progress.current++; + device_event_emit(abstract, DC_EVENT_PROGRESS, &progress); + + if (callback && !callback(dive, strlen(dive), fingerprint, sizeof(fingerprint), userdata)) + break; + } return status; } diff --git a/src/oceans_s1_parser.c b/src/oceans_s1_parser.c index f545c15..dd30f06 100644 --- a/src/oceans_s1_parser.c +++ b/src/oceans_s1_parser.c @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include "oceans_s1.h" #include "context-private.h" @@ -16,6 +18,9 @@ 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; }; @@ -37,7 +42,7 @@ static const dc_parser_vtable_t oceans_s1_parser_vtable = { dc_status_t oceans_s1_parser_create(dc_parser_t **out, dc_context_t *context) { - oceans_s1_parser_t* parser = NULL; + oceans_s1_parser_t *parser = NULL; if (out == NULL) return DC_STATUS_INVALIDARGS; @@ -54,23 +59,139 @@ oceans_s1_parser_create(dc_parser_t **out, dc_context_t *context) 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) { - dc_status_t status = DC_STATUS_SUCCESS; - // Fill me - return DC_STATUS_SUCCESS; + 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_get_datetime(dc_parser_t *abstract, dc_datetime_t *datetime) { - // Fill me + 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_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; @@ -78,8 +199,9 @@ oceans_s1_parser_get_field(dc_parser_t* abstract, dc_field_type_t type, unsigned } static dc_status_t -oceans_s1_parser_samples_foreach(dc_parser_t* abstract, dc_sample_callback_t callback, void* userdata) +oceans_s1_parser_samples_foreach(dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) { - // Fill me - return DC_STATUS_SUCCESS; + struct oceans_s1_parser_t *s1 = (struct oceans_s1_parser_t *)abstract; + + return oceans_s1_parse_dive(s1, s1->base.data, callback, userdata); } From 8d17c3c01cd81873bd6be145d8ce3762bd519d6e Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 25 Jun 2020 10:40:41 -0700 Subject: [PATCH 7/7] Oceans S1: polish up the downloading logic for usability This adds a few finishing touches to actually download dives in the expected order (newest first), which fixes the handling of already downloaded dives. It also adds the fingerprinting code to optimize the downloading a bit. Finally, it handles cancellation in the middle. Signed-off-by: Linus Torvalds --- src/oceans_s1.c | 185 +++++++++++++++++++++++++++-------------- src/oceans_s1_parser.c | 1 - 2 files changed, 124 insertions(+), 62 deletions(-) diff --git a/src/oceans_s1.c b/src/oceans_s1.c index 7e126a2..e70e6ee 100644 --- a/src/oceans_s1.c +++ b/src/oceans_s1.c @@ -13,12 +13,12 @@ #include "device-private.h" #include "array.h" -#define ISINSTANCE(device) dc_device_isinstance((device), &oceans_s1_device_vtable) +#define S1_FINGERPRINT 32 typedef struct oceans_s1_device_t { dc_device_t base; dc_iostream_t* iostream; - unsigned char fingerprint[4]; + 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); @@ -429,13 +429,11 @@ oceans_s1_device_set_fingerprint(dc_device_t *abstract, const unsigned char data { oceans_s1_device_t *s1 = (oceans_s1_device_t*)abstract; - if (size && size != sizeof(s1->fingerprint)) + if (size > sizeof(s1->fingerprint)) return DC_STATUS_INVALIDARGS; - if (size) - memcpy(s1->fingerprint, data, sizeof(s1->fingerprint)); - else - memset(s1->fingerprint, 0, sizeof(s1->fingerprint)); + memset(s1->fingerprint, 0, sizeof(s1->fingerprint)); + memcpy(s1->fingerprint, data, size); return DC_STATUS_SUCCESS; } @@ -472,23 +470,21 @@ get_one_dive(oceans_s1_device_t *s1, int nr, const unsigned char **dive) return oceans_s1_get_blob(s1, dive); } -static const unsigned char *get_string_line(const unsigned char *in, const unsigned char **next) +static const unsigned char *get_string_line(const unsigned char **blob) { + const unsigned char *in = *blob; const unsigned char *line; unsigned char c; - if (!in) { - *next = NULL; + *blob = NULL; + if (!in) return NULL; - } while (isspace(*in)) in++; - if (!*in) { - *next = NULL; + if (!*in) return NULL; - } line = in; while ((c = *in) != 0) { @@ -496,39 +492,132 @@ static const unsigned char *get_string_line(const unsigned char *in, const unsig break; in++; } - *next = in; + *blob = in; return line; } -static int count_dives(const unsigned char *divelist) -{ - int dives = 0; - const unsigned char *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; +}; - while ((line = get_string_line(divelist, &divelist)) != NULL) { - if (strncmp(line, "dive ", 5)) - continue; - dives++; +/* + * 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 dives; + 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; - const unsigned char *divelist, *line; + 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, &divelist); + status = get_dive_list(s1, &blob); if (status != DC_STATUS_SUCCESS) return status; - nr = count_dives(divelist); + 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; @@ -536,49 +625,23 @@ oceans_s1_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback, voi progress.maximum = nr; device_event_emit(abstract, DC_EVENT_PROGRESS, &progress); - int dive_nr = 0, dive_unknown = 0, dive_o2 = 0; - int dive_depth, dive_time; - long long dive_date = -1; - char fingerprint[32]; + for (current_dive = divelist; current_dive; current_dive = current_dive->next) { + const unsigned char *blob; - while ((line = get_string_line(divelist, &divelist)) != NULL) { - int linelen = divelist - line; - const unsigned char *dive; + if (!memcmp(current_dive->fingerprint, s1->fingerprint, S1_FINGERPRINT)) + break; - /* We only care about 'dive' and 'enddive' lines */ - if (linelen < 8 || linelen >= 32) - continue; + if (device_is_cancelled(&s1->base)) + break; - if (!memcmp(line, "dive ", 5)) { - int nr, unknown, o2; - long long date; - - if (sscanf(line, "dive %d,%d,%d,%lld", &nr, &unknown, &o2, &date) != 4) - continue; - dive_nr = nr; - dive_unknown = unknown; - dive_o2 = o2; - dive_date = date; - - memset(fingerprint, 0, sizeof(fingerprint)); - memcpy(fingerprint, line, linelen); - continue; - } - - if (memcmp(line, "enddive ", 8)) - continue; - - if (sscanf(line, "enddive %d,%d", &dive_depth, &dive_time) != 2) - continue; - - status = get_one_dive(s1, dive_nr, &dive); + 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(dive, strlen(dive), fingerprint, sizeof(fingerprint), userdata)) + if (callback && !callback(blob, strlen(blob), current_dive->fingerprint, S1_FINGERPRINT, userdata)) break; } diff --git a/src/oceans_s1_parser.c b/src/oceans_s1_parser.c index dd30f06..b2e012f 100644 --- a/src/oceans_s1_parser.c +++ b/src/oceans_s1_parser.c @@ -4,7 +4,6 @@ #include #include #include -#include #include #include