From 4bbebe7c32a1ca45709fd90753d0ccba96cd3076 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 16 Jan 2019 21:15:43 +0100 Subject: [PATCH] Add clock synchronization support The Ratio dive computers support synchronizing the internal clock. One complication is that recent firmware versions (4.0.56 or 4.1.10) support two timezones (home and abroad), while the libdivecomputer api only supports one timezone. To deal with this, the most recent firmware versions (4.0.58 or 4.1.12) will interprete an invalid timezone index (0xFF) as leaving the timezone unchanged. If the firmware doesn't support the dual timezone command, or if the firmware doesn't have the invalid timezone index modification yet, then a fallback to the single timezone command is provided. Note that in the latter case the side effect is that both timezones will be changed! --- src/divesystem_idive.c | 131 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 1 deletion(-) diff --git a/src/divesystem_idive.c b/src/divesystem_idive.c index b9e4efc..1119b23 100644 --- a/src/divesystem_idive.c +++ b/src/divesystem_idive.c @@ -28,6 +28,8 @@ #include "checksum.h" #include "array.h" +#define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array)) + #define ISINSTANCE(device) dc_device_isinstance((device), &divesystem_idive_device_vtable) #define ISIX3M(model) ((model) >= 0x21) @@ -48,6 +50,7 @@ #define CMD_IX3M_RANGE 0x78 #define CMD_IX3M_HEADER 0x79 #define CMD_IX3M_SAMPLE 0x7A +#define CMD_IX3M_TIMESYNC 0x13 #define ERR_INVALID_CMD 0x10 #define ERR_INVALID_LENGTH 0x20 @@ -60,6 +63,10 @@ #define NSTEPS 1000 #define STEP(i,n) (NSTEPS * (i) / (n)) +#define EPOCH 1199145600 /* 2008-01-01 00:00:00 */ + +#define TZ_IDX_UNCHANGED 0xFF + typedef struct divesystem_idive_command_t { unsigned char cmd; unsigned int size; @@ -82,6 +89,7 @@ typedef struct divesystem_idive_device_t { static dc_status_t divesystem_idive_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); static dc_status_t divesystem_idive_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +static dc_status_t divesystem_idive_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime); static const dc_device_vtable_t divesystem_idive_device_vtable = { sizeof(divesystem_idive_device_t), @@ -91,7 +99,7 @@ static const dc_device_vtable_t divesystem_idive_device_vtable = { NULL, /* write */ NULL, /* dump */ divesystem_idive_device_foreach, /* foreach */ - NULL, /* timesync */ + divesystem_idive_device_timesync, /* timesync */ NULL /* close */ }; @@ -523,3 +531,124 @@ divesystem_idive_device_foreach (dc_device_t *abstract, dc_dive_callback_t callb return DC_STATUS_SUCCESS; } + +static dc_status_t +divesystem_idive_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + divesystem_idive_device_t *device = (divesystem_idive_device_t *) abstract; + unsigned int errcode = 0; + + static const signed char tz_array[] = { + -12, 0, /* UTC-12 */ + -11, 0, /* UTC-11 */ + -10, 0, /* UTC-10 */ + -9, 30, /* UTC-9:30 */ + -9, 0, /* UTC-9 */ + -8, 0, /* UTC-8 */ + -7, 0, /* UTC-7 */ + -6, 0, /* UTC-6 */ + -5, 0, /* UTC-5 */ + -4, 30, /* UTC-4:30 */ + -4, 0, /* UTC-4 */ + -3, 30, /* UTC-3:30 */ + -3, 0, /* UTC-3 */ + -2, 0, /* UTC-2 */ + -1, 0, /* UTC-1 */ + 0, 0, /* UTC */ + 1, 0, /* UTC+1 */ + 2, 0, /* UTC+2 */ + 3, 0, /* UTC+3 */ + 3, 30, /* UTC+3:30 */ + 4, 0, /* UTC+4 */ + 4, 30, /* UTC+4:30 */ + 5, 0, /* UTC+5 */ + 5, 30, /* UTC+5:30 */ + 5, 45, /* UTC+5:45 */ + 6, 0, /* UTC+6 */ + 6, 30, /* UTC+6:30 */ + 7, 0, /* UTC+7 */ + 8, 0, /* UTC+8 */ + 8, 45, /* UTC+8:45 */ + 9, 0, /* UTC+9 */ + 9, 30, /* UTC+9:30 */ + 9, 45, /* UTC+9:45 */ + 10, 0, /* UTC+10 */ + 10, 30, /* UTC+10:30 */ + 11, 0, /* UTC+11 */ + 11, 30, /* UTC+11:30 */ + 12, 0, /* UTC+12 */ + 12, 45, /* UTC+12:45 */ + 13, 0, /* UTC+13 */ + 13, 45, /* UTC+13:45 */ + 14, 0 /* UTC+14 */ + }; + + if (!ISIX3M(device->model)) { + return DC_STATUS_UNSUPPORTED; + } + + if (datetime == NULL) { + ERROR (abstract->context, "Invalid parameter specified."); + return DC_STATUS_INVALIDARGS; + } + + // Get the UTC timestamp. + dc_ticks_t timestamp = dc_datetime_mktime(datetime); + if (timestamp == -1) { + ERROR (abstract->context, "Invalid date/time value specified."); + return DC_STATUS_INVALIDARGS; + } + + // Adjust the epoch. + timestamp -= EPOCH; + + // Find the timezone index. + size_t tz_idx = C_ARRAY_SIZE(tz_array); + for (size_t i = 0; i < C_ARRAY_SIZE(tz_array); i += 2) { + int timezone = tz_array[i] * 3600; + if (timezone < 0) { + timezone -= tz_array[i + 1] * 60; + } else { + timezone += tz_array[i + 1] * 60; + } + + if (timezone == datetime->timezone) { + tz_idx = i; + break; + } + } + if (tz_idx >= C_ARRAY_SIZE(tz_array)) { + ERROR (abstract->context, "Invalid timezone value specified."); + return DC_STATUS_INVALIDARGS; + } + + // Adjust the timezone index. + tz_idx /= 2; + + // Send the command. + unsigned char command[] = { + CMD_IX3M_TIMESYNC, + (timestamp >> 0) & 0xFF, + (timestamp >> 8) & 0xFF, + (timestamp >> 16) & 0xFF, + (timestamp >> 24) & 0xFF, + tz_idx, // Home timezone + TZ_IDX_UNCHANGED}; // Travel timezone + rc = divesystem_idive_transfer (device, command, sizeof(command), NULL, 0, &errcode); + if (rc != DC_STATUS_SUCCESS) { + if (errcode == ERR_INVALID_LENGTH || errcode == ERR_INVALID_DATA) { + // Fallback to the variant without the second timezone if the + // firmware doesn't support two timezones (ERR_INVALID_LENGTH) or + // leaving the timezone unchanged (ERR_INVALID_DATA). + rc = divesystem_idive_transfer (device, command, sizeof(command) - 1, NULL, 0, &errcode); + if (rc != DC_STATUS_SUCCESS) { + return rc; + } + } else { + return rc; + } + } + + return DC_STATUS_SUCCESS; +}