diff --git a/contrib/udev/libdivecomputer.rules b/contrib/udev/libdivecomputer.rules index 08c234e..14d0ce6 100644 --- a/contrib/udev/libdivecomputer.rules +++ b/contrib/udev/libdivecomputer.rules @@ -7,11 +7,17 @@ SUBSYSTEM=="usb", ATTR{idVendor}=="1493", ATTR{idProduct}=="0030", GROUP="plugde # Suunto EON Core SUBSYSTEM=="usb", ATTR{idVendor}=="1493", ATTR{idProduct}=="0033", GROUP="plugdev" +# Suunto D5 +SUBSYSTEM=="usb", ATTR{idVendor}=="1493", ATTR{idProduct}=="0035", GROUP="plugdev" + # Scubapro G2 SUBSYSTEM=="usb", ATTR{idVendor}=="2e6c", ATTR{idProduct}=="3201", GROUP="plugdev" # Scubapro G2 Console SUBSYSTEM=="usb", ATTR{idVendor}=="2e6c", ATTR{idProduct}=="3211", GROUP="plugdev" +# Scubapro G2 HUD +SUBSYSTEM=="usb", ATTR{idVendor}=="2e6c", ATTR{idProduct}=="0x4201", GROUP="plugdev" + # Scubapro Aladin Square SUBSYSTEM=="usb", ATTR{idVendor}=="c251", ATTR{idProduct}=="2006", GROUP="plugdev" diff --git a/src/aes.c b/src/aes.c index a2f00dd..55eac5c 100644 --- a/src/aes.c +++ b/src/aes.c @@ -1,4 +1,28 @@ /* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to This is an implementation of the AES128 algorithm, specifically ECB and CBC mode. diff --git a/src/aes.h b/src/aes.h index 051d813..fedd0a5 100644 --- a/src/aes.h +++ b/src/aes.h @@ -1,3 +1,29 @@ +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +*/ #ifndef _AES_H_ #define _AES_H_ diff --git a/src/descriptor.c b/src/descriptor.c index c9f51ee..0d9a561 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -254,6 +254,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Mares", "Icon HD Net Ready", DC_FAMILY_MARES_ICONHD , 0x15, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Puck Pro", DC_FAMILY_MARES_ICONHD , 0x18, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_mares}, {"Mares", "Nemo Wide 2", DC_FAMILY_MARES_ICONHD , 0x19, DC_TRANSPORT_SERIAL, NULL}, + {"Mares", "Genius", DC_FAMILY_MARES_ICONHD , 0x1C, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_mares}, {"Mares", "Puck 2", DC_FAMILY_MARES_ICONHD , 0x1F, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Quad Air", DC_FAMILY_MARES_ICONHD , 0x23, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_mares}, {"Mares", "Smart Air", DC_FAMILY_MARES_ICONHD , 0x24, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_mares}, diff --git a/src/divesystem_idive.c b/src/divesystem_idive.c index 1119b23..1c71de9 100644 --- a/src/divesystem_idive.c +++ b/src/divesystem_idive.c @@ -486,7 +486,7 @@ divesystem_idive_device_foreach (dc_device_t *abstract, dc_dive_callback_t callb if (!dc_buffer_append(buffer, packet, commands->header.size)) { ERROR (abstract->context, "Insufficient buffer space available."); dc_buffer_free(buffer); - return rc; + return DC_STATUS_NOMEMORY; } for (unsigned int j = 0; j < nsamples; j += commands->nsamples) { @@ -515,7 +515,7 @@ divesystem_idive_device_foreach (dc_device_t *abstract, dc_dive_callback_t callb if (!dc_buffer_append(buffer, packet, commands->sample.size * n)) { ERROR (abstract->context, "Insufficient buffer space available."); dc_buffer_free(buffer); - return rc; + return DC_STATUS_NOMEMORY; } } diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index ec9a4eb..bb318e1 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -28,11 +28,15 @@ #include "device-private.h" #include "array.h" #include "rbstream.h" +#include "platform.h" #define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array)) #define ISINSTANCE(device) dc_device_isinstance((device), &mares_iconhd_device_vtable) +#define NSTEPS 1000 +#define STEP(i,n) (NSTEPS * (i) / (n)) + #define MATRIX 0x0F #define SMART 0x000010 #define SMARTAPNEA 0x010010 @@ -40,6 +44,7 @@ #define ICONHDNET 0x15 #define PUCKPRO 0x18 #define NEMOWIDE2 0x19 +#define GENIUS 0x1C #define PUCK2 0x1F #define QUADAIR 0x23 #define SMARTAIR 0x24 @@ -49,6 +54,20 @@ #define ACK 0xAA #define EOF 0xEA +#define XOR 0xA5 + +#define CMD_VERSION 0xC2 +#define CMD_FLASHSIZE 0xB3 +#define CMD_READ 0xE7 +#define CMD_OBJ_INIT 0xBF +#define CMD_OBJ_EVEN 0xAC +#define CMD_OBJ_ODD 0xFE + +#define OBJ_LOGBOOK 0x2008 +#define OBJ_LOGBOOK_COUNT 0x01 +#define OBJ_DIVE 0x3000 +#define OBJ_DIVE_HEADER 0x02 +#define OBJ_DIVE_DATA 0x03 #define AIR 0 #define GAUGE 1 @@ -71,6 +90,7 @@ typedef struct mares_iconhd_device_t { dc_iostream_t *iostream; const mares_iconhd_layout_t *layout; unsigned char fingerprint[10]; + unsigned int fingerprint_size; unsigned char version[140]; unsigned int model; unsigned int packetsize; @@ -131,6 +151,7 @@ mares_iconhd_get_model (mares_iconhd_device_t *device) {"Icon AIR", ICONHDNET}, {"Puck Pro", PUCKPRO}, {"Nemo Wide 2", NEMOWIDE2}, + {"Genius", GENIUS}, {"Puck 2", PUCK2}, {"Quad Air", QUADAIR}, {"Smart Air", SMARTAIR}, @@ -311,6 +332,119 @@ mares_iconhd_transfer (mares_iconhd_device_t *device, const unsigned char comman return DC_STATUS_SUCCESS; } +static dc_status_t +mares_iconhd_read_object(mares_iconhd_device_t *device, dc_event_progress_t *progress, dc_buffer_t *buffer, unsigned int index, unsigned int subindex) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + dc_transport_t transport = dc_iostream_get_transport (device->iostream); + const unsigned int maxpacket = (transport == DC_TRANSPORT_BLE) ? 124 : 504; + + // Update and emit a progress event. + unsigned int initial = 0; + if (progress) { + initial = progress->current; + device_event_emit (abstract, DC_EVENT_PROGRESS, progress); + } + + // Transfer the init packet. + unsigned char rsp_init[16]; + unsigned char cmd_init[18] = { + CMD_OBJ_INIT, + CMD_OBJ_INIT ^ XOR, + 0x40, + (index >> 0) & 0xFF, + (index >> 8) & 0xFF, + subindex & 0xFF + }; + memset (cmd_init + 6, 0x00, sizeof(cmd_init) - 6); + status = mares_iconhd_transfer (device, cmd_init, sizeof (cmd_init), rsp_init, sizeof (rsp_init)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to transfer the init packet."); + return status; + } + + // Verify the packet header. + if (memcmp (cmd_init + 3, rsp_init + 1, 3) != 0) { + ERROR (abstract->context, "Unexpected packet header."); + return DC_STATUS_PROTOCOL; + } + + unsigned int nbytes = 0, size = 0; + if (rsp_init[0] == 0x41) { + // A large (and variable size) payload is split into multiple + // data packets. The first packet contains only the total size + // of the payload. + size = array_uint32_le (rsp_init + 4); + } else if (rsp_init[0] == 0x42) { + // A short (and fixed size) payload is embedded into the first + // data packet. + size = sizeof(rsp_init) - 4; + + // Append the payload to the output buffer. + if (!dc_buffer_append (buffer, rsp_init + 4, sizeof(rsp_init) - 4)) { + ERROR (abstract->context, "Insufficient buffer space available."); + return DC_STATUS_NOMEMORY; + } + + nbytes += sizeof(rsp_init) - 4; + } else { + ERROR (abstract->context, "Unexpected packet type (%02x).", rsp_init[0]); + return DC_STATUS_PROTOCOL; + } + + // Update and emit a progress event. + if (progress) { + progress->current = initial + STEP (nbytes, size); + device_event_emit (abstract, DC_EVENT_PROGRESS, progress); + } + + unsigned int npackets = 0; + while (nbytes < size) { + // Get the command byte. + unsigned char toggle = npackets % 2; + unsigned char cmd = toggle == 0 ? CMD_OBJ_EVEN : CMD_OBJ_ODD; + + // Get the packet size. + unsigned int len = size - nbytes; + if (len > maxpacket) { + len = maxpacket; + } + + // Transfer the segment packet. + unsigned char rsp_segment[1 + 504]; + unsigned char cmd_segment[] = {cmd, cmd ^ XOR}; + status = mares_iconhd_transfer (device, cmd_segment, sizeof (cmd_segment), rsp_segment, len + 1); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to transfer the segment packet."); + return status; + } + + // Verify the packet header. + if ((rsp_segment[0] & 0xF0) >> 4 != toggle) { + ERROR (abstract->context, "Unexpected packet header (%02x).", rsp_segment[0]); + return DC_STATUS_PROTOCOL; + } + + // Append the payload to the output buffer. + if (!dc_buffer_append (buffer, rsp_segment + 1, len)) { + ERROR (abstract->context, "Insufficient buffer space available."); + return DC_STATUS_NOMEMORY; + } + + nbytes += len; + npackets++; + + // Update and emit the progress events. + if (progress) { + progress->current = initial + STEP (nbytes, size); + device_event_emit (abstract, DC_EVENT_PROGRESS, progress); + } + } + + return status; +} + dc_status_t mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) { @@ -331,6 +465,7 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_ device->iostream = iostream; device->layout = NULL; memset (device->fingerprint, 0, sizeof (device->fingerprint)); + device->fingerprint_size = sizeof (device->fingerprint); memset (device->version, 0, sizeof (device->version)); device->model = 0; device->packetsize = 0; @@ -370,7 +505,7 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_ dc_iostream_purge (device->iostream, DC_DIRECTION_ALL); // Send the version command. - unsigned char command[] = {0xC2, 0x67}; + unsigned char command[] = {CMD_VERSION, CMD_VERSION ^ XOR}; status = mares_iconhd_transfer (device, command, sizeof (command), device->version, sizeof (device->version)); if (status != DC_STATUS_SUCCESS) { @@ -383,7 +518,7 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_ // Read the size of the flash memory. unsigned int memsize = 0; if (device->model == QUAD) { - unsigned char cmd_flash[] = {0xB3, 0x16}; + unsigned char cmd_flash[] = {CMD_FLASHSIZE, CMD_FLASHSIZE ^ XOR}; unsigned char rsp_flash[4] = {0}; status = mares_iconhd_transfer (device, cmd_flash, sizeof (cmd_flash), rsp_flash, sizeof (rsp_flash)); if (status != DC_STATUS_SUCCESS) { @@ -421,6 +556,11 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_ device->layout = &mares_iconhdnet_layout; device->packetsize = 256; break; + case GENIUS: + device->layout = &mares_iconhdnet_layout; + device->packetsize = 256; + device->fingerprint_size = 4; + break; case ICONHDNET: device->layout = &mares_iconhdnet_layout; device->packetsize = 4096; @@ -447,13 +587,13 @@ mares_iconhd_device_set_fingerprint (dc_device_t *abstract, const unsigned char { mares_iconhd_device_t *device = (mares_iconhd_device_t *) abstract; - if (size && size != sizeof (device->fingerprint)) + if (size && size != device->fingerprint_size) return DC_STATUS_INVALIDARGS; if (size) - memcpy (device->fingerprint, data, sizeof (device->fingerprint)); + memcpy (device->fingerprint, data, device->fingerprint_size); else - memset (device->fingerprint, 0, sizeof (device->fingerprint)); + memset (device->fingerprint, 0, device->fingerprint_size); return DC_STATUS_SUCCESS; } @@ -473,7 +613,7 @@ mares_iconhd_device_read (dc_device_t *abstract, unsigned int address, unsigned len = device->packetsize; // Read the packet. - unsigned char command[] = {0xE7, 0x42, + unsigned char command[] = {CMD_READ, CMD_READ ^ XOR, (address ) & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF, @@ -516,48 +656,19 @@ mares_iconhd_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) dc_buffer_get_size (buffer), device->packetsize); } - static dc_status_t -mares_iconhd_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) +mares_iconhd_device_foreach_raw (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) { dc_status_t rc = DC_STATUS_SUCCESS; mares_iconhd_device_t *device = (mares_iconhd_device_t *) abstract; - if (!ISINSTANCE (abstract)) - return DC_STATUS_INVALIDARGS; - const mares_iconhd_layout_t *layout = device->layout; // Enable progress notifications. dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; - progress.maximum = layout->rb_profile_end - layout->rb_profile_begin + 4; + progress.maximum = layout->rb_profile_end - layout->rb_profile_begin; 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); - - // Read the serial number. - unsigned char serial[4] = {0}; - rc = mares_iconhd_device_read (abstract, 0x0C, serial, sizeof (serial)); - if (rc != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the memory."); - return rc; - } - - // Update and emit a progress event. - progress.current += sizeof (serial); - device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - - // Emit a device info event. - dc_event_devinfo_t devinfo; - devinfo.model = device->model; - devinfo.firmware = 0; - devinfo.serial = array_uint32_le (serial); - device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); - // Get the model code. unsigned int model = device->model; @@ -734,3 +845,113 @@ mares_iconhd_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, return rc; } + +static dc_status_t +mares_iconhd_device_foreach_object (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + mares_iconhd_device_t *device = (mares_iconhd_device_t *) abstract; + + // Enable progress notifications. + dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Allocate memory for the dives. + dc_buffer_t *buffer = dc_buffer_new (4096); + if (buffer == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Read the number of dives. + rc = mares_iconhd_read_object (device, NULL, buffer, OBJ_LOGBOOK, OBJ_LOGBOOK_COUNT); + if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the number of dives."); + dc_buffer_free (buffer); + return rc; + } + + if (dc_buffer_get_size (buffer) < 2) { + ERROR (abstract->context, "Unexpected number of bytes received (" DC_PRINTF_SIZE ").", + dc_buffer_get_size (buffer)); + dc_buffer_free (buffer); + return DC_STATUS_PROTOCOL; + } + + // Get the number of dives. + unsigned int ndives = array_uint16_le (dc_buffer_get_data(buffer)); + + // Update and emit a progress event. + progress.current = 1 * NSTEPS; + progress.maximum = (1 + ndives * 2) * NSTEPS; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Download the dives. + for (unsigned int i = 0; i < ndives; ++i) { + // Erase the buffer. + dc_buffer_clear (buffer); + + // Read the dive header. + rc = mares_iconhd_read_object (device, &progress, buffer, OBJ_DIVE + i, OBJ_DIVE_HEADER); + if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the dive header."); + break; + } + + // Check the fingerprint data. + if (memcmp (dc_buffer_get_data (buffer) + 0x08, device->fingerprint, device->fingerprint_size) == 0) { + INFO (abstract->context, "Stopping due to detecting a matching fingerprint"); + break; + } + + // Read the dive data. + rc = mares_iconhd_read_object (device, &progress, buffer, OBJ_DIVE + i, OBJ_DIVE_DATA); + if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the dive data."); + break; + } + + const unsigned char *data = dc_buffer_get_data (buffer); + if (callback && !callback (data, dc_buffer_get_size (buffer), data + 0x08, device->fingerprint_size, userdata)) { + break; + } + } + + dc_buffer_free(buffer); + + return rc; +} + +static dc_status_t +mares_iconhd_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + mares_iconhd_device_t *device = (mares_iconhd_device_t *) abstract; + + // 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); + + // Read the serial number. + unsigned char serial[4] = {0}; + rc = mares_iconhd_device_read (abstract, 0x0C, serial, sizeof (serial)); + if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the memory."); + return rc; + } + + // Emit a device info event. + dc_event_devinfo_t devinfo; + devinfo.model = device->model; + devinfo.firmware = 0; + devinfo.serial = array_uint32_le (serial); + device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); + + if (device->model == GENIUS) { + return mares_iconhd_device_foreach_object (abstract, callback, userdata); + } else { + return mares_iconhd_device_foreach_raw (abstract, callback, userdata); + } +} diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index c12e8c4..a76ca20 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -27,6 +27,7 @@ #include "context-private.h" #include "parser-private.h" #include "array.h" +#include "checksum.h" #define ISINSTANCE(parser) dc_parser_isinstance((parser), &mares_iconhd_parser_vtable) @@ -34,19 +35,94 @@ #define SMARTAPNEA 0x010010 #define ICONHD 0x14 #define ICONHDNET 0x15 +#define GENIUS 0x1C #define QUADAIR 0x23 #define SMARTAIR 0x24 -#define NGASMIXES 3 -#define NTANKS NGASMIXES +#define NGASMIXES_ICONHD 3 +#define NGASMIXES_GENIUS 5 +#define NGASMIXES NGASMIXES_GENIUS -#define AIR 0 -#define GAUGE 1 -#define NITROX 2 -#define FREEDIVE 3 +#define NTANKS_ICONHD NGASMIXES_ICONHD +#define NTANKS_GENIUS NGASMIXES_GENIUS +#define NTANKS NGASMIXES + +#define ICONHD_AIR 0 +#define ICONHD_GAUGE 1 +#define ICONHD_NITROX 2 +#define ICONHD_FREEDIVE 3 + +#define GENIUS_AIR 0 +#define GENIUS_NITROX_SINGLE 1 +#define GENIUS_NITROX_MULTI 2 +#define GENIUS_TRIMIX 3 +#define GENIUS_GAUGE 4 +#define GENIUS_FREEDIVE 5 + +// Record types and sizes +#define DSTR_TYPE 0x44535452 // Dive start record +#define DSTR_SIZE 58 +#define TISS_TYPE 0x54495353 // Tissue record +#define TISS_SIZE 138 +#define DPRS_TYPE 0x44505253 // Sample record +#define DPRS_SIZE 34 +#define AIRS_TYPE 0x41495253 // Air integration record +#define AIRS_SIZE 16 +#define DEND_TYPE 0x44454E44 // Dive end record +#define DEND_SIZE 162 + +#define GASMIX_OFF 0 +#define GASMIX_READY 1 +#define GASMIX_INUSE 2 +#define GASMIX_IGNRD 3 + +#define WATER_SALT 0 +#define WATER_FRESH 1 +#define WATER_EN13319 2 + +#define ALARM_NONE 0 +#define ALARM_SLOW_DOWN 1 +#define ALARM_FAST_ASCENT 2 +#define ALARM_UNCONTROLLED_ASCENT 3 +#define ALARM_MOD_REACHED 4 +#define ALARM_CNS_DANGER 5 +#define ALARM_CNS_EXTREME 6 +#define ALARM_MISSED_DECO 7 +#define ALARM_DIVE_VIOLATION_DECO 8 +#define ALARM_LOW_BATTERY 9 +#define ALARM_VERY_LOW_BATTERY 10 +#define ALARM_PROBE_LOW_BATTERY 11 +#define ALARM_LOW_TANK_PRESSURE 12 +#define ALARM_TANK_RESERVE_REACHED 13 +#define ALARM_TANK_LOST_LINK 14 +#define ALARM_MAX_DIVE_DEPTH 15 +#define ALARM_RUN_AWAY_DECO 16 +#define ALARM_TANK_HALF_REACHED 17 +#define ALARM_NODECO_2MIN 18 +#define ALARM_NODECO_DECO 19 +#define ALARM_MULTIGAS_ATANKISLOW 20 +#define ALARM_DIVETIME_HALFTIME 21 +#define ALARM_DIVETIME_FULLTIME 22 +#define ALARM_GAS_SWITCHPOINT 23 +#define ALARM_GAS_IGNORED 24 +#define ALARM_GAS_CHANGED 25 +#define ALARM_GAS_NOTCHANGED 26 +#define ALARM_GAS_ADDED 27 typedef struct mares_iconhd_parser_t mares_iconhd_parser_t; +typedef struct mares_iconhd_gasmix_t { + unsigned int oxygen; + unsigned int helium; +} mares_iconhd_gasmix_t; + +typedef struct mares_iconhd_tank_t { + unsigned int volume; + unsigned int workpressure; + unsigned int beginpressure; + unsigned int endpressure; +} mares_iconhd_tank_t; + struct mares_iconhd_parser_t { dc_parser_t base; unsigned int model; @@ -54,14 +130,15 @@ struct mares_iconhd_parser_t { unsigned int cached; unsigned int mode; unsigned int nsamples; - unsigned int footer; unsigned int samplesize; + unsigned int headersize; unsigned int settings; unsigned int interval; unsigned int samplerate; unsigned int ntanks; unsigned int ngasmixes; - unsigned int oxygen[NGASMIXES]; + mares_iconhd_gasmix_t gasmix[NGASMIXES]; + mares_iconhd_tank_t tank[NTANKS]; }; static dc_status_t mares_iconhd_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); @@ -79,17 +156,35 @@ static const dc_parser_vtable_t mares_iconhd_parser_vtable = { NULL /* destroy */ }; +static unsigned int +mares_genius_isvalid (const unsigned char data[], size_t size, unsigned int type) +{ + if (size < 10) { + return 0; + } + + unsigned int head = array_uint32_be(data); + unsigned int tail = array_uint32_be(data + size - 4); + if (head != type || tail != type) { + return 0; + } + + unsigned short crc = array_uint16_le(data + size - 6); + unsigned short ccrc = checksum_crc16_ccitt(data + 4, size - 10, 0x0000); + if (crc != ccrc) { + return 0; + } + + return 1; +} + static dc_status_t -mares_iconhd_parser_cache (mares_iconhd_parser_t *parser) +mares_iconhd_cache (mares_iconhd_parser_t *parser) { dc_parser_t *abstract = (dc_parser_t *) parser; const unsigned char *data = parser->base.data; unsigned int size = parser->base.size; - if (parser->cached) { - return DC_STATUS_SUCCESS; - } - unsigned int header = 0x5C; if (parser->model == ICONHDNET) header = 0x80; @@ -134,7 +229,7 @@ mares_iconhd_parser_cache (mares_iconhd_parser_t *parser) headersize = 0x84; samplesize = 12; } else if (parser->model == SMART) { - if (mode == FREEDIVE) { + if (mode == ICONHD_FREEDIVE) { headersize = 0x2E; samplesize = 6; } else { @@ -160,7 +255,7 @@ mares_iconhd_parser_cache (mares_iconhd_parser_t *parser) unsigned int settings = 0; if (parser->model == SMARTAPNEA) { settings = array_uint16_le (p + 0x1C); - } else if (parser->mode == FREEDIVE) { + } else if (parser->mode == ICONHD_FREEDIVE) { settings = array_uint16_le (p + 0x08); } else { settings = array_uint16_le (p + 0x0C); @@ -195,56 +290,190 @@ mares_iconhd_parser_cache (mares_iconhd_parser_t *parser) // Gas mixes unsigned int ngasmixes = 0; - unsigned int oxygen[NGASMIXES] = {0}; - if (mode == GAUGE || mode == FREEDIVE) { + mares_iconhd_gasmix_t gasmix[NGASMIXES_ICONHD] = {0}; + if (mode == ICONHD_GAUGE || mode == ICONHD_FREEDIVE) { ngasmixes = 0; - } else if (mode == AIR) { - oxygen[0] = 21; + } else if (mode == ICONHD_AIR) { + gasmix[0].oxygen = 21; + gasmix[0].helium = 0; ngasmixes = 1; } else { // Count the number of active gas mixes. The active gas // mixes are always first, so we stop counting as soon // as the first gas marked as disabled is found. ngasmixes = 0; - while (ngasmixes < NGASMIXES) { + while (ngasmixes < NGASMIXES_ICONHD) { if (p[0x10 + ngasmixes * 4 + 1] & 0x80) break; - oxygen[ngasmixes] = p[0x10 + ngasmixes * 4]; + gasmix[ngasmixes].oxygen = p[0x10 + ngasmixes * 4]; + gasmix[ngasmixes].helium = 0; ngasmixes++; } } // Tanks unsigned int ntanks = 0; + mares_iconhd_tank_t tank[NTANKS_ICONHD] = {0}; if (parser->model == ICONHDNET || parser->model == QUADAIR || parser->model == SMARTAIR) { unsigned int tankoffset = (parser->model == ICONHDNET) ? 0x58 : 0x5C; - while (ntanks < NTANKS) { - unsigned int beginpressure = array_uint16_le (p + tankoffset + ntanks * 4 + 0); - unsigned int endpressure = array_uint16_le (p + tankoffset + ntanks * 4 + 2); - if (beginpressure == 0 && (endpressure == 0 || endpressure == 36000)) + while (ntanks < NTANKS_ICONHD) { + tank[ntanks].volume = array_uint16_le (p + tankoffset + 0x0C + ntanks * 8 + 0); + tank[ntanks].workpressure = array_uint16_le (p + tankoffset + 0x0C + ntanks * 8 + 2); + tank[ntanks].beginpressure = array_uint16_le (p + tankoffset + ntanks * 4 + 0); + tank[ntanks].endpressure = array_uint16_le (p + tankoffset + ntanks * 4 + 2); + if (tank[ntanks].beginpressure == 0 && (tank[ntanks].endpressure == 0 || tank[ntanks].endpressure == 36000)) break; ntanks++; } } + // Limit the size to the actual length. + parser->base.size = length; + + // Cache the data for later use. + parser->mode = mode; + parser->nsamples = nsamples; + parser->samplesize = samplesize; + parser->headersize = headersize; + parser->settings = settings; + parser->interval = interval; + parser->samplerate = samplerate; + parser->ntanks = ntanks; + parser->ngasmixes = ngasmixes; + for (unsigned int i = 0; i < ngasmixes; ++i) { + parser->gasmix[i] = gasmix[i]; + } + for (unsigned int i = 0; i < ntanks; ++i) { + parser->tank[i] = tank[i]; + } + parser->cached = 1; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +mares_genius_cache (mares_iconhd_parser_t *parser) +{ + dc_parser_t *abstract = (dc_parser_t *) parser; + const unsigned char *data = parser->base.data; + unsigned int size = parser->base.size; + + if (size < 4) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + // Check the header type and version. + unsigned int type = array_uint16_le (data); + unsigned int major = data[2]; + unsigned int minor = data[3]; + if (type != 1 || major != 0 || minor != 0) { + ERROR (abstract->context, "Unsupported object type (%u) or version (%u.%u).", + type, major, minor); + return DC_STATUS_DATAFORMAT; + } + + // Get the header size. + unsigned int headersize = 0xB8; + if (headersize > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + // Get the number of samples in the profile data. + unsigned int nsamples = array_uint16_le (data + 0x20); + + // Get the dive settings. + unsigned int settings = array_uint32_le (data + 0x0C); + + // Get the dive mode. + unsigned int mode = settings & 0xF; + + // Calculate the total number of bytes for this dive. + unsigned int nbytes = headersize + 4 + DSTR_SIZE + TISS_SIZE + nsamples * DPRS_SIZE + (nsamples / 4) * AIRS_SIZE + DEND_SIZE; + if (nbytes > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + // Gas mixes and tanks. + unsigned int ntanks = 0; + unsigned int ngasmixes = 0; + mares_iconhd_gasmix_t gasmix[NGASMIXES_GENIUS] = {0}; + mares_iconhd_tank_t tank[NTANKS_GENIUS] = {0}; + for (unsigned int i = 0; i < NGASMIXES_GENIUS; i++) { + unsigned int offset = 0x54 + i * 20; + unsigned int gasmixparams = array_uint32_le(data + offset + 0); + unsigned int beginpressure = array_uint16_le(data + offset + 4); + unsigned int endpressure = array_uint16_le(data + offset + 6); + unsigned int volume = array_uint16_le(data + offset + 8); + unsigned int workpressure = array_uint16_le(data + offset + 10); + + unsigned int o2 = (gasmixparams ) & 0x7F; + unsigned int n2 = (gasmixparams >> 7) & 0x7F; + unsigned int he = (gasmixparams >> 14) & 0x7F; + unsigned int state = (gasmixparams >> 21) & 0x03; + unsigned int changed = (gasmixparams >> 23) & 0x01; + + if (o2 + n2 + he != 100) { + WARNING (abstract->context, "Invalid gas mix (%u%% He, %u%% O2, %u%% N2).", he, o2, n2); + } + + // The active gas mixes are always first, so we stop processing + // as soon as the first gas mix marked as disabled is found. + if (state != GASMIX_OFF && ngasmixes == i) { + gasmix[i].oxygen = o2; + gasmix[i].helium = he; + ngasmixes++; + } + + // Assume the active transmitters are always first, so we can + // stop processing as soon as the first inactive transmitter is + // found. + if ((beginpressure != 0 || (endpressure != 0 && endpressure != 36000)) && + (ntanks == i)) { + tank[i].volume = volume; + tank[i].workpressure = workpressure; + tank[i].beginpressure = beginpressure; + tank[i].endpressure = endpressure; + ntanks++; + } + } + // Cache the data for later use. parser->mode = mode; parser->nsamples = nsamples; - parser->footer = length - headersize; - parser->samplesize = samplesize; + parser->samplesize = DPRS_SIZE; + parser->headersize = headersize; parser->settings = settings; - parser->interval = interval; - parser->samplerate = samplerate; + parser->interval = 5; + parser->samplerate = 1; parser->ntanks = ntanks; parser->ngasmixes = ngasmixes; for (unsigned int i = 0; i < ngasmixes; ++i) { - parser->oxygen[i] = oxygen[i]; + parser->gasmix[i] = gasmix[i]; + } + for (unsigned int i = 0; i < ntanks; ++i) { + parser->tank[i] = tank[i]; } parser->cached = 1; return DC_STATUS_SUCCESS; } +static dc_status_t +mares_iconhd_parser_cache (mares_iconhd_parser_t *parser) +{ + if (parser->cached) { + return DC_STATUS_SUCCESS; + } + + if (parser->model == GENIUS) { + return mares_genius_cache (parser); + } else { + return mares_iconhd_cache (parser); + } +} dc_status_t mares_iconhd_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model) @@ -264,17 +493,24 @@ mares_iconhd_parser_create (dc_parser_t **out, dc_context_t *context, unsigned i // Set the default values. parser->model = model; parser->cached = 0; - parser->mode = AIR; + parser->mode = (model == GENIUS) ? GENIUS_AIR : ICONHD_AIR; parser->nsamples = 0; - parser->footer = 0; parser->samplesize = 0; + parser->headersize = 0; parser->settings = 0; parser->interval = 0; parser->samplerate = 0; parser->ntanks = 0; parser->ngasmixes = 0; for (unsigned int i = 0; i < NGASMIXES; ++i) { - parser->oxygen[i] = 0; + parser->gasmix[i].oxygen = 0; + parser->gasmix[i].helium = 0; + } + for (unsigned int i = 0; i < NTANKS; ++i) { + parser->tank[i].volume = 0; + parser->tank[i].workpressure = 0; + parser->tank[i].beginpressure = 0; + parser->tank[i].endpressure = 0; } *out = (dc_parser_t*) parser; @@ -290,17 +526,24 @@ mares_iconhd_parser_set_data (dc_parser_t *abstract, const unsigned char *data, // Reset the cache. parser->cached = 0; - parser->mode = AIR; + parser->mode = (parser->model == GENIUS) ? GENIUS_AIR : ICONHD_AIR; parser->nsamples = 0; - parser->footer = 0; parser->samplesize = 0; + parser->headersize = 0; parser->settings = 0; parser->interval = 0; parser->samplerate = 0; parser->ntanks = 0; parser->ngasmixes = 0; for (unsigned int i = 0; i < NGASMIXES; ++i) { - parser->oxygen[i] = 0; + parser->gasmix[i].oxygen = 0; + parser->gasmix[i].helium = 0; + } + for (unsigned int i = 0; i < NTANKS; ++i) { + parser->tank[i].volume = 0; + parser->tank[i].workpressure = 0; + parser->tank[i].beginpressure = 0; + parser->tank[i].endpressure = 0; } return DC_STATUS_SUCCESS; @@ -317,28 +560,43 @@ mares_iconhd_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime if (rc != DC_STATUS_SUCCESS) return rc; - const unsigned char *p = abstract->data + parser->footer; - if (parser->model == SMART) { - if (parser->mode == FREEDIVE) { - p += 0x20; - } else { - p += 2; + // Pointer to the header data. + const unsigned char *p = abstract->data; + if (parser->model != GENIUS) { + p += abstract->size - parser->headersize; + if (parser->model != SMART && parser->model != SMARTAPNEA && parser->model != SMARTAIR) { + p += 4; } + } + + // Offset to the date/time field. + if (parser->model == GENIUS) { + p += 0x08; } else if (parser->model == SMARTAPNEA) { p += 0x40; - } else if (parser->model == SMARTAIR) { - p += 2; + } else if (parser->mode == ICONHD_FREEDIVE) { + p += 0x20; } else { - p += 6; + p += 2; } if (datetime) { - datetime->hour = array_uint16_le (p + 0); - datetime->minute = array_uint16_le (p + 2); - datetime->second = 0; - datetime->day = array_uint16_le (p + 4); - datetime->month = array_uint16_le (p + 6) + 1; - datetime->year = array_uint16_le (p + 8) + 1900; + if (parser->model == GENIUS) { + unsigned int timestamp = array_uint32_le (p); + datetime->hour = (timestamp ) & 0x1F; + datetime->minute = (timestamp >> 5) & 0x3F; + datetime->second = 0; + datetime->day = (timestamp >> 11) & 0x1F; + datetime->month = (timestamp >> 16) & 0x0F; + datetime->year = (timestamp >> 20) & 0x0FFF; + } else { + datetime->hour = array_uint16_le (p + 0); + datetime->minute = array_uint16_le (p + 2); + datetime->second = 0; + datetime->day = array_uint16_le (p + 4); + datetime->month = array_uint16_le (p + 6) + 1; + datetime->year = array_uint16_le (p + 8) + 1900; + } datetime->timezone = DC_TIMEZONE_NONE; } @@ -356,13 +614,16 @@ mares_iconhd_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi if (rc != DC_STATUS_SUCCESS) return rc; - const unsigned char *p = abstract->data + parser->footer; - if (parser->model != SMART && parser->model != SMARTAPNEA && parser->model != SMARTAIR) { - p += 4; + // Pointer to the header data. + const unsigned char *p = abstract->data; + if (parser->model != GENIUS) { + p += abstract->size - parser->headersize; + if (parser->model != SMART && parser->model != SMARTAPNEA && parser->model != SMARTAIR) { + p += 4; + } } - unsigned int volume = 0, workpressure = 0; - unsigned int tankoffset = 0; + unsigned int metric = parser->model == GENIUS ? p[0x34] : parser->settings & 0x0100; dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_tank_t *tank = (dc_tank_t *) value; @@ -371,9 +632,11 @@ mares_iconhd_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi if (value) { switch (type) { case DC_FIELD_DIVETIME: - if (parser->model == SMARTAPNEA) { + if (parser->model == GENIUS) { + *((unsigned int *) value) = parser->nsamples * parser->interval; + } else if (parser->model == SMARTAPNEA) { *((unsigned int *) value) = array_uint16_le (p + 0x24); - } else if (parser->mode == FREEDIVE) { + } else if (parser->mode == ICONHD_FREEDIVE) { unsigned int divetime = 0; unsigned int offset = 4; for (unsigned int i = 0; i < parser->nsamples; ++i) { @@ -386,9 +649,11 @@ mares_iconhd_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi } break; case DC_FIELD_MAXDEPTH: - if (parser->model == SMARTAPNEA) + if (parser->model == GENIUS) + *((double *) value) = array_uint16_le (p + 0x22) / 10.0; + else if (parser->model == SMARTAPNEA) *((double *) value) = array_uint16_le (p + 0x3A) / 10.0; - else if (parser->mode == FREEDIVE) + else if (parser->mode == ICONHD_FREEDIVE) *((double *) value) = array_uint16_le (p + 0x1A) / 10.0; else *((double *) value) = array_uint16_le (p + 0x00) / 10.0; @@ -397,31 +662,28 @@ mares_iconhd_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi *((unsigned int *) value) = parser->ngasmixes; break; case DC_FIELD_GASMIX: - gasmix->oxygen = parser->oxygen[flags] / 100.0; - gasmix->helium = 0.0; + gasmix->oxygen = parser->gasmix[flags].oxygen / 100.0; + gasmix->helium = parser->gasmix[flags].helium / 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: - tankoffset = (parser->model == ICONHDNET) ? 0x58 : 0x5C; - volume = array_uint16_le (p + tankoffset + 0x0C + flags * 8 + 0); - workpressure = array_uint16_le (p + tankoffset + 0x0C + flags * 8 + 2); - if (parser->settings & 0x0100) { + if (metric) { tank->type = DC_TANKVOLUME_METRIC; - tank->volume = volume; - tank->workpressure = workpressure; + tank->volume = parser->tank[flags].volume; + tank->workpressure = parser->tank[flags].workpressure; } else { - if (workpressure == 0) + if (parser->tank[flags].workpressure == 0) return DC_STATUS_DATAFORMAT; tank->type = DC_TANKVOLUME_IMPERIAL; - tank->volume = volume * CUFT * 1000.0; - tank->volume /= workpressure * PSI / ATM; - tank->workpressure = workpressure * PSI / BAR; + tank->volume = parser->tank[flags].volume * CUFT * 1000.0; + tank->volume /= parser->tank[flags].workpressure * PSI / ATM; + tank->workpressure = parser->tank[flags].workpressure * PSI / BAR; } - tank->beginpressure = array_uint16_le (p + tankoffset + flags * 4 + 0) / 100.0; - tank->endpressure = array_uint16_le (p + tankoffset + flags * 4 + 2) / 100.0; + tank->beginpressure = parser->tank[flags].beginpressure / 100.0; + tank->endpressure = parser->tank[flags].endpressure / 100.0; if (flags < parser->ngasmixes) { tank->gasmix = flags; } else { @@ -429,16 +691,35 @@ mares_iconhd_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi } break; case DC_FIELD_ATMOSPHERIC: - // Pressure (1/8 millibar) - if (parser->model == SMARTAPNEA) + if (parser->model == GENIUS) + *((double *) value) = array_uint16_le (p + 0x3E) / 1000.0; + else if (parser->model == SMARTAPNEA) *((double *) value) = array_uint16_le (p + 0x38) / 1000.0; - else if (parser->mode == FREEDIVE) + else if (parser->mode == ICONHD_FREEDIVE) *((double *) value) = array_uint16_le (p + 0x18) / 1000.0; else *((double *) value) = array_uint16_le (p + 0x22) / 8000.0; break; case DC_FIELD_SALINITY: - if (parser->model == SMARTAPNEA) { + if (parser->model == GENIUS) { + unsigned int salinity = (parser->settings >> 5) & 0x03; + switch (salinity) { + case WATER_FRESH: + water->type = DC_WATER_FRESH; + water->density = 0.0; + break; + case WATER_SALT: + water->type = DC_WATER_SALT; + water->density = 0.0; + break; + case WATER_EN13319: + water->type = DC_WATER_SALT; + water->density = MSW / GRAVITY; + break; + default: + return DC_STATUS_DATAFORMAT; + } + } else if (parser->model == SMARTAPNEA) { unsigned int salinity = parser->settings & 0x003F; if (salinity == 0) { water->type = DC_WATER_FRESH; @@ -456,35 +737,58 @@ mares_iconhd_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi } break; case DC_FIELD_TEMPERATURE_MINIMUM: - if (parser->model == SMARTAPNEA) + if (parser->model == GENIUS) + *((double *) value) = (signed short) array_uint16_le (p + 0x28) / 10.0; + else if (parser->model == SMARTAPNEA) *((double *) value) = (signed short) array_uint16_le (p + 0x3E) / 10.0; - else if (parser->mode == FREEDIVE) + else if (parser->mode == ICONHD_FREEDIVE) *((double *) value) = (signed short) array_uint16_le (p + 0x1C) / 10.0; else *((double *) value) = (signed short) array_uint16_le (p + 0x42) / 10.0; break; case DC_FIELD_TEMPERATURE_MAXIMUM: - if (parser->model == SMARTAPNEA) + if (parser->model == GENIUS) + *((double *) value) = (signed short) array_uint16_le (p + 0x26) / 10.0; + else if (parser->model == SMARTAPNEA) *((double *) value) = (signed short) array_uint16_le (p + 0x3C) / 10.0; - else if (parser->mode == FREEDIVE) + else if (parser->mode == ICONHD_FREEDIVE) *((double *) value) = (signed short) array_uint16_le (p + 0x1E) / 10.0; else *((double *) value) = (signed short) array_uint16_le (p + 0x44) / 10.0; break; case DC_FIELD_DIVEMODE: - switch (parser->mode) { - case AIR: - case NITROX: - *((dc_divemode_t *) value) = DC_DIVEMODE_OC; - break; - case GAUGE: - *((dc_divemode_t *) value) = DC_DIVEMODE_GAUGE; - break; - case FREEDIVE: - *((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE; - break; - default: - return DC_STATUS_DATAFORMAT; + if (parser->model == GENIUS) { + switch (parser->mode) { + case GENIUS_AIR: + case GENIUS_NITROX_SINGLE: + case GENIUS_NITROX_MULTI: + case GENIUS_TRIMIX: + *((dc_divemode_t *) value) = DC_DIVEMODE_OC; + break; + case GENIUS_GAUGE: + *((dc_divemode_t *) value) = DC_DIVEMODE_GAUGE; + break; + case GENIUS_FREEDIVE: + *((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE; + break; + default: + return DC_STATUS_DATAFORMAT; + } + } else { + switch (parser->mode) { + case ICONHD_AIR: + case ICONHD_NITROX: + *((dc_divemode_t *) value) = DC_DIVEMODE_OC; + break; + case ICONHD_GAUGE: + *((dc_divemode_t *) value) = DC_DIVEMODE_GAUGE; + break; + case ICONHD_FREEDIVE: + *((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE; + break; + default: + return DC_STATUS_DATAFORMAT; + } } break; default: @@ -519,8 +823,43 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t // Previous gas mix - initialize with impossible value unsigned int gasmix_previous = 0xFFFFFFFF; - unsigned int time = 0; + unsigned int isairintegrated = (parser->model == ICONHDNET || parser->model == QUADAIR || parser->model == SMARTAIR || parser->model == GENIUS); + unsigned int offset = 4; + unsigned int marker = 0; + if (parser->model == GENIUS) { + // Skip the dive header. + data += parser->headersize; + + // Check the profile type and version. + unsigned int type = array_uint16_le (data); + unsigned int major = data[2]; + unsigned int minor = data[3]; + if (type != 0 || major != 2 || minor != 0) { + ERROR (abstract->context, "Unsupported object type (%u) or version (%u.%u).", + type, major, minor); + return DC_STATUS_DATAFORMAT; + } + + // Skip the DSTR record. + if (!mares_genius_isvalid (data + offset, DSTR_SIZE, DSTR_TYPE)) { + ERROR (abstract->context, "Invalid DSTR record."); + return DC_STATUS_DATAFORMAT; + } + offset += DSTR_SIZE; + + // Skip the TISS record. + if (!mares_genius_isvalid (data + offset, TISS_SIZE, TISS_TYPE)) { + ERROR (abstract->context, "Invalid TISS record."); + return DC_STATUS_DATAFORMAT; + } + offset += TISS_SIZE; + + // Size of the record type marker. + marker = 4; + } + + unsigned int time = 0; unsigned int nsamples = 0; while (nsamples < parser->nsamples) { dc_sample_value_t sample = {0}; @@ -555,7 +894,7 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t offset += 2 * parser->samplerate; } - } else if (parser->mode == FREEDIVE) { + } else if (parser->model != GENIUS && parser->mode == ICONHD_FREEDIVE) { unsigned int maxdepth = array_uint16_le (data + offset + 0); unsigned int divetime = array_uint16_le (data + offset + 2); unsigned int surftime = array_uint16_le (data + offset + 4); @@ -581,23 +920,39 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t offset += parser->samplesize; nsamples++; } else { + unsigned int depth = 0, temperature = 0; + unsigned int gasmix = 0, misc = 0, alarms = 0; + if (parser->model == GENIUS) { + if (!mares_genius_isvalid (data + offset, DPRS_SIZE, DPRS_TYPE)) { + ERROR (abstract->context, "Invalid DPRS record."); + return DC_STATUS_DATAFORMAT; + } + + depth = array_uint16_le (data + offset + marker + 0); + temperature = array_uint16_le (data + offset + marker + 4); + alarms = array_uint32_le (data + offset + marker + 0x0C); + misc = array_uint32_le (data + offset + marker + 0x14); + gasmix = (misc >> 6) & 0xF; + } else { + depth = array_uint16_le (data + offset + 0); + temperature = array_uint16_le (data + offset + 2) & 0x0FFF; + gasmix = (data[offset + 3] & 0xF0) >> 4; + } + // Time (seconds). time += parser->interval; sample.time = time; if (callback) callback (DC_SAMPLE_TIME, sample, userdata); // Depth (1/10 m). - unsigned int depth = array_uint16_le (data + offset + 0); sample.depth = depth / 10.0; if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); // Temperature (1/10 °C). - unsigned int temperature = array_uint16_le (data + offset + 2) & 0x0FFF; sample.temperature = temperature / 10.0; if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); // Current gas mix - unsigned int gasmix = (data[offset + 3] & 0xF0) >> 4; if (parser->ngasmixes > 0) { if (gasmix >= parser->ngasmixes) { ERROR (abstract->context, "Invalid gas mix index."); @@ -610,14 +965,61 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t } } + if (parser->model == GENIUS) { + // Deco stop / NDL. + unsigned int decostop = (misc >> 18) & 0x01; + unsigned int decodepth = (misc >> 19) & 0x7F; + if (decostop) { + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.depth = decodepth; + } else { + sample.deco.type = DC_DECO_NDL; + sample.deco.depth = 0.0; + } + sample.deco.time = array_uint16_le (data + offset + marker + 0x0A) * 60; + if (callback) callback (DC_SAMPLE_DECO, sample, userdata); + + // Alarms + for (unsigned int v = alarms, i = 0; v; v >>= 1, ++i) { + if ((v & 1) == 0) { + continue; + } + + switch (i) { + case ALARM_FAST_ASCENT: + case ALARM_UNCONTROLLED_ASCENT: + sample.event.type = SAMPLE_EVENT_ASCENT; + break; + case ALARM_MISSED_DECO: + case ALARM_DIVE_VIOLATION_DECO: + sample.event.type = SAMPLE_EVENT_CEILING; + break; + default: + sample.event.type = SAMPLE_EVENT_NONE; + break; + } + + if (sample.event.type != SAMPLE_EVENT_NONE) { + sample.event.time = 0; + sample.event.flags = 0; + sample.event.value = 0; + if (callback) callback (DC_SAMPLE_EVENT, sample, userdata); + } + } + } + offset += parser->samplesize; nsamples++; // Some extra data. - if ((parser->model == ICONHDNET || parser->model == QUADAIR || parser->model == SMARTAIR) && - (nsamples % 4) == 0) { + if (isairintegrated && (nsamples % 4) == 0) { + if (parser->model == GENIUS && !mares_genius_isvalid (data + offset, AIRS_SIZE, AIRS_TYPE)) { + ERROR (abstract->context, "Invalid AIRS record."); + return DC_STATUS_DATAFORMAT; + } + // Pressure (1/100 bar). - unsigned int pressure = array_uint16_le(data + offset); + unsigned int pressure = array_uint16_le(data + offset + marker + 0); if (gasmix < parser->ntanks) { sample.pressure.tank = gasmix; sample.pressure.value = pressure / 100.0; @@ -626,10 +1028,19 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t WARNING (abstract->context, "Invalid tank with non-zero pressure."); } - offset += 8; + offset += (parser->model == GENIUS) ? AIRS_SIZE : 8; } } } + if (parser->model == GENIUS) { + // Skip the DEND record. + if (!mares_genius_isvalid (data + offset, DEND_SIZE, DEND_TYPE)) { + ERROR (abstract->context, "Invalid DEND record."); + return DC_STATUS_DATAFORMAT; + } + offset += DEND_SIZE; + } + return DC_STATUS_SUCCESS; } diff --git a/src/oceanic_atom2_parser.c b/src/oceanic_atom2_parser.c index 6c0009a..de7f303 100644 --- a/src/oceanic_atom2_parser.c +++ b/src/oceanic_atom2_parser.c @@ -736,7 +736,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == GEO || parser->model == GEO20 || parser->model == MANTA || parser->model == I300 || parser->model == I200 || parser->model == I100 || - parser->model == I300C || TALIS) { + parser->model == I300C || parser->model == TALIS) { have_pressure = 0; }