diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..8fa7569 --- /dev/null +++ b/.github/workflows/build.yml @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..37f578e --- /dev/null +++ b/.github/workflows/release.yml @@ -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 diff --git a/configure.ac b/configure.ac index ca1c5bc..9b43b4b 100644 --- a/configure.ac +++ b/configure.ac @@ -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. +]) diff --git a/examples/common.c b/examples/common.c index b943fd1..1972cfa 100644 --- a/examples/common.c +++ b/examples/common.c @@ -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}, diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h index ac11fa6..6e2c499 100644 --- a/include/libdivecomputer/common.h +++ b/include/libdivecomputer/common.h @@ -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 */ diff --git a/msvc/libdivecomputer.vcproj b/msvc/libdivecomputer.vcproj index 8298de6..0e689a1 100644 --- a/msvc/libdivecomputer.vcproj +++ b/msvc/libdivecomputer.vcproj @@ -398,6 +398,10 @@ RelativePath="..\src\parser.c" > + + @@ -454,6 +458,14 @@ RelativePath="..\src\socket.c" > + + + + @@ -856,6 +868,10 @@ RelativePath="..\src\socket.h" > + + diff --git a/src/Makefile.am b/src/Makefile.am index ab9ea2b..dd88eac 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/aes.c b/src/aes.c index 55eac5c..15a1963 100644 --- a/src/aes.c +++ b/src/aes.c @@ -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) diff --git a/src/atomics_cobalt.c b/src/atomics_cobalt.c index a67ef82..1c2e3a5 100644 --- a/src/atomics_cobalt.c +++ b/src/atomics_cobalt.c @@ -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++; } diff --git a/src/descriptor.c b/src/descriptor.c index 2a75217..a25ebee 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -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", 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); diff --git a/src/device.c b/src/device.c index cb19c8e..991937c 100644 --- a/src/device.c +++ b/src/device.c @@ -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; diff --git a/src/irda.c b/src/irda.c index 2cc58ab..8c5d612 100644 --- a/src/irda.c +++ b/src/irda.c @@ -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); diff --git a/src/mclean_extreme.c b/src/mclean_extreme.c index 5c8060c..b69546f 100644 --- a/src/mclean_extreme.c +++ b/src/mclean_extreme.c @@ -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; } } diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index 333b994..26f5c83 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -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; diff --git a/src/oceanic_atom2_parser.c b/src/oceanic_atom2_parser.c index 0f58e4c..64be773 100644 --- a/src/oceanic_atom2_parser.c +++ b/src/oceanic_atom2_parser.c @@ -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; diff --git a/src/oceanic_vtpro_parser.c b/src/oceanic_vtpro_parser.c index d09b2f9..fff7389 100644 --- a/src/oceanic_vtpro_parser.c +++ b/src/oceanic_vtpro_parser.c @@ -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; } diff --git a/src/parser.c b/src/parser.c index 4a6d0a6..f1e1ebc 100644 --- a/src/parser.c +++ b/src/parser.c @@ -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; diff --git a/src/platform.c b/src/platform.c new file mode 100644 index 0000000..16a3c08 --- /dev/null +++ b/src/platform.c @@ -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 +#else +#include +#include +#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; +} diff --git a/src/platform.h b/src/platform.h index ab82fb7..438aa7d 100644 --- a/src/platform.h +++ b/src/platform.h @@ -45,6 +45,8 @@ extern "C" { #endif #endif +int dc_platform_sleep(unsigned int milliseconds); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/serial_posix.c b/src/serial_posix.c index 4fbcb96..8872982 100644 --- a/src/serial_posix.c +++ b/src/serial_posix.c @@ -30,7 +30,6 @@ #include // fcntl #include // tcgetattr, tcsetattr, cfsetispeed, cfsetospeed, tcflush, tcsendbreak #include // ioctl -#include // nanosleep #ifdef HAVE_LINUX_SERIAL_H #include #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,16 +995,10 @@ 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); - } + SYSERROR (abstract->context, errcode); + return syserror (errcode); } return DC_STATUS_SUCCESS; diff --git a/src/serial_win32.c b/src/serial_win32.c index 2b009f0..aeeeb83 100644 --- a/src/serial_win32.c +++ b/src/serial_win32.c @@ -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; } diff --git a/src/shearwater_petrel.c b/src/shearwater_petrel.c index 9d03d6e..e142dd0 100644 --- a/src/shearwater_petrel.c +++ b/src/shearwater_petrel.c @@ -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: diff --git a/src/socket.c b/src/socket.c index 84d58a4..a99a8f2 100644 --- a/src/socket.c +++ b/src/socket.c @@ -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 ) { - SYSERROR (abstract->context, errcode); - return dc_socket_syserror (errcode); - } + 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; } diff --git a/src/sporasub_sp2.c b/src/sporasub_sp2.c new file mode 100644 index 0000000..c0f6b49 --- /dev/null +++ b/src/sporasub_sp2.c @@ -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 // memcpy, memcmp +#include // 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; +} diff --git a/src/sporasub_sp2.h b/src/sporasub_sp2.h new file mode 100644 index 0000000..de2ca42 --- /dev/null +++ b/src/sporasub_sp2.h @@ -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 +#include +#include +#include + +#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 */ diff --git a/src/sporasub_sp2_parser.c b/src/sporasub_sp2_parser.c new file mode 100644 index 0000000..e036dd2 --- /dev/null +++ b/src/sporasub_sp2_parser.c @@ -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 + +#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; +} diff --git a/src/suunto_d9.c b/src/suunto_d9.c index 8b8320e..17f7e7f 100644 --- a/src/suunto_d9.c +++ b/src/suunto_d9.c @@ -33,13 +33,14 @@ #define C_ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a))) -#define D4i 0x19 -#define D6i 0x1A -#define D9tx 0x1B -#define DX 0x1C -#define VYPERNOVO 0x1D -#define ZOOPNOVO 0x1E -#define D4F 0x20 +#define D4i 0x19 +#define D6i 0x1A +#define D9tx 0x1B +#define DX 0x1C +#define VYPERNOVO 0x1D +#define ZOOPNOVO_A 0x1E +#define ZOOPNOVO_B 0x1F +#define D4F 0x20 typedef struct suunto_d9_device_t { suunto_common2_device_t base; @@ -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) diff --git a/src/suunto_d9_parser.c b/src/suunto_d9_parser.c index 933f7bf..061dd8e 100644 --- a/src/suunto_d9_parser.c +++ b/src/suunto_d9_parser.c @@ -33,21 +33,22 @@ #define MAXPARAMS 3 #define NGASMIXES 11 -#define D9 0x0E -#define D6 0x0F -#define VYPER2 0x10 -#define COBRA2 0x11 -#define D4 0x12 -#define VYPERAIR 0x13 -#define COBRA3 0x14 -#define HELO2 0x15 -#define D4i 0x19 -#define D6i 0x1A -#define D9tx 0x1B -#define DX 0x1C -#define VYPERNOVO 0x1D -#define ZOOPNOVO 0x1E -#define D4F 0x20 +#define D9 0x0E +#define D6 0x0F +#define VYPER2 0x10 +#define COBRA2 0x11 +#define D4 0x12 +#define VYPERAIR 0x13 +#define COBRA3 0x14 +#define HELO2 0x15 +#define D4i 0x19 +#define D6i 0x1A +#define D9tx 0x1B +#define DX 0x1C +#define VYPERNOVO 0x1D +#define ZOOPNOVO_A 0x1E +#define ZOOPNOVO_B 0x1F +#define D4F 0x20 #define ID_D6I_V1_MIX2 0x1871C062 #define ID_D6I_V1_MIX3 0x1871C063 @@ -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; diff --git a/src/uwatec_aladin.c b/src/uwatec_aladin.c index 3ebd094..1cfec54 100644 --- a/src/uwatec_aladin.c +++ b/src/uwatec_aladin.c @@ -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."); - return status; + 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++; } }