From 3f7af8c349d0ad47ae0f4749702148fe623c44c1 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 16 Dec 2013 13:03:29 +0100 Subject: [PATCH] Add support for the Dive Rite NiTek Q. --- examples/universal.c | 1 + include/libdivecomputer/Makefile.am | 4 +- include/libdivecomputer/common.h | 4 +- include/libdivecomputer/diverite.h | 27 ++ include/libdivecomputer/diverite_nitekq.h | 45 +++ msvc/libdivecomputer.vcproj | 16 + src/Makefile.am | 1 + src/descriptor.c | 2 + src/device.c | 4 + src/diverite_nitekq.c | 443 ++++++++++++++++++++++ src/diverite_nitekq_parser.c | 319 ++++++++++++++++ src/libdivecomputer.symbols | 3 + src/parser.c | 4 + 13 files changed, 871 insertions(+), 2 deletions(-) create mode 100644 include/libdivecomputer/diverite.h create mode 100644 include/libdivecomputer/diverite_nitekq.h create mode 100644 src/diverite_nitekq.c create mode 100644 src/diverite_nitekq_parser.c diff --git a/examples/universal.c b/examples/universal.c index f7af41b..03d615d 100644 --- a/examples/universal.c +++ b/examples/universal.c @@ -101,6 +101,7 @@ static const backend_table_t g_backends[] = { {"cobalt", DC_FAMILY_ATOMICS_COBALT}, {"predator", DC_FAMILY_SHEARWATER_PREDATOR}, {"petrel", DC_FAMILY_SHEARWATER_PETREL}, + {"nitekq", DC_FAMILY_DIVERITE_NITEKQ}, }; static dc_family_t diff --git a/include/libdivecomputer/Makefile.am b/include/libdivecomputer/Makefile.am index 576c2fd..891f4e0 100644 --- a/include/libdivecomputer/Makefile.am +++ b/include/libdivecomputer/Makefile.am @@ -47,4 +47,6 @@ libdivecomputer_HEADERS = \ atomics_cobalt.h \ shearwater.h \ shearwater_petrel.h \ - shearwater_predator.h + shearwater_predator.h \ + diverite.h \ + diverite_nitekq.h diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h index 2c27744..983ec07 100644 --- a/include/libdivecomputer/common.h +++ b/include/libdivecomputer/common.h @@ -80,7 +80,9 @@ typedef enum dc_family_t { DC_FAMILY_ATOMICS_COBALT = (9 << 16), /* Shearwater */ DC_FAMILY_SHEARWATER_PREDATOR = (10 << 16), - DC_FAMILY_SHEARWATER_PETREL + DC_FAMILY_SHEARWATER_PETREL, + /* Dive Rite */ + DC_FAMILY_DIVERITE_NITEKQ = (11 << 16), } dc_family_t; #ifdef __cplusplus diff --git a/include/libdivecomputer/diverite.h b/include/libdivecomputer/diverite.h new file mode 100644 index 0000000..9bf6bf6 --- /dev/null +++ b/include/libdivecomputer/diverite.h @@ -0,0 +1,27 @@ +/* + * libdivecomputer + * + * Copyright (C) 2013 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 DIVERITE_H +#define DIVERITE_H + +#include "diverite_nitekq.h" + +#endif /* DIVERITE_H */ diff --git a/include/libdivecomputer/diverite_nitekq.h b/include/libdivecomputer/diverite_nitekq.h new file mode 100644 index 0000000..d7dd5e8 --- /dev/null +++ b/include/libdivecomputer/diverite_nitekq.h @@ -0,0 +1,45 @@ +/* + * libdivecomputer + * + * Copyright (C) 2013 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 DIVERITE_NITEKQ_H +#define DIVERITE_NITEKQ_H + +#include "context.h" +#include "device.h" +#include "parser.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +dc_status_t +diverite_nitekq_device_open (dc_device_t **device, dc_context_t *context, const char *name); + +dc_status_t +diverite_nitekq_extract_dives (dc_device_t *device, const unsigned char data[], unsigned int size, dc_dive_callback_t callback, void *userdata); + +dc_status_t +diverite_nitekq_parser_create (dc_parser_t **parser, dc_context_t *context); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* DIVERITE_NITEKQ_H */ diff --git a/msvc/libdivecomputer.vcproj b/msvc/libdivecomputer.vcproj index 95b9b84..78a8190 100644 --- a/msvc/libdivecomputer.vcproj +++ b/msvc/libdivecomputer.vcproj @@ -230,6 +230,14 @@ RelativePath="..\src\device.c" > + + + + @@ -508,6 +516,14 @@ RelativePath="..\include\libdivecomputer\device.h" > + + + + diff --git a/src/Makefile.am b/src/Makefile.am index fcab17f..6a39b36 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -51,6 +51,7 @@ libdivecomputer_la_SOURCES = \ shearwater_common.h shearwater_common.c \ shearwater_predator.c shearwater_predator_parser.c \ shearwater_petrel.c \ + diverite_nitekq.c diverite_nitekq_parser.c \ ringbuffer.h ringbuffer.c \ checksum.h checksum.c \ array.h array.c \ diff --git a/src/descriptor.c b/src/descriptor.c index df29e23..25777aa 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -209,6 +209,8 @@ static const dc_descriptor_t g_descriptors[] = { {"Shearwater", "Predator", DC_FAMILY_SHEARWATER_PREDATOR, 2}, /* Shearwater Petrel */ {"Shearwater", "Petrel", DC_FAMILY_SHEARWATER_PETREL, 3}, + /* Dive Rite NiTek Q */ + {"Dive Rite", "NiTek Q", DC_FAMILY_DIVERITE_NITEKQ, 0}, }; typedef struct dc_descriptor_iterator_t { diff --git a/src/device.c b/src/device.c index eb206c1..22cea50 100644 --- a/src/device.c +++ b/src/device.c @@ -33,6 +33,7 @@ #include #include #include +#include #include "device-private.h" #include "context-private.h" @@ -150,6 +151,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr case DC_FAMILY_SHEARWATER_PETREL: rc = shearwater_petrel_device_open (&device, context, name); break; + case DC_FAMILY_DIVERITE_NITEKQ: + rc = diverite_nitekq_device_open (&device, context, name); + break; default: return DC_STATUS_INVALIDARGS; } diff --git a/src/diverite_nitekq.c b/src/diverite_nitekq.c new file mode 100644 index 0000000..067296d --- /dev/null +++ b/src/diverite_nitekq.c @@ -0,0 +1,443 @@ +/* + * libdivecomputer + * + * Copyright (C) 2013 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 +#include + +#include + +#include "context-private.h" +#include "device-private.h" +#include "checksum.h" +#include "serial.h" +#include "array.h" + +#define ISINSTANCE(device) dc_device_isinstance((device), &diverite_nitekq_device_vtable) + +#define EXITCODE(rc) \ +( \ + rc == -1 ? DC_STATUS_IO : DC_STATUS_TIMEOUT \ +) + +#define KEEPALIVE 0x3E // '<' +#define BLOCK 0x42 // 'B' +#define DISCONNECT 0x44 // 'D' +#define HANDSHAKE 0x48 // 'H' +#define RESET 0x52 // 'R' +#define UPLOAD 0x55 // 'U' + +#define SZ_PACKET 256 +#define SZ_MEMORY (128 * SZ_PACKET) +#define SZ_LOGBOOK 6 + +#define LOGBOOK 0x0320 +#define ADDRESS 0x0384 +#define EOP 0x03E6 +#define RB_PROFILE_BEGIN 0x03E8 +#define RB_PROFILE_END SZ_MEMORY + +typedef struct diverite_nitekq_device_t { + dc_device_t base; + serial_t *port; + unsigned char version[32]; + unsigned char fingerprint[SZ_LOGBOOK]; +} diverite_nitekq_device_t; + +static dc_status_t diverite_nitekq_device_set_fingerprint (dc_device_t *device, const unsigned char data[], unsigned int size); +static dc_status_t diverite_nitekq_device_dump (dc_device_t *abstract, dc_buffer_t *buffer); +static dc_status_t diverite_nitekq_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +static dc_status_t diverite_nitekq_device_close (dc_device_t *abstract); + +static const dc_device_vtable_t diverite_nitekq_device_vtable = { + DC_FAMILY_DIVERITE_NITEKQ, + diverite_nitekq_device_set_fingerprint, /* set_fingerprint */ + NULL, /* read */ + NULL, /* write */ + diverite_nitekq_device_dump, /* dump */ + diverite_nitekq_device_foreach, /* foreach */ + diverite_nitekq_device_close /* close */ +}; + + +static dc_status_t +diverite_nitekq_send (diverite_nitekq_device_t *device, unsigned char cmd) +{ + dc_device_t *abstract = (dc_device_t *) device; + + if (device_is_cancelled (abstract)) + return DC_STATUS_CANCELLED; + + // Send the command. + unsigned char command[] = {cmd}; + int n = serial_write (device->port, command, sizeof (command)); + if (n != sizeof (command)) { + ERROR (abstract->context, "Failed to send the command."); + return EXITCODE (n); + } + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +diverite_nitekq_receive (diverite_nitekq_device_t *device, unsigned char data[], unsigned int size) +{ + dc_device_t *abstract = (dc_device_t *) device; + + // Read the answer. + int n = serial_read (device->port, data, size); + if (n != size) { + ERROR (abstract->context, "Failed to receive the answer."); + return EXITCODE (n); + } + + // Read the checksum. + unsigned char checksum[2] = {0}; + n = serial_read (device->port, checksum, sizeof (checksum)); + if (n != sizeof (checksum)) { + ERROR (abstract->context, "Failed to receive the checksum."); + return EXITCODE (n); + } + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +diverite_nitekq_handshake (diverite_nitekq_device_t *device) +{ + dc_device_t *abstract = (dc_device_t *) device; + + // Send the command. + unsigned char command[] = {HANDSHAKE}; + int n = serial_write (device->port, command, sizeof (command)); + if (n != sizeof (command)) { + ERROR (abstract->context, "Failed to send the command."); + return EXITCODE (n); + } + + // Read the answer. + n = serial_read (device->port, device->version, sizeof (device->version)); + if (n != sizeof (device->version)) { + ERROR (abstract->context, "Failed to receive the answer."); + return EXITCODE (n); + } + + return DC_STATUS_SUCCESS; +} + + +dc_status_t +diverite_nitekq_device_open (dc_device_t **out, dc_context_t *context, const char *name) +{ + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + diverite_nitekq_device_t *device = (diverite_nitekq_device_t *) malloc (sizeof (diverite_nitekq_device_t)); + if (device == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Initialize the base class. + device_init (&device->base, context, &diverite_nitekq_device_vtable); + + // Set the default values. + device->port = NULL; + + // Open the device. + int rc = serial_open (&device->port, context, name); + if (rc == -1) { + ERROR (context, "Failed to open the serial port."); + free (device); + return DC_STATUS_IO; + } + + // Set the serial communication protocol (9600 8N1). + rc = serial_configure (device->port, 9600, 8, SERIAL_PARITY_NONE, 1, SERIAL_FLOWCONTROL_NONE); + if (rc == -1) { + ERROR (context, "Failed to set the terminal attributes."); + serial_close (device->port); + free (device); + return DC_STATUS_IO; + } + + // Set the timeout for receiving data (1000ms). + if (serial_set_timeout (device->port, 1000) == -1) { + ERROR (context, "Failed to set the timeout."); + serial_close (device->port); + free (device); + return DC_STATUS_IO; + } + + // Make sure everything is in a sane state. + serial_sleep (device->port, 100); + serial_flush (device->port, SERIAL_QUEUE_BOTH); + + // Perform the handshaking. + dc_status_t status = diverite_nitekq_handshake (device); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to handshake."); + serial_close (device->port); + free (device); + return status; + } + + + *out = (dc_device_t*) device; + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +diverite_nitekq_device_close (dc_device_t *abstract) +{ + diverite_nitekq_device_t *device = (diverite_nitekq_device_t*) abstract; + + // Disconnect. + diverite_nitekq_send (device, DISCONNECT); + + // Close the device. + if (serial_close (device->port) == -1) { + free (device); + return DC_STATUS_IO; + } + + // Free memory. + free (device); + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +diverite_nitekq_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size) +{ + diverite_nitekq_device_t *device = (diverite_nitekq_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 +diverite_nitekq_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) +{ + diverite_nitekq_device_t *device = (diverite_nitekq_device_t*) abstract; + dc_status_t rc = DC_STATUS_SUCCESS; + unsigned char packet[256] = {0}; + + // Erase the current contents of the buffer. + if (!dc_buffer_clear (buffer) || !dc_buffer_reserve (buffer, SZ_PACKET + SZ_MEMORY)) { + ERROR (abstract->context, "Insufficient buffer space available."); + return DC_STATUS_NOMEMORY; + } + + // Enable progress notifications. + dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; + progress.maximum = SZ_PACKET + SZ_MEMORY; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // 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); + + // Emit a device info event. + dc_event_devinfo_t devinfo; + devinfo.model = 0; + devinfo.firmware = 0; + devinfo.serial = 0; + for (unsigned int i = 0; i < 4; ++i) { + devinfo.serial *= 100; + devinfo.serial += bcd2dec (device->version[0x0A + i]); + } + device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); + + // Send the upload request. It's not clear whether this request is + // actually needed, but let's send it anyway. + rc = diverite_nitekq_send (device, UPLOAD); + if (rc != DC_STATUS_SUCCESS) { + return rc; + } + + // Receive the response packet. It's currently not used (or needed) + // for anything, but we prepend it to the main data anyway, in case + // we ever need it in the future. + rc = diverite_nitekq_receive (device, packet, sizeof (packet)); + if (rc != DC_STATUS_SUCCESS) { + return rc; + } + + dc_buffer_append (buffer, packet, sizeof (packet)); + + // Update and emit a progress event. + progress.current += SZ_PACKET; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Send the request to initiate downloading memory blocks. + rc = diverite_nitekq_send (device, RESET); + if (rc != DC_STATUS_SUCCESS) { + return rc; + } + + for (unsigned int i = 0; i < 128; ++i) { + // Request the next memory block. + rc = diverite_nitekq_send (device, BLOCK); + if (rc != DC_STATUS_SUCCESS) { + return rc; + } + + // Receive the memory block. + rc = diverite_nitekq_receive (device, packet, sizeof (packet)); + if (rc != DC_STATUS_SUCCESS) { + return rc; + } + + dc_buffer_append (buffer, packet, sizeof (packet)); + + // Update and emit a progress event. + progress.current += SZ_PACKET; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + } + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +diverite_nitekq_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) +{ + dc_buffer_t *buffer = dc_buffer_new (0); + if (buffer == NULL) + return DC_STATUS_NOMEMORY; + + dc_status_t rc = diverite_nitekq_device_dump (abstract, buffer); + if (rc != DC_STATUS_SUCCESS) { + dc_buffer_free (buffer); + return rc; + } + + rc = diverite_nitekq_extract_dives (abstract, + dc_buffer_get_data (buffer), dc_buffer_get_size (buffer), callback, userdata); + + dc_buffer_free (buffer); + + return rc; +} + + +dc_status_t +diverite_nitekq_extract_dives (dc_device_t *abstract, const unsigned char data[], unsigned int size, dc_dive_callback_t callback, void *userdata) +{ + diverite_nitekq_device_t *device = (diverite_nitekq_device_t *) abstract; + dc_context_t *context = (abstract ? abstract->context : NULL); + + if (abstract && !ISINSTANCE (abstract)) + return DC_STATUS_INVALIDARGS; + + if (size < SZ_PACKET + SZ_MEMORY) + return DC_STATUS_DATAFORMAT; + + // Skip the first packet. We don't need it for anything. It also + // makes the logic easier because all offsets in the data are + // relative to the real start of the memory (e.g. excluding this + // artificial first block). + data += SZ_PACKET; + + // Allocate memory. + unsigned char *buffer = (unsigned char *) malloc (RB_PROFILE_END - RB_PROFILE_BEGIN); + if (buffer == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Get the end of profile pointer. + unsigned int eop = array_uint16_be(data + EOP); + if (eop < RB_PROFILE_BEGIN || eop >= RB_PROFILE_END) { + ERROR (context, "Invalid ringbuffer pointer detected."); + free (buffer); + return DC_STATUS_DATAFORMAT; + } + + // When a new dive is added, the device moves all existing logbook + // and address entries towards the end, such that the most recent + // one is always the first one. This is not the case for the profile + // data, which is added at the end. + unsigned int previous = eop; + for (unsigned int i = 0; i < 10; ++i) { + // Get the pointer to the logbook entry. + const unsigned char *p = data + LOGBOOK + i * SZ_LOGBOOK; + + // Abort if an empty logbook is found. + if (array_isequal (p, SZ_LOGBOOK, 0x00)) + break; + + // Get the address of the profile data. + unsigned int address = array_uint16_be(data + ADDRESS + i * 2); + if (address < RB_PROFILE_BEGIN || address >= RB_PROFILE_END) { + ERROR (context, "Invalid ringbuffer pointer detected."); + free (buffer); + return DC_STATUS_DATAFORMAT; + } + + // Check the fingerprint data. + if (device && memcmp (p, device->fingerprint, sizeof (device->fingerprint)) == 0) + break; + + // Copy the logbook entry. + memcpy (buffer, p, SZ_LOGBOOK); + + // Copy the profile data. + unsigned int length = 0; + if (previous > address) { + length = previous - address; + memcpy (buffer + SZ_LOGBOOK, data + address, length); + } else { + unsigned int len_a = RB_PROFILE_END - address; + unsigned int len_b = previous - RB_PROFILE_BEGIN; + length = len_a + len_b; + memcpy (buffer + SZ_LOGBOOK, data + address, len_a); + memcpy (buffer + SZ_LOGBOOK + len_a, data + RB_PROFILE_BEGIN, len_b); + } + + if (callback && !callback (buffer, length + SZ_LOGBOOK, buffer, SZ_LOGBOOK, userdata)) { + break; + } + + previous = address; + } + + free (buffer); + + return DC_STATUS_SUCCESS; +} diff --git a/src/diverite_nitekq_parser.c b/src/diverite_nitekq_parser.c new file mode 100644 index 0000000..fc080a8 --- /dev/null +++ b/src/diverite_nitekq_parser.c @@ -0,0 +1,319 @@ +/* + * libdivecomputer + * + * Copyright (C) 2013 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 +#include + +#include "context-private.h" +#include "parser-private.h" +#include "array.h" + +#define ISINSTANCE(parser) dc_device_isinstance((parser), &diverite_nitekq_parser_vtable) + +#define SZ_LOGBOOK 6 + +#define NGASMIXES 7 + +typedef struct diverite_nitekq_parser_t diverite_nitekq_parser_t; + +struct diverite_nitekq_parser_t { + dc_parser_t base; + // Cached fields. + unsigned int cached; + unsigned int metric; + unsigned int ngasmixes; + unsigned int o2[NGASMIXES]; + unsigned int he[NGASMIXES]; + unsigned int divetime; + double maxdepth; +}; + +static dc_status_t diverite_nitekq_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t diverite_nitekq_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); +static dc_status_t diverite_nitekq_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); +static dc_status_t diverite_nitekq_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); +static dc_status_t diverite_nitekq_parser_destroy (dc_parser_t *abstract); + +static const dc_parser_vtable_t diverite_nitekq_parser_vtable = { + DC_FAMILY_DIVERITE_NITEKQ, + diverite_nitekq_parser_set_data, /* set_data */ + diverite_nitekq_parser_get_datetime, /* datetime */ + diverite_nitekq_parser_get_field, /* fields */ + diverite_nitekq_parser_samples_foreach, /* samples_foreach */ + diverite_nitekq_parser_destroy /* destroy */ +}; + + +dc_status_t +diverite_nitekq_parser_create (dc_parser_t **out, dc_context_t *context) +{ + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + diverite_nitekq_parser_t *parser = (diverite_nitekq_parser_t *) malloc (sizeof (diverite_nitekq_parser_t)); + if (parser == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Initialize the base class. + parser_init (&parser->base, context, &diverite_nitekq_parser_vtable); + + // Set the default values. + parser->cached = 0; + parser->metric = 0; + parser->divetime = 0; + parser->maxdepth = 0.0; + parser->ngasmixes = 0; + for (unsigned int i = 0; i < NGASMIXES; ++i) { + parser->o2[i] = 0; + parser->he[i] = 0; + } + + *out = (dc_parser_t*) parser; + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +diverite_nitekq_parser_destroy (dc_parser_t *abstract) +{ + // Free memory. + free (abstract); + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +diverite_nitekq_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) +{ + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +diverite_nitekq_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) +{ + if (abstract->size < SZ_LOGBOOK) + return DC_STATUS_DATAFORMAT; + + const unsigned char *p = abstract->data; + + if (datetime) { + datetime->year = p[0] + 2000; + datetime->month = p[1]; + datetime->day = p[2]; + datetime->hour = p[3]; + datetime->minute = p[4]; + datetime->second = p[5]; + } + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +diverite_nitekq_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) +{ + diverite_nitekq_parser_t *parser = (diverite_nitekq_parser_t *) abstract; + + if (abstract->size < SZ_LOGBOOK) + return DC_STATUS_DATAFORMAT; + + dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + + if (!parser->cached) { + dc_status_t rc = diverite_nitekq_parser_samples_foreach (abstract, NULL, NULL); + if (rc != DC_STATUS_SUCCESS) + return rc; + } + + if (value) { + switch (type) { + case DC_FIELD_DIVETIME: + *((unsigned int *) value) = parser->divetime; + break; + case DC_FIELD_MAXDEPTH: + if (parser->metric) + *((double *) value) = parser->maxdepth / 10.0; + else + *((double *) value) = parser->maxdepth * FEET / 10.0; + break; + case DC_FIELD_GASMIX_COUNT: + *((unsigned int *) value) = parser->ngasmixes; + break; + case DC_FIELD_GASMIX: + gasmix->helium = parser->he[flags] / 100.0; + gasmix->oxygen = parser->o2[flags] / 100.0; + gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; + break; + default: + return DC_STATUS_UNSUPPORTED; + } + } + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +diverite_nitekq_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +{ + diverite_nitekq_parser_t *parser = (diverite_nitekq_parser_t *) abstract; + + if (abstract->size < SZ_LOGBOOK) + return DC_STATUS_DATAFORMAT; + + const unsigned char *data = abstract->data + SZ_LOGBOOK; + unsigned int size = abstract->size - SZ_LOGBOOK; + + unsigned int type = 0; + unsigned int metric = 0; + unsigned int interval = 0; + unsigned int maxdepth = 0; + unsigned int oxygen[NGASMIXES]; + unsigned int helium[NGASMIXES]; + unsigned int ngasmixes = 0; + unsigned int gasmix = 0xFFFFFFFF; /* initialize with impossible value */ + unsigned int gasmix_previous = gasmix; + + unsigned int time = 0; + unsigned int offset = 0; + while (offset + 2 <= size) { + if (data[offset] == 0xFF) { + unsigned int o2 = 0, he = 0; + unsigned int i = 0; + + type = data[offset + 1]; + switch (type) { + case 0x01: // Settings + if (offset + 27 > size) + return DC_STATUS_DATAFORMAT; + metric = (data[0x10] & 0x04) >> 2; + interval = data[0x11]; + offset += 27; + break; + case 0x02: // OC Samples + case 0x03: // CC Samples + offset += 2; + break; + case 0x04: // Gas Change + if (offset + 7 > size) + return DC_STATUS_DATAFORMAT; + + // Get the new gas mix. + o2 = data[offset + 5]; + he = data[offset + 6]; + + // Find the gasmix in the list. + i = 0; + while (i < ngasmixes) { + if (o2 == oxygen[i] && he == helium[i]) + break; + i++; + } + + // Add it to list if not found. + if (i >= ngasmixes) { + if (i >= NGASMIXES) { + ERROR (abstract->context, "Maximum number of gas mixes reached."); + return DC_STATUS_DATAFORMAT; + } + oxygen[i] = o2; + helium[i] = he; + ngasmixes = i + 1; + } + + // Remember the index. + gasmix = i; + offset += 7; + break; + default: + INFO (abstract->context, "Unknown type %02x", type); + break; + } + } else if (type == 2 || type == 3) { + dc_sample_value_t sample = {0}; + + if (interval == 0) { + ERROR (abstract->context, "No sample interval present."); + return DC_STATUS_DATAFORMAT; + } + + // Time (seconds). + time += interval; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + + // Gas change + if (gasmix != gasmix_previous) { + sample.event.type = SAMPLE_EVENT_GASCHANGE2; + sample.event.time = 0; + sample.event.flags = 0; + sample.event.value = oxygen[gasmix] | (helium[gasmix] << 16); + if (callback) callback (DC_SAMPLE_EVENT, sample, userdata); + gasmix_previous = gasmix; + } + + // Depth (1/10 m or ft). + unsigned int depth = array_uint16_be (data + offset); + if (maxdepth < depth) + maxdepth = depth; + if (metric) + sample.depth = depth / 10.0; + else + sample.depth = depth * FEET / 10.0; + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + offset += 2; + + // PPO2 + if (type == 3) { + if (offset + 1 > size) + return DC_STATUS_DATAFORMAT; + unsigned int ppo2 = data[offset]; + sample.ppo2 = ppo2 / 100.0; + if (callback) callback (DC_SAMPLE_PPO2, sample, userdata); + offset++; + } + } else { + ERROR (abstract->context, "Invalid sample type %02x.", type); + return DC_STATUS_DATAFORMAT; + } + } + + // Cache the data for later use. + for (unsigned int i = 0; i < ngasmixes; ++i) { + parser->he[i] = helium[i]; + parser->o2[i] = oxygen[i]; + } + parser->ngasmixes = ngasmixes; + parser->maxdepth = maxdepth; + parser->divetime = time; + parser->metric = metric; + parser->cached = 1; + + return DC_STATUS_SUCCESS; +} diff --git a/src/libdivecomputer.symbols b/src/libdivecomputer.symbols index a73440f..8b4e60c 100644 --- a/src/libdivecomputer.symbols +++ b/src/libdivecomputer.symbols @@ -66,6 +66,7 @@ atomics_cobalt_parser_create atomics_cobalt_parser_set_calibration shearwater_predator_parser_create shearwater_petrel_parser_create +diverite_nitekq_parser_create dc_device_open dc_device_close @@ -160,3 +161,5 @@ atomics_cobalt_device_set_simulation shearwater_predator_device_open shearwater_predator_extract_dives shearwater_petrel_device_open +diverite_nitekq_device_open +diverite_nitekq_extract_dives diff --git a/src/parser.c b/src/parser.c index 2789a4e..568bb94 100644 --- a/src/parser.c +++ b/src/parser.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "parser-private.h" #include "device-private.h" @@ -127,6 +128,9 @@ dc_parser_new (dc_parser_t **out, dc_device_t *device) case DC_FAMILY_SHEARWATER_PETREL: rc = shearwater_petrel_parser_create (&parser, context); break; + case DC_FAMILY_DIVERITE_NITEKQ: + rc = diverite_nitekq_parser_create (&parser, context); + break; default: return DC_STATUS_INVALIDARGS; }