Add support for the Sporasub SP2

The Sporasub SP2 uses a very simple communication protocol and memory
layout, but with some unusual aspects:

Dives are artifically limited to a maximum of 6000 samples.

Unlike all other dive computers, the dives are not stored in some kind
of ringbuffer structure. Once the memory is full, no new dives can be
recorded. The existing dives need to be erased first, and the dive
computer will start recording again at te start of the memory area. The
Sporasub application has an "Auto-clear watch memory after data
transfer" feature for this purpose.

I didn't implement a more efficient download algorithm because
downloading a full memory dumps takes less than 10 seconds.
This commit is contained in:
Jef Driesen 2021-03-10 21:38:45 +01:00
parent 1418766a1a
commit 6ef72ab420
10 changed files with 758 additions and 0 deletions

View File

@ -93,6 +93,7 @@ static const backend_table_t g_backends[] = {
{"divecomputereu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0},
{"mclean", DC_FAMILY_MCLEAN_EXTREME, 0},
{"lynx", DC_FAMILY_LIQUIVISION_LYNX, 0},
{"sp2", DC_FAMILY_SPORASUB_SP2, 0},
};
static const transport_table_t g_transports[] = {

View File

@ -108,6 +108,8 @@ typedef enum dc_family_t {
DC_FAMILY_MCLEAN_EXTREME = (16 << 16),
/* Liquivision */
DC_FAMILY_LIQUIVISION_LYNX = (17 << 16),
/* Sporasub */
DC_FAMILY_SPORASUB_SP2 = (18 << 16),
} dc_family_t;
#ifdef __cplusplus

View File

@ -458,6 +458,14 @@
RelativePath="..\src\socket.c"
>
</File>
<File
RelativePath="..\src\sporasub_sp2.c"
>
</File>
<File
RelativePath="..\src\sporasub_sp2_parser.c"
>
</File>
<File
RelativePath="..\src\suunto_common.c"
>
@ -824,6 +832,10 @@
RelativePath="..\src\socket.h"
>
</File>
<File
RelativePath="..\src\sporasub_sp2.h"
>
</File>
<File
RelativePath="..\src\suunto_common.h"
>

View File

@ -74,6 +74,7 @@ libdivecomputer_la_SOURCES = \
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 \
sporasub_sp2.h sporasub_sp2.c sporasub_sp2_parser.c \
socket.h socket.c \
irda.c \
usb.c \

View File

@ -415,6 +415,8 @@ static const dc_descriptor_t g_descriptors[] = {
{"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},
/* Sporasub */
{"Sporasub", "SP2", DC_FAMILY_SPORASUB_SP2, 0, DC_TRANSPORT_SERIAL, NULL},
};
static int

View File

@ -59,6 +59,7 @@
#include "tecdiving_divecomputereu.h"
#include "mclean_extreme.h"
#include "liquivision_lynx.h"
#include "sporasub_sp2.h"
#include "device-private.h"
#include "context-private.h"
@ -219,6 +220,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr
case DC_FAMILY_LIQUIVISION_LYNX:
rc = liquivision_lynx_device_open (&device, context, iostream);
break;
case DC_FAMILY_SPORASUB_SP2:
rc = sporasub_sp2_device_open (&device, context, iostream);
break;
default:
return DC_STATUS_INVALIDARGS;
}

View File

@ -59,6 +59,7 @@
#include "tecdiving_divecomputereu.h"
#include "mclean_extreme.h"
#include "liquivision_lynx.h"
#include "sporasub_sp2.h"
#include "context-private.h"
#include "parser-private.h"
@ -180,6 +181,9 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa
case DC_FAMILY_LIQUIVISION_LYNX:
rc = liquivision_lynx_parser_create (&parser, context, model);
break;
case DC_FAMILY_SPORASUB_SP2:
rc = sporasub_sp2_parser_create (&parser, context);
break;
default:
return DC_STATUS_INVALIDARGS;
}

489
src/sporasub_sp2.c Normal file
View File

@ -0,0 +1,489 @@
/*
* libdivecomputer
*
* Copyright (C) 2021 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 <string.h> // memcpy, memcmp
#include <stdlib.h> // malloc, free
#include "sporasub_sp2.h"
#include "context-private.h"
#include "device-private.h"
#include "checksum.h"
#include "array.h"
#define ISINSTANCE(device) dc_device_isinstance((device), &sporasub_sp2_device_vtable)
#define SZ_MEMORY 0x10000
#define RB_PROFILE_BEGIN 0x0060
#define RB_PROFILE_END SZ_MEMORY
#define MAXRETRIES 4
#define MAXPACKET 256
#define HEADER_HI 0xA0
#define HEADER_LO 0xA2
#define TRAILER_HI 0xB0
#define TRAILER_LO 0xB3
#define CMD_VERSION 0x10
#define CMD_READ 0x12
#define CMD_TIMESYNC 0x39
#define SZ_VERSION 23
#define SZ_READ 128
#define SZ_HEADER 32
#define SZ_SAMPLE 4
typedef struct sporasub_sp2_device_t {
dc_device_t base;
dc_iostream_t *iostream;
unsigned char version[SZ_VERSION];
unsigned char fingerprint[6];
} sporasub_sp2_device_t;
static dc_status_t sporasub_sp2_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size);
static dc_status_t sporasub_sp2_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size);
static dc_status_t sporasub_sp2_device_dump (dc_device_t *abstract, dc_buffer_t *buffer);
static dc_status_t sporasub_sp2_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata);
static dc_status_t sporasub_sp2_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime);
static const dc_device_vtable_t sporasub_sp2_device_vtable = {
sizeof(sporasub_sp2_device_t),
DC_FAMILY_SPORASUB_SP2,
sporasub_sp2_device_set_fingerprint, /* set_fingerprint */
sporasub_sp2_device_read, /* read */
NULL, /* write */
sporasub_sp2_device_dump, /* dump */
sporasub_sp2_device_foreach, /* foreach */
sporasub_sp2_device_timesync, /* timesync */
NULL /* close */
};
static unsigned int
iceil (unsigned int x, unsigned int n)
{
// Round up to next higher multiple.
return ((x + n - 1) / n) * n;
}
static dc_status_t
sporasub_sp2_send (sporasub_sp2_device_t *device, unsigned char command, 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;
}
unsigned int len = size + 1;
unsigned int csum = checksum_add_uint16 (data, size, command);
unsigned char packet[MAXPACKET + 9] = {0};
packet[0] = HEADER_HI;
packet[1] = HEADER_LO;
packet[2] = (len >> 8) & 0xFF;
packet[3] = (len ) & 0xFF;
packet[4] = command;
if (size) {
memcpy(packet + 5, data, size);
}
packet[size + 5] = (csum >> 8) & 0xFF;
packet[size + 6] = (csum ) & 0xFF;
packet[size + 7] = TRAILER_HI;
packet[size + 8] = TRAILER_LO;
// Send the command to the device.
status = dc_iostream_write (device->iostream, packet, size + 9, NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to send the command.");
return status;
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
sporasub_sp2_receive (sporasub_sp2_device_t *device, unsigned char command, 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;
}
// Receive the answer of the device.
unsigned char packet[MAXPACKET + 9] = {0};
status = dc_iostream_read (device->iostream, packet, size + 9, NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to receive the answer.");
return status;
}
// Verify the header and trailer of the packet.
if (packet[0] != HEADER_HI || packet[1] != HEADER_LO ||
packet[size + 7] != TRAILER_HI || packet[size + 8] != TRAILER_LO) {
ERROR (abstract->context, "Unexpected answer header/trailer byte.");
return DC_STATUS_PROTOCOL;
}
// Verify the packet length.
unsigned int len = array_uint16_be (packet + 2);
if (len != size + 1) {
ERROR (abstract->context, "Unexpected packet length.");
return DC_STATUS_PROTOCOL;
}
// Verify the command byte.
if (packet[4] != command) {
ERROR (abstract->context, "Unexpected answer header/trailer byte.");
return DC_STATUS_PROTOCOL;
}
// Verify the checksum of the packet.
unsigned short crc = array_uint16_be (packet + size + 5);
unsigned short ccrc = checksum_add_uint16 (packet + 4, size + 1, 0);
if (crc != ccrc) {
ERROR (abstract->context, "Unexpected answer checksum.");
return DC_STATUS_PROTOCOL;
}
if (size) {
memcpy (data, packet + 5, size);
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
sporasub_sp2_packet (sporasub_sp2_device_t *device, unsigned char cmd, 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;
// Send the command to the device.
status = sporasub_sp2_send (device, cmd, command, csize);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to send the command.");
return status;
}
// Receive the answer of the device.
status = sporasub_sp2_receive (device, cmd + 1, 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
sporasub_sp2_transfer (sporasub_sp2_device_t *device, unsigned char cmd, 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 = sporasub_sp2_packet (device, cmd, command, csize, answer, asize)) != DC_STATUS_SUCCESS) {
// Automatically discard a corrupted packet,
// and request a new one.
if (rc != DC_STATUS_PROTOCOL && rc != DC_STATUS_TIMEOUT)
return rc;
// Abort if the maximum number of retries is reached.
if (nretries++ >= MAXRETRIES)
return rc;
// Discard any garbage bytes.
dc_iostream_sleep (device->iostream, 100);
dc_iostream_purge (device->iostream, DC_DIRECTION_INPUT);
}
return rc;
}
dc_status_t
sporasub_sp2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream)
{
dc_status_t status = DC_STATUS_SUCCESS;
sporasub_sp2_device_t *device = NULL;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
// Allocate memory.
device = (sporasub_sp2_device_t *) dc_device_allocate (context, &sporasub_sp2_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 (460800 8N1).
status = dc_iostream_configure (device->iostream, 460800, 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 (1000 ms).
status = dc_iostream_set_timeout (device->iostream, 1000);
if (status != DC_STATUS_SUCCESS) {
ERROR (context, "Failed to set the timeout.");
goto error_free;
}
// Clear the RTS line.
status = dc_iostream_set_rts (device->iostream, 0);
if (status != DC_STATUS_SUCCESS) {
ERROR (context, "Failed to clear the RTS line.");
goto error_free;
}
// Set the DTR line.
status = dc_iostream_set_dtr (device->iostream, 1);
if (status != DC_STATUS_SUCCESS) {
ERROR (context, "Failed to set the DTR line.");
goto error_free;
}
dc_iostream_sleep (device->iostream, 100);
dc_iostream_purge (device->iostream, DC_DIRECTION_ALL);
// Read the version packet.
status = sporasub_sp2_packet(device, CMD_VERSION, NULL, 0, device->version, sizeof(device->version));
if (status != DC_STATUS_SUCCESS) {
ERROR (context, "Failed to read the version packet.");
goto error_free;
}
*out = (dc_device_t *) device;
return DC_STATUS_SUCCESS;
error_free:
dc_device_deallocate ((dc_device_t *) device);
return status;
}
static dc_status_t
sporasub_sp2_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size)
{
sporasub_sp2_device_t *device = (sporasub_sp2_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
sporasub_sp2_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size)
{
dc_status_t status = DC_STATUS_SUCCESS;
sporasub_sp2_device_t *device = (sporasub_sp2_device_t *) abstract;
unsigned int nbytes = 0;
while (nbytes < size) {
// Calculate the packet size.
unsigned int len = size - nbytes;
if (len > SZ_READ)
len = SZ_READ;
// Build the raw command.
unsigned char command[] = {
(address ) & 0xFF,
(address >> 8) & 0xFF,
len};
// Send the command and receive the answer.
status = sporasub_sp2_transfer (device, CMD_READ, command, sizeof(command), data + nbytes, len);
if (status != DC_STATUS_SUCCESS)
return status;
nbytes += len;
address += len;
data += len;
}
return status;
}
static dc_status_t
sporasub_sp2_device_dump (dc_device_t *abstract, dc_buffer_t *buffer)
{
// Allocate the required amount of memory.
if (!dc_buffer_resize (buffer, SZ_MEMORY)) {
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), SZ_READ);
}
static dc_status_t
sporasub_sp2_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
{
dc_status_t status = DC_STATUS_SUCCESS;
sporasub_sp2_device_t *device = (sporasub_sp2_device_t *) abstract;
// Emit a device info event.
dc_event_devinfo_t devinfo;
devinfo.model = 0;
devinfo.firmware = 0;
devinfo.serial = array_uint16_be (device->version + 1);
device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo);
// Emit a vendor event.
dc_event_vendor_t vendor;
vendor.data = device->version;
vendor.size = sizeof (device->version);
device_event_emit (abstract, DC_EVENT_VENDOR, &vendor);
dc_buffer_t *buffer = dc_buffer_new (SZ_MEMORY);
if (buffer == NULL) {
status = DC_STATUS_NOMEMORY;
goto error_exit;
}
status = sporasub_sp2_device_dump (abstract, buffer);
if (status != DC_STATUS_SUCCESS) {
goto error_free_buffer;
}
unsigned char *data = dc_buffer_get_data (buffer);
// Get the number of dives.
unsigned int ndives = array_uint16_le (data + 0x02);
// Get the profile pointer.
unsigned int eop = array_uint16_le (data + 0x04);
if (eop < RB_PROFILE_BEGIN || eop > RB_PROFILE_END) {
ERROR (abstract->context, "Invalid profile pointer (0x%04x).", eop);
status = DC_STATUS_DATAFORMAT;
goto error_free_buffer;
}
unsigned short *logbook = (unsigned short *) malloc(ndives * sizeof (unsigned short));
if (logbook == NULL) {
ERROR (abstract->context, "Out of memory.");
status = DC_STATUS_NOMEMORY;
goto error_free_buffer;
}
// Find all dives.
unsigned int count = 0;
unsigned int address = RB_PROFILE_BEGIN;
while (address + SZ_HEADER <= RB_PROFILE_END && count < ndives) {
if (address == eop) {
WARNING (abstract->context, "Reached end of profile pointer.");
break;
}
// Get the dive length.
unsigned int nsamples = array_uint16_le (data + address);
unsigned int length = SZ_HEADER + nsamples * SZ_SAMPLE;
if (address + length > RB_PROFILE_END) {
WARNING (abstract->context, "Reached end of memory.");
break;
}
// Store the address.
logbook[count] = address;
count++;
// The start of the next dive is always aligned to 32 bytes.
address += iceil (length, SZ_HEADER);
}
// Process the dives in reverse order (newest first).
for (unsigned int i = 0; i < count; ++i) {
unsigned int idx = count - 1 - i;
unsigned int offset = logbook[idx];
// Get the dive length.
unsigned int nsamples = array_uint16_le (data + offset);
unsigned int length = SZ_HEADER + nsamples * SZ_SAMPLE;
// Check the fingerprint data.
if (memcmp (data + offset + 2, device->fingerprint, sizeof (device->fingerprint)) == 0)
break;
if (callback && !callback (data + offset, length, data + offset + 2, sizeof (device->fingerprint), userdata)) {
break;
}
}
free (logbook);
error_free_buffer:
dc_buffer_free (buffer);
error_exit:
return status;
}
static dc_status_t
sporasub_sp2_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime)
{
dc_status_t status = DC_STATUS_SUCCESS;
sporasub_sp2_device_t *device = (sporasub_sp2_device_t *) abstract;
if (datetime == NULL || datetime->year < 2000) {
ERROR (abstract->context, "Invalid parameter specified.");
return DC_STATUS_INVALIDARGS;
}
// Build the raw command.
unsigned char command[] = {
datetime->year - 2000,
datetime->month,
datetime->day,
datetime->hour,
datetime->minute,
datetime->second};
// Send the command and receive the answer.
unsigned char answer[1] = {0};
status = sporasub_sp2_transfer (device, CMD_TIMESYNC, command, sizeof(command), answer, sizeof(answer));
if (status != DC_STATUS_SUCCESS)
return status;
// Verify the response code.
if (answer[0] != 0) {
ERROR (abstract->context, "Invalid response code 0x%02x returned.", answer[0]);
return DC_STATUS_PROTOCOL;
}
return DC_STATUS_SUCCESS;
}

