From 472e9e984cdf522219adbf8aebc4c2f9778fc5c7 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 12 Apr 2018 21:18:27 +0200 Subject: [PATCH] Add support for the Tecdiving DiveComputer.eu --- examples/common.c | 1 + include/libdivecomputer/common.h | 2 + msvc/libdivecomputer.vcproj | 12 + src/Makefile.am | 1 + src/descriptor.c | 18 + src/device.c | 4 + src/parser.c | 4 + src/tecdiving_divecomputereu.c | 542 ++++++++++++++++++++++++++ src/tecdiving_divecomputereu.h | 43 ++ src/tecdiving_divecomputereu_parser.c | 186 +++++++++ 10 files changed, 813 insertions(+) create mode 100644 src/tecdiving_divecomputereu.c create mode 100644 src/tecdiving_divecomputereu.h create mode 100644 src/tecdiving_divecomputereu_parser.c diff --git a/examples/common.c b/examples/common.c index 785c8d0..b97343f 100644 --- a/examples/common.c +++ b/examples/common.c @@ -88,6 +88,7 @@ static const backend_table_t g_backends[] = { {"aqualand", DC_FAMILY_CITIZEN_AQUALAND, 0}, {"idive", DC_FAMILY_DIVESYSTEM_IDIVE, 0x03}, {"cochran", DC_FAMILY_COCHRAN_COMMANDER, 0}, + {"divecomputereu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0}, }; static const transport_table_t g_transports[] = { diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h index a82a6d2..1058b01 100644 --- a/include/libdivecomputer/common.h +++ b/include/libdivecomputer/common.h @@ -101,6 +101,8 @@ typedef enum dc_family_t { DC_FAMILY_DIVESYSTEM_IDIVE = (13 << 16), /* Cochran */ DC_FAMILY_COCHRAN_COMMANDER = (14 << 16), + /* Tecdiving */ + DC_FAMILY_TECDIVING_DIVECOMPUTEREU = (15 << 16), } dc_family_t; #ifdef __cplusplus diff --git a/msvc/libdivecomputer.vcproj b/msvc/libdivecomputer.vcproj index 915807b..1a88ff8 100644 --- a/msvc/libdivecomputer.vcproj +++ b/msvc/libdivecomputer.vcproj @@ -482,6 +482,14 @@ RelativePath="..\src\suunto_vyper_parser.c" > + + + + @@ -816,6 +824,10 @@ RelativePath="..\src\suunto_vyper2.h" > + + diff --git a/src/Makefile.am b/src/Makefile.am index 5ed2dd4..6afa24e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -70,6 +70,7 @@ libdivecomputer_la_SOURCES = \ array.h array.c \ buffer.c \ cochran_commander.h cochran_commander.c cochran_commander_parser.c \ + tecdiving_divecomputereu.h tecdiving_divecomputereu.c tecdiving_divecomputereu_parser.c \ socket.h socket.c \ irda.c \ usbhid.c \ diff --git a/src/descriptor.c b/src/descriptor.c index 6dd2a31..c4e86ca 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -33,6 +33,7 @@ static int dc_filter_uwatec (dc_transport_t transport, const void *userdata); static int dc_filter_suunto (dc_transport_t transport, const void *userdata); static int dc_filter_shearwater (dc_transport_t transport, const void *userdata); static int dc_filter_hw (dc_transport_t transport, const void *userdata); +static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata); static dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item); @@ -323,6 +324,8 @@ static const dc_descriptor_t g_descriptors[] = { {"Cochran", "EMC-14", DC_FAMILY_COCHRAN_COMMANDER, 3, DC_TRANSPORT_SERIAL, NULL}, {"Cochran", "EMC-16", DC_FAMILY_COCHRAN_COMMANDER, 4, DC_TRANSPORT_SERIAL, NULL}, {"Cochran", "EMC-20H", DC_FAMILY_COCHRAN_COMMANDER, 5, DC_TRANSPORT_SERIAL, NULL}, + /* Tecdiving DiveComputer.eu */ + {"Tecdiving", "DiveComputer.eu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_tecdiving}, }; static int @@ -447,6 +450,21 @@ static int dc_filter_shearwater (dc_transport_t transport, const void *userdata) return 1; } +static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata) +{ + static const char *bluetooth[] = { + "DiveComputer", + }; + + if (transport == DC_TRANSPORT_BLUETOOTH) { + return dc_filter_internal_name ((const char *) userdata, bluetooth, C_ARRAY_SIZE(bluetooth)); + } else if (transport == DC_TRANSPORT_SERIAL) { + return dc_filter_internal_rfcomm ((const char *) userdata); + } + + return 1; +} + dc_status_t dc_descriptor_iterator (dc_iterator_t **out) { diff --git a/src/device.c b/src/device.c index 83c71dc..fc59464 100644 --- a/src/device.c +++ b/src/device.c @@ -55,6 +55,7 @@ #include "citizen_aqualand.h" #include "divesystem_idive.h" #include "cochran_commander.h" +#include "tecdiving_divecomputereu.h" #include "device-private.h" #include "context-private.h" @@ -203,6 +204,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr case DC_FAMILY_COCHRAN_COMMANDER: rc = cochran_commander_device_open (&device, context, iostream); break; + case DC_FAMILY_TECDIVING_DIVECOMPUTEREU: + rc = tecdiving_divecomputereu_device_open (&device, context, iostream); + break; default: return DC_STATUS_INVALIDARGS; } diff --git a/src/parser.c b/src/parser.c index 366f8b7..064328d 100644 --- a/src/parser.c +++ b/src/parser.c @@ -55,6 +55,7 @@ #include "citizen_aqualand.h" #include "divesystem_idive.h" #include "cochran_commander.h" +#include "tecdiving_divecomputereu.h" #include "context-private.h" #include "parser-private.h" @@ -164,6 +165,9 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa case DC_FAMILY_COCHRAN_COMMANDER: rc = cochran_commander_parser_create (&parser, context, model); break; + case DC_FAMILY_TECDIVING_DIVECOMPUTEREU: + rc = tecdiving_divecomputereu_parser_create (&parser, context); + break; default: return DC_STATUS_INVALIDARGS; } diff --git a/src/tecdiving_divecomputereu.c b/src/tecdiving_divecomputereu.c new file mode 100644 index 0000000..bb2a35d --- /dev/null +++ b/src/tecdiving_divecomputereu.c @@ -0,0 +1,542 @@ +/* + * libdivecomputer + * + * Copyright (C) 2018 Jef Driesen + * + * 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 // memcmp, memcpy +#include // malloc, free + +#include "tecdiving_divecomputereu.h" +#include "context-private.h" +#include "device-private.h" +#include "array.h" + +#define ISINSTANCE(device) dc_device_isinstance((device), &tecdiving_divecomputereu_device_vtable) + +#define MAXRETRIES 14 + +#define STX 0x7E + +#define CMD_INIT 0x53 +#define CMD_LIST 0x57 +#define CMD_DIVE 0x58 +#define CMD_EXIT 0x59 + +#define RSP_INIT 0x56 +#define RSP_LIST CMD_LIST +#define RSP_HEADER 0x51 +#define RSP_PROFILE 0x52 + +#define SZ_MAXCMD 2 +#define SZ_SUMMARY 7 +#define SZ_SAMPLE 8 +#define SZ_INIT 56 +#define SZ_LIST (2 + 0x10000 * SZ_SUMMARY) +#define SZ_HEADER 100 +#define SZ_PROFILE (1000 * SZ_SAMPLE) + +#define NSTEPS 1000 +#define STEP(i,n) (NSTEPS * (i) / (n)) + +typedef struct tecdiving_divecomputereu_device_t { + dc_device_t base; + dc_iostream_t *iostream; + unsigned char fingerprint[SZ_SUMMARY]; + unsigned char version[SZ_INIT]; +} tecdiving_divecomputereu_device_t; + +static dc_status_t tecdiving_divecomputereu_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); +static dc_status_t tecdiving_divecomputereu_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +static dc_status_t tecdiving_divecomputereu_device_close (dc_device_t *abstract); + +static const dc_device_vtable_t tecdiving_divecomputereu_device_vtable = { + sizeof(tecdiving_divecomputereu_device_t), + DC_FAMILY_TECDIVING_DIVECOMPUTEREU, + tecdiving_divecomputereu_device_set_fingerprint, /* set_fingerprint */ + NULL, /* read */ + NULL, /* write */ + NULL, /* dump */ + tecdiving_divecomputereu_device_foreach, /* foreach */ + NULL, /* timesync */ + tecdiving_divecomputereu_device_close, /* close */ +}; + +static unsigned short +checksum_crc (const unsigned char data[], unsigned int size, unsigned short init) +{ + unsigned short crc = init; + for (unsigned int i = 0; i < size; ++i) { + crc ^= data[i] << 8; + if (crc & 0x8000) { + crc <<= 1; + crc ^= 0x1021; + } else { + crc <<= 1; + } + } + + return crc; +} + +static dc_status_t +tecdiving_divecomputereu_send (tecdiving_divecomputereu_device_t *device, unsigned char cmd, const unsigned char data[], size_t size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + unsigned short crc = 0; + + if (device_is_cancelled (abstract)) + return DC_STATUS_CANCELLED; + + if (size > SZ_MAXCMD) + return DC_STATUS_INVALIDARGS; + + // Setup the data packet + unsigned char packet[SZ_MAXCMD + 11] = { + STX, + 0x00, + (size >> 0) & 0xFF, + (size >> 8) & 0xFF, + (size >> 16) & 0xFF, + (size >> 24) & 0xFF, + cmd, + }; + memcpy(packet + 7, data, size); + crc = checksum_crc (packet + 1, size + 6, 0); + packet[size + 7] = (crc >> 8) & 0xFF; + packet[size + 8] = (crc ) & 0xFF; + packet[size + 9] = 0x00; + packet[size + 10] = 0x00; + + // Give the dive computer some extra time. + dc_iostream_sleep (device->iostream, 300); + + // Send the data packet. + status = dc_iostream_write (device->iostream, packet, size + 11, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the command."); + return status; + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +tecdiving_divecomputereu_receive (tecdiving_divecomputereu_device_t *device, unsigned char rsp, unsigned char data[], size_t size, size_t *actual) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + unsigned char header[7]; + unsigned int nretries = 0; + + // Read the packet start byte. + // Unfortunately it takes a relative long time, about 6-8 seconds, + // before the STX byte arrives. Hence the standard timeout of one + // second is not sufficient, and we need to retry a few times on + // timeout. The advantage over using a single read operation with a + // large timeout is that we can give the user a chance to cancel the + // operation. + while (1) { + status = dc_iostream_read (device->iostream, header + 0, 1, NULL); + if (status != DC_STATUS_SUCCESS) { + if (status != DC_STATUS_TIMEOUT) { + ERROR (abstract->context, "Failed to receive the packet start byte."); + return status; + } + + // Abort if the maximum number of retries is reached. + if (nretries++ >= MAXRETRIES) + return status; + + // Cancel if requested by the user. + if (device_is_cancelled (abstract)) + return DC_STATUS_CANCELLED; + + // Try again. + continue; + } + + if (header[0] == STX) + break; + + // Reset the retry counter. + nretries = 0; + } + + // Read the packet header. + status = dc_iostream_read (device->iostream, header + 1, sizeof(header) - 1, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the packet header."); + return status; + } + + // Verify the type byte. + unsigned int type = header[1]; + if (type != 0x00) { + ERROR (abstract->context, "Unexpected type byte (%02x).", type); + return DC_STATUS_PROTOCOL; + } + + // Verify the length. + unsigned int length = array_uint32_le (header + 2); + if (length > size) { + ERROR (abstract->context, "Unexpected packet length (%u).", length); + return DC_STATUS_PROTOCOL; + } + + // Get the command type. + unsigned int cmd = header[6]; + if (cmd != rsp) { + ERROR (abstract->context, "Unexpected command byte (%02x).", cmd); + return DC_STATUS_PROTOCOL; + } + + size_t nbytes = 0; + while (nbytes < length) { + // Set the maximum packet size. + size_t len = 1000; + + // Limit the packet size to the total size. + if (nbytes + len > length) + len = length - nbytes; + + // Read the packet payload. + status = dc_iostream_read (device->iostream, data + nbytes, len, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the packet payload."); + return status; + } + + nbytes += len; + } + + // Read the packet checksum. + unsigned char checksum[4]; + status = dc_iostream_read (device->iostream, checksum, sizeof(checksum), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the packet checksum."); + return status; + } + + // Verify the checksum. + unsigned short crc = array_uint16_be (checksum); + unsigned short ccrc = 0; + ccrc = checksum_crc (header + 1, sizeof(header) - 1, ccrc); + ccrc = checksum_crc (data, length, ccrc); + if (crc != ccrc || checksum[2] != 0x00 || checksum[3] != 0) { + ERROR (abstract->context, "Unexpected packet checksum."); + return DC_STATUS_PROTOCOL; + } + + if (actual == NULL) { + // Verify the actual length. + if (length != size) { + ERROR (abstract->context, "Unexpected packet length (%u).", length); + return DC_STATUS_PROTOCOL; + } + } else { + // Return the actual length. + *actual = length; + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +tecdiving_divecomputereu_readdive (dc_device_t *abstract, dc_event_progress_t *progress, unsigned int idx, dc_buffer_t *buffer) +{ + dc_status_t status = DC_STATUS_SUCCESS; + tecdiving_divecomputereu_device_t *device = (tecdiving_divecomputereu_device_t *) abstract; + + // Erase the buffer. + dc_buffer_clear (buffer); + + // Encode the one based logbook ID. + unsigned int number = idx + 1; + unsigned char id[] = { + (number >> 8) & 0xFF, + (number ) & 0xFF, + }; + + // Request the dive. + status = tecdiving_divecomputereu_send (device, CMD_DIVE, id, sizeof(id)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the dive command."); + return status; + } + + // Read the dive header. + unsigned char header[SZ_HEADER]; + status = tecdiving_divecomputereu_receive (device, RSP_HEADER, header, sizeof(header), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the dive header."); + return status; + } + + // Get the number of samples. + unsigned int nsamples = array_uint32_be (header + 36); + + // Calculate the total size. + unsigned int size = sizeof(header) + nsamples * SZ_SAMPLE; + + // Update and emit a progress event. + if (progress) { + progress->current = (idx + 1) * NSTEPS + STEP(sizeof(header), size); + device_event_emit (abstract, DC_EVENT_PROGRESS, progress); + } + + // Allocate memory for the dive. + if (!dc_buffer_resize (buffer, size)) { + ERROR (abstract->context, "Insufficient buffer space available."); + return DC_STATUS_NOMEMORY; + } + + // Cache the pointer. + unsigned char *data = dc_buffer_get_data(buffer); + + // Append the header. + memcpy (data, header, sizeof(header)); + + unsigned int nbytes = sizeof(header); + while (nbytes < size) { + // Get the packet size. The maximum size for a single data + // packet is 1000 samples. + unsigned int len = size - nbytes; + if (len > SZ_PROFILE) + len = SZ_PROFILE; + + // Read the dive samples. + status = tecdiving_divecomputereu_receive (device, RSP_PROFILE, data + nbytes, len, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the dive samples."); + return status; + } + + nbytes += len; + + // Update and emit a progress event. + if (progress) { + progress->current = (idx + 1) * NSTEPS + STEP(nbytes, size); + device_event_emit (abstract, DC_EVENT_PROGRESS, progress); + } + } + + return DC_STATUS_SUCCESS; +} + +dc_status_t +tecdiving_divecomputereu_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) +{ + dc_status_t status = DC_STATUS_SUCCESS; + tecdiving_divecomputereu_device_t *device = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + device = (tecdiving_divecomputereu_device_t *) dc_device_allocate (context, &tecdiving_divecomputereu_device_vtable); + if (device == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Set the default values. + device->iostream = iostream; + memset (device->fingerprint, 0, sizeof (device->fingerprint)); + + // Set the serial communication protocol (115200 8N1). + status = dc_iostream_configure (device->iostream, 115200, 8, DC_PARITY_NONE, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the terminal attributes."); + goto error_free; + } + + // Set the timeout for receiving data (1000ms). + status = dc_iostream_set_timeout (device->iostream, 1000); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the timeout."); + goto error_free; + } + + // Send the init command. + status = tecdiving_divecomputereu_send (device, CMD_INIT, NULL, 0); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to send the init command."); + goto error_free; + } + + // Read the device info. + status = tecdiving_divecomputereu_receive (device, RSP_INIT, device->version, sizeof(device->version), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to receive the device info."); + goto error_free; + } + + *out = (dc_device_t *) device; + + return DC_STATUS_SUCCESS; + +error_free: + dc_device_deallocate ((dc_device_t *) device); + return status; +} + +static dc_status_t +tecdiving_divecomputereu_device_close (dc_device_t *abstract) +{ + dc_status_t status = DC_STATUS_SUCCESS; + tecdiving_divecomputereu_device_t *device = (tecdiving_divecomputereu_device_t *) abstract; + + status = tecdiving_divecomputereu_send (device, CMD_EXIT, NULL, 0); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the exit command."); + return status; + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +tecdiving_divecomputereu_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size) +{ + tecdiving_divecomputereu_device_t *device = (tecdiving_divecomputereu_device_t *) abstract; + + if (size && size != sizeof (device->fingerprint)) + return DC_STATUS_INVALIDARGS; + + if (size) + memcpy (device->fingerprint, data, sizeof (device->fingerprint)); + else + memset (device->fingerprint, 0, sizeof (device->fingerprint)); + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +tecdiving_divecomputereu_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) +{ + dc_status_t status = DC_STATUS_SUCCESS; + tecdiving_divecomputereu_device_t *device = (tecdiving_divecomputereu_device_t *) abstract; + + // Enable progress notifications. + dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Emit a device info event. + dc_event_devinfo_t devinfo; + devinfo.model = 0; + devinfo.firmware = 0; + devinfo.serial = array_uint16_be (device->version + 0x22) << 16 | array_uint16_be (device->version + 0x26); + device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); + + // Emit a vendor event. + dc_event_vendor_t vendor; + vendor.data = device->version; + vendor.size = sizeof(device->version); + device_event_emit (abstract, DC_EVENT_VENDOR, &vendor); + + // Allocate memory for the dive list. + size_t length = SZ_LIST; + unsigned char *logbook = (unsigned char *) malloc (length); + if (logbook == NULL) { + status = DC_STATUS_NOMEMORY; + goto error_exit; + } + + // Request the dive list. + status = tecdiving_divecomputereu_send (device, CMD_LIST, NULL, 0); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the list command."); + goto error_free; + } + + // Read the dive list. + status = tecdiving_divecomputereu_receive (device, RSP_LIST, logbook, length, &length); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the logbook."); + goto error_free; + } + + // Verify the minimum length. + if (length < 2) { + status = DC_STATUS_DATAFORMAT; + goto error_free; + } + + // Get the number of logbook entries. + unsigned int nlogbooks = array_uint16_be (logbook); + if (length != 2 + nlogbooks * SZ_SUMMARY) { + status = DC_STATUS_DATAFORMAT; + goto error_free; + } + + // Count the number of dives to download. + unsigned int ndives = 0; + for (unsigned int i = 0; i < nlogbooks; ++i) { + unsigned int offset = 2 + i * SZ_SUMMARY; + + if (memcmp(logbook + offset, device->fingerprint, sizeof(device->fingerprint)) == 0) + break; + + ndives++; + } + + // Update and emit a progress event. + progress.current = 1 * NSTEPS; + progress.maximum = (ndives + 1) * NSTEPS; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Allocate a memory buffer for a single dive. + dc_buffer_t *buffer = dc_buffer_new(0); + if (buffer == NULL) { + status = DC_STATUS_NOMEMORY; + goto error_free; + } + + for (unsigned int i = 0; i < ndives; ++i) { + unsigned int offset = 2 + i * SZ_SUMMARY; + + // Read the dive. + status = tecdiving_divecomputereu_readdive (abstract, &progress, i, buffer); + if (status != DC_STATUS_SUCCESS) { + goto error_free; + } + + // Cache the pointer. + unsigned char *data = dc_buffer_get_data(buffer); + unsigned int size = dc_buffer_get_size(buffer); + + // Verify the logbook entry. + if (memcmp (data, logbook + offset, SZ_SUMMARY) != 0) { + ERROR (abstract->context, "Dive header doesn't match logbook entry."); + status = DC_STATUS_DATAFORMAT; + goto error_free; + } + + if (callback && !callback (data, size, data, sizeof(device->fingerprint), userdata)) { + break; + } + } + +error_free: + dc_buffer_free (buffer); + free (logbook); +error_exit: + return status; +} diff --git a/src/tecdiving_divecomputereu.h b/src/tecdiving_divecomputereu.h new file mode 100644 index 0000000..714ae43 --- /dev/null +++ b/src/tecdiving_divecomputereu.h @@ -0,0 +1,43 @@ +/* + * libdivecomputer + * + * Copyright (C) 2018 Jef Driesen + * + * 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 TECDIVING_DIVECOMPUTEREU_H +#define TECDIVING_DIVECOMPUTEREU_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +dc_status_t +tecdiving_divecomputereu_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); + +dc_status_t +tecdiving_divecomputereu_parser_create (dc_parser_t **parser, dc_context_t *context); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* TECDIVING_DIVECOMPUTEREU_H */ diff --git a/src/tecdiving_divecomputereu_parser.c b/src/tecdiving_divecomputereu_parser.c new file mode 100644 index 0000000..a3006ea --- /dev/null +++ b/src/tecdiving_divecomputereu_parser.c @@ -0,0 +1,186 @@ +/* + * libdivecomputer + * + * Copyright (C) 2018 Jef Driesen + * + * 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 + +#include "tecdiving_divecomputereu.h" +#include "context-private.h" +#include "parser-private.h" +#include "array.h" + +#define ISINSTANCE(parser) dc_device_isinstance((parser), &tecdiving_divecomputereu_parser_vtable) + +#define SZ_HEADER 100 + +typedef struct tecdiving_divecomputereu_parser_t tecdiving_divecomputereu_parser_t; + +struct tecdiving_divecomputereu_parser_t { + dc_parser_t base; +}; + +static dc_status_t tecdiving_divecomputereu_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t tecdiving_divecomputereu_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); +static dc_status_t tecdiving_divecomputereu_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); +static dc_status_t tecdiving_divecomputereu_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); + +static const dc_parser_vtable_t tecdiving_divecomputereu_parser_vtable = { + sizeof(tecdiving_divecomputereu_parser_t), + DC_FAMILY_TECDIVING_DIVECOMPUTEREU, + tecdiving_divecomputereu_parser_set_data, /* set_data */ + tecdiving_divecomputereu_parser_get_datetime, /* datetime */ + tecdiving_divecomputereu_parser_get_field, /* fields */ + tecdiving_divecomputereu_parser_samples_foreach, /* samples_foreach */ + NULL /* destroy */ +}; + + +dc_status_t +tecdiving_divecomputereu_parser_create (dc_parser_t **out, dc_context_t *context) +{ + tecdiving_divecomputereu_parser_t *parser = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + parser = (tecdiving_divecomputereu_parser_t *) dc_parser_allocate (context, &tecdiving_divecomputereu_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 +tecdiving_divecomputereu_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) +{ + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +tecdiving_divecomputereu_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) +{ + const unsigned char *data = abstract->data; + + if (abstract->size < SZ_HEADER) + return DC_STATUS_DATAFORMAT; + + if (datetime) { + datetime->year = data[2] + 2000; + datetime->month = data[3]; + datetime->day = data[4]; + datetime->hour = data[5]; + datetime->minute = data[6]; + datetime->second = data[7]; + datetime->timezone = DC_TIMEZONE_NONE; + } + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +tecdiving_divecomputereu_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) +{ + const unsigned char *data = abstract->data; + + if (abstract->size < SZ_HEADER) + return DC_STATUS_DATAFORMAT; + + if (value) { + switch (type) { + case DC_FIELD_DIVETIME: + *((unsigned int *) value) = array_uint16_be (data + 23) * 60; + break; + case DC_FIELD_AVGDEPTH: + *((double *) value) = array_uint16_be (data + 27) / 100; + break; + case DC_FIELD_MAXDEPTH: + *((double *) value) = array_uint16_be (data + 29) / 10; + break; + case DC_FIELD_ATMOSPHERIC: + *((double *) value) = array_uint16_be (data + 14) / 1000.0; + break; + case DC_FIELD_TEMPERATURE_SURFACE: + *((double *) value) = (signed char) data[17]; + break; + case DC_FIELD_TEMPERATURE_MINIMUM: + *((double *) value) = (signed char) data[41]; + break; + case DC_FIELD_TEMPERATURE_MAXIMUM: + *((double *) value) = (signed char) data[42]; + break; + default: + return DC_STATUS_UNSUPPORTED; + } + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +tecdiving_divecomputereu_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +{ + const unsigned char *data = abstract->data; + unsigned int size = abstract->size; + + unsigned int time = 0; + unsigned int interval = data[47]; + + unsigned int offset = SZ_HEADER; + while (offset + 8 <= size) { + dc_sample_value_t sample = {0}; + + // Time (seconds). + time += interval; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + + // Depth (1/10 m). + unsigned int depth = array_uint16_be (data + offset + 2); + sample.depth = depth / 10.0; + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + + // Temperature (Celsius). + signed int temperature = (signed char) data[offset]; + sample.temperature = temperature; + if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); + + // ppO2 + unsigned int ppo2 = data[offset + 1]; + sample.ppo2 = ppo2 / 10.0; + if (callback) callback (DC_SAMPLE_PPO2, sample, userdata); + + // Setpoint + unsigned int setpoint = data[offset + 4]; + sample.setpoint = setpoint / 10.0; + if (callback) callback (DC_SAMPLE_SETPOINT, sample, userdata); + + offset += 8; + } + + return DC_STATUS_SUCCESS; +}