diff --git a/configure.ac b/configure.ac index 4c868f7..ad8fd06 100644 --- a/configure.ac +++ b/configure.ac @@ -111,16 +111,36 @@ AS_IF([test "x$with_hidapi" != "xno"], [ ]) ]) +# Checks for BlueZ (bluetooth) support. +AC_ARG_WITH([bluez], + [AS_HELP_STRING([--without-bluez], + [Build without the BlueZ library])], + [], [with_bluez=auto]) +AS_IF([test "x$with_bluez" != "xno"], [ + PKG_CHECK_MODULES([BLUEZ], [bluez], [have_bluez=yes], [have_bluez=no]) + AS_IF([test "x$have_bluez" = "xyes"], [ + AC_DEFINE([HAVE_BLUEZ], [1], [BlueZ library]) + DEPENDENCIES="$DEPENDENCIES bluez" + ]) +]) + AC_SUBST([DEPENDENCIES]) -# Checks for IrDA support. -AC_CHECK_HEADERS([winsock2.h af_irda.h], [irda_win32=yes], [irda_win32=no], [ +# Checks for Windows bluetooth support. +AC_CHECK_HEADERS([winsock2.h ws2bth.h], , , [ #if HAVE_WINSOCK2_H # include # endif ]) -AC_CHECK_HEADERS([sys/socket.h linux/types.h linux/irda.h], [irda_linux=yes], [irda_linux=no], [ +# Checks for IrDA support. +AC_CHECK_HEADERS([winsock2.h af_irda.h], , , [ +#if HAVE_WINSOCK2_H +# include +# endif +]) + +AC_CHECK_HEADERS([sys/socket.h linux/types.h linux/irda.h], , , [ #if HAVE_SYS_SOCKET_H # include # endif @@ -129,12 +149,6 @@ AC_CHECK_HEADERS([sys/socket.h linux/types.h linux/irda.h], [irda_linux=yes], [i # endif ]) -if test "$irda_win32" = "yes" || test "$irda_linux" = "yes"; then - AC_DEFINE([HAVE_IRDA], [1], [IrDA support]) -fi - -AM_CONDITIONAL([IRDA], [test "$irda_win32" = "yes" || test "$irda_linux" = "yes"]) - # Checks for header files. AC_CHECK_HEADERS([linux/serial.h]) AC_CHECK_HEADERS([IOKit/serial/ioss.h]) diff --git a/examples/common.c b/examples/common.c index 72dd517..1024fde 100644 --- a/examples/common.c +++ b/examples/common.c @@ -54,8 +54,8 @@ static const backend_table_t g_backends[] = { {"aladin", DC_FAMILY_UWATEC_ALADIN, 0x3F}, {"memomouse", DC_FAMILY_UWATEC_MEMOMOUSE, 0}, {"smart", DC_FAMILY_UWATEC_SMART, 0x10}, - {"g2", DC_FAMILY_SCUBAPRO_G2, 0x11}, {"meridian", DC_FAMILY_UWATEC_MERIDIAN, 0x20}, + {"g2", DC_FAMILY_UWATEC_G2, 0x11}, {"sensus", DC_FAMILY_REEFNET_SENSUS, 1}, {"sensuspro", DC_FAMILY_REEFNET_SENSUSPRO, 2}, {"sensusultra", DC_FAMILY_REEFNET_SENSUSULTRA, 3}, diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h index fdaaa1b..293de3b 100644 --- a/include/libdivecomputer/common.h +++ b/include/libdivecomputer/common.h @@ -59,8 +59,7 @@ typedef enum dc_family_t { DC_FAMILY_UWATEC_MEMOMOUSE, DC_FAMILY_UWATEC_SMART, DC_FAMILY_UWATEC_MERIDIAN, - /* We'll enumerate the Scubapro G2 under Uwatec */ - DC_FAMILY_SCUBAPRO_G2, + DC_FAMILY_UWATEC_G2, /* Oceanic */ DC_FAMILY_OCEANIC_VTPRO = (4 << 16), DC_FAMILY_OCEANIC_VEO250, diff --git a/include/libdivecomputer/descriptor.h b/include/libdivecomputer/descriptor.h index 1df5b12..8601b0f 100644 --- a/include/libdivecomputer/descriptor.h +++ b/include/libdivecomputer/descriptor.h @@ -33,7 +33,9 @@ typedef enum dc_transport_t { DC_TRANSPORT_NONE, DC_TRANSPORT_SERIAL, DC_TRANSPORT_USB, - DC_TRANSPORT_IRDA + DC_TRANSPORT_USBHID, + DC_TRANSPORT_IRDA, + DC_TRANSPORT_BLUETOOTH } dc_transport_t; typedef struct dc_descriptor_t dc_descriptor_t; diff --git a/msvc/libdivecomputer.vcproj b/msvc/libdivecomputer.vcproj index 260ecbf..f7b7883 100644 --- a/msvc/libdivecomputer.vcproj +++ b/msvc/libdivecomputer.vcproj @@ -42,7 +42,7 @@ Name="VCCLCompilerTool" Optimization="0" AdditionalIncludeDirectories="..\include" - PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBDIVECOMPUTER_EXPORTS;ENABLE_LOGGING;HAVE_IRDA" + PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBDIVECOMPUTER_EXPORTS;ENABLE_LOGGING;HAVE_AF_IRDA_H;HAVE_WS2BTH_H" MinimalRebuild="true" BasicRuntimeChecks="3" RuntimeLibrary="3" @@ -119,7 +119,7 @@ Optimization="2" EnableIntrinsicFunctions="true" AdditionalIncludeDirectories="..\include" - PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBDIVECOMPUTER_EXPORTS;ENABLE_LOGGING;HAVE_IRDA" + PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBDIVECOMPUTER_EXPORTS;ENABLE_LOGGING;HAVE_AF_IRDA_H;HAVE_WS2BTH_H" RuntimeLibrary="2" EnableFunctionLevelLinking="true" UsePrecompiledHeader="0" @@ -194,6 +194,10 @@ RelativePath="..\src\atomics_cobalt_parser.c" > + + @@ -524,6 +528,10 @@ RelativePath="..\include\libdivecomputer\atomics_cobalt.h" > + + diff --git a/src/Makefile.am b/src/Makefile.am index 0d60abb..1856483 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,15 +1,16 @@ AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include -AM_CFLAGS = $(LIBUSB_CFLAGS) $(HIDAPI_CFLAGS) +AM_CFLAGS = $(LIBUSB_CFLAGS) $(HIDAPI_CFLAGS) $(BLUEZ_CFLAGS) lib_LTLIBRARIES = libdivecomputer.la -libdivecomputer_la_LIBADD = $(LIBUSB_LIBS) $(HIDAPI_LIBS) -lm -lz +libdivecomputer_la_LIBADD = $(LIBUSB_LIBS) $(HIDAPI_LIBS) $(BLUEZ_LIBS) -lm -lz libdivecomputer_la_LDFLAGS = \ -version-info $(DC_VERSION_LIBTOOL) \ -no-undefined \ -export-symbols libdivecomputer.exp if OS_WIN32 +libdivecomputer_la_LIBADD += -lws2_32 libdivecomputer_la_LDFLAGS += -Wc,-static-libgcc endif @@ -75,16 +76,9 @@ else libdivecomputer_la_SOURCES += serial.h serial_posix.c endif -if IRDA -if OS_WIN32 -libdivecomputer_la_LIBADD += -lws2_32 -endif libdivecomputer_la_SOURCES += irda.h irda.c -else -libdivecomputer_la_SOURCES += irda.h irda_dummy.c -endif - libdivecomputer_la_SOURCES += usbhid.h usbhid.c +libdivecomputer_la_SOURCES += bluetooth.h bluetooth.c if OS_WIN32 libdivecomputer_la_SOURCES += libdivecomputer.rc diff --git a/src/array.c b/src/array.c index 13a73e5..5574083 100644 --- a/src/array.c +++ b/src/array.c @@ -198,6 +198,13 @@ array_uint32_le (const unsigned char data[]) } +unsigned int +array_uint32_word_be (const unsigned char data[]) +{ + return data[1] + (data[0] << 8) + (data[3] << 16) + (data[2] << 24); +} + + void array_uint32_le_set (unsigned char data[], const unsigned int input) { diff --git a/src/array.h b/src/array.h index cd0a3a1..da70efa 100644 --- a/src/array.h +++ b/src/array.h @@ -64,6 +64,9 @@ array_uint32_be (const unsigned char data[]); unsigned int array_uint32_le (const unsigned char data[]); +unsigned int +array_uint32_word_be (const unsigned char data[]); + void array_uint32_le_set (unsigned char data[], const unsigned int input); diff --git a/src/bluetooth.c b/src/bluetooth.c new file mode 100644 index 0000000..bc9ffe0 --- /dev/null +++ b/src/bluetooth.c @@ -0,0 +1,604 @@ +/* + * libdivecomputer + * + * Copyright (C) 2013 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 // malloc, free + +#ifdef _WIN32 +#define NOGDI +#include +#include +#ifdef HAVE_WS2BTH_H +#define BLUETOOTH +#include +#endif +#else +#include // errno +#include // close +#include // socket, getsockopt +#include // socket, getsockopt +#include // select +#include // ioctl +#include +#ifdef HAVE_BLUEZ +#define BLUETOOTH +#include +#include +#include +#include +#endif +#endif + +#include "bluetooth.h" +#include "common-private.h" +#include "context-private.h" + +#ifdef _WIN32 +typedef int s_ssize_t; +typedef DWORD s_errcode_t; +#define S_ERRNO WSAGetLastError () +#define S_EINTR WSAEINTR +#define S_EAGAIN WSAEWOULDBLOCK +#define S_ENOMEM WSA_NOT_ENOUGH_MEMORY +#define S_EINVAL WSAEINVAL +#define S_EACCES WSAEACCES +#define S_EAFNOSUPPORT WSAEAFNOSUPPORT +#define S_INVALID INVALID_SOCKET +#define S_IOCTL ioctlsocket +#define S_CLOSE closesocket +#else +typedef ssize_t s_ssize_t; +typedef int s_errcode_t; +#define S_ERRNO errno +#define S_EINTR EINTR +#define S_EAGAIN EAGAIN +#define S_ENOMEM ENOMEM +#define S_EINVAL EINVAL +#define S_EACCES EACCES +#define S_EAFNOSUPPORT EAFNOSUPPORT +#define S_INVALID -1 +#define S_IOCTL ioctl +#define S_CLOSE close +#endif + +#ifdef _WIN32 +#define DC_ADDRESS_FORMAT "%012I64X" +#else +#define DC_ADDRESS_FORMAT "%012llX" +#endif + +#define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array)) + +#define MAX_DEVICES 255 +#define MAX_PERIODS 8 + +struct dc_bluetooth_t { + dc_context_t *context; +#ifdef _WIN32 + SOCKET fd; +#else + int fd; +#endif + int timeout; +}; + +#ifdef BLUETOOTH +static dc_status_t +syserror(s_errcode_t errcode) +{ + switch (errcode) { + case S_EINVAL: + return DC_STATUS_INVALIDARGS; + case S_ENOMEM: + return DC_STATUS_NOMEMORY; + case S_EACCES: + return DC_STATUS_NOACCESS; + case S_EAFNOSUPPORT: + return DC_STATUS_UNSUPPORTED; + default: + return DC_STATUS_IO; + } +} +#endif + +#ifdef HAVE_BLUEZ +static dc_bluetooth_address_t +dc_address_get (const bdaddr_t *ba) +{ + dc_bluetooth_address_t address = 0; + + size_t shift = 0; + for (size_t i = 0; i < C_ARRAY_SIZE(ba->b); ++i) { + address |= (dc_bluetooth_address_t) ba->b[i] << shift; + shift += 8; + } + + return address; +} + +static void +dc_address_set (bdaddr_t *ba, dc_bluetooth_address_t address) +{ + size_t shift = 0; + for (size_t i = 0; i < C_ARRAY_SIZE(ba->b); ++i) { + ba->b[i] = (address >> shift) & 0xFF; + shift += 8; + } +} +#endif + +dc_status_t +dc_bluetooth_open (dc_bluetooth_t **out, dc_context_t *context) +{ +#ifdef BLUETOOTH + dc_status_t status = DC_STATUS_SUCCESS; + dc_bluetooth_t *device = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + device = (dc_bluetooth_t *) malloc (sizeof (dc_bluetooth_t)); + if (device == NULL) { + SYSERROR (context, S_ENOMEM); + return DC_STATUS_NOMEMORY; + } + + // Library context. + device->context = context; + + // Default to blocking reads. + device->timeout = -1; + +#ifdef _WIN32 + // Initialize the winsock dll. + WSADATA wsaData; + WORD wVersionRequested = MAKEWORD (2, 2); + int rc = WSAStartup (wVersionRequested, &wsaData); + if (rc != 0) { + SYSERROR (context, rc); + status = DC_STATUS_UNSUPPORTED; + goto error_free; + } + + // Confirm that the winsock dll supports version 2.2. + // Note that if the dll supports versions greater than 2.2 in addition to + // 2.2, it will still return 2.2 since that is the version we requested. + if (LOBYTE (wsaData.wVersion) != 2 || + HIBYTE (wsaData.wVersion) != 2) { + ERROR (context, "Incorrect winsock version."); + status = DC_STATUS_UNSUPPORTED; + goto error_wsacleanup; + } +#endif + + // Open the socket. +#ifdef _WIN32 + device->fd = socket (AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM); +#else + device->fd = socket (AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); +#endif + if (device->fd == S_INVALID) { + s_errcode_t errcode = S_ERRNO; + SYSERROR (context, errcode); + status = syserror(errcode); + goto error_wsacleanup; + } + + *out = device; + + return DC_STATUS_SUCCESS; + +error_wsacleanup: +#ifdef _WIN32 + WSACleanup (); +error_free: +#endif + free (device); + return status; +#else + return DC_STATUS_UNSUPPORTED; +#endif +} + +dc_status_t +dc_bluetooth_close (dc_bluetooth_t *device) +{ +#ifdef BLUETOOTH + dc_status_t status = DC_STATUS_SUCCESS; + + if (device == NULL) + return DC_STATUS_SUCCESS; + + // Terminate all send and receive operations. + shutdown (device->fd, 0); + + // Close the socket. + if (S_CLOSE (device->fd) != 0) { + s_errcode_t errcode = S_ERRNO; + SYSERROR (device->context, errcode); + dc_status_set_error(&status, syserror(errcode)); + } + +#ifdef _WIN32 + // Terminate the winsock dll. + if (WSACleanup () != 0) { + s_errcode_t errcode = S_ERRNO; + SYSERROR (device->context, errcode); + dc_status_set_error(&status, syserror(errcode)); + } +#endif + + // Free memory. + free (device); + + return status; +#else + return DC_STATUS_UNSUPPORTED; +#endif +} + +dc_status_t +dc_bluetooth_set_timeout (dc_bluetooth_t *device, int timeout) +{ +#ifdef BLUETOOTH + if (device == NULL) + return DC_STATUS_INVALIDARGS; + + INFO (device->context, "Timeout: value=%i", timeout); + + device->timeout = timeout; + + return DC_STATUS_SUCCESS; +#else + return DC_STATUS_UNSUPPORTED; +#endif +} + +dc_status_t +dc_bluetooth_discover (dc_bluetooth_t *device, dc_bluetooth_callback_t callback, void *userdata) +{ +#ifdef BLUETOOTH + dc_status_t status = DC_STATUS_SUCCESS; + + if (device == NULL) + return DC_STATUS_INVALIDARGS; + +#ifdef _WIN32 + WSAQUERYSET wsaq; + memset(&wsaq, 0, sizeof (wsaq)); + wsaq.dwSize = sizeof (wsaq); + wsaq.dwNameSpace = NS_BTH; + wsaq.lpcsaBuffer = NULL; + + HANDLE hLookup; + if (WSALookupServiceBegin(&wsaq, LUP_CONTAINERS | LUP_FLUSHCACHE, &hLookup) != 0) { + s_errcode_t errcode = S_ERRNO; + if (errcode == WSASERVICE_NOT_FOUND) { + // No remote bluetooth devices found. + status = DC_STATUS_SUCCESS; + } else { + SYSERROR (device->context, errcode); + status = syserror(errcode); + } + goto error_exit; + } + + unsigned char buf[4096]; + LPWSAQUERYSET pwsaResults = (LPWSAQUERYSET) buf; + memset(pwsaResults, 0, sizeof(WSAQUERYSET)); + pwsaResults->dwSize = sizeof(WSAQUERYSET); + pwsaResults->dwNameSpace = NS_BTH; + pwsaResults->lpBlob = NULL; + + while (1) { + DWORD dwSize = sizeof(buf); + if (WSALookupServiceNext (hLookup, LUP_RETURN_NAME | LUP_RETURN_ADDR, &dwSize, pwsaResults) != 0) { + s_errcode_t errcode = S_ERRNO; + if (errcode == WSA_E_NO_MORE || errcode == WSAENOMORE) { + break; // No more results. + } + SYSERROR (device->context, errcode); + status = syserror(errcode); + goto error_close; + } + + if (pwsaResults->dwNumberOfCsAddrs == 0 || + pwsaResults->lpcsaBuffer == NULL || + pwsaResults->lpcsaBuffer->RemoteAddr.lpSockaddr == NULL) { + ERROR (device->context, "Invalid results returned"); + status = DC_STATUS_IO; + goto error_close; + } + + SOCKADDR_BTH *sa = (SOCKADDR_BTH *) pwsaResults->lpcsaBuffer->RemoteAddr.lpSockaddr; + dc_bluetooth_address_t address = sa->btAddr; + const char *name = (char *) pwsaResults->lpszServiceInstanceName; + + INFO (device->context, "Discover: address=" DC_ADDRESS_FORMAT ", name=%s", address, name); + + if (callback) callback (address, name, userdata); + + } + +error_close: + WSALookupServiceEnd (hLookup); +#else + // Get the resource number for the first available bluetooth adapter. + int dev = hci_get_route (NULL); + if (dev < 0) { + s_errcode_t errcode = S_ERRNO; + SYSERROR (device->context, errcode); + status = syserror(errcode); + goto error_exit; + } + + // Open a socket to the bluetooth adapter. + int fd = hci_open_dev (dev); + if (fd < 0) { + s_errcode_t errcode = S_ERRNO; + SYSERROR (device->context, errcode); + status = syserror(errcode); + goto error_exit; + } + + // Allocate a buffer to store the results of the discovery. + inquiry_info *devices = (inquiry_info *) malloc (MAX_DEVICES * sizeof(inquiry_info)); + if (devices == NULL) { + s_errcode_t errcode = S_ERRNO; + SYSERROR (device->context, errcode); + status = syserror(errcode); + goto error_close; + } + + // Perform the bluetooth device discovery. The inquiry lasts for at + // most MAX_PERIODS * 1.28 seconds, and at most MAX_DEVICES devices + // will be returned. + int ndevices = hci_inquiry (dev, MAX_PERIODS, MAX_DEVICES, NULL, &devices, IREQ_CACHE_FLUSH); + if (ndevices < 0) { + s_errcode_t errcode = S_ERRNO; + SYSERROR (device->context, errcode); + status = syserror(errcode); + goto error_free; + } + + for (unsigned int i = 0; i < ndevices; ++i) { + dc_bluetooth_address_t address = dc_address_get (&devices[i].bdaddr); + + // Get the user friendly name. + char buf[HCI_MAX_NAME_LENGTH], *name = buf; + int rc = hci_read_remote_name (fd, &devices[i].bdaddr, sizeof(buf), buf, 0); + if (rc < 0) { + name = NULL; + } + + INFO (device->context, "Discover: address=" DC_ADDRESS_FORMAT ", name=%s", address, name); + + if (callback) callback (address, name, userdata); + } + +error_free: + free(devices); +error_close: + hci_close_dev(fd); +#endif + +error_exit: + return status; +#else + return DC_STATUS_UNSUPPORTED; +#endif +} + +dc_status_t +dc_bluetooth_connect (dc_bluetooth_t *device, dc_bluetooth_address_t address, unsigned int port) +{ +#ifdef BLUETOOTH + if (device == NULL) + return DC_STATUS_INVALIDARGS; + + INFO (device->context, "Connect: address=" DC_ADDRESS_FORMAT ", port=%d", address, port); + +#ifdef _WIN32 + SOCKADDR_BTH sa; + sa.addressFamily = AF_BTH; + sa.btAddr = address; + sa.port = port; + memset(&sa.serviceClassId, 0, sizeof(sa.serviceClassId)); +#else + struct sockaddr_rc sa; + sa.rc_family = AF_BLUETOOTH; + sa.rc_channel = port; + dc_address_set (&sa.rc_bdaddr, address); +#endif + + if (connect (device->fd, (struct sockaddr *) &sa, sizeof (sa)) != 0) { + s_errcode_t errcode = S_ERRNO; + SYSERROR (device->context, errcode); + return syserror(errcode); + } + + return DC_STATUS_SUCCESS; +#else + return DC_STATUS_UNSUPPORTED; +#endif +} + +dc_status_t +dc_bluetooth_get_available (dc_bluetooth_t *device, size_t *value) +{ +#ifdef BLUETOOTH + if (device == NULL) + return DC_STATUS_INVALIDARGS; + +#ifdef _WIN32 + unsigned long bytes = 0; +#else + int bytes = 0; +#endif + + if (S_IOCTL (device->fd, FIONREAD, &bytes) != 0) { + s_errcode_t errcode = S_ERRNO; + SYSERROR (device->context, errcode); + return syserror(errcode); + } + + if (value) + *value = bytes; + + return DC_STATUS_SUCCESS; +#else + return DC_STATUS_UNSUPPORTED; +#endif +} + +dc_status_t +dc_bluetooth_read (dc_bluetooth_t *device, void *data, size_t size, size_t *actual) +{ +#ifdef BLUETOOTH + dc_status_t status = DC_STATUS_SUCCESS; + size_t nbytes = 0; + + if (device == NULL) { + status = DC_STATUS_INVALIDARGS; + goto out_invalidargs; + } + + while (nbytes < size) { + fd_set fds; + FD_ZERO (&fds); + FD_SET (device->fd, &fds); + + struct timeval tvt; + if (device->timeout > 0) { + tvt.tv_sec = (device->timeout / 1000); + tvt.tv_usec = (device->timeout % 1000) * 1000; + } else if (device->timeout == 0) { + timerclear (&tvt); + } + + int rc = select (device->fd + 1, &fds, NULL, NULL, device->timeout >= 0 ? &tvt : NULL); + if (rc < 0) { + s_errcode_t errcode = S_ERRNO; + if (errcode == S_EINTR) + continue; // Retry. + SYSERROR (device->context, errcode); + status = syserror(errcode); + goto out; + } else if (rc == 0) { + break; // Timeout. + } + + s_ssize_t n = recv (device->fd, (char*) data + nbytes, size - nbytes, 0); + if (n < 0) { + s_errcode_t errcode = S_ERRNO; + if (errcode == S_EINTR || errcode == S_EAGAIN) + continue; // Retry. + SYSERROR (device->context, errcode); + status = syserror(errcode); + goto out; + } else if (n == 0) { + break; // EOF reached. + } + + nbytes += n; + } + + if (nbytes != size) { + status = DC_STATUS_TIMEOUT; + } + +out: + HEXDUMP (device->context, DC_LOGLEVEL_INFO, "Read", (unsigned char *) data, nbytes); + +out_invalidargs: + if (actual) + *actual = nbytes; + + return status; +#else + return DC_STATUS_UNSUPPORTED; +#endif +} + +dc_status_t +dc_bluetooth_write (dc_bluetooth_t *device, const void *data, size_t size, size_t *actual) +{ +#ifdef BLUETOOTH + dc_status_t status = DC_STATUS_SUCCESS; + size_t nbytes = 0; + + if (device == NULL) { + status = DC_STATUS_INVALIDARGS; + goto out_invalidargs; + } + + while (nbytes < size) { + fd_set fds; + FD_ZERO (&fds); + FD_SET (device->fd, &fds); + + int rc = select (device->fd + 1, NULL, &fds, NULL, NULL); + if (rc < 0) { + s_errcode_t errcode = S_ERRNO; + if (errcode == S_EINTR) + continue; // Retry. + SYSERROR (device->context, errcode); + status = syserror(errcode); + goto out; + } else if (rc == 0) { + break; // Timeout. + } + + s_ssize_t n = send (device->fd, (char*) data + nbytes, size - nbytes, 0); + if (n < 0) { + s_errcode_t errcode = S_ERRNO; + if (errcode == S_EINTR || errcode == S_EAGAIN) + continue; // Retry. + SYSERROR (device->context, errcode); + status = syserror(errcode); + goto out; + } else if (n == 0) { + break; // EOF. + } + + nbytes += n; + } + + if (nbytes != size) { + status = DC_STATUS_TIMEOUT; + } + +out: + HEXDUMP (device->context, DC_LOGLEVEL_INFO, "Write", (unsigned char *) data, nbytes); + +out_invalidargs: + if (actual) + *actual = nbytes; + + return status; +#else + return DC_STATUS_UNSUPPORTED; +#endif +} diff --git a/src/bluetooth.h b/src/bluetooth.h new file mode 100644 index 0000000..d2e3868 --- /dev/null +++ b/src/bluetooth.h @@ -0,0 +1,174 @@ +/* + * libdivecomputer + * + * Copyright (C) 2013 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_BLUETOOTH_H +#define DC_BLUETOOTH_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * Opaque object representing a bluetooth connection. + */ +typedef struct dc_bluetooth_t dc_bluetooth_t; + +/** + * Bluetooth address (48 bits). + */ +#if defined (_WIN32) && !defined (__GNUC__) +typedef unsigned __int64 dc_bluetooth_address_t; +#else +typedef unsigned long long dc_bluetooth_address_t; +#endif + +/** + * Bluetooth enumeration callback. + * + * @param[in] address The bluetooth device address. + * @param[in] name The bluetooth device name. + * @param[in] userdata The user data pointer. + */ +typedef void (*dc_bluetooth_callback_t) (dc_bluetooth_address_t address, const char *name, void *userdata); + +/** + * Open an bluetooth connection. + * + * @param[out] bluetooth A location to store the bluetooth connection. + * @param[in] context A valid context object. + * @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code + * on failure. + */ +dc_status_t +dc_bluetooth_open (dc_bluetooth_t **bluetooth, dc_context_t *context); + +/** + * Close the bluetooth connection and free all resources. + * + * @param[in] bluetooth A valid bluetooth connection. + * @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code + * on failure. + */ +dc_status_t +dc_bluetooth_close (dc_bluetooth_t *bluetooth); + +/** + * Set the read timeout. + * + * There are three distinct modes available: + * + * 1. Blocking (timeout < 0): + * + * The read operation is blocked until all the requested bytes have + * been received. If the requested number of bytes does not arrive, + * the operation will block forever. + * + * 2. Non-blocking (timeout == 0): + * + * The read operation returns immediately with the bytes that have + * already been received, even if no bytes have been received. + * + * 3. Timeout (timeout > 0): + * + * The read operation is blocked until all the requested bytes have + * been received. If the requested number of bytes does not arrive + * within the specified amount of time, the operation will return + * with the bytes that have already been received. + * + * @param[in] bluetooth A valid bluetooth connection. + * @param[in] timeout The timeout in milliseconds. + * @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code + * on failure. + */ +dc_status_t +dc_bluetooth_set_timeout (dc_bluetooth_t *bluetooth, int timeout); + +/** + * Enumerate the bluetooth devices. + * + * @param[in] bluetooth A valid bluetooth connection. + * @param[in] callback The callback function to call. + * @param[in] userdata User data to pass to the callback function. + * @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code + * on failure. + */ +dc_status_t +dc_bluetooth_discover (dc_bluetooth_t *bluetooth, dc_bluetooth_callback_t callback, void *userdata); + +/** + * Connect to an bluetooth device. + * + * @param[in] bluetooth A valid bluetooth connection. + * @param[in] address The bluetooth device address. + * @param[in] port The bluetooth port number. + * @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code + * on failure. + */ +dc_status_t +dc_bluetooth_connect (dc_bluetooth_t *bluetooth, dc_bluetooth_address_t address, unsigned int port); + +/** + * Query the number of available bytes in the input buffer. + * + * @param[in] bluetooth A valid bluetooth connection. + * @param[out] value A location to store the number of bytes in + * the input buffer. + * @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code + * on failure. + */ +dc_status_t +dc_bluetooth_get_available (dc_bluetooth_t *bluetooth, size_t *value); + +/** + * Read data from the bluetooth connection. + * + * @param[in] bluetooth A valid bluetooth connection. + * @param[out] data The memory buffer to read the data into. + * @param[in] size The number of bytes to read. + * @param[out] actual An (optional) location to store the actual + * number of bytes transferred. + * @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code + * on failure. + */ +dc_status_t +dc_bluetooth_read (dc_bluetooth_t *bluetooth, void *data, size_t size, size_t *actual); + +/** + * Write data to the bluetooth connection. + * + * @param[in] bluetooth A valid bluetooth connection. + * @param[in] data The memory buffer to write the data from. + * @param[in] size The number of bytes to write. + * @param[out] actual An (optional) location to store the actual + * number of bytes transferred. + * @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code + * on failure. + */ +dc_status_t +dc_bluetooth_write (dc_bluetooth_t *bluetooth, const void *data, size_t size, size_t *actual); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* DC_BLUETOOTH_H */ diff --git a/src/cochran_commander.c b/src/cochran_commander.c index 99fe369..1ad38f0 100644 --- a/src/cochran_commander.c +++ b/src/cochran_commander.c @@ -28,31 +28,36 @@ #include "device-private.h" #include "serial.h" #include "array.h" +#include "ringbuffer.h" #define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array)) -#define COCHRAN_MODEL_COMMANDER_AIR_NITROX 0 -#define COCHRAN_MODEL_EMC_14 1 -#define COCHRAN_MODEL_EMC_16 2 -#define COCHRAN_MODEL_EMC_20 3 +#define MAXRETRIES 2 + +#define COCHRAN_MODEL_COMMANDER_PRE21000 0 +#define COCHRAN_MODEL_COMMANDER_AIR_NITROX 1 +#define COCHRAN_MODEL_EMC_14 2 +#define COCHRAN_MODEL_EMC_16 3 +#define COCHRAN_MODEL_EMC_20 4 typedef enum cochran_endian_t { ENDIAN_LE, ENDIAN_BE, + ENDIAN_WORD_BE, } cochran_endian_t; typedef struct cochran_commander_model_t { - unsigned char id[8 + 1]; + unsigned char id[3 + 1]; unsigned int model; } cochran_commander_model_t; typedef struct cochran_data_t { unsigned char config[1024]; unsigned char *logbook; - unsigned char *sample; unsigned short int dive_count; int fp_dive_num; + int invalid_profile_dive_num; unsigned int logbook_size; @@ -78,7 +83,9 @@ typedef struct cochran_device_layout_t { // Profile ringbuffer. unsigned int rb_profile_begin; unsigned int rb_profile_end; - // Profile pointers. + // pointers. + unsigned int pt_fingerprint; + unsigned int fingerprint_size; unsigned int pt_profile_pre; unsigned int pt_profile_begin; unsigned int pt_profile_end; @@ -109,15 +116,15 @@ static const dc_device_vtable_t cochran_commander_device_vtable = { cochran_commander_device_close /* close */ }; -// Cochran Commander Nitrox -static const cochran_device_layout_t cochran_cmdr_device_layout = { - COCHRAN_MODEL_COMMANDER_AIR_NITROX, // model +// Cochran Commander pre-21000 s/n +static const cochran_device_layout_t cochran_cmdr_1_device_layout = { + COCHRAN_MODEL_COMMANDER_PRE21000, // model 24, // address_bits - ENDIAN_BE, // endian + ENDIAN_WORD_BE, // endian 115200, // baudrate 0x046, // cf_dive_count - 0x06E, // cf_last_log - 0x200, // cf_last_interdive + 0x6c, // cf_last_log + 0x70, // cf_last_interdive 0x0AA, // cf_serial_number 0x00000000, // rb_logbook_begin 0x00020000, // rb_logbook_end @@ -125,6 +132,32 @@ static const cochran_device_layout_t cochran_cmdr_device_layout = { 512, // rb_logbook_entry_count 0x00020000, // rb_profile_begin 0x00100000, // rb_profile_end + 12, // pt_fingerprint + 4, // fingerprint_size + 28, // pt_profile_pre + 0, // pt_profile_begin + 128, // pt_profile_end +}; + + +// Cochran Commander Nitrox +static const cochran_device_layout_t cochran_cmdr_device_layout = { + COCHRAN_MODEL_COMMANDER_AIR_NITROX, // model + 24, // address_bits + ENDIAN_WORD_BE, // endian + 115200, // baudrate + 0x046, // cf_dive_count + 0x06C, // cf_last_log + 0x070, // cf_last_interdive + 0x0AA, // cf_serial_number + 0x00000000, // rb_logbook_begin + 0x00020000, // rb_logbook_end + 256, // rb_logbook_entry_size + 512, // rb_logbook_entry_count + 0x00020000, // rb_profile_begin + 0x00100000, // rb_profile_end + 0, // pt_fingerprint + 6, // fingerprint_size 30, // pt_profile_pre 6, // pt_profile_begin 128, // pt_profile_end @@ -146,6 +179,8 @@ static const cochran_device_layout_t cochran_emc14_device_layout = { 256, // rb_logbook_entry_count 0x00022000, // rb_profile_begin 0x00200000, // rb_profile_end + 0, // pt_fingerprint + 6, // fingerprint_size 30, // pt_profile_pre 6, // pt_profile_begin 256, // pt_profile_end @@ -167,6 +202,8 @@ static const cochran_device_layout_t cochran_emc16_device_layout = { 1024, // rb_logbook_entry_count 0x00094000, // rb_profile_begin 0x00800000, // rb_profile_end + 0, // pt_fingerprint + 6, // fingerprint_size 30, // pt_profile_pre 6, // pt_profile_begin 256, // pt_profile_end @@ -188,6 +225,8 @@ static const cochran_device_layout_t cochran_emc20_device_layout = { 1024, // rb_logbook_entry_count 0x00094000, // rb_profile_begin 0x01000000, // rb_profile_end + 0, // pt_fingerprint + 6, // fingerprint_size 30, // pt_profile_pre 6, // pt_profile_begin 256, // pt_profile_end @@ -199,15 +238,20 @@ static unsigned int cochran_commander_get_model (cochran_commander_device_t *device) { const cochran_commander_model_t models[] = { - {"AM\x11""2212\x02", COCHRAN_MODEL_COMMANDER_AIR_NITROX}, - {"AM7303\x8b\x43", COCHRAN_MODEL_EMC_14}, - {"AMA315\xC3\xC5", COCHRAN_MODEL_EMC_16}, - {"AM2315\xA3\x71", COCHRAN_MODEL_EMC_20}, + {"\x11""21", COCHRAN_MODEL_COMMANDER_PRE21000}, + {"\x11""22", COCHRAN_MODEL_COMMANDER_AIR_NITROX}, + {"730", COCHRAN_MODEL_EMC_14}, + {"731", COCHRAN_MODEL_EMC_14}, + {"A30", COCHRAN_MODEL_EMC_16}, + {"A31", COCHRAN_MODEL_EMC_16}, + {"230", COCHRAN_MODEL_EMC_20}, + {"231", COCHRAN_MODEL_EMC_20}, + {"\x40""30", COCHRAN_MODEL_EMC_20}, }; unsigned int model = 0xFFFFFFFF; for (unsigned int i = 0; i < C_ARRAY_SIZE(models); ++i) { - if (memcmp (device->id + 0x3B, models[i].id, sizeof(models[i].id) - 1) == 0) { + if (memcmp (device->id + 0x3D, models[i].id, sizeof(models[i].id) - 1) == 0) { model = models[i].model; break; } @@ -429,24 +473,165 @@ cochran_commander_read (cochran_commander_device_t *device, dc_event_progress_t return DC_STATUS_SUCCESS; } +static unsigned int +cochran_commander_read_retry (cochran_commander_device_t *device, dc_event_progress_t *progress, unsigned int address, unsigned char data[], unsigned int size) +{ + // Save the state of the progress events. + unsigned int saved = progress->current; -static void + unsigned int nretries = 0; + dc_status_t rc = DC_STATUS_SUCCESS; + while ((rc = cochran_commander_read (device, progress, address, data, size)) != DC_STATUS_SUCCESS) { + // Automatically discard a corrupted packet, + // and request a new one. + if (rc != DC_STATUS_PROTOCOL && rc != DC_STATUS_TIMEOUT) + return rc; + + // Abort if the maximum number of retries is reached. + if (nretries++ >= MAXRETRIES) + return rc; + + // Restore the state of the progress events. + progress->current = saved; + } + + return rc; +} + + +/* + * For corrupt dives the end-of-samples pointer is 0xFFFFFFFF + * search for a reasonable size, e.g. using next dive start sample + * or end-of-samples to limit searching for recoverable samples + */ +static unsigned int +cochran_commander_guess_sample_end_address(cochran_commander_device_t *device, cochran_data_t *data, unsigned int log_num) +{ + const unsigned char *log_entry = data->logbook + device->layout->rb_logbook_entry_size * log_num; + + if (log_num == data->dive_count) + // Return next usable address from config page + return array_uint32_le(data->config + device->layout->rb_profile_end); + + // Next log's start address + return array_uint32_le(log_entry + device->layout->rb_logbook_entry_size + device->layout->pt_profile_begin); +} + + +static unsigned int +cochran_commander_profile_size(cochran_commander_device_t *device, cochran_data_t *data, int dive_num, unsigned int sample_start_address, unsigned int sample_end_address) +{ + // Validate addresses + if (sample_start_address < device->layout->rb_profile_begin || + sample_start_address > device->layout->rb_profile_end || + sample_end_address < device->layout->rb_profile_begin || + (sample_end_address > device->layout->rb_profile_end && + sample_end_address != 0xFFFFFFFF)) { + return 0; + } + + if (sample_end_address == 0xFFFFFFFF) + // Corrupt dive, guess the end address + sample_end_address = cochran_commander_guess_sample_end_address(device, data, dive_num); + + return ringbuffer_distance(sample_start_address, sample_end_address, 0, device->layout->rb_profile_begin, device->layout->rb_profile_end); +} + + +/* + * Do several things. Find the log that matches the fingerprint, + * calculate the total read size for progress indicator, + * Determine the most recent dive without profile data. + */ + +static unsigned int cochran_commander_find_fingerprint(cochran_commander_device_t *device, cochran_data_t *data) { - // Skip to fingerprint to reduce time - if (data->dive_count < device->layout->rb_logbook_entry_count) - data->fp_dive_num = data->dive_count; - else - data->fp_dive_num = device->layout->rb_logbook_entry_count; - data->fp_dive_num--; + // We track profile ringbuffer usage to determine which dives have profile data + int profile_capacity_remaining = device->layout->rb_profile_end - device->layout->rb_profile_begin; - while (data->fp_dive_num >= 0 && memcmp(device->fingerprint, - data->logbook + data->fp_dive_num * device->layout->rb_logbook_entry_size, - sizeof(device->fingerprint))) - data->fp_dive_num--; + int dive_count = -1; + data->fp_dive_num = -1; + + // Start at end of log + if (data->dive_count < device->layout->rb_logbook_entry_count) + dive_count = data->dive_count; + else + dive_count = device->layout->rb_logbook_entry_count; + dive_count--; + + unsigned int sample_read_size = 0; + data->invalid_profile_dive_num = -1; + + // Remove the pre-dive events that occur after the last dive + unsigned int rb_head_ptr = 0; + if (device->layout->endian == ENDIAN_WORD_BE) + rb_head_ptr = (array_uint32_word_be(data->config + device->layout->cf_last_log) & 0xfffff000) + 0x2000; + else + rb_head_ptr = (array_uint32_le(data->config + device->layout->cf_last_log) & 0xfffff000) + 0x2000; + + unsigned int head_dive = 0, tail_dive = 0; + + if (data->dive_count <= device->layout->rb_logbook_entry_count) { + head_dive = data->dive_count; + tail_dive = 0; + } else { + // Log wrapped + tail_dive = data->dive_count % device->layout->rb_logbook_entry_count; + head_dive = tail_dive; + } + + unsigned int last_profile_idx = (device->layout->rb_logbook_entry_count + head_dive - 1) % device->layout->rb_logbook_entry_count; + unsigned int last_profile_end = array_uint32_le(data->logbook + last_profile_idx * device->layout->rb_logbook_entry_size + device->layout->pt_profile_end); + unsigned int last_profile_pre = 0xFFFFFFFF; + + if (device->layout->endian == ENDIAN_WORD_BE) + last_profile_pre = array_uint32_word_be(data->config + device->layout->cf_last_log); + else + last_profile_pre = array_uint32_le(data->config + device->layout->cf_last_log); + + if (rb_head_ptr > last_profile_end) + profile_capacity_remaining -= rb_head_ptr - last_profile_end; + + // Loop through dives to find FP, Accumulate profile data size, + // and find the last dive with invalid profile + for (unsigned int i = 0; i <= dive_count; ++i) { + unsigned int idx = (device->layout->rb_logbook_entry_count + head_dive - (i + 1)) % device->layout->rb_logbook_entry_count; + + unsigned char *log_entry = data->logbook + idx * device->layout->rb_logbook_entry_size; + + // We're done if we find the fingerprint + if (!memcmp(device->fingerprint, log_entry + device->layout->pt_fingerprint, device->layout->fingerprint_size)) { + data->fp_dive_num = idx; + break; + } + + unsigned int profile_pre = array_uint32_le(log_entry + device->layout->pt_profile_pre); + unsigned int profile_begin = array_uint32_le(log_entry + device->layout->pt_profile_begin); + unsigned int profile_end = array_uint32_le(log_entry + device->layout->pt_profile_end); + + unsigned int sample_size = cochran_commander_profile_size(device, data, idx, profile_pre, last_profile_pre); + unsigned int read_size = cochran_commander_profile_size(device, data, idx, profile_begin, profile_end); + last_profile_pre = profile_pre; + + // Determine if sample exists + if (profile_capacity_remaining > 0) { + // Subtract this dive's profile size including post-dive events + profile_capacity_remaining -= sample_size; + if (profile_capacity_remaining < 0) { + // Save the last dive that is missing profile data + data->invalid_profile_dive_num = idx; + } + // Accumulate read size for progress bar + sample_read_size += read_size; + } + } + + return sample_read_size; } + static void cochran_commander_get_sample_parms(cochran_commander_device_t *device, cochran_data_t *data) { @@ -509,103 +694,6 @@ cochran_commander_get_sample_parms(cochran_commander_device_t *device, cochran_d } -/* - * For corrupt dives the end-of-samples pointer is 0xFFFFFFFF - * search for a reasonable size, e.g. using next dive start sample - * or end-of-samples to limit searching for recoverable samples - */ -static unsigned int -cochran_commander_guess_sample_end_address(cochran_commander_device_t *device, cochran_data_t *data, unsigned int log_num) -{ - const unsigned char *log_entry = data->logbook + device->layout->rb_logbook_entry_size * log_num; - - if (log_num == data->dive_count) - // Return next usable address from config page - return array_uint32_le(data->config + device->layout->rb_profile_end); - - // Next log's start address - return array_uint32_le(log_entry + device->layout->rb_logbook_entry_size + device->layout->pt_profile_begin); -} - - -static dc_status_t -cochran_commander_read_all (cochran_commander_device_t *device, cochran_data_t *data) -{ - dc_device_t *abstract = (dc_device_t *) device; - dc_status_t rc = DC_STATUS_SUCCESS; - - // Calculate max data sizes - unsigned int max_config = sizeof(data->config); - unsigned int max_logbook = device->layout->rb_logbook_end - device->layout->rb_logbook_begin; - unsigned int max_sample = device->layout->rb_profile_end - device->layout->rb_profile_begin; - - dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; - progress.maximum = max_config + max_logbook + max_sample; - device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - - // Emit ID block - dc_event_vendor_t vendor; - vendor.data = device->id; - vendor.size = sizeof (device->id); - device_event_emit (abstract, DC_EVENT_VENDOR, &vendor); - - // Read config - rc = cochran_commander_read_config(device, &progress, data->config, sizeof(data->config)); - if (rc != DC_STATUS_SUCCESS) - return rc; - - // Determine size of dive list to read. - if (device->layout->endian == ENDIAN_LE) - data->dive_count = array_uint16_le (data->config + device->layout->cf_dive_count); - else - data->dive_count = array_uint16_be (data->config + device->layout->cf_dive_count); - - if (data->dive_count > device->layout->rb_logbook_entry_count) { - data->logbook_size = device->layout->rb_logbook_entry_count * device->layout->rb_logbook_entry_size; - } else { - data->logbook_size = data->dive_count * device->layout->rb_logbook_entry_size; - } - - progress.maximum -= max_logbook - data->logbook_size; - device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - - // Allocate space for log book. - data->logbook = (unsigned char *) malloc(data->logbook_size); - if (data->logbook == NULL) { - ERROR (abstract->context, "Failed to allocate memory."); - return DC_STATUS_NOMEMORY; - } - - // Request log book - rc = cochran_commander_read(device, &progress, 0, data->logbook, data->logbook_size); - if (rc != DC_STATUS_SUCCESS) - return rc; - - // Determine sample memory to read - cochran_commander_find_fingerprint(device, data); - cochran_commander_get_sample_parms(device, data); - - progress.maximum -= max_sample - data->sample_size; - device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - - if (data->sample_size > 0) { - data->sample = (unsigned char *) malloc(data->sample_size); - if (data->sample == NULL) { - ERROR (abstract->context, "Failed to allocate memory."); - return DC_STATUS_NOMEMORY; - } - - // Read the sample data - rc = cochran_commander_read (device, &progress, data->sample_data_offset, data->sample, data->sample_size); - if (rc != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the sample data."); - return rc; - } - } - - return DC_STATUS_SUCCESS; -} - dc_status_t cochran_commander_device_open (dc_device_t **out, dc_context_t *context, const char *name) { @@ -647,6 +735,9 @@ cochran_commander_device_open (dc_device_t **out, dc_context_t *context, const c unsigned int model = cochran_commander_get_model(device); switch (model) { + case COCHRAN_MODEL_COMMANDER_PRE21000: + device->layout = &cochran_cmdr_1_device_layout; + break; case COCHRAN_MODEL_COMMANDER_AIR_NITROX: device->layout = &cochran_cmdr_device_layout; break; @@ -697,13 +788,13 @@ cochran_commander_device_set_fingerprint (dc_device_t *abstract, const unsigned { cochran_commander_device_t *device = (cochran_commander_device_t *) abstract; - if (size && size != sizeof (device->fingerprint)) + if (size && size != device->layout->fingerprint_size) return DC_STATUS_INVALIDARGS; if (size) - memcpy (device->fingerprint, data, sizeof (device->fingerprint)); + memcpy (device->fingerprint, data, device->layout->fingerprint_size); else - memset (device->fingerprint, 0xFF, sizeof (device->fingerprint)); + memset (device->fingerprint, 0xFF, sizeof(device->fingerprint)); return DC_STATUS_SUCCESS; } @@ -767,111 +858,172 @@ static dc_status_t cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) { cochran_commander_device_t *device = (cochran_commander_device_t *) abstract; + const cochran_device_layout_t *layout = device->layout; dc_status_t status = DC_STATUS_SUCCESS; cochran_data_t data; data.logbook = NULL; - data.sample = NULL; - status = cochran_commander_read_all (device, &data); - if (status != DC_STATUS_SUCCESS) - goto error; - // Emit a device info event. - dc_event_devinfo_t devinfo; - devinfo.model = device->layout->model; - devinfo.firmware = 0; // unknown - devinfo.serial = array_uint32_le(data.config + device->layout->cf_serial_number); - device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); + // Calculate max data sizes + unsigned int max_config = sizeof(data.config); + unsigned int max_logbook = layout->rb_logbook_end - layout->rb_logbook_begin; + unsigned int max_sample = layout->rb_profile_end - layout->rb_profile_begin; - // Calculate profile RB effective head pointer - // Cochran seems to erase 8K chunks so round up. - unsigned int last_start_address = (array_uint32_le(data.config + device->layout->cf_last_interdive) & 0xfffff000) + 0x2000; - if (last_start_address < device->layout->rb_profile_begin || last_start_address > device->layout->rb_profile_end) { - ERROR(abstract->context, "Invalid profile ringbuffer head pointer in Cochran config block."); - status = DC_STATUS_DATAFORMAT; + // setup progress indication + dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; + progress.maximum = max_config + max_logbook + max_sample; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Emit ID block + dc_event_vendor_t vendor; + vendor.data = device->id; + vendor.size = sizeof (device->id); + device_event_emit (abstract, DC_EVENT_VENDOR, &vendor); + + // Read config + dc_status_t rc = DC_STATUS_SUCCESS; + rc = cochran_commander_read_config(device, &progress, data.config, sizeof(data.config)); + if (rc != DC_STATUS_SUCCESS) + return rc; + + // Determine size of dive list to read. + if (layout->endian == ENDIAN_LE) + data.dive_count = array_uint16_le (data.config + layout->cf_dive_count); + else + data.dive_count = array_uint16_be (data.config + layout->cf_dive_count); + + if (data.dive_count == 0) + // No dives to read + return DC_STATUS_SUCCESS; + + if (data.dive_count > layout->rb_logbook_entry_count) { + data.logbook_size = layout->rb_logbook_entry_count * layout->rb_logbook_entry_size; + } else { + data.logbook_size = data.dive_count * layout->rb_logbook_entry_size; + } + + // Update progress indicator with new maximum + progress.maximum -= max_logbook - data.logbook_size; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Allocate space for log book. + data.logbook = (unsigned char *) malloc(data.logbook_size); + if (data.logbook == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Request log book + rc = cochran_commander_read(device, &progress, 0, data.logbook, data.logbook_size); + if (rc != DC_STATUS_SUCCESS) { + status = rc; goto error; } - // We track profile ringbuffer usage to determine which dives have profile data - int profile_capacity_remaining = device->layout->rb_profile_end - device->layout->rb_profile_begin; + // Locate fingerprint, recent dive with invalid profile and calc read size + unsigned int profile_read_size = cochran_commander_find_fingerprint(device, &data); + // Update progress indicator with new maximum + progress.maximum -= (max_sample - profile_read_size); + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - unsigned int dive_count = 0; - if (data.dive_count < device->layout->rb_logbook_entry_count) - dive_count = data.dive_count; + cochran_commander_get_sample_parms(device, &data); + + // Emit a device info event. + dc_event_devinfo_t devinfo; + devinfo.model = layout->model; + devinfo.firmware = 0; // unknown + if (layout->endian == ENDIAN_WORD_BE) + devinfo.serial = array_uint32_word_be(data.config + layout->cf_serial_number); else - dive_count = device->layout->rb_logbook_entry_count; + devinfo.serial = array_uint32_le(data.config + layout->cf_serial_number); + + device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); + + unsigned int head_dive = 0, tail_dive = 0, dive_count = 0; + + if (data.dive_count <= layout->rb_logbook_entry_count) { + head_dive = data.dive_count; + tail_dive = 0; + } else { + // Log wrapped + tail_dive = data.dive_count % layout->rb_logbook_entry_count; + head_dive = tail_dive; + } + + // Change tail to dive following the fingerprint dive. + if (data.fp_dive_num > -1) + tail_dive = (data.fp_dive_num + 1) % layout->rb_logbook_entry_count; + + // Number of dives to read + dive_count = (layout->rb_logbook_entry_count + head_dive - tail_dive) % layout->rb_logbook_entry_count; + + int invalid_profile_flag = 0; // Loop through each dive - for (int i = dive_count - 1; i > data.fp_dive_num; i--) { - unsigned char *log_entry = data.logbook + i * device->layout->rb_logbook_entry_size; + for (unsigned int i = 0; i < dive_count; ++i) { + unsigned int idx = (layout->rb_logbook_entry_count + head_dive - (i + 1)) % layout->rb_logbook_entry_count; - unsigned int sample_start_address = array_uint32_le (log_entry + device->layout->pt_profile_begin); - unsigned int sample_end_address = array_uint32_le (log_entry + device->layout->pt_profile_end); + unsigned char *log_entry = data.logbook + idx * layout->rb_logbook_entry_size; - // Validate - if (sample_start_address < device->layout->rb_profile_begin || - sample_start_address > device->layout->rb_profile_end || - sample_end_address < device->layout->rb_profile_begin || - (sample_end_address > device->layout->rb_profile_end && - sample_end_address != 0xFFFFFFFF)) { - continue; - } + unsigned int sample_start_address = array_uint32_le (log_entry + layout->pt_profile_begin); + unsigned int sample_end_address = array_uint32_le (log_entry + layout->pt_profile_end); - if (sample_end_address == 0xFFFFFFFF) - // Corrupt dive, guess the end address - sample_end_address = cochran_commander_guess_sample_end_address(device, &data, i); - - // Determine if sample exists - if (profile_capacity_remaining > 0) { - // Subtract this dive's profile size including post-dive events - profile_capacity_remaining -= (last_start_address - sample_start_address); - // Adjust for a dive that wraps the buffer - if (sample_start_address > last_start_address) - profile_capacity_remaining -= device->layout->rb_profile_end - device->layout->rb_profile_begin; - } - last_start_address = sample_start_address; - - unsigned char *sample = NULL; int sample_size = 0; - if (profile_capacity_remaining < 0) { - // There is no profile for this dive - sample = NULL; - sample_size = 0; - } else { - // Calculate the size of the profile only - sample = data.sample + sample_start_address - data.sample_data_offset; - sample_size = sample_end_address - sample_start_address; - if (sample_size < 0) - // Adjust for ring buffer wrap-around - sample_size += device->layout->rb_profile_end - device->layout->rb_profile_begin; - } + // Determine if profile exists + if (idx == data.invalid_profile_dive_num) + invalid_profile_flag = 1; + + if (!invalid_profile_flag) + sample_size = cochran_commander_profile_size(device, &data, idx, sample_start_address, sample_end_address); // Build dive blob - unsigned int dive_size = device->layout->rb_logbook_entry_size + sample_size; + unsigned int dive_size = layout->rb_logbook_entry_size + sample_size; unsigned char *dive = (unsigned char *) malloc(dive_size); if (dive == NULL) { status = DC_STATUS_NOMEMORY; goto error; } - memcpy(dive, log_entry, device->layout->rb_logbook_entry_size); // log + memcpy(dive, log_entry, layout->rb_logbook_entry_size); // log - // Copy profile data + // Read profile data if (sample_size) { + if (sample_end_address == 0xFFFFFFFF) + // Corrupt dive, guess the end address + sample_end_address = cochran_commander_guess_sample_end_address(device, &data, idx); + if (sample_start_address <= sample_end_address) { - memcpy(dive + device->layout->rb_logbook_entry_size, sample, sample_size); + rc = cochran_commander_read_retry (device, &progress, sample_start_address, dive + layout->rb_logbook_entry_size, sample_size); + if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the sample data."); + status = rc; + free(dive); + goto error; + } } else { // It wrapped the buffer, copy two sections - unsigned int size = device->layout->rb_profile_end - sample_start_address; + unsigned int size = layout->rb_profile_end - sample_start_address; - memcpy(dive + device->layout->rb_logbook_entry_size, sample, size); - memcpy(dive + device->layout->rb_logbook_entry_size + size, - data.sample, sample_end_address - device->layout->rb_profile_begin); + rc = cochran_commander_read_retry (device, &progress, sample_start_address, dive + layout->rb_logbook_entry_size, size); + if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the sample data."); + status = rc; + free(dive); + goto error; + } + + rc = cochran_commander_read_retry (device, &progress, layout->rb_profile_begin, dive + layout->rb_logbook_entry_size + size, sample_end_address - layout->rb_profile_begin); + if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the sample data."); + status = rc; + free(dive); + goto error; + } } } - if (callback && !callback (dive, dive_size, dive, sizeof(device->fingerprint), userdata)) { + if (callback && !callback (dive, dive_size, dive + layout->pt_fingerprint, layout->fingerprint_size, userdata)) { free(dive); break; } @@ -881,6 +1033,5 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call error: free(data.logbook); - free(data.sample); return status; } diff --git a/src/cochran_commander_parser.c b/src/cochran_commander_parser.c index ab854ad..5a0ad55 100644 --- a/src/cochran_commander_parser.c +++ b/src/cochran_commander_parser.c @@ -31,10 +31,14 @@ #define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array)) -#define COCHRAN_MODEL_COMMANDER_AIR_NITROX 0 -#define COCHRAN_MODEL_EMC_14 1 -#define COCHRAN_MODEL_EMC_16 2 -#define COCHRAN_MODEL_EMC_20 3 +#define COCHRAN_MODEL_COMMANDER_PRE21000 0 +#define COCHRAN_MODEL_COMMANDER_AIR_NITROX 1 +#define COCHRAN_MODEL_EMC_14 2 +#define COCHRAN_MODEL_EMC_16 3 +#define COCHRAN_MODEL_EMC_20 4 + +// Cochran time stamps start at Jan 1, 1992 +#define COCHRAN_EPOCH 694242000 #define UNSUPPORTED 0xFFFFFFFF @@ -43,11 +47,19 @@ typedef enum cochran_sample_format_t { SAMPLE_EMC, } cochran_sample_format_t; + +typedef enum cochran_date_encoding_t { + DATE_ENCODING_MSDHYM, + DATE_ENCODING_SMHDMY, + DATE_ENCODING_TICKS, +} cochran_date_encoding_t; + typedef struct cochran_parser_layout_t { cochran_sample_format_t format; unsigned int headersize; unsigned int samplesize; - unsigned int second, minute, hour, day, month, year; + cochran_date_encoding_t date_encoding; + unsigned int datetime; unsigned int pt_profile_begin; unsigned int water_conductivity; unsigned int pt_profile_pre; @@ -101,11 +113,36 @@ static const dc_parser_vtable_t cochran_commander_parser_vtable = { NULL /* destroy */ }; +static const cochran_parser_layout_t cochran_cmdr_1_parser_layout = { + SAMPLE_CMDR, // type + 256, // headersize + 2, // samplesize + DATE_ENCODING_TICKS, // date_encoding + 8, // datetime, 4 bytes + 0, // pt_profile_begin, 4 bytes + 24, // water_conductivity, 1 byte, 0=low(fresh), 2=high(sea) + 28, // pt_profile_pre, 4 bytes + 43, // start_temp, 1 byte, F + 54, // start_depth, 2 bytes, /4=ft + 68, // dive_number, 2 bytes + 73, // altitude, 1 byte, /4=kilofeet + 128, // pt_profile_end, 4 bytes + 153, // end_temp, 1 byte F + 166, // divetime, 2 bytes, minutes + 168, // max_depth, 2 bytes, /4=ft + 170, // avg_depth, 2 bytes, /4=ft + 210, // oxygen, 4 bytes (2 of) 2 bytes, /256=% + UNSUPPORTED, // helium, 4 bytes (2 of) 2 bytes, /256=% + 232, // min_temp, 1 byte, /2+20=F + 233, // max_temp, 1 byte, /2+20=F +}; + static const cochran_parser_layout_t cochran_cmdr_parser_layout = { SAMPLE_CMDR, // type 256, // headersize 2, // samplesize - 1, 0, 3, 2, 5, 4, // second, minute, hour, day, month, year, 1 byte each + DATE_ENCODING_MSDHYM, // date_encoding + 0, // datetime, 6 bytes 6, // pt_profile_begin, 4 bytes 24, // water_conductivity, 1 byte, 0=low(fresh), 2=high(sea) 30, // pt_profile_pre, 4 bytes @@ -128,7 +165,8 @@ static const cochran_parser_layout_t cochran_emc_parser_layout = { SAMPLE_EMC, // type 512, // headersize 3, // samplesize - 0, 1, 2, 3, 4, 5, // second, minute, hour, day, month, year, 1 byte each + DATE_ENCODING_SMHDMY, // date_encoding + 0, // datetime, 6 bytes 6, // pt_profile_begin, 4 bytes 24, // water_conductivity, 1 byte 0=low(fresh), 2=high(sea) 30, // pt_profile_pre, 4 bytes @@ -296,6 +334,11 @@ cochran_commander_parser_create (dc_parser_t **out, dc_context_t *context, unsig parser->model = model; switch (model) { + case COCHRAN_MODEL_COMMANDER_PRE21000: + parser->layout = &cochran_cmdr_1_parser_layout; + parser->events = cochran_cmdr_event_bytes; + parser->nevents = C_ARRAY_SIZE(cochran_cmdr_event_bytes); + break; case COCHRAN_MODEL_COMMANDER_AIR_NITROX: parser->layout = &cochran_cmdr_parser_layout; parser->events = cochran_cmdr_event_bytes; @@ -340,13 +383,32 @@ cochran_commander_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dat if (abstract->size < layout->headersize) return DC_STATUS_DATAFORMAT; + dc_ticks_t ts = 0; + if (datetime) { - datetime->second = data[layout->second]; - datetime->minute = data[layout->minute]; - datetime->hour = data[layout->hour]; - datetime->day = data[layout->day]; - datetime->month = data[layout->month]; - datetime->year = data[layout->year] + (data[layout->year] > 91 ? 1900 : 2000); + switch (layout->date_encoding) + { + case DATE_ENCODING_MSDHYM: + datetime->second = data[layout->datetime + 1]; + datetime->minute = data[layout->datetime + 0]; + datetime->hour = data[layout->datetime + 3]; + datetime->day = data[layout->datetime + 2]; + datetime->month = data[layout->datetime + 5]; + datetime->year = data[layout->datetime + 4] + (data[layout->datetime + 4] > 91 ? 1900 : 2000); + break; + case DATE_ENCODING_SMHDMY: + datetime->second = data[layout->datetime + 0]; + datetime->minute = data[layout->datetime + 1]; + datetime->hour = data[layout->datetime + 2]; + datetime->day = data[layout->datetime + 3]; + datetime->month = data[layout->datetime + 4]; + datetime->year = data[layout->datetime + 5] + (data[layout->datetime + 5] > 91 ? 1900 : 2000); + break; + case DATE_ENCODING_TICKS: + ts = array_uint32_le(data + layout->datetime) + COCHRAN_EPOCH; + dc_datetime_localtime(datetime, ts); + break; + } } return DC_STATUS_SUCCESS; @@ -471,10 +533,11 @@ cochran_commander_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb // know what the dive summary values are (i.e. max depth, min temp) if (array_uint32_le(data + layout->pt_profile_end) == 0xFFFFFFFF) { corrupt_dive = 1; + dc_datetime_t d; + cochran_commander_parser_get_datetime(abstract, &d); WARNING(abstract->context, "Incomplete dive on %02d/%02d/%02d at %02d:%02d:%02d, trying to parse samples", - data[layout->year], data[layout->month], data[layout->day], - data[layout->hour], data[layout->minute], data[layout->second]); + d.year, d.month, d.day, d.hour, d.minute, d.second); // Eliminate inter-dive events size = cochran_commander_backparse(parser, samples, size); @@ -484,7 +547,8 @@ cochran_commander_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb // and temp every other second. // Prime values from the dive log section - if (parser->model == COCHRAN_MODEL_COMMANDER_AIR_NITROX) { + if (parser->model == COCHRAN_MODEL_COMMANDER_AIR_NITROX || + parser->model == COCHRAN_MODEL_COMMANDER_PRE21000) { // Commander stores start depth in quarter-feet start_depth = array_uint16_le (data + layout->start_depth) / 4.0; } else { @@ -503,6 +567,7 @@ cochran_commander_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb sample.gasmix = 0; if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata); + unsigned int last_gasmix = sample.gasmix; while (offset < size) { const unsigned char *s = samples + offset; @@ -537,45 +602,48 @@ cochran_commander_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb if (s[0] & 0x80) { offset += cochran_commander_handle_event(parser, s[0], callback, userdata); - if (layout->format == SAMPLE_EMC) { - // EMC models have events indicating change in deco status - // Commander may have them but I don't have example data - switch (s[0]) { - case 0xC5: // Deco obligation begins - deco_obligation = 1; - break; - case 0xD8: // Deco obligation ends - deco_obligation = 0; - break; - case 0xAB: // Decrement ceiling (deeper) - deco_ceiling += 10; // feet + // Events indicating change in deco status + switch (s[0]) { + case 0xC5: // Deco obligation begins + deco_obligation = 1; + break; + case 0xD8: // Deco obligation ends + deco_obligation = 0; + break; + case 0xAB: // Decrement ceiling (deeper) + deco_ceiling += 10; // feet - sample.deco.type = DC_DECO_DECOSTOP; - sample.deco.time = (array_uint16_le(s + layout->samplesize) + 1) * 60; - sample.deco.depth = deco_ceiling * FEET; - if (callback) callback(DC_SAMPLE_DECO, sample, userdata); - break; - case 0xAD: // Increment ceiling (shallower) - deco_ceiling -= 10; // feet + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.time = (array_uint16_le(s + 3) + 1) * 60; + sample.deco.depth = deco_ceiling * FEET; + if (callback) callback(DC_SAMPLE_DECO, sample, userdata); + break; + case 0xAD: // Increment ceiling (shallower) + deco_ceiling -= 10; // feet - sample.deco.type = DC_DECO_DECOSTOP; - sample.deco.depth = deco_ceiling * FEET; - sample.deco.time = (array_uint16_le(s + layout->samplesize) + 1) * 60; - if (callback) callback(DC_SAMPLE_DECO, sample, userdata); - break; - case 0xC0: // Switched to FO2 21% mode (surface) - // Event seen upon surfacing - break; - case 0xCD: // Switched to deco blend - case 0xEF: // Switched to gas blend 2 + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.depth = deco_ceiling * FEET; + sample.deco.time = (array_uint16_le(s + 3) + 1) * 60; + if (callback) callback(DC_SAMPLE_DECO, sample, userdata); + break; + case 0xC0: // Switched to FO2 21% mode (surface) + // Event seen upon surfacing + break; + case 0xCD: // Switched to deco blend + case 0xEF: // Switched to gas blend 2 + if (last_gasmix != 1) { sample.gasmix = 1; if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata); - break; - case 0xF3: // Switched to gas blend 1 + last_gasmix = sample.gasmix; + } + break; + case 0xF3: // Switched to gas blend 1 + if (last_gasmix != 0) { sample.gasmix = 0; if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata); - break; + last_gasmix = sample.gasmix; } + break; } continue; diff --git a/src/descriptor.c b/src/descriptor.c index fcee816..30e4ced 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -29,6 +29,16 @@ #define USBHID #endif +#ifdef _WIN32 +#ifdef HAVE_AF_IRDA_H +#define IRDA +#endif +#else +#ifdef HAVE_LINUX_IRDA_H +#define IRDA +#endif +#endif + #include #include @@ -89,7 +99,6 @@ static const dc_descriptor_t g_descriptors[] = { /* Suunto EON Steel */ #ifdef USBHID {"Suunto", "EON Steel", DC_FAMILY_SUUNTO_EONSTEEL, 0}, // BLE - {"Scubapro", "G2", DC_FAMILY_SCUBAPRO_G2, 0x11}, // BLE #endif /* Uwatec Aladin */ {"Uwatec", "Aladin Air Twin", DC_FAMILY_UWATEC_ALADIN, 0x1C}, // FTDI @@ -102,7 +111,7 @@ static const dc_descriptor_t g_descriptors[] = { /* Uwatec Memomouse */ {"Uwatec", "Memomouse", DC_FAMILY_UWATEC_MEMOMOUSE, 0}, // FTDI /* Uwatec Smart */ -#ifdef HAVE_IRDA +#ifdef IRDA {"Uwatec", "Smart Pro", DC_FAMILY_UWATEC_SMART, 0x10}, {"Uwatec", "Galileo Sol", DC_FAMILY_UWATEC_SMART, 0x11}, {"Uwatec", "Galileo Luna", DC_FAMILY_UWATEC_SMART, 0x11}, @@ -127,6 +136,10 @@ static const dc_descriptor_t g_descriptors[] = { {"Scubapro", "Mantis", DC_FAMILY_UWATEC_MERIDIAN, 0x20}, {"Scubapro", "Chromis", DC_FAMILY_UWATEC_MERIDIAN, 0x24}, {"Scubapro", "Mantis 2", DC_FAMILY_UWATEC_MERIDIAN, 0x26}, + /* Scubapro G2 */ +#ifdef USBHID + {"Scubapro", "G2", DC_FAMILY_UWATEC_G2, 0x32}, // BLE +#endif /* Reefnet */ {"Reefnet", "Sensus", DC_FAMILY_REEFNET_SENSUS, 1}, {"Reefnet", "Sensus Pro", DC_FAMILY_REEFNET_SENSUSPRO, 2}, @@ -302,10 +315,12 @@ static const dc_descriptor_t g_descriptors[] = { {"DiveSystem", "iDive2 Easy", DC_FAMILY_DIVESYSTEM_IDIVE, 0x42}, {"DiveSystem", "iDive2 Deep", DC_FAMILY_DIVESYSTEM_IDIVE, 0x44}, {"DiveSystem", "iDive2 Tech+", DC_FAMILY_DIVESYSTEM_IDIVE, 0x45}, - {"Cochran", "Commander", DC_FAMILY_COCHRAN_COMMANDER, 0}, - {"Cochran", "EMC-14", DC_FAMILY_COCHRAN_COMMANDER, 1}, - {"Cochran", "EMC-16", DC_FAMILY_COCHRAN_COMMANDER, 2}, - {"Cochran", "EMC-20H", DC_FAMILY_COCHRAN_COMMANDER, 3}, + /* Cochran Commander */ + {"Cochran", "Commander I", DC_FAMILY_COCHRAN_COMMANDER, 0}, + {"Cochran", "Commander II", DC_FAMILY_COCHRAN_COMMANDER, 1}, + {"Cochran", "EMC-14", DC_FAMILY_COCHRAN_COMMANDER, 2}, + {"Cochran", "EMC-16", DC_FAMILY_COCHRAN_COMMANDER, 3}, + {"Cochran", "EMC-20H", DC_FAMILY_COCHRAN_COMMANDER, 4}, }; typedef struct dc_descriptor_iterator_t { @@ -430,9 +445,9 @@ dc_descriptor_get_transport (dc_descriptor_t *descriptor) if (descriptor->type == DC_FAMILY_ATOMICS_COBALT) return DC_TRANSPORT_USB; else if (descriptor->type == DC_FAMILY_SUUNTO_EONSTEEL) - return DC_TRANSPORT_USB; - else if (descriptor->type == DC_FAMILY_SCUBAPRO_G2) - return DC_TRANSPORT_USB; + return DC_TRANSPORT_USBHID; + else if (descriptor->type == DC_FAMILY_UWATEC_G2) + return DC_TRANSPORT_USBHID; else if (descriptor->type == DC_FAMILY_UWATEC_SMART) return DC_TRANSPORT_IRDA; else diff --git a/src/device.c b/src/device.c index 756e26e..9a92966 100644 --- a/src/device.c +++ b/src/device.c @@ -127,9 +127,6 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr case DC_FAMILY_SUUNTO_EONSTEEL: rc = suunto_eonsteel_device_open (&device, context, name); break; - case DC_FAMILY_SCUBAPRO_G2: - rc = scubapro_g2_device_open (&device, context, name); - break; case DC_FAMILY_UWATEC_ALADIN: rc = uwatec_aladin_device_open (&device, context, name); break; @@ -142,6 +139,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr case DC_FAMILY_UWATEC_MERIDIAN: rc = uwatec_meridian_device_open (&device, context, name); break; + case DC_FAMILY_UWATEC_G2: + rc = scubapro_g2_device_open (&device, context, name); + break; case DC_FAMILY_REEFNET_SENSUS: rc = reefnet_sensus_device_open (&device, context, name); break; diff --git a/src/divesystem_idive.c b/src/divesystem_idive.c index e2a5cda..d26e2b4 100644 --- a/src/divesystem_idive.c +++ b/src/divesystem_idive.c @@ -42,7 +42,14 @@ #define START 0x55 #define ACK 0x06 #define NAK 0x15 -#define BUSY 0x60 + +#define ERR_INVALID_CMD 0x10 +#define ERR_INVALID_LENGTH 0x20 +#define ERR_INVALID_DATA 0x30 +#define ERR_UNSUPPORTED 0x40 +#define ERR_UNAVAILABLE 0x58 +#define ERR_UNREADABLE 0x5F +#define ERR_BUSY 0x60 #define NSTEPS 1000 #define STEP(i,n) (NSTEPS * (i) / (n)) @@ -291,72 +298,98 @@ divesystem_idive_receive (divesystem_idive_device_t *device, unsigned char answe static dc_status_t -divesystem_idive_transfer (divesystem_idive_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize) +divesystem_idive_packet (divesystem_idive_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, unsigned int *errorcode) { - dc_status_t rc = DC_STATUS_SUCCESS; + dc_status_t status = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; unsigned char packet[MAXPACKET] = {0}; - unsigned int length = 0; - unsigned int nretries = 0; + unsigned int length = sizeof(packet); + unsigned int errcode = 0; - while (1) { - // Send the command. - rc = divesystem_idive_send (device, command, csize); - if (rc != DC_STATUS_SUCCESS) - return rc; + // Send the command. + status = divesystem_idive_send (device, command, csize); + if (status != DC_STATUS_SUCCESS) { + goto error; + } - // Receive the answer. - length = sizeof(packet); - rc = divesystem_idive_receive (device, packet, &length); - if (rc != DC_STATUS_SUCCESS) - return rc; + // Receive the answer. + status = divesystem_idive_receive (device, packet, &length); + if (status != DC_STATUS_SUCCESS) { + goto error; + } - // Verify the command byte. - if (packet[0] != command[0]) { - ERROR (abstract->context, "Unexpected packet header."); - return DC_STATUS_PROTOCOL; - } + // Verify the command byte. + if (packet[0] != command[0]) { + ERROR (abstract->context, "Unexpected packet header."); + status = DC_STATUS_PROTOCOL; + goto error; + } - // Check the ACK byte. - if (packet[length - 1] == ACK) - break; - - // Verify the NAK byte. - if (packet[length - 1] != NAK) { - ERROR (abstract->context, "Unexpected ACK/NAK byte."); - return DC_STATUS_PROTOCOL; - } - - // Verify the length of the packet. - if (length != 3) { - ERROR (abstract->context, "Unexpected packet length."); - return DC_STATUS_PROTOCOL; - } - - // Verify the error code. - unsigned int errcode = packet[1]; - if (errcode != BUSY) { - ERROR (abstract->context, "Received NAK packet with error code %02x.", errcode); - return DC_STATUS_PROTOCOL; - } - - // Abort if the maximum number of retries is reached. - if (nretries++ >= MAXRETRIES) - return DC_STATUS_PROTOCOL; - - // Delay the next attempt. - dc_serial_sleep(device->port, 100); + // Verify the ACK/NAK byte. + unsigned int type = packet[length - 1]; + if (type != ACK && type != NAK) { + ERROR (abstract->context, "Unexpected ACK/NAK byte."); + status = DC_STATUS_PROTOCOL; + goto error; } // Verify the length of the packet. - if (asize != length - 2) { + unsigned int expected = (type == ACK ? asize : 1) + 2; + if (length != expected) { ERROR (abstract->context, "Unexpected packet length."); - return DC_STATUS_PROTOCOL; + status = DC_STATUS_PROTOCOL; + goto error; + } + + // Get the error code from a NAK packet. + if (type == NAK) { + errcode = packet[1]; + ERROR (abstract->context, "Received NAK packet with error code %02x.", errcode); + status = DC_STATUS_PROTOCOL; + goto error; } memcpy(answer, packet + 1, length - 2); - return DC_STATUS_SUCCESS; +error: + if (errorcode) { + *errorcode = errcode; + } + + return status; +} + + +static dc_status_t +divesystem_idive_transfer (divesystem_idive_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, unsigned int *errorcode) +{ + dc_status_t status = DC_STATUS_SUCCESS; + unsigned int errcode = 0; + + unsigned int nretries = 0; + while ((status = divesystem_idive_packet (device, command, csize, answer, asize, &errcode)) != DC_STATUS_SUCCESS) { + // Automatically discard a corrupted packet, + // and request a new one. + if (status != DC_STATUS_PROTOCOL && status != DC_STATUS_TIMEOUT) + break; + + // Abort if the device reports a fatal error. + if (errcode && errcode != ERR_BUSY) + break; + + // Abort if the maximum number of retries is reached. + if (nretries++ >= MAXRETRIES) + break; + + // Delay the next attempt. + dc_serial_sleep (device->port, 100); + } + + if (errorcode) { + *errorcode = errcode; + } + + return status; } static dc_status_t @@ -365,6 +398,7 @@ divesystem_idive_device_foreach (dc_device_t *abstract, dc_dive_callback_t callb dc_status_t rc = DC_STATUS_SUCCESS; divesystem_idive_device_t *device = (divesystem_idive_device_t *) abstract; unsigned char packet[MAXPACKET - 2]; + unsigned int errcode = 0; const divesystem_idive_commands_t *commands = &idive; if (device->model >= IX3M_EASY) { @@ -376,7 +410,7 @@ divesystem_idive_device_foreach (dc_device_t *abstract, dc_dive_callback_t callb device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); unsigned char cmd_id[] = {commands->id.cmd, 0xED}; - rc = divesystem_idive_transfer (device, cmd_id, sizeof(cmd_id), packet, commands->id.size); + rc = divesystem_idive_transfer (device, cmd_id, sizeof(cmd_id), packet, commands->id.size, &errcode); if (rc != DC_STATUS_SUCCESS) return rc; @@ -402,9 +436,14 @@ divesystem_idive_device_foreach (dc_device_t *abstract, dc_dive_callback_t callb } unsigned char cmd_range[] = {commands->range.cmd, 0x8D}; - rc = divesystem_idive_transfer (device, cmd_range, sizeof(cmd_range), packet, commands->range.size); - if (rc != DC_STATUS_SUCCESS) - return rc; + rc = divesystem_idive_transfer (device, cmd_range, sizeof(cmd_range), packet, commands->range.size, &errcode); + if (rc != DC_STATUS_SUCCESS) { + if (errcode == ERR_UNAVAILABLE) { + return DC_STATUS_SUCCESS; // No dives found. + } else { + return rc; + } + } // Get the range of the available dive numbers. unsigned int first = array_uint16_le (packet + 0); @@ -431,9 +470,15 @@ divesystem_idive_device_foreach (dc_device_t *abstract, dc_dive_callback_t callb unsigned char cmd_header[] = {commands->header.cmd, (number ) & 0xFF, (number >> 8) & 0xFF}; - rc = divesystem_idive_transfer (device, cmd_header, sizeof(cmd_header), packet, commands->header.size); - if (rc != DC_STATUS_SUCCESS) - return rc; + rc = divesystem_idive_transfer (device, cmd_header, sizeof(cmd_header), packet, commands->header.size, &errcode); + if (rc != DC_STATUS_SUCCESS) { + if (errcode == ERR_UNREADABLE) { + WARNING(abstract->context, "Skipped unreadable dive!"); + continue; + } else { + return rc; + } + } if (memcmp(packet + 7, device->fingerprint, sizeof(device->fingerprint)) == 0) break; @@ -453,7 +498,7 @@ divesystem_idive_device_foreach (dc_device_t *abstract, dc_dive_callback_t callb unsigned char cmd_sample[] = {commands->sample.cmd, (idx ) & 0xFF, (idx >> 8) & 0xFF}; - rc = divesystem_idive_transfer (device, cmd_sample, sizeof(cmd_sample), packet, commands->sample.size * commands->nsamples); + rc = divesystem_idive_transfer (device, cmd_sample, sizeof(cmd_sample), packet, commands->sample.size * commands->nsamples, &errcode); if (rc != DC_STATUS_SUCCESS) return rc; diff --git a/src/irda.c b/src/irda.c index 2271a69..0f20970 100644 --- a/src/irda.c +++ b/src/irda.c @@ -19,21 +19,31 @@ * MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + #include // malloc, free #include // snprintf #ifdef _WIN32 #define NOGDI #include #include + #ifdef HAVE_AF_IRDA_H + #define IRDA #include + #endif #else #include // strerror #include // errno #include // close #include // socket, getsockopt #include // socket, getsockopt + #ifdef HAVE_LINUX_IRDA_H + #define IRDA #include // irda #include // irda + #endif #include // select #include // ioctl #include @@ -86,6 +96,7 @@ struct dc_irda_t { int timeout; }; +#ifdef IRDA static dc_status_t syserror(s_errcode_t errcode) { @@ -102,10 +113,12 @@ syserror(s_errcode_t errcode) return DC_STATUS_IO; } } +#endif dc_status_t dc_irda_open (dc_irda_t **out, dc_context_t *context) { +#ifdef IRDA dc_status_t status = DC_STATUS_SUCCESS; dc_irda_t *device = NULL; @@ -167,11 +180,15 @@ error_free: #endif free (device); return status; +#else + return DC_STATUS_UNSUPPORTED; +#endif } dc_status_t dc_irda_close (dc_irda_t *device) { +#ifdef IRDA dc_status_t status = DC_STATUS_SUCCESS; if (device == NULL) @@ -200,11 +217,15 @@ dc_irda_close (dc_irda_t *device) free (device); return status; +#else + return DC_STATUS_UNSUPPORTED; +#endif } dc_status_t dc_irda_set_timeout (dc_irda_t *device, int timeout) { +#ifdef IRDA if (device == NULL) return DC_STATUS_INVALIDARGS; @@ -213,6 +234,9 @@ dc_irda_set_timeout (dc_irda_t *device, int timeout) device->timeout = timeout; return DC_STATUS_SUCCESS; +#else + return DC_STATUS_UNSUPPORTED; +#endif } @@ -230,6 +254,7 @@ dc_irda_set_timeout (dc_irda_t *device, int timeout) dc_status_t dc_irda_discover (dc_irda_t *device, dc_irda_callback_t callback, void *userdata) { +#ifdef IRDA if (device == NULL) return DC_STATUS_INVALIDARGS; @@ -303,11 +328,15 @@ dc_irda_discover (dc_irda_t *device, dc_irda_callback_t callback, void *userdata } return DC_STATUS_SUCCESS; +#else + return DC_STATUS_UNSUPPORTED; +#endif } dc_status_t dc_irda_connect_name (dc_irda_t *device, unsigned int address, const char *name) { +#ifdef IRDA if (device == NULL) return DC_STATUS_INVALIDARGS; @@ -341,11 +370,15 @@ dc_irda_connect_name (dc_irda_t *device, unsigned int address, const char *name) } return DC_STATUS_SUCCESS; +#else + return DC_STATUS_UNSUPPORTED; +#endif } dc_status_t dc_irda_connect_lsap (dc_irda_t *device, unsigned int address, unsigned int lsap) { +#ifdef IRDA if (device == NULL) return DC_STATUS_INVALIDARGS; @@ -374,11 +407,15 @@ dc_irda_connect_lsap (dc_irda_t *device, unsigned int address, unsigned int lsap } return DC_STATUS_SUCCESS; +#else + return DC_STATUS_UNSUPPORTED; +#endif } dc_status_t dc_irda_get_available (dc_irda_t *device, size_t *value) { +#ifdef IRDA if (device == NULL) return DC_STATUS_INVALIDARGS; @@ -398,11 +435,15 @@ dc_irda_get_available (dc_irda_t *device, size_t *value) *value = bytes; return DC_STATUS_SUCCESS; +#else + return DC_STATUS_UNSUPPORTED; +#endif } dc_status_t dc_irda_read (dc_irda_t *device, void *data, size_t size, size_t *actual) { +#ifdef IRDA dc_status_t status = DC_STATUS_SUCCESS; size_t nbytes = 0; @@ -463,11 +504,15 @@ out_invalidargs: *actual = nbytes; return status; +#else + return DC_STATUS_UNSUPPORTED; +#endif } dc_status_t dc_irda_write (dc_irda_t *device, const void *data, size_t size, size_t *actual) { +#ifdef IRDA dc_status_t status = DC_STATUS_SUCCESS; size_t nbytes = 0; @@ -520,4 +565,7 @@ out_invalidargs: *actual = nbytes; return status; +#else + return DC_STATUS_UNSUPPORTED; +#endif } diff --git a/src/irda_dummy.c b/src/irda_dummy.c deleted file mode 100644 index 7579324..0000000 --- a/src/irda_dummy.c +++ /dev/null @@ -1,78 +0,0 @@ -/* - * libdivecomputer - * - * Copyright (C) 2010 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 "irda.h" - -dc_status_t -dc_irda_open (dc_irda_t **out, dc_context_t *context) -{ - return DC_STATUS_UNSUPPORTED; -} - -dc_status_t -dc_irda_close (dc_irda_t *device) -{ - return DC_STATUS_UNSUPPORTED; -} - -dc_status_t -dc_irda_set_timeout (dc_irda_t *device, int timeout) -{ - return DC_STATUS_UNSUPPORTED; -} - -dc_status_t -dc_irda_discover (dc_irda_t *device, dc_irda_callback_t callback, void *userdata) -{ - return DC_STATUS_UNSUPPORTED; -} - -dc_status_t -dc_irda_connect_name (dc_irda_t *device, unsigned int address, const char *name) -{ - return DC_STATUS_UNSUPPORTED; -} - -dc_status_t -dc_irda_connect_lsap (dc_irda_t *device, unsigned int address, unsigned int lsap) -{ - return DC_STATUS_UNSUPPORTED; -} - -dc_status_t -dc_irda_get_available (dc_irda_t *device, size_t *value) -{ - return DC_STATUS_UNSUPPORTED; -} - -dc_status_t -dc_irda_read (dc_irda_t *device, void *data, size_t size, size_t *actual) -{ - return DC_STATUS_UNSUPPORTED; -} - -dc_status_t -dc_irda_write (dc_irda_t *device, const void *data, size_t size, size_t *actual) -{ - return DC_STATUS_UNSUPPORTED; -} diff --git a/src/oceanic_atom2_parser.c b/src/oceanic_atom2_parser.c index 4a27978..137adbc 100644 --- a/src/oceanic_atom2_parser.c +++ b/src/oceanic_atom2_parser.c @@ -402,7 +402,8 @@ oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser) } else if (parser->model == T3B || parser->model == VT3 || parser->model == DG03) { mode = (data[2] & 0xC0) >> 6; - } else if (parser->model == VEO20 || parser->model == VEO30) { + } else if (parser->model == VEO20 || parser->model == VEO30 || + parser->model == OCS) { mode = (data[1] & 0x60) >> 5; } diff --git a/src/parser.c b/src/parser.c index e30ee0c..b1257e9 100644 --- a/src/parser.c +++ b/src/parser.c @@ -95,11 +95,9 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa case DC_FAMILY_UWATEC_MEMOMOUSE: rc = uwatec_memomouse_parser_create (&parser, context, devtime, systime); break; - case DC_FAMILY_SCUBAPRO_G2: - rc = uwatec_smart_parser_create (&parser, context, 0x11, devtime, systime); - break; case DC_FAMILY_UWATEC_SMART: case DC_FAMILY_UWATEC_MERIDIAN: + case DC_FAMILY_UWATEC_G2: rc = uwatec_smart_parser_create (&parser, context, model, devtime, systime); break; case DC_FAMILY_REEFNET_SENSUS: diff --git a/src/scubapro_g2.c b/src/scubapro_g2.c index f12f8b1..33ecfc4 100644 --- a/src/scubapro_g2.c +++ b/src/scubapro_g2.c @@ -45,7 +45,7 @@ static dc_status_t scubapro_g2_device_close (dc_device_t *abstract); static const dc_device_vtable_t scubapro_g2_device_vtable = { sizeof(scubapro_g2_device_t), - DC_FAMILY_SCUBAPRO_G2, + DC_FAMILY_UWATEC_G2, scubapro_g2_device_set_fingerprint, /* set_fingerprint */ NULL, /* read */ NULL, /* write */ diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index 0aff90a..6d4a3ec 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -619,11 +619,11 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal if ((status & OC) == 0) { // PPO2 -#ifdef SENSOR_AVERAGE - sample.ppo2 = data[offset + 6] / 100.0; - if (callback) callback (DC_SAMPLE_PPO2, sample, userdata); -#else if ((status & PPO2_EXTERNAL) == 0) { +#ifdef SENSOR_AVERAGE + sample.ppo2 = data[offset + 6] / 100.0; + if (callback) callback (DC_SAMPLE_PPO2, sample, userdata); +#else sample.ppo2 = data[offset + 12] * parser->calibration[0]; if (callback && (data[86] & 0x01)) callback (DC_SAMPLE_PPO2, sample, userdata); @@ -632,8 +632,8 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal sample.ppo2 = data[offset + 15] * parser->calibration[2]; if (callback && (data[86] & 0x04)) callback (DC_SAMPLE_PPO2, sample, userdata); - } #endif + } // Setpoint if (parser->model > PREDATOR) { diff --git a/src/uwatec_smart_parser.c b/src/uwatec_smart_parser.c index b350f1e..99bfa28 100644 --- a/src/uwatec_smart_parser.c +++ b/src/uwatec_smart_parser.c @@ -47,6 +47,7 @@ #define MERIDIAN 0x20 #define CHROMIS 0x24 #define MANTIS2 0x26 +#define G2 0x32 #define UNSUPPORTED 0xFFFFFFFF @@ -439,6 +440,8 @@ uwatec_smart_parser_cache (uwatec_smart_parser_t *parser) parser->events[2] = uwatec_smart_galileo_events_2; parser->nevents[2] = C_ARRAY_SIZE (uwatec_smart_galileo_events_2); } + } else if (parser->model == G2) { + trimix = 1; } // Get the settings. @@ -500,7 +503,8 @@ uwatec_smart_parser_cache (uwatec_smart_parser_t *parser) divemode != DC_DIVEMODE_FREEDIVE) { if (parser->model == GALILEO || parser->model == GALILEOTRIMIX || parser->model == ALADIN2G || parser->model == MERIDIAN || - parser->model == CHROMIS || parser->model == MANTIS2) { + parser->model == CHROMIS || parser->model == MANTIS2 || + parser->model == G2) { unsigned int offset = header->tankpressure + 2 * i; endpressure = array_uint16_le(data + offset); beginpressure = array_uint16_le(data + offset + 2 * header->ngases); @@ -578,6 +582,7 @@ uwatec_smart_parser_create (dc_parser_t **out, dc_context_t *context, unsigned i case MERIDIAN: case CHROMIS: case MANTIS2: + case G2: parser->headersize = 152; parser->header = &uwatec_smart_galileo_header; parser->samples = uwatec_smart_galileo_samples; @@ -928,7 +933,8 @@ uwatec_smart_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t unsigned int id = 0; if (parser->model == GALILEO || parser->model == GALILEOTRIMIX || parser->model == ALADIN2G || parser->model == MERIDIAN || - parser->model == CHROMIS || parser->model == MANTIS2) { + parser->model == CHROMIS || parser->model == MANTIS2 || + parser->model == G2) { // Uwatec Galileo id = uwatec_galileo_identify (data[offset]); } else {