43
src/sporasub_sp2.h Normal file
View File

@ -0,0 +1,43 @@
/*
* libdivecomputer
*
* Copyright (C) 2021 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 SPORASUB_SP2_H
#define SPORASUB_SP2_H
#include <libdivecomputer/context.h>
#include <libdivecomputer/iostream.h>
#include <libdivecomputer/device.h>
#include <libdivecomputer/parser.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
dc_status_t
sporasub_sp2_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream);
dc_status_t
sporasub_sp2_parser_create (dc_parser_t **parser, dc_context_t *context);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* SPORASUB_SP2_H */

200
src/sporasub_sp2_parser.c Normal file
View File

@ -0,0 +1,200 @@
/*
* libdivecomputer
*
* Copyright (C) 2021 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 <stdlib.h>
#include "sporasub_sp2.h"
#include "context-private.h"
#include "parser-private.h"
#include "array.h"
#define ISINSTANCE(parser) dc_device_isinstance((parser), &sporasub_sp2_parser_vtable)
#define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array))
#define SZ_HEADER 0x20
#define SZ_SAMPLE 0x04
typedef struct sporasub_sp2_parser_t sporasub_sp2_parser_t;
struct sporasub_sp2_parser_t {
dc_parser_t base;
};
static dc_status_t sporasub_sp2_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size);
static dc_status_t sporasub_sp2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime);
static dc_status_t sporasub_sp2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value);
static dc_status_t sporasub_sp2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata);
static const dc_parser_vtable_t sporasub_sp2_parser_vtable = {
sizeof(sporasub_sp2_parser_t),
DC_FAMILY_SPORASUB_SP2,
sporasub_sp2_parser_set_data, /* set_data */
sporasub_sp2_parser_get_datetime, /* datetime */
sporasub_sp2_parser_get_field, /* fields */
sporasub_sp2_parser_samples_foreach, /* samples_foreach */
NULL /* destroy */
};
dc_status_t
sporasub_sp2_parser_create (dc_parser_t **out, dc_context_t *context)
{
sporasub_sp2_parser_t *parser = NULL;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
// Allocate memory.
parser = (sporasub_sp2_parser_t *) dc_parser_allocate (context, &sporasub_sp2_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
sporasub_sp2_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size)
{
return DC_STATUS_SUCCESS;
}
static dc_status_t
sporasub_sp2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime)
{
const unsigned char *data = abstract->data;
unsigned int size = abstract->size;
if (size < SZ_HEADER)
return DC_STATUS_DATAFORMAT;
if (datetime) {
datetime->year = data[4] + 2000;
datetime->month = data[3];
datetime->day = data[2];
datetime->hour = data[7];
datetime->minute = data[6];
datetime->second = data[5];
datetime->timezone = DC_TIMEZONE_NONE;
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
sporasub_sp2_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 < SZ_HEADER)
return DC_STATUS_DATAFORMAT;
if (value) {
switch (type) {
case DC_FIELD_DIVETIME:
*((unsigned int *) value) = data[0x08] + data[0x09] * 60;
break;
case DC_FIELD_MAXDEPTH:
*((double *) value) = array_uint16_le (data + 0x14) / 100.0;
break;
case DC_FIELD_DIVEMODE:
*((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE;
break;
case DC_FIELD_TEMPERATURE_MINIMUM:
*((double *) value) = array_uint16_le (data + 0x18) / 10.0;
break;
case DC_FIELD_TEMPERATURE_MAXIMUM:
*((double *) value) = array_uint16_le (data + 0x16) / 10.0;
break;
default:
return DC_STATUS_UNSUPPORTED;
}
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
sporasub_sp2_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 < SZ_HEADER)
return DC_STATUS_DATAFORMAT;
unsigned int nsamples = array_uint16_le(data);
// Get the sample interval.
unsigned int interval_idx = data[0x1A];
const unsigned int intervals[] = {1, 2, 5, 10};
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];
unsigned int time = 0;
unsigned int count = 0;
unsigned int offset = SZ_HEADER;
while (offset + SZ_SAMPLE <= size && count < nsamples) {
dc_sample_value_t sample = {0};
unsigned int value = array_uint32_le (data + offset);
unsigned int heartrate = (value & 0xFF000000) >> 24;
unsigned int temperature = (value & 0x00FFC000) >> 14;
unsigned int unknown = (value & 0x00003000) >> 12;
unsigned int depth = (value & 0x00000FFF) >> 0;
// Time (seconds)
time += interval;
sample.time = time;
if (callback) callback (DC_SAMPLE_TIME, sample, userdata);
// Depth (1/100 m)
sample.depth = depth / 100.0;
if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
// Temperature (1/10 °C)
sample.temperature = temperature / 10.0 - 20.0;
if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
// Heartrate
if (heartrate) {
sample.heartbeat = heartrate;
if (callback) callback (DC_SAMPLE_HEARTBEAT, sample, userdata);
}
offset += SZ_SAMPLE;
count++;
}
return DC_STATUS_SUCCESS;
}