The new vendor event provides a mechanism to deliver auxiliary data, which is automatically retrieved during the data transfer, but not accessible through the library interface otherwise. Possible examples include handshake data and/or device identification data. This event is mainly intended for diagnostic purposes, in combination with the memory dumping support. Very few applications will actually need it for anything else.
418 lines
11 KiB
C
418 lines
11 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 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 device_backend_t atomics_cobalt_device_backend = {
|
|
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 */
|
|
};
|
|
|
|
static int
|
|
device_is_atomics_cobalt (dc_device_t *abstract)
|
|
{
|
|
if (abstract == NULL)
|
|
return 0;
|
|
|
|
return abstract->backend == &atomics_cobalt_device_backend;
|
|
}
|
|
|
|
|
|
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_backend);
|
|
|
|
// 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;
|
|
|
|
if (! device_is_atomics_cobalt (abstract))
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
#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 (! device_is_atomics_cobalt (abstract))
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
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 (! device_is_atomics_cobalt (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 (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);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
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) {
|
|
ERROR (abstract->context, "Failed to receive the answer.");
|
|
return EXITCODE(rc);
|
|
}
|
|
|
|
// 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;
|
|
|
|
if (! device_is_atomics_cobalt (abstract))
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
// 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;
|
|
}
|