Currently the dive computer backends are responsible for opening (and closing) the underlying I/O stream internally. The consequence is that each backend is hardwired to a specific transport type (e.g. serial, irda or usbhid). In order to remove this dependency and support more than one transport type in the same backend, the opening (and closing) of the I/O stream is moved to the application. The dc_device_open() function is modified to accept a pointer to the I/O stream, instead of a string with the device node (which only makes sense for serial communication). The dive computer backends only depend on the common I/O interface.
363 lines
11 KiB
C
363 lines
11 KiB
C
/*
|
|
* libdivecomputer
|
|
*
|
|
* Copyright (C) 2009 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> // malloc
|
|
#include <string.h> // memcpy, memcmp
|
|
#include <assert.h> // assert
|
|
|
|
#include "context-private.h"
|
|
#include "mares_common.h"
|
|
#include "checksum.h"
|
|
#include "array.h"
|
|
|
|
#define MAXRETRIES 4
|
|
|
|
#define FP_OFFSET 8
|
|
#define FP_SIZE 5
|
|
|
|
#define NEMO 0
|
|
#define NEMOWIDE 1
|
|
#define NEMOAIR 4
|
|
#define PUCK 7
|
|
#define NEMOEXCEL 17
|
|
#define NEMOAPNEIST 18
|
|
#define PUCKAIR 19
|
|
|
|
#define AIR 0
|
|
#define NITROX 1
|
|
#define FREEDIVE 2
|
|
#define GAUGE 3
|
|
|
|
void
|
|
mares_common_device_init (mares_common_device_t *device, dc_iostream_t *iostream)
|
|
{
|
|
assert (device != NULL);
|
|
|
|
// Set the default values.
|
|
device->iostream = iostream;
|
|
device->echo = 0;
|
|
device->delay = 0;
|
|
}
|
|
|
|
|
|
static void
|
|
mares_common_make_ascii (const unsigned char raw[], unsigned int rsize, unsigned char ascii[], unsigned int asize)
|
|
{
|
|
assert (asize == 2 * (rsize + 2));
|
|
|
|
// Header
|
|
ascii[0] = '<';
|
|
|
|
// Data
|
|
array_convert_bin2hex (raw, rsize, ascii + 1, 2 * rsize);
|
|
|
|
// Checksum
|
|
unsigned char checksum = checksum_add_uint8 (ascii + 1, 2 * rsize, 0x00);
|
|
array_convert_bin2hex (&checksum, 1, ascii + 1 + 2 * rsize, 2);
|
|
|
|
// Trailer
|
|
ascii[asize - 1] = '>';
|
|
}
|
|
|
|
|
|
static dc_status_t
|
|
mares_common_packet (mares_common_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize)
|
|
{
|
|
dc_status_t status = DC_STATUS_SUCCESS;
|
|
dc_device_t *abstract = (dc_device_t *) device;
|
|
|
|
if (device_is_cancelled (abstract))
|
|
return DC_STATUS_CANCELLED;
|
|
|
|
if (device->delay) {
|
|
dc_iostream_sleep (device->iostream, device->delay);
|
|
}
|
|
|
|
// Send the command to the device.
|
|
status = dc_iostream_write (device->iostream, command, csize, NULL);
|
|
if (status != DC_STATUS_SUCCESS) {
|
|
ERROR (abstract->context, "Failed to send the command.");
|
|
return status;
|
|
}
|
|
|
|
if (device->echo) {
|
|
// Receive the echo of the command.
|
|
unsigned char echo[PACKETSIZE] = {0};
|
|
status = dc_iostream_read (device->iostream, echo, csize, NULL);
|
|
if (status != DC_STATUS_SUCCESS) {
|
|
ERROR (abstract->context, "Failed to receive the echo.");
|
|
return status;
|
|
}
|
|
|
|
// Verify the echo.
|
|
if (memcmp (echo, command, csize) != 0) {
|
|
WARNING (abstract->context, "Unexpected echo.");
|
|
}
|
|
}
|
|
|
|
// Receive the answer of the device.
|
|
status = dc_iostream_read (device->iostream, answer, asize, 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 (answer[0] != '<' || answer[asize - 1] != '>') {
|
|
ERROR (abstract->context, "Unexpected answer header/trailer byte.");
|
|
return DC_STATUS_PROTOCOL;
|
|
}
|
|
|
|
// Verify the checksum of the packet.
|
|
unsigned char crc = 0;
|
|
unsigned char ccrc = checksum_add_uint8 (answer + 1, asize - 4, 0x00);
|
|
array_convert_hex2bin (answer + asize - 3, 2, &crc, 1);
|
|
if (crc != ccrc) {
|
|
ERROR (abstract->context, "Unexpected answer checksum.");
|
|
return DC_STATUS_PROTOCOL;
|
|
}
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
static dc_status_t
|
|
mares_common_transfer (mares_common_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize)
|
|
{
|
|
unsigned int nretries = 0;
|
|
dc_status_t rc = DC_STATUS_SUCCESS;
|
|
while ((rc = mares_common_packet (device, 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
|
|
mares_common_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size)
|
|
{
|
|
mares_common_device_t *device = (mares_common_device_t*) abstract;
|
|
|
|
unsigned int nbytes = 0;
|
|
while (nbytes < size) {
|
|
// Calculate the packet size.
|
|
unsigned int len = size - nbytes;
|
|
if (len > PACKETSIZE)
|
|
len = PACKETSIZE;
|
|
|
|
// Build the raw command.
|
|
unsigned char raw[] = {0x51,
|
|
(address ) & 0xFF, // Low
|
|
(address >> 8) & 0xFF, // High
|
|
len}; // Count
|
|
|
|
// Build the ascii command.
|
|
unsigned char command[2 * (sizeof (raw) + 2)] = {0};
|
|
mares_common_make_ascii (raw, sizeof (raw), command, sizeof (command));
|
|
|
|
// Send the command and receive the answer.
|
|
unsigned char answer[2 * (PACKETSIZE + 2)] = {0};
|
|
dc_status_t rc = mares_common_transfer (device, command, sizeof (command), answer, 2 * (len + 2));
|
|
if (rc != DC_STATUS_SUCCESS)
|
|
return rc;
|
|
|
|
// Extract the raw data from the packet.
|
|
array_convert_hex2bin (answer + 1, 2 * len, data, len);
|
|
|
|
nbytes += len;
|
|
address += len;
|
|
data += len;
|
|
}
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
dc_status_t
|
|
mares_common_extract_dives (dc_context_t *context, const mares_common_layout_t *layout, const unsigned char fingerprint[], const unsigned char data[], dc_dive_callback_t callback, void *userdata)
|
|
{
|
|
assert (layout != NULL);
|
|
|
|
// Get the freedive mode for this model.
|
|
unsigned int model = data[1];
|
|
unsigned int freedive = FREEDIVE;
|
|
if (model == NEMOWIDE || model == NEMOAIR || model == PUCK || model == PUCKAIR)
|
|
freedive = GAUGE;
|
|
|
|
// Get the end of the profile ring buffer.
|
|
unsigned int eop = array_uint16_le (data + 0x6B);
|
|
if (eop < layout->rb_profile_begin || eop >= layout->rb_profile_end) {
|
|
ERROR (context, "Ringbuffer pointer out of range (0x%04x).", eop);
|
|
return DC_STATUS_DATAFORMAT;
|
|
}
|
|
|
|
// Make the ringbuffer linear, to avoid having to deal
|
|
// with the wrap point. The buffer has extra space to
|
|
// store the profile data for the freedives.
|
|
unsigned char *buffer = (unsigned char *) malloc (
|
|
layout->rb_profile_end - layout->rb_profile_begin +
|
|
layout->rb_freedives_end - layout->rb_freedives_begin);
|
|
if (buffer == NULL) {
|
|
ERROR (context, "Failed to allocate memory.");
|
|
return DC_STATUS_NOMEMORY;
|
|
}
|
|
|
|
memcpy (buffer + 0, data + eop, layout->rb_profile_end - eop);
|
|
memcpy (buffer + layout->rb_profile_end - eop, data + layout->rb_profile_begin, eop - layout->rb_profile_begin);
|
|
|
|
// For a freedive session, the Mares Nemo stores all the freedives of
|
|
// that session in a single logbook entry, and each sample is actually
|
|
// a summary for each individual freedive in the session. The profile
|
|
// data is stored in a separate memory area. Since only the most recent
|
|
// recent freediving session can have profile data, we keep track of the
|
|
// number of freedives.
|
|
unsigned int nfreedives = 0;
|
|
|
|
unsigned int offset = layout->rb_profile_end - layout->rb_profile_begin;
|
|
while (offset >= 3) {
|
|
// Check for the presence of extra header bytes, which can be detected
|
|
// by means of a three byte marker sequence.
|
|
unsigned int extra = 0;
|
|
const unsigned char marker[3] = {0xAA, 0xBB, 0xCC};
|
|
if (memcmp (buffer + offset - 3, marker, sizeof (marker)) == 0) {
|
|
if (model == PUCKAIR)
|
|
extra = 7;
|
|
else
|
|
extra = 12;
|
|
}
|
|
|
|
// Check for overflows due to incomplete dives.
|
|
if (offset < extra + 3)
|
|
break;
|
|
|
|
// Check the dive mode of the logbook entry. Valid modes are
|
|
// 0 (air), 1 (EANx), 2 (freedive) or 3 (bottom timer).
|
|
// If the ringbuffer has never reached the wrap point before,
|
|
// there will be "empty" memory (filled with 0xFF) and
|
|
// processing should stop at this point.
|
|
unsigned int mode = buffer[offset - extra - 1];
|
|
if (mode == 0xFF)
|
|
break;
|
|
|
|
// The header and sample size are dependant on the dive mode. Only
|
|
// in freedive mode, the sizes are different from the other modes.
|
|
unsigned int header_size = 53;
|
|
unsigned int sample_size = 2;
|
|
if (extra) {
|
|
if (model == PUCKAIR)
|
|
sample_size = 3;
|
|
else
|
|
sample_size = 5;
|
|
}
|
|
if (mode == freedive) {
|
|
header_size = 28;
|
|
sample_size = 6;
|
|
nfreedives++;
|
|
}
|
|
|
|
// Get the number of samples in the profile data.
|
|
unsigned int nsamples = array_uint16_le (buffer + offset - extra - 3);
|
|
|
|
// 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 = 2 + nsamples * sample_size + header_size + extra;
|
|
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_uint16_le (buffer + offset);
|
|
if (length != nbytes) {
|
|
ERROR (context, "Calculated and stored size are not equal (%u %u).", length, nbytes);
|
|
free (buffer);
|
|
return DC_STATUS_DATAFORMAT;
|
|
}
|
|
|
|
// Process the profile data for the most recent freedive entry.
|
|
// Since we are processing the entries backwards (newest to oldest),
|
|
// this entry will always be the first one.
|
|
if (mode == freedive && nfreedives == 1) {
|
|
// Count the number of freedives in the profile data.
|
|
unsigned int count = 0;
|
|
unsigned int idx = layout->rb_freedives_begin;
|
|
while (idx + 2 <= layout->rb_freedives_end &&
|
|
count != nsamples)
|
|
{
|
|
// Each freedive in the session ends with a zero sample.
|
|
unsigned int sample = array_uint16_le (data + idx);
|
|
if (sample == 0)
|
|
count++;
|
|
|
|
// Move to the next sample.
|
|
idx += 2;
|
|
}
|
|
|
|
// Verify that the number of freedive entries in the session
|
|
// equals the number of freedives in the profile data. If
|
|
// both values are different, the profile data is incomplete.
|
|
if (count != nsamples) {
|
|
ERROR (context, "Unexpected number of freedive sessions (%u %u).", count, nsamples);
|
|
free (buffer);
|
|
return DC_STATUS_DATAFORMAT;
|
|
}
|
|
|
|
// Append the profile data to the main logbook entry. The
|
|
// buffer is guaranteed to have enough space, and the dives
|
|
// that will be overwritten have already been processed.
|
|
memcpy (buffer + offset + nbytes, data + layout->rb_freedives_begin, idx - layout->rb_freedives_begin);
|
|
nbytes += idx - layout->rb_freedives_begin;
|
|
}
|
|
|
|
unsigned int fp_offset = offset + length - extra - FP_OFFSET;
|
|
if (fingerprint && memcmp (buffer + fp_offset, fingerprint, FP_SIZE) == 0) {
|
|
free (buffer);
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
if (callback && !callback (buffer + offset, nbytes, buffer + fp_offset, FP_SIZE, userdata)) {
|
|
free (buffer);
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
free (buffer);
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|