diff --git a/examples/common.c b/examples/common.c
index e305536..cfabc03 100644
--- a/examples/common.c
+++ b/examples/common.c
@@ -91,6 +91,7 @@ static const backend_table_t g_backends[] = {
{"cochran", DC_FAMILY_COCHRAN_COMMANDER, 0},
{"divecomputereu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0},
{"mclean", DC_FAMILY_MCLEAN_EXTREME, 0},
+ {"lynx", DC_FAMILY_LIQUIVISION_LYNX, 0},
};
static const transport_table_t g_transports[] = {
diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h
index a8703ab..b8216ac 100644
--- a/include/libdivecomputer/common.h
+++ b/include/libdivecomputer/common.h
@@ -106,6 +106,8 @@ typedef enum dc_family_t {
DC_FAMILY_TECDIVING_DIVECOMPUTEREU = (15 << 16),
/* McLean */
DC_FAMILY_MCLEAN_EXTREME = (16 << 16),
+ /* Liquivision */
+ DC_FAMILY_LIQUIVISION_LYNX = (17 << 16),
} dc_family_t;
#ifdef __cplusplus
diff --git a/msvc/libdivecomputer.vcproj b/msvc/libdivecomputer.vcproj
index 1c8b772..0580e56 100644
--- a/msvc/libdivecomputer.vcproj
+++ b/msvc/libdivecomputer.vcproj
@@ -318,6 +318,14 @@
RelativePath="..\src\iterator.c"
>
+
+
+
+
@@ -684,6 +692,10 @@
RelativePath="..\include\libdivecomputer\iterator.h"
>
+
+
diff --git a/src/Makefile.am b/src/Makefile.am
index f2bcd46..7b18286 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -73,6 +73,7 @@ libdivecomputer_la_SOURCES = \
cochran_commander.h cochran_commander.c cochran_commander_parser.c \
tecdiving_divecomputereu.h tecdiving_divecomputereu.c tecdiving_divecomputereu_parser.c \
mclean_extreme.h mclean_extreme.c mclean_extreme_parser.c \
+ liquivision_lynx.h liquivision_lynx.c liquivision_lynx_parser.c \
socket.h socket.c \
irda.c \
usbhid.c \
diff --git a/src/checksum.c b/src/checksum.c
index 816c58d..9d06630 100644
--- a/src/checksum.c
+++ b/src/checksum.c
@@ -157,3 +157,48 @@ checksum_crc32 (const unsigned char data[], unsigned int size)
return crc ^ 0xffffffff;
}
+
+unsigned int
+checksum_crc32b (const unsigned char data[], unsigned int size)
+{
+ static const unsigned int crc_table[] = {
+ 0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9, 0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005,
+ 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61, 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD,
+ 0x4C11DB70, 0x48D0C6C7, 0x4593E01E, 0x4152FDA9, 0x5F15ADAC, 0x5BD4B01B, 0x569796C2, 0x52568B75,
+ 0x6A1936C8, 0x6ED82B7F, 0x639B0DA6, 0x675A1011, 0x791D4014, 0x7DDC5DA3, 0x709F7B7A, 0x745E66CD,
+ 0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039, 0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5,
+ 0xBE2B5B58, 0xBAEA46EF, 0xB7A96036, 0xB3687D81, 0xAD2F2D84, 0xA9EE3033, 0xA4AD16EA, 0xA06C0B5D,
+ 0xD4326D90, 0xD0F37027, 0xDDB056FE, 0xD9714B49, 0xC7361B4C, 0xC3F706FB, 0xCEB42022, 0xCA753D95,
+ 0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1, 0xE13EF6F4, 0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D,
+ 0x34867077, 0x30476DC0, 0x3D044B19, 0x39C556AE, 0x278206AB, 0x23431B1C, 0x2E003DC5, 0x2AC12072,
+ 0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16, 0x018AEB13, 0x054BF6A4, 0x0808D07D, 0x0CC9CDCA,
+ 0x7897AB07, 0x7C56B6B0, 0x71159069, 0x75D48DDE, 0x6B93DDDB, 0x6F52C06C, 0x6211E6B5, 0x66D0FB02,
+ 0x5E9F46BF, 0x5A5E5B08, 0x571D7DD1, 0x53DC6066, 0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA,
+ 0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E, 0xBFA1B04B, 0xBB60ADFC, 0xB6238B25, 0xB2E29692,
+ 0x8AAD2B2F, 0x8E6C3698, 0x832F1041, 0x87EE0DF6, 0x99A95DF3, 0x9D684044, 0x902B669D, 0x94EA7B2A,
+ 0xE0B41DE7, 0xE4750050, 0xE9362689, 0xEDF73B3E, 0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2,
+ 0xC6BCF05F, 0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686, 0xD5B88683, 0xD1799B34, 0xDC3ABDED, 0xD8FBA05A,
+ 0x690CE0EE, 0x6DCDFD59, 0x608EDB80, 0x644FC637, 0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB,
+ 0x4F040D56, 0x4BC510E1, 0x46863638, 0x42472B8F, 0x5C007B8A, 0x58C1663D, 0x558240E4, 0x51435D53,
+ 0x251D3B9E, 0x21DC2629, 0x2C9F00F0, 0x285E1D47, 0x36194D42, 0x32D850F5, 0x3F9B762C, 0x3B5A6B9B,
+ 0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF, 0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623,
+ 0xF12F560E, 0xF5EE4BB9, 0xF8AD6D60, 0xFC6C70D7, 0xE22B20D2, 0xE6EA3D65, 0xEBA91BBC, 0xEF68060B,
+ 0xD727BBB6, 0xD3E6A601, 0xDEA580D8, 0xDA649D6F, 0xC423CD6A, 0xC0E2D0DD, 0xCDA1F604, 0xC960EBB3,
+ 0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7, 0xAE3AFBA2, 0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B,
+ 0x9B3660C6, 0x9FF77D71, 0x92B45BA8, 0x9675461F, 0x8832161A, 0x8CF30BAD, 0x81B02D74, 0x857130C3,
+ 0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640, 0x4E8EE645, 0x4A4FFBF2, 0x470CDD2B, 0x43CDC09C,
+ 0x7B827D21, 0x7F436096, 0x7200464F, 0x76C15BF8, 0x68860BFD, 0x6C47164A, 0x61043093, 0x65C52D24,
+ 0x119B4BE9, 0x155A565E, 0x18197087, 0x1CD86D30, 0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC,
+ 0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088, 0x2497D08D, 0x2056CD3A, 0x2D15EBE3, 0x29D4F654,
+ 0xC5A92679, 0xC1683BCE, 0xCC2B1D17, 0xC8EA00A0, 0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB, 0xDBEE767C,
+ 0xE3A1CBC1, 0xE760D676, 0xEA23F0AF, 0xEEE2ED18, 0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4,
+ 0x89B8FD09, 0x8D79E0BE, 0x803AC667, 0x84FBDBD0, 0x9ABC8BD5, 0x9E7D9662, 0x933EB0BB, 0x97FFAD0C,
+ 0xAFB010B1, 0xAB710D06, 0xA6322BDF, 0xA2F33668, 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4,
+ };
+
+ unsigned int crc = 0xffffffff;
+ for (unsigned int i = 0; i < size; ++i)
+ crc = crc_table[((crc >> 24) ^ data[i]) & 0xFF] ^ (crc << 8);
+
+ return crc ^ 0xffffffff;
+}
diff --git a/src/checksum.h b/src/checksum.h
index 9a53aa6..c8a9001 100644
--- a/src/checksum.h
+++ b/src/checksum.h
@@ -44,6 +44,9 @@ checksum_crc16_ccitt (const unsigned char data[], unsigned int size, unsigned sh
unsigned int
checksum_crc32 (const unsigned char data[], unsigned int size);
+unsigned int
+checksum_crc32b (const unsigned char data[], unsigned int size);
+
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/src/descriptor.c b/src/descriptor.c
index de3b52a..a8d221c 100644
--- a/src/descriptor.c
+++ b/src/descriptor.c
@@ -380,6 +380,11 @@ static const dc_descriptor_t g_descriptors[] = {
{"Tecdiving", "DiveComputer.eu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_tecdiving},
/* McLean Extreme */
{ "McLean", "Extreme", DC_FAMILY_MCLEAN_EXTREME, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_mclean},
+ /* Liquivision */
+ {"Liquivision", "Xen", DC_FAMILY_LIQUIVISION_LYNX, 0, DC_TRANSPORT_SERIAL, NULL},
+ {"Liquivision", "Xeo", DC_FAMILY_LIQUIVISION_LYNX, 1, DC_TRANSPORT_SERIAL, NULL},
+ {"Liquivision", "Lynx", DC_FAMILY_LIQUIVISION_LYNX, 2, DC_TRANSPORT_SERIAL, NULL},
+ {"Liquivision", "Kaon", DC_FAMILY_LIQUIVISION_LYNX, 3, DC_TRANSPORT_SERIAL, NULL},
};
static int
diff --git a/src/device.c b/src/device.c
index 92cfa36..271c2f4 100644
--- a/src/device.c
+++ b/src/device.c
@@ -58,6 +58,7 @@
#include "cochran_commander.h"
#include "tecdiving_divecomputereu.h"
#include "mclean_extreme.h"
+#include "liquivision_lynx.h"
#include "device-private.h"
#include "context-private.h"
@@ -215,6 +216,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr
case DC_FAMILY_MCLEAN_EXTREME:
rc = mclean_extreme_device_open (&device, context, iostream);
break;
+ case DC_FAMILY_LIQUIVISION_LYNX:
+ rc = liquivision_lynx_device_open (&device, context, iostream);
+ break;
default:
return DC_STATUS_INVALIDARGS;
}
diff --git a/src/liquivision_lynx.c b/src/liquivision_lynx.c
new file mode 100644
index 0000000..b91b326
--- /dev/null
+++ b/src/liquivision_lynx.c
@@ -0,0 +1,677 @@
+/*
+ * libdivecomputer
+ *
+ * Copyright (C) 2020 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 "liquivision_lynx.h"
+#include "context-private.h"
+#include "device-private.h"
+#include "ringbuffer.h"
+#include "rbstream.h"
+#include "checksum.h"
+#include "array.h"
+
+#define ISINSTANCE(device) dc_device_isinstance((device), &liquivision_lynx_device_vtable)
+
+#define XEN 0
+#define XEO 1
+#define LYNX 2
+#define KAON 3
+
+#define XEN_V1 0x83321485 // Not supported
+#define XEN_V2 0x83321502
+#define XEN_V3 0x83328401
+
+#define XEO_V1_A 0x17485623
+#define XEO_V1_B 0x27485623
+#define XEO_V2_A 0x17488401
+#define XEO_V2_B 0x27488401
+#define XEO_V3_A 0x17488402
+#define XEO_V3_B 0x27488402
+
+#define LYNX_V1 0x67488403
+#define LYNX_V2 0x67488404
+#define LYNX_V3 0x67488405
+
+#define KAON_V1 0x37488402
+#define KAON_V2 0x47488402
+
+#define MAXRETRIES 2
+#define MAXPACKET 12
+#define SEGMENTSIZE 0x400
+#define PAGESIZE 0x1000
+#define MEMSIZE 0x200000
+
+#define RB_LOGBOOK_BEGIN (1 * PAGESIZE)
+#define RB_LOGBOOK_END (25 * PAGESIZE)
+#define RB_LOGBOOK_SIZE (RB_LOGBOOK_END - RB_LOGBOOK_BEGIN)
+#define RB_LOGBOOK_DISTANCE(a,b) ringbuffer_distance (a, b, 1, RB_LOGBOOK_BEGIN, RB_LOGBOOK_END)
+
+#define RB_PROFILE_BEGIN (25 * PAGESIZE)
+#define RB_PROFILE_END (500 * PAGESIZE)
+#define RB_PROFILE_SIZE (RB_PROFILE_END - RB_PROFILE_BEGIN)
+#define RB_PROFILE_DISTANCE(a,b) ringbuffer_distance (a, b, 1, RB_PROFILE_BEGIN, RB_PROFILE_END)
+
+#define SZ_HEADER_XEN 80
+#define SZ_HEADER_OTHER 96
+#define SZ_HEADER_MAX SZ_HEADER_OTHER
+
+typedef struct liquivision_lynx_device_t {
+ dc_device_t base;
+ dc_iostream_t *iostream;
+ unsigned char fingerprint[4];
+} liquivision_lynx_device_t;
+
+static dc_status_t liquivision_lynx_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size);
+static dc_status_t liquivision_lynx_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size);
+static dc_status_t liquivision_lynx_device_dump (dc_device_t *abstract, dc_buffer_t *buffer);
+static dc_status_t liquivision_lynx_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata);
+static dc_status_t liquivision_lynx_device_close (dc_device_t *abstract);
+
+static const dc_device_vtable_t liquivision_lynx_device_vtable = {
+ sizeof(liquivision_lynx_device_t),
+ DC_FAMILY_LIQUIVISION_LYNX,
+ liquivision_lynx_device_set_fingerprint, /* set_fingerprint */
+ liquivision_lynx_device_read, /* read */
+ NULL, /* write */
+ liquivision_lynx_device_dump, /* dump */
+ liquivision_lynx_device_foreach, /* foreach */
+ NULL, /* timesync */
+ liquivision_lynx_device_close /* close */
+};
+
+static dc_status_t
+liquivision_lynx_send (liquivision_lynx_device_t *device, const unsigned char data[], unsigned int size)
+{
+ dc_status_t status = DC_STATUS_SUCCESS;
+ dc_device_t *abstract = (dc_device_t *) device;
+
+ if (size > MAXPACKET)
+ return DC_STATUS_INVALIDARGS;
+
+ // Build the packet.
+ unsigned char packet[2 + MAXPACKET + 2] = {0};
+ packet[0] = 0x00;
+ packet[1] = 0xB1;
+ if (size) {
+ memcpy (packet + 2, data, size);
+ }
+ packet[2 + size + 0] = 0x0B;
+ packet[2 + size + 1] = 0x0E;
+
+ // Send the packet to the device.
+ status = dc_iostream_write (device->iostream, packet, size + 4, NULL);
+ if (status != DC_STATUS_SUCCESS) {
+ ERROR (abstract->context, "Failed to send the packet.");
+ return status;
+ }
+
+ return DC_STATUS_SUCCESS;
+}
+
+static dc_status_t
+liquivision_lynx_recv (liquivision_lynx_device_t *device, unsigned char data[], unsigned int size)
+{
+ dc_status_t status = DC_STATUS_SUCCESS;
+ dc_device_t *abstract = (dc_device_t *) device;
+
+ if (size > SEGMENTSIZE)
+ return DC_STATUS_INVALIDARGS;
+
+ // Receive the packet from the device.
+ unsigned char packet[1 + SEGMENTSIZE + 2] = {0};
+ status = dc_iostream_read (device->iostream, packet, 1 + size + 2, NULL);
+ if (status != DC_STATUS_SUCCESS) {
+ ERROR (abstract->context, "Failed to receive the packet.");
+ return status;
+ }
+
+ // Verify the start byte.
+ if (packet[0] != 0xC5) {
+ ERROR (abstract->context, "Unexpected answer start byte (%02x).", packet[0]);
+ return DC_STATUS_PROTOCOL;
+ }
+
+ // Verify the checksum.
+ unsigned short crc = array_uint16_be (packet + 1 + size);
+ unsigned short ccrc = checksum_crc16_ccitt (packet + 1, size, 0xffff);
+ if (crc != ccrc) {
+ ERROR (abstract->context, "Unexpected answer checksum (%04x %04x).", crc, ccrc);
+ return DC_STATUS_PROTOCOL;
+ }
+
+ if (size) {
+ memcpy (data, packet + 1, size);
+ }
+
+ return DC_STATUS_SUCCESS;
+}
+
+static dc_status_t
+liquivision_lynx_packet (liquivision_lynx_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize)
+{
+ dc_status_t status = DC_STATUS_SUCCESS;
+ dc_device_t *abstract = (dc_device_t *) device;
+
+ if (device_is_cancelled (abstract))
+ return DC_STATUS_CANCELLED;
+
+ status = liquivision_lynx_send (device, command, csize);
+ if (status != DC_STATUS_SUCCESS) {
+ ERROR (abstract->context, "Failed to send the command.");
+ return status;
+ }
+
+ if (asize) {
+ status = liquivision_lynx_recv (device, answer, asize);
+ if (status != DC_STATUS_SUCCESS) {
+ ERROR (abstract->context, "Failed to receive the answer.");
+ return status;
+ }
+ }
+
+ return DC_STATUS_SUCCESS;
+}
+
+static dc_status_t
+liquivision_lynx_transfer (liquivision_lynx_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize)
+{
+ unsigned int nretries = 0;
+ dc_status_t rc = DC_STATUS_SUCCESS;
+ while ((rc = liquivision_lynx_packet (device, command, csize, answer, asize)) != DC_STATUS_SUCCESS) {
+ if (rc != DC_STATUS_TIMEOUT && rc != DC_STATUS_PROTOCOL)
+ return rc;
+
+ // Abort if the maximum number of retries is reached.
+ if (nretries++ >= MAXRETRIES)
+ return rc;
+
+ // Delay the next attempt.
+ dc_iostream_sleep (device->iostream, 100);
+ dc_iostream_purge (device->iostream, DC_DIRECTION_INPUT);
+ }
+
+ return DC_STATUS_SUCCESS;
+}
+
+dc_status_t
+liquivision_lynx_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream)
+{
+ dc_status_t status = DC_STATUS_SUCCESS;
+ liquivision_lynx_device_t *device = NULL;
+
+ if (out == NULL)
+ return DC_STATUS_INVALIDARGS;
+
+ // Allocate memory.
+ device = (liquivision_lynx_device_t *) dc_device_allocate (context, &liquivision_lynx_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 (9600 8N1).
+ status = dc_iostream_configure (device->iostream, 9600, 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 (3000 ms).
+ status = dc_iostream_set_timeout (device->iostream, 3000);
+ if (status != DC_STATUS_SUCCESS) {
+ ERROR (context, "Failed to set the timeout.");
+ goto error_free;
+ }
+
+ // Set the DTR line.
+ status = dc_iostream_set_dtr (device->iostream, 0);
+ if (status != DC_STATUS_SUCCESS) {
+ ERROR (context, "Failed to set the DTR line.");
+ goto error_free;
+ }
+
+ // Set the RTS line.
+ status = dc_iostream_set_rts (device->iostream, 0);
+ if (status != DC_STATUS_SUCCESS) {
+ ERROR (context, "Failed to set the RTS line.");
+ goto error_free;
+ }
+
+ // Make sure everything is in a sane state.
+ dc_iostream_sleep (device->iostream, 100);
+ dc_iostream_purge (device->iostream, DC_DIRECTION_ALL);
+
+ // Wakeup the device.
+ for (unsigned int i = 0; i < 6000; ++i) {
+ const unsigned char init[] = {0xAA};
+ dc_iostream_write (device->iostream, init, sizeof (init), NULL);
+ }
+
+ *out = (dc_device_t *) device;
+
+ return DC_STATUS_SUCCESS;
+
+error_free:
+ dc_device_deallocate ((dc_device_t *) device);
+ return status;
+}
+
+
+static dc_status_t
+liquivision_lynx_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size)
+{
+ liquivision_lynx_device_t *device = (liquivision_lynx_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
+liquivision_lynx_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size)
+{
+ dc_status_t status = DC_STATUS_SUCCESS;
+ liquivision_lynx_device_t *device = (liquivision_lynx_device_t *) abstract;
+
+ if ((address % SEGMENTSIZE != 0) ||
+ (size % SEGMENTSIZE != 0))
+ return DC_STATUS_INVALIDARGS;
+
+ // Get the page and segment number.
+ unsigned int page = (address / PAGESIZE);
+ unsigned int segment = (address % PAGESIZE) / SEGMENTSIZE;
+
+ unsigned int nbytes = 0;
+ while (nbytes < size) {
+ const unsigned char command[] = {
+ 0x50, 0x41, 0x47, 0x45,
+ '0' + ((page / 100) % 10),
+ '0' + ((page / 10) % 10),
+ '0' + ((page / 1) % 10),
+ '0' + ((page / 100) % 10),
+ '0' + ((page / 10) % 10),
+ '0' + ((page / 1) % 10),
+ '0' + segment,
+ '0' + segment
+ };
+
+ status = liquivision_lynx_transfer (device, command, sizeof(command), data + nbytes, SEGMENTSIZE);
+ if (status != DC_STATUS_SUCCESS) {
+ ERROR (abstract->context, "Failed to read page %u segment %u.", page, segment);
+ return status;
+ }
+
+ nbytes += SEGMENTSIZE;
+ segment++;
+ if (segment == (PAGESIZE / SEGMENTSIZE)) {
+ segment = 0;
+ page++;
+ }
+ }
+
+ return DC_STATUS_SUCCESS;
+}
+
+static dc_status_t
+liquivision_lynx_device_dump (dc_device_t *abstract, dc_buffer_t *buffer)
+{
+ // Allocate the required amount of memory.
+ if (!dc_buffer_resize (buffer, MEMSIZE)) {
+ ERROR (abstract->context, "Insufficient buffer space available.");
+ return DC_STATUS_NOMEMORY;
+ }
+
+ return device_dump_read (abstract, dc_buffer_get_data (buffer),
+ dc_buffer_get_size (buffer), SEGMENTSIZE);
+}
+
+static dc_status_t
+liquivision_lynx_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
+{
+ dc_status_t status = DC_STATUS_SUCCESS;
+ liquivision_lynx_device_t *device = (liquivision_lynx_device_t *) abstract;
+
+ // Enable progress notifications.
+ dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
+ progress.maximum = SEGMENTSIZE + RB_LOGBOOK_SIZE + RB_PROFILE_SIZE;
+ device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
+
+ // Send the info command.
+ unsigned char rsp_info[6] = {0};
+ const unsigned char cmd_info[] = {0x49, 0x4E, 0x46, 0x4F, 0x49, 0x4E, 0x46, 0x4F, 0x49, 0x4E, 0x46, 0x4F};
+ status = liquivision_lynx_transfer (device, cmd_info, sizeof(cmd_info), rsp_info, sizeof(rsp_info));
+ if (status != DC_STATUS_SUCCESS) {
+ ERROR (abstract->context, "Failed to send the info command.");
+ goto error_exit;
+ }
+
+ // Get the model and version.
+ unsigned int model = array_uint16_le(rsp_info + 0);
+ unsigned int version = array_uint32_le(rsp_info + 2);
+
+ // Send the more info command.
+ unsigned char rsp_more[12] = {0};
+ const unsigned char cmd_more[] = {0x4D, 0x4F, 0x52, 0x45, 0x49, 0x4E, 0x46, 0x4F, 0x4D, 0x4F, 0x52, 0x45};
+ status = liquivision_lynx_transfer (device, cmd_more, sizeof(cmd_more), rsp_more, sizeof(rsp_more));
+ if (status != DC_STATUS_SUCCESS) {
+ ERROR (abstract->context, "Failed to send the more info command.");
+ goto error_exit;
+ }
+
+ // Emit a device info event.
+ dc_event_devinfo_t devinfo;
+ devinfo.model = model;
+ devinfo.firmware = 0;
+ devinfo.serial = array_uint32_le(rsp_more + 0);
+ device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo);
+
+ // Read the config segment.
+ unsigned char config[SEGMENTSIZE] = {0};
+ status = liquivision_lynx_device_read (abstract, 0, config, sizeof (config));
+ if (status != DC_STATUS_SUCCESS) {
+ ERROR (abstract->context, "Failed to read the memory.");
+ goto error_exit;
+ }
+
+ // Get the header size.
+ unsigned int headersize = (model == XEN) ? SZ_HEADER_XEN : SZ_HEADER_OTHER;
+
+ // Get the number of headers per page.
+ unsigned int npages = PAGESIZE / headersize;
+
+ // Get the logbook pointers.
+ unsigned int begin = array_uint16_le (config + 0x46);
+ unsigned int end = array_uint16_le (config + 0x48);
+ unsigned int rb_logbook_begin = RB_LOGBOOK_BEGIN + (begin / npages) * PAGESIZE + (begin % npages) * headersize;
+ unsigned int rb_logbook_end = RB_LOGBOOK_BEGIN + (end / npages) * PAGESIZE + (end % npages) * headersize;
+ if (rb_logbook_begin < RB_LOGBOOK_BEGIN || rb_logbook_begin > RB_LOGBOOK_END ||
+ rb_logbook_end < RB_LOGBOOK_BEGIN || rb_logbook_end > RB_LOGBOOK_END) {
+ ERROR (abstract->context, "Invalid logbook pointers (%04x, %04x).",
+ rb_logbook_begin, rb_logbook_end);
+ status = DC_STATUS_DATAFORMAT;
+ goto error_exit;
+ }
+
+ // Calculate the logbook size.
+#if 0
+ unsigned int rb_logbook_size = RB_LOGBOOK_DISTANCE (rb_logbook_begin, rb_logbook_end);
+#else
+ // The logbook begin pointer is explicitly ignored, because it only takes
+ // into account dives for which the profile is still available.
+ unsigned int rb_logbook_size = RB_LOGBOOK_SIZE;
+#endif
+
+ // Get the profile pointers.
+ unsigned int rb_profile_begin = array_uint32_le (config + 0x4A);
+ unsigned int rb_profile_end = array_uint32_le (config + 0x4E);
+ if (rb_profile_begin < RB_PROFILE_BEGIN || rb_profile_begin > RB_PROFILE_END ||
+ rb_profile_end < RB_PROFILE_BEGIN || rb_profile_end > RB_PROFILE_END) {
+ ERROR (abstract->context, "Invalid profile pointers (%04x, %04x).",
+ rb_profile_begin, rb_profile_end);
+ status = DC_STATUS_DATAFORMAT;
+ goto error_exit;
+ }
+
+ // Update and emit a progress event.
+ progress.current += SEGMENTSIZE;
+ progress.maximum -= RB_LOGBOOK_SIZE - rb_logbook_size;
+ device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
+
+ // Allocate memory for the logbook entries.
+ unsigned char *logbook = (unsigned char *) malloc (rb_logbook_size);
+ if (logbook == NULL) {
+ status = DC_STATUS_NOMEMORY;
+ goto error_exit;
+ }
+
+ // Create the ringbuffer stream.
+ dc_rbstream_t *rblogbook = NULL;
+ status = dc_rbstream_new (&rblogbook, abstract, SEGMENTSIZE, SEGMENTSIZE, RB_LOGBOOK_BEGIN, RB_LOGBOOK_END, rb_logbook_end);
+ if (status != DC_STATUS_SUCCESS) {
+ ERROR (abstract->context, "Failed to create the ringbuffer stream.");
+ goto error_free_logbook;
+ }
+
+ // The logbook ringbuffer is read backwards to retrieve the most recent
+ // entries first. If an already downloaded entry is identified (by means
+ // of its fingerprint), the transfer is aborted immediately to reduce
+ // the transfer time.
+ unsigned int nbytes = 0;
+ unsigned int offset = rb_logbook_size;
+ unsigned int address = rb_logbook_end;
+ while (nbytes < rb_logbook_size) {
+ // Handle the ringbuffer wrap point.
+ if (address == RB_LOGBOOK_BEGIN)
+ address = RB_LOGBOOK_END;
+
+ // Skip the padding bytes.
+ if ((address % PAGESIZE) == 0) {
+ unsigned int padding = PAGESIZE % headersize;
+ unsigned char dummy[SZ_HEADER_MAX] = {0};
+ status = dc_rbstream_read (rblogbook, &progress, dummy, padding);
+ if (status != DC_STATUS_SUCCESS) {
+ ERROR (abstract->context, "Failed to read the memory.");
+ goto error_free_rblogbook;
+ }
+
+ address -= padding;
+ nbytes += padding;
+ }
+
+ // Move to the start of the current entry.
+ address -= headersize;
+ offset -= headersize;
+
+ // Read the logbook entry.
+ status = dc_rbstream_read (rblogbook, &progress, logbook + offset, headersize);
+ if (status != DC_STATUS_SUCCESS) {
+ ERROR (abstract->context, "Failed to read the memory.");
+ goto error_free_rblogbook;
+ }
+
+ nbytes += headersize;
+
+ if (array_isequal (logbook + offset, headersize, 0xFF)) {
+ offset += headersize;
+ break;
+ }
+
+ // Verify the checksum.
+ unsigned int unused = 2;
+ if (version == XEO_V1_A || version == XEO_V1_B) {
+ unused = 6;
+ }
+ unsigned char header[SZ_HEADER_MAX] = {0};
+ memcpy (header + 0, rsp_info + 2, 4);
+ memcpy (header + 4, logbook + offset + 4, headersize - 4);
+ unsigned int crc = array_uint32_le (logbook + offset + 0);
+ unsigned int ccrc = checksum_crc32b (header, headersize - unused);
+ if (crc != ccrc) {
+ WARNING (abstract->context, "Invalid dive checksum (%08x %08x)", crc, ccrc);
+ status = DC_STATUS_DATAFORMAT;
+ goto error_free_rblogbook;
+ }
+
+ // Compare the fingerprint to identify previously downloaded entries.
+ if (memcmp (logbook + offset, device->fingerprint, sizeof(device->fingerprint)) == 0) {
+ offset += headersize;
+ break;
+ }
+ }
+
+ // Update and emit a progress event.
+ progress.maximum -= rb_logbook_size - nbytes;
+ device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
+
+ // Go through the logbook entries a first time, to calculate the total
+ // amount of bytes in the profile ringbuffer.
+ unsigned int rb_profile_size = 0;
+
+ // Traverse the logbook ringbuffer backwards to retrieve the most recent
+ // dives first. The logbook ringbuffer is linearized at this point, so
+ // we do not have to take into account any memory wrapping near the end
+ // of the memory buffer.
+ unsigned int remaining = RB_PROFILE_SIZE;
+ unsigned int previous = rb_profile_end;
+ unsigned int entry = rb_logbook_size;
+ while (entry != offset) {
+ // Move to the start of the current entry.
+ entry -= headersize;
+
+ // Get the profile pointer.
+ unsigned int current = array_uint32_le (logbook + entry + 16);
+ if (current < RB_PROFILE_BEGIN || current >= RB_PROFILE_END) {
+ ERROR (abstract->context, "Invalid profile ringbuffer pointer (%08x).", current);
+ status = DC_STATUS_DATAFORMAT;
+ goto error_free_rblogbook;
+ }
+
+ // Calculate the length.
+ unsigned int length = RB_PROFILE_DISTANCE (current, previous);
+
+ // Make sure the profile size is valid.
+ if (length > remaining) {
+ remaining = 0;
+ length = 0;
+ }
+
+ // Update the total profile size.
+ rb_profile_size += length;
+
+ // Move to the start of the current dive.
+ remaining -= length;
+ previous = current;
+ }
+
+ // At this point, we know the exact amount of data
+ // that needs to be transferred for the profiles.
+ progress.maximum -= RB_PROFILE_SIZE - rb_profile_size;
+ device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
+
+ // Allocate memory for the profile data.
+ unsigned char *profile = (unsigned char *) malloc (headersize + rb_profile_size);
+ if (profile == NULL) {
+ status = DC_STATUS_NOMEMORY;
+ goto error_free_rblogbook;
+ }
+
+ // Create the ringbuffer stream.
+ dc_rbstream_t *rbprofile = NULL;
+ status = dc_rbstream_new (&rbprofile, abstract, SEGMENTSIZE, SEGMENTSIZE, RB_PROFILE_BEGIN, RB_PROFILE_END, rb_profile_end);
+ if (status != DC_STATUS_SUCCESS) {
+ ERROR (abstract->context, "Failed to create the ringbuffer stream.");
+ goto error_free_profile;
+ }
+
+ // Traverse the logbook ringbuffer backwards to retrieve the most recent
+ // dives first. The logbook ringbuffer is linearized at this point, so
+ // we do not have to take into account any memory wrapping near the end
+ // of the memory buffer.
+ remaining = rb_profile_size;
+ previous = rb_profile_end;
+ entry = rb_logbook_size;
+ while (entry != offset) {
+ // Move to the start of the current entry.
+ entry -= headersize;
+
+ // Get the profile pointer.
+ unsigned int current = array_uint32_le (logbook + entry + 16);
+ if (current < RB_PROFILE_BEGIN || current >= RB_PROFILE_END) {
+ ERROR (abstract->context, "Invalid profile ringbuffer pointer (%08x).", current);
+ status = DC_STATUS_DATAFORMAT;
+ goto error_free_rbprofile;
+ }
+
+ // Calculate the length.
+ unsigned int length = RB_PROFILE_DISTANCE (current, previous);
+
+ // Make sure the profile size is valid.
+ if (length > remaining) {
+ remaining = 0;
+ length = 0;
+ }
+
+ // Move to the start of the current dive.
+ remaining -= length;
+ previous = current;
+
+ // Read the dive.
+ status = dc_rbstream_read (rbprofile, &progress, profile + remaining + headersize, length);
+ if (status != DC_STATUS_SUCCESS) {
+ ERROR (abstract->context, "Failed to read the dive.");
+ goto error_free_rbprofile;
+ }
+
+ // Prepend the logbook entry to the profile data. The memory buffer is
+ // large enough to store this entry. The checksum is replaced with the
+ // flash version number.
+ memcpy (profile + remaining + 0, rsp_info + 2, 4);
+ memcpy (profile + remaining + 4, logbook + entry + 4, headersize - 4);
+
+ if (callback && !callback (profile + remaining, headersize + length, logbook + entry, sizeof(device->fingerprint), userdata)) {
+ break;
+ }
+ }
+
+error_free_rbprofile:
+ dc_rbstream_free (rbprofile);
+error_free_profile:
+ free (profile);
+error_free_rblogbook:
+ dc_rbstream_free (rblogbook);
+error_free_logbook:
+ free (logbook);
+error_exit:
+ return status;
+}
+
+static dc_status_t
+liquivision_lynx_device_close (dc_device_t *abstract)
+{
+ dc_status_t status = DC_STATUS_SUCCESS;
+ liquivision_lynx_device_t *device = (liquivision_lynx_device_t*) abstract;
+ dc_status_t rc = DC_STATUS_SUCCESS;
+
+ // Send the finish command.
+ const unsigned char cmd_finish[] = {0x46, 0x49, 0x4E, 0x49, 0x53, 0x48, 0x46, 0x49, 0x4E, 0x49, 0x53, 0x48};
+ status = liquivision_lynx_transfer (device, cmd_finish, sizeof(cmd_finish), NULL, 0);
+ if (status != DC_STATUS_SUCCESS) {
+ ERROR (abstract->context, "Failed to send the finish command.");
+ dc_status_set_error(&status, rc);
+ }
+
+ return status;
+}
diff --git a/src/liquivision_lynx.h b/src/liquivision_lynx.h
new file mode 100644
index 0000000..52e7294
--- /dev/null
+++ b/src/liquivision_lynx.h
@@ -0,0 +1,43 @@
+/*
+ * libdivecomputer
+ *
+ * Copyright (C) 2020 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 LIQUIVISION_LYNX_H
+#define LIQUIVISION_LYNX_H
+
+#include
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+dc_status_t
+liquivision_lynx_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream);
+
+dc_status_t
+liquivision_lynx_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int model);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* LIQUIVISION_LYNX_H */
diff --git a/src/liquivision_lynx_parser.c b/src/liquivision_lynx_parser.c
new file mode 100644
index 0000000..8489a50
--- /dev/null
+++ b/src/liquivision_lynx_parser.c
@@ -0,0 +1,596 @@
+/*
+ * libdivecomputer
+ *
+ * Copyright (C) 2020 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 "liquivision_lynx.h"
+#include "context-private.h"
+#include "parser-private.h"
+#include "array.h"
+
+#define ISINSTANCE(parser) dc_parser_isinstance((parser), &liquivision_lynx_parser_vtable)
+
+#define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array))
+
+#define XEN 0
+#define XEO 1
+#define LYNX 2
+#define KAON 3
+
+#define XEN_V1 0x83321485 // Not supported
+#define XEN_V2 0x83321502
+#define XEN_V3 0x83328401
+
+#define XEO_V1_A 0x17485623
+#define XEO_V1_B 0x27485623
+#define XEO_V2_A 0x17488401
+#define XEO_V2_B 0x27488401
+#define XEO_V3_A 0x17488402
+#define XEO_V3_B 0x27488402
+
+#define LYNX_V1 0x67488403
+#define LYNX_V2 0x67488404
+#define LYNX_V3 0x67488405
+
+#define KAON_V1 0x37488402
+#define KAON_V2 0x47488402
+
+#define SZ_HEADER_XEN 80
+#define SZ_HEADER_OTHER 96
+
+#define FRESH 0
+#define BRACKISH 1
+#define SALT 2
+
+#define DECO 0
+#define GAUGE 1
+#define TEC 2
+#define REC 3
+
+#define NORMAL 0
+#define BOOKMARK 1
+#define ALARM_DEPTH 2
+#define ALARM_TIME 3
+#define ALARM_VELOCITY 4
+#define DECOSTOP 5
+#define DECOSTOP_BREACHED 6
+#define GASMIX 7
+#define SETPOINT 8
+#define BAILOUT_ON 9
+#define BAILOUT_OFF 10
+#define EMERGENCY_ON 11
+#define EMERGENCY_OFF 12
+#define LOST_GAS 13
+#define SAFETY_STOP 14
+#define TANK_PRESSURE 15
+#define TANK_LIST 16
+
+#define NGASMIXES 11
+#define NTANKS 11
+
+#define INVALID 0xFFFFFFFF
+
+typedef struct liquivision_lynx_parser_t liquivision_lynx_parser_t;
+
+typedef struct liquivision_lynx_gasmix_t {
+ unsigned int oxygen;
+ unsigned int helium;
+} liquivision_lynx_gasmix_t;
+
+typedef struct liquivision_lynx_tank_t {
+ unsigned int id;
+ unsigned int beginpressure;
+ unsigned int endpressure;
+} liquivision_lynx_tank_t;
+
+struct liquivision_lynx_parser_t {
+ dc_parser_t base;
+ unsigned int model;
+ unsigned int headersize;
+ // Cached fields.
+ unsigned int cached;
+ unsigned int ngasmixes;
+ unsigned int ntanks;
+ liquivision_lynx_gasmix_t gasmix[NGASMIXES];
+ liquivision_lynx_tank_t tank[NTANKS];
+};
+
+static dc_status_t liquivision_lynx_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size);
+static dc_status_t liquivision_lynx_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime);
+static dc_status_t liquivision_lynx_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value);
+static dc_status_t liquivision_lynx_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata);
+
+static const dc_parser_vtable_t liquivision_lynx_parser_vtable = {
+ sizeof(liquivision_lynx_parser_t),
+ DC_FAMILY_LIQUIVISION_LYNX,
+ liquivision_lynx_parser_set_data, /* set_data */
+ liquivision_lynx_parser_get_datetime, /* datetime */
+ liquivision_lynx_parser_get_field, /* fields */
+ liquivision_lynx_parser_samples_foreach, /* samples_foreach */
+ NULL /* destroy */
+};
+
+
+dc_status_t
+liquivision_lynx_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model)
+{
+ liquivision_lynx_parser_t *parser = NULL;
+
+ if (out == NULL)
+ return DC_STATUS_INVALIDARGS;
+
+ // Allocate memory.
+ parser = (liquivision_lynx_parser_t *) dc_parser_allocate (context, &liquivision_lynx_parser_vtable);
+ if (parser == NULL) {
+ ERROR (context, "Failed to allocate memory.");
+ return DC_STATUS_NOMEMORY;
+ }
+
+ // Set the default values.
+ parser->model = model;
+ parser->headersize = (model == XEN) ? SZ_HEADER_XEN : SZ_HEADER_OTHER;
+ parser->cached = 0;
+ parser->ngasmixes = 0;
+ parser->ntanks = 0;
+ for (unsigned int i = 0; i < NGASMIXES; ++i) {
+ parser->gasmix[i].oxygen = 0;
+ parser->gasmix[i].helium = 0;
+ }
+ for (unsigned int i = 0; i < NTANKS; ++i) {
+ parser->tank[i].id = 0;
+ parser->tank[i].beginpressure = 0;
+ parser->tank[i].endpressure = 0;
+ }
+
+ *out = (dc_parser_t *) parser;
+
+ return DC_STATUS_SUCCESS;
+}
+
+
+static dc_status_t
+liquivision_lynx_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size)
+{
+ liquivision_lynx_parser_t *parser = (liquivision_lynx_parser_t *) abstract;
+
+ // Reset the cache.
+ parser->cached = 0;
+ parser->ngasmixes = 0;
+ parser->ntanks = 0;
+ for (unsigned int i = 0; i < NGASMIXES; ++i) {
+ parser->gasmix[i].oxygen = 0;
+ parser->gasmix[i].helium = 0;
+ }
+ for (unsigned int i = 0; i < NTANKS; ++i) {
+ parser->tank[i].id = 0;
+ parser->tank[i].beginpressure = 0;
+ parser->tank[i].endpressure = 0;
+ }
+
+ return DC_STATUS_SUCCESS;
+}
+
+
+static dc_status_t
+liquivision_lynx_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime)
+{
+ liquivision_lynx_parser_t *parser = (liquivision_lynx_parser_t *) abstract;
+
+ if (abstract->size < parser->headersize)
+ return DC_STATUS_DATAFORMAT;
+
+ const unsigned char *p = abstract->data + 40;
+
+ if (datetime) {
+ datetime->year = array_uint16_le (p + 18);
+ datetime->month = array_uint16_le (p + 16) + 1;
+ datetime->day = array_uint16_le (p + 12) + 1;
+ datetime->hour = array_uint16_le (p + 8);
+ datetime->minute = array_uint16_le (p + 6);
+ datetime->second = array_uint16_le (p + 4);
+ datetime->timezone = DC_TIMEZONE_NONE;
+ }
+
+ return DC_STATUS_SUCCESS;
+}
+
+
+static dc_status_t
+liquivision_lynx_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value)
+{
+ liquivision_lynx_parser_t *parser = (liquivision_lynx_parser_t *) abstract;
+
+ if (abstract->size < parser->headersize)
+ return DC_STATUS_DATAFORMAT;
+
+ if (!parser->cached) {
+ dc_status_t rc = liquivision_lynx_parser_samples_foreach (abstract, NULL, NULL);
+ if (rc != DC_STATUS_SUCCESS)
+ return rc;
+ }
+
+ dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
+ dc_tank_t *tank = (dc_tank_t *) value;
+ dc_salinity_t *water = (dc_salinity_t *) value;
+
+ if (value) {
+ switch (type) {
+ case DC_FIELD_DIVETIME:
+ *((unsigned int *) value) = array_uint32_le (abstract->data + 4);
+ break;
+ case DC_FIELD_MAXDEPTH:
+ *((double *) value) = array_uint16_le (abstract->data + 28) / 100.0;
+ break;
+ case DC_FIELD_AVGDEPTH:
+ *((double *) value) = array_uint16_le (abstract->data + 30) / 100.0;
+ break;
+ case DC_FIELD_TEMPERATURE_MINIMUM:
+ *((double *) value) = (signed short) array_uint16_le (abstract->data + 34) / 10.0;
+ break;
+ case DC_FIELD_TEMPERATURE_MAXIMUM:
+ *((double *) value) = (signed short) array_uint16_le (abstract->data + 36) / 10.0;
+ break;
+ case DC_FIELD_SALINITY:
+ switch (abstract->data[38]) {
+ case FRESH:
+ water->type = DC_WATER_FRESH;
+ water->density = 1000.0;
+ break;
+ case BRACKISH:
+ water->type = DC_WATER_SALT;
+ water->density = 1015.0;
+ break;
+ case SALT:
+ water->type = DC_WATER_SALT;
+ water->density = 1025.0;
+ break;
+ default:
+ return DC_STATUS_DATAFORMAT;
+ }
+ break;
+ case DC_FIELD_ATMOSPHERIC:
+ *((double *) value) = array_uint16_le (abstract->data + 26) / 1000.0;
+ break;
+ case DC_FIELD_DIVEMODE:
+ if (parser->model == XEN) {
+ *((unsigned int *) value) = DC_DIVEMODE_GAUGE;
+ } else {
+ switch (abstract->data[92] & 0x0F) {
+ case DECO:
+ case TEC:
+ case REC:
+ *((unsigned int *) value) = DC_DIVEMODE_OC;
+ break;
+ case GAUGE:
+ *((unsigned int *) value) = DC_DIVEMODE_GAUGE;
+ break;
+ default:
+ return DC_STATUS_DATAFORMAT;
+ }
+ }
+ break;
+ case DC_FIELD_GASMIX_COUNT:
+ *((unsigned int *) value) = parser->ngasmixes;
+ break;
+ case DC_FIELD_GASMIX:
+ gasmix->helium = parser->gasmix[flags].helium / 100.0;
+ gasmix->oxygen = parser->gasmix[flags].oxygen / 100.0;
+ gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium;
+ break;
+ case DC_FIELD_TANK_COUNT:
+ *((unsigned int *) value) = parser->ntanks;
+ break;
+ case DC_FIELD_TANK:
+ tank->type = DC_TANKVOLUME_NONE;
+ tank->volume = 0.0;
+ tank->workpressure = 0.0;
+ tank->beginpressure = parser->tank[flags].beginpressure / 100.0;
+ tank->endpressure = parser->tank[flags].endpressure / 100.0;
+ tank->gasmix = DC_GASMIX_UNKNOWN;
+ break;
+ default:
+ return DC_STATUS_UNSUPPORTED;
+ }
+ }
+
+ return DC_STATUS_SUCCESS;
+}
+
+static dc_status_t
+liquivision_lynx_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
+{
+ liquivision_lynx_parser_t *parser = (liquivision_lynx_parser_t *) abstract;
+ const unsigned char *data = abstract->data;
+ unsigned int size = abstract->size;
+
+ if (size < parser->headersize)
+ return DC_STATUS_DATAFORMAT;
+
+ // Get the version.
+ unsigned int version = array_uint32_le(data);
+
+ // Get the sample interval.
+ unsigned int interval_idx = data[39];
+ const unsigned int intervals[] = {1, 2, 5, 10, 30, 60};
+ if (interval_idx > C_ARRAY_SIZE(intervals)) {
+ ERROR (abstract->context, "Invalid sample interval index %u", interval_idx);
+ return DC_STATUS_DATAFORMAT;
+ }
+ unsigned int interval = intervals[interval_idx];
+
+ // Get the number of samples and events.
+ unsigned int nsamples = array_uint32_le (data + 8);
+ unsigned int nevents = array_uint32_le (data + 12);
+
+ unsigned int ngasmixes = 0;
+ unsigned int ntanks = 0;
+ liquivision_lynx_gasmix_t gasmix[NGASMIXES] = {0};
+ liquivision_lynx_tank_t tank[NTANKS] = {0};
+ unsigned int o2_previous = INVALID, he_previous = INVALID;
+ unsigned int gasmix_idx = INVALID;
+ unsigned int have_gasmix = 0;
+ unsigned int tank_id_previous = INVALID;
+ unsigned int tank_idx = INVALID;
+ unsigned int pressure[NTANKS] = {0};
+ unsigned int have_pressure = 0;
+ unsigned int setpoint = 0, have_setpoint = 0;
+ unsigned int deco = 0, have_deco = 0;
+
+ unsigned int time = 0;
+ unsigned int samples = 0;
+ unsigned int events = 0;
+ unsigned int offset = parser->headersize;
+ while (offset + 2 <= size) {
+ dc_sample_value_t sample = {0};
+
+ unsigned int value = array_uint16_le (data + offset);
+ offset += 2;
+
+ if (value & 0x8000) {
+ if (events >= nevents) {
+ break;
+ }
+
+ if (offset + 4 > size) {
+ ERROR (abstract->context, "Buffer overflow at offset %u", offset);
+ return DC_STATUS_DATAFORMAT;
+ }
+
+ unsigned int type = value & 0x7FFF;
+ unsigned int timestamp = array_uint32_le (data + offset + 2);
+ offset += 4;
+
+ // Get the sample length.
+ unsigned int length = 0;
+ switch (type) {
+ case DECOSTOP:
+ case GASMIX:
+ length = 2;
+ break;
+ case SETPOINT:
+ length = 1;
+ break;
+ case TANK_LIST:
+ length = NTANKS * 2;
+ break;
+ case TANK_PRESSURE:
+ if (version == LYNX_V1) {
+ length = 4;
+ } else {
+ length = 6;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (offset + length > size) {
+ ERROR (abstract->context, "Buffer overflow at offset %u", offset);
+ return DC_STATUS_DATAFORMAT;
+ }
+
+ unsigned int o2 = 0, he = 0;
+ unsigned int tank_id = 0, tank_pressure = 0;
+
+ switch (type) {
+ case NORMAL:
+ case BOOKMARK:
+ case ALARM_DEPTH:
+ case ALARM_TIME:
+ case ALARM_VELOCITY:
+ case DECOSTOP_BREACHED:
+ case BAILOUT_ON:
+ case BAILOUT_OFF:
+ case EMERGENCY_ON:
+ case EMERGENCY_OFF:
+ case LOST_GAS:
+ case SAFETY_STOP:
+ break;
+ case DECOSTOP:
+ deco = array_uint16_le (data + offset);
+ have_deco = 1;
+ break;
+ case GASMIX:
+ o2 = data[offset + 0];
+ he = data[offset + 1];
+ if (o2 != o2_previous || he != he_previous) {
+ // Find the gasmix in the list.
+ unsigned int i = 0;
+ while (i < ngasmixes) {
+ if (o2 == gasmix[i].oxygen && he == gasmix[i].helium)
+ 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;
+ }
+ gasmix[i].oxygen = o2;
+ gasmix[i].helium = he;
+ ngasmixes = i + 1;
+ }
+
+ o2_previous = o2;
+ he_previous = he;
+ gasmix_idx = i;
+ have_gasmix = 1;
+ }
+ break;
+ case SETPOINT:
+ setpoint = data[offset];
+ have_setpoint = 1;
+ break;
+ case TANK_PRESSURE:
+ tank_id = array_uint16_le (data + offset + 0);
+ tank_pressure = array_uint16_le (data + offset + 2);
+ if (tank_id != tank_id_previous) {
+ // Find the tank in the list.
+ unsigned int i = 0;
+ while (i < ntanks) {
+ if (tank_id == tank[i].id)
+ break;
+ i++;
+ }
+
+ // Add a new tank if necessary.
+ if (i >= ntanks) {
+ if (i >= NTANKS) {
+ ERROR (abstract->context, "Maximum number of tanks reached.");
+ return DC_STATUS_DATAFORMAT;
+ }
+ tank[i].id = tank_id;
+ tank[i].beginpressure = tank_pressure;
+ tank[i].endpressure = tank_pressure;
+ ntanks = i + 1;
+ }
+
+ tank_id_previous = tank_id;
+ tank_idx = i;
+ }
+ tank[tank_idx].endpressure = tank_pressure;
+ pressure[tank_idx] = tank_pressure;
+ have_pressure |= 1 << tank_idx;
+ break;
+ case TANK_LIST:
+ break;
+ default:
+ WARNING (abstract->context, "Unknown event %u", type);
+ break;
+ }
+
+ offset += length;
+ events++;
+ } else {
+ if (samples >= nsamples) {
+ break;
+ }
+
+ // Get the sample length.
+ unsigned int length = 2;
+ if (version == XEO_V1_A || version == XEO_V2_A ||
+ version == XEO_V3_A || version == KAON_V1 ||
+ version == KAON_V2) {
+ length += 14;
+ }
+
+ if (offset + length > size) {
+ ERROR (abstract->context, "Buffer overflow at offset %u", offset);
+ return DC_STATUS_DATAFORMAT;
+ }
+
+ // Time (seconds).
+ time += interval;
+ sample.time = time;
+ if (callback) callback (DC_SAMPLE_TIME, sample, userdata);
+
+ // Depth (1/100 m).
+ sample.depth = value / 100.0;
+ if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
+
+ // Temperature (1/10 °C).
+ int temperature = (signed short) array_uint16_le (data + offset);
+ sample.temperature = temperature / 10.0;
+ if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
+
+ // Gas mix
+ if (have_gasmix) {
+ sample.gasmix = gasmix_idx;
+ if (callback) callback (DC_SAMPLE_GASMIX, sample, userdata);
+ have_gasmix = 0;
+ }
+
+ // Setpoint (1/10 bar).
+ if (have_setpoint) {
+ sample.setpoint = setpoint / 10.0;
+ if (callback) callback (DC_SAMPLE_SETPOINT, sample, userdata);
+ have_setpoint = 0;
+ }
+
+ // Tank pressure (1/100 bar).
+ if (have_pressure) {
+ for (unsigned int i = 0; i < ntanks; ++i) {
+ if (have_pressure & (1 << i)) {
+ sample.pressure.tank = i;
+ sample.pressure.value = pressure[i] / 100.0;
+ if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata);
+ }
+ }
+ have_pressure = 0;
+ }
+
+ // Deco/ndl
+ if (have_deco) {
+ if (deco) {
+ sample.deco.type = DC_DECO_DECOSTOP;
+ sample.deco.depth = deco / 100.0;
+ } else {
+ sample.deco.type = DC_DECO_NDL;
+ sample.deco.depth = 0.0;
+ }
+ sample.deco.time = 0;
+ if (callback) callback (DC_SAMPLE_DECO, sample, userdata);
+ have_deco = 0;
+ }
+
+ offset += length;
+ samples++;
+ }
+ }
+
+ // Cache the data for later use.
+ for (unsigned int i = 0; i < ntanks; ++i) {
+ parser->tank[i] = tank[i];
+ }
+ for (unsigned int i = 0; i < ngasmixes; ++i) {
+ parser->gasmix[i] = gasmix[i];
+ }
+ parser->ngasmixes = ngasmixes;
+ parser->ntanks = ntanks;
+ parser->cached = 1;
+
+ return DC_STATUS_SUCCESS;
+}
diff --git a/src/parser.c b/src/parser.c
index 977db14..2f7251d 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -58,6 +58,7 @@
#include "cochran_commander.h"
#include "tecdiving_divecomputereu.h"
#include "mclean_extreme.h"
+#include "liquivision_lynx.h"
#include "context-private.h"
#include "parser-private.h"
@@ -176,6 +177,9 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa
case DC_FAMILY_MCLEAN_EXTREME:
rc = mclean_extreme_parser_create (&parser, context);
break;
+ case DC_FAMILY_LIQUIVISION_LYNX:
+ rc = liquivision_lynx_parser_create (&parser, context, model);
+ break;
default:
return DC_STATUS_INVALIDARGS;
}