Merge branch 'master' of git://git.libdivecomputer.org/libdivecomputer into Subsurface-branch

Merge with upstream libdivecomputer from Jef:

 - Jef merged my Scubapro G2 work, but renamed everything, and didn't
   get the newer IO model code. Very annoying.

   I went along with changing the G2 model family name to
   DC_FAMILY_UWATEC_G2 just to keep some of the basic infrastructure
   more easily mergeable.  But his uwatec_g2 version is not usable.

 - Cochran updates from John Van Ostrand

 - Misc improvements from Jef:
    * divesystems idive improvements
    * Oceanic OCS freedive mode
    * ppO2 callback cleanup

 - Some transport type work:
    * changes to IRDA configuration
    * basic bluetooth rfcomm transport mode

* 'master' of git://git.libdivecomputer.org/libdivecomputer: (35 commits)
  Removed unused code
  Fixed duplicate gasmix event reports
  Added decompression event handling for the Commander
  Fix bad profiles when profile ringbuffer wraps around
  Changed cochran_comander_profile_size function parameters
  Fixed location and encoding of Commander II pointers
  Use a local variable for the layout pointer
  Add new EMC device model string
  Add support for Pre-21000 s/n Commander dive computers
  Fix problems with wrapped logbook ringbuffer
  Retry read operations on failure
  Change profile download to be incremental
  Fix the id string offset
  Fix the progress events
  Use the trimix data format
  Use the correct model number
  Enable more fine grained progress events
  Abort with an error if the buffer is too small
  Use the standard libdivecomputer error codes
  Scubapro G2: add missed command packet logging
  ...
This commit is contained in:
Linus Torvalds 2017-07-10 14:00:47 -07:00
commit 9fd6635cf6
22 changed files with 1494 additions and 435 deletions

View File

@ -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 <winsock2.h>
# 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 <winsock2.h>
# endif
])
AC_CHECK_HEADERS([sys/socket.h linux/types.h linux/irda.h], , , [
#if HAVE_SYS_SOCKET_H
# include <sys/socket.h>
# 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])

View File

@ -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},

View File

@ -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,

View File

@ -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;

View File

@ -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"
>
</File>
<File
RelativePath="..\src\bluetooth.c"
>
</File>
<File
RelativePath="..\src\buffer.c"
>
@ -524,6 +528,10 @@
RelativePath="..\include\libdivecomputer\atomics_cobalt.h"
>
</File>
<File
RelativePath="..\src\bluetooth.h"
>
</File>
<File
RelativePath="..\include\libdivecomputer\buffer.h"
>

View File

@ -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

View File

@ -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)
{

View File

@ -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);

604
src/bluetooth.c Normal file
View File

@ -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 <stdlib.h> // malloc, free
#ifdef _WIN32
#define NOGDI
#include <winsock2.h>
#include <windows.h>
#ifdef HAVE_WS2BTH_H
#define BLUETOOTH
#include <ws2bth.h>
#endif
#else
#include <errno.h> // errno
#include <unistd.h> // close
#include <sys/types.h> // socket, getsockopt
#include <sys/socket.h> // socket, getsockopt
#include <sys/select.h> // select
#include <sys/ioctl.h> // ioctl
#include <sys/time.h>
#ifdef HAVE_BLUEZ
#define BLUETOOTH
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#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
}

174
src/bluetooth.h Normal file
View File

@ -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 <libdivecomputer/common.h>
#include <libdivecomputer/context.h>
#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 */

View File

@ -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)
// 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;
// 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;
}
// 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);
cochran_commander_get_sample_parms(device, &data);
// Emit a device info event.
dc_event_devinfo_t devinfo;
devinfo.model = device->layout->model;
devinfo.model = layout->model;
devinfo.firmware = 0; // unknown
devinfo.serial = array_uint32_le(data.config + device->layout->cf_serial_number);
if (layout->endian == ENDIAN_WORD_BE)
devinfo.serial = array_uint32_word_be(data.config + layout->cf_serial_number);
else
devinfo.serial = array_uint32_le(data.config + layout->cf_serial_number);
device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo);
// 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;
goto error;
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;
}
// 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;
// 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;
unsigned int dive_count = 0;
if (data.dive_count < device->layout->rb_logbook_entry_count)
dive_count = data.dive_count;
else
dive_count = device->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;
}

View File

@ -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,9 +602,7 @@ 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
// Events indicating change in deco status
switch (s[0]) {
case 0xC5: // Deco obligation begins
deco_obligation = 1;
@ -551,7 +614,7 @@ cochran_commander_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb
deco_ceiling += 10; // feet
sample.deco.type = DC_DECO_DECOSTOP;
sample.deco.time = (array_uint16_le(s + layout->samplesize) + 1) * 60;
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;
@ -560,7 +623,7 @@ cochran_commander_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb
sample.deco.type = DC_DECO_DECOSTOP;
sample.deco.depth = deco_ceiling * FEET;
sample.deco.time = (array_uint16_le(s + layout->samplesize) + 1) * 60;
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)
@ -568,14 +631,19 @@ cochran_commander_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb
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);
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;

View File

@ -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 <stddef.h>
#include <stdlib.h>
@ -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

View File

@ -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;

View File

@ -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;
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;
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;
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) {
// Verify the ACK/NAK byte.
unsigned int type = packet[length - 1];
if (type != ACK && type != NAK) {
ERROR (abstract->context, "Unexpected ACK/NAK byte.");
return DC_STATUS_PROTOCOL;
status = DC_STATUS_PROTOCOL;
goto error;
}
// Verify the length of the packet.
if (length != 3) {
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;
}
// Verify the error code.
unsigned int errcode = packet[1];
if (errcode != BUSY) {
// 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);
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 length of the packet.
if (asize != length - 2) {
ERROR (abstract->context, "Unexpected packet length.");
return DC_STATUS_PROTOCOL;
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)
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)
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;

View File

@ -19,21 +19,31 @@
* MA 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h> // malloc, free
#include <stdio.h> // snprintf
#ifdef _WIN32
#define NOGDI
#include <winsock2.h>
#include <windows.h>
#ifdef HAVE_AF_IRDA_H
#define IRDA
#include <af_irda.h>
#endif
#else
#include <string.h> // strerror
#include <errno.h> // errno
#include <unistd.h> // close
#include <sys/types.h> // socket, getsockopt
#include <sys/socket.h> // socket, getsockopt
#ifdef HAVE_LINUX_IRDA_H
#define IRDA
#include <linux/types.h> // irda
#include <linux/irda.h> // irda
#endif
#include <sys/select.h> // select
#include <sys/ioctl.h> // ioctl
#include <sys/time.h>
@ -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
}

View File

@ -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 <stddef.h>
#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;
}

View File

@ -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;
}

View File

@ -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:

View File

@ -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 */

View File

@ -619,11 +619,11 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
if ((status & OC) == 0) {
// PPO2
if ((status & PPO2_EXTERNAL) == 0) {
#ifdef SENSOR_AVERAGE
sample.ppo2 = data[offset + 6] / 100.0;
if (callback) callback (DC_SAMPLE_PPO2, sample, userdata);
#else
if ((status & PPO2_EXTERNAL) == 0) {
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) {

View File

@ -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 {