libdivecomputer/src/atomics_cobalt.c
Jef Driesen a21f60e0bc Handle a libusb timeout as a non fatal error.
Dives are downloaded using bulk transfers with an 8K buffer. Normally
the 2 second timeout is more than sufficient, and the timeout should
never expire, unless there is some serious communcation problem. But
nevertheless, users are reporting timeouts for dives having a length
that is an exact multiple of the USB packet size (64 bytes). In that
case, libusb reports a timeout with an non-zero amount of bytes
received. Despite the timeout, the received data contains a complete
dive.

I suspect libusb is somehow unable to determine whether the transfer is
complete and therefore waits until the timeout expires. For transfers
that are not a multiple of the USB packet size, the end of the transfer
is indicated by the last incomplete packet. This is not the case if the
length is an exact multiple of the USB packet size. This problem is
usually solved by sending a zero-length packet. Maybe the USB stack of
the Cobalt is not sending such a zero-length packet?

Atomics will address the problem with a Coblat 2 firmware upgrade, that
will simply append two zero bytes if the length is a multiple of 64
bytes. As a workaround for older firmware versions, we ignore the
timeout and process all received data. This shouldn't have any
disadvantages. An incomplete dive, for example due to a real timeout,
will now be detected by means of the minimum length and/or the checksum.
2015-10-28 08:08:17 +01:00

413 lines
12 KiB
C

