Merge git://github.com/libdivecomputer/libdivecomputer into Subsurface-DS9

Merge upstream updates from Jef:

 - add suppoort for various new variants of existing dive computers:

    + Suunto Eon Steel Black, and new variant of Zoop Novo

    + Sherwood Beacon

    + new Shearwater Perdix AI model number

 - add new Sporasub SP2 support

 - various minor fixes and updates

* 'master' of git://github.com/libdivecomputer/libdivecomputer: (22 commits)
  Add support for a new Suunto Zoop Novo variant
  Add support for the EON Steel Black
  Add support for the Sporasub SP2
  Fix an overflow in the progress events
  Use a common sleep implementation
  Fix the clang compiler flag detection
  Add Github Actions CI builds and releases
  Show a summary after configuration
  Extend the OS detection to non Windows platforms
  Implement the ndl/deco sample
  Fix the maximum depth
  Mark the McLean Extreme as supporting BLE
  Fix -Wcast-qual compiler warning
  Mark the new iX3M 2021 models as supporting BLE
  Add support for the Sherwood Beacon
  Remove the infinite timeout
  Simplify the loop for reading the packet header
  Add a new Perdix AI hardware type
  Fix the McLean Extreme fingerprint feature
  Perform the check for the NULL key earlier
  ...
This commit is contained in:
Linus Torvalds 2021-04-22 08:40:35 -07:00
commit 9ab0800c00
29 changed files with 1179 additions and 122 deletions

95
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,95 @@
name: Build
on: [push, pull_request]
jobs:
linux:
name: Linux
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
env:
CC: ${{ matrix.compiler }}
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: sudo apt-get install libbluetooth-dev libusb-1.0-0-dev
- run: autoreconf --install --force
- run: ./configure --prefix=/usr
- run: make
- run: make distcheck
- name: Package artifacts
run: |
make install DESTDIR=$PWD/artifacts
tar -czf ${{ github.job }}-${{ matrix.compiler }}.tar.gz -C artifacts usr
- uses: actions/upload-artifact@v2
with:
name: ${{ github.job }}-${{ matrix.compiler }}
path: ${{ github.job }}-${{ matrix.compiler }}.tar.gz
mac:
name: Mac
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
env:
CC: ${{ matrix.compiler }}
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: brew install autoconf automake libtool hidapi libusb
- run: autoreconf --install --force
- run: ./configure --prefix=/usr
- run: make
- run: make distcheck
- name: Package artifacts
run: |
make install DESTDIR=$PWD/artifacts
tar -czf ${{ github.job }}-${{ matrix.compiler }}.tar.gz -C artifacts usr
- uses: actions/upload-artifact@v2
with:
name: ${{ github.job }}-${{ matrix.compiler }}
path: ${{ github.job }}-${{ matrix.compiler }}.tar.gz
windows:
name: Windows
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch: [i686, x86_64]
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: sudo apt-get install gcc-mingw-w64 binutils-mingw-w64 mingw-w64-tools
- run: autoreconf --install --force
- run: ./configure --host=${{ matrix.arch }}-w64-mingw32 --prefix=/usr
- run: make
- run: make distcheck
- name: Package artifacts
run: |
make install DESTDIR=$PWD/artifacts
tar -czf ${{ github.job }}-${{ matrix.arch }}.tar.gz -C artifacts usr
- uses: actions/upload-artifact@v2
with:
name: ${{ github.job }}-${{ matrix.arch }}
path: ${{ github.job }}-${{ matrix.arch }}.tar.gz

61
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,61 @@
name: Release
on:
push:
tags: 'v*'
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Version number
id: version
run: |
VERSION="${GITHUB_REF/refs\/tags\/v/}"
if [ "${VERSION}" = "${VERSION%%-*}" ]; then
PRERELEASE=false
else
PRERELEASE=true
fi
echo ::set-output name=version::${VERSION}
echo ::set-output name=prerelease::${PRERELEASE}
- name: Build distribution tarball
id: build
run: |
sudo apt-get install libbluetooth-dev libusb-1.0-0-dev
autoreconf --install --force
./configure
make
make distcheck
- name: Check tarball version number
id: check
run: |
FILENAME="libdivecomputer-${{ steps.version.outputs.version }}.tar.gz"
if [ ! -f "${FILENAME}" ]; then
echo ::error ::Tarball \'${FILENAME}\' not found!
exit 1
fi
- uses: actions/create-release@v1
id: release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
prerelease: ${{ steps.version.outputs.prerelease }}
- uses: actions/upload-release-asset@v1
id: upload
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.release.outputs.upload_url }}
asset_path: libdivecomputer-${{ steps.version.outputs.version }}.tar.gz
asset_name: libdivecomputer-${{ steps.version.outputs.version }}.tar.gz
asset_content_type: application/gzip

View File

