diff --git a/examples/common.c b/examples/common.c index a4927cd..b943fd1 100644 --- a/examples/common.c +++ b/examples/common.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include "common.h" @@ -90,9 +91,12 @@ static const backend_table_t g_backends[] = { {"idive", DC_FAMILY_DIVESYSTEM_IDIVE, 0x03}, {"cochran", DC_FAMILY_COCHRAN_COMMANDER, 0}, {"divecomputereu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0}, + {"mclean", DC_FAMILY_MCLEAN_EXTREME, 0}, + {"lynx", DC_FAMILY_LIQUIVISION_LYNX, 0}, + + // Not merged upstream yet {"descentmk1", DC_FAMILY_GARMIN, 0}, {"cosmiq", DC_FAMILY_DEEPBLU, 0}, - {"mclean", DC_FAMILY_MCLEAN_EXTREME, 0}, {"oceans", DC_FAMILY_OCEANS_S1, 0}, }; @@ -103,6 +107,8 @@ static const transport_table_t g_transports[] = { {"irda", DC_TRANSPORT_IRDA}, {"bluetooth", DC_TRANSPORT_BLUETOOTH}, {"ble", DC_TRANSPORT_BLE}, + + // Not merged upstream yet {"usbstorage",DC_TRANSPORT_USBSTORAGE}, }; @@ -401,6 +407,41 @@ dctool_file_read (const char *filename) return buffer; } +static dc_status_t +dctool_usb_open (dc_iostream_t **out, dc_context_t *context, dc_descriptor_t *descriptor) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_iostream_t *iostream = NULL; + + // Discover the usb device. + dc_iterator_t *iterator = NULL; + dc_usb_device_t *device = NULL; + dc_usb_iterator_new (&iterator, context, descriptor); + while (dc_iterator_next (iterator, &device) == DC_STATUS_SUCCESS) { + break; + } + dc_iterator_free (iterator); + + if (device == NULL) { + ERROR ("No dive computer found."); + status = DC_STATUS_NODEVICE; + goto cleanup; + } + + // Open the usb device. + status = dc_usb_open (&iostream, context, device); + if (status != DC_STATUS_SUCCESS) { + ERROR ("Failed to open the usb device."); + goto cleanup; + } + + *out = iostream; + +cleanup: + dc_usb_device_free (device); + return status; +} + static dc_status_t dctool_usbhid_open (dc_iostream_t **out, dc_context_t *context, dc_descriptor_t *descriptor) { @@ -535,16 +576,18 @@ dctool_iostream_open (dc_iostream_t **iostream, dc_context_t *context, dc_descri case DC_TRANSPORT_SERIAL: return dc_serial_open (iostream, context, devname); case DC_TRANSPORT_USB: - return DC_STATUS_SUCCESS; + return dctool_usb_open(iostream, context, descriptor); case DC_TRANSPORT_USBHID: return dctool_usbhid_open(iostream, context, descriptor); case DC_TRANSPORT_IRDA: return dctool_irda_open (iostream, context, descriptor, devname); case DC_TRANSPORT_BLUETOOTH: return dctool_bluetooth_open (iostream, context, descriptor, devname); - case DC_TRANSPORT_USBSTORAGE: - return dc_usb_storage_open (iostream, context, devname); default: return DC_STATUS_UNSUPPORTED; + + // Not merged upstream yet + case DC_TRANSPORT_USBSTORAGE: + return dc_usb_storage_open (iostream, context, devname); } } diff --git a/examples/dctool_scan.c b/examples/dctool_scan.c index b96d014..a68d46b 100644 --- a/examples/dctool_scan.c +++ b/examples/dctool_scan.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include "dctool.h" @@ -59,6 +60,9 @@ scan (dc_context_t *context, dc_descriptor_t *descriptor, dc_transport_t transpo case DC_TRANSPORT_BLUETOOTH: status = dc_bluetooth_iterator_new (&iterator, context, descriptor); break; + case DC_TRANSPORT_USB: + status = dc_usb_iterator_new (&iterator, context, descriptor); + break; case DC_TRANSPORT_USBHID: status = dc_usbhid_iterator_new (&iterator, context, descriptor); break; @@ -90,6 +94,10 @@ scan (dc_context_t *context, dc_descriptor_t *descriptor, dc_transport_t transpo dc_bluetooth_device_get_name (device)); dc_bluetooth_device_free (device); break; + case DC_TRANSPORT_USB: + printf ("%04x:%04x\n", dc_usb_device_get_vid (device), dc_usb_device_get_pid (device)); + dc_usb_device_free (device); + break; case DC_TRANSPORT_USBHID: printf ("%04x:%04x\n", dc_usbhid_device_get_vid (device), dc_usbhid_device_get_pid (device)); dc_usbhid_device_free (device); diff --git a/include/libdivecomputer/Makefile.am b/include/libdivecomputer/Makefile.am index bffc849..6f679c7 100644 --- a/include/libdivecomputer/Makefile.am +++ b/include/libdivecomputer/Makefile.am @@ -12,6 +12,7 @@ libdivecomputer_HEADERS = \ bluetooth.h \ ble.h \ irda.h \ + usb.h \ usbhid.h \ custom.h \ device.h \ diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h index 065925e..ac11fa6 100644 --- a/include/libdivecomputer/common.h +++ b/include/libdivecomputer/common.h @@ -110,12 +110,16 @@ typedef enum dc_family_t { DC_FAMILY_TECDIVING_DIVECOMPUTEREU = (15 << 16), /* McLean */ DC_FAMILY_MCLEAN_EXTREME = (16 << 16), + /* Liquivision */ + DC_FAMILY_LIQUIVISION_LYNX = (17 << 16), + + // Not merged upstream yet /* Garmin */ - DC_FAMILY_GARMIN = (17 << 16), + DC_FAMILY_GARMIN = (100 << 16), /* Deepblu */ - DC_FAMILY_DEEPBLU = (18 << 16), + DC_FAMILY_DEEPBLU = (101 << 16), /* Oceans S1 */ - DC_FAMILY_OCEANS_S1 = (19 << 16), + DC_FAMILY_OCEANS_S1 = (102 << 16), } dc_family_t; #ifdef __cplusplus diff --git a/include/libdivecomputer/usb.h b/include/libdivecomputer/usb.h new file mode 100644 index 0000000..6d13813 --- /dev/null +++ b/include/libdivecomputer/usb.h @@ -0,0 +1,145 @@ +/* + * libdivecomputer + * + * Copyright (C) 2020 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 + */ + +#ifndef DC_USB_H +#define DC_USB_H + +#include "common.h" +#include "context.h" +#include "iostream.h" +#include "iterator.h" +#include "descriptor.h" +#include "ioctl.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * Perform a USB control transfer. + * + * The parameters for the control transfer are specified in the + * #dc_usb_control_t data structure. If the control transfer requires + * additional data as in- or output, the buffer must be located + * immediately after the #dc_usb_control_t data structure, and the + * length of the buffer must be indicated in the #wLength field. The + * size of the ioctl request is the total size, including the size of + * the #dc_usb_control_t structure. + */ +#define DC_IOCTL_USB_CONTROL_READ DC_IOCTL_IOR('u', 0, DC_IOCTL_SIZE_VARIABLE) +#define DC_IOCTL_USB_CONTROL_WRITE DC_IOCTL_IOW('u', 0, DC_IOCTL_SIZE_VARIABLE) + +/** + * USB control transfer. + */ +typedef struct dc_usb_control_t { + unsigned char bmRequestType; + unsigned char bRequest; + unsigned short wValue; + unsigned short wIndex; + unsigned short wLength; +} dc_usb_control_t; + +/** + * Endpoint direction bits of the USB control transfer. + */ +typedef enum dc_usb_endpoint_t { + DC_USB_ENDPOINT_OUT = 0x00, + DC_USB_ENDPOINT_IN = 0x80 +} dc_usb_endpoint_t; + +/** + * Request type bits of the USB control transfer. + */ +typedef enum dc_usb_request_t { + DC_USB_REQUEST_STANDARD = 0x00, + DC_USB_REQUEST_CLASS = 0x20, + DC_USB_REQUEST_VENDOR = 0x40, + DC_USB_REQUEST_RESERVED = 0x60 +} dc_usb_request_t; + +/** + * Recipient bits of the USB control transfer. + */ +typedef enum dc_usb_recipient_t { + DC_USB_RECIPIENT_DEVICE = 0x00, + DC_USB_RECIPIENT_INTERFACE = 0x01, + DC_USB_RECIPIENT_ENDPOINT = 0x02, + DC_USB_RECIPIENT_OTHER = 0x03, +} dc_usb_recipient_t; + +/** + * Opaque object representing a USB device. + */ +typedef struct dc_usb_device_t dc_usb_device_t; + +/** + * Get the vendor id (VID) of the USB device. + * + * @param[in] device A valid USB device. + */ +unsigned int +dc_usb_device_get_vid (dc_usb_device_t *device); + +/** + * Get the product id (PID) of the USB device. + * + * @param[in] device A valid USB device. + */ +unsigned int +dc_usb_device_get_pid (dc_usb_device_t *device); + +/** + * Destroy the USB device and free all resources. + * + * @param[in] device A valid USB device. + */ +void +dc_usb_device_free(dc_usb_device_t *device); + +/** + * Create an iterator to enumerate the USB devices. + * + * @param[out] iterator A location to store the iterator. + * @param[in] context A valid context object. + * @param[in] descriptor A valid device descriptor or NULL. + * @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code + * on failure. + */ +dc_status_t +dc_usb_iterator_new (dc_iterator_t **iterator, dc_context_t *context, dc_descriptor_t *descriptor); + +/** + * Open a USB connection. + * + * @param[out] iostream A location to store the USB connection. + * @param[in] context A valid context object. + * @param[in] device A valid USB device. + * @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code + * on failure. + */ +dc_status_t +dc_usb_open (dc_iostream_t **iostream, dc_context_t *context, dc_usb_device_t *device); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* DC_USB_H */ diff --git a/msvc/libdivecomputer.vcproj b/msvc/libdivecomputer.vcproj index 91f015d..8298de6 100644 --- a/msvc/libdivecomputer.vcproj +++ b/msvc/libdivecomputer.vcproj @@ -318,6 +318,14 @@ RelativePath="..\src\iterator.c" > + + + + @@ -546,6 +554,10 @@ RelativePath="..\src\timer.c" > + + @@ -720,6 +732,10 @@ RelativePath="..\include\libdivecomputer\iterator.h" > + + @@ -912,6 +928,10 @@ RelativePath="..\include\libdivecomputer\units.h" > + + diff --git a/src/Makefile.am b/src/Makefile.am index 37da7e8..e9560f9 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -69,21 +69,26 @@ libdivecomputer_la_SOURCES = \ rbstream.h rbstream.c \ checksum.h checksum.c \ array.h array.c \ - field-cache.h field-cache.c \ buffer.c \ cochran_commander.h cochran_commander.c cochran_commander_parser.c \ tecdiving_divecomputereu.h tecdiving_divecomputereu.c tecdiving_divecomputereu_parser.c \ - garmin.h garmin.c garmin_parser.c \ - deepblu.h deepblu.c deepblu_parser.c \ mclean_extreme.h mclean_extreme.c mclean_extreme_parser.c \ - oceans_s1.h oceans_s1.c oceans_s1_parser.c \ + liquivision_lynx.h liquivision_lynx.c liquivision_lynx_parser.c \ socket.h socket.c \ irda.c \ + usb.c \ usbhid.c \ bluetooth.c \ - usb_storage.c \ custom.c +# Not merged upstream yet +libdivecomputer_la_SOURCES += \ + usb_storage.c \ + field-cache.h field-cache.c \ + garmin.h garmin.c garmin_parser.c \ + deepblu.h deepblu.c deepblu_parser.c \ + oceans_s1.h oceans_s1.c oceans_s1_parser.c + if OS_WIN32 libdivecomputer_la_SOURCES += serial_win32.c else diff --git a/src/atomics_cobalt.c b/src/atomics_cobalt.c index e907b77..a67ef82 100644 --- a/src/atomics_cobalt.c +++ b/src/atomics_cobalt.c @@ -19,19 +19,10 @@ * MA 02110-1301 USA */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - #include // memcmp, memcpy #include // malloc, free -#ifdef HAVE_LIBUSB -#ifdef _WIN32 -#define NOGDI -#endif -#include -#endif +#include #include "atomics_cobalt.h" #include "context-private.h" @@ -41,8 +32,6 @@ #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 COBALT1 0 #define COBALT2 2 @@ -58,10 +47,7 @@ typedef struct atomics_cobalt_device_t { dc_device_t base; -#ifdef HAVE_LIBUSB - libusb_context *context; - libusb_device_handle *handle; -#endif + dc_iostream_t *iostream; unsigned int simulation; unsigned char fingerprint[6]; unsigned char version[SZ_VERSION]; @@ -69,7 +55,6 @@ typedef struct 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 = { sizeof(atomics_cobalt_device_t), @@ -80,22 +65,19 @@ static const dc_device_vtable_t atomics_cobalt_device_vtable = { NULL, /* dump */ atomics_cobalt_device_foreach, /* foreach */ NULL, /* timesync */ - atomics_cobalt_device_close /* close */ + NULL /* close */ }; dc_status_t -atomics_cobalt_device_open (dc_device_t **out, dc_context_t *context) +atomics_cobalt_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) { -#ifdef HAVE_LIBUSB dc_status_t status = DC_STATUS_SUCCESS; atomics_cobalt_device_t *device = NULL; -#endif if (out == NULL) return DC_STATUS_INVALIDARGS; -#ifdef HAVE_LIBUSB // Allocate memory. device = (atomics_cobalt_device_t *) dc_device_allocate (context, &atomics_cobalt_device_vtable); if (device == NULL) { @@ -104,67 +86,30 @@ atomics_cobalt_device_open (dc_device_t **out, dc_context_t *context) } // Set the default values. - device->context = NULL; - device->handle = NULL; + device->iostream = iostream; 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."); - status = DC_STATUS_IO; + // Set the timeout for receiving data (2000 ms). + status = dc_iostream_set_timeout (device->iostream, TIMEOUT); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the timeout."); goto error_free; } - device->handle = libusb_open_device_with_vid_pid (device->context, VID, PID); - if (device->handle == NULL) { - ERROR (context, "Failed to open the usb device."); - status = DC_STATUS_IO; - goto error_usb_exit; - } - - rc = libusb_claim_interface (device->handle, 0); - if (rc < 0) { - ERROR (context, "Failed to claim the usb interface."); - status = DC_STATUS_IO; - goto error_usb_close; - } - 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."); - goto error_usb_close; + goto error_free; } *out = (dc_device_t*) device; return DC_STATUS_SUCCESS; -error_usb_close: - libusb_close (device->handle); -error_usb_exit: - libusb_exit (device->context); error_free: dc_device_deallocate ((dc_device_t *) device); return status; -#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 - - return DC_STATUS_SUCCESS; } @@ -202,6 +147,7 @@ atomics_cobalt_device_set_simulation (dc_device_t *abstract, unsigned int simula dc_status_t atomics_cobalt_device_version (dc_device_t *abstract, unsigned char data[], unsigned int size) { + dc_status_t status = DC_STATUS_SUCCESS; atomics_cobalt_device_t *device = (atomics_cobalt_device_t *) abstract; if (!ISINSTANCE (abstract)) @@ -210,31 +156,31 @@ atomics_cobalt_device_version (dc_device_t *abstract, unsigned char data[], unsi 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); - } + unsigned char bRequest = 0x01; + dc_usb_control_t control = { + DC_USB_REQUEST_VENDOR | DC_USB_RECIPIENT_DEVICE | DC_USB_ENDPOINT_OUT, /* bmRequestType */ + bRequest, /* bRequest */ + 0, /* wValue */ + 0, /* wIndex */ + 0, /* wLength */ + }; - HEXDUMP (abstract->context, DC_LOGLEVEL_INFO, "Write", &bRequest, 1); + status = dc_iostream_ioctl (device->iostream, DC_IOCTL_USB_CONTROL_WRITE, &control, sizeof(control)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the command."); + return status; + } // Receive the answer from the dive computer. - int length = 0; + size_t 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)) { + status = dc_iostream_read (device->iostream, packet, sizeof(packet), &length); + if (status != DC_STATUS_SUCCESS || length != sizeof (packet)) { ERROR (abstract->context, "Failed to receive the answer."); - return EXITCODE(rc); + return status; } - 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); @@ -246,16 +192,13 @@ atomics_cobalt_device_version (dc_device_t *abstract, unsigned char data[], unsi 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 + dc_status_t status = DC_STATUS_SUCCESS; atomics_cobalt_device_t *device = (atomics_cobalt_device_t *) abstract; if (device_is_cancelled (abstract)) @@ -268,35 +211,37 @@ atomics_cobalt_read_dive (dc_device_t *abstract, dc_buffer_t *buffer, int init, } // Send the command to the dive computer. - uint8_t bRequest = 0; + unsigned char 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); + dc_usb_control_t control = { + DC_USB_REQUEST_VENDOR | DC_USB_RECIPIENT_DEVICE | DC_USB_ENDPOINT_OUT, /* bmRequestType */ + bRequest, /* bRequest */ + 0, /* wValue */ + 0, /* wIndex */ + 0, /* wLength */ + }; + + status = dc_iostream_ioctl (device->iostream, DC_IOCTL_USB_CONTROL_WRITE, &control, sizeof(control)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the command."); + return status; + } unsigned int nbytes = 0; while (1) { // Receive the answer from the dive computer. - int length = 0; + size_t 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) { + status = dc_iostream_read (device->iostream, packet, sizeof(packet), &length); + if (status != DC_STATUS_SUCCESS && status != DC_STATUS_TIMEOUT) { ERROR (abstract->context, "Failed to receive the answer."); - return EXITCODE(rc); + return status; } - HEXDUMP (abstract->context, DC_LOGLEVEL_INFO, "Read", packet, length); - // Update and emit a progress event. if (progress) { progress->current += length; @@ -343,9 +288,6 @@ atomics_cobalt_read_dive (dc_device_t *abstract, dc_buffer_t *buffer, int init, dc_buffer_slice (buffer, 0, nbytes - 2); return DC_STATUS_SUCCESS; -#else - return DC_STATUS_UNSUPPORTED; -#endif } diff --git a/src/atomics_cobalt.h b/src/atomics_cobalt.h index 0308350..09f0226 100644 --- a/src/atomics_cobalt.h +++ b/src/atomics_cobalt.h @@ -23,6 +23,7 @@ #define ATOMICS_COBALT_H #include +#include #include #include #include @@ -32,7 +33,7 @@ extern "C" { #endif /* __cplusplus */ dc_status_t -atomics_cobalt_device_open (dc_device_t **device, dc_context_t *context); +atomics_cobalt_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); dc_status_t atomics_cobalt_parser_create (dc_parser_t **parser, dc_context_t *context); diff --git a/src/bluetooth.c b/src/bluetooth.c index 282a31a..1889bf7 100644 --- a/src/bluetooth.c +++ b/src/bluetooth.c @@ -79,7 +79,7 @@ static dc_status_t dc_bluetooth_iterator_free (dc_iterator_t *iterator); typedef struct dc_bluetooth_iterator_t { dc_iterator_t base; - dc_filter_t filter; + dc_descriptor_t *descriptor; #ifdef _WIN32 HANDLE hLookup; #else @@ -376,7 +376,7 @@ dc_bluetooth_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descri iterator->count = ndevices; iterator->current = 0; #endif - iterator->filter = dc_descriptor_get_filter (descriptor); + iterator->descriptor = descriptor; *out = (dc_iterator_t *) iterator; @@ -456,7 +456,7 @@ dc_bluetooth_iterator_next (dc_iterator_t *abstract, void *out) INFO (abstract->context, "Discover: address=" DC_ADDRESS_FORMAT ", name=%s", address, name ? name : ""); - if (iterator->filter && !iterator->filter (DC_TRANSPORT_BLUETOOTH, name)) { + if (!dc_descriptor_filter (iterator->descriptor, DC_TRANSPORT_BLUETOOTH, name, NULL)) { continue; } diff --git a/src/checksum.c b/src/checksum.c index 816c58d..9d06630 100644 --- a/src/checksum.c +++ b/src/checksum.c @@ -157,3 +157,48 @@ checksum_crc32 (const unsigned char data[], unsigned int size) return crc ^ 0xffffffff; } + +unsigned int +checksum_crc32b (const unsigned char data[], unsigned int size) +{ + static const unsigned int crc_table[] = { + 0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9, 0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005, + 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61, 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD, + 0x4C11DB70, 0x48D0C6C7, 0x4593E01E, 0x4152FDA9, 0x5F15ADAC, 0x5BD4B01B, 0x569796C2, 0x52568B75, + 0x6A1936C8, 0x6ED82B7F, 0x639B0DA6, 0x675A1011, 0x791D4014, 0x7DDC5DA3, 0x709F7B7A, 0x745E66CD, + 0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039, 0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5, + 0xBE2B5B58, 0xBAEA46EF, 0xB7A96036, 0xB3687D81, 0xAD2F2D84, 0xA9EE3033, 0xA4AD16EA, 0xA06C0B5D, + 0xD4326D90, 0xD0F37027, 0xDDB056FE, 0xD9714B49, 0xC7361B4C, 0xC3F706FB, 0xCEB42022, 0xCA753D95, + 0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1, 0xE13EF6F4, 0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D, + 0x34867077, 0x30476DC0, 0x3D044B19, 0x39C556AE, 0x278206AB, 0x23431B1C, 0x2E003DC5, 0x2AC12072, + 0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16, 0x018AEB13, 0x054BF6A4, 0x0808D07D, 0x0CC9CDCA, + 0x7897AB07, 0x7C56B6B0, 0x71159069, 0x75D48DDE, 0x6B93DDDB, 0x6F52C06C, 0x6211E6B5, 0x66D0FB02, + 0x5E9F46BF, 0x5A5E5B08, 0x571D7DD1, 0x53DC6066, 0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA, + 0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E, 0xBFA1B04B, 0xBB60ADFC, 0xB6238B25, 0xB2E29692, + 0x8AAD2B2F, 0x8E6C3698, 0x832F1041, 0x87EE0DF6, 0x99A95DF3, 0x9D684044, 0x902B669D, 0x94EA7B2A, + 0xE0B41DE7, 0xE4750050, 0xE9362689, 0xEDF73B3E, 0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2, + 0xC6BCF05F, 0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686, 0xD5B88683, 0xD1799B34, 0xDC3ABDED, 0xD8FBA05A, + 0x690CE0EE, 0x6DCDFD59, 0x608EDB80, 0x644FC637, 0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB, + 0x4F040D56, 0x4BC510E1, 0x46863638, 0x42472B8F, 0x5C007B8A, 0x58C1663D, 0x558240E4, 0x51435D53, + 0x251D3B9E, 0x21DC2629, 0x2C9F00F0, 0x285E1D47, 0x36194D42, 0x32D850F5, 0x3F9B762C, 0x3B5A6B9B, + 0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF, 0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623, + 0xF12F560E, 0xF5EE4BB9, 0xF8AD6D60, 0xFC6C70D7, 0xE22B20D2, 0xE6EA3D65, 0xEBA91BBC, 0xEF68060B, + 0xD727BBB6, 0xD3E6A601, 0xDEA580D8, 0xDA649D6F, 0xC423CD6A, 0xC0E2D0DD, 0xCDA1F604, 0xC960EBB3, + 0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7, 0xAE3AFBA2, 0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B, + 0x9B3660C6, 0x9FF77D71, 0x92B45BA8, 0x9675461F, 0x8832161A, 0x8CF30BAD, 0x81B02D74, 0x857130C3, + 0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640, 0x4E8EE645, 0x4A4FFBF2, 0x470CDD2B, 0x43CDC09C, + 0x7B827D21, 0x7F436096, 0x7200464F, 0x76C15BF8, 0x68860BFD, 0x6C47164A, 0x61043093, 0x65C52D24, + 0x119B4BE9, 0x155A565E, 0x18197087, 0x1CD86D30, 0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC, + 0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088, 0x2497D08D, 0x2056CD3A, 0x2D15EBE3, 0x29D4F654, + 0xC5A92679, 0xC1683BCE, 0xCC2B1D17, 0xC8EA00A0, 0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB, 0xDBEE767C, + 0xE3A1CBC1, 0xE760D676, 0xEA23F0AF, 0xEEE2ED18, 0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4, + 0x89B8FD09, 0x8D79E0BE, 0x803AC667, 0x84FBDBD0, 0x9ABC8BD5, 0x9E7D9662, 0x933EB0BB, 0x97FFAD0C, + 0xAFB010B1, 0xAB710D06, 0xA6322BDF, 0xA2F33668, 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4, + }; + + unsigned int crc = 0xffffffff; + for (unsigned int i = 0; i < size; ++i) + crc = crc_table[((crc >> 24) ^ data[i]) & 0xFF] ^ (crc << 8); + + return crc ^ 0xffffffff; +} diff --git a/src/checksum.h b/src/checksum.h index 9a53aa6..c8a9001 100644 --- a/src/checksum.h +++ b/src/checksum.h @@ -44,6 +44,9 @@ checksum_crc16_ccitt (const unsigned char data[], unsigned int size, unsigned sh unsigned int checksum_crc32 (const unsigned char data[], unsigned int size); +unsigned int +checksum_crc32b (const unsigned char data[], unsigned int size); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/descriptor-private.h b/src/descriptor-private.h index 5023f7f..d829422 100644 --- a/src/descriptor-private.h +++ b/src/descriptor-private.h @@ -33,10 +33,14 @@ typedef struct dc_usb_desc_t { unsigned short pid; } dc_usb_desc_t; -typedef int (*dc_filter_t) (dc_transport_t transport, const void *userdata); +typedef struct dc_usb_params_t { + unsigned int interface; + unsigned char endpoint_in; + unsigned char endpoint_out; +} dc_usb_params_t; -dc_filter_t -dc_descriptor_get_filter (dc_descriptor_t *descriptor); +int +dc_descriptor_filter (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata, void *params); #ifdef __cplusplus } diff --git a/src/descriptor.c b/src/descriptor.c index bd4458d..9b02949 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -36,22 +36,37 @@ values, \ C_ARRAY_SIZE(values) - isnullterminated, \ C_ARRAY_ITEMSIZE(values), \ - match) + match, \ + NULL, NULL, 0) + +#define DC_FILTER_INTERNAL_WITH_PARAMS(key, values, isnullterminated, match, params_dst, params_src) \ + dc_filter_internal( \ + key, \ + values, \ + C_ARRAY_SIZE(values) - isnullterminated, \ + C_ARRAY_ITEMSIZE(values), \ + match, \ + params_dst, params_src, sizeof *(params_src)) typedef int (*dc_match_t)(const void *, const void *); -static int dc_filter_uwatec (dc_transport_t transport, const void *userdata); -static int dc_filter_suunto (dc_transport_t transport, const void *userdata); -static int dc_filter_shearwater (dc_transport_t transport, const void *userdata); -static int dc_filter_hw (dc_transport_t transport, const void *userdata); -static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata); -static int dc_filter_garmin (dc_transport_t transport, const void *userdata); -static int dc_filter_mares (dc_transport_t transport, const void *userdata); -static int dc_filter_divesystem (dc_transport_t transport, const void *userdata); -static int dc_filter_oceanic (dc_transport_t transport, const void *userdata); -static int dc_filter_deepblu (dc_transport_t transport, const void *userdata); -static int dc_filter_mclean (dc_transport_t transport, const void *userdata); -static int dc_filter_oceans(dc_transport_t transport, const void *userdata); +typedef int (*dc_filter_t) (dc_transport_t transport, const void *userdata, void *params); + +static int dc_filter_uwatec (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_suunto (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_shearwater (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_hw (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_mares (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_divesystem (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_oceanic (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_mclean (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_atomic (dc_transport_t transport, const void *userdata, void *params); + +// Not merged upstream yet +static int dc_filter_garmin (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_deepblu (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_oceans(dc_transport_t transport, const void *userdata, void *params); static dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item); @@ -251,6 +266,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Oceanic", "Veo 4.0", DC_FAMILY_OCEANIC_ATOM2, 0x4654, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, {"Sherwood", "Wisdom 4", DC_FAMILY_OCEANIC_ATOM2, 0x4655, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, {"Oceanic", "Pro Plus 4", DC_FAMILY_OCEANIC_ATOM2, 0x4656, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, + {"Aqualung", "i470TC", DC_FAMILY_OCEANIC_ATOM2, 0x4743, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, /* Mares Nemo */ {"Mares", "Nemo", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Nemo Steel", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL}, @@ -316,8 +332,8 @@ static const dc_descriptor_t g_descriptors[] = { {"Dive Rite", "NiTek Trio", DC_FAMILY_ZEAGLE_N2ITION3, 0, DC_TRANSPORT_SERIAL, NULL}, {"Scubapro", "XTender 5", DC_FAMILY_ZEAGLE_N2ITION3, 0, DC_TRANSPORT_SERIAL, NULL}, /* Atomic Aquatics Cobalt */ - {"Atomic Aquatics", "Cobalt", DC_FAMILY_ATOMICS_COBALT, 0, DC_TRANSPORT_USB, NULL}, - {"Atomic Aquatics", "Cobalt 2", DC_FAMILY_ATOMICS_COBALT, 2, DC_TRANSPORT_USB, NULL}, + {"Atomic Aquatics", "Cobalt", DC_FAMILY_ATOMICS_COBALT, 0, DC_TRANSPORT_USB, dc_filter_atomic}, + {"Atomic Aquatics", "Cobalt 2", DC_FAMILY_ATOMICS_COBALT, 2, DC_TRANSPORT_USB, dc_filter_atomic}, /* Shearwater Predator */ {"Shearwater", "Predator", DC_FAMILY_SHEARWATER_PREDATOR, 2, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_shearwater}, /* Shearwater Petrel */ @@ -380,12 +396,19 @@ static const dc_descriptor_t g_descriptors[] = { {"Cochran", "EMC-20H", DC_FAMILY_COCHRAN_COMMANDER, 5, DC_TRANSPORT_SERIAL, NULL}, /* Tecdiving DiveComputer.eu */ {"Tecdiving", "DiveComputer.eu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_tecdiving}, + /* McLean Extreme */ + { "McLean", "Extreme", DC_FAMILY_MCLEAN_EXTREME, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_mclean}, + /* Liquivision */ + {"Liquivision", "Xen", DC_FAMILY_LIQUIVISION_LYNX, 0, DC_TRANSPORT_SERIAL, NULL}, + {"Liquivision", "Xeo", DC_FAMILY_LIQUIVISION_LYNX, 1, DC_TRANSPORT_SERIAL, NULL}, + {"Liquivision", "Lynx", DC_FAMILY_LIQUIVISION_LYNX, 2, DC_TRANSPORT_SERIAL, NULL}, + {"Liquivision", "Kaon", DC_FAMILY_LIQUIVISION_LYNX, 3, DC_TRANSPORT_SERIAL, NULL}, + + // Not merged upstream yet /* Garmin */ {"Garmin", "Descent Mk1", DC_FAMILY_GARMIN, 2859, DC_TRANSPORT_USBSTORAGE, dc_filter_garmin}, /* Deepblu */ {"Deepblu", "Cosmiq+", DC_FAMILY_DEEPBLU, 0, DC_TRANSPORT_BLE, dc_filter_deepblu}, - /* McLean Extreme */ - { "McLean", "Extreme", DC_FAMILY_MCLEAN_EXTREME, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_mclean}, /* Oceans S1 */ { "Oceans", "S1", DC_FAMILY_OCEANS_S1, 0, DC_TRANSPORT_BLE, dc_filter_oceans }, }; @@ -464,13 +487,16 @@ dc_match_oceanic (const void *key, const void *value) } static int -dc_filter_internal (const void *key, const void *values, size_t count, size_t size, dc_match_t match) +dc_filter_internal (const void *key, const void *values, size_t count, size_t size, dc_match_t match, void *params_dst, const void *params_src, size_t params_size) { if (key == NULL) return 0; for (size_t i = 0; i < count; ++i) { if (match (key, (const unsigned char *) values + i * size)) { + if (params_src && params_dst) { + memcpy (params_dst, params_src, params_size); + } return 1; } } @@ -485,7 +511,7 @@ static const char * const rfcomm[] = { NULL }; -static int dc_filter_uwatec (dc_transport_t transport, const void *userdata) +static int dc_filter_uwatec (dc_transport_t transport, const void *userdata, void *params) { static const char * const irda[] = { "Aladin Smart Com", @@ -520,7 +546,7 @@ static int dc_filter_uwatec (dc_transport_t transport, const void *userdata) return 1; } -static int dc_filter_suunto (dc_transport_t transport, const void *userdata) +static int dc_filter_suunto (dc_transport_t transport, const void *userdata, void *params) { static const dc_usb_desc_t usbhid[] = { {0x1493, 0x0030}, // Eon Steel @@ -542,7 +568,7 @@ static int dc_filter_suunto (dc_transport_t transport, const void *userdata) return 1; } -static int dc_filter_hw (dc_transport_t transport, const void *userdata) +static int dc_filter_hw (dc_transport_t transport, const void *userdata, void *params) { static const char * const bluetooth[] = { "OSTC", @@ -558,7 +584,7 @@ static int dc_filter_hw (dc_transport_t transport, const void *userdata) return 1; } -static int dc_filter_shearwater (dc_transport_t transport, const void *userdata) +static int dc_filter_shearwater (dc_transport_t transport, const void *userdata, void *params) { static const char * const bluetooth[] = { "Predator", @@ -578,7 +604,7 @@ static int dc_filter_shearwater (dc_transport_t transport, const void *userdata) return 1; } -static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata) +static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata, void *params) { static const char * const bluetooth[] = { "DiveComputer", @@ -593,20 +619,7 @@ static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata) return 1; } -static int dc_filter_garmin (dc_transport_t transport, const void *userdata) -{ - static const dc_usb_desc_t usbhid[] = { - {0x091e, 0x2b2b}, // Garmin Descent Mk1 - }; - - if (transport == DC_TRANSPORT_USBSTORAGE) { - return DC_FILTER_INTERNAL (userdata, usbhid, 0, dc_match_usb); - } - - return 1; -} - -static int dc_filter_mares (dc_transport_t transport, const void *userdata) +static int dc_filter_mares (dc_transport_t transport, const void *userdata, void *params) { static const char * const bluetooth[] = { "Mares bluelink pro", @@ -620,7 +633,7 @@ static int dc_filter_mares (dc_transport_t transport, const void *userdata) return 1; } -static int dc_filter_divesystem (dc_transport_t transport, const void *userdata) +static int dc_filter_divesystem (dc_transport_t transport, const void *userdata, void *params) { static const char * const bluetooth[] = { "DS", @@ -633,7 +646,7 @@ static int dc_filter_divesystem (dc_transport_t transport, const void *userdata) return 1; } -static int dc_filter_oceanic (dc_transport_t transport, const void *userdata) +static int dc_filter_oceanic (dc_transport_t transport, const void *userdata, void *params) { static const unsigned int model[] = { 0x4552, // Oceanic Pro Plus X @@ -645,6 +658,7 @@ static int dc_filter_oceanic (dc_transport_t transport, const void *userdata) 0x4654, // Oceanic Veo 4.0 0x4655, // Sherwood Wisdom 4 0x4656, // Oceanic Pro Plus 4 + 0x4743, // Aqualung i470TC }; if (transport == DC_TRANSPORT_BLE) { @@ -654,7 +668,53 @@ static int dc_filter_oceanic (dc_transport_t transport, const void *userdata) return 1; } -static int dc_filter_deepblu (dc_transport_t transport, const void *userdata) +static int dc_filter_mclean(dc_transport_t transport, const void *userdata, void *params) +{ + static const char * const bluetooth[] = { + "McLean Extreme", + }; + + if (transport == DC_TRANSPORT_BLUETOOTH) { + return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_name); + } else if (transport == DC_TRANSPORT_SERIAL) { + return DC_FILTER_INTERNAL(userdata, rfcomm, 1, dc_match_devname); + } + + return 1; +} + +static int dc_filter_atomic (dc_transport_t transport, const void *userdata, void *params) +{ + static const dc_usb_desc_t usb[] = { + {0x0471, 0x0888}, // Atomic Aquatics Cobalt + }; + + static const dc_usb_params_t usb_params = { + 0, 0x82, 0x02 + }; + + if (transport == DC_TRANSPORT_USB) { + return DC_FILTER_INTERNAL_WITH_PARAMS (userdata, usb, 0, dc_match_usb, params, &usb_params); + } + + return 1; +} + +// Not merged upstream yet +static int dc_filter_garmin (dc_transport_t transport, const void *userdata, void *params) +{ + static const dc_usb_desc_t usbhid[] = { + {0x091e, 0x2b2b}, // Garmin Descent Mk1 + }; + + if (transport == DC_TRANSPORT_USBSTORAGE) { + return DC_FILTER_INTERNAL (userdata, usbhid, 0, dc_match_usb); + } + + return 1; +} + +static int dc_filter_deepblu (dc_transport_t transport, const void *userdata, void *params) { static const char * const bluetooth[] = { "COSMIQ", @@ -667,22 +727,7 @@ static int dc_filter_deepblu (dc_transport_t transport, const void *userdata) return 1; } -static int dc_filter_mclean(dc_transport_t transport, const void *userdata) -{ - static const char * const bluetooth[] = { - "Extreme", - }; - - if (transport == DC_TRANSPORT_BLUETOOTH) { - return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_name); - } else if (transport == DC_TRANSPORT_SERIAL) { - return DC_FILTER_INTERNAL(userdata, rfcomm, 1, dc_match_devname); - } - - return 1; -} - -static int dc_filter_oceans(dc_transport_t transport, const void* userdata) +static int dc_filter_oceans(dc_transport_t transport, const void* userdata, void *params) { static const char* const ble[] = { "S1", @@ -786,11 +831,11 @@ dc_descriptor_get_transports (dc_descriptor_t *descriptor) return descriptor->transports; } -dc_filter_t -dc_descriptor_get_filter (dc_descriptor_t *descriptor) +int +dc_descriptor_filter (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata, void *params) { - if (descriptor == NULL) - return NULL; + if (descriptor == NULL || descriptor->filter == NULL) + return 1; - return descriptor->filter; + return descriptor->filter (transport, userdata, params); } diff --git a/src/device.c b/src/device.c index 17cc4ed..7c98605 100644 --- a/src/device.c +++ b/src/device.c @@ -57,9 +57,12 @@ #include "divesystem_idive.h" #include "cochran_commander.h" #include "tecdiving_divecomputereu.h" +#include "mclean_extreme.h" +#include "liquivision_lynx.h" + +// Not merged upstream yet #include "garmin.h" #include "deepblu.h" -#include "mclean_extreme.h" #include "oceans_s1.h" #include "device-private.h" @@ -192,7 +195,7 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr rc = zeagle_n2ition3_device_open (&device, context, iostream); break; case DC_FAMILY_ATOMICS_COBALT: - rc = atomics_cobalt_device_open (&device, context); + rc = atomics_cobalt_device_open (&device, context, iostream); break; case DC_FAMILY_SHEARWATER_PREDATOR: rc = shearwater_predator_device_open (&device, context, iostream); @@ -215,20 +218,25 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr case DC_FAMILY_TECDIVING_DIVECOMPUTEREU: rc = tecdiving_divecomputereu_device_open (&device, context, iostream); break; + case DC_FAMILY_MCLEAN_EXTREME: + rc = mclean_extreme_device_open (&device, context, iostream); + break; + case DC_FAMILY_LIQUIVISION_LYNX: + rc = liquivision_lynx_device_open (&device, context, iostream); + break; + default: + return DC_STATUS_INVALIDARGS; + + // Not merged upstream yet case DC_FAMILY_GARMIN: rc = garmin_device_open (&device, context, iostream); break; case DC_FAMILY_DEEPBLU: rc = deepblu_device_open (&device, context, iostream); break; - case DC_FAMILY_MCLEAN_EXTREME: - rc = mclean_extreme_device_open (&device, context, iostream); - break; case DC_FAMILY_OCEANS_S1: rc = oceans_s1_device_open(&device, context, iostream); break; - default: - return DC_STATUS_INVALIDARGS; } *out = device; diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 3e01f88..c669ea2 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -468,39 +468,25 @@ hw_ostc3_device_init_service (hw_ostc3_device_t *device) const unsigned char command[] = {S_INIT, 0xAB, 0xCD, 0xEF}; unsigned char answer[5] = {0}; - for (size_t i = 0; i < 4; ++i) { - // Send the command. - status = hw_ostc3_write (device, NULL, command + i, 1); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to send the command."); - return status; - } - - // Read the answer. - status = hw_ostc3_read (device, NULL, answer + i, 1); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to receive the answer."); - return status; - } - - // Verify the answer. - const unsigned char expected = (i == 0 ? 0x4B : command[i]); - if (answer[i] != expected) { - ERROR (abstract->context, "Unexpected answer byte."); - return DC_STATUS_PROTOCOL; - } - } - - // Read the ready byte. - status = hw_ostc3_read (device, NULL, answer + 4, 1); + // Send the command and service key. + status = hw_ostc3_write (device, NULL, command, sizeof (command)); if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to receive the ready byte."); + ERROR (abstract->context, "Failed to send the command."); return status; } - // Verify the ready byte. - if (answer[4] != S_READY) { - ERROR (abstract->context, "Unexpected ready byte."); + // Read the response. + status = hw_ostc3_read (device, NULL, answer, sizeof (answer)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the answer."); + return status; + } + + // Verify the response to service mode. + if (answer[0] != 0x4B || answer[1] != 0xAB || + answer[2] != 0xCD || answer[3] != 0xEF || + answer[4] != S_READY) { + ERROR (abstract->context, "Failed to verify the answer."); return DC_STATUS_PROTOCOL; } diff --git a/src/irda.c b/src/irda.c index 214e39f..448c57c 100644 --- a/src/irda.c +++ b/src/irda.c @@ -212,8 +212,6 @@ dc_irda_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descriptor_ S_CLOSE (fd); dc_socket_exit (context); - dc_filter_t filter = dc_descriptor_get_filter (descriptor); - unsigned int count = 0; #ifdef _WIN32 for (size_t i = 0; i < list->numDevice; ++i) { @@ -233,7 +231,7 @@ dc_irda_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descriptor_ INFO (context, "Discover: address=%08x, name=%s, charset=%02x, hints=%04x", address, name, charset, hints); - if (filter && !filter (DC_TRANSPORT_IRDA, name)) { + if (!dc_descriptor_filter (descriptor, DC_TRANSPORT_IRDA, name, NULL)) { continue; } diff --git a/src/libdivecomputer.symbols b/src/libdivecomputer.symbols index 96fe6fe..508097a 100644 --- a/src/libdivecomputer.symbols +++ b/src/libdivecomputer.symbols @@ -70,6 +70,12 @@ dc_irda_device_free dc_irda_iterator_new dc_irda_open +dc_usb_device_get_vid +dc_usb_device_get_pid +dc_usb_device_free +dc_usb_iterator_new +dc_usb_open + dc_usbhid_device_get_vid dc_usbhid_device_get_pid dc_usbhid_device_free diff --git a/src/liquivision_lynx.c b/src/liquivision_lynx.c new file mode 100644 index 0000000..b91b326 --- /dev/null +++ b/src/liquivision_lynx.c @@ -0,0 +1,677 @@ +/* + * libdivecomputer + * + * Copyright (C) 2020 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 +#include +#include + +#include + +#include "liquivision_lynx.h" +#include "context-private.h" +#include "device-private.h" +#include "ringbuffer.h" +#include "rbstream.h" +#include "checksum.h" +#include "array.h" + +#define ISINSTANCE(device) dc_device_isinstance((device), &liquivision_lynx_device_vtable) + +#define XEN 0 +#define XEO 1 +#define LYNX 2 +#define KAON 3 + +#define XEN_V1 0x83321485 // Not supported +#define XEN_V2 0x83321502 +#define XEN_V3 0x83328401 + +#define XEO_V1_A 0x17485623 +#define XEO_V1_B 0x27485623 +#define XEO_V2_A 0x17488401 +#define XEO_V2_B 0x27488401 +#define XEO_V3_A 0x17488402 +#define XEO_V3_B 0x27488402 + +#define LYNX_V1 0x67488403 +#define LYNX_V2 0x67488404 +#define LYNX_V3 0x67488405 + +#define KAON_V1 0x37488402 +#define KAON_V2 0x47488402 + +#define MAXRETRIES 2 +#define MAXPACKET 12 +#define SEGMENTSIZE 0x400 +#define PAGESIZE 0x1000 +#define MEMSIZE 0x200000 + +#define RB_LOGBOOK_BEGIN (1 * PAGESIZE) +#define RB_LOGBOOK_END (25 * PAGESIZE) +#define RB_LOGBOOK_SIZE (RB_LOGBOOK_END - RB_LOGBOOK_BEGIN) +#define RB_LOGBOOK_DISTANCE(a,b) ringbuffer_distance (a, b, 1, RB_LOGBOOK_BEGIN, RB_LOGBOOK_END) + +#define RB_PROFILE_BEGIN (25 * PAGESIZE) +#define RB_PROFILE_END (500 * PAGESIZE) +#define RB_PROFILE_SIZE (RB_PROFILE_END - RB_PROFILE_BEGIN) +#define RB_PROFILE_DISTANCE(a,b) ringbuffer_distance (a, b, 1, RB_PROFILE_BEGIN, RB_PROFILE_END) + +#define SZ_HEADER_XEN 80 +#define SZ_HEADER_OTHER 96 +#define SZ_HEADER_MAX SZ_HEADER_OTHER + +typedef struct liquivision_lynx_device_t { + dc_device_t base; + dc_iostream_t *iostream; + unsigned char fingerprint[4]; +} liquivision_lynx_device_t; + +static dc_status_t liquivision_lynx_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); +static dc_status_t liquivision_lynx_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size); +static dc_status_t liquivision_lynx_device_dump (dc_device_t *abstract, dc_buffer_t *buffer); +static dc_status_t liquivision_lynx_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +static dc_status_t liquivision_lynx_device_close (dc_device_t *abstract); + +static const dc_device_vtable_t liquivision_lynx_device_vtable = { + sizeof(liquivision_lynx_device_t), + DC_FAMILY_LIQUIVISION_LYNX, + liquivision_lynx_device_set_fingerprint, /* set_fingerprint */ + liquivision_lynx_device_read, /* read */ + NULL, /* write */ + liquivision_lynx_device_dump, /* dump */ + liquivision_lynx_device_foreach, /* foreach */ + NULL, /* timesync */ + liquivision_lynx_device_close /* close */ +}; + +static dc_status_t +liquivision_lynx_send (liquivision_lynx_device_t *device, const unsigned char data[], unsigned int size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + + if (size > MAXPACKET) + return DC_STATUS_INVALIDARGS; + + // Build the packet. + unsigned char packet[2 + MAXPACKET + 2] = {0}; + packet[0] = 0x00; + packet[1] = 0xB1; + if (size) { + memcpy (packet + 2, data, size); + } + packet[2 + size + 0] = 0x0B; + packet[2 + size + 1] = 0x0E; + + // Send the packet to the device. + status = dc_iostream_write (device->iostream, packet, size + 4, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the packet."); + return status; + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +liquivision_lynx_recv (liquivision_lynx_device_t *device, unsigned char data[], unsigned int size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + + if (size > SEGMENTSIZE) + return DC_STATUS_INVALIDARGS; + + // Receive the packet from the device. + unsigned char packet[1 + SEGMENTSIZE + 2] = {0}; + status = dc_iostream_read (device->iostream, packet, 1 + size + 2, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the packet."); + return status; + } + + // Verify the start byte. + if (packet[0] != 0xC5) { + ERROR (abstract->context, "Unexpected answer start byte (%02x).", packet[0]); + return DC_STATUS_PROTOCOL; + } + + // Verify the checksum. + unsigned short crc = array_uint16_be (packet + 1 + size); + unsigned short ccrc = checksum_crc16_ccitt (packet + 1, size, 0xffff); + if (crc != ccrc) { + ERROR (abstract->context, "Unexpected answer checksum (%04x %04x).", crc, ccrc); + return DC_STATUS_PROTOCOL; + } + + if (size) { + memcpy (data, packet + 1, size); + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +liquivision_lynx_packet (liquivision_lynx_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; + + status = liquivision_lynx_send (device, command, csize); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the command."); + return status; + } + + if (asize) { + status = liquivision_lynx_recv (device, answer, asize); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the answer."); + return status; + } + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +liquivision_lynx_transfer (liquivision_lynx_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 = liquivision_lynx_packet (device, command, csize, answer, asize)) != DC_STATUS_SUCCESS) { + if (rc != DC_STATUS_TIMEOUT && rc != DC_STATUS_PROTOCOL) + return rc; + + // Abort if the maximum number of retries is reached. + if (nretries++ >= MAXRETRIES) + return rc; + + // Delay the next attempt. + dc_iostream_sleep (device->iostream, 100); + dc_iostream_purge (device->iostream, DC_DIRECTION_INPUT); + } + + return DC_STATUS_SUCCESS; +} + +dc_status_t +liquivision_lynx_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) +{ + dc_status_t status = DC_STATUS_SUCCESS; + liquivision_lynx_device_t *device = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + device = (liquivision_lynx_device_t *) dc_device_allocate (context, &liquivision_lynx_device_vtable); + if (device == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Set the default values. + device->iostream = iostream; + memset (device->fingerprint, 0, sizeof (device->fingerprint)); + + // Set the serial communication protocol (9600 8N1). + status = dc_iostream_configure (device->iostream, 9600, 8, DC_PARITY_NONE, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the terminal attributes."); + goto error_free; + } + + // Set the timeout for receiving data (3000 ms). + status = dc_iostream_set_timeout (device->iostream, 3000); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the timeout."); + goto error_free; + } + + // Set the DTR line. + status = dc_iostream_set_dtr (device->iostream, 0); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the DTR line."); + goto error_free; + } + + // Set the RTS line. + status = dc_iostream_set_rts (device->iostream, 0); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the RTS line."); + goto error_free; + } + + // Make sure everything is in a sane state. + dc_iostream_sleep (device->iostream, 100); + dc_iostream_purge (device->iostream, DC_DIRECTION_ALL); + + // Wakeup the device. + for (unsigned int i = 0; i < 6000; ++i) { + const unsigned char init[] = {0xAA}; + dc_iostream_write (device->iostream, init, sizeof (init), NULL); + } + + *out = (dc_device_t *) device; + + return DC_STATUS_SUCCESS; + +error_free: + dc_device_deallocate ((dc_device_t *) device); + return status; +} + + +static dc_status_t +liquivision_lynx_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size) +{ + liquivision_lynx_device_t *device = (liquivision_lynx_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 +liquivision_lynx_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + liquivision_lynx_device_t *device = (liquivision_lynx_device_t *) abstract; + + if ((address % SEGMENTSIZE != 0) || + (size % SEGMENTSIZE != 0)) + return DC_STATUS_INVALIDARGS; + + // Get the page and segment number. + unsigned int page = (address / PAGESIZE); + unsigned int segment = (address % PAGESIZE) / SEGMENTSIZE; + + unsigned int nbytes = 0; + while (nbytes < size) { + const unsigned char command[] = { + 0x50, 0x41, 0x47, 0x45, + '0' + ((page / 100) % 10), + '0' + ((page / 10) % 10), + '0' + ((page / 1) % 10), + '0' + ((page / 100) % 10), + '0' + ((page / 10) % 10), + '0' + ((page / 1) % 10), + '0' + segment, + '0' + segment + }; + + status = liquivision_lynx_transfer (device, command, sizeof(command), data + nbytes, SEGMENTSIZE); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read page %u segment %u.", page, segment); + return status; + } + + nbytes += SEGMENTSIZE; + segment++; + if (segment == (PAGESIZE / SEGMENTSIZE)) { + segment = 0; + page++; + } + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +liquivision_lynx_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) +{ + // Allocate the required amount of memory. + if (!dc_buffer_resize (buffer, MEMSIZE)) { + ERROR (abstract->context, "Insufficient buffer space available."); + return DC_STATUS_NOMEMORY; + } + + return device_dump_read (abstract, dc_buffer_get_data (buffer), + dc_buffer_get_size (buffer), SEGMENTSIZE); +} + +static dc_status_t +liquivision_lynx_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) +{ + dc_status_t status = DC_STATUS_SUCCESS; + liquivision_lynx_device_t *device = (liquivision_lynx_device_t *) abstract; + + // Enable progress notifications. + dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; + progress.maximum = SEGMENTSIZE + RB_LOGBOOK_SIZE + RB_PROFILE_SIZE; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Send the info command. + unsigned char rsp_info[6] = {0}; + const unsigned char cmd_info[] = {0x49, 0x4E, 0x46, 0x4F, 0x49, 0x4E, 0x46, 0x4F, 0x49, 0x4E, 0x46, 0x4F}; + status = liquivision_lynx_transfer (device, cmd_info, sizeof(cmd_info), rsp_info, sizeof(rsp_info)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the info command."); + goto error_exit; + } + + // Get the model and version. + unsigned int model = array_uint16_le(rsp_info + 0); + unsigned int version = array_uint32_le(rsp_info + 2); + + // Send the more info command. + unsigned char rsp_more[12] = {0}; + const unsigned char cmd_more[] = {0x4D, 0x4F, 0x52, 0x45, 0x49, 0x4E, 0x46, 0x4F, 0x4D, 0x4F, 0x52, 0x45}; + status = liquivision_lynx_transfer (device, cmd_more, sizeof(cmd_more), rsp_more, sizeof(rsp_more)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the more info command."); + goto error_exit; + } + + // Emit a device info event. + dc_event_devinfo_t devinfo; + devinfo.model = model; + devinfo.firmware = 0; + devinfo.serial = array_uint32_le(rsp_more + 0); + device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); + + // Read the config segment. + unsigned char config[SEGMENTSIZE] = {0}; + status = liquivision_lynx_device_read (abstract, 0, config, sizeof (config)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the memory."); + goto error_exit; + } + + // Get the header size. + unsigned int headersize = (model == XEN) ? SZ_HEADER_XEN : SZ_HEADER_OTHER; + + // Get the number of headers per page. + unsigned int npages = PAGESIZE / headersize; + + // Get the logbook pointers. + unsigned int begin = array_uint16_le (config + 0x46); + unsigned int end = array_uint16_le (config + 0x48); + unsigned int rb_logbook_begin = RB_LOGBOOK_BEGIN + (begin / npages) * PAGESIZE + (begin % npages) * headersize; + unsigned int rb_logbook_end = RB_LOGBOOK_BEGIN + (end / npages) * PAGESIZE + (end % npages) * headersize; + if (rb_logbook_begin < RB_LOGBOOK_BEGIN || rb_logbook_begin > RB_LOGBOOK_END || + rb_logbook_end < RB_LOGBOOK_BEGIN || rb_logbook_end > RB_LOGBOOK_END) { + ERROR (abstract->context, "Invalid logbook pointers (%04x, %04x).", + rb_logbook_begin, rb_logbook_end); + status = DC_STATUS_DATAFORMAT; + goto error_exit; + } + + // Calculate the logbook size. +#if 0 + unsigned int rb_logbook_size = RB_LOGBOOK_DISTANCE (rb_logbook_begin, rb_logbook_end); +#else + // The logbook begin pointer is explicitly ignored, because it only takes + // into account dives for which the profile is still available. + unsigned int rb_logbook_size = RB_LOGBOOK_SIZE; +#endif + + // Get the profile pointers. + unsigned int rb_profile_begin = array_uint32_le (config + 0x4A); + unsigned int rb_profile_end = array_uint32_le (config + 0x4E); + if (rb_profile_begin < RB_PROFILE_BEGIN || rb_profile_begin > RB_PROFILE_END || + rb_profile_end < RB_PROFILE_BEGIN || rb_profile_end > RB_PROFILE_END) { + ERROR (abstract->context, "Invalid profile pointers (%04x, %04x).", + rb_profile_begin, rb_profile_end); + status = DC_STATUS_DATAFORMAT; + goto error_exit; + } + + // Update and emit a progress event. + progress.current += SEGMENTSIZE; + progress.maximum -= RB_LOGBOOK_SIZE - rb_logbook_size; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Allocate memory for the logbook entries. + unsigned char *logbook = (unsigned char *) malloc (rb_logbook_size); + if (logbook == NULL) { + status = DC_STATUS_NOMEMORY; + goto error_exit; + } + + // Create the ringbuffer stream. + dc_rbstream_t *rblogbook = NULL; + status = dc_rbstream_new (&rblogbook, abstract, SEGMENTSIZE, SEGMENTSIZE, RB_LOGBOOK_BEGIN, RB_LOGBOOK_END, rb_logbook_end); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to create the ringbuffer stream."); + goto error_free_logbook; + } + + // The logbook ringbuffer is read backwards to retrieve the most recent + // entries first. If an already downloaded entry is identified (by means + // of its fingerprint), the transfer is aborted immediately to reduce + // the transfer time. + unsigned int nbytes = 0; + unsigned int offset = rb_logbook_size; + unsigned int address = rb_logbook_end; + while (nbytes < rb_logbook_size) { + // Handle the ringbuffer wrap point. + if (address == RB_LOGBOOK_BEGIN) + address = RB_LOGBOOK_END; + + // Skip the padding bytes. + if ((address % PAGESIZE) == 0) { + unsigned int padding = PAGESIZE % headersize; + unsigned char dummy[SZ_HEADER_MAX] = {0}; + status = dc_rbstream_read (rblogbook, &progress, dummy, padding); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the memory."); + goto error_free_rblogbook; + } + + address -= padding; + nbytes += padding; + } + + // Move to the start of the current entry. + address -= headersize; + offset -= headersize; + + // Read the logbook entry. + status = dc_rbstream_read (rblogbook, &progress, logbook + offset, headersize); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the memory."); + goto error_free_rblogbook; + } + + nbytes += headersize; + + if (array_isequal (logbook + offset, headersize, 0xFF)) { + offset += headersize; + break; + } + + // Verify the checksum. + unsigned int unused = 2; + if (version == XEO_V1_A || version == XEO_V1_B) { + unused = 6; + } + unsigned char header[SZ_HEADER_MAX] = {0}; + memcpy (header + 0, rsp_info + 2, 4); + memcpy (header + 4, logbook + offset + 4, headersize - 4); + unsigned int crc = array_uint32_le (logbook + offset + 0); + unsigned int ccrc = checksum_crc32b (header, headersize - unused); + if (crc != ccrc) { + WARNING (abstract->context, "Invalid dive checksum (%08x %08x)", crc, ccrc); + status = DC_STATUS_DATAFORMAT; + goto error_free_rblogbook; + } + + // Compare the fingerprint to identify previously downloaded entries. + if (memcmp (logbook + offset, device->fingerprint, sizeof(device->fingerprint)) == 0) { + offset += headersize; + break; + } + } + + // Update and emit a progress event. + progress.maximum -= rb_logbook_size - nbytes; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Go through the logbook entries a first time, to calculate the total + // amount of bytes in the profile ringbuffer. + unsigned int rb_profile_size = 0; + + // Traverse the logbook ringbuffer backwards to retrieve the most recent + // dives first. The logbook ringbuffer is linearized at this point, so + // we do not have to take into account any memory wrapping near the end + // of the memory buffer. + unsigned int remaining = RB_PROFILE_SIZE; + unsigned int previous = rb_profile_end; + unsigned int entry = rb_logbook_size; + while (entry != offset) { + // Move to the start of the current entry. + entry -= headersize; + + // Get the profile pointer. + unsigned int current = array_uint32_le (logbook + entry + 16); + if (current < RB_PROFILE_BEGIN || current >= RB_PROFILE_END) { + ERROR (abstract->context, "Invalid profile ringbuffer pointer (%08x).", current); + status = DC_STATUS_DATAFORMAT; + goto error_free_rblogbook; + } + + // Calculate the length. + unsigned int length = RB_PROFILE_DISTANCE (current, previous); + + // Make sure the profile size is valid. + if (length > remaining) { + remaining = 0; + length = 0; + } + + // Update the total profile size. + rb_profile_size += length; + + // Move to the start of the current dive. + remaining -= length; + previous = current; + } + + // At this point, we know the exact amount of data + // that needs to be transferred for the profiles. + progress.maximum -= RB_PROFILE_SIZE - rb_profile_size; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Allocate memory for the profile data. + unsigned char *profile = (unsigned char *) malloc (headersize + rb_profile_size); + if (profile == NULL) { + status = DC_STATUS_NOMEMORY; + goto error_free_rblogbook; + } + + // Create the ringbuffer stream. + dc_rbstream_t *rbprofile = NULL; + status = dc_rbstream_new (&rbprofile, abstract, SEGMENTSIZE, SEGMENTSIZE, RB_PROFILE_BEGIN, RB_PROFILE_END, rb_profile_end); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to create the ringbuffer stream."); + goto error_free_profile; + } + + // Traverse the logbook ringbuffer backwards to retrieve the most recent + // dives first. The logbook ringbuffer is linearized at this point, so + // we do not have to take into account any memory wrapping near the end + // of the memory buffer. + remaining = rb_profile_size; + previous = rb_profile_end; + entry = rb_logbook_size; + while (entry != offset) { + // Move to the start of the current entry. + entry -= headersize; + + // Get the profile pointer. + unsigned int current = array_uint32_le (logbook + entry + 16); + if (current < RB_PROFILE_BEGIN || current >= RB_PROFILE_END) { + ERROR (abstract->context, "Invalid profile ringbuffer pointer (%08x).", current); + status = DC_STATUS_DATAFORMAT; + goto error_free_rbprofile; + } + + // Calculate the length. + unsigned int length = RB_PROFILE_DISTANCE (current, previous); + + // Make sure the profile size is valid. + if (length > remaining) { + remaining = 0; + length = 0; + } + + // Move to the start of the current dive. + remaining -= length; + previous = current; + + // Read the dive. + status = dc_rbstream_read (rbprofile, &progress, profile + remaining + headersize, length); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the dive."); + goto error_free_rbprofile; + } + + // Prepend the logbook entry to the profile data. The memory buffer is + // large enough to store this entry. The checksum is replaced with the + // flash version number. + memcpy (profile + remaining + 0, rsp_info + 2, 4); + memcpy (profile + remaining + 4, logbook + entry + 4, headersize - 4); + + if (callback && !callback (profile + remaining, headersize + length, logbook + entry, sizeof(device->fingerprint), userdata)) { + break; + } + } + +error_free_rbprofile: + dc_rbstream_free (rbprofile); +error_free_profile: + free (profile); +error_free_rblogbook: + dc_rbstream_free (rblogbook); +error_free_logbook: + free (logbook); +error_exit: + return status; +} + +static dc_status_t +liquivision_lynx_device_close (dc_device_t *abstract) +{ + dc_status_t status = DC_STATUS_SUCCESS; + liquivision_lynx_device_t *device = (liquivision_lynx_device_t*) abstract; + dc_status_t rc = DC_STATUS_SUCCESS; + + // Send the finish command. + const unsigned char cmd_finish[] = {0x46, 0x49, 0x4E, 0x49, 0x53, 0x48, 0x46, 0x49, 0x4E, 0x49, 0x53, 0x48}; + status = liquivision_lynx_transfer (device, cmd_finish, sizeof(cmd_finish), NULL, 0); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the finish command."); + dc_status_set_error(&status, rc); + } + + return status; +} diff --git a/src/liquivision_lynx.h b/src/liquivision_lynx.h new file mode 100644 index 0000000..52e7294 --- /dev/null +++ b/src/liquivision_lynx.h @@ -0,0 +1,43 @@ +/* + * libdivecomputer + * + * Copyright (C) 2020 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 + */ + +#ifndef LIQUIVISION_LYNX_H +#define LIQUIVISION_LYNX_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +dc_status_t +liquivision_lynx_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); + +dc_status_t +liquivision_lynx_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int model); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* LIQUIVISION_LYNX_H */ diff --git a/src/liquivision_lynx_parser.c b/src/liquivision_lynx_parser.c new file mode 100644 index 0000000..8489a50 --- /dev/null +++ b/src/liquivision_lynx_parser.c @@ -0,0 +1,596 @@ +/* + * libdivecomputer + * + * Copyright (C) 2020 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 +#include + +#include + +#include "liquivision_lynx.h" +#include "context-private.h" +#include "parser-private.h" +#include "array.h" + +#define ISINSTANCE(parser) dc_parser_isinstance((parser), &liquivision_lynx_parser_vtable) + +#define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array)) + +#define XEN 0 +#define XEO 1 +#define LYNX 2 +#define KAON 3 + +#define XEN_V1 0x83321485 // Not supported +#define XEN_V2 0x83321502 +#define XEN_V3 0x83328401 + +#define XEO_V1_A 0x17485623 +#define XEO_V1_B 0x27485623 +#define XEO_V2_A 0x17488401 +#define XEO_V2_B 0x27488401 +#define XEO_V3_A 0x17488402 +#define XEO_V3_B 0x27488402 + +#define LYNX_V1 0x67488403 +#define LYNX_V2 0x67488404 +#define LYNX_V3 0x67488405 + +#define KAON_V1 0x37488402 +#define KAON_V2 0x47488402 + +#define SZ_HEADER_XEN 80 +#define SZ_HEADER_OTHER 96 + +#define FRESH 0 +#define BRACKISH 1 +#define SALT 2 + +#define DECO 0 +#define GAUGE 1 +#define TEC 2 +#define REC 3 + +#define NORMAL 0 +#define BOOKMARK 1 +#define ALARM_DEPTH 2 +#define ALARM_TIME 3 +#define ALARM_VELOCITY 4 +#define DECOSTOP 5 +#define DECOSTOP_BREACHED 6 +#define GASMIX 7 +#define SETPOINT 8 +#define BAILOUT_ON 9 +#define BAILOUT_OFF 10 +#define EMERGENCY_ON 11 +#define EMERGENCY_OFF 12 +#define LOST_GAS 13 +#define SAFETY_STOP 14 +#define TANK_PRESSURE 15 +#define TANK_LIST 16 + +#define NGASMIXES 11 +#define NTANKS 11 + +#define INVALID 0xFFFFFFFF + +typedef struct liquivision_lynx_parser_t liquivision_lynx_parser_t; + +typedef struct liquivision_lynx_gasmix_t { + unsigned int oxygen; + unsigned int helium; +} liquivision_lynx_gasmix_t; + +typedef struct liquivision_lynx_tank_t { + unsigned int id; + unsigned int beginpressure; + unsigned int endpressure; +} liquivision_lynx_tank_t; + +struct liquivision_lynx_parser_t { + dc_parser_t base; + unsigned int model; + unsigned int headersize; + // Cached fields. + unsigned int cached; + unsigned int ngasmixes; + unsigned int ntanks; + liquivision_lynx_gasmix_t gasmix[NGASMIXES]; + liquivision_lynx_tank_t tank[NTANKS]; +}; + +static dc_status_t liquivision_lynx_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t liquivision_lynx_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); +static dc_status_t liquivision_lynx_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); +static dc_status_t liquivision_lynx_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); + +static const dc_parser_vtable_t liquivision_lynx_parser_vtable = { + sizeof(liquivision_lynx_parser_t), + DC_FAMILY_LIQUIVISION_LYNX, + liquivision_lynx_parser_set_data, /* set_data */ + liquivision_lynx_parser_get_datetime, /* datetime */ + liquivision_lynx_parser_get_field, /* fields */ + liquivision_lynx_parser_samples_foreach, /* samples_foreach */ + NULL /* destroy */ +}; + + +dc_status_t +liquivision_lynx_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model) +{ + liquivision_lynx_parser_t *parser = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + parser = (liquivision_lynx_parser_t *) dc_parser_allocate (context, &liquivision_lynx_parser_vtable); + if (parser == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Set the default values. + parser->model = model; + parser->headersize = (model == XEN) ? SZ_HEADER_XEN : SZ_HEADER_OTHER; + parser->cached = 0; + parser->ngasmixes = 0; + parser->ntanks = 0; + for (unsigned int i = 0; i < NGASMIXES; ++i) { + parser->gasmix[i].oxygen = 0; + parser->gasmix[i].helium = 0; + } + for (unsigned int i = 0; i < NTANKS; ++i) { + parser->tank[i].id = 0; + parser->tank[i].beginpressure = 0; + parser->tank[i].endpressure = 0; + } + + *out = (dc_parser_t *) parser; + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +liquivision_lynx_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) +{ + liquivision_lynx_parser_t *parser = (liquivision_lynx_parser_t *) abstract; + + // Reset the cache. + parser->cached = 0; + parser->ngasmixes = 0; + parser->ntanks = 0; + for (unsigned int i = 0; i < NGASMIXES; ++i) { + parser->gasmix[i].oxygen = 0; + parser->gasmix[i].helium = 0; + } + for (unsigned int i = 0; i < NTANKS; ++i) { + parser->tank[i].id = 0; + parser->tank[i].beginpressure = 0; + parser->tank[i].endpressure = 0; + } + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +liquivision_lynx_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) +{ + liquivision_lynx_parser_t *parser = (liquivision_lynx_parser_t *) abstract; + + if (abstract->size < parser->headersize) + return DC_STATUS_DATAFORMAT; + + const unsigned char *p = abstract->data + 40; + + if (datetime) { + datetime->year = array_uint16_le (p + 18); + datetime->month = array_uint16_le (p + 16) + 1; + datetime->day = array_uint16_le (p + 12) + 1; + datetime->hour = array_uint16_le (p + 8); + datetime->minute = array_uint16_le (p + 6); + datetime->second = array_uint16_le (p + 4); + datetime->timezone = DC_TIMEZONE_NONE; + } + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +liquivision_lynx_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) +{ + liquivision_lynx_parser_t *parser = (liquivision_lynx_parser_t *) abstract; + + if (abstract->size < parser->headersize) + return DC_STATUS_DATAFORMAT; + + if (!parser->cached) { + dc_status_t rc = liquivision_lynx_parser_samples_foreach (abstract, NULL, NULL); + if (rc != DC_STATUS_SUCCESS) + return rc; + } + + dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + dc_tank_t *tank = (dc_tank_t *) value; + dc_salinity_t *water = (dc_salinity_t *) value; + + if (value) { + switch (type) { + case DC_FIELD_DIVETIME: + *((unsigned int *) value) = array_uint32_le (abstract->data + 4); + break; + case DC_FIELD_MAXDEPTH: + *((double *) value) = array_uint16_le (abstract->data + 28) / 100.0; + break; + case DC_FIELD_AVGDEPTH: + *((double *) value) = array_uint16_le (abstract->data + 30) / 100.0; + break; + case DC_FIELD_TEMPERATURE_MINIMUM: + *((double *) value) = (signed short) array_uint16_le (abstract->data + 34) / 10.0; + break; + case DC_FIELD_TEMPERATURE_MAXIMUM: + *((double *) value) = (signed short) array_uint16_le (abstract->data + 36) / 10.0; + break; + case DC_FIELD_SALINITY: + switch (abstract->data[38]) { + case FRESH: + water->type = DC_WATER_FRESH; + water->density = 1000.0; + break; + case BRACKISH: + water->type = DC_WATER_SALT; + water->density = 1015.0; + break; + case SALT: + water->type = DC_WATER_SALT; + water->density = 1025.0; + break; + default: + return DC_STATUS_DATAFORMAT; + } + break; + case DC_FIELD_ATMOSPHERIC: + *((double *) value) = array_uint16_le (abstract->data + 26) / 1000.0; + break; + case DC_FIELD_DIVEMODE: + if (parser->model == XEN) { + *((unsigned int *) value) = DC_DIVEMODE_GAUGE; + } else { + switch (abstract->data[92] & 0x0F) { + case DECO: + case TEC: + case REC: + *((unsigned int *) value) = DC_DIVEMODE_OC; + break; + case GAUGE: + *((unsigned int *) value) = DC_DIVEMODE_GAUGE; + break; + default: + return DC_STATUS_DATAFORMAT; + } + } + break; + case DC_FIELD_GASMIX_COUNT: + *((unsigned int *) value) = parser->ngasmixes; + break; + case DC_FIELD_GASMIX: + gasmix->helium = parser->gasmix[flags].helium / 100.0; + gasmix->oxygen = parser->gasmix[flags].oxygen / 100.0; + gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; + break; + case DC_FIELD_TANK_COUNT: + *((unsigned int *) value) = parser->ntanks; + break; + case DC_FIELD_TANK: + tank->type = DC_TANKVOLUME_NONE; + tank->volume = 0.0; + tank->workpressure = 0.0; + tank->beginpressure = parser->tank[flags].beginpressure / 100.0; + tank->endpressure = parser->tank[flags].endpressure / 100.0; + tank->gasmix = DC_GASMIX_UNKNOWN; + break; + default: + return DC_STATUS_UNSUPPORTED; + } + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +liquivision_lynx_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +{ + liquivision_lynx_parser_t *parser = (liquivision_lynx_parser_t *) abstract; + const unsigned char *data = abstract->data; + unsigned int size = abstract->size; + + if (size < parser->headersize) + return DC_STATUS_DATAFORMAT; + + // Get the version. + unsigned int version = array_uint32_le(data); + + // Get the sample interval. + unsigned int interval_idx = data[39]; + const unsigned int intervals[] = {1, 2, 5, 10, 30, 60}; + if (interval_idx > C_ARRAY_SIZE(intervals)) { + ERROR (abstract->context, "Invalid sample interval index %u", interval_idx); + return DC_STATUS_DATAFORMAT; + } + unsigned int interval = intervals[interval_idx]; + + // Get the number of samples and events. + unsigned int nsamples = array_uint32_le (data + 8); + unsigned int nevents = array_uint32_le (data + 12); + + unsigned int ngasmixes = 0; + unsigned int ntanks = 0; + liquivision_lynx_gasmix_t gasmix[NGASMIXES] = {0}; + liquivision_lynx_tank_t tank[NTANKS] = {0}; + unsigned int o2_previous = INVALID, he_previous = INVALID; + unsigned int gasmix_idx = INVALID; + unsigned int have_gasmix = 0; + unsigned int tank_id_previous = INVALID; + unsigned int tank_idx = INVALID; + unsigned int pressure[NTANKS] = {0}; + unsigned int have_pressure = 0; + unsigned int setpoint = 0, have_setpoint = 0; + unsigned int deco = 0, have_deco = 0; + + unsigned int time = 0; + unsigned int samples = 0; + unsigned int events = 0; + unsigned int offset = parser->headersize; + while (offset + 2 <= size) { + dc_sample_value_t sample = {0}; + + unsigned int value = array_uint16_le (data + offset); + offset += 2; + + if (value & 0x8000) { + if (events >= nevents) { + break; + } + + if (offset + 4 > size) { + ERROR (abstract->context, "Buffer overflow at offset %u", offset); + return DC_STATUS_DATAFORMAT; + } + + unsigned int type = value & 0x7FFF; + unsigned int timestamp = array_uint32_le (data + offset + 2); + offset += 4; + + // Get the sample length. + unsigned int length = 0; + switch (type) { + case DECOSTOP: + case GASMIX: + length = 2; + break; + case SETPOINT: + length = 1; + break; + case TANK_LIST: + length = NTANKS * 2; + break; + case TANK_PRESSURE: + if (version == LYNX_V1) { + length = 4; + } else { + length = 6; + } + break; + default: + break; + } + + if (offset + length > size) { + ERROR (abstract->context, "Buffer overflow at offset %u", offset); + return DC_STATUS_DATAFORMAT; + } + + unsigned int o2 = 0, he = 0; + unsigned int tank_id = 0, tank_pressure = 0; + + switch (type) { + case NORMAL: + case BOOKMARK: + case ALARM_DEPTH: + case ALARM_TIME: + case ALARM_VELOCITY: + case DECOSTOP_BREACHED: + case BAILOUT_ON: + case BAILOUT_OFF: + case EMERGENCY_ON: + case EMERGENCY_OFF: + case LOST_GAS: + case SAFETY_STOP: + break; + case DECOSTOP: + deco = array_uint16_le (data + offset); + have_deco = 1; + break; + case GASMIX: + o2 = data[offset + 0]; + he = data[offset + 1]; + if (o2 != o2_previous || he != he_previous) { + // Find the gasmix in the list. + unsigned int i = 0; + while (i < ngasmixes) { + if (o2 == gasmix[i].oxygen && he == gasmix[i].helium) + break; + i++; + } + + // Add it to list if not found. + if (i >= ngasmixes) { + if (i >= NGASMIXES) { + ERROR (abstract->context, "Maximum number of gas mixes reached."); + return DC_STATUS_DATAFORMAT; + } + gasmix[i].oxygen = o2; + gasmix[i].helium = he; + ngasmixes = i + 1; + } + + o2_previous = o2; + he_previous = he; + gasmix_idx = i; + have_gasmix = 1; + } + break; + case SETPOINT: + setpoint = data[offset]; + have_setpoint = 1; + break; + case TANK_PRESSURE: + tank_id = array_uint16_le (data + offset + 0); + tank_pressure = array_uint16_le (data + offset + 2); + if (tank_id != tank_id_previous) { + // Find the tank in the list. + unsigned int i = 0; + while (i < ntanks) { + if (tank_id == tank[i].id) + break; + i++; + } + + // Add a new tank if necessary. + if (i >= ntanks) { + if (i >= NTANKS) { + ERROR (abstract->context, "Maximum number of tanks reached."); + return DC_STATUS_DATAFORMAT; + } + tank[i].id = tank_id; + tank[i].beginpressure = tank_pressure; + tank[i].endpressure = tank_pressure; + ntanks = i + 1; + } + + tank_id_previous = tank_id; + tank_idx = i; + } + tank[tank_idx].endpressure = tank_pressure; + pressure[tank_idx] = tank_pressure; + have_pressure |= 1 << tank_idx; + break; + case TANK_LIST: + break; + default: + WARNING (abstract->context, "Unknown event %u", type); + break; + } + + offset += length; + events++; + } else { + if (samples >= nsamples) { + break; + } + + // Get the sample length. + unsigned int length = 2; + if (version == XEO_V1_A || version == XEO_V2_A || + version == XEO_V3_A || version == KAON_V1 || + version == KAON_V2) { + length += 14; + } + + if (offset + length > size) { + ERROR (abstract->context, "Buffer overflow at offset %u", offset); + return DC_STATUS_DATAFORMAT; + } + + // Time (seconds). + time += interval; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + + // Depth (1/100 m). + sample.depth = value / 100.0; + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + + // Temperature (1/10 °C). + int temperature = (signed short) array_uint16_le (data + offset); + sample.temperature = temperature / 10.0; + if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); + + // Gas mix + if (have_gasmix) { + sample.gasmix = gasmix_idx; + if (callback) callback (DC_SAMPLE_GASMIX, sample, userdata); + have_gasmix = 0; + } + + // Setpoint (1/10 bar). + if (have_setpoint) { + sample.setpoint = setpoint / 10.0; + if (callback) callback (DC_SAMPLE_SETPOINT, sample, userdata); + have_setpoint = 0; + } + + // Tank pressure (1/100 bar). + if (have_pressure) { + for (unsigned int i = 0; i < ntanks; ++i) { + if (have_pressure & (1 << i)) { + sample.pressure.tank = i; + sample.pressure.value = pressure[i] / 100.0; + if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata); + } + } + have_pressure = 0; + } + + // Deco/ndl + if (have_deco) { + if (deco) { + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.depth = deco / 100.0; + } else { + sample.deco.type = DC_DECO_NDL; + sample.deco.depth = 0.0; + } + sample.deco.time = 0; + if (callback) callback (DC_SAMPLE_DECO, sample, userdata); + have_deco = 0; + } + + offset += length; + samples++; + } + } + + // Cache the data for later use. + for (unsigned int i = 0; i < ntanks; ++i) { + parser->tank[i] = tank[i]; + } + for (unsigned int i = 0; i < ngasmixes; ++i) { + parser->gasmix[i] = gasmix[i]; + } + parser->ngasmixes = ngasmixes; + parser->ntanks = ntanks; + parser->cached = 1; + + return DC_STATUS_SUCCESS; +} diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index a132b9e..366de96 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -176,6 +176,7 @@ static const oceanic_common_version_t oceanic_oc1_version[] = { {"AQUAI550 \0\0 1024"}, {"AQUA550C \0\0 1024"}, {"WISDOM04 \0\0 1024"}, + {"AQUA470C \0\0 1024"}, }; static const oceanic_common_version_t oceanic_oci_version[] = { diff --git a/src/oceanic_atom2_parser.c b/src/oceanic_atom2_parser.c index a53d2af..f582110 100644 --- a/src/oceanic_atom2_parser.c +++ b/src/oceanic_atom2_parser.c @@ -98,6 +98,7 @@ #define VEO40 0x4654 #define WISDOM4 0x4655 #define PROPLUS4 0x4656 +#define I470TC 0x4743 #define NORMAL 0 #define GAUGE 1 @@ -172,7 +173,7 @@ oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, unsigned model == I300 || model == I550 || model == I200 || model == I200C || model == I300C || model == GEO40 || - model == VEO40) { + model == VEO40 || model == I470TC) { parser->headersize -= PAGESIZE; } else if (model == VT4 || model == VT41) { parser->headersize += PAGESIZE; @@ -275,6 +276,7 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim case VISION: case XPAIR: case WISDOM4: + case I470TC: datetime->year = ((p[5] & 0xE0) >> 5) + ((p[7] & 0xE0) >> 2) + 2000; datetime->month = (p[3] & 0x0F); datetime->day = ((p[0] & 0x80) >> 3) + ((p[3] & 0xF0) >> 4); @@ -488,6 +490,10 @@ oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser) o2_offset = 0x30; ngasmixes = 4; o2_step = 2; + } else if (parser->model == I470TC) { + o2_offset = 0x28; + ngasmixes = 3; + o2_step = 2; } else if (parser->model == WISDOM4) { o2_offset = header + 4; ngasmixes = 1; @@ -739,7 +745,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == TX1 || parser->model == A300CS || parser->model == VTX || parser->model == I450T || parser->model == I750TC || parser->model == PROPLUSX || - parser->model == I770R) { + parser->model == I770R || parser->model == I470TC) { samplesize = PAGESIZE; } @@ -873,7 +879,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ } // Time. - if (parser->model == I450T) { + if (parser->model == I450T || parser->model == I470TC) { unsigned int minute = bcd2dec(data[offset + 0]); unsigned int hour = bcd2dec(data[offset + 1] & 0x0F); unsigned int second = bcd2dec(data[offset + 2]); @@ -914,7 +920,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I450T || parser->model == I300 || parser->model == I200 || parser->model == I100 || parser->model == I300C || parser->model == I200C || - parser->model == GEO40 || parser->model == VEO40) { + parser->model == GEO40 || parser->model == VEO40 || + parser->model == I470TC) { temperature = data[offset + 3]; } else if (parser->model == OCS || parser->model == TX1) { temperature = data[offset + 1]; @@ -956,7 +963,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ if (have_pressure) { if (parser->model == OC1A || parser->model == OC1B || parser->model == OC1C || parser->model == OCI || - parser->model == I450T) + parser->model == I450T || parser->model == I470TC) pressure = (data[offset + 10] + (data[offset + 11] << 8)) & 0x0FFF; else if (parser->model == VT4 || parser->model == VT41|| parser->model == ATOM3 || parser->model == ATOM31 || @@ -989,7 +996,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I450T || parser->model == I300 || parser->model == I200 || parser->model == I100 || parser->model == I300C || parser->model == I200C || - parser->model == GEO40 || parser->model == VEO40) + parser->model == GEO40 || parser->model == VEO40 || + parser->model == I470TC) depth = (data[offset + 4] + (data[offset + 5] << 8)) & 0x0FFF; else if (parser->model == ATOM1) depth = data[offset + 3] * 16; @@ -1043,7 +1051,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == OC1C || parser->model == OCI || parser->model == I100 || parser->model == I300C || parser->model == I450T || parser->model == I200C || - parser->model == GEO40 || parser->model == VEO40) { + parser->model == GEO40 || parser->model == VEO40 || + parser->model == I470TC) { decostop = (data[offset + 7] & 0xF0) >> 4; decotime = array_uint16_le(data + offset + 6) & 0x0FFF; have_deco = 1; @@ -1068,7 +1077,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ } else if (parser->model == I450T || parser->model == OC1A || parser->model == OC1B || parser->model == OC1C || parser->model == OCI || parser->model == PROPLUSX || - parser->model == I770R) { + parser->model == I770R || parser->model == I470TC) { rbt = array_uint16_le(data + offset + 8) & 0x01FF; have_rbt = 1; } else if (parser->model == VISION || parser->model == XPAIR || diff --git a/src/parser.c b/src/parser.c index e76cb63..4a6d0a6 100644 --- a/src/parser.c +++ b/src/parser.c @@ -57,9 +57,12 @@ #include "divesystem_idive.h" #include "cochran_commander.h" #include "tecdiving_divecomputereu.h" +#include "mclean_extreme.h" +#include "liquivision_lynx.h" + +// Not merged upstream yet #include "garmin.h" #include "deepblu.h" -#include "mclean_extreme.h" #include "oceans_s1.h" #include "context-private.h" @@ -176,20 +179,25 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa case DC_FAMILY_TECDIVING_DIVECOMPUTEREU: rc = tecdiving_divecomputereu_parser_create (&parser, context); break; + case DC_FAMILY_MCLEAN_EXTREME: + rc = mclean_extreme_parser_create (&parser, context); + break; + case DC_FAMILY_LIQUIVISION_LYNX: + rc = liquivision_lynx_parser_create (&parser, context, model); + break; + default: + return DC_STATUS_INVALIDARGS; + + // Not merged upstream yet case DC_FAMILY_GARMIN: rc = garmin_parser_create (&parser, context); break; case DC_FAMILY_DEEPBLU: rc = deepblu_parser_create (&parser, context); break; - case DC_FAMILY_MCLEAN_EXTREME: - rc = mclean_extreme_parser_create (&parser, context); - break; case DC_FAMILY_OCEANS_S1: rc = oceans_s1_parser_create(&parser, context); break; - default: - return DC_STATUS_INVALIDARGS; } *out = parser; diff --git a/src/serial_posix.c b/src/serial_posix.c index 897b428..4fbcb96 100644 --- a/src/serial_posix.c +++ b/src/serial_posix.c @@ -88,7 +88,7 @@ struct dc_serial_device_t { typedef struct dc_serial_iterator_t { dc_iterator_t base; - dc_filter_t filter; + dc_descriptor_t *descriptor; DIR *dp; } dc_serial_iterator_t; @@ -189,7 +189,7 @@ dc_serial_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descripto goto error_free; } - iterator->filter = dc_descriptor_get_filter (descriptor); + iterator->descriptor = descriptor; *out = (dc_iterator_t *) iterator; @@ -230,7 +230,7 @@ dc_serial_iterator_next (dc_iterator_t *abstract, void *out) return DC_STATUS_NOMEMORY; } - if (iterator->filter && !iterator->filter (DC_TRANSPORT_SERIAL, filename)) { + if (!dc_descriptor_filter (iterator->descriptor, DC_TRANSPORT_SERIAL, filename, NULL)) { continue; } diff --git a/src/serial_win32.c b/src/serial_win32.c index 3cb10ff..8427a25 100644 --- a/src/serial_win32.c +++ b/src/serial_win32.c @@ -57,7 +57,7 @@ struct dc_serial_device_t { typedef struct dc_serial_iterator_t { dc_iterator_t base; - dc_filter_t filter; + dc_descriptor_t *descriptor; HKEY hKey; DWORD count; DWORD current; @@ -180,7 +180,7 @@ dc_serial_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descripto } } - iterator->filter = dc_descriptor_get_filter (descriptor); + iterator->descriptor = descriptor; iterator->hKey = hKey; iterator->count = count; iterator->current = 0; @@ -226,7 +226,7 @@ dc_serial_iterator_next (dc_iterator_t *abstract, void *out) // Null terminate the string. data[data_len] = 0; - if (iterator->filter && !iterator->filter (DC_TRANSPORT_SERIAL, data)) { + if (!dc_descriptor_filter (iterator->descriptor, DC_TRANSPORT_SERIAL, data, NULL)) { continue; } diff --git a/src/usb.c b/src/usb.c new file mode 100644 index 0000000..e39bc48 --- /dev/null +++ b/src/usb.c @@ -0,0 +1,589 @@ +/* + * libdivecomputer + * + * Copyright (C) 2020 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 +#include + +#ifdef HAVE_LIBUSB +#ifdef _WIN32 +#define NOGDI +#endif +#include +#endif + +#include + +#include "common-private.h" +#include "context-private.h" +#include "iostream-private.h" +#include "descriptor-private.h" +#include "iterator-private.h" +#include "platform.h" + +#define ISINSTANCE(device) dc_iostream_isinstance((device), &dc_usb_vtable) + +typedef struct dc_usb_session_t { + size_t refcount; +#ifdef HAVE_LIBUSB + libusb_context *handle; +#endif +} dc_usb_session_t; + +struct dc_usb_device_t { + unsigned short vid, pid; + dc_usb_session_t *session; +#ifdef HAVE_LIBUSB + struct libusb_device *handle; + int interface; + unsigned char endpoint_in; + unsigned char endpoint_out; +#endif +}; + +#ifdef HAVE_LIBUSB +static dc_status_t dc_usb_iterator_next (dc_iterator_t *iterator, void *item); +static dc_status_t dc_usb_iterator_free (dc_iterator_t *iterator); + +static dc_status_t dc_usb_set_timeout (dc_iostream_t *iostream, int timeout); +static dc_status_t dc_usb_poll (dc_iostream_t *iostream, int timeout); +static dc_status_t dc_usb_read (dc_iostream_t *iostream, void *data, size_t size, size_t *actual); +static dc_status_t dc_usb_write (dc_iostream_t *iostream, const void *data, size_t size, size_t *actual); +static dc_status_t dc_usb_ioctl (dc_iostream_t *iostream, unsigned int request, void *data, size_t size); +static dc_status_t dc_usb_close (dc_iostream_t *iostream); + +typedef struct dc_usb_iterator_t { + dc_iterator_t base; + dc_descriptor_t *descriptor; + dc_usb_session_t *session; + struct libusb_device **devices; + size_t count; + size_t current; +} dc_usb_iterator_t; + +typedef struct dc_usb_t { + /* Base class. */ + dc_iostream_t base; + /* Internal state. */ + dc_usb_session_t *session; + libusb_device_handle *handle; + int interface; + unsigned char endpoint_in; + unsigned char endpoint_out; + unsigned int timeout; +} dc_usb_t; + +static const dc_iterator_vtable_t dc_usb_iterator_vtable = { + sizeof(dc_usb_iterator_t), + dc_usb_iterator_next, + dc_usb_iterator_free, +}; + +static const dc_iostream_vtable_t dc_usb_vtable = { + sizeof(dc_usb_t), + dc_usb_set_timeout, /* set_timeout */ + NULL, /* set_break */ + NULL, /* set_dtr */ + NULL, /* set_rts */ + NULL, /* get_lines */ + NULL, /* get_available */ + NULL, /* configure */ + dc_usb_poll, /* poll */ + dc_usb_read, /* read */ + dc_usb_write, /* write */ + dc_usb_ioctl, /* ioctl */ + NULL, /* flush */ + NULL, /* purge */ + NULL, /* sleep */ + dc_usb_close, /* close */ +}; + +static dc_status_t +syserror(int errcode) +{ + switch (errcode) { + case LIBUSB_ERROR_INVALID_PARAM: + return DC_STATUS_INVALIDARGS; + case LIBUSB_ERROR_NO_MEM: + return DC_STATUS_NOMEMORY; + case LIBUSB_ERROR_NO_DEVICE: + case LIBUSB_ERROR_NOT_FOUND: + return DC_STATUS_NODEVICE; + case LIBUSB_ERROR_ACCESS: + case LIBUSB_ERROR_BUSY: + return DC_STATUS_NOACCESS; + case LIBUSB_ERROR_TIMEOUT: + return DC_STATUS_TIMEOUT; + default: + return DC_STATUS_IO; + } +} + +static dc_status_t +dc_usb_session_new (dc_usb_session_t **out, dc_context_t *context) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_usb_session_t *session = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + session = (dc_usb_session_t *) malloc (sizeof(dc_usb_session_t)); + if (session == NULL) { + ERROR (context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_unlock; + } + + session->refcount = 1; + + int rc = libusb_init (&session->handle); + if (rc != LIBUSB_SUCCESS) { + ERROR (context, "Failed to initialize usb support (%s).", + libusb_error_name (rc)); + status = syserror (rc); + goto error_free; + } + + *out = session; + + return status; + +error_free: + free (session); +error_unlock: + return status; +} + +static dc_usb_session_t * +dc_usb_session_ref (dc_usb_session_t *session) +{ + if (session == NULL) + return NULL; + + session->refcount++; + + return session; +} + +static dc_status_t +dc_usb_session_unref (dc_usb_session_t *session) +{ + if (session == NULL) + return DC_STATUS_SUCCESS; + + if (--session->refcount == 0) { + libusb_exit (session->handle); + free (session); + } + + return DC_STATUS_SUCCESS; +} +#endif + +unsigned int +dc_usb_device_get_vid (dc_usb_device_t *device) +{ + if (device == NULL) + return 0; + + return device->vid; +} + +unsigned int +dc_usb_device_get_pid (dc_usb_device_t *device) +{ + if (device == NULL) + return 0; + + return device->pid; +} + +void +dc_usb_device_free(dc_usb_device_t *device) +{ + if (device == NULL) + return; + +#ifdef HAVE_LIBUSB + libusb_unref_device (device->handle); + dc_usb_session_unref (device->session); +#endif + free (device); +} + +dc_status_t +dc_usb_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descriptor_t *descriptor) +{ +#ifdef HAVE_LIBUSB + dc_status_t status = DC_STATUS_SUCCESS; + dc_usb_iterator_t *iterator = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + iterator = (dc_usb_iterator_t *) dc_iterator_allocate (context, &dc_usb_iterator_vtable); + if (iterator == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Initialize the usb library. + status = dc_usb_session_new (&iterator->session, context); + if (status != DC_STATUS_SUCCESS) { + goto error_free; + } + + // Enumerate the USB devices. + struct libusb_device **devices = NULL; + ssize_t ndevices = libusb_get_device_list (iterator->session->handle, &devices); + if (ndevices < 0) { + ERROR (context, "Failed to enumerate the usb devices (%s).", + libusb_error_name (ndevices)); + status = syserror (ndevices); + goto error_session_unref; + } + + iterator->devices = devices; + iterator->count = ndevices; + iterator->current = 0; + iterator->descriptor = descriptor; + + *out = (dc_iterator_t *) iterator; + + return DC_STATUS_SUCCESS; + +error_session_unref: + dc_usb_session_unref (iterator->session); +error_free: + dc_iterator_deallocate ((dc_iterator_t *) iterator); + return status; +#else + return DC_STATUS_UNSUPPORTED; +#endif +} + +#ifdef HAVE_LIBUSB +static dc_status_t +dc_usb_iterator_next (dc_iterator_t *abstract, void *out) +{ + dc_usb_iterator_t *iterator = (dc_usb_iterator_t *) abstract; + dc_usb_device_t *device = NULL; + + while (iterator->current < iterator->count) { + struct libusb_device *current = iterator->devices[iterator->current++]; + + // Get the device descriptor. + struct libusb_device_descriptor dev; + int rc = libusb_get_device_descriptor (current, &dev); + if (rc < 0) { + ERROR (abstract->context, "Failed to get the device descriptor (%s).", + libusb_error_name (rc)); + return syserror (rc); + } + + dc_usb_desc_t usb = {dev.idVendor, dev.idProduct}; + dc_usb_params_t params = {0, 0, 0}; + if (!dc_descriptor_filter (iterator->descriptor, DC_TRANSPORT_USB, &usb, ¶ms)) { + continue; + } + + // Get the active configuration descriptor. + struct libusb_config_descriptor *config = NULL; + rc = libusb_get_active_config_descriptor (current, &config); + if (rc != LIBUSB_SUCCESS) { + ERROR (abstract->context, "Failed to get the configuration descriptor (%s).", + libusb_error_name (rc)); + return syserror (rc); + } + + // Find the first matching interface. + const struct libusb_interface_descriptor *interface = NULL; + for (unsigned int i = 0; i < config->bNumInterfaces; i++) { + const struct libusb_interface *iface = &config->interface[i]; + for (int j = 0; j < iface->num_altsetting; j++) { + const struct libusb_interface_descriptor *desc = &iface->altsetting[j]; + if (interface == NULL && desc->bInterfaceNumber == params.interface) { + interface = desc; + } + } + } + + if (interface == NULL) { + libusb_free_config_descriptor (config); + continue; + } + + // Find the first matching input and output bulk endpoints. + const struct libusb_endpoint_descriptor *ep_in = NULL, *ep_out = NULL; + for (unsigned int i = 0; i < interface->bNumEndpoints; i++) { + const struct libusb_endpoint_descriptor *desc = &interface->endpoint[i]; + + unsigned int type = desc->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK; + unsigned int direction = desc->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK; + + if (type != LIBUSB_TRANSFER_TYPE_BULK) { + continue; + } + + if (ep_in == NULL && direction == LIBUSB_ENDPOINT_IN && + (params.endpoint_in == 0 || params.endpoint_in == desc->bEndpointAddress)) { + ep_in = desc; + } + + if (ep_out == NULL && direction == LIBUSB_ENDPOINT_OUT && + (params.endpoint_out == 0 || params.endpoint_out == desc->bEndpointAddress)) { + ep_out = desc; + } + } + + if (ep_in == NULL || ep_out == NULL) { + libusb_free_config_descriptor (config); + continue; + } + + device = (dc_usb_device_t *) malloc (sizeof(dc_usb_device_t)); + if (device == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + libusb_free_config_descriptor (config); + return DC_STATUS_NOMEMORY; + } + + device->session = dc_usb_session_ref (iterator->session); + device->vid = dev.idVendor; + device->pid = dev.idProduct; + device->handle = libusb_ref_device (current); + device->interface = interface->bInterfaceNumber; + device->endpoint_in = ep_in->bEndpointAddress; + device->endpoint_out = ep_out->bEndpointAddress; + + *(dc_usb_device_t **) out = device; + + libusb_free_config_descriptor (config); + + return DC_STATUS_SUCCESS; + } + + return DC_STATUS_DONE; +} + +static dc_status_t +dc_usb_iterator_free (dc_iterator_t *abstract) +{ + dc_usb_iterator_t *iterator = (dc_usb_iterator_t *) abstract; + + libusb_free_device_list (iterator->devices, 1); + dc_usb_session_unref (iterator->session); + + return DC_STATUS_SUCCESS; +} +#endif + +dc_status_t +dc_usb_open (dc_iostream_t **out, dc_context_t *context, dc_usb_device_t *device) +{ +#ifdef HAVE_LIBUSB + dc_status_t status = DC_STATUS_SUCCESS; + dc_usb_t *usb = NULL; + + if (out == NULL || device == NULL) + return DC_STATUS_INVALIDARGS; + + INFO (context, "Open: vid=%04x, pid=%04x, interface=%u, endpoints=%02x,%02x", + device->vid, device->pid, device->interface, device->endpoint_in, device->endpoint_out); + + // Allocate memory. + usb = (dc_usb_t *) dc_iostream_allocate (context, &dc_usb_vtable, DC_TRANSPORT_USB); + if (usb == NULL) { + ERROR (context, "Out of memory."); + return DC_STATUS_NOMEMORY; + } + + // Initialize the usb library. + usb->session = dc_usb_session_ref (device->session); + if (usb->session == NULL) { + goto error_free; + } + + // Open the USB device. + int rc = libusb_open (device->handle, &usb->handle); + if (rc != LIBUSB_SUCCESS) { + ERROR (context, "Failed to open the usb device (%s).", + libusb_error_name (rc)); + status = syserror (rc); + goto error_session_unref; + } + +#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000102) + libusb_set_auto_detach_kernel_driver (usb->handle, 1); +#endif + + // Claim the interface. + rc = libusb_claim_interface (usb->handle, device->interface); + if (rc != LIBUSB_SUCCESS) { + ERROR (context, "Failed to claim the usb interface (%s).", + libusb_error_name (rc)); + status = syserror (rc); + goto error_usb_close; + } + + usb->interface = device->interface; + usb->endpoint_in = device->endpoint_in; + usb->endpoint_out = device->endpoint_out; + usb->timeout = 0; + + *out = (dc_iostream_t *) usb; + + return DC_STATUS_SUCCESS; + +error_usb_close: + libusb_close (usb->handle); +error_session_unref: + dc_usb_session_unref (usb->session); +error_free: + dc_iostream_deallocate ((dc_iostream_t *) usb); + return status; +#else + return DC_STATUS_UNSUPPORTED; +#endif +} + +#ifdef HAVE_LIBUSB +static dc_status_t +dc_usb_close (dc_iostream_t *abstract) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_usb_t *usb = (dc_usb_t *) abstract; + + libusb_release_interface (usb->handle, usb->interface); + libusb_close (usb->handle); + dc_usb_session_unref (usb->session); + + return status; +} + +static dc_status_t +dc_usb_set_timeout (dc_iostream_t *abstract, int timeout) +{ + dc_usb_t *usb = (dc_usb_t *) abstract; + + if (timeout < 0) { + usb->timeout = 0; + } else if (timeout == 0) { + return DC_STATUS_UNSUPPORTED; + } else { + usb->timeout = timeout; + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +dc_usb_poll (dc_iostream_t *abstract, int timeout) +{ + return DC_STATUS_UNSUPPORTED; +} + +static dc_status_t +dc_usb_read (dc_iostream_t *abstract, void *data, size_t size, size_t *actual) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_usb_t *usb = (dc_usb_t *) abstract; + int nbytes = 0; + + int rc = libusb_bulk_transfer (usb->handle, usb->endpoint_in, data, size, &nbytes, usb->timeout); + if (rc != LIBUSB_SUCCESS || nbytes < 0) { + ERROR (abstract->context, "Usb read bulk transfer failed (%s).", + libusb_error_name (rc)); + status = syserror (rc); + if (nbytes < 0) + nbytes = 0; + goto out; + } + +out: + if (actual) + *actual = nbytes; + + return status; +} + +static dc_status_t +dc_usb_write (dc_iostream_t *abstract, const void *data, size_t size, size_t *actual) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_usb_t *usb = (dc_usb_t *) abstract; + int nbytes = 0; + + int rc = libusb_bulk_transfer (usb->handle, usb->endpoint_out, (void *) data, size, &nbytes, 0); + if (rc != LIBUSB_SUCCESS || nbytes < 0) { + ERROR (abstract->context, "Usb write bulk transfer failed (%s).", + libusb_error_name (rc)); + status = syserror (rc); + if (nbytes < 0) + nbytes = 0; + goto out; + } + +out: + if (actual) + *actual = nbytes; + + return status; +} + +static dc_status_t +dc_usb_ioctl_control (dc_iostream_t *abstract, void *data, size_t size) +{ + dc_usb_t *usb = (dc_usb_t *) abstract; + const dc_usb_control_t *control = (const dc_usb_control_t *) data; + + if (size < sizeof(control) || control->wLength > size - sizeof(control)) { + return DC_STATUS_INVALIDARGS; + } + + int rc = libusb_control_transfer (usb->handle, + control->bmRequestType, control->bRequest, control->wValue, control->wIndex, + (unsigned char *) data + sizeof(control), control->wLength, usb->timeout); + if (rc != LIBUSB_SUCCESS) { + ERROR (abstract->context, "Usb control transfer failed (%s).", + libusb_error_name (rc)); + return syserror (rc); + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +dc_usb_ioctl (dc_iostream_t *abstract, unsigned int request, void *data, size_t size) +{ + switch (request) { + case DC_IOCTL_USB_CONTROL_READ: + case DC_IOCTL_USB_CONTROL_WRITE: + return dc_usb_ioctl_control (abstract, data, size); + default: + return DC_STATUS_UNSUPPORTED; + } +} +#endif diff --git a/src/usbhid.c b/src/usbhid.c index e26d42b..735ceef 100644 --- a/src/usbhid.c +++ b/src/usbhid.c @@ -102,7 +102,7 @@ static dc_status_t dc_usbhid_close (dc_iostream_t *iostream); typedef struct dc_usbhid_iterator_t { dc_iterator_t base; - dc_filter_t filter; + dc_descriptor_t *descriptor; dc_usbhid_session_t *session; #if defined(USE_LIBUSB) struct libusb_device **devices; @@ -398,7 +398,7 @@ dc_usbhid_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descripto iterator->devices = devices; iterator->current = devices; #endif - iterator->filter = dc_descriptor_get_filter (descriptor); + iterator->descriptor = descriptor; *out = (dc_iterator_t *) iterator; @@ -435,7 +435,7 @@ dc_usbhid_iterator_next (dc_iterator_t *abstract, void *out) } dc_usb_desc_t usb = {dev.idVendor, dev.idProduct}; - if (iterator->filter && !iterator->filter (DC_TRANSPORT_USBHID, &usb)) { + if (!dc_descriptor_filter (iterator->descriptor, DC_TRANSPORT_USBHID, &usb, NULL)) { continue; } @@ -518,7 +518,7 @@ dc_usbhid_iterator_next (dc_iterator_t *abstract, void *out) iterator->current = current->next; dc_usb_desc_t usb = {current->vendor_id, current->product_id}; - if (iterator->filter && !iterator->filter (DC_TRANSPORT_USBHID, &usb)) { + if (!dc_descriptor_filter (iterator->descriptor, DC_TRANSPORT_USBHID, &usb)) { continue; } @@ -703,10 +703,12 @@ dc_usbhid_read (dc_iostream_t *abstract, void *data, size_t size, size_t *actual #if defined(USE_LIBUSB) int rc = libusb_interrupt_transfer (usbhid->handle, usbhid->endpoint_in, data, size, &nbytes, usbhid->timeout); - if (rc != LIBUSB_SUCCESS) { + if (rc != LIBUSB_SUCCESS || nbytes < 0) { ERROR (abstract->context, "Usb read interrupt transfer failed (%s).", libusb_error_name (rc)); status = syserror (rc); + if (nbytes < 0) + nbytes = 0; goto out; } #elif defined(USE_HIDAPI) @@ -749,10 +751,12 @@ dc_usbhid_write (dc_iostream_t *abstract, const void *data, size_t size, size_t } int rc = libusb_interrupt_transfer (usbhid->handle, usbhid->endpoint_out, (void *) buffer, length, &nbytes, 0); - if (rc != LIBUSB_SUCCESS) { + if (rc != LIBUSB_SUCCESS || nbytes < 0) { ERROR (abstract->context, "Usb write interrupt transfer failed (%s).", libusb_error_name (rc)); status = syserror (rc); + if (nbytes < 0) + nbytes = 0; goto out; } diff --git a/src/uwatec_smart.c b/src/uwatec_smart.c index 23ce6b9..5918646 100644 --- a/src/uwatec_smart.c +++ b/src/uwatec_smart.c @@ -477,8 +477,8 @@ uwatec_smart_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_ goto error_free; } - // Set the timeout for receiving data (3000ms). - status = dc_iostream_set_timeout (device->iostream, 3000); + // Set the timeout for receiving data (5000ms). + status = dc_iostream_set_timeout (device->iostream, 5000); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to set the timeout."); goto error_free;