/*
* libdivecomputer
*
* Copyright (C) 2011 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
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h> // memcmp, memcpy
#include <stdlib.h> // malloc, free
#ifdef HAVE_LIBUSB
#ifdef _WIN32
#define NOGDI
#endif
#include <libusb-1.0/libusb.h>
#endif
#include <libdivecomputer/atomics_cobalt.h>
#include "context-private.h"
#include "device-private.h"
#include "checksum.h"
#include "array.h"
#define ISINSTANCE(device) dc_device_isinstance((device), &atomics_cobalt_device_vtable)
#define EXITCODE(rc) (rc == LIBUSB_ERROR_TIMEOUT ? DC_STATUS_TIMEOUT : DC_STATUS_IO)
#define VID 0x0471
#define PID 0x0888
#define TIMEOUT 2000
#define FP_OFFSET 20
#define SZ_MEMORY (29 * 64 * 1024)
#define SZ_VERSION 14
typedef struct atomics_cobalt_device_t {
dc_device_t base;
#ifdef HAVE_LIBUSB
libusb_context *context;
libusb_device_handle *handle;
#endif
unsigned int simulation;
unsigned char fingerprint[6];
unsigned char version[SZ_VERSION];
} atomics_cobalt_device_t;
static dc_status_t atomics_cobalt_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size);
static dc_status_t atomics_cobalt_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata);
static dc_status_t atomics_cobalt_device_close (dc_device_t *abstract);
static const dc_device_vtable_t atomics_cobalt_device_vtable = {
DC_FAMILY_ATOMICS_COBALT,
atomics_cobalt_device_set_fingerprint, /* set_fingerprint */
NULL, /* read */
NULL, /* write */
NULL, /* dump */
atomics_cobalt_device_foreach, /* foreach */
atomics_cobalt_device_close /* close */
};
dc_status_t
atomics_cobalt_device_open (dc_device_t **out, dc_context_t *context)
{
if (out == NULL)
return DC_STATUS_INVALIDARGS;
#ifdef HAVE_LIBUSB
// Allocate memory.
atomics_cobalt_device_t *device = (atomics_cobalt_device_t *) malloc (sizeof (atomics_cobalt_device_t));
if (device == NULL) {
ERROR (context, "Failed to allocate memory.");
return DC_STATUS_NOMEMORY;
}
// Initialize the base class.
device_init (&device->base, context, &atomics_cobalt_device_vtable);
// Set the default values.
device->context = NULL;
device->handle = NULL;
device->simulation = 0;
memset (device->fingerprint, 0, sizeof (device->fingerprint));
int rc = libusb_init (&device->context);
if (rc < 0) {
ERROR (context, "Failed to initialize usb support.");
free (device);
return DC_STATUS_IO;
}
device->handle = libusb_open_device_with_vid_pid (device->context, VID, PID);
if (device->handle == NULL) {
ERROR (context, "Failed to open the usb device.");
libusb_exit (device->context);
free (device);
return DC_STATUS_IO;
}
rc = libusb_claim_interface (device->handle, 0);
if (rc < 0) {
ERROR (context, "Failed to claim the usb interface.");
libusb_close (device->handle);
libusb_exit (device->context);
free (device);
return DC_STATUS_IO;
}
dc_status_t status = atomics_cobalt_device_version ((dc_device_t *) device, device->version, sizeof (device->version));
if (status != DC_STATUS_SUCCESS) {
ERROR (context, "Failed to identify the dive computer.");
libusb_close (device->handle);
libusb_exit (device->context);
free (device);
return status;
}
*out = (dc_device_t*) device;
return DC_STATUS_SUCCESS;
#else
return DC_STATUS_UNSUPPORTED;
#endif
}
static dc_status_t
atomics_cobalt_device_close (dc_device_t *abstract)
{
atomics_cobalt_device_t *device = (atomics_cobalt_device_t *) abstract;
#ifdef HAVE_LIBUSB
libusb_release_interface(device->handle, 0);
libusb_close (device->handle);
libusb_exit (device->context);
#endif
// Free memory.
free (device);
return DC_STATUS_SUCCESS;
}
static dc_status_t
atomics_cobalt_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size)
{
atomics_cobalt_device_t *device = (atomics_cobalt_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;
}
dc_status_t
atomics_cobalt_device_set_simulation (dc_device_t *abstract, unsigned int simulation)
{
atomics_cobalt_device_t *device = (atomics_cobalt_device_t *) abstract;
if (!ISINSTANCE (abstract))
return DC_STATUS_INVALIDARGS;
device->simulation = simulation;
return DC_STATUS_SUCCESS;
}
dc_status_t
atomics_cobalt_device_version (dc_device_t *abstract, unsigned char data[], unsigned int size)
{
atomics_cobalt_device_t *device = (atomics_cobalt_device_t *) abstract;
if (!ISINSTANCE (abstract))
return DC_STATUS_INVALIDARGS;
if (size < SZ_VERSION)
return DC_STATUS_INVALIDARGS;
#ifdef HAVE_LIBUSB
// Send the command to the dive computer.
uint8_t bRequest = 0x01;
int rc = libusb_control_transfer (device->handle,
LIBUSB_RECIPIENT_DEVICE | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT,
bRequest, 0, 0, NULL, 0, TIMEOUT);
if (rc != LIBUSB_SUCCESS) {
ERROR (abstract->context, "Failed to send the command.");
return EXITCODE(rc);
}
HEXDUMP (abstract->context, DC_LOGLEVEL_INFO, "Write", &bRequest, 1);
// Receive the answer from the dive computer.
int length = 0;
unsigned char packet[SZ_VERSION + 2] = {0};
rc = libusb_bulk_transfer (device->handle, 0x82,
packet, sizeof (packet), &length, TIMEOUT);
if (rc != LIBUSB_SUCCESS || length != sizeof (packet)) {
ERROR (abstract->context, "Failed to receive the answer.");
return EXITCODE(rc);
}
HEXDUMP (abstract->context, DC_LOGLEVEL_INFO, "Read", packet, length);
// Verify the checksum of the packet.
unsigned short crc = array_uint16_le (packet + SZ_VERSION);
unsigned short ccrc = checksum_add_uint16 (packet, SZ_VERSION, 0x0);
if (crc != ccrc) {
ERROR (abstract->context, "Unexpected answer checksum.");
return DC_STATUS_PROTOCOL;
}
memcpy (data, packet, SZ_VERSION);
return DC_STATUS_SUCCESS;
#else
return DC_STATUS_UNSUPPORTED;
#endif
}
static dc_status_t
atomics_cobalt_read_dive (dc_device_t *abstract, dc_buffer_t *buffer, int init, dc_event_progress_t *progress)
{
#ifdef HAVE_LIBUSB
atomics_cobalt_device_t *device = (atomics_cobalt_device_t *) abstract;
if (device_is_cancelled (abstract))
return DC_STATUS_CANCELLED;
// Erase the current contents of the buffer.
if (!dc_buffer_clear (buffer)) {
ERROR (abstract->context, "Insufficient buffer space available.");
return DC_STATUS_NOMEMORY;
}
// Send the command to the dive computer.
uint8_t bRequest = 0;
if (device->simulation)
bRequest = init ? 0x02 : 0x03;
else
bRequest = init ? 0x09 : 0x0A;
int rc = libusb_control_transfer (device->handle,
LIBUSB_RECIPIENT_DEVICE | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT,
bRequest, 0, 0, NULL, 0, TIMEOUT);
if (rc != LIBUSB_SUCCESS) {
ERROR (abstract->context, "Failed to send the command.");
return EXITCODE(rc);
}
HEXDUMP (abstract->context, DC_LOGLEVEL_INFO, "Write", &bRequest, 1);
unsigned int nbytes = 0;
while (1) {
// Receive the answer from the dive computer.
int length = 0;
unsigned char packet[8 * 1024] = {0};
rc = libusb_bulk_transfer (device->handle, 0x82,
packet, sizeof (packet), &length, TIMEOUT);
if (rc != LIBUSB_SUCCESS && rc != LIBUSB_ERROR_TIMEOUT) {
ERROR (abstract->context, "Failed to receive the answer.");
return EXITCODE(rc);
}
HEXDUMP (abstract->context, DC_LOGLEVEL_INFO, "Read", packet, length);
// Update and emit a progress event.
if (progress) {
progress->current += length;
device_event_emit (abstract, DC_EVENT_PROGRESS, progress);
}
// Append the packet to the output buffer.
dc_buffer_append (buffer, packet, length);
nbytes += length;
// If we received fewer bytes than requested, the transfer is finished.
if (length < sizeof (packet))
break;
}
// Check for a buffer error.
if (dc_buffer_get_size (buffer) != nbytes) {
ERROR (abstract->context, "Insufficient buffer space available.");
return DC_STATUS_NOMEMORY;
}
// Check for the minimum length.
if (nbytes < 2) {
ERROR (abstract->context, "Data packet is too short.");
return DC_STATUS_PROTOCOL;
}
// When only two 0xFF bytes are received, there are no more dives.
unsigned char *data = dc_buffer_get_data (buffer);
if (nbytes == 2 && data[0] == 0xFF && data[1] == 0xFF) {
dc_buffer_clear (buffer);
return DC_STATUS_SUCCESS;
}
// Verify the checksum of the packet.
unsigned short crc = array_uint16_le (data + nbytes - 2);
unsigned short ccrc = checksum_add_uint16 (data, nbytes - 2, 0x0);
if (crc != ccrc) {
ERROR (abstract->context, "Unexpected answer checksum.");
return DC_STATUS_PROTOCOL;
}
// Remove the checksum bytes.
dc_buffer_slice (buffer, 0, nbytes - 2);
return DC_STATUS_SUCCESS;
#else
return DC_STATUS_UNSUPPORTED;
#endif
}
static dc_status_t
atomics_cobalt_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
{
atomics_cobalt_device_t *device = (atomics_cobalt_device_t *) abstract;
// Enable progress notifications.
dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
progress.maximum = SZ_MEMORY + 2;
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);
// Emit a device info event.
dc_event_devinfo_t devinfo;
devinfo.model = array_uint16_le (device->version + 12);
devinfo.firmware = (array_uint16_le (device->version + 8) << 16)
+ array_uint16_le (device->version + 10);
devinfo.serial = 0;
for (unsigned int i = 0; i < 8; ++i) {
devinfo.serial *= 10;
devinfo.serial += device->version[i] - '0';
}
device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo);
// Allocate a memory buffer.
dc_buffer_t *buffer = dc_buffer_new (0);
if (buffer == NULL)
return DC_STATUS_NOMEMORY;
unsigned int ndives = 0;
dc_status_t rc = DC_STATUS_SUCCESS;
while ((rc = atomics_cobalt_read_dive (abstract, buffer, (ndives == 0), &progress)) == DC_STATUS_SUCCESS) {
unsigned char *data = dc_buffer_get_data (buffer);
unsigned int size = dc_buffer_get_size (buffer);
if (size == 0) {
dc_buffer_free (buffer);
return DC_STATUS_SUCCESS;
}
if (memcmp (data + FP_OFFSET, device->fingerprint, sizeof (device->fingerprint)) == 0) {
dc_buffer_free (buffer);
return DC_STATUS_SUCCESS;
}
if (callback && !callback (data, size, data + FP_OFFSET, sizeof (device->fingerprint), userdata)) {
dc_buffer_free (buffer);
return DC_STATUS_SUCCESS;
}
// Adjust the maximum value to take into account the two checksum bytes
// for the next dive. Since we don't know the total number of dives in
// advance, we can't calculate the total number of checksum bytes and
// adjust the maximum on the fly.
progress.maximum += 2;
ndives++;
}
dc_buffer_free (buffer);
return rc;
}