@ -72,18 +72,21 @@ AM_CONDITIONAL([HAVE_MANDOC],[test -n "$MANDOC"])
# Enable automake silent build rules.
m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])
# Checks for native Windows.
AC_MSG_CHECKING([for native Win32])
# Checks for operating system.
AC_MSG_CHECKING([for operating system])
case "$host" in
*-*-mingw*)
os_win32=yes
platform=windows
;;
*-*-darwin*)
platform=mac
;;
*)
os_win32=no
platform=default
;;
esac
AC_MSG_RESULT([$os_win32])
AM_CONDITIONAL([OS_WIN32], [test "$os_win32" = "yes"])
AC_MSG_RESULT([$platform])
AM_CONDITIONAL([OS_WIN32], [test "$platform" = "windows"])
DEPENDENCIES=""
@ -187,6 +190,7 @@ AC_CHECK_FUNCS([clock_gettime mach_absolute_time])
AC_CHECK_FUNCS([getopt_long])
# Checks for supported compiler options.
AX_APPEND_COMPILE_FLAGS([-Werror=unknown-warning-option],[ERROR_CFLAGS])
AX_APPEND_COMPILE_FLAGS([ \
-Wall \
-Wshadow \
@ -204,10 +208,10 @@ AX_APPEND_COMPILE_FLAGS([ \
-Wno-pointer-sign \
-Wno-shadow \
-fmacro-prefix-map='$(top_srcdir)/'= \
])
],,[$ERROR_CFLAGS])
# Windows specific compiler options.
AS_IF([test "$os_win32" = "yes"], [
AS_IF([test "$platform" = "windows"], [
AX_APPEND_COMPILE_FLAGS([-Wno-pedantic-ms-format])
])
@ -223,6 +227,25 @@ m4_ifset([dc_version_suffix],[
AC_DEFINE(HAVE_VERSION_SUFFIX, [1], [Define if a version suffix is present.])
])
# Supported transports
transport_serial="yes"
transport_usb="${have_libusb-no}"
if test "$have_hidapi" = "yes"; then
transport_usbhid="yes"
elif test "$have_libusb" = "yes" && test "$platform" != "mac"; then
transport_usbhid="yes"
else
transport_usbhid="no"
fi
if test "$platform" = "windows"; then
transport_irda="$ac_cv_header_af_irda_h"
transport_bluetooth="$ac_cv_header_ws2bth_h"
else
transport_irda="$ac_cv_header_linux_irda_h"
transport_bluetooth="${have_bluez-no}"
fi
transport_ble="no"
AC_CONFIG_FILES([
libdivecomputer.pc
Makefile
@ -237,3 +260,35 @@ AC_CONFIG_FILES([
examples/Makefile
])
AC_OUTPUT
AC_MSG_NOTICE([
$PACKAGE $VERSION
===============
Compiler:
CC : $CC
CFLAGS : $CFLAGS
LDFLAGS : $LDFLAGS
Features:
Logging : $enable_logging
Pseudo terminal : $enable_pty
Example applications : $enable_examples
Documentation : $enable_doc
Transports:
Serial : $transport_serial
USB : $transport_usb
USBHID : $transport_usbhid
IrDA : $transport_irda
Bluetooth : $transport_bluetooth
BLE : $transport_ble
Building:
Type 'make' to compile $PACKAGE.
Type 'make install' to install $PACKAGE.
])

View File

@ -93,6 +93,7 @@ static const backend_table_t g_backends[] = {
{"divecomputereu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0},
{"mclean", DC_FAMILY_MCLEAN_EXTREME, 0},
{"lynx", DC_FAMILY_LIQUIVISION_LYNX, 0},
{"sp2", DC_FAMILY_SPORASUB_SP2, 0},
// Not merged upstream yet
{"descentmk1", DC_FAMILY_GARMIN, 0},

View File

@ -112,6 +112,8 @@ typedef enum dc_family_t {
DC_FAMILY_MCLEAN_EXTREME = (16 << 16),
/* Liquivision */
DC_FAMILY_LIQUIVISION_LYNX = (17 << 16),
/* Sporasub */
DC_FAMILY_SPORASUB_SP2 = (18 << 16),
// Not merged upstream yet
/* Garmin */

View File

@ -398,6 +398,10 @@
RelativePath="..\src\parser.c"
>
</File>
<File
RelativePath="..\src\platform.c"
>
</File>
<File
RelativePath="..\src\rbstream.c"
>
@ -454,6 +458,14 @@
RelativePath="..\src\socket.c"
>
</File>
<File
RelativePath="..\src\sporasub_sp2.c"
>
</File>
<File
RelativePath="..\src\sporasub_sp2_parser.c"
>
</File>
<File
RelativePath="..\src\suunto_common.c"
>
@ -856,6 +868,10 @@
RelativePath="..\src\socket.h"
>
</File>
<File
RelativePath="..\src\sporasub_sp2.h"
>
</File>
<File
RelativePath="..\src\suunto_common.h"
>

View File

@ -64,7 +64,7 @@ libdivecomputer_la_SOURCES = \
diverite_nitekq.h diverite_nitekq.c diverite_nitekq_parser.c \
citizen_aqualand.h citizen_aqualand.c citizen_aqualand_parser.c \
divesystem_idive.h divesystem_idive.c divesystem_idive_parser.c \
platform.h \
platform.h platform.c \
ringbuffer.h ringbuffer.c \
rbstream.h rbstream.c \
checksum.h checksum.c \
@ -74,6 +74,7 @@ libdivecomputer_la_SOURCES = \
tecdiving_divecomputereu.h tecdiving_divecomputereu.c tecdiving_divecomputereu_parser.c \
mclean_extreme.h mclean_extreme.c mclean_extreme_parser.c \
liquivision_lynx.h liquivision_lynx.c liquivision_lynx_parser.c \
sporasub_sp2.h sporasub_sp2.c sporasub_sp2_parser.c \
socket.h socket.c \
irda.c \
usb.c \

View File

@ -98,7 +98,7 @@ typedef struct aes_state_t {
#if defined(CBC) && CBC
// Initial Vector used only for CBC mode
uint8_t* Iv;
const uint8_t* Iv;
#endif
} aes_state_t;
@ -542,7 +542,7 @@ void AES128_CBC_encrypt_buffer(uint8_t* output, uint8_t* input, uint32_t length,
if(iv != 0)
{
state.Iv = (uint8_t*)iv;
state.Iv = iv;
}
for(i = 0; i < length; i += KEYLEN)
@ -584,7 +584,7 @@ void AES128_CBC_decrypt_buffer(uint8_t* output, uint8_t* input, uint32_t length,
// If iv is passed as 0, we continue to encrypt without re-setting the Iv
if(iv != 0)
{
state.Iv = (uint8_t*)iv;
state.Iv = iv;
}
for(i = 0; i < length; i += KEYLEN)

View File

@ -210,6 +210,15 @@ atomics_cobalt_read_dive (dc_device_t *abstract, dc_buffer_t *buffer, int init,
return DC_STATUS_NOMEMORY;
}
// Adjust the maximum value to take into account the two byte checksum and
// the 8 byte serial number. Those extra bytes are not stored inside the
// dive header and are added dynamically during the data transfer. Since we
// don't know the total number of dives in advance, we can't calculate the
// total number of extra bytes and adjust the maximum on the fly.
if (progress) {
progress->maximum += 2 + 8;
}
// Send the command to the dive computer.
unsigned char bRequest = 0;
if (device->simulation)
@ -348,12 +357,6 @@ atomics_cobalt_device_foreach (dc_device_t *abstract, dc_dive_callback_t callbac
return DC_STATUS_SUCCESS;
}
// Adjust the maximum value to take into account the two checksum bytes
// for the next dive. Since we don't know the total number of dives in
// advance, we can't calculate the total number of checksum bytes and
// adjust the maximum on the fly.
progress.maximum += 2;
ndives++;
}

View File

@ -130,11 +130,13 @@ static const dc_descriptor_t g_descriptors[] = {
{"Suunto", "DX", DC_FAMILY_SUUNTO_D9, 0x1C, DC_TRANSPORT_SERIAL, NULL},
{"Suunto", "Vyper Novo", DC_FAMILY_SUUNTO_D9, 0x1D, DC_TRANSPORT_SERIAL, NULL},
{"Suunto", "Zoop Novo", DC_FAMILY_SUUNTO_D9, 0x1E, DC_TRANSPORT_SERIAL, NULL},
{"Suunto", "Zoop Novo", DC_FAMILY_SUUNTO_D9, 0x1F, DC_TRANSPORT_SERIAL, NULL},
{"Suunto", "D4f", DC_FAMILY_SUUNTO_D9, 0x20, DC_TRANSPORT_SERIAL, NULL},
/* Suunto EON Steel */
{"Suunto", "EON Steel", DC_FAMILY_SUUNTO_EONSTEEL, 0, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_suunto},
{"Suunto", "EON Core", DC_FAMILY_SUUNTO_EONSTEEL, 1, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_suunto},
{"Suunto", "D5", DC_FAMILY_SUUNTO_EONSTEEL, 2, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_suunto},
{"Suunto", "EON Steel Black", DC_FAMILY_SUUNTO_EONSTEEL, 3, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_suunto},
/* Uwatec Aladin */
{"Uwatec", "Aladin Air Twin", DC_FAMILY_UWATEC_ALADIN, 0x1C, DC_TRANSPORT_SERIAL, NULL},
{"Uwatec", "Aladin Sport Plus", DC_FAMILY_UWATEC_ALADIN, 0x3E, DC_TRANSPORT_SERIAL, NULL},
@ -268,6 +270,7 @@ static const dc_descriptor_t g_descriptors[] = {
{"Oceanic", "Veo 4.0", DC_FAMILY_OCEANIC_ATOM2, 0x4654, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic},
{"Sherwood", "Wisdom 4", DC_FAMILY_OCEANIC_ATOM2, 0x4655, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic},
{"Oceanic", "Pro Plus 4", DC_FAMILY_OCEANIC_ATOM2, 0x4656, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic},
{"Sherwood", "Beacon", DC_FAMILY_OCEANIC_ATOM2, 0x4742, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic},
{"Aqualung", "i470TC", DC_FAMILY_OCEANIC_ATOM2, 0x4743, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic},
/* Mares Nemo */
{"Mares", "Nemo", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL},
@ -389,12 +392,12 @@ static const dc_descriptor_t g_descriptors[] = {
{"Ratio", "iDive Color Deep", DC_FAMILY_DIVESYSTEM_IDIVE, 0x54, DC_TRANSPORT_SERIAL, NULL},
{"Ratio", "iDive Color Tech+",DC_FAMILY_DIVESYSTEM_IDIVE, 0x55, DC_TRANSPORT_SERIAL, NULL},
{"Ratio", "iDive Color Reb", DC_FAMILY_DIVESYSTEM_IDIVE, 0x56, DC_TRANSPORT_SERIAL, NULL},
{"Ratio", "iX3M 2021 GPS Fancy", DC_FAMILY_DIVESYSTEM_IDIVE, 0x60, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_divesystem},
{"Ratio", "iX3M 2021 GPS Easy", DC_FAMILY_DIVESYSTEM_IDIVE, 0x61, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_divesystem},
{"Ratio", "iX3M 2021 GPS Pro ", DC_FAMILY_DIVESYSTEM_IDIVE, 0x62, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_divesystem},
{"Ratio", "iX3M 2021 GPS Deep", DC_FAMILY_DIVESYSTEM_IDIVE, 0x63, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_divesystem},
{"Ratio", "iX3M 2021 GPS Tech+", DC_FAMILY_DIVESYSTEM_IDIVE, 0x64, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_divesystem},
{"Ratio", "iX3M 2021 GPS Reb", DC_FAMILY_DIVESYSTEM_IDIVE, 0x65, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_divesystem},
{"Ratio", "iX3M 2021 GPS Fancy", DC_FAMILY_DIVESYSTEM_IDIVE, 0x60, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_divesystem},
{"Ratio", "iX3M 2021 GPS Easy", DC_FAMILY_DIVESYSTEM_IDIVE, 0x61, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_divesystem},
{"Ratio", "iX3M 2021 GPS Pro ", DC_FAMILY_DIVESYSTEM_IDIVE, 0x62, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_divesystem},
{"Ratio", "iX3M 2021 GPS Deep", DC_FAMILY_DIVESYSTEM_IDIVE, 0x63, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_divesystem},
{"Ratio", "iX3M 2021 GPS Tech+", DC_FAMILY_DIVESYSTEM_IDIVE, 0x64, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_divesystem},
{"Ratio", "iX3M 2021 GPS Reb", DC_FAMILY_DIVESYSTEM_IDIVE, 0x65, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_divesystem},
{"Ratio", "iX3M 2021 Pro Fancy", DC_FAMILY_DIVESYSTEM_IDIVE, 0x70, DC_TRANSPORT_SERIAL, NULL},
{"Ratio", "iX3M 2021 Pro Easy", DC_FAMILY_DIVESYSTEM_IDIVE, 0x71, DC_TRANSPORT_SERIAL, NULL},
{"Ratio", "iX3M 2021 Pro Pro", DC_FAMILY_DIVESYSTEM_IDIVE, 0x72, DC_TRANSPORT_SERIAL, NULL},
@ -419,6 +422,8 @@ static const dc_descriptor_t g_descriptors[] = {
{"Liquivision", "Xeo", DC_FAMILY_LIQUIVISION_LYNX, 1, DC_TRANSPORT_SERIAL, NULL},
{"Liquivision", "Lynx", DC_FAMILY_LIQUIVISION_LYNX, 2, DC_TRANSPORT_SERIAL, NULL},
{"Liquivision", "Kaon", DC_FAMILY_LIQUIVISION_LYNX, 3, DC_TRANSPORT_SERIAL, NULL},
/* Sporasub */
{"Sporasub", "SP2", DC_FAMILY_SPORASUB_SP2, 0, DC_TRANSPORT_SERIAL, NULL},
// Not merged upstream yet
/* Garmin -- model numbers as defined in FIT format; USB product id is (0x4000 | model) */
@ -501,14 +506,16 @@ dc_match_oceanic (const void *key, const void *value)
0
};
return dc_match_number_with_prefix (key, &prefix);
const char *p = prefix;
return dc_match_number_with_prefix (key, &p);
}
static int
dc_filter_internal (const void *key, const void *values, size_t count, size_t size, dc_match_t match, void *params_dst, const void *params_src, size_t params_size)
{
if (key == NULL)
return 0;
return 1;
for (size_t i = 0; i < count; ++i) {
if (match (key, (const unsigned char *) values + i * size)) {
@ -571,11 +578,13 @@ static int dc_filter_suunto (dc_transport_t transport, const void *userdata, voi
{0x1493, 0x0030}, // Eon Steel
{0x1493, 0x0033}, // Eon Core
{0x1493, 0x0035}, // D5
{0x1493, 0x0036}, // EON Steel Black
};
static const char * const bluetooth[] = {
"EON Steel",
"EON Core",
"Suunto D5",
"EON Steel Black",
};
if (transport == DC_TRANSPORT_USBHID) {
@ -660,7 +669,7 @@ static int dc_filter_divesystem (dc_transport_t transport, const void *userdata,
"IX5M",
};
if (transport == DC_TRANSPORT_BLUETOOTH) {
if (transport == DC_TRANSPORT_BLUETOOTH || transport == DC_TRANSPORT_BLE) {
return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_number_with_prefix);
}
@ -680,6 +689,7 @@ static int dc_filter_oceanic (dc_transport_t transport, const void *userdata, vo
0x4654, // Oceanic Veo 4.0
0x4655, // Sherwood Wisdom 4
0x4656, // Oceanic Pro Plus 4
0x4742, // Sherwood Beacon
0x4743, // Aqualung i470TC
};
@ -856,7 +866,7 @@ dc_descriptor_get_transports (dc_descriptor_t *descriptor)
int
dc_descriptor_filter (dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata, void *params)
{
if (descriptor == NULL || descriptor->filter == NULL)
if (descriptor == NULL || descriptor->filter == NULL || userdata == NULL)
return 1;
return descriptor->filter (transport, userdata, params);

View File

@ -59,6 +59,7 @@
#include "tecdiving_divecomputereu.h"
#include "mclean_extreme.h"
#include "liquivision_lynx.h"
#include "sporasub_sp2.h"
// Not merged upstream yet
#include "garmin.h"
@ -224,6 +225,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr
case DC_FAMILY_LIQUIVISION_LYNX:
rc = liquivision_lynx_device_open (&device, context, iostream);
break;
case DC_FAMILY_SPORASUB_SP2:
rc = sporasub_sp2_device_open (&device, context, iostream);
break;
default:
return DC_STATUS_INVALIDARGS;

View File

@ -202,11 +202,7 @@ dc_irda_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descriptor_
// modified by the previous getsockopt call.
size = sizeof (data);
#ifdef _WIN32
Sleep (1000);
#else
sleep (1);
#endif
dc_platform_sleep (1000);
}
S_CLOSE (fd);

View File

@ -42,7 +42,7 @@
#define CMD_FIRMWARE 0xAD
#define SZ_PACKET 512
#define SZ_FINGERPRINT 7
#define SZ_FINGERPRINT 4
#define SZ_CFG 0x002D
#define SZ_COMPUTER (SZ_CFG + 0x6A)
#define SZ_HEADER (SZ_CFG + 0x31)
@ -580,10 +580,10 @@ mclean_extreme_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback
unsigned char *data = dc_buffer_get_data(buffer);
unsigned int size = dc_buffer_get_size(buffer);
if (memcmp(data, device->fingerprint, sizeof(device->fingerprint)) == 0)
if (memcmp(data + SZ_CFG, device->fingerprint, sizeof(device->fingerprint)) == 0)
break;
if (callback && !callback (data, size, data, sizeof(device->fingerprint), userdata)) {
if (callback && !callback (data, size, data + SZ_CFG, sizeof(device->fingerprint), userdata)) {
break;
}
}

View File

@ -41,6 +41,7 @@
#define SAGE 0x4647
#define I770R 0x4651
#define GEO40 0x4653
#define BEACON 0x4742
#define MAXPACKET 256
#define MAXRETRIES 2
@ -493,6 +494,7 @@ static const oceanic_common_version_t versions[] = {
{"OCEANVTX \0\0 2048", 0, &aeris_a300cs_layout},
{"AQUAI750 \0\0 2048", 0, &aeris_a300cs_layout},
{"SWDRAGON \0\0 2048", 0, &aeris_a300cs_layout},
{"SWBEACON \0\0 2048", 0, &aeris_a300cs_layout},
{"AQUAI450 \0\0 2048", 0, &aqualung_i450t_layout},
@ -904,7 +906,7 @@ oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream
}
if (dc_iostream_get_transport (device->iostream) == DC_TRANSPORT_BLE &&
model != PROPLUSX && model != SAGE ) {
model != PROPLUSX && model != SAGE && model != BEACON) {
status = oceanic_atom2_ble_handshake(device);
if (status != DC_STATUS_SUCCESS) {
goto error_free;

View File

@ -99,6 +99,7 @@
#define VEO40 0x4654
#define WISDOM4 0x4655
#define PROPLUS4 0x4656
#define BEACON 0x4742
#define I470TC 0x4743
#define NORMAL 0
@ -192,7 +193,8 @@ oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, unsigned
parser->footersize = 0;
} else if (model == A300CS || model == VTX ||
model == I450T || model == I750TC ||
model == I770R || model == SAGE) {
model == I770R || model == SAGE ||
model == BEACON) {
parser->headersize = 5 * PAGESIZE;
} else if (model == PROPLUSX) {
parser->headersize = 3 * PAGESIZE;
@ -347,6 +349,7 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim
case PROPLUSX:
case I770R:
case SAGE:
case BEACON:
datetime->year = (p[10]) + 2000;
datetime->month = (p[8]);
datetime->day = (p[9]);
@ -468,7 +471,8 @@ oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser)
he_offset = 0x48;
ngasmixes = 6;
} else if (parser->model == A300CS || parser->model == VTX ||
parser->model == I750TC || parser->model == SAGE) {
parser->model == I750TC || parser->model == SAGE ||
parser->model == BEACON) {
o2_offset = 0x2A;
if (data[0x39] & 0x04) {
ngasmixes = 1;
@ -690,7 +694,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
if (parser->model == A300CS || parser->model == VTX ||
parser->model == I450T || parser->model == I750TC ||
parser->model == PROPLUSX || parser->model == I770R ||
parser->model == SAGE)
parser->model == SAGE || parser->model == BEACON)
idx = 0x1f;
switch (data[idx] & 0x03) {
case 0:
@ -747,7 +751,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
parser->model == VTX || parser->model == I450T ||
parser->model == I750TC || parser->model == PROPLUSX ||
parser->model == I770R || parser->model == I470TC ||
parser->model == SAGE) {
parser->model == SAGE || parser->model == BEACON) {
samplesize = PAGESIZE;
}
@ -833,7 +837,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
pressure = (((data[offset + 7] << 8) + data[offset + 6]) & 0x0FFF);
} else if (parser->model == A300CS || parser->model == VTX ||
parser->model == I750TC || parser->model == I770R ||
parser->model == SAGE) {
parser->model == SAGE || parser->model == BEACON) {
// Tank pressure (1 psi) and number (one based index)
tank = (data[offset + 1] & 0x03) - 1;
pressure = ((data[offset + 7] << 8) + data[offset + 6]) & 0x0FFF;
@ -935,7 +939,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
temperature = ((data[offset + 7] & 0xF0) >> 4) | ((data[offset + 7] & 0x0C) << 2) | ((data[offset + 5] & 0x0C) << 4);
} else if (parser->model == A300CS || parser->model == VTX ||
parser->model == I750TC || parser->model == PROPLUSX ||
parser->model == I770R|| parser->model == SAGE) {
parser->model == I770R|| parser->model == SAGE ||
parser->model == BEACON) {
temperature = data[offset + 11];
} else {
unsigned int sign;
@ -980,7 +985,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
else if (parser->model == TX1 || parser->model == A300CS ||
parser->model == VTX || parser->model == I750TC ||
parser->model == PROPLUSX || parser->model == I770R ||
parser->model == SAGE)
parser->model == SAGE || parser->model == BEACON)
pressure = array_uint16_le (data + offset + 4);
else
pressure -= data[offset + 1];
@ -1032,7 +1037,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
unsigned int decostop = 0, decotime = 0;
if (parser->model == A300CS || parser->model == VTX ||
parser->model == I750TC || parser->model == SAGE ||
parser->model == PROPLUSX || parser->model == I770R) {
parser->model == PROPLUSX || parser->model == I770R ||
parser->model == BEACON) {
decostop = (data[offset + 15] & 0x70) >> 4;
decotime = array_uint16_le(data + offset + 6) & 0x03FF;
have_deco = 1;

View File

@ -181,7 +181,7 @@ oceanic_vtpro_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns
maxdepth = data[footer + 1];
} else {
oxygen = data[footer + 3];
maxdepth = array_uint16_le(data + footer + 0) & 0x0FFF;
maxdepth = array_uint16_le(data + footer + 0) & 0x01FF;
}
dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
@ -359,6 +359,21 @@ oceanic_vtpro_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
sample.temperature = (temperature - 32.0) * (5.0 / 9.0);
if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
// NDL / Deco
if (parser->model != AERIS500AI) {
unsigned int decostop = (data[offset + 5] & 0xF0) >> 4;
unsigned int decotime = array_uint16_le(data + offset + 4) & 0x0FFF;
if (decostop) {
sample.deco.type = DC_DECO_DECOSTOP;
sample.deco.depth = decostop * 10 * FEET;
} else {
sample.deco.type = DC_DECO_NDL;
sample.deco.depth = 0.0;
}
sample.deco.time = decotime * 60;
if (callback) callback (DC_SAMPLE_DECO, sample, userdata);
}
offset += PAGESIZE / 2;
}

View File

@ -59,6 +59,7 @@
#include "tecdiving_divecomputereu.h"
#include "mclean_extreme.h"
#include "liquivision_lynx.h"
#include "sporasub_sp2.h"
// Not merged upstream yet
#include "garmin.h"
@ -185,6 +186,9 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa
case DC_FAMILY_LIQUIVISION_LYNX:
rc = liquivision_lynx_parser_create (&parser, context, model);
break;
case DC_FAMILY_SPORASUB_SP2:
rc = sporasub_sp2_parser_create (&parser, context);
break;
default:
return DC_STATUS_INVALIDARGS;

51
src/platform.c Normal file
View File

@ -0,0 +1,51 @@
/*
* libdivecomputer
*
* Copyright (C) 2021 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 _WIN32
#define WIN32_LEAN_AND_MEAN
#define NOGDI
#include <windows.h>
#else
#include <time.h>
#include <errno.h>
#endif
#include "platform.h"
int
dc_platform_sleep (unsigned int milliseconds)
{
#ifdef _WIN32
Sleep (milliseconds);
#else
struct timespec ts;
ts.tv_sec = (milliseconds / 1000);
ts.tv_nsec = (milliseconds % 1000) * 1000000;
while (nanosleep (&ts, &ts) != 0) {
if (errno != EINTR ) {
return -1;
}
}
#endif
return 0;
}

View File

@ -45,6 +45,8 @@ extern "C" {
#endif
#endif
int dc_platform_sleep(unsigned int milliseconds);
#ifdef __cplusplus
}
#endif /* __cplusplus */

View File

@ -30,7 +30,6 @@
#include <fcntl.h> // fcntl
#include <termios.h> // tcgetattr, tcsetattr, cfsetispeed, cfsetospeed, tcflush, tcsendbreak
#include <sys/ioctl.h> // ioctl
#include <time.h> // nanosleep
#ifdef HAVE_LINUX_SERIAL_H
#include <linux/serial.h>
#endif
@ -59,6 +58,7 @@
#include "iostream-private.h"
#include "iterator-private.h"
#include "descriptor-private.h"
#include "platform.h"
#include "timer.h"
#define DIRNAME "/dev"
@ -995,17 +995,11 @@ dc_serial_get_lines (dc_iostream_t *abstract, unsigned int *value)
static dc_status_t
dc_serial_sleep (dc_iostream_t *abstract, unsigned int timeout)
{
struct timespec ts;
ts.tv_sec = (timeout / 1000);
ts.tv_nsec = (timeout % 1000) * 1000000;
while (nanosleep (&ts, &ts) != 0) {
if (dc_platform_sleep (timeout) != 0) {
int errcode = errno;
if (errcode != EINTR ) {
SYSERROR (abstract->context, errcode);
return syserror (errcode);
}
}
return DC_STATUS_SUCCESS;
}

View File

@ -32,6 +32,7 @@
#include "iostream-private.h"
#include "iterator-private.h"
#include "descriptor-private.h"
#include "platform.h"
static dc_status_t dc_serial_iterator_next (dc_iterator_t *iterator, void *item);
static dc_status_t dc_serial_iterator_free (dc_iterator_t *iterator);
@ -836,7 +837,11 @@ dc_serial_get_lines (dc_iostream_t *abstract, unsigned int *value)
static dc_status_t
dc_serial_sleep (dc_iostream_t *abstract, unsigned int timeout)
{
Sleep (timeout);
if (dc_platform_sleep (timeout) != 0) {
DWORD errcode = GetLastError ();
SYSERROR (abstract->context, errcode);
return syserror (errcode);
}
return DC_STATUS_SUCCESS;
}

View File

@ -238,6 +238,7 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call
case 0x0C0C:
case 0x0C0D:
case 0x0D0D:
case 0x7C2D:
model = PERDIXAI;
break;
case 0x0F0F:

View File

@ -20,6 +20,7 @@
*/
#include "socket.h"
#include "platform.h"
#include "common-private.h"
#include "context-private.h"
@ -357,21 +358,11 @@ dc_socket_ioctl (dc_iostream_t *abstract, unsigned int request, void *data, size
dc_status_t
dc_socket_sleep (dc_iostream_t *abstract, unsigned int timeout)
{
#ifdef _WIN32
Sleep (timeout);
#else
struct timespec ts;
ts.tv_sec = (timeout / 1000);
ts.tv_nsec = (timeout % 1000) * 1000000;
while (nanosleep (&ts, &ts) != 0) {
int errcode = errno;
if (errcode != EINTR ) {
if (dc_platform_sleep (timeout) != 0) {
s_errcode_t errcode = S_ERRNO;
SYSERROR (abstract->context, errcode);
return dc_socket_syserror(errcode);
}
}
#endif
return DC_STATUS_SUCCESS;
}

489
src/sporasub_sp2.c Normal file
View File

@ -0,0 +1,489 @@
/*
* libdivecomputer
*
* Copyright (C) 2021 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 <string.h> // memcpy, memcmp
#include <stdlib.h> // malloc, free
#include "sporasub_sp2.h"
#include "context-private.h"
#include "device-private.h"
#include "checksum.h"
#include "array.h"
#define ISINSTANCE(device) dc_device_isinstance((device), &sporasub_sp2_device_vtable)
#define SZ_MEMORY 0x10000
#define RB_PROFILE_BEGIN 0x0060
#define RB_PROFILE_END SZ_MEMORY
#define MAXRETRIES 4
#define MAXPACKET 256
#define HEADER_HI 0xA0
#define HEADER_LO 0xA2
#define TRAILER_HI 0xB0
#define TRAILER_LO 0xB3
#define CMD_VERSION 0x10
#define CMD_READ 0x12
#define CMD_TIMESYNC 0x39
#define SZ_VERSION 23
#define SZ_READ 128
#define SZ_HEADER 32
#define SZ_SAMPLE 4
typedef struct sporasub_sp2_device_t {
dc_device_t base;
dc_iostream_t *iostream;
unsigned char version[SZ_VERSION];
unsigned char fingerprint[6];
} sporasub_sp2_device_t;
static dc_status_t sporasub_sp2_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size);
static dc_status_t sporasub_sp2_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size);
static dc_status_t sporasub_sp2_device_dump (dc_device_t *abstract, dc_buffer_t *buffer);
static dc_status_t sporasub_sp2_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata);
static dc_status_t sporasub_sp2_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime);
static const dc_device_vtable_t sporasub_sp2_device_vtable = {
sizeof(sporasub_sp2_device_t),
DC_FAMILY_SPORASUB_SP2,
sporasub_sp2_device_set_fingerprint, /* set_fingerprint */
sporasub_sp2_device_read, /* read */
NULL, /* write */
sporasub_sp2_device_dump, /* dump */
sporasub_sp2_device_foreach, /* foreach */
sporasub_sp2_device_timesync, /* timesync */
NULL /* close */
};
static unsigned int
iceil (unsigned int x, unsigned int n)
{
// Round up to next higher multiple.
return ((x + n - 1) / n) * n;
}
static dc_status_t
sporasub_sp2_send (sporasub_sp2_device_t *device, unsigned char command, const unsigned char data[], unsigned int size)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_device_t *abstract = (dc_device_t *) device;
if (size > MAXPACKET) {
return DC_STATUS_INVALIDARGS;
}
unsigned int len = size + 1;
unsigned int csum = checksum_add_uint16 (data, size, command);
unsigned char packet[MAXPACKET + 9] = {0};
packet[0] = HEADER_HI;
packet[1] = HEADER_LO;
packet[2] = (len >> 8) & 0xFF;
packet[3] = (len ) & 0xFF;
packet[4] = command;
if (size) {
memcpy(packet + 5, data, size);
}
packet[size + 5] = (csum >> 8) & 0xFF;
packet[size + 6] = (csum ) & 0xFF;
packet[size + 7] = TRAILER_HI;
packet[size + 8] = TRAILER_LO;
// Send the command to the device.
status = dc_iostream_write (device->iostream, packet, size + 9, NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to send the command.");
return status;
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
sporasub_sp2_receive (sporasub_sp2_device_t *device, unsigned char command, unsigned char data[], unsigned int size)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_device_t *abstract = (dc_device_t *) device;
if (size > MAXPACKET) {
return DC_STATUS_INVALIDARGS;
}
// Receive the answer of the device.
unsigned char packet[MAXPACKET + 9] = {0};
status = dc_iostream_read (device->iostream, packet, size + 9, NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to receive the answer.");
return status;
}
// Verify the header and trailer of the packet.
if (packet[0] != HEADER_HI || packet[1] != HEADER_LO ||
packet[size + 7] != TRAILER_HI || packet[size + 8] != TRAILER_LO) {
ERROR (abstract->context, "Unexpected answer header/trailer byte.");
return DC_STATUS_PROTOCOL;
}
// Verify the packet length.
unsigned int len = array_uint16_be (packet + 2);
if (len != size + 1) {
ERROR (abstract->context, "Unexpected packet length.");
return DC_STATUS_PROTOCOL;
}
// Verify the command byte.
if (packet[4] != command) {
ERROR (abstract->context, "Unexpected answer header/trailer byte.");
return DC_STATUS_PROTOCOL;
}
// Verify the checksum of the packet.
unsigned short crc = array_uint16_be (packet + size + 5);
unsigned short ccrc = checksum_add_uint16 (packet + 4, size + 1, 0);
if (crc != ccrc) {
ERROR (abstract->context, "Unexpected answer checksum.");
return DC_STATUS_PROTOCOL;
}
if (size) {
memcpy (data, packet + 5, size);
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
sporasub_sp2_packet (sporasub_sp2_device_t *device, unsigned char cmd, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_device_t *abstract = (dc_device_t *) device;
if (device_is_cancelled (abstract))
return DC_STATUS_CANCELLED;
// Send the command to the device.
status = sporasub_sp2_send (device, cmd, command, csize);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to send the command.");
return status;
}
// Receive the answer of the device.
status = sporasub_sp2_receive (device, cmd + 1, answer, asize);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to receive the answer.");
return status;
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
sporasub_sp2_transfer (sporasub_sp2_device_t *device, unsigned char cmd, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize)
{
unsigned int nretries = 0;
dc_status_t rc = DC_STATUS_SUCCESS;
while ((rc = sporasub_sp2_packet (device, cmd, command, csize, answer, asize)) != 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;
// Discard any garbage bytes.
dc_iostream_sleep (device->iostream, 100);
dc_iostream_purge (device->iostream, DC_DIRECTION_INPUT);
}
return rc;
}
dc_status_t
sporasub_sp2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream)
{
dc_status_t status = DC_STATUS_SUCCESS;
sporasub_sp2_device_t *device = NULL;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
// Allocate memory.
device = (sporasub_sp2_device_t *) dc_device_allocate (context, &sporasub_sp2_device_vtable);
if (device == NULL) {
ERROR (context, "Failed to allocate memory.");
return DC_STATUS_NOMEMORY;
}
// Set the default values.
device->iostream = iostream;
memset (device->fingerprint, 0, sizeof (device->fingerprint));
// Set the serial communication protocol (460800 8N1).
status = dc_iostream_configure (device->iostream, 460800, 8, DC_PARITY_NONE, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE);
if (status != DC_STATUS_SUCCESS) {
ERROR (context, "Failed to set the terminal attributes.");
goto error_free;
}
// Set the timeout for receiving data (1000 ms).
status = dc_iostream_set_timeout (device->iostream, 1000);
if (status != DC_STATUS_SUCCESS) {
ERROR (context, "Failed to set the timeout.");
goto error_free;
}
// Clear the RTS line.
status = dc_iostream_set_rts (device->iostream, 0);
if (status != DC_STATUS_SUCCESS) {
ERROR (context, "Failed to clear the RTS line.");
goto error_free;
}
// Set the DTR line.
status = dc_iostream_set_dtr (device->iostream, 1);
if (status != DC_STATUS_SUCCESS) {
ERROR (context, "Failed to set the DTR line.");
goto error_free;
}
dc_iostream_sleep (device->iostream, 100);
dc_iostream_purge (device->iostream, DC_DIRECTION_ALL);
// Read the version packet.
status = sporasub_sp2_packet(device, CMD_VERSION, NULL, 0, device->version, sizeof(device->version));
if (status != DC_STATUS_SUCCESS) {
ERROR (context, "Failed to read the version packet.");
goto error_free;
}
*out = (dc_device_t *) device;
return DC_STATUS_SUCCESS;
error_free:
dc_device_deallocate ((dc_device_t *) device);
return status;
}
static dc_status_t
sporasub_sp2_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size)
{
sporasub_sp2_device_t *device = (sporasub_sp2_device_t *) abstract;
if (size && size != sizeof (device->fingerprint))
return DC_STATUS_INVALIDARGS;
if (size)
memcpy (device->fingerprint, data, sizeof (device->fingerprint));
else
memset (device->fingerprint, 0, sizeof (device->fingerprint));
return DC_STATUS_SUCCESS;
}
static dc_status_t
sporasub_sp2_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size)
{
dc_status_t status = DC_STATUS_SUCCESS;
sporasub_sp2_device_t *device = (sporasub_sp2_device_t *) abstract;
unsigned int nbytes = 0;
while (nbytes < size) {
// Calculate the packet size.
unsigned int len = size - nbytes;
if (len > SZ_READ)
len = SZ_READ;
// Build the raw command.
unsigned char command[] = {
(address ) & 0xFF,
(address >> 8) & 0xFF,
len};
// Send the command and receive the answer.
status = sporasub_sp2_transfer (device, CMD_READ, command, sizeof(command), data + nbytes, len);
if (status != DC_STATUS_SUCCESS)
return status;
nbytes += len;
address += len;
data += len;
}
return status;
}
static dc_status_t
sporasub_sp2_device_dump (dc_device_t *abstract, dc_buffer_t *buffer)
{
// Allocate the required amount of memory.
if (!dc_buffer_resize (buffer, SZ_MEMORY)) {
ERROR (abstract->context, "Insufficient buffer space available.");
return DC_STATUS_NOMEMORY;
}
return device_dump_read (abstract, dc_buffer_get_data (buffer),
dc_buffer_get_size (buffer), SZ_READ);
}
static dc_status_t
sporasub_sp2_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
{
dc_status_t status = DC_STATUS_SUCCESS;
sporasub_sp2_device_t *device = (sporasub_sp2_device_t *) abstract;
// Emit a device info event.
dc_event_devinfo_t devinfo;
devinfo.model = 0;
devinfo.firmware = 0;
devinfo.serial = array_uint16_be (device->version + 1);
device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo);
// Emit a vendor event.
dc_event_vendor_t vendor;
vendor.data = device->version;
vendor.size = sizeof (device->version);
device_event_emit (abstract, DC_EVENT_VENDOR, &vendor);
dc_buffer_t *buffer = dc_buffer_new (SZ_MEMORY);
if (buffer == NULL) {
status = DC_STATUS_NOMEMORY;
goto error_exit;
}
status = sporasub_sp2_device_dump (abstract, buffer);
if (status != DC_STATUS_SUCCESS) {
goto error_free_buffer;
}
unsigned char *data = dc_buffer_get_data (buffer);
// Get the number of dives.
unsigned int ndives = array_uint16_le (data + 0x02);
// Get the profile pointer.
unsigned int eop = array_uint16_le (data + 0x04);
if (eop < RB_PROFILE_BEGIN || eop > RB_PROFILE_END) {
ERROR (abstract->context, "Invalid profile pointer (0x%04x).", eop);
status = DC_STATUS_DATAFORMAT;
goto error_free_buffer;
}
unsigned short *logbook = (unsigned short *) malloc(ndives * sizeof (unsigned short));
if (logbook == NULL) {
ERROR (abstract->context, "Out of memory.");
status = DC_STATUS_NOMEMORY;
goto error_free_buffer;
}
// Find all dives.
unsigned int count = 0;
unsigned int address = RB_PROFILE_BEGIN;
while (address + SZ_HEADER <= RB_PROFILE_END && count < ndives) {
if (address == eop) {
WARNING (abstract->context, "Reached end of profile pointer.");
break;
}
// Get the dive length.
unsigned int nsamples = array_uint16_le (data + address);
unsigned int length = SZ_HEADER + nsamples * SZ_SAMPLE;
if (address + length > RB_PROFILE_END) {
WARNING (abstract->context, "Reached end of memory.");
break;
}
// Store the address.
logbook[count] = address;
count++;
// The start of the next dive is always aligned to 32 bytes.
address += iceil (length, SZ_HEADER);
}
// Process the dives in reverse order (newest first).
for (unsigned int i = 0; i < count; ++i) {
unsigned int idx = count - 1 - i;
unsigned int offset = logbook[idx];
// Get the dive length.
unsigned int nsamples = array_uint16_le (data + offset);
unsigned int length = SZ_HEADER + nsamples * SZ_SAMPLE;
// Check the fingerprint data.
if (memcmp (data + offset + 2, device->fingerprint, sizeof (device->fingerprint)) == 0)
break;
if (callback && !callback (data + offset, length, data + offset + 2, sizeof (device->fingerprint), userdata)) {
break;
}
}
free (logbook);
error_free_buffer:
dc_buffer_free (buffer);
error_exit:
return status;
}
static dc_status_t
sporasub_sp2_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime)
{
dc_status_t status = DC_STATUS_SUCCESS;
sporasub_sp2_device_t *device = (sporasub_sp2_device_t *) abstract;
if (datetime == NULL || datetime->year < 2000) {
ERROR (abstract->context, "Invalid parameter specified.");
return DC_STATUS_INVALIDARGS;
}
// Build the raw command.
unsigned char command[] = {
datetime->year - 2000,
datetime->month,
datetime->day,
datetime->hour,
datetime->minute,
datetime->second};
// Send the command and receive the answer.
unsigned char answer[1] = {0};
status = sporasub_sp2_transfer (device, CMD_TIMESYNC, command, sizeof(command), answer, sizeof(answer));
if (status != DC_STATUS_SUCCESS)
return status;
// Verify the response code.
if (answer[0] != 0) {
ERROR (abstract->context, "Invalid response code 0x%02x returned.", answer[0]);
return DC_STATUS_PROTOCOL;
}
return DC_STATUS_SUCCESS;
}

43
src/sporasub_sp2.h Normal file
View File

@ -0,0 +1,43 @@
/*
* libdivecomputer
*
* Copyright (C) 2021 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 SPORASUB_SP2_H
#define SPORASUB_SP2_H
#include <libdivecomputer/context.h>
#include <libdivecomputer/iostream.h>
#include <libdivecomputer/device.h>
#include <libdivecomputer/parser.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
dc_status_t
sporasub_sp2_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream);
dc_status_t
sporasub_sp2_parser_create (dc_parser_t **parser, dc_context_t *context);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* SPORASUB_SP2_H */

200
src/sporasub_sp2_parser.c Normal file
View File

@ -0,0 +1,200 @@
/*
* libdivecomputer
*
* Copyright (C) 2021 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 <stdlib.h>
#include "sporasub_sp2.h"
#include "context-private.h"
#include "parser-private.h"
#include "array.h"
#define ISINSTANCE(parser) dc_device_isinstance((parser), &sporasub_sp2_parser_vtable)
#define C_ARRAY_SIZE(array) (sizeof (array) / sizeof *(array))
#define SZ_HEADER 0x20
#define SZ_SAMPLE 0x04
typedef struct sporasub_sp2_parser_t sporasub_sp2_parser_t;
struct sporasub_sp2_parser_t {
dc_parser_t base;
};
static dc_status_t sporasub_sp2_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size);
static dc_status_t sporasub_sp2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime);
static dc_status_t sporasub_sp2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value);
static dc_status_t sporasub_sp2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata);
static const dc_parser_vtable_t sporasub_sp2_parser_vtable = {
sizeof(sporasub_sp2_parser_t),
DC_FAMILY_SPORASUB_SP2,
sporasub_sp2_parser_set_data, /* set_data */
sporasub_sp2_parser_get_datetime, /* datetime */
sporasub_sp2_parser_get_field, /* fields */
sporasub_sp2_parser_samples_foreach, /* samples_foreach */
NULL /* destroy */
};
dc_status_t
sporasub_sp2_parser_create (dc_parser_t **out, dc_context_t *context)
{
sporasub_sp2_parser_t *parser = NULL;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
// Allocate memory.
parser = (sporasub_sp2_parser_t *) dc_parser_allocate (context, &sporasub_sp2_parser_vtable);
if (parser == NULL) {
ERROR (context, "Failed to allocate memory.");
return DC_STATUS_NOMEMORY;
}
*out = (dc_parser_t *) parser;
return DC_STATUS_SUCCESS;
}
static dc_status_t
sporasub_sp2_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size)
{
return DC_STATUS_SUCCESS;
}
static dc_status_t
sporasub_sp2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime)
{
const unsigned char *data = abstract->data;
unsigned int size = abstract->size;
if (size < SZ_HEADER)
return DC_STATUS_DATAFORMAT;
if (datetime) {
datetime->year = data[4] + 2000;
datetime->month = data[3];
datetime->day = data[2];
datetime->hour = data[7];
datetime->minute = data[6];
datetime->second = data[5];
datetime->timezone = DC_TIMEZONE_NONE;
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
sporasub_sp2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value)
{
const unsigned char *data = abstract->data;
unsigned int size = abstract->size;
if (size < SZ_HEADER)
return DC_STATUS_DATAFORMAT;
if (value) {
switch (type) {
case DC_FIELD_DIVETIME:
*((unsigned int *) value) = data[0x08] + data[0x09] * 60;
break;
case DC_FIELD_MAXDEPTH:
*((double *) value) = array_uint16_le (data + 0x14) / 100.0;
break;
case DC_FIELD_DIVEMODE:
*((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE;
break;
case DC_FIELD_TEMPERATURE_MINIMUM:
*((double *) value) = array_uint16_le (data + 0x18) / 10.0;
break;
case DC_FIELD_TEMPERATURE_MAXIMUM:
*((double *) value) = array_uint16_le (data + 0x16) / 10.0;
break;
default:
return DC_STATUS_UNSUPPORTED;
}
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
sporasub_sp2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
{
const unsigned char *data = abstract->data;
unsigned int size = abstract->size;
if (size < SZ_HEADER)
return DC_STATUS_DATAFORMAT;
unsigned int nsamples = array_uint16_le(data);
// Get the sample interval.
unsigned int interval_idx = data[0x1A];
const unsigned int intervals[] = {1, 2, 5, 10};
if (interval_idx >= C_ARRAY_SIZE(intervals)) {
ERROR (abstract->context, "Invalid sample interval index %u", interval_idx);
return DC_STATUS_DATAFORMAT;
}
unsigned int interval = intervals[interval_idx];
unsigned int time = 0;
unsigned int count = 0;
unsigned int offset = SZ_HEADER;
while (offset + SZ_SAMPLE <= size && count < nsamples) {
dc_sample_value_t sample = {0};
unsigned int value = array_uint32_le (data + offset);
unsigned int heartrate = (value & 0xFF000000) >> 24;
unsigned int temperature = (value & 0x00FFC000) >> 14;
unsigned int unknown = (value & 0x00003000) >> 12;
unsigned int depth = (value & 0x00000FFF) >> 0;
// Time (seconds)
time += interval;
sample.time = time;
if (callback) callback (DC_SAMPLE_TIME, sample, userdata);
// Depth (1/100 m)
sample.depth = depth / 100.0;
if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
// Temperature (1/10 °C)
sample.temperature = temperature / 10.0 - 20.0;
if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
// Heartrate
if (heartrate) {
sample.heartbeat = heartrate;
if (callback) callback (DC_SAMPLE_HEARTBEAT, sample, userdata);
}
offset += SZ_SAMPLE;
count++;
}
return DC_STATUS_SUCCESS;
}

View File

@ -38,7 +38,8 @@
#define D9tx 0x1B
#define DX 0x1C
#define VYPERNOVO 0x1D
#define ZOOPNOVO 0x1E
#define ZOOPNOVO_A 0x1E
#define ZOOPNOVO_B 0x1F
#define D4F 0x20
typedef struct suunto_d9_device_t {
@ -100,7 +101,7 @@ suunto_d9_device_autodetect (suunto_d9_device_t *device, unsigned int model)
// Use the model number as a hint to speedup the detection.
unsigned int hint = 0;
if (model == D4i || model == D6i || model == D9tx ||
model == DX || model == VYPERNOVO || model == ZOOPNOVO ||
model == DX || model == VYPERNOVO || model == ZOOPNOVO_A || model == ZOOPNOVO_B ||
model == D4F)
hint = 1;
@ -184,7 +185,7 @@ suunto_d9_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *
// Override the base class values.
model = device->base.version[0];
if (model == D4i || model == D6i || model == D9tx ||
model == VYPERNOVO || model == ZOOPNOVO ||
model == VYPERNOVO || model == ZOOPNOVO_A || model == ZOOPNOVO_B ||
model == D4F)
device->base.layout = &suunto_d9tx_layout;
else if (model == DX)

View File

@ -46,7 +46,8 @@
#define D9tx 0x1B
#define DX 0x1C
#define VYPERNOVO 0x1D
#define ZOOPNOVO 0x1E
#define ZOOPNOVO_A 0x1E
#define ZOOPNOVO_B 0x1F
#define D4F 0x20
#define ID_D6I_V1_MIX2 0x1871C062
@ -144,8 +145,8 @@ suunto_d9_parser_cache (suunto_d9_parser_t *parser)
gasmode_offset = 0x1F;
gasmix_offset = 0x54;
gasmix_count = 8;
} else if (parser->model == D4i || parser->model == ZOOPNOVO ||
parser->model == D4F) {
} else if (parser->model == D4i || parser->model == ZOOPNOVO_A ||
parser->model == ZOOPNOVO_B || parser->model == D4F) {
gasmode_offset = 0x1D;
if (id == ID_D4I_V2)
gasmix_offset = 0x67;
@ -182,8 +183,9 @@ suunto_d9_parser_cache (suunto_d9_parser_t *parser)
config += 1;
} else if (parser->model == HELO2 || parser->model == D4i ||
parser->model == D6i || parser->model == D9tx ||
parser->model == DX || parser->model == ZOOPNOVO ||
parser->model == VYPERNOVO || parser->model == D4F) {
parser->model == DX || parser->model == ZOOPNOVO_A ||
parser->model == ZOOPNOVO_B || parser->model == VYPERNOVO ||
parser->model == D4F) {
config = gasmix_offset + gasmix_count * 6;
}
if (config + 1 > size)
@ -206,8 +208,9 @@ suunto_d9_parser_cache (suunto_d9_parser_t *parser)
for (unsigned int i = 0; i < gasmix_count; ++i) {
if (parser->model == HELO2 || parser->model == D4i ||
parser->model == D6i || parser->model == D9tx ||
parser->model == DX || parser->model == ZOOPNOVO ||
parser->model == VYPERNOVO || parser->model == D4F) {
parser->model == DX || parser->model == ZOOPNOVO_A ||
parser->model == ZOOPNOVO_B || parser->model == VYPERNOVO ||
parser->model == D4F) {
parser->oxygen[i] = data[gasmix_offset + 6 * i + 1];
parser->helium[i] = data[gasmix_offset + 6 * i + 2];
} else {
@ -224,8 +227,9 @@ suunto_d9_parser_cache (suunto_d9_parser_t *parser)
if (parser->model == HELO2) {
parser->gasmix = data[0x26];
} else if (parser->model == D4i || parser->model == D6i ||
parser->model == D9tx || parser->model == ZOOPNOVO ||
parser->model == VYPERNOVO || parser->model == D4F) {
parser->model == D9tx || parser->model == ZOOPNOVO_A ||
parser->model == ZOOPNOVO_B || parser->model == VYPERNOVO ||
parser->model == D4F) {
if (id == ID_D4I_V2 || id == ID_D6I_V2) {
parser->gasmix = data[0x2D];
} else {
@ -312,8 +316,9 @@ suunto_d9_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime)
if (parser->model == HELO2 || parser->model == DX)
offset = 0x17;
else if (parser->model == D4i || parser->model == D6i ||
parser->model == D9tx || parser->model == ZOOPNOVO ||
parser->model == VYPERNOVO || parser->model == D4F)
parser->model == D9tx || parser->model == ZOOPNOVO_A ||
parser->model == ZOOPNOVO_B || parser->model == VYPERNOVO ||
parser->model == D4F)
offset = 0x13;
if (abstract->size < offset + 7)
@ -324,8 +329,8 @@ suunto_d9_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime)
if (datetime) {
if (parser->model == D4i || parser->model == D6i ||
parser->model == D9tx || parser->model == DX ||
parser->model == ZOOPNOVO || parser->model == VYPERNOVO ||
parser->model == D4F) {
parser->model == ZOOPNOVO_A || parser->model == ZOOPNOVO_B ||
parser->model == VYPERNOVO || parser->model == D4F) {
datetime->year = p[0] + (p[1] << 8);
datetime->month = p[2];
datetime->day = p[3];
@ -371,8 +376,8 @@ suunto_d9_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigne
*((unsigned int *) value) = array_uint16_le (data + 0x0B);
else if (parser->model == D4i || parser->model == D6i ||
parser->model == D9tx || parser->model == DX ||
parser->model == ZOOPNOVO || parser->model == VYPERNOVO ||
parser->model == D4F)
parser->model == ZOOPNOVO_A || parser->model == ZOOPNOVO_B ||
parser->model == VYPERNOVO || parser->model == D4F)
*((unsigned int *) value) = array_uint16_le (data + 0x0D);
else if (parser->model == HELO2)
*((unsigned int *) value) = array_uint16_le (data + 0x0D) * 60;
@ -494,8 +499,8 @@ suunto_d9_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t ca
unsigned int interval_sample_offset = 0x18;
if (parser->model == HELO2 || parser->model == D4i ||
parser->model == D6i || parser->model == D9tx ||
parser->model == ZOOPNOVO || parser->model == VYPERNOVO ||
parser->model == D4F)
parser->model == ZOOPNOVO_A || parser->model == ZOOPNOVO_B ||
parser->model == VYPERNOVO || parser->model == D4F)
interval_sample_offset = 0x1E;
else if (parser->model == DX)
interval_sample_offset = 0x22;

View File

@ -96,8 +96,8 @@ uwatec_aladin_device_open (dc_device_t **out, dc_context_t *context, dc_iostream
goto error_free;
}
// Set the timeout for receiving data (INFINITE).
status = dc_iostream_set_timeout (device->iostream, -1);
// Set the timeout for receiving data (3000ms).
status = dc_iostream_set_timeout (device->iostream, 3000);
if (status != DC_STATUS_SUCCESS) {
ERROR (context, "Failed to set the timeout.");
goto error_free;
@ -164,20 +164,24 @@ uwatec_aladin_device_dump (dc_device_t *abstract, dc_buffer_t *buffer)
unsigned char answer[SZ_MEMORY + 2] = {0};
// Receive the header of the package.
for (unsigned int i = 0; i < 4;) {
unsigned int i = 0;
while (i < 4) {
if (device_is_cancelled (abstract))
return DC_STATUS_CANCELLED;
status = dc_iostream_read (device->iostream, answer + i, 1, NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to receive the answer.");
if (status != DC_STATUS_TIMEOUT)
return status;
}
if (answer[i] == (i < 3 ? 0x55 : 0x00)) {
i++; // Continue.
} else {
i = 0; // Reset.
const unsigned char expected = i < 3 ? 0x55 : 0x00;
if (status != DC_STATUS_SUCCESS || answer[i] != expected) {
device_event_emit (abstract, DC_EVENT_WAITING, NULL);
i = 0;
} else {
i++;
}
}