Apparantly there are also two different type of serial numbers present, and their interpretation depends on the application. The Windows Dive Organizer application shows both a serial number (byte offset 0x04) and a warranty number (byte offset 0x0C). However, the Mac OS X Divers' Diary application shows the number at byte offset 0x0C as the serial number. Very confusing. For now, we just stick to the number at byte offset 0x0C, because that's the number that is shown by the device itself.
472 lines
13 KiB
C
472 lines
13 KiB
C
/*
|
|
* libdivecomputer
|
|
*
|
|
* Copyright (C) 2010 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 <libdivecomputer/mares_iconhd.h>
|
|
|
|
#include "context-private.h"
|
|
#include "device-private.h"
|
|
#include "serial.h"
|
|
#include "array.h"
|
|
|
|
#define EXITCODE(rc) \
|
|
( \
|
|
rc == -1 ? DC_STATUS_IO : DC_STATUS_TIMEOUT \
|
|
)
|
|
|
|
#if defined(_WIN32) || defined(__APPLE__)
|
|
#define BAUDRATE 256000
|
|
#else
|
|
#define BAUDRATE 230400
|
|
#endif
|
|
|
|
#define ICONHD 0x14
|
|
#define ICONHDNET 0x15
|
|
|
|
#define ACK 0xAA
|
|
#define EOF 0xEA
|
|
|
|
#define RB_PROFILE_BEGIN 0xA000
|
|
#define RB_PROFILE_END MARES_ICONHD_MEMORY_SIZE
|
|
|
|
typedef struct mares_iconhd_device_t {
|
|
dc_device_t base;
|
|
serial_t *port;
|
|
unsigned char fingerprint[10];
|
|
unsigned char version[140];
|
|
} mares_iconhd_device_t;
|
|
|
|
static dc_status_t mares_iconhd_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size);
|
|
static dc_status_t mares_iconhd_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size);
|
|
static dc_status_t mares_iconhd_device_dump (dc_device_t *abstract, dc_buffer_t *buffer);
|
|
static dc_status_t mares_iconhd_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata);
|
|
static dc_status_t mares_iconhd_device_close (dc_device_t *abstract);
|
|
|
|
static const device_backend_t mares_iconhd_device_backend = {
|
|
DC_FAMILY_MARES_ICONHD,
|
|
mares_iconhd_device_set_fingerprint, /* set_fingerprint */
|
|
NULL, /* version */
|
|
mares_iconhd_device_read, /* read */
|
|
NULL, /* write */
|
|
mares_iconhd_device_dump, /* dump */
|
|
mares_iconhd_device_foreach, /* foreach */
|
|
mares_iconhd_device_close /* close */
|
|
};
|
|
|
|
static int
|
|
device_is_mares_iconhd (dc_device_t *abstract)
|
|
{
|
|
if (abstract == NULL)
|
|
return 0;
|
|
|
|
return abstract->backend == &mares_iconhd_device_backend;
|
|
}
|
|
|
|
|
|
static unsigned int
|
|
mares_iconhd_get_model (mares_iconhd_device_t *device, unsigned int model)
|
|
{
|
|
dc_context_t *context = (device ? ((dc_device_t *) device)->context : NULL);
|
|
|
|
// Try to correct an invalid model code using the version packet.
|
|
if (model == 0xFF) {
|
|
WARNING (context, "Invalid model code detected!");
|
|
const unsigned char iconhdnet[] = "Icon AIR";
|
|
if (device && memcmp (device->version + 0x46, iconhdnet, sizeof (iconhdnet) - 1) == 0)
|
|
model = ICONHDNET;
|
|
}
|
|
|
|
return model;
|
|
}
|
|
|
|
static dc_status_t
|
|
mares_iconhd_transfer (mares_iconhd_device_t *device,
|
|
const unsigned char command[], unsigned int csize,
|
|
unsigned char answer[], unsigned int asize,
|
|
unsigned int events)
|
|
{
|
|
dc_device_t *abstract = (dc_device_t *) device;
|
|
|
|
// Enable progress notifications.
|
|
dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
|
|
if (events) {
|
|
progress.maximum = asize;
|
|
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
|
|
}
|
|
|
|
// Send the command to the dive computer.
|
|
int n = serial_write (device->port, command, csize);
|
|
if (n != csize) {
|
|
ERROR (abstract->context, "Failed to send the command.");
|
|
return EXITCODE (n);
|
|
}
|
|
|
|
// Receive the header byte.
|
|
unsigned char header[1] = {0};
|
|
n = serial_read (device->port, header, sizeof (header));
|
|
if (n != sizeof (header)) {
|
|
ERROR (abstract->context, "Failed to receive the answer.");
|
|
return EXITCODE (n);
|
|
}
|
|
|
|
// Verify the header byte.
|
|
if (header[0] != ACK) {
|
|
ERROR (abstract->context, "Unexpected answer byte.");
|
|
return DC_STATUS_PROTOCOL;
|
|
}
|
|
|
|
unsigned int nbytes = 0;
|
|
while (nbytes < asize) {
|
|
// Set the minimum packet size.
|
|
unsigned int len = 1024;
|
|
|
|
// Increase the packet size if more data is immediately available.
|
|
int available = serial_get_received (device->port);
|
|
if (available > len)
|
|
len = available;
|
|
|
|
// Limit the packet size to the total size.
|
|
if (nbytes + len > asize)
|
|
len = asize - nbytes;
|
|
|
|
// Read the packet.
|
|
n = serial_read (device->port, answer + nbytes, len);
|
|
if (n != len) {
|
|
ERROR (abstract->context, "Failed to receive the answer.");
|
|
return EXITCODE (n);
|
|
}
|
|
|
|
// Update and emit a progress event.
|
|
if (events) {
|
|
progress.current += len;
|
|
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
|
|
}
|
|
|
|
nbytes += len;
|
|
}
|
|
|
|
// Receive the trailer byte.
|
|
unsigned char trailer[1] = {0};
|
|
n = serial_read (device->port, trailer, sizeof (trailer));
|
|
if (n != sizeof (trailer)) {
|
|
ERROR (abstract->context, "Failed to receive the answer.");
|
|
return EXITCODE (n);
|
|
}
|
|
|
|
// Verify the trailer byte.
|
|
if (trailer[0] != EOF) {
|
|
ERROR (abstract->context, "Unexpected answer byte.");
|
|
return DC_STATUS_PROTOCOL;
|
|
}
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
static dc_status_t
|
|
mares_iconhd_version (mares_iconhd_device_t *device, unsigned char data[], unsigned int size)
|
|
{
|
|
unsigned char command[] = {0xC2, 0x67};
|
|
return mares_iconhd_transfer (device, command, sizeof (command), data, size, 0);
|
|
}
|
|
|
|
|
|
static dc_status_t
|
|
mares_iconhd_read (mares_iconhd_device_t *device, unsigned int address, unsigned char data[], unsigned int size, int events)
|
|
{
|
|
unsigned char command[] = {0xE7, 0x42,
|
|
(address ) & 0xFF,
|
|
(address >> 8) & 0xFF,
|
|
(address >> 16) & 0xFF,
|
|
(address >> 24) & 0xFF,
|
|
(size ) & 0xFF,
|
|
(size >> 8) & 0xFF,
|
|
(size >> 16) & 0xFF,
|
|
(size >> 24) & 0xFF};
|
|
return mares_iconhd_transfer (device, command, sizeof (command), data, size, events);
|
|
}
|
|
|
|
|
|
dc_status_t
|
|
mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, const char *name)
|
|
{
|
|
if (out == NULL)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
// Allocate memory.
|
|
mares_iconhd_device_t *device = (mares_iconhd_device_t *) malloc (sizeof (mares_iconhd_device_t));
|
|
if (device == NULL) {
|
|
ERROR (context, "Failed to allocate memory.");
|
|
return DC_STATUS_NOMEMORY;
|
|
}
|
|
|
|
// Initialize the base class.
|
|
device_init (&device->base, context, &mares_iconhd_device_backend);
|
|
|
|
// Set the default values.
|
|
device->port = NULL;
|
|
memset (device->fingerprint, 0, sizeof (device->fingerprint));
|
|
memset (device->version, 0, sizeof (device->version));
|
|
|
|
// Open the device.
|
|
int rc = serial_open (&device->port, context, name);
|
|
if (rc == -1) {
|
|
ERROR (context, "Failed to open the serial port.");
|
|
free (device);
|
|
return DC_STATUS_IO;
|
|
}
|
|
|
|
// Set the serial communication protocol (256000 8N1).
|
|
rc = serial_configure (device->port, BAUDRATE, 8, SERIAL_PARITY_NONE, 1, SERIAL_FLOWCONTROL_NONE);
|
|
if (rc == -1) {
|
|
ERROR (context, "Failed to set the terminal attributes.");
|
|
serial_close (device->port);
|
|
free (device);
|
|
return DC_STATUS_IO;
|
|
}
|
|
|
|
// Set the timeout for receiving data (1000 ms).
|
|
if (serial_set_timeout (device->port, 1000) == -1) {
|
|
ERROR (context, "Failed to set the timeout.");
|
|
serial_close (device->port);
|
|
free (device);
|
|
return DC_STATUS_IO;
|
|
}
|
|
|
|
// Set the DTR/RTS lines.
|
|
if (serial_set_dtr (device->port, 0) == -1 ||
|
|
serial_set_rts (device->port, 0) == -1)
|
|
{
|
|
ERROR (context, "Failed to set the DTR/RTS line.");
|
|
serial_close (device->port);
|
|
free (device);
|
|
return DC_STATUS_IO;
|
|
}
|
|
|
|
// Make sure everything is in a sane state.
|
|
serial_flush (device->port, SERIAL_QUEUE_BOTH);
|
|
|
|
// Send the version command.
|
|
dc_status_t status = mares_iconhd_version (device, device->version, sizeof (device->version));
|
|
if (status != DC_STATUS_SUCCESS) {
|
|
serial_close (device->port);
|
|
free (device);
|
|
return status;
|
|
}
|
|
|
|
*out = (dc_device_t *) device;
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
static dc_status_t
|
|
mares_iconhd_device_close (dc_device_t *abstract)
|
|
{
|
|
mares_iconhd_device_t *device = (mares_iconhd_device_t*) abstract;
|
|
|
|
if (! device_is_mares_iconhd (abstract))
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
// Close the device.
|
|
if (serial_close (device->port) == -1) {
|
|
free (device);
|
|
return DC_STATUS_IO;
|
|
}
|
|
|
|
// Free memory.
|
|
free (device);
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
static dc_status_t
|
|
mares_iconhd_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size)
|
|
{
|
|
mares_iconhd_device_t *device = (mares_iconhd_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
|
|
mares_iconhd_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size)
|
|
{
|
|
mares_iconhd_device_t *device = (mares_iconhd_device_t *) abstract;
|
|
|
|
return mares_iconhd_read (device, address, data, size, 0);
|
|
}
|
|
|
|
|
|
static dc_status_t
|
|
mares_iconhd_device_dump (dc_device_t *abstract, dc_buffer_t *buffer)
|
|
{
|
|
mares_iconhd_device_t *device = (mares_iconhd_device_t *) abstract;
|
|
|
|
// Erase the current contents of the buffer and
|
|
// pre-allocate the required amount of memory.
|
|
if (!dc_buffer_clear (buffer) || !dc_buffer_resize (buffer, MARES_ICONHD_MEMORY_SIZE)) {
|
|
ERROR (abstract->context, "Insufficient buffer space available.");
|
|
return DC_STATUS_NOMEMORY;
|
|
}
|
|
|
|
return mares_iconhd_read (device, 0, dc_buffer_get_data (buffer),
|
|
dc_buffer_get_size (buffer), 1);
|
|
}
|
|
|
|
|
|
static dc_status_t
|
|
mares_iconhd_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
|
|
{
|
|
mares_iconhd_device_t *device = (mares_iconhd_device_t *) abstract;
|
|
|
|
dc_buffer_t *buffer = dc_buffer_new (MARES_ICONHD_MEMORY_SIZE);
|
|
if (buffer == NULL)
|
|
return DC_STATUS_NOMEMORY;
|
|
|
|
dc_status_t rc = mares_iconhd_device_dump (abstract, buffer);
|
|
if (rc != DC_STATUS_SUCCESS) {
|
|
dc_buffer_free (buffer);
|
|
return rc;
|
|
}
|
|
|
|
// Emit a device info event.
|
|
unsigned char *data = dc_buffer_get_data (buffer);
|
|
dc_event_devinfo_t devinfo;
|
|
devinfo.model = mares_iconhd_get_model (device, data[0]);
|
|
devinfo.firmware = 0;
|
|
devinfo.serial = array_uint32_le (data + 0x0C);
|
|
device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo);
|
|
|
|
rc = mares_iconhd_extract_dives (abstract, dc_buffer_get_data (buffer),
|
|
dc_buffer_get_size (buffer), callback, userdata);
|
|
|
|
dc_buffer_free (buffer);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
dc_status_t
|
|
mares_iconhd_extract_dives (dc_device_t *abstract, const unsigned char data[], unsigned int size, dc_dive_callback_t callback, void *userdata)
|
|
{
|
|
mares_iconhd_device_t *device = (mares_iconhd_device_t *) abstract;
|
|
dc_context_t *context = (abstract ? abstract->context : NULL);
|
|
|
|
if (abstract && !device_is_mares_iconhd (abstract))
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
if (size < MARES_ICONHD_MEMORY_SIZE)
|
|
return DC_STATUS_DATAFORMAT;
|
|
|
|
// Get the model code.
|
|
unsigned int model = mares_iconhd_get_model (device, data[0]);
|
|
|
|
// Get the corresponding dive header size.
|
|
unsigned int header = 0x5C;
|
|
if (model == ICONHDNET)
|
|
header = 0x80;
|
|
|
|
// Get the end of the profile ring buffer.
|
|
unsigned int eop = 0;
|
|
const unsigned int config[] = {0x2001, 0x3001};
|
|
for (unsigned int i = 0; i < sizeof (config) / sizeof (*config); ++i) {
|
|
eop = array_uint32_le (data + config[i]);
|
|
if (eop != 0xFFFFFFFF)
|
|
break;
|
|
}
|
|
if (eop < RB_PROFILE_BEGIN || eop >= RB_PROFILE_END) {
|
|
ERROR (context, "Ringbuffer pointer out of range.");
|
|
return DC_STATUS_DATAFORMAT;
|
|
}
|
|
|
|
// Make the ringbuffer linear, to avoid having to deal with the wrap point.
|
|
unsigned char *buffer = (unsigned char *) malloc (RB_PROFILE_END - RB_PROFILE_BEGIN);
|
|
if (buffer == NULL) {
|
|
ERROR (context, "Failed to allocate memory.");
|
|
return DC_STATUS_NOMEMORY;
|
|
}
|
|
|
|
memcpy (buffer + 0, data + eop, RB_PROFILE_END - eop);
|
|
memcpy (buffer + RB_PROFILE_END - eop, data + RB_PROFILE_BEGIN, eop - RB_PROFILE_BEGIN);
|
|
|
|
unsigned int offset = RB_PROFILE_END - RB_PROFILE_BEGIN;
|
|
while (offset >= header + 4) {
|
|
// Get the number of samples in the profile data.
|
|
unsigned int nsamples = array_uint16_le (buffer + offset - header + 2);
|
|
if (nsamples == 0xFFFF)
|
|
break;
|
|
|
|
// Calculate the total number of bytes for this dive.
|
|
// If the buffer does not contain that much bytes, we reached the
|
|
// end of the ringbuffer. The current dive is incomplete (partially
|
|
// overwritten with newer data), and processing should stop.
|
|
unsigned int nbytes = 4 + header;
|
|
if (model == ICONHDNET)
|
|
nbytes += nsamples * 12 + (nsamples / 4) * 8;
|
|
else
|
|
nbytes += nsamples * 8;
|
|
if (offset < nbytes)
|
|
break;
|
|
|
|
// Move to the start of the dive.
|
|
offset -= nbytes;
|
|
|
|
// Verify that the length that is stored in the profile data
|
|
// equals the calculated length. If both values are different,
|
|
// something is wrong and an error is returned.
|
|
unsigned int length = array_uint32_le (buffer + offset);
|
|
if (length == 0 || length == 0xFFFFFFFF)
|
|
break;
|
|
if (length != nbytes) {
|
|
ERROR (context, "Calculated and stored size are not equal.");
|
|
free (buffer);
|
|
return DC_STATUS_DATAFORMAT;
|
|
}
|
|
|
|
unsigned char *fp = buffer + offset + length - header + 6;
|
|
if (device && memcmp (fp, device->fingerprint, sizeof (device->fingerprint)) == 0) {
|
|
free (buffer);
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
if (callback && !callback (buffer + offset, length, fp, sizeof (device->fingerprint), userdata)) {
|
|
free (buffer);
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
free (buffer);
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|