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.
413 lines
12 KiB
C
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;
|
|
}
|