diff --git a/examples/common.c b/examples/common.c index 6c1c131..10eb496 100644 --- a/examples/common.c +++ b/examples/common.c @@ -94,6 +94,7 @@ static const backend_table_t g_backends[] = { {"extreme", DC_FAMILY_MCLEAN_EXTREME, 0}, {"lynx", DC_FAMILY_LIQUIVISION_LYNX, 0}, {"sp2", DC_FAMILY_SPORASUB_SP2, 0}, + {"excursion", DC_FAMILY_DEEPSIX_EXCURSION, 0}, }; static const transport_table_t g_transports[] = { diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h index 62f62f3..f6ca9fe 100644 --- a/include/libdivecomputer/common.h +++ b/include/libdivecomputer/common.h @@ -110,6 +110,8 @@ typedef enum dc_family_t { DC_FAMILY_LIQUIVISION_LYNX = (17 << 16), /* Sporasub */ DC_FAMILY_SPORASUB_SP2 = (18 << 16), + /* Deep Six */ + DC_FAMILY_DEEPSIX_EXCURSION = (19 << 16), } dc_family_t; #ifdef __cplusplus diff --git a/msvc/libdivecomputer.vcxproj b/msvc/libdivecomputer.vcxproj index 2f99a43..5c8fe3a 100644 --- a/msvc/libdivecomputer.vcxproj +++ b/msvc/libdivecomputer.vcxproj @@ -182,6 +182,8 @@ + + @@ -304,6 +306,7 @@ + diff --git a/src/Makefile.am b/src/Makefile.am index 19bca52..5cec346 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -75,6 +75,7 @@ libdivecomputer_la_SOURCES = \ mclean_extreme.h mclean_extreme.c mclean_extreme_parser.c \ liquivision_lynx.h liquivision_lynx.c liquivision_lynx_parser.c \ sporasub_sp2.h sporasub_sp2.c sporasub_sp2_parser.c \ + deepsix_excursion.h deepsix_excursion.c deepsix_excursion_parser.c \ socket.h socket.c \ irda.c \ usb.c \ diff --git a/src/deepsix_excursion.c b/src/deepsix_excursion.c new file mode 100644 index 0000000..687f6f6 --- /dev/null +++ b/src/deepsix_excursion.c @@ -0,0 +1,438 @@ +/* + * libdivecomputer + * + * Copyright (C) 2021 Ryan Gardner, 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 "deepsix_excursion.h" +#include "context-private.h" +#include "device-private.h" +#include "platform.h" +#include "checksum.h" +#include "array.h" + +#define MAXPACKET 255 + +#define HEADERSIZE 156 + +#define NSTEPS 1000 +#define STEP(i,n) (NSTEPS * (i) / (n)) + +#define FP_SIZE 6 +#define FP_OFFSET 12 + +#define DIR_WRITE 0x00 +#define DIR_READ 0x01 + +#define GRP_INFO 0xA0 +#define CMD_INFO_SERIAL 0x03 +#define CMD_INFO_LASTDIVE 0x04 + +#define GRP_SETTINGS 0xB0 +#define CMD_SETTINGS_DATE 0x01 +#define CMD_SETTINGS_TIME 0x03 +#define CMD_SETTINGS_STORE 0x27 +#define CMD_SETTINGS_LOAD 0x28 + +#define GRP_DIVE 0xC0 +#define CMD_DIVE_HEADER 0x02 +#define CMD_DIVE_PROFILE 0x03 + +typedef struct deepsix_excursion_device_t { + dc_device_t base; + dc_iostream_t *iostream; + unsigned char fingerprint[FP_SIZE]; +} deepsix_excursion_device_t; + +static dc_status_t deepsix_excursion_device_set_fingerprint (dc_device_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +static dc_status_t deepsix_excursion_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime); + +static const dc_device_vtable_t deepsix_excursion_device_vtable = { + sizeof(deepsix_excursion_device_t), + DC_FAMILY_DEEPSIX_EXCURSION, + deepsix_excursion_device_set_fingerprint, /* set_fingerprint */ + NULL, /* read */ + NULL, /* write */ + NULL, /* dump */ + deepsix_excursion_device_foreach, /* foreach */ + deepsix_excursion_device_timesync, /* timesync */ + NULL, /* close */ +}; + +static dc_status_t +deepsix_excursion_send (deepsix_excursion_device_t *device, unsigned char grp, unsigned char cmd, unsigned char dir, const unsigned char data[], unsigned int size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + unsigned char packet[4 + MAXPACKET + 1]; + + if (device_is_cancelled (abstract)) + return DC_STATUS_CANCELLED; + + if (size > MAXPACKET) + return DC_STATUS_INVALIDARGS; + + // Setup the data packet + packet[0] = grp; + packet[1] = cmd; + packet[2] = dir; + packet[3] = size; + if (size) { + memcpy(packet + 4, data, size); + } + packet[size + 4] = checksum_add_uint8 (packet, size + 4, 0) ^ 0xFF; + + // Send the data packet. + status = dc_iostream_write (device->iostream, packet, 4 + size + 1, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the command."); + return status; + } + + return status; +} + +static dc_status_t +deepsix_excursion_recv (deepsix_excursion_device_t *device, unsigned char grp, unsigned char cmd, unsigned char dir, unsigned char data[], unsigned int size, unsigned int *actual) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + unsigned char packet[4 + MAXPACKET + 1]; + size_t transferred = 0; + + // Read the packet header, payload and checksum. + status = dc_iostream_read (device->iostream, packet, sizeof(packet), &transferred); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the packet."); + return status; + } + + if (transferred < 4) { + ERROR (abstract->context, "Packet header too short ("DC_PRINTF_SIZE").", transferred); + return DC_STATUS_PROTOCOL; + } + + // Verify the packet header. + if (packet[0] != grp || packet[1] != cmd || packet[2] != dir) { + ERROR (device->base.context, "Unexpected packet header."); + return DC_STATUS_PROTOCOL; + } + + unsigned int len = packet[3]; + if (len > MAXPACKET) { + ERROR (abstract->context, "Packet header length too large (%u).", len); + return DC_STATUS_PROTOCOL; + } + + if (transferred < 4 + len + 1) { + ERROR (abstract->context, "Packet data too short ("DC_PRINTF_SIZE").", transferred); + return DC_STATUS_PROTOCOL; + } + + // Verify the checksum. + unsigned char csum = checksum_add_uint8 (packet, len + 4, 0) ^ 0xFF; + if (packet[len + 4] != csum) { + ERROR (abstract->context, "Unexpected packet checksum (%02x)", csum); + return DC_STATUS_PROTOCOL; + } + + if (len > size) { + ERROR (abstract->context, "Unexpected packet length (%u).", len); + return DC_STATUS_PROTOCOL; + } + + memcpy(data, packet + 4, len); + + if (actual) + *actual = len; + + return status; +} + +static dc_status_t +deepsix_excursion_transfer (deepsix_excursion_device_t *device, unsigned char grp, unsigned char cmd, unsigned char dir, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, unsigned int *actual) +{ + dc_status_t status = DC_STATUS_SUCCESS; + + status = deepsix_excursion_send (device, grp, cmd, dir, command, csize); + if (status != DC_STATUS_SUCCESS) + return status; + + status = deepsix_excursion_recv (device, grp + 1, cmd, dir, answer, asize, actual); + if (status != DC_STATUS_SUCCESS) + return status; + + return status; +} + +dc_status_t +deepsix_excursion_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) +{ + dc_status_t status = DC_STATUS_SUCCESS; + deepsix_excursion_device_t *device = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + device = (deepsix_excursion_device_t *) dc_device_allocate (context, &deepsix_excursion_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; + } + + // Make sure everything is in a sane state. + dc_iostream_sleep (device->iostream, 300); + dc_iostream_purge (device->iostream, DC_DIRECTION_ALL); + + *out = (dc_device_t *) device; + + return DC_STATUS_SUCCESS; + +error_free: + dc_device_deallocate ((dc_device_t *) device); + return status; +} + +static dc_status_t +deepsix_excursion_device_set_fingerprint (dc_device_t *abstract, const unsigned char *data, unsigned int size) +{ + deepsix_excursion_device_t *device = (deepsix_excursion_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 +deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) +{ + dc_status_t status = DC_STATUS_SUCCESS; + deepsix_excursion_device_t *device = (deepsix_excursion_device_t *) abstract; + + // Enable progress notifications. + dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Load the settings into memory. + status = deepsix_excursion_transfer (device, GRP_SETTINGS, CMD_SETTINGS_LOAD, DIR_WRITE, NULL, 0, NULL, 0, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to load the settings."); + return status; + } + + // Read the serial number + unsigned char rsp_serial[12] = {0}; + status = deepsix_excursion_transfer (device, GRP_INFO, CMD_INFO_SERIAL, DIR_READ, NULL, 0, rsp_serial, sizeof(rsp_serial), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the serial number."); + return status; + } + + // Emit a device info event. + dc_event_devinfo_t devinfo; + devinfo.model = 0; + devinfo.firmware = 0; + devinfo.serial = array_convert_str2num (rsp_serial + 3, sizeof(rsp_serial) - 3); + device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); + + // Read the index of the last dive. + const unsigned char cmd_index[2] = {0}; + unsigned char rsp_index[2] = {0}; + status = deepsix_excursion_transfer (device, GRP_INFO, CMD_INFO_LASTDIVE, DIR_READ, cmd_index, sizeof(cmd_index), rsp_index, sizeof(rsp_index), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the last dive index."); + return status; + } + + // Calculate the number of dives. + unsigned int ndives = array_uint16_le (rsp_index); + + // Update and emit a progress event. + progress.maximum = ndives * NSTEPS; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + dc_buffer_t *buffer = dc_buffer_new(0); + if (buffer == NULL) { + ERROR (abstract->context, "Insufficient buffer space available."); + return DC_STATUS_NOMEMORY; + } + + for (unsigned int i = 0; i < ndives; ++i) { + unsigned int number = ndives - i; + + const unsigned char cmd_header[] = { + (number ) & 0xFF, + (number >> 8) & 0xFF}; + unsigned char rsp_header[HEADERSIZE] = {0}; + status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_HEADER, DIR_READ, cmd_header, sizeof(cmd_header), rsp_header, sizeof(rsp_header), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the dive header."); + goto error_free; + } + + if (memcmp(rsp_header + FP_OFFSET, device->fingerprint, sizeof(device->fingerprint)) == 0) + break; + + unsigned int length = array_uint32_le (rsp_header + 8); + + // Update and emit a progress event. + progress.current = i * NSTEPS + STEP(sizeof(rsp_header), sizeof(rsp_header) + length); + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + dc_buffer_clear(buffer); + dc_buffer_reserve(buffer, sizeof(rsp_header) + length); + + if (!dc_buffer_append(buffer, rsp_header, sizeof(rsp_header))) { + ERROR (abstract->context, "Insufficient buffer space available."); + status = DC_STATUS_NOMEMORY; + goto error_free; + } + + unsigned offset = 0; + while (offset < length) { + unsigned int len = 0; + const unsigned char cmd_profile[] = { + (number ) & 0xFF, + (number >> 8) & 0xFF, + (offset ) & 0xFF, + (offset >> 8) & 0xFF, + (offset >> 16) & 0xFF, + (offset >> 24) & 0xFF}; + unsigned char rsp_profile[MAXPACKET] = {0}; + status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_PROFILE, DIR_READ, cmd_profile, sizeof(cmd_profile), rsp_profile, sizeof(rsp_profile), &len); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the dive profile."); + goto error_free; + } + + // Remove padding from the last packet. + unsigned int n = len; + if (offset + n > length) { + n = length - offset; + } + + // Update and emit a progress event. + progress.current = i * NSTEPS + STEP(sizeof(rsp_header) + offset + n, sizeof(rsp_header) + length); + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + if (!dc_buffer_append(buffer, rsp_profile, n)) { + ERROR (abstract->context, "Insufficient buffer space available."); + status = DC_STATUS_NOMEMORY; + goto error_free; + } + + offset += n; + } + + unsigned char *data = dc_buffer_get_data(buffer); + unsigned int size = dc_buffer_get_size(buffer); + if (callback && !callback (data, size, data + FP_OFFSET, sizeof(device->fingerprint), userdata)) { + break; + } + } + +error_free: + dc_buffer_free(buffer); + return status; +} + +static dc_status_t +deepsix_excursion_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) +{ + dc_status_t status = DC_STATUS_SUCCESS; + deepsix_excursion_device_t *device = (deepsix_excursion_device_t *) abstract; + + if (datetime == NULL || datetime->year < 2000) { + ERROR (abstract->context, "Invalid date/time value specified."); + return DC_STATUS_INVALIDARGS; + } + + const unsigned char cmd_date[] = { + datetime->year - 2000, + datetime->month, + datetime->day}; + + const unsigned char cmd_time[] = { + datetime->hour, + datetime->minute, + datetime->second}; + + const unsigned char cmd_store[] = {0x00}; + + unsigned char rsp_date[sizeof(cmd_date)] = {0}; + status = deepsix_excursion_transfer (device, GRP_SETTINGS, CMD_SETTINGS_DATE, DIR_WRITE, cmd_date, sizeof(cmd_date), rsp_date, sizeof(rsp_date), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to set the date."); + return status; + } + + if (memcmp(rsp_date, cmd_date, sizeof(cmd_date)) != 0) { + ERROR (abstract->context, "Failed to verify the date."); + return DC_STATUS_PROTOCOL; + } + + unsigned char rsp_time[sizeof(cmd_time)] = {0}; + status = deepsix_excursion_transfer (device, GRP_SETTINGS, CMD_SETTINGS_TIME, DIR_WRITE, cmd_time, sizeof(cmd_time), rsp_time, sizeof(rsp_time), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to set the time."); + return status; + } + + if (memcmp(rsp_time, cmd_time, sizeof(cmd_time)) != 0) { + ERROR (abstract->context, "Failed to verify the time."); + return DC_STATUS_PROTOCOL; + } + + status = deepsix_excursion_transfer (device, GRP_SETTINGS, CMD_SETTINGS_STORE, DIR_WRITE, cmd_store, sizeof(cmd_store), NULL, 0, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to store the settings."); + return status; + } + + return status; +} diff --git a/src/deepsix_excursion.h b/src/deepsix_excursion.h new file mode 100644 index 0000000..42ecffc --- /dev/null +++ b/src/deepsix_excursion.h @@ -0,0 +1,43 @@ +/* + * libdivecomputer + * + * Copyright (C) 2021 Ryan Gardner, 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 DEEPSIX_EXCURSION_H +#define DEEPSIX_EXCURSION_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +dc_status_t +deepsix_excursion_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream); + +dc_status_t +deepsix_excursion_parser_create (dc_parser_t **parser, dc_context_t *context); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* DEEPSIX_EXCURSION_H */ diff --git a/src/deepsix_excursion_parser.c b/src/deepsix_excursion_parser.c new file mode 100644 index 0000000..a5d312d --- /dev/null +++ b/src/deepsix_excursion_parser.c @@ -0,0 +1,251 @@ +/* + * libdivecomputer + * + * Copyright (C) 2021 Ryan Gardner, 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 + +#include "deepsix_excursion.h" +#include "context-private.h" +#include "parser-private.h" +#include "array.h" + +#define HEADERSIZE 156 + +#define ALARM 0x0001 +#define TEMPERATURE 0x0002 +#define DECO 0x0003 +#define CEILING 0x0004 +#define CNS 0x0005 + +#define DENSITY 1024.0 + +typedef struct deepsix_excursion_parser_t { + dc_parser_t base; +} deepsix_excursion_parser_t; + +static dc_status_t deepsix_excursion_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t deepsix_excursion_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); +static dc_status_t deepsix_excursion_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); +static dc_status_t deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); + +static const dc_parser_vtable_t deepsix_parser_vtable = { + sizeof(deepsix_excursion_parser_t), + DC_FAMILY_DEEPSIX_EXCURSION, + deepsix_excursion_parser_set_data, /* set_data */ + deepsix_excursion_parser_get_datetime, /* datetime */ + deepsix_excursion_parser_get_field, /* fields */ + deepsix_excursion_parser_samples_foreach, /* samples_foreach */ + NULL /* destroy */ +}; + +dc_status_t +deepsix_excursion_parser_create (dc_parser_t **out, dc_context_t *context) +{ + deepsix_excursion_parser_t *parser = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + parser = (deepsix_excursion_parser_t *) dc_parser_allocate (context, &deepsix_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 +deepsix_excursion_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) +{ + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepsix_excursion_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) +{ + const unsigned char *data = abstract->data; + unsigned int size = abstract->size; + + if (size < HEADERSIZE) + return DC_STATUS_DATAFORMAT; + + if (datetime) { + datetime->year = data[12] + 2000; + datetime->month = data[13]; + datetime->day = data[14]; + datetime->hour = data[15]; + datetime->minute = data[16]; + datetime->second = data[17]; + datetime->timezone = DC_TIMEZONE_NONE; + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepsix_excursion_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) +{ + const unsigned char *data = abstract->data; + unsigned int size = abstract->size; + + if (size < HEADERSIZE) + return DC_STATUS_DATAFORMAT; + + unsigned int atmospheric = array_uint32_le(data + 56); + + dc_salinity_t *water = (dc_salinity_t *) value; + + if (value) { + switch (type) { + case DC_FIELD_DIVETIME: + *((unsigned int *) value) = array_uint32_le(data + 20); + break; + case DC_FIELD_MAXDEPTH: + *((double *) value) = (signed int)(array_uint32_le(data + 28) - atmospheric) * (BAR / 1000.0) / (DENSITY * GRAVITY); + break; + break; + case DC_FIELD_TEMPERATURE_MINIMUM: + *((double *) value) = (signed int) array_uint32_le(data + 32) / 10.0; + break; + case DC_FIELD_ATMOSPHERIC: + *((double *) value) = atmospheric / 1000.0; + break; + case DC_FIELD_SALINITY: + water->type = DC_WATER_SALT; + water->density = DENSITY; + break; + case DC_FIELD_DIVEMODE: + switch (array_uint32_le(data + 4)) { + case 0: + *((dc_divemode_t *) value) = DC_DIVEMODE_OC; + break; + case 1: + *((dc_divemode_t *) value) = DC_DIVEMODE_GAUGE; + break; + case 2: + *((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE; + break; + default: + return DC_STATUS_DATAFORMAT; + } + break; + default: + return DC_STATUS_UNSUPPORTED; + } + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepsix_excursion_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; + + if (size < HEADERSIZE) + return DC_STATUS_DATAFORMAT; + + int firmware4c = memcmp(data + 48, "D01-4C", 6) == 0; + + unsigned int maxtype = firmware4c ? TEMPERATURE : CNS; + + unsigned int interval = array_uint32_le(data + 24); + unsigned int atmospheric = array_uint32_le(data + 56); + + unsigned int time = 0; + unsigned int offset = HEADERSIZE; + while (offset + 1 < size) { + dc_sample_value_t sample = {0}; + + // Get the sample type. + unsigned int type = data[offset]; + if (type < 1 || type > maxtype) { + ERROR (abstract->context, "Unknown sample type (%u).", type); + return DC_STATUS_DATAFORMAT; + } + + // Get the sample length. + unsigned int length = 1; + if (type == ALARM || type == CEILING) { + length = 8; + } else if (type == TEMPERATURE || type == DECO || type == CNS) { + length = 6; + } + + // Verify the length. + if (offset + length > size) { + WARNING (abstract->context, "Unexpected end of data."); + break; + } + + unsigned int misc = data[offset + 1]; + unsigned int depth = array_uint16_le(data + offset + 2); + + if (type == TEMPERATURE) { + time += interval; + sample.time = time; + if (callback) callback(DC_SAMPLE_TIME, sample, userdata); + + sample.depth = (signed int)(depth - atmospheric) * (BAR / 1000.0) / (DENSITY * GRAVITY); + if (callback) callback(DC_SAMPLE_DEPTH, sample, userdata); + } + + if (type == ALARM) { + unsigned int alarm_time = array_uint16_le(data + offset + 4); + unsigned int alarm_value = array_uint16_le(data + offset + 6); + } else if (type == TEMPERATURE) { + unsigned int temperature = array_uint16_le(data + offset + 4); + if (firmware4c) { + if (temperature > 1300) { + length = 8; + } else if (temperature >= 10) { + sample.temperature = temperature / 10.0; + if (callback) callback(DC_SAMPLE_TEMPERATURE, sample, userdata); + } + } else { + sample.temperature = temperature / 10.0; + if (callback) callback(DC_SAMPLE_TEMPERATURE, sample, userdata); + } + } else if (type == DECO) { + unsigned int deco = array_uint16_le(data + offset + 4); + } else if (type == CEILING) { + unsigned int ceiling_depth = array_uint16_le(data + offset + 4); + unsigned int ceiling_time = array_uint16_le(data + offset + 6); + } else if (type == CNS) { + unsigned int cns = array_uint16_le(data + offset + 4); + sample.cns = cns; + if (callback) callback(DC_SAMPLE_CNS, sample, userdata); + } + + offset += length; + } + + return DC_STATUS_SUCCESS; +} diff --git a/src/descriptor.c b/src/descriptor.c index 06b0243..67b4a25 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -62,6 +62,7 @@ static int dc_filter_divesystem (dc_transport_t transport, const void *userdata, static int dc_filter_oceanic (dc_transport_t transport, const void *userdata, void *params); static int dc_filter_mclean (dc_transport_t transport, const void *userdata, void *params); static int dc_filter_atomic (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_deepsix (dc_transport_t transport, const void *userdata, void *params); static dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item); @@ -421,6 +422,8 @@ static const dc_descriptor_t g_descriptors[] = { {"Liquivision", "Kaon", DC_FAMILY_LIQUIVISION_LYNX, 3, DC_TRANSPORT_SERIAL, NULL}, /* Sporasub */ {"Sporasub", "SP2", DC_FAMILY_SPORASUB_SP2, 0, DC_TRANSPORT_SERIAL, NULL}, + /* Deep Six Excursion */ + {"Deep Six", "Excursion", DC_FAMILY_DEEPSIX_EXCURSION, 0, DC_TRANSPORT_BLE, dc_filter_deepsix}, }; static int @@ -720,6 +723,20 @@ static int dc_filter_atomic (dc_transport_t transport, const void *userdata, voi return 1; } +static int dc_filter_deepsix (dc_transport_t transport, const void *userdata, void *params) +{ + static const char * const bluetooth[] = { + "EXCURSION", + }; + + if (transport == DC_TRANSPORT_BLE) { + return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_name); + } + + return 1; +} + + dc_status_t dc_descriptor_iterator (dc_iterator_t **out) { diff --git a/src/device.c b/src/device.c index 6c6732d..3cd3e3a 100644 --- a/src/device.c +++ b/src/device.c @@ -60,6 +60,7 @@ #include "mclean_extreme.h" #include "liquivision_lynx.h" #include "sporasub_sp2.h" +#include "deepsix_excursion.h" #include "device-private.h" #include "context-private.h" @@ -223,6 +224,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr case DC_FAMILY_SPORASUB_SP2: rc = sporasub_sp2_device_open (&device, context, iostream); break; + case DC_FAMILY_DEEPSIX_EXCURSION: + rc = deepsix_excursion_device_open (&device, context, iostream); + break; default: return DC_STATUS_INVALIDARGS; } diff --git a/src/parser.c b/src/parser.c index 2596de4..9a0f71c 100644 --- a/src/parser.c +++ b/src/parser.c @@ -60,6 +60,7 @@ #include "mclean_extreme.h" #include "liquivision_lynx.h" #include "sporasub_sp2.h" +#include "deepsix_excursion.h" #include "context-private.h" #include "parser-private.h" @@ -184,6 +185,9 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa case DC_FAMILY_SPORASUB_SP2: rc = sporasub_sp2_parser_create (&parser, context); break; + case DC_FAMILY_DEEPSIX_EXCURSION: + rc = deepsix_excursion_parser_create (&parser, context); + break; default: return DC_STATUS_INVALIDARGS; }