From a8540cad42eed827fc552d06d37153fb848d0f10 Mon Sep 17 00:00:00 2001 From: Latisha Date: Tue, 13 Oct 2026 10:25:27 +0800 Subject: [PATCH] suunto_nautic: flag an unanswered handshake; temperatures from the Windows records - suunto_nautic_device_handshake_acknowledged: the open succeeds when only other frames arrive before the link goes quiet (a sleeping watch), so apps can tell the user to press a button when the first request times out - the water temperature range comes from the /Summary Windows records, which hold Temperature.Min/Max in schema order on both watches; Ocean firmware writes the dive record's pair reversed. The dive record stays the fallback Co-Authored-By: Claude Opus 5.5 (1M context) --- include/libdivecomputer/suunto_nautic.h | 8 ++++++ src/suunto_nautic.c | 16 ++++++++++- src/suunto_nautic_parser.c | 37 +++++++++++++++++++++++-- 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/include/libdivecomputer/suunto_nautic.h b/include/libdivecomputer/suunto_nautic.h index 06426b7..f003ce6 100644 --- a/include/libdivecomputer/suunto_nautic.h +++ b/include/libdivecomputer/suunto_nautic.h @@ -44,6 +44,14 @@ extern "C" { * "/System/Mode", "/Logbook/Entries") and return the raw, undecoded * response payload (the ACK only). */ +/* + * 1 when the watch answered the EVA handshake on open. The open still succeeds + * when only other frames arrived before the link went quiet (a sleeping watch + * or the tail of an earlier stream), so 0 means the first request may time out. + */ +int +suunto_nautic_device_handshake_acknowledged (dc_device_t *device); + dc_status_t suunto_nautic_device_request (dc_device_t *device, const char *path, dc_buffer_t *response); diff --git a/src/suunto_nautic.c b/src/suunto_nautic.c index c9a9472..f79443f 100644 --- a/src/suunto_nautic.c +++ b/src/suunto_nautic.c @@ -160,6 +160,8 @@ typedef struct suunto_nautic_device_t { // fingerprint set" (a real dive ID is never 0 -- that would be a // 1970 timestamp), matching every other driver's convention. unsigned char fingerprint[4]; + // Whether the watch answered the EVA handshake; the open carries on without it. + int handshake_acknowledged; } suunto_nautic_device_t; static dc_status_t suunto_nautic_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); @@ -456,6 +458,7 @@ suunto_nautic_device_open (dc_device_t **out, dc_context_t *context, dc_iostream if (device->sequence == 0) device->sequence = 1; memset (device->fingerprint, 0, sizeof (device->fingerprint)); + device->handshake_acknowledged = 0; status = dc_hdlc_open (&device->iostream, context, iostream, 244, 244); if (status != DC_STATUS_SUCCESS) { @@ -504,8 +507,10 @@ suunto_nautic_device_open (dc_device_t **out, dc_context_t *context, dc_iostream HEXDUMP (context, DC_LOGLEVEL_DEBUG, "EVA RSP", handshake_rsp, handshake_len); - if (handshake_len >= 2 && handshake_rsp[0] == 0xA5 && handshake_rsp[1] == RPC_OP_HELLO_ACK) + if (handshake_len >= 2 && handshake_rsp[0] == 0xA5 && handshake_rsp[1] == RPC_OP_HELLO_ACK) { + device->handshake_acknowledged = 1; break; + } if (++handshake_skips >= MAX_FOREIGN_SKIPS) { ERROR (context, "No EVA handshake response among the frames on the link."); status = DC_STATUS_TIMEOUT; @@ -535,6 +540,15 @@ suunto_nautic_device_close (dc_device_t *abstract) return dc_iostream_close (device->iostream); } +int +suunto_nautic_device_handshake_acknowledged (dc_device_t *abstract) +{ + if (abstract == NULL || abstract->vtable->type != DC_FAMILY_SUUNTO_NAUTIC) + return 0; + + return ((suunto_nautic_device_t *) abstract)->handshake_acknowledged; +} + dc_status_t suunto_nautic_device_request (dc_device_t *abstract, const char *path, dc_buffer_t *response) { diff --git a/src/suunto_nautic_parser.c b/src/suunto_nautic_parser.c index 634417f..b6a6595 100644 --- a/src/suunto_nautic_parser.c +++ b/src/suunto_nautic_parser.c @@ -157,6 +157,13 @@ // (int32 LE, Pa). The end pressures are the app's; the start pressures keep a // transmitter's pre-pairing dropout value (9 bar on 1788683271), so they aren't used. #define SUMMARY_FOOTER_RECORD 1 +// Records 2-4 are the Windows; each carries Temperature.Min/Max (float32 LE, K) +// in the order its schema gives, on both watches. The dive record's pair above is +// reversed by Ocean firmware, so the Windows are read first. +#define SUMMARY_WINDOW_FIRST 2 +#define SUMMARY_WINDOW_COUNT 3 +#define SUMMARY_WINDOW_TEMP_MIN 101 +#define SUMMARY_WINDOW_TEMP_MAX 105 #define SUMMARY_FOOTER_GAS_BASE 282 #define SUMMARY_FOOTER_GAS_END1 29 #define SUMMARY_FOOTER_GAS_END2 37 @@ -351,6 +358,29 @@ suunto_nautic_summary_record (const unsigned char *sbem, size_t size, unsigned i return NULL; } +// Lowest minimum and highest maximum water temperature across the Windows records (K). +static int +suunto_nautic_summary_window_temperatures (const unsigned char *sbem, size_t size, double *tmin, double *tmax) +{ + int found = 0; + for (unsigned int i = 0; i < SUMMARY_WINDOW_COUNT; i++) { + size_t length = 0; + const unsigned char *window = suunto_nautic_summary_record (sbem, size, SUMMARY_WINDOW_FIRST + i, &length); + if (window == NULL || length < SUMMARY_WINDOW_TEMP_MAX + 4) + continue; + double lo = array_float_le (window + SUMMARY_WINDOW_TEMP_MIN); + double hi = array_float_le (window + SUMMARY_WINDOW_TEMP_MAX); + if (!(lo > 250.0 && hi < 330.0 && lo <= hi)) + continue; + if (!found || lo < *tmin) + *tmin = lo; + if (!found || hi > *tmax) + *tmax = hi; + found = 1; + } + return found; +} + // The /Summary's dive record: the last record long enough to hold its fields. static const unsigned char * suunto_nautic_summary_dive_record (const unsigned char *sbem, size_t size) @@ -994,8 +1024,11 @@ suunto_nautic_parser_parse (dc_parser_t *abstract, dc_sample_callback_t callback double avgdepth = array_float_le (record + SUMMARY_DIVE_AVGDEPTH); if (avgdepth > 0.0 && avgdepth <= parser->maxdepth) parser->avgdepth = avgdepth; - double tmin = array_float_le (record + SUMMARY_DIVE_TEMP_MIN); - double tmax = array_float_le (record + SUMMARY_DIVE_TEMP_MAX); + double tmin = 0.0, tmax = 0.0; + if (!suunto_nautic_summary_window_temperatures (sbem, sbem_size, &tmin, &tmax)) { + tmin = array_float_le (record + SUMMARY_DIVE_TEMP_MIN); + tmax = array_float_le (record + SUMMARY_DIVE_TEMP_MAX); + } if (tmin > tmax) { double t = tmin; tmin = tmax;