diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8610ffc..e41d43a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: CC: ${{ matrix.compiler }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install dependencies run: sudo apt-get install libbluetooth-dev libusb-1.0-0-dev - run: autoreconf --install --force @@ -30,7 +30,7 @@ jobs: run: | make install DESTDIR=$PWD/artifacts tar -czf ${{ github.job }}-${{ matrix.compiler }}.tar.gz -C artifacts usr - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: ${{ github.job }}-${{ matrix.compiler }} path: ${{ github.job }}-${{ matrix.compiler }}.tar.gz @@ -50,7 +50,7 @@ jobs: CC: ${{ matrix.compiler }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install dependencies run: brew install autoconf automake libtool hidapi libusb - run: autoreconf --install --force @@ -61,7 +61,7 @@ jobs: run: | make install DESTDIR=$PWD/artifacts tar -czf ${{ github.job }}-${{ matrix.compiler }}.tar.gz -C artifacts usr - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: ${{ github.job }}-${{ matrix.compiler }} path: ${{ github.job }}-${{ matrix.compiler }}.tar.gz @@ -78,12 +78,12 @@ jobs: arch: [i686, x86_64] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install dependencies run: sudo apt-get install gcc-mingw-w64 binutils-mingw-w64 mingw-w64-tools - name: Install libusb env: - LIBUSB_VERSION: 1.0.24 + LIBUSB_VERSION: 1.0.26 run: | wget -c https://github.com/libusb/libusb/archive/refs/tags/v${LIBUSB_VERSION}.tar.gz tar xzf v${LIBUSB_VERSION}.tar.gz @@ -95,13 +95,13 @@ jobs: popd - name: Install hidapi env: - HIDAPI_VERSION: 0.10.1 + HIDAPI_VERSION: 0.12.0 run: | wget -c https://github.com/libusb/hidapi/archive/refs/tags/hidapi-${HIDAPI_VERSION}.tar.gz tar xzf hidapi-${HIDAPI_VERSION}.tar.gz pushd hidapi-hidapi-${HIDAPI_VERSION} autoreconf --install --force - ./configure --host=${{ matrix.arch }}-w64-mingw32 --prefix=/usr + ./configure --host=${{ matrix.arch }}-w64-mingw32 --prefix=/usr LDFLAGS='-static-libgcc' make make install DESTDIR=$PWD/../artifacts popd @@ -118,7 +118,7 @@ jobs: run: | make install DESTDIR=$PWD/artifacts tar -czf ${{ github.job }}-${{ matrix.arch }}.tar.gz -C artifacts usr - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 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 index 37f578e..fcf8f23 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,19 +9,13 @@ jobs: name: Release runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - 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} + echo "version=${VERSION}" >> $GITHUB_OUTPUT - name: Build distribution tarball id: build @@ -41,21 +35,13 @@ jobs: exit 1 fi - - uses: actions/create-release@v1 + - name: Create Github release id: release + run: | + VERSION="${{ steps.version.outputs.version }}" + if [ "${VERSION}" != "${VERSION%%-*}" ]; then + PRERELEASE="-p" + fi + gh release create ${PRERELEASE} "${{ github.ref }}" "libdivecomputer-${VERSION}.tar.gz" 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 1ac86db..a58d3e1 100644 --- a/configure.ac +++ b/configure.ac @@ -120,12 +120,12 @@ AS_IF([test "x$with_libmtp" != "xno"], [ AC_ARG_WITH([hidapi], [AS_HELP_STRING([--without-hidapi], [Build without the hidapi library])], - [], [with_hidapi=auto]) + [], [with_hidapi=hidapi]) AS_IF([test "x$with_hidapi" != "xno"], [ - PKG_CHECK_MODULES([HIDAPI], [hidapi], [have_hidapi=yes], [have_hidapi=no]) + PKG_CHECK_MODULES([HIDAPI], [$with_hidapi], [have_hidapi=yes], [have_hidapi=no]) AS_IF([test "x$have_hidapi" = "xyes"], [ AC_DEFINE([HAVE_HIDAPI], [1], [hidapi library]) - DEPENDENCIES="$DEPENDENCIES hidapi" + DEPENDENCIES="$DEPENDENCIES $with_hidapi" ]) ]) @@ -170,7 +170,7 @@ AC_CHECK_HEADERS([sys/socket.h linux/types.h linux/irda.h], , , [ # Checks for header files. AC_CHECK_HEADERS([linux/serial.h]) AC_CHECK_HEADERS([IOKit/serial/ioss.h]) -AC_CHECK_HEADERS([getopt.h]) +AC_CHECK_HEADERS([unistd.h getopt.h]) AC_CHECK_HEADERS([sys/param.h]) AC_CHECK_HEADERS([pthread.h]) AC_CHECK_HEADERS([mach/mach_time.h]) diff --git a/contrib/udev/libdivecomputer.rules b/contrib/udev/libdivecomputer.rules index 02ef7da..026afc5 100644 --- a/contrib/udev/libdivecomputer.rules +++ b/contrib/udev/libdivecomputer.rules @@ -1,26 +1,35 @@ # Atomic Aquatics Cobalt SUBSYSTEM=="usb", ATTR{idVendor}=="0471", ATTR{idProduct}=="0888", GROUP="plugdev" +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0471", ATTRS{idProduct}=="0888", GROUP="plugdev" # Suunto EON Steel SUBSYSTEM=="usb", ATTR{idVendor}=="1493", ATTR{idProduct}=="0030", GROUP="plugdev" +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1493", ATTRS{idProduct}=="0030", GROUP="plugdev" # Suunto EON Core SUBSYSTEM=="usb", ATTR{idVendor}=="1493", ATTR{idProduct}=="0033", GROUP="plugdev" +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1493", ATTRS{idProduct}=="0033", GROUP="plugdev" # Suunto D5 SUBSYSTEM=="usb", ATTR{idVendor}=="1493", ATTR{idProduct}=="0035", GROUP="plugdev" +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1493", ATTRS{idProduct}=="0035", GROUP="plugdev" # Suunto EON Steel Black SUBSYSTEM=="usb", ATTR{idVendor}=="1493", ATTR{idProduct}=="0036", GROUP="plugdev" +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1493", ATTRS{idProduct}=="0036", GROUP="plugdev" # Scubapro G2 SUBSYSTEM=="usb", ATTR{idVendor}=="2e6c", ATTR{idProduct}=="3201", GROUP="plugdev" +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2e6c", ATTRS{idProduct}=="3201", GROUP="plugdev" # Scubapro G2 Console SUBSYSTEM=="usb", ATTR{idVendor}=="2e6c", ATTR{idProduct}=="3211", GROUP="plugdev" +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2e6c", ATTRS{idProduct}=="3211", GROUP="plugdev" # Scubapro G2 HUD SUBSYSTEM=="usb", ATTR{idVendor}=="2e6c", ATTR{idProduct}=="4201", GROUP="plugdev" +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2e6c", ATTRS{idProduct}=="4201", GROUP="plugdev" # Scubapro Aladin Square SUBSYSTEM=="usb", ATTR{idVendor}=="c251", ATTR{idProduct}=="2006", GROUP="plugdev" +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="c251", ATTRS{idProduct}=="2006", GROUP="plugdev" diff --git a/examples/common.c b/examples/common.c index d8922ba..47d457f 100644 --- a/examples/common.c +++ b/examples/common.c @@ -96,11 +96,11 @@ static const backend_table_t g_backends[] = { {"sp2", DC_FAMILY_SPORASUB_SP2, 0}, {"excursion", DC_FAMILY_DEEPSIX_EXCURSION, 0}, {"screen", DC_FAMILY_SEAC_SCREEN, 0}, + {"cosmiq", DC_FAMILY_DEEPBLU_COSMIQ, 0}, + {"s1", DC_FAMILY_OCEANS_S1, 0}, // Not merged upstream yet {"descentmk1", DC_FAMILY_GARMIN, 0}, - {"cosmiq", DC_FAMILY_DEEPBLU, 0}, - {"oceans", DC_FAMILY_OCEANS_S1, 0}, }; static const transport_table_t g_transports[] = { diff --git a/examples/dctool.c b/examples/dctool.c index 3104bc4..9d3f74b 100644 --- a/examples/dctool.c +++ b/examples/dctool.c @@ -24,10 +24,12 @@ #endif #include -#include #include #include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_download.c b/examples/dctool_download.c index 2a39f5f..04aedcf 100644 --- a/examples/dctool_download.c +++ b/examples/dctool_download.c @@ -24,9 +24,11 @@ #endif #include -#include #include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_dump.c b/examples/dctool_dump.c index 6aa2e4a..966edbc 100644 --- a/examples/dctool_dump.c +++ b/examples/dctool_dump.c @@ -24,9 +24,11 @@ #endif #include -#include #include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_fwupdate.c b/examples/dctool_fwupdate.c index 596ccb1..4395c39 100644 --- a/examples/dctool_fwupdate.c +++ b/examples/dctool_fwupdate.c @@ -24,8 +24,10 @@ #endif #include -#include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_help.c b/examples/dctool_help.c index 2848c24..33ea0a5 100644 --- a/examples/dctool_help.c +++ b/examples/dctool_help.c @@ -24,8 +24,10 @@ #endif #include -#include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_list.c b/examples/dctool_list.c index 89e5386..bc70d13 100644 --- a/examples/dctool_list.c +++ b/examples/dctool_list.c @@ -24,8 +24,10 @@ #endif #include -#include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_parse.c b/examples/dctool_parse.c index 6b7da08..b898b5c 100644 --- a/examples/dctool_parse.c +++ b/examples/dctool_parse.c @@ -24,9 +24,11 @@ #endif #include -#include #include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_read.c b/examples/dctool_read.c index 2a9bf18..31ffc8d 100644 --- a/examples/dctool_read.c +++ b/examples/dctool_read.c @@ -24,8 +24,10 @@ #endif #include -#include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_scan.c b/examples/dctool_scan.c index 868a9a7..378a8f8 100644 --- a/examples/dctool_scan.c +++ b/examples/dctool_scan.c @@ -24,8 +24,10 @@ #endif #include -#include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_timesync.c b/examples/dctool_timesync.c index 35647b1..eeff0bb 100644 --- a/examples/dctool_timesync.c +++ b/examples/dctool_timesync.c @@ -24,8 +24,10 @@ #endif #include -#include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_version.c b/examples/dctool_version.c index deb0702..dbb7a40 100644 --- a/examples/dctool_version.c +++ b/examples/dctool_version.c @@ -24,8 +24,10 @@ #endif #include -#include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/dctool_write.c b/examples/dctool_write.c index fc76c78..04db198 100644 --- a/examples/dctool_write.c +++ b/examples/dctool_write.c @@ -24,8 +24,10 @@ #endif #include -#include #include +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif diff --git a/examples/output_xml.c b/examples/output_xml.c index 306a86e..8e9380a 100644 --- a/examples/output_xml.c +++ b/examples/output_xml.c @@ -386,6 +386,30 @@ dctool_xml_output_write (dctool_output_t *abstract, dc_parser_t *parser, const u names[divemode]); } + // Parse the deco model. + message ("Parsing the deco model.\n"); + dc_decomodel_t decomodel = {DC_DECOMODEL_NONE}; + status = dc_parser_get_field (parser, DC_FIELD_DECOMODEL, 0, &decomodel); + if (status != DC_STATUS_SUCCESS && status != DC_STATUS_UNSUPPORTED) { + ERROR ("Error parsing the deco model."); + goto cleanup; + } + + if (status != DC_STATUS_UNSUPPORTED) { + const char *names[] = {"none", "buhlmann", "vpm", "rgbm", "dciem"}; + fprintf (output->ostream, "%s\n", + names[decomodel.type]); + if (decomodel.type == DC_DECOMODEL_BUHLMANN && + (decomodel.params.gf.low != 0 || decomodel.params.gf.high != 0)) { + fprintf (output->ostream, "%u/%u\n", + decomodel.params.gf.low, decomodel.params.gf.high); + } + if (decomodel.conservatism) { + fprintf (output->ostream, "%d\n", + decomodel.conservatism); + } + } + // Parse the salinity. message ("Parsing the salinity.\n"); dc_salinity_t salinity = {DC_WATER_FRESH, 0.0}; diff --git a/examples/utils.h b/examples/utils.h index da8a65b..0fb2712 100644 --- a/examples/utils.h +++ b/examples/utils.h @@ -26,6 +26,12 @@ extern "C" { #endif /* __cplusplus */ +#ifdef _MSC_VER +#define snprintf _snprintf +#define strcasecmp _stricmp +#define strncasecmp _strnicmp +#endif + #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) #define FUNCTION __func__ #else diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h index 6e8c90c..6619bd0 100644 --- a/include/libdivecomputer/common.h +++ b/include/libdivecomputer/common.h @@ -118,14 +118,14 @@ typedef enum dc_family_t { DC_FAMILY_DEEPSIX_EXCURSION = (19 << 16), /* Seac Screen */ DC_FAMILY_SEAC_SCREEN = (20 << 16), + /* Deepblu Cosmiq */ + DC_FAMILY_DEEPBLU_COSMIQ = (21 << 16), + /* Oceans S1 */ + DC_FAMILY_OCEANS_S1 = (22 << 16), // Not merged upstream yet /* Garmin */ DC_FAMILY_GARMIN = (100 << 16), - /* Deepblu */ - DC_FAMILY_DEEPBLU = (101 << 16), - /* Oceans S1 */ - DC_FAMILY_OCEANS_S1 = (102 << 16), } dc_family_t; #ifdef __cplusplus diff --git a/include/libdivecomputer/parser.h b/include/libdivecomputer/parser.h index 29befdd..37c9f81 100644 --- a/include/libdivecomputer/parser.h +++ b/include/libdivecomputer/parser.h @@ -67,6 +67,7 @@ typedef enum dc_field_type_t { DC_FIELD_TANK_COUNT, DC_FIELD_TANK, DC_FIELD_DIVEMODE, + DC_FIELD_DECOMODEL, DC_FIELD_STRING, } dc_field_type_t; @@ -223,6 +224,43 @@ typedef struct dc_tank_t { double endpressure; /* End pressure (bar) */ } dc_tank_t; +typedef enum dc_decomodel_type_t { + DC_DECOMODEL_NONE, + DC_DECOMODEL_BUHLMANN, + DC_DECOMODEL_VPM, + DC_DECOMODEL_RGBM, + DC_DECOMODEL_DCIEM, +} dc_decomodel_type_t; + +/* + * Decompression model + * + * The type field contains the decompression algorithm. + * + * The (optional) conservatism field contains the personal adjustment + * setting of the algorithm. The exact interpretation depends on the + * dive computer, but the default value (zero) will typically correspond + * to the neutral setting, while a positive value is more conservative + * and a negative value more aggressive. + * + * The (optional) params field contains the parameters of the algorithm: + * + * DC_DECOMODEL_BUHLMANN: The Gradient Factor (GF) parameters low and + * high. For a pure Buhlmann algorithm (e.g. without GF enabled), both + * values are 100. If GF are enabled, but the actual parameter values + * are not available from the dive computer, both values are zero. + */ +typedef struct dc_decomodel_t { + dc_decomodel_type_t type; + int conservatism; + union { + struct { + unsigned int high; + unsigned int low; + } gf; + } params; +} dc_decomodel_t; + typedef struct dc_field_string_t { const char *desc; const char *value; @@ -275,6 +313,15 @@ dc_parser_new2 (dc_parser_t **parser, dc_context_t *context, dc_descriptor_t *de dc_family_t dc_parser_get_type (dc_parser_t *parser); +dc_status_t +dc_parser_set_clock (dc_parser_t *parser, unsigned int devtime, dc_ticks_t systime); + +dc_status_t +dc_parser_set_atmospheric (dc_parser_t *parser, double atmospheric); + +dc_status_t +dc_parser_set_density (dc_parser_t *parser, double density); + dc_status_t dc_parser_set_data (dc_parser_t *parser, const unsigned char *data, unsigned int size); diff --git a/msvc/libdivecomputer.vcxproj b/msvc/libdivecomputer.vcxproj index 84bac6b..b3b370f 100644 --- a/msvc/libdivecomputer.vcxproj +++ b/msvc/libdivecomputer.vcxproj @@ -182,6 +182,8 @@ + + @@ -217,6 +219,9 @@ + + + @@ -308,6 +313,7 @@ + @@ -330,6 +336,8 @@ + + diff --git a/src/Makefile.am b/src/Makefile.am index 71d30ab..c269fd2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -77,6 +77,9 @@ libdivecomputer_la_SOURCES = \ sporasub_sp2.h sporasub_sp2.c sporasub_sp2_parser.c \ deepsix_excursion.h deepsix_excursion.c deepsix_excursion_parser.c \ seac_screen.h seac_screen.c seac_screen_parser.c \ + deepblu_cosmiq.h deepblu_cosmiq.c deepblu_cosmiq_parser.c \ + oceans_s1_common.h oceans_s1_common.c \ + oceans_s1.h oceans_s1.c oceans_s1_parser.c \ socket.h socket.c \ irda.c \ usb.c \ @@ -88,9 +91,7 @@ libdivecomputer_la_SOURCES = \ libdivecomputer_la_SOURCES += \ usb_storage.c \ field-cache.h field-cache.c \ - garmin.h garmin.c garmin_parser.c \ - deepblu.h deepblu.c deepblu_parser.c \ - oceans_s1.h oceans_s1.c oceans_s1_parser.c + garmin.h garmin.c garmin_parser.c if OS_WIN32 libdivecomputer_la_SOURCES += serial_win32.c diff --git a/src/array.c b/src/array.c index 6d9550e..1216d41 100644 --- a/src/array.c +++ b/src/array.c @@ -208,6 +208,32 @@ array_uint_le (const unsigned char data[], unsigned int n) return value; } +unsigned long long +array_uint64_be (const unsigned char data[]) +{ + return ((unsigned long long) data[0] << 56) | + ((unsigned long long) data[1] << 48) | + ((unsigned long long) data[2] << 40) | + ((unsigned long long) data[3] << 32) | + ((unsigned long long) data[4] << 24) | + ((unsigned long long) data[5] << 16) | + ((unsigned long long) data[6] << 8) | + ((unsigned long long) data[7] << 0); +} + +unsigned long long +array_uint64_le (const unsigned char data[]) +{ + return ((unsigned long long) data[0] << 0) | + ((unsigned long long) data[1] << 8) | + ((unsigned long long) data[2] << 16) | + ((unsigned long long) data[3] << 24) | + ((unsigned long long) data[4] << 32) | + ((unsigned long long) data[5] << 40) | + ((unsigned long long) data[6] << 48) | + ((unsigned long long) data[7] << 56); +} + unsigned int array_uint32_be (const unsigned char data[]) { @@ -237,17 +263,6 @@ array_uint32_word_be (const unsigned char data[]) ((unsigned int) data[3] << 16); } - -void -array_uint32_le_set (unsigned char data[], const unsigned int input) -{ - data[0] = input & 0xFF; - data[1] = (input >> 8) & 0xFF; - data[2] = (input >> 16) & 0xFF; - data[3] = (input >> 24) & 0xFF; -} - - unsigned int array_uint24_be (const unsigned char data[]) { @@ -256,16 +271,6 @@ array_uint24_be (const unsigned char data[]) ((unsigned int) data[2] << 0); } - -void -array_uint24_be_set (unsigned char data[], const unsigned int input) -{ - data[0] = (input >> 16) & 0xFF; - data[1] = (input >> 8) & 0xFF; - data[2] = input & 0xFF; -} - - unsigned int array_uint24_le (const unsigned char data[]) { @@ -289,8 +294,93 @@ array_uint16_le (const unsigned char data[]) ((unsigned int) data[1] << 8); } +void +array_uint64_be_set (unsigned char data[], const unsigned long long input) +{ + data[0] = (input >> 56) & 0xFF; + data[1] = (input >> 48) & 0xFF; + data[2] = (input >> 40) & 0xFF; + data[3] = (input >> 32) & 0xFF; + data[4] = (input >> 24) & 0xFF; + data[5] = (input >> 16) & 0xFF; + data[6] = (input >> 8) & 0xFF; + data[7] = (input ) & 0xFF; +} + +void +array_uint64_le_set (unsigned char data[], const unsigned long long input) +{ + data[0] = (input ) & 0xFF; + data[1] = (input >> 8) & 0xFF; + data[2] = (input >> 16) & 0xFF; + data[3] = (input >> 24) & 0xFF; + data[4] = (input >> 32) & 0xFF; + data[5] = (input >> 40) & 0xFF; + data[6] = (input >> 48) & 0xFF; + data[7] = (input >> 56) & 0xFF; +} + +void +array_uint32_be_set (unsigned char data[], const unsigned int input) +{ + data[0] = (input >> 24) & 0xFF; + data[1] = (input >> 16) & 0xFF; + data[2] = (input >> 8) & 0xFF; + data[3] = (input ) & 0xFF; +} + +void +array_uint32_le_set (unsigned char data[], const unsigned int input) +{ + data[0] = (input ) & 0xFF; + data[1] = (input >> 8) & 0xFF; + data[2] = (input >> 16) & 0xFF; + data[3] = (input >> 24) & 0xFF; +} + +void +array_uint24_be_set (unsigned char data[], const unsigned int input) +{ + data[0] = (input >> 16) & 0xFF; + data[1] = (input >> 8) & 0xFF; + data[2] = (input ) & 0xFF; +} + +void +array_uint24_le_set (unsigned char data[], const unsigned int input) +{ + data[0] = (input ) & 0xFF; + data[1] = (input >> 8) & 0xFF; + data[2] = (input >> 16) & 0xFF; +} + +void +array_uint16_be_set (unsigned char data[], const unsigned short input) +{ + data[0] = (input >> 8) & 0xFF; + data[1] = (input ) & 0xFF; +} + +void +array_uint16_le_set (unsigned char data[], const unsigned short input) +{ + data[0] = (input ) & 0xFF; + data[1] = (input >> 8) & 0xFF; +} + unsigned char bcd2dec (unsigned char value) { return ((value >> 4) & 0x0f) * 10 + (value & 0x0f); } + +unsigned char +dec2bcd (unsigned char value) +{ + if (value >= 100) + return 0; + + unsigned char hi = value / 10; + unsigned char lo = value % 10; + return (hi << 4) | lo; +} diff --git a/src/array.h b/src/array.h index f0f54cd..ac4c3b1 100644 --- a/src/array.h +++ b/src/array.h @@ -66,6 +66,12 @@ array_uint_be (const unsigned char data[], unsigned int n); unsigned int array_uint_le (const unsigned char data[], unsigned int n); +unsigned long long +array_uint64_be (const unsigned char data[]); + +unsigned long long +array_uint64_le (const unsigned char data[]); + unsigned int array_uint32_be (const unsigned char data[]); @@ -75,15 +81,9 @@ array_uint32_le (const unsigned char data[]); unsigned int array_uint32_word_be (const unsigned char data[]); -void -array_uint32_le_set (unsigned char data[], const unsigned int input); - unsigned int array_uint24_be (const unsigned char data[]); -void -array_uint24_be_set (unsigned char data[], const unsigned int input); - unsigned int array_uint24_le (const unsigned char data[]); @@ -93,9 +93,36 @@ array_uint16_be (const unsigned char data[]); unsigned short array_uint16_le (const unsigned char data[]); +void +array_uint64_be_set (unsigned char data[], const unsigned long long input); + +void +array_uint64_le_set (unsigned char data[], const unsigned long long input); + +void +array_uint32_be_set (unsigned char data[], const unsigned int input); + +void +array_uint32_le_set (unsigned char data[], const unsigned int input); + +void +array_uint24_be_set (unsigned char data[], const unsigned int input); + +void +array_uint24_le_set (unsigned char data[], const unsigned int input); + +void +array_uint16_be_set (unsigned char data[], const unsigned short input); + +void +array_uint16_le_set (unsigned char data[], const unsigned short input); + unsigned char bcd2dec (unsigned char value); +unsigned char +dec2bcd (unsigned char value); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/atomics_cobalt_parser.c b/src/atomics_cobalt_parser.c index 4c56825..7a95e6f 100644 --- a/src/atomics_cobalt_parser.c +++ b/src/atomics_cobalt_parser.c @@ -50,6 +50,7 @@ struct atomics_cobalt_parser_t { }; static dc_status_t atomics_cobalt_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t atomics_cobalt_parser_set_density (dc_parser_t *abstract, double density); static dc_status_t atomics_cobalt_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); static dc_status_t atomics_cobalt_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); static dc_status_t atomics_cobalt_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); @@ -58,6 +59,9 @@ static const dc_parser_vtable_t atomics_cobalt_parser_vtable = { sizeof(atomics_cobalt_parser_t), DC_FAMILY_ATOMICS_COBALT, atomics_cobalt_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + atomics_cobalt_parser_set_density, /* set_density */ atomics_cobalt_parser_get_datetime, /* datetime */ atomics_cobalt_parser_get_field, /* fields */ atomics_cobalt_parser_samples_foreach, /* samples_foreach */ @@ -81,7 +85,7 @@ atomics_cobalt_parser_create (dc_parser_t **out, dc_context_t *context) } // Set the default values. - parser->hydrostatic = 1025.0 * GRAVITY; + parser->hydrostatic = DEF_DENSITY_SALT * GRAVITY; *out = (dc_parser_t*) parser; @@ -110,6 +114,17 @@ atomics_cobalt_parser_set_calibration (dc_parser_t *abstract, double atmospheric } +static dc_status_t +atomics_cobalt_parser_set_density (dc_parser_t *abstract, double density) +{ + atomics_cobalt_parser_t *parser = (atomics_cobalt_parser_t *) abstract; + + parser->hydrostatic = density * GRAVITY; + + return DC_STATUS_SUCCESS; +} + + static dc_status_t atomics_cobalt_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) { diff --git a/src/bluetooth.c b/src/bluetooth.c index 1889bf7..aa5f05a 100644 --- a/src/bluetooth.c +++ b/src/bluetooth.c @@ -24,7 +24,6 @@ #endif #include // malloc, free -#include #include "socket.h" @@ -231,7 +230,7 @@ dc_bluetooth_addr2str(dc_bluetooth_address_t address, char *str, size_t size) if (str == NULL || size < DC_BLUETOOTH_SIZE) return NULL; - int n = snprintf(str, size, "%02X:%02X:%02X:%02X:%02X:%02X", + int n = dc_platform_snprintf(str, size, "%02X:%02X:%02X:%02X:%02X:%02X", (unsigned char)((address >> 40) & 0xFF), (unsigned char)((address >> 32) & 0xFF), (unsigned char)((address >> 24) & 0xFF), diff --git a/src/citizen_aqualand_parser.c b/src/citizen_aqualand_parser.c index 319dcf7..5b95555 100644 --- a/src/citizen_aqualand_parser.c +++ b/src/citizen_aqualand_parser.c @@ -45,6 +45,9 @@ static const dc_parser_vtable_t citizen_aqualand_parser_vtable = { sizeof(citizen_aqualand_parser_t), DC_FAMILY_CITIZEN_AQUALAND, citizen_aqualand_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ citizen_aqualand_parser_get_datetime, /* datetime */ citizen_aqualand_parser_get_field, /* fields */ citizen_aqualand_parser_samples_foreach, /* samples_foreach */ diff --git a/src/cochran_commander_parser.c b/src/cochran_commander_parser.c index 853b01b..a4515ea 100644 --- a/src/cochran_commander_parser.c +++ b/src/cochran_commander_parser.c @@ -108,6 +108,9 @@ static const dc_parser_vtable_t cochran_commander_parser_vtable = { sizeof(cochran_commander_parser_t), DC_FAMILY_COCHRAN_COMMANDER, cochran_commander_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ cochran_commander_parser_get_datetime, /* datetime */ cochran_commander_parser_get_field, /* fields */ cochran_commander_parser_samples_foreach, /* samples_foreach */ diff --git a/src/context-private.h b/src/context-private.h index 7f50a68..fed80e5 100644 --- a/src/context-private.h +++ b/src/context-private.h @@ -28,6 +28,8 @@ #include +#include "platform.h" + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ @@ -40,12 +42,6 @@ extern "C" { #define FUNCTION __FUNCTION__ #endif -#if defined(__GNUC__) -#define ATTR_FORMAT_PRINTF(a,b) __attribute__((format(printf, a, b))) -#else -#define ATTR_FORMAT_PRINTF(a,b) -#endif - #ifdef ENABLE_LOGGING #define HEXDUMP(context, loglevel, prefix, data, size) dc_context_hexdump (context, loglevel, __FILE__, __LINE__, FUNCTION, prefix, data, size) #define SYSERROR(context, errcode) dc_context_syserror (context, DC_LOGLEVEL_ERROR, __FILE__, __LINE__, FUNCTION, errcode) @@ -63,7 +59,7 @@ extern "C" { #endif dc_status_t -dc_context_log (dc_context_t *context, dc_loglevel_t loglevel, const char *file, unsigned int line, const char *function, const char *format, ...) ATTR_FORMAT_PRINTF(6, 7); +dc_context_log (dc_context_t *context, dc_loglevel_t loglevel, const char *file, unsigned int line, const char *function, const char *format, ...) DC_ATTR_FORMAT_PRINTF(6, 7); dc_status_t dc_context_syserror (dc_context_t *context, dc_loglevel_t loglevel, const char *file, unsigned int line, const char *function, int errcode); diff --git a/src/context.c b/src/context.c index 17f7245..6c63d63 100644 --- a/src/context.c +++ b/src/context.c @@ -25,7 +25,6 @@ #include #include -#include #include #include @@ -36,6 +35,7 @@ #endif #include "context-private.h" +#include "platform.h" #include "timer.h" struct dc_context_t { @@ -49,55 +49,6 @@ struct dc_context_t { }; #ifdef ENABLE_LOGGING -/* - * A wrapper for the vsnprintf function, which will always null terminate the - * string and returns a negative value if the destination buffer is too small. - */ -static int -l_vsnprintf (char *str, size_t size, const char *format, va_list ap) -{ - int n; - - if (size == 0) - return -1; - -#ifdef _MSC_VER - /* - * The non-standard vsnprintf implementation provided by MSVC doesn't null - * terminate the string and returns a negative value if the destination - * buffer is too small. - */ - n = _vsnprintf (str, size - 1, format, ap); - if (n == size - 1 || n < 0) - str[size - 1] = 0; -#else - /* - * The C99 vsnprintf function will always null terminate the string. If the - * destination buffer is too small, the return value is the number of - * characters that would have been written if the buffer had been large - * enough. - */ - n = vsnprintf (str, size, format, ap); - if (n >= 0 && (size_t) n >= size) - n = -1; -#endif - - return n; -} - -static int -l_snprintf (char *str, size_t size, const char *format, ...) -{ - va_list ap; - int n; - - va_start (ap, format); - n = l_vsnprintf (str, size, format, ap); - va_end (ap); - - return n; -} - static int l_hexdump (char *str, size_t size, const unsigned char data[], size_t n) { @@ -244,7 +195,7 @@ dc_context_log (dc_context_t *context, dc_loglevel_t loglevel, const char *file, return DC_STATUS_SUCCESS; va_start (ap, format); - l_vsnprintf (context->msg, sizeof (context->msg), format, ap); + dc_platform_vsnprintf (context->msg, sizeof (context->msg), format, ap); va_end (ap); context->logfunc (context, loglevel, file, line, function, context->msg, context->userdata); @@ -310,7 +261,7 @@ dc_context_hexdump (dc_context_t *context, dc_loglevel_t loglevel, const char *f if (context->logfunc == NULL) return DC_STATUS_SUCCESS; - n = l_snprintf (context->msg, sizeof (context->msg), "%s: size=%u, data=", prefix, size); + n = dc_platform_snprintf (context->msg, sizeof (context->msg), "%s: size=%u, data=", prefix, size); if (n >= 0) { n = l_hexdump (context->msg + n, sizeof (context->msg) - n, data, size); diff --git a/src/cressi_edy_parser.c b/src/cressi_edy_parser.c index eaf55c3..3817fc7 100644 --- a/src/cressi_edy_parser.c +++ b/src/cressi_edy_parser.c @@ -47,6 +47,9 @@ static const dc_parser_vtable_t cressi_edy_parser_vtable = { sizeof(cressi_edy_parser_t), DC_FAMILY_CRESSI_EDY, cressi_edy_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ cressi_edy_parser_get_datetime, /* datetime */ cressi_edy_parser_get_field, /* fields */ cressi_edy_parser_samples_foreach, /* samples_foreach */ diff --git a/src/cressi_goa_parser.c b/src/cressi_goa_parser.c index 53263b1..f5d4a4c 100644 --- a/src/cressi_goa_parser.c +++ b/src/cressi_goa_parser.c @@ -29,9 +29,6 @@ #define ISINSTANCE(parser) dc_device_isinstance((parser), &cressi_goa_parser_vtable) #define SZ_HEADER 23 -#define SZ_HEADER_SCUBA 0x61 -#define SZ_HEADER_FREEDIVE 0x2B -#define SZ_HEADER_GAUGE 0x2D #define DEPTH 0 #define DEPTH2 1 @@ -45,16 +42,26 @@ #define NGASMIXES 2 +#define UNDEFINED 0xFFFFFFFF + typedef struct cressi_goa_parser_t cressi_goa_parser_t; struct cressi_goa_parser_t { dc_parser_t base; unsigned int model; - // Cached fields. - unsigned int cached; - double maxdepth; }; +typedef struct cressi_goa_layout_t { + unsigned int headersize; + unsigned int datetime; + unsigned int divetime; + unsigned int gasmix; + unsigned int atmospheric; + unsigned int maxdepth; + unsigned int avgdepth; + unsigned int temperature; +} cressi_goa_layout_t; + static dc_status_t cressi_goa_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); static dc_status_t cressi_goa_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); static dc_status_t cressi_goa_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); @@ -64,17 +71,60 @@ static const dc_parser_vtable_t cressi_goa_parser_vtable = { sizeof(cressi_goa_parser_t), DC_FAMILY_CRESSI_GOA, cressi_goa_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ cressi_goa_parser_get_datetime, /* datetime */ cressi_goa_parser_get_field, /* fields */ cressi_goa_parser_samples_foreach, /* samples_foreach */ NULL /* destroy */ }; -static const unsigned int headersizes[] = { - SZ_HEADER_SCUBA, - SZ_HEADER_SCUBA, - SZ_HEADER_FREEDIVE, - SZ_HEADER_GAUGE, +static const cressi_goa_layout_t layouts[] = { + /* SCUBA */ + { + 0x61, /* headersize */ + 0x11, /* datetime */ + 0x19, /* divetime */ + 0x1F, /* gasmix */ + 0x23, /* atmospheric */ + 0x4E, /* maxdepth */ + 0x50, /* avgdepth */ + 0x52, /* temperature */ + }, + /* NITROX */ + { + 0x61, /* headersize */ + 0x11, /* datetime */ + 0x19, /* divetime */ + 0x1F, /* gasmix */ + 0x23, /* atmospheric */ + 0x4E, /* maxdepth */ + 0x50, /* avgdepth */ + 0x52, /* temperature */ + }, + /* FREEDIVE */ + { + 0x2B, /* headersize */ + 0x11, /* datetime */ + 0x19, /* divetime */ + UNDEFINED, /* gasmix */ + UNDEFINED, /* atmospheric */ + 0x1C, /* maxdepth */ + UNDEFINED, /* avgdepth */ + 0x1E, /* temperature */ + }, + /* GAUGE */ + { + 0x2D, /* headersize */ + 0x11, /* datetime */ + 0x19, /* divetime */ + UNDEFINED, /* gasmix */ + 0x1B, /* atmospheric */ + 0x1D, /* maxdepth */ + 0x1F, /* avgdepth */ + 0x21, /* temperature */ + }, }; dc_status_t @@ -93,8 +143,6 @@ cressi_goa_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int } parser->model = model; - parser->cached = 0; - parser->maxdepth = 0.0; *out = (dc_parser_t*) parser; @@ -104,12 +152,6 @@ cressi_goa_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int static dc_status_t cressi_goa_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) { - cressi_goa_parser_t *parser = (cressi_goa_parser_t *) abstract; - - // Reset the cache. - parser->cached = 0; - parser->maxdepth = 0.0; - return DC_STATUS_SUCCESS; } @@ -123,15 +165,16 @@ cressi_goa_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) return DC_STATUS_DATAFORMAT; unsigned int divemode = data[2]; - if (divemode >= C_ARRAY_SIZE(headersizes)) { + if (divemode >= C_ARRAY_SIZE(layouts)) { return DC_STATUS_DATAFORMAT; } - unsigned int headersize = headersizes[divemode]; - if (size < headersize) + const cressi_goa_layout_t *layout = &layouts[divemode]; + + if (size < layout->headersize) return DC_STATUS_DATAFORMAT; - const unsigned char *p = abstract->data + 0x11; + const unsigned char *p = abstract->data + layout->datetime; if (datetime) { datetime->year = array_uint16_le(p); @@ -149,7 +192,6 @@ cressi_goa_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) static dc_status_t cressi_goa_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) { - cressi_goa_parser_t *parser = (cressi_goa_parser_t *) abstract; const unsigned char *data = abstract->data; unsigned int size = abstract->size; @@ -157,29 +199,19 @@ cressi_goa_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsign return DC_STATUS_DATAFORMAT; unsigned int divemode = data[2]; - if (divemode >= C_ARRAY_SIZE(headersizes)) { + if (divemode >= C_ARRAY_SIZE(layouts)) { return DC_STATUS_DATAFORMAT; } - unsigned int headersize = headersizes[divemode]; - if (size < headersize) + const cressi_goa_layout_t *layout = &layouts[divemode]; + + if (size < layout->headersize) return DC_STATUS_DATAFORMAT; - if (!parser->cached) { - sample_statistics_t statistics = SAMPLE_STATISTICS_INITIALIZER; - dc_status_t rc = cressi_goa_parser_samples_foreach ( - abstract, sample_statistics_cb, &statistics); - if (rc != DC_STATUS_SUCCESS) - return rc; - - parser->cached = 1; - parser->maxdepth = statistics.maxdepth; - } - unsigned int ngasmixes = 0; - if (divemode == SCUBA || divemode == NITROX) { + if (layout->gasmix != UNDEFINED) { for (unsigned int i = 0; i < NGASMIXES; ++i) { - if (data[0x20 + 2 * i] == 0) + if (data[layout->gasmix + 2 * i + 1] == 0) break; ngasmixes++; } @@ -190,17 +222,36 @@ cressi_goa_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsign if (value) { switch (type) { case DC_FIELD_DIVETIME: - *((unsigned int *) value) = array_uint16_le (data + 0x19); + if (layout->divetime == UNDEFINED) + return DC_STATUS_UNSUPPORTED; + *((unsigned int *) value) = array_uint16_le (data + layout->divetime); break; case DC_FIELD_MAXDEPTH: - *((double *) value) = parser->maxdepth; + if (layout->maxdepth == UNDEFINED) + return DC_STATUS_UNSUPPORTED; + *((double *) value) = array_uint16_le (data + layout->maxdepth) / 10.0; + break; + case DC_FIELD_AVGDEPTH: + if (layout->avgdepth == UNDEFINED) + return DC_STATUS_UNSUPPORTED; + *((double *) value) = array_uint16_le (data + layout->avgdepth) / 10.0; + break; + case DC_FIELD_TEMPERATURE_MINIMUM: + if (layout->temperature == UNDEFINED) + return DC_STATUS_UNSUPPORTED; + *((double *) value) = array_uint16_le (data + layout->temperature) / 10.0; + break; + case DC_FIELD_ATMOSPHERIC: + if (layout->atmospheric == UNDEFINED) + return DC_STATUS_UNSUPPORTED; + *((double *) value) = array_uint16_le (data + layout->atmospheric) / 1000.0; break; case DC_FIELD_GASMIX_COUNT: *((unsigned int *) value) = ngasmixes; break; case DC_FIELD_GASMIX: gasmix->helium = 0.0; - gasmix->oxygen = data[0x20 + 2 * flags] / 100.0; + gasmix->oxygen = data[layout->gasmix + 2 * flags + 1] / 100.0; gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; break; case DC_FIELD_DIVEMODE: @@ -237,12 +288,13 @@ cressi_goa_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t c return DC_STATUS_DATAFORMAT; unsigned int divemode = data[2]; - if (divemode >= C_ARRAY_SIZE(headersizes)) { + if (divemode >= C_ARRAY_SIZE(layouts)) { return DC_STATUS_DATAFORMAT; } - unsigned int headersize = headersizes[divemode]; - if (size < headersize) + const cressi_goa_layout_t *layout = &layouts[divemode]; + + if (size < layout->headersize) return DC_STATUS_DATAFORMAT; unsigned int interval = divemode == FREEDIVE ? 2 : 5; @@ -254,7 +306,7 @@ cressi_goa_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t c unsigned int have_temperature = 0; unsigned int complete = 0; - unsigned int offset = headersize; + unsigned int offset = layout->headersize; while (offset + 2 <= size) { dc_sample_value_t sample = {0}; diff --git a/src/cressi_leonardo_parser.c b/src/cressi_leonardo_parser.c index d4f37f5..eefebdf 100644 --- a/src/cressi_leonardo_parser.c +++ b/src/cressi_leonardo_parser.c @@ -48,6 +48,9 @@ static const dc_parser_vtable_t cressi_leonardo_parser_vtable = { sizeof(cressi_leonardo_parser_t), DC_FAMILY_CRESSI_LEONARDO, cressi_leonardo_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ cressi_leonardo_parser_get_datetime, /* datetime */ cressi_leonardo_parser_get_field, /* fields */ cressi_leonardo_parser_samples_foreach, /* samples_foreach */ diff --git a/src/deepblu.c b/src/deepblu.c deleted file mode 100644 index 5c33f60..0000000 --- a/src/deepblu.c +++ /dev/null @@ -1,500 +0,0 @@ -/* - * Deepblu Cosmiq+ downloading - * - * Copyright (C) 2019 Linus Torvalds - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - -#include -#include - -#include "deepblu.h" -#include "context-private.h" -#include "device-private.h" -#include "array.h" - -// "Write state" -#define CMD_SETTIME 0x20 // Send 6 byte date-time, get single-byte 00x00 ack -#define CMD_23 0x23 // Send 00/01 byte, get ack back? Some metric/imperial setting? - -// "Read dives"? -#define CMD_GETDIVENR 0x40 // Send empty byte, get single-byte number of dives back -#define CMD_GETDIVE 0x41 // Send dive number (1-nr) byte, get dive stat length byte back - #define RSP_DIVESTAT 0x42 // .. followed by packets of dive stat for that dive of that length -#define CMD_GETPROFILE 0x43 // Send dive number (1-nr) byte, get dive profile length BE word back - #define RSP_DIVEPROF 0x44 // .. followed by packets of dive profile of that length - -// "Read state" -#define CMD_GETTIME 0x50 // Send empty byte, get six-byte bcd date-time back -#define CMD_51 0x51 // Send empty byte, get four bytes back (03 dc 00 e3) -#define CMD_52 0x52 // Send empty byte, get two bytes back (bf 8d) -#define CMD_53 0x53 // Send empty byte, get six bytes back (0e 81 00 03 00 00) -#define CMD_54 0x54 // Send empty byte, get byte back (00) -#define CMD_55 0x55 // Send empty byte, get byte back (00) -#define CMD_56 0x56 // Send empty byte, get byte back (00) -#define CMD_57 0x57 // Send empty byte, get byte back (00) -#define CMD_58 0x58 // Send empty byte, get byte back (52) -#define CMD_59 0x59 // Send empty byte, get six bytes back (00 00 07 00 00 00) - // (00 00 00 00 00 00) -#define CMD_5a 0x5a // Send empty byte, get six bytes back (23 1b 09 d8 37 c0) -#define CMD_5b 0x5b // Send empty byte, get six bytes back (00 21 00 14 00 01) - // (00 00 00 14 00 01) -#define CMD_5c 0x5c // Send empty byte, get six bytes back (13 88 00 46 20 00) - // (13 88 00 3c 15 00) -#define CMD_5d 0x5d // Send empty byte, get six bytes back (19 00 23 0C 02 0E) - // (14 14 14 0c 01 0e) -#define CMD_5f 0x5f // Send empty byte, get six bytes back (00 00 07 00 00 00) - -#define COSMIQ_HDR_SIZE 36 - -typedef struct deepblu_device_t { - dc_device_t base; - dc_iostream_t *iostream; - unsigned char fingerprint[COSMIQ_HDR_SIZE]; -} deepblu_device_t; - -static dc_status_t deepblu_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); -static dc_status_t deepblu_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); -static dc_status_t deepblu_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime); -static dc_status_t deepblu_device_close (dc_device_t *abstract); - -static const dc_device_vtable_t deepblu_device_vtable = { - sizeof(deepblu_device_t), - DC_FAMILY_DEEPBLU, - deepblu_device_set_fingerprint, /* set_fingerprint */ - NULL, /* read */ - NULL, /* write */ - NULL, /* dump */ - deepblu_device_foreach, /* foreach */ - deepblu_device_timesync, /* timesync */ - deepblu_device_close, /* close */ -}; - -// Maximum data in a packet. It's actually much -// less than this, since BLE packets are small and -// with the 7 bytes of headers and final newline -// and the HEX encoding, the actual maximum is -// just something like 6 bytes. -// -// But in theory the data could be done over -// multiple packets. That doesn't seem to be -// the case in anything I've seen so far. -// -// Pick something small and easy to use for -// stack buffers. -#define MAX_DATA 20 - -static char * -write_hex_byte(unsigned char data, char *p) -{ - static const char hex[16] = "0123456789ABCDEF"; - *p++ = hex[data >> 4]; - *p++ = hex[data & 0xf]; - return p; -} - -// -// Send a cmd packet. -// -// The format of the cmd on the "wire" is: -// - byte '#' -// - HEX char of cmd -// - HEX char two's complement modular sum of packet data (including cmd/size) -// - HEX char size of data as encoded in HEX -// - n * HEX char data -// - byte '\n' -// so you end up having 8 bytes of header/trailer overhead, and two bytes -// for every byte of data sent due to the HEX encoding. -// -static dc_status_t -deepblu_send_cmd(deepblu_device_t *device, const unsigned char cmd, const unsigned char data[], size_t size) -{ - char buffer[8+2*MAX_DATA], *p; - unsigned char csum; - int i; - - if (size > MAX_DATA) - return DC_STATUS_INVALIDARGS; - - // Calculate packet csum - csum = cmd + 2*size; - for (i = 0; i < size; i++) - csum += data[i]; - csum = -csum; - - // Fill the data buffer - p = buffer; - *p++ = '#'; - p = write_hex_byte(cmd, p); - p = write_hex_byte(csum, p); - p = write_hex_byte(size*2, p); - for (i = 0; i < size; i++) - p = write_hex_byte(data[i], p); - *p++ = '\n'; - - // .. and send it out - return dc_iostream_write(device->iostream, buffer, p-buffer, NULL); -} - -// -// Receive one 'line' of data -// -// The deepblu BLE protocol is ASCII line based and packetized. -// Normally one packet is one line, but it looks like the Nordic -// Semi BLE chip will sometimes send packets early (some internal -// serial buffer timeout?) with incompete data. -// -// So read packets until you get newline. -static dc_status_t -deepblu_recv_line(deepblu_device_t *device, unsigned char *buf, size_t size) -{ - while (1) { - unsigned char buffer[20]; - size_t transferred = 0; - dc_status_t status; - - status = dc_iostream_read(device->iostream, buffer, sizeof(buffer), &transferred); - if (status != DC_STATUS_SUCCESS) { - ERROR(device->base.context, "Failed to receive Deepblu reply packet."); - return status; - } - if (transferred > size) { - ERROR(device->base.context, "Deepblu reply packet with too much data (got %zu, expected %zu)", transferred, size); - return DC_STATUS_IO; - } - if (!transferred) { - ERROR(device->base.context, "Empty Deepblu reply packet"); - return DC_STATUS_IO; - } - memcpy(buf, buffer, transferred); - buf += transferred; - size -= transferred; - if (buf[-1] == '\n') - break; - } - buf[-1] = 0; - return DC_STATUS_SUCCESS; -} - -static int -hex_nibble(char c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - return -1; -} - -static int -read_hex_byte(char *p) -{ - // This is negative if either of the nibbles is invalid - return (hex_nibble(p[0]) << 4) | hex_nibble(p[1]); -} - - -// -// Receive a reply packet -// -// The reply packet has the same format as the cmd packet we -// send, except the first byte is '$' instead of '#'. -static dc_status_t -deepblu_recv_data(deepblu_device_t *device, const unsigned char expected, unsigned char *buf, size_t size, size_t *received) -{ - int len, i; - dc_status_t status; - char buffer[8+2*MAX_DATA]; - int cmd, csum, ndata; - - status = deepblu_recv_line(device, buffer, sizeof(buffer)); - if (status != DC_STATUS_SUCCESS) - return status; - - // deepblu_recv_line() always zero-terminates the result - // if it returned success, and has removed the final newline. - len = strlen(buffer); - HEXDUMP(device->base.context, DC_LOGLEVEL_DEBUG, "rcv", buffer, len); - - // A valid reply should always be at least 7 characters: the - // initial '$' and the three header HEX bytes. - if (len < 8 || buffer[0] != '$') { - ERROR(device->base.context, "Invalid Deepblu reply packet"); - return DC_STATUS_IO; - } - - cmd = read_hex_byte(buffer+1); - csum = read_hex_byte(buffer+3); - ndata = read_hex_byte(buffer+5); - if ((cmd | csum | ndata) < 0) { - ERROR(device->base.context, "non-hex Deepblu reply packet header"); - return DC_STATUS_IO; - } - - // Verify the data length: it's the size of the HEX data, - // and should also match the line length we got (the 7 - // is for the header data we already decoded above). - if ((ndata & 1) || ndata != len - 7) { - ERROR(device->base.context, "Deepblu reply packet data length does not match (claimed %d, got %d)", ndata, len-7); - return DC_STATUS_IO; - } - - if (ndata >> 1 > size) { - ERROR(device->base.context, "Deepblu reply packet too big for buffer (ndata=%d, size=%zu)", ndata, size); - return DC_STATUS_IO; - } - - csum += cmd + ndata; - - for (i = 7; i < len; i += 2) { - int byte = read_hex_byte(buffer + i); - if (byte < 0) { - ERROR(device->base.context, "Deepblu reply packet data not valid hex"); - return DC_STATUS_IO; - } - *buf++ = byte; - csum += byte; - } - - if (csum & 255) { - ERROR(device->base.context, "Deepblu reply packet csum not valid (%x)", csum); - return DC_STATUS_IO; - } - - *received = ndata >> 1; - return DC_STATUS_SUCCESS; -} - -// Common communication pattern: send a command, expect data back with the same -// command byte. -static dc_status_t -deepblu_send_recv(deepblu_device_t *device, const unsigned char cmd, - const unsigned char *data, size_t data_size, - unsigned char *result, size_t result_size) -{ - dc_status_t status; - size_t got; - - status = deepblu_send_cmd(device, cmd, data, data_size); - if (status != DC_STATUS_SUCCESS) - return status; - status = deepblu_recv_data(device, cmd, result, result_size, &got); - if (status != DC_STATUS_SUCCESS) - return status; - if (got != result_size) { - ERROR(device->base.context, "Deepblu result size didn't match expected (expected %zu, got %zu)", - result_size, got); - return DC_STATUS_IO; - } - return DC_STATUS_SUCCESS; -} - -static dc_status_t -deepblu_recv_bulk(deepblu_device_t *device, const unsigned char cmd, unsigned char *buf, size_t len) -{ - while (len) { - dc_status_t status; - size_t got; - - status = deepblu_recv_data(device, cmd, buf, len, &got); - if (status != DC_STATUS_SUCCESS) - return status; - if (got > len) { - ERROR(device->base.context, "Deepblu bulk receive overflow"); - return DC_STATUS_IO; - } - buf += got; - len -= got; - } - return DC_STATUS_SUCCESS; -} - -dc_status_t -deepblu_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) -{ - deepblu_device_t *device; - - if (out == NULL) - return DC_STATUS_INVALIDARGS; - - // Allocate memory. - device = (deepblu_device_t *) dc_device_allocate (context, &deepblu_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)); - - *out = (dc_device_t *) device; - return DC_STATUS_SUCCESS; -} - -static dc_status_t -deepblu_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size) -{ - deepblu_device_t *device = (deepblu_device_t *)abstract; - - HEXDUMP(device->base.context, DC_LOGLEVEL_DEBUG, "set_fingerprint", data, size); - - 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 unsigned char bcd(int val) -{ - if (val >= 0 && val < 100) { - int high = val / 10; - int low = val % 10; - return (high << 4) | low; - } - return 0; -} - -static dc_status_t -deepblu_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime) -{ - deepblu_device_t *device = (deepblu_device_t *)abstract; - unsigned char result[1], data[6]; - dc_status_t status; - size_t len; - - data[0] = bcd(datetime->year - 2000); - data[1] = bcd(datetime->month); - data[2] = bcd(datetime->day); - data[3] = bcd(datetime->hour); - data[4] = bcd(datetime->minute); - data[5] = bcd(datetime->second); - - // Maybe also check that we received one zero byte (ack?) - return deepblu_send_recv(device, CMD_SETTIME, - data, sizeof(data), - result, sizeof(result)); -} - -static dc_status_t -deepblu_device_close (dc_device_t *abstract) -{ - deepblu_device_t *device = (deepblu_device_t *) abstract; - - return DC_STATUS_SUCCESS; -} - -static const char zero[MAX_DATA]; - -static dc_status_t -deepblu_download_dive(deepblu_device_t *device, unsigned char nr, dc_dive_callback_t callback, void *userdata) -{ - unsigned char header_len; - unsigned char profilebytes[2]; - unsigned int profile_len; - dc_status_t status; - char header[256]; - unsigned char *profile; - - status = deepblu_send_recv(device, CMD_GETDIVE, &nr, 1, &header_len, 1); - if (status != DC_STATUS_SUCCESS) - return status; - status = deepblu_recv_bulk(device, RSP_DIVESTAT, header, header_len); - if (status != DC_STATUS_SUCCESS) - return status; - memset(header + header_len, 0, 256 - header_len); - - /* The header is the fingerprint. If we've already seen this header, we're done */ - if (memcmp(header, device->fingerprint, sizeof (device->fingerprint)) == 0) - return DC_STATUS_DONE; - - status = deepblu_send_recv(device, CMD_GETPROFILE, &nr, 1, profilebytes, sizeof(profilebytes)); - if (status != DC_STATUS_SUCCESS) - return status; - profile_len = (profilebytes[0] << 8) | profilebytes[1]; - - profile = malloc(256 + profile_len); - if (!profile) { - ERROR (device->base.context, "Insufficient buffer space available."); - return DC_STATUS_NOMEMORY; - } - - // We make the dive data be 256 bytes of header, followed by the profile data - memcpy(profile, header, 256); - - status = deepblu_recv_bulk(device, RSP_DIVEPROF, profile+256, profile_len); - if (status != DC_STATUS_SUCCESS) - return status; - - if (callback) { - if (!callback(profile, profile_len+256, header, header_len, userdata)) - return DC_STATUS_DONE; - } - free(profile); - return DC_STATUS_SUCCESS; -} - -static dc_status_t -deepblu_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) -{ - dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; - deepblu_device_t *device = (deepblu_device_t *) abstract; - unsigned char nrdives, val; - dc_status_t status; - int i; - - val = 0; - status = deepblu_send_recv(device, CMD_GETDIVENR, &val, 1, &nrdives, 1); - if (status != DC_STATUS_SUCCESS) - return status; - - if (!nrdives) - return DC_STATUS_SUCCESS; - - progress.maximum = nrdives; - progress.current = 0; - device_event_emit(abstract, DC_EVENT_PROGRESS, &progress); - - for (i = 1; i <= nrdives; i++) { - if (device_is_cancelled(abstract)) { - dc_status_set_error(&status, DC_STATUS_CANCELLED); - break; - } - - status = deepblu_download_dive(device, i, callback, userdata); - switch (status) { - case DC_STATUS_DONE: - i = nrdives; - break; - case DC_STATUS_SUCCESS: - break; - default: - return status; - } - progress.current = i; - device_event_emit(abstract, DC_EVENT_PROGRESS, &progress); - } - - return DC_STATUS_SUCCESS; -} diff --git a/src/deepblu_cosmiq.c b/src/deepblu_cosmiq.c new file mode 100644 index 0000000..5454842 --- /dev/null +++ b/src/deepblu_cosmiq.c @@ -0,0 +1,548 @@ +/* + * libdivecomputer + * + * Copyright (C) 2019 Linus Torvalds + * Copyright (C) 2022 Jef Driesen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include +#include + +#include "deepblu_cosmiq.h" +#include "context-private.h" +#include "device-private.h" +#include "platform.h" +#include "checksum.h" +#include "array.h" + +// Maximum data in a packet. It's actually much +// less than this, since BLE packets are small and +// with the 7 bytes of headers and final newline +// and the HEX encoding, the actual maximum is +// just something like 6 bytes. +// +// But in theory the data could be done over +// multiple packets. That doesn't seem to be +// the case in anything I've seen so far. +// +// Pick something small and easy to use for +// stack buffers. +#define MAX_DATA 20 + +#define SZ_HEADER 36 + +#define FP_OFFSET 6 +#define FP_SIZE 6 + +#define CMD_SET_DATETIME 0x20 + +#define CMD_DIVE_COUNT 0x40 +#define CMD_DIVE_HEADER 0x41 +#define CMD_DIVE_HEADER_DATA 0x42 +#define CMD_DIVE_PROFILE 0x43 +#define CMD_DIVE_PROFILE_DATA 0x44 + +#define CMD_SYSTEM_FW 0x58 +#define CMD_SYSTEM_MAC 0x5A + +#define NSTEPS 1000 +#define STEP(i,n) (NSTEPS * (i) / (n)) + +typedef struct deepblu_cosmiq_device_t { + dc_device_t base; + dc_iostream_t *iostream; + unsigned char fingerprint[FP_SIZE]; +} deepblu_cosmiq_device_t; + +static dc_status_t deepblu_cosmiq_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); +static dc_status_t deepblu_cosmiq_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +static dc_status_t deepblu_cosmiq_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime); + +static const dc_device_vtable_t deepblu_cosmiq_device_vtable = { + sizeof(deepblu_cosmiq_device_t), + DC_FAMILY_DEEPBLU_COSMIQ, + deepblu_cosmiq_device_set_fingerprint, /* set_fingerprint */ + NULL, /* read */ + NULL, /* write */ + NULL, /* dump */ + deepblu_cosmiq_device_foreach, /* foreach */ + deepblu_cosmiq_device_timesync, /* timesync */ + NULL, /* close */ +}; + +// +// Send a cmd packet. +// +// The format of the cmd on the "wire" is: +// - byte '#' +// - HEX char of cmd +// - HEX char two's complement modular sum of packet data (including cmd/size) +// - HEX char size of data as encoded in HEX +// - n * HEX char data +// - byte '\n' +// so you end up having 8 bytes of header/trailer overhead, and two bytes +// for every byte of data sent due to the HEX encoding. +// +static dc_status_t +deepblu_cosmiq_send (deepblu_cosmiq_device_t *device, const unsigned char cmd, const unsigned char data[], size_t size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + + if (size > MAX_DATA) + return DC_STATUS_INVALIDARGS; + + if (device_is_cancelled (abstract)) + return DC_STATUS_CANCELLED; + + // Build the packet. + unsigned char csum = ~checksum_add_uint8 (data, size, cmd + 2 * size) + 1; + unsigned char raw[3 + MAX_DATA] = { + cmd, + csum, + 2 * size + }; + if (size) { + memcpy (raw + 3, data, size); + } + unsigned char packet[1 + 2 * (3 + MAX_DATA) + 1] = {0}; + packet[0] = '#'; + array_convert_bin2hex (raw, 3 + size, packet + 1, 2 * (3 + size)); + packet[1 + 2 * (3 + size)] = '\n'; + + HEXDUMP (device->base.context, DC_LOGLEVEL_DEBUG, "cmd", raw, 3 + size); + + // Send the command. + status = dc_iostream_write (device->iostream, packet, 2 + 2 * (3 + size), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (device->base.context, "Failed to send the command."); + return status; + } + + return status; +} + +// +// Receive one 'line' of data +// +// The deepblu BLE protocol is ASCII line based and packetized. +// Normally one packet is one line, but it looks like the Nordic +// Semi BLE chip will sometimes send packets early (some internal +// serial buffer timeout?) with incompete data. +// +// So read packets until you get newline. +static dc_status_t +deepblu_cosmiq_recv_line (deepblu_cosmiq_device_t *device, unsigned char data[], size_t size, size_t *actual) +{ + dc_status_t status = DC_STATUS_SUCCESS; + size_t nbytes = 0; + + while (1) { + unsigned char buffer[20] = {0}; + size_t transferred = 0; + + status = dc_iostream_read (device->iostream, buffer, sizeof(buffer), &transferred); + if (status != DC_STATUS_SUCCESS) { + ERROR (device->base.context, "Failed to receive the reply packet."); + return status; + } + + if (transferred < 1) { + ERROR (device->base.context, "Empty reply packet received."); + return DC_STATUS_PROTOCOL; + } + + // Append the payload data to the output buffer. If the output + // buffer is too small, the error is not reported immediately + // but delayed until all packets have been received. + if (nbytes < size) { + size_t n = transferred; + if (nbytes + n > size) { + n = size - nbytes; + } + memcpy(data + nbytes, buffer, n); + } + nbytes += transferred; + + // Last packet? + if (buffer[transferred - 1] == '\n') + break; + } + + // Verify the expected number of bytes. + if (nbytes > size) { + ERROR (device->base.context, "Unexpected number of bytes received (" DC_PRINTF_SIZE " " DC_PRINTF_SIZE ").", nbytes, size); + return DC_STATUS_PROTOCOL; + } + + if (actual) + *actual = nbytes; + + return DC_STATUS_SUCCESS; +} + +// +// Receive a reply packet +// +// The reply packet has the same format as the cmd packet we +// send, except the first byte is '$' instead of '#'. +static dc_status_t +deepblu_cosmiq_recv (deepblu_cosmiq_device_t *device, const unsigned char cmd, unsigned char data[], size_t size, size_t *actual) +{ + dc_status_t status = DC_STATUS_SUCCESS; + unsigned char packet[1 + 2 * (3 + MAX_DATA) + 1] = {0}; + + size_t transferred = 0; + status = deepblu_cosmiq_recv_line (device, packet, sizeof(packet), &transferred); + if (status != DC_STATUS_SUCCESS) + return status; + + if (transferred < 8 || (transferred % 2) != 0) { + ERROR (device->base.context, "Unexpected packet length (" DC_PRINTF_SIZE ").", transferred); + return DC_STATUS_PROTOCOL; + } + + if (packet[0] != '$' || packet[transferred - 1] != '\n') { + ERROR (device->base.context, "Unexpected packet start/end byte."); + return DC_STATUS_PROTOCOL; + } + + size_t length = transferred - 2; + + unsigned char raw[3 + MAX_DATA] = {0}; + if (array_convert_hex2bin (packet + 1, length, raw, length / 2) != 0) { + ERROR (device->base.context, "Unexpected packet data."); + return DC_STATUS_PROTOCOL; + } + + length /= 2; + + HEXDUMP (device->base.context, DC_LOGLEVEL_DEBUG, "rcv", raw, length); + + unsigned char rsp = raw[0]; + if (rsp != cmd) { + ERROR (device->base.context, "Unexpected packet command byte (%02x)", rsp); + return DC_STATUS_PROTOCOL; + } + + unsigned int n = raw[2]; + if ((n % 2) != 0 || n != transferred - 8) { + ERROR (device->base.context, "Unexpected packet length (%u)", n); + return DC_STATUS_PROTOCOL; + } + + unsigned char csum = checksum_add_uint8 (raw, length, 0); + if (csum != 0) { + ERROR (device->base.context, "Unexpected packet checksum (%02x).", csum); + return DC_STATUS_PROTOCOL; + } + + length -= 3; + + if (length > size) { + ERROR (device->base.context, "Unexpected number of bytes received (" DC_PRINTF_SIZE " " DC_PRINTF_SIZE ").", length, size); + return DC_STATUS_PROTOCOL; + } + + memcpy (data, raw + 3, length); + + if (actual) + *actual = length; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepblu_cosmiq_transfer (deepblu_cosmiq_device_t *device, const unsigned char cmd, + const unsigned char input[], size_t isize, + unsigned char output[], size_t osize) +{ + dc_status_t status = DC_STATUS_SUCCESS; + + status = deepblu_cosmiq_send (device, cmd, input, isize); + if (status != DC_STATUS_SUCCESS) + return status; + + size_t transferred = 0; + status = deepblu_cosmiq_recv (device, cmd, output, osize, &transferred); + if (status != DC_STATUS_SUCCESS) + return status; + + if (transferred != osize) { + ERROR (device->base.context, "Unexpected number of bytes received (" DC_PRINTF_SIZE " " DC_PRINTF_SIZE ").", osize, transferred); + return DC_STATUS_PROTOCOL; + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepblu_cosmiq_recv_bulk (deepblu_cosmiq_device_t *device, dc_event_progress_t *progress, const unsigned char cmd, unsigned char data[], size_t size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + + const unsigned int initial = progress ? progress->current : 0; + + size_t nbytes = 0; + while (nbytes < size) { + size_t transferred = 0; + status = deepblu_cosmiq_recv (device, cmd, data + nbytes, size - nbytes, &transferred); + if (status != DC_STATUS_SUCCESS) + return status; + + nbytes += transferred; + + // Update and emit a progress event. + if (progress) { + progress->current = initial + STEP(nbytes, size); + device_event_emit (abstract, DC_EVENT_PROGRESS, progress); + } + } + + return DC_STATUS_SUCCESS; +} + +dc_status_t +deepblu_cosmiq_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) +{ + dc_status_t status = DC_STATUS_SUCCESS; + deepblu_cosmiq_device_t *device = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + device = (deepblu_cosmiq_device_t *) dc_device_allocate (context, &deepblu_cosmiq_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 timeout for receiving data (1000ms). + status = dc_iostream_set_timeout (device->iostream, 1000); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the timeout."); + goto error_free; + } + + dc_iostream_purge (device->iostream, DC_DIRECTION_ALL); + + *out = (dc_device_t *) device; + + return DC_STATUS_SUCCESS; + +error_free: + dc_device_deallocate ((dc_device_t *) device); + return status; +} + +static dc_status_t +deepblu_cosmiq_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size) +{ + deepblu_cosmiq_device_t *device = (deepblu_cosmiq_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 +deepblu_cosmiq_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) +{ + dc_status_t status = DC_STATUS_SUCCESS; + deepblu_cosmiq_device_t *device = (deepblu_cosmiq_device_t *) abstract; + const unsigned char zero = 0; + + // Enable progress notifications. + dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Read the firmware version. + unsigned char fw[1] = {0}; + status = deepblu_cosmiq_transfer (device, CMD_SYSTEM_FW, &zero, 1, fw, sizeof(fw)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the firmware version."); + goto error_exit; + } + + // Read the MAC address. + unsigned char mac[6] = {0}; + status = deepblu_cosmiq_transfer (device, CMD_SYSTEM_MAC, &zero, 1, mac, sizeof(mac)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the MAC address."); + goto error_exit; + } + + // Emit a device info event. + dc_event_devinfo_t devinfo; + devinfo.model = 0; + devinfo.firmware = fw[0] & 0x3F; + devinfo.serial = array_uint32_le (mac); + device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); + + unsigned char ndives = 0; + status = deepblu_cosmiq_transfer (device, CMD_DIVE_COUNT, &zero, 1, &ndives, 1); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the number of dives."); + goto error_exit; + } + + // Update and emit a progress event. + progress.current = (ndives == 0 ? 1 : 0) * NSTEPS; + progress.maximum = (ndives + 1) * NSTEPS; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + if (ndives == 0) { + status = DC_STATUS_SUCCESS; + goto error_exit; + } + + unsigned char *headers = malloc (ndives * SZ_HEADER); + if (headers == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_exit; + } + + unsigned int count = 0; + for (unsigned int i = 0; i < ndives; ++i) { + unsigned char number = i + 1; + + unsigned char len = 0; + status = deepblu_cosmiq_transfer (device, CMD_DIVE_HEADER, &number, 1, &len, 1); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the dive header."); + goto error_free_headers; + } + + if (len != SZ_HEADER) { + status = DC_STATUS_PROTOCOL; + goto error_free_headers; + } + + status = deepblu_cosmiq_recv_bulk (device, NULL, CMD_DIVE_HEADER_DATA, headers + i * SZ_HEADER, SZ_HEADER); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the dive header."); + goto error_free_headers; + } + + // Update and emit a progress event. + progress.current = STEP(i + 1, ndives); + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + if (memcmp (headers + i * SZ_HEADER + FP_OFFSET, device->fingerprint, sizeof (device->fingerprint)) == 0) + break; + + count++; + } + + // Update and emit a progress event. + progress.current = 1 * NSTEPS; + progress.maximum = (count + 1) * NSTEPS; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Allocate memory for the dive. + dc_buffer_t *dive = dc_buffer_new (4096); + if (dive == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_free_headers; + } + + for (unsigned int i = 0; i < count; ++i) { + unsigned char number = i + 1; + + unsigned char rsp_len[2] = {0}; + status = deepblu_cosmiq_transfer (device, CMD_DIVE_PROFILE, &number, 1, rsp_len, sizeof(rsp_len)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the dive profile."); + goto error_free_dive; + } + + unsigned int len = array_uint16_be (rsp_len); + + // Erase the buffer. + dc_buffer_clear (dive); + + // Allocate memory for the dive. + if (!dc_buffer_resize (dive, len + SZ_HEADER)) { + ERROR (abstract->context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_free_dive; + } + + // Cache the pointer. + unsigned char *data = dc_buffer_get_data (dive); + + memcpy (data, headers + i * SZ_HEADER, SZ_HEADER); + + status = deepblu_cosmiq_recv_bulk (device, &progress, CMD_DIVE_PROFILE_DATA, data + SZ_HEADER, len); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the dive profile."); + goto error_free_dive; + } + + if (callback && !callback (data, len + SZ_HEADER, data + FP_OFFSET, FP_SIZE, userdata)) + break; + } + +error_free_dive: + dc_buffer_free (dive); +error_free_headers: + free (headers); +error_exit: + return status; +} + +static dc_status_t +deepblu_cosmiq_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) +{ + dc_status_t status = DC_STATUS_SUCCESS; + deepblu_cosmiq_device_t *device = (deepblu_cosmiq_device_t *) abstract; + + if (datetime->year < 2000) { + ERROR (abstract->context, "Invalid date/time value specified."); + return DC_STATUS_INVALIDARGS; + } + + const unsigned char cmd[6] = { + dec2bcd(datetime->year - 2000), + dec2bcd(datetime->month), + dec2bcd(datetime->day), + dec2bcd(datetime->hour), + dec2bcd(datetime->minute), + dec2bcd(datetime->second)}; + unsigned char rsp[1] = {0}; + status = deepblu_cosmiq_transfer (device, CMD_SET_DATETIME, + cmd, sizeof(cmd), rsp, sizeof(rsp)); + if (status != DC_STATUS_SUCCESS) + return status; + + return DC_STATUS_SUCCESS; +} diff --git a/src/deepblu.h b/src/deepblu_cosmiq.h similarity index 77% rename from src/deepblu.h rename to src/deepblu_cosmiq.h index 92b821f..3587812 100644 --- a/src/deepblu.h +++ b/src/deepblu_cosmiq.h @@ -1,7 +1,8 @@ /* - * Deepblu Cosmiq+ downloading/parsing + * libdivecomputer * * Copyright (C) 2018 Linus Torvalds + * Copyright (C) 2022 Jef Driesen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -19,8 +20,8 @@ * MA 02110-1301 USA */ -#ifndef DEEPBLU_H -#define DEEPBLU_H +#ifndef DEEPBLU_COSMIQ_H +#define DEEPBLU_COSMIQ_H #include #include @@ -32,12 +33,12 @@ extern "C" { #endif /* __cplusplus */ dc_status_t -deepblu_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); +deepblu_cosmiq_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); dc_status_t -deepblu_parser_create (dc_parser_t **parser, dc_context_t *context); +deepblu_cosmiq_parser_create (dc_parser_t **parser, dc_context_t *context); #ifdef __cplusplus } #endif /* __cplusplus */ -#endif /* DEEPBLU_H */ +#endif /* DEEPBLU_COSMIQ_H */ diff --git a/src/deepblu_cosmiq_parser.c b/src/deepblu_cosmiq_parser.c new file mode 100644 index 0000000..508bc5d --- /dev/null +++ b/src/deepblu_cosmiq_parser.c @@ -0,0 +1,218 @@ +/* + * libdivecomputer + * + * Copyright (C) 2019 Linus Torvalds + * Copyright (C) 2022 Jef Driesen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include + +#include + +#include "deepblu_cosmiq.h" +#include "context-private.h" +#include "parser-private.h" +#include "array.h" + +#define SCUBA 2 +#define GAUGE 3 +#define FREEDIVE 4 + +#define SZ_HEADER 36 +#define SZ_SAMPLE 4 + +typedef struct deepblu_cosmiq_parser_t { + dc_parser_t base; + double hydrostatic; +} deepblu_cosmiq_parser_t; + +static dc_status_t deepblu_cosmiq_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t deepblu_cosmiq_parser_set_density (dc_parser_t *abstract, double density); +static dc_status_t deepblu_cosmiq_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); +static dc_status_t deepblu_cosmiq_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); +static dc_status_t deepblu_cosmiq_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); + +static const dc_parser_vtable_t deepblu_cosmiq_parser_vtable = { + sizeof(deepblu_cosmiq_parser_t), + DC_FAMILY_DEEPBLU_COSMIQ, + deepblu_cosmiq_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + deepblu_cosmiq_parser_set_density, /* set_density */ + deepblu_cosmiq_parser_get_datetime, /* datetime */ + deepblu_cosmiq_parser_get_field, /* fields */ + deepblu_cosmiq_parser_samples_foreach, /* samples_foreach */ + NULL /* destroy */ +}; + +dc_status_t +deepblu_cosmiq_parser_create (dc_parser_t **out, dc_context_t *context) +{ + deepblu_cosmiq_parser_t *parser = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + parser = (deepblu_cosmiq_parser_t *) dc_parser_allocate (context, &deepblu_cosmiq_parser_vtable); + if (parser == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Set the default values. + parser->hydrostatic = DEF_DENSITY_SALT * GRAVITY; + + *out = (dc_parser_t *) parser; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepblu_cosmiq_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) +{ + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepblu_cosmiq_parser_set_density (dc_parser_t *abstract, double density) +{ + deepblu_cosmiq_parser_t *parser = (deepblu_cosmiq_parser_t *) abstract; + + parser->hydrostatic = density * GRAVITY; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepblu_cosmiq_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 = array_uint16_le(data + 6); + datetime->day = data[8]; + datetime->month = data[9]; + datetime->minute = data[10]; + datetime->hour = data[11]; + datetime->second = 0; + datetime->timezone = DC_TIMEZONE_NONE; + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepblu_cosmiq_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) +{ + deepblu_cosmiq_parser_t *parser = (deepblu_cosmiq_parser_t *) abstract; + const unsigned char *data = abstract->data; + unsigned int size = abstract->size; + + dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + + if (size < SZ_HEADER) + return DC_STATUS_DATAFORMAT; + + unsigned int mode = data[2]; + unsigned int atmospheric = array_uint16_le (data + 4) & 0x1FFF; + + if (value) { + switch (type) { + case DC_FIELD_DIVETIME: + if (mode == SCUBA || mode == GAUGE) + *((unsigned int *) value) = array_uint16_le(data + 12) * 60; + else + *((unsigned int *) value) = array_uint16_le(data + 12); + break; + case DC_FIELD_MAXDEPTH: + *((double *) value) = (signed int) (array_uint16_le (data + 22) - atmospheric) * (BAR / 1000.0) / parser->hydrostatic; + break; + case DC_FIELD_GASMIX_COUNT: + *((unsigned int *) value) = mode == SCUBA; + break; + case DC_FIELD_GASMIX: + gasmix->oxygen = data[3] / 100.0; + gasmix->helium = 0.0; + gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; + break; + case DC_FIELD_DIVEMODE: + switch (mode) { + case SCUBA: + *((dc_divemode_t *) value) = DC_DIVEMODE_OC; + break; + case GAUGE: + *((dc_divemode_t *) value) = DC_DIVEMODE_GAUGE; + break; + case FREEDIVE: + *((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE; + break; + default: + ERROR (abstract->context, "Unknown activity type '%02x'", mode); + return DC_STATUS_DATAFORMAT; + } + break; + case DC_FIELD_ATMOSPHERIC: + *((double *) value) = atmospheric / 1000.0; + break; + default: + return DC_STATUS_UNSUPPORTED; + } + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepblu_cosmiq_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +{ + deepblu_cosmiq_parser_t *parser = (deepblu_cosmiq_parser_t *) abstract; + const unsigned char *data = abstract->data; + unsigned int size = abstract->size; + + if (size < SZ_HEADER) + return DC_STATUS_DATAFORMAT; + + unsigned int interval = data[26]; + unsigned int atmospheric = array_uint16_le (data + 4) & 0x1FFF; + + unsigned int time = 0; + unsigned int offset = SZ_HEADER; + while (offset + SZ_SAMPLE <= size) { + dc_sample_value_t sample = {0}; + unsigned int temperature = array_uint16_le(data + offset + 0); + unsigned int depth = array_uint16_le(data + offset + 2); + offset += SZ_SAMPLE; + + time += interval; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + + sample.depth = (signed int) (depth - atmospheric) * (BAR / 1000.0) / parser->hydrostatic; + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + + sample.temperature = temperature / 10.0; + if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); + } + + return DC_STATUS_SUCCESS; +} diff --git a/src/deepblu_parser.c b/src/deepblu_parser.c deleted file mode 100644 index eff1b35..0000000 --- a/src/deepblu_parser.c +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Deeplu Cosmiq+ parsing - * - * Copyright (C) 2019 Linus Torvalds - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - -#include -#include -#include -#include - -#include "deepblu.h" -#include "context-private.h" -#include "parser-private.h" -#include "array.h" -#include "field-cache.h" - -#define MAXFIELDS 128 - -struct msg_desc; - -typedef struct deepblu_parser_t { - dc_parser_t base; - - dc_sample_callback_t callback; - void *userdata; - - // 20 sec for scuba, 1 sec for freedives - int sample_interval; - - // Common fields - struct dc_field_cache cache; -} deepblu_parser_t; - -static dc_status_t deepblu_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); -static dc_status_t deepblu_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); -static dc_status_t deepblu_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); -static dc_status_t deepblu_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); - -static const dc_parser_vtable_t deepblu_parser_vtable = { - sizeof(deepblu_parser_t), - DC_FAMILY_DEEPBLU, - deepblu_parser_set_data, /* set_data */ - deepblu_parser_get_datetime, /* datetime */ - deepblu_parser_get_field, /* fields */ - deepblu_parser_samples_foreach, /* samples_foreach */ - NULL /* destroy */ -}; - -dc_status_t -deepblu_parser_create (dc_parser_t **out, dc_context_t *context) -{ - deepblu_parser_t *parser = NULL; - - if (out == NULL) - return DC_STATUS_INVALIDARGS; - - // Allocate memory. - parser = (deepblu_parser_t *) dc_parser_allocate (context, &deepblu_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 double -pressure_to_depth(unsigned int mbar) -{ - // Specific weight of seawater (millibar to cm) - const double specific_weight = 1.024 * 0.980665; - - // Absolute pressure, subtract surface pressure - if (mbar < 1013) - return 0.0; - mbar -= 1013; - return mbar / specific_weight / 100.0; -} - -static dc_status_t -deepblu_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) -{ - deepblu_parser_t *deepblu = (deepblu_parser_t *) abstract; - const unsigned char *hdr = data; - const unsigned char *profile = data + 256; - unsigned int divetime, maxpressure; - dc_gasmix_t gasmix = {0, }; - - if (size < 256) - return DC_STATUS_IO; - - deepblu->callback = NULL; - deepblu->userdata = NULL; - memset(&deepblu->cache, 0, sizeof(deepblu->cache)); - - // LE16 at 0 is 'dive number' - - // LE16 at 12 is the dive time - // It's in seconds for freedives, minutes for scuba/gauge - divetime = hdr[12] + 256*hdr[13]; - - // Byte at 2 is 'activity type' (2 = scuba, 3 = gauge, 4 = freedive) - // Byte at 3 is O2 percentage - switch (data[2]) { - case 2: - // SCUBA - divetime in minutes - divetime *= 60; - gasmix.oxygen = data[3] / 100.0; - DC_ASSIGN_IDX(deepblu->cache, GASMIX, 0, gasmix); - DC_ASSIGN_FIELD(deepblu->cache, GASMIX_COUNT, 1); - DC_ASSIGN_FIELD(deepblu->cache, DIVEMODE, DC_DIVEMODE_OC); - break; - case 3: - // GAUGE - divetime in minutes - divetime *= 60; - DC_ASSIGN_FIELD(deepblu->cache, DIVEMODE, DC_DIVEMODE_GAUGE); - break; - case 4: - // FREEDIVE - divetime in seconds - DC_ASSIGN_FIELD(deepblu->cache, DIVEMODE, DC_DIVEMODE_FREEDIVE); - deepblu->sample_interval = 1; - break; - default: - ERROR (abstract->context, "Deepblu: unknown activity type '%02x'", data[2]); - break; - } - - // Seems to be fixed at 20s for scuba, 1s for freedive - deepblu->sample_interval = hdr[26]; - - maxpressure = hdr[22] + 256*hdr[23]; // Maxpressure in millibar - - DC_ASSIGN_FIELD(deepblu->cache, DIVETIME, divetime); - DC_ASSIGN_FIELD(deepblu->cache, MAXDEPTH, pressure_to_depth(maxpressure)); - - return DC_STATUS_SUCCESS; -} - -// The layout of the header in the 'data' is -// 0: LE16 dive number -// 2: dive type byte? -// 3: O2 percentage byte -// 4: unknown -// 5: unknown -// 6: LE16 year -// 8: day of month -// 9: month -// 10: minute -// 11: hour -// 12: LE16 dive time -// 14: LE16 ?? -// 16: LE16 surface pressure? -// 18: LE16 ?? -// 20: LE16 ?? -// 22: LE16 max depth pressure -// 24: LE16 water temp -// 26: LE16 ?? -// 28: LE16 ?? -// 30: LE16 ?? -// 32: LE16 ?? -// 34: LE16 ?? -static dc_status_t -deepblu_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) -{ - deepblu_parser_t *deepblu = (deepblu_parser_t *) abstract; - const unsigned char *data = deepblu->base.data; - int len = deepblu->base.size; - - if (len < 256) - return DC_STATUS_IO; - datetime->year = data[6] + (data[7] << 8); - datetime->day = data[8]; - datetime->month = data[9]; - datetime->minute = data[10]; - datetime->hour = data[11]; - datetime->second = 0; - datetime->timezone = DC_TIMEZONE_NONE; - - return DC_STATUS_SUCCESS; -} - -static dc_status_t -deepblu_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) -{ - deepblu_parser_t *deepblu = (deepblu_parser_t *) abstract; - - if (!value) - return DC_STATUS_INVALIDARGS; - - return dc_field_get(&deepblu->cache, type, flags, value); -} - -static dc_status_t -deepblu_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) -{ - deepblu_parser_t *deepblu = (deepblu_parser_t *) abstract; - const unsigned char *data = deepblu->base.data; - int len = deepblu->base.size, i; - - deepblu->callback = callback; - deepblu->userdata = userdata; - - // Skip the header information - if (len < 256) - return DC_STATUS_IO; - data += 256; - len -= 256; - - // The rest should be samples every 20s with temperature and depth - for (i = 0; i < len/4; i++) { - dc_sample_value_t sample = {0}; - unsigned int temp = data[0]+256*data[1]; - unsigned int pressure = data[2]+256*data[3]; - - data += 4; - sample.time = (i+1)*deepblu->sample_interval; - if (callback) callback (DC_SAMPLE_TIME, sample, userdata); - - sample.depth = pressure_to_depth(pressure); - if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); - - sample.temperature = temp / 10.0; - if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); - } - - return DC_STATUS_SUCCESS; -} diff --git a/src/deepsix_excursion.c b/src/deepsix_excursion.c index 378e20f..de0bb64 100644 --- a/src/deepsix_excursion.c +++ b/src/deepsix_excursion.c @@ -32,7 +32,8 @@ #define MAXPACKET 255 -#define HEADERSIZE 156 +#define HEADERSIZE_MIN 128 +#define HEADERSIZE_V0 156 #define NSTEPS 1000 #define STEP(i,n) (NSTEPS * (i) / (n)) @@ -55,9 +56,14 @@ #define CMD_SETTINGS_STORE 0x27 #define CMD_SETTINGS_LOAD 0x28 -#define GRP_DIVE 0xC0 -#define CMD_DIVE_HEADER 0x02 -#define CMD_DIVE_PROFILE 0x03 +#define GRP_DIVE 0xC0 +#define CMD_DIVE_HEADER 0x02 +#define CMD_DIVE_PROFILE 0x03 +#define CMD_DIVE_COUNT 0x05 +#define CMD_DIVE_INDEX_LAST 0x06 +#define CMD_DIVE_INDEX_FIRST 0x07 +#define CMD_DIVE_INDEX_PREVIOUS 0x08 +#define CMD_DIVE_INDEX_NEXT 0x09 typedef struct deepsix_excursion_device_t { dc_device_t base; @@ -163,7 +169,9 @@ deepsix_excursion_recv (deepsix_excursion_device_t *device, unsigned char grp, u return DC_STATUS_PROTOCOL; } - memcpy(data, packet + 4, len); + if (len) { + memcpy(data, packet + 4, len); + } if (actual) *actual = len; @@ -214,8 +222,8 @@ deepsix_excursion_device_open (dc_device_t **out, dc_context_t *context, dc_iost goto error_free; } - // Set the timeout for receiving data (1000ms). - status = dc_iostream_set_timeout (device->iostream, 1000); + // 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; @@ -298,20 +306,45 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call devinfo.serial = array_convert_str2num (rsp_serial + 3, sizeof(rsp_serial) - 3); device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); - // Read the index of the last dive. - const unsigned char cmd_index[2] = {0}; - unsigned char rsp_index[2] = {0}; - status = deepsix_excursion_transfer (device, GRP_INFO, CMD_INFO_LASTDIVE, DIR_READ, cmd_index, sizeof(cmd_index), rsp_index, sizeof(rsp_index), NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the last dive index."); - return status; + // Firmware version 6+ uses the new commands. + unsigned int fw6 = memcmp(rsp_software, "D01", 3) == 0 && rsp_software[4] >= '6'; + + unsigned int ndives = 0, last = 0; + if (fw6) { + // Read the number of dives. + unsigned char rsp_ndives[2] = {0}; + status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_COUNT, DIR_READ, NULL, 0, rsp_ndives, sizeof(rsp_ndives), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the number of dives"); + return status; + } + + // Read the index of the last dive. + unsigned char rsp_last[4] = {0}; + status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_INDEX_LAST, DIR_READ, NULL, 0, rsp_last, sizeof(rsp_last), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the last dive index."); + return status; + } + + ndives = array_uint16_le (rsp_ndives); + last = array_uint16_le (rsp_last); + } else { + // Read the index of the last dive. + const unsigned char cmd_last[2] = {0}; + unsigned char rsp_last[2] = {0}; + status = deepsix_excursion_transfer (device, GRP_INFO, CMD_INFO_LASTDIVE, DIR_READ, cmd_last, sizeof(cmd_last), rsp_last, sizeof(rsp_last), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the last dive index."); + return status; + } + + ndives = last = array_uint16_le (rsp_last); } - // Calculate the number of dives. - unsigned int ndives = array_uint16_le (rsp_index); - // Update and emit a progress event. - progress.maximum = ndives * NSTEPS; + progress.current = 1 * NSTEPS; + progress.maximum = (ndives + 1) * NSTEPS; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); dc_buffer_t *buffer = dc_buffer_new(0); @@ -320,32 +353,58 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call return DC_STATUS_NOMEMORY; } + unsigned int number = last; for (unsigned int i = 0; i < ndives; ++i) { - unsigned int number = ndives - i; + if (fw6) { + if (i > 0) { + const unsigned char cmd_previous[] = { + (number ) & 0xFF, + (number >> 8) & 0xFF, + (number >> 16) & 0xFF, + (number >> 24) & 0xFF}; + unsigned char rsp_previous[4] = {0}; + status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_INDEX_PREVIOUS, DIR_READ, + cmd_previous, sizeof(cmd_previous), rsp_previous, sizeof(rsp_previous), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the previous dive index"); + return status; + } + number = array_uint32_le (rsp_previous); + } + } else { + number = ndives - i; + } + unsigned int headersize = 0; const unsigned char cmd_header[] = { (number ) & 0xFF, (number >> 8) & 0xFF}; - unsigned char rsp_header[HEADERSIZE] = {0}; - status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_HEADER, DIR_READ, cmd_header, sizeof(cmd_header), rsp_header, sizeof(rsp_header), NULL); + unsigned char rsp_header[MAXPACKET] = {0}; + status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_HEADER, DIR_READ, + cmd_header, sizeof(cmd_header), rsp_header, sizeof(rsp_header), &headersize); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the dive header."); goto error_free; } + if (headersize < HEADERSIZE_MIN || headersize != (fw6 ? rsp_header[2] : HEADERSIZE_V0)) { + ERROR (abstract->context, "Unexpected size of the dive header (%u).", headersize); + goto error_free; + } + if (memcmp(rsp_header + FP_OFFSET, device->fingerprint, sizeof(device->fingerprint)) == 0) break; unsigned int length = array_uint32_le (rsp_header + 8); // Update and emit a progress event. - progress.current = i * NSTEPS + STEP(sizeof(rsp_header), sizeof(rsp_header) + length); - device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + progress.current = (i + 1) * NSTEPS + STEP(headersize, headersize + length); + device_event_emit(abstract, DC_EVENT_PROGRESS, &progress); dc_buffer_clear(buffer); - dc_buffer_reserve(buffer, sizeof(rsp_header) + length); + dc_buffer_reserve(buffer, headersize + length); - if (!dc_buffer_append(buffer, rsp_header, sizeof(rsp_header))) { + if (!dc_buffer_append(buffer, rsp_header, headersize)) { ERROR (abstract->context, "Insufficient buffer space available."); status = DC_STATUS_NOMEMORY; goto error_free; @@ -362,7 +421,8 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call (offset >> 16) & 0xFF, (offset >> 24) & 0xFF}; unsigned char rsp_profile[MAXPACKET] = {0}; - status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_PROFILE, DIR_READ, cmd_profile, sizeof(cmd_profile), rsp_profile, sizeof(rsp_profile), &len); + status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_PROFILE, DIR_READ, + cmd_profile, sizeof(cmd_profile), rsp_profile, sizeof(rsp_profile), &len); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the dive profile."); goto error_free; @@ -375,8 +435,8 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call } // Update and emit a progress event. - progress.current = i * NSTEPS + STEP(sizeof(rsp_header) + offset + n, sizeof(rsp_header) + length); - device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + progress.current = (i + 1) * NSTEPS + STEP(headersize + offset + n, headersize + length); + device_event_emit(abstract, DC_EVENT_PROGRESS, &progress); if (!dc_buffer_append(buffer, rsp_profile, n)) { ERROR (abstract->context, "Insufficient buffer space available."); @@ -405,7 +465,7 @@ deepsix_excursion_device_timesync (dc_device_t *abstract, const dc_datetime_t *d dc_status_t status = DC_STATUS_SUCCESS; deepsix_excursion_device_t *device = (deepsix_excursion_device_t *) abstract; - if (datetime == NULL || datetime->year < 2000) { + if (datetime->year < 2000) { ERROR (abstract->context, "Invalid date/time value specified."); return DC_STATUS_INVALIDARGS; } diff --git a/src/deepsix_excursion_parser.c b/src/deepsix_excursion_parser.c index a5d312d..2fc8c2c 100644 --- a/src/deepsix_excursion_parser.c +++ b/src/deepsix_excursion_parser.c @@ -31,7 +31,11 @@ #include "parser-private.h" #include "array.h" -#define HEADERSIZE 156 +#define HEADERSIZE_MIN 128 + +#define MAX_SAMPLES 7 +#define MAX_EVENTS 7 +#define MAX_GASMIXES 20 #define ALARM 0x0001 #define TEMPERATURE 0x0002 @@ -39,27 +43,147 @@ #define CEILING 0x0004 #define CNS 0x0005 -#define DENSITY 1024.0 +#define SAMPLE_TEMPERATURE 0 +#define SAMPLE_DECO_NDL 1 +#define SAMPLE_CNS 2 + +#define EVENT_CHANGE_GAS 7 +#define EVENT_ALARMS 8 +#define EVENT_CHANGE_SETPOINT 9 +#define EVENT_SAMPLES_MISSED 10 +#define EVENT_RESERVED 15 + +#define ALARM_ASCENTRATE 0 +#define ALARM_CEILING 1 +#define ALARM_PO2 2 +#define ALARM_MAXDEPTH 3 +#define ALARM_DIVETIME 4 +#define ALARM_CNS 5 + +#define DECOSTOP 0x02 +#define SAFETYSTOP 0x04 + +#define DENSITY 1024 + +#define UNDEFINED 0xFFFFFFFF + +#define FWVERSION(major,minor) ( \ + ((((major) + '0') & 0xFF) << 8) | \ + ((minor) & 0xFF)) + +typedef struct deepsix_excursion_layout_t { + unsigned int headersize; + unsigned int version; + unsigned int divemode; + unsigned int samplerate; + unsigned int salinity; + unsigned int datetime; + unsigned int divetime; + unsigned int maxdepth; + unsigned int temperature_min; + unsigned int avgdepth; + unsigned int firmware; + unsigned int temperature_surf; + unsigned int atmospheric; + unsigned int gf; +} deepsix_excursion_layout_t; + +typedef struct deepsix_excursion_gasmix_t { + unsigned int id; + unsigned int oxygen; + unsigned int helium; +} deepsix_excursion_gasmix_t; + +typedef struct deepsix_excursion_sample_info_t { + unsigned int type; + unsigned int divisor; + unsigned int size; +} deepsix_excursion_sample_info_t; + +typedef struct deepsix_excursion_event_info_t { + unsigned int type; + unsigned int size; +} deepsix_excursion_event_info_t; typedef struct deepsix_excursion_parser_t { dc_parser_t base; + unsigned int cached; + unsigned int ngasmixes; + deepsix_excursion_gasmix_t gasmix[MAX_GASMIXES]; } deepsix_excursion_parser_t; static dc_status_t deepsix_excursion_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); static dc_status_t deepsix_excursion_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); static dc_status_t deepsix_excursion_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); static dc_status_t deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); +static dc_status_t deepsix_excursion_parser_samples_foreach_v0 (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); +static dc_status_t deepsix_excursion_parser_samples_foreach_v1 (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); static const dc_parser_vtable_t deepsix_parser_vtable = { sizeof(deepsix_excursion_parser_t), DC_FAMILY_DEEPSIX_EXCURSION, deepsix_excursion_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ deepsix_excursion_parser_get_datetime, /* datetime */ deepsix_excursion_parser_get_field, /* fields */ deepsix_excursion_parser_samples_foreach, /* samples_foreach */ NULL /* destroy */ }; +static const deepsix_excursion_layout_t deepsix_excursion_layout_v0 = { + 156,/* headersize */ + UNDEFINED, /* version */ + 4, /* divemode */ + 24, /* samplerate */ + UNDEFINED, /* salinity */ + 12, /* datetime */ + 20, /* divetime */ + 28, /* maxdepth */ + 32, /* temperature_min */ + UNDEFINED, /* avgdepth */ + 48, /* firmware */ + UNDEFINED, /* temperature_surf */ + 56, /* atmospheric */ + UNDEFINED, /* gf */ +}; + +static const deepsix_excursion_layout_t deepsix_excursion_layout_v1 = { + 129,/* headersize */ + 3, /* version */ + 4, /* divemode */ + 5, /* samplerate */ + 7, /* salinity */ + 12, /* datetime */ + 19, /* divetime */ + 29, /* maxdepth */ + 31, /* temperature_min */ + 33, /* avgdepth */ + 35, /* firmware */ + 43, /* temperature_surf */ + 45, /* atmospheric */ + 127, /* gf */ +}; + +static double +pressure_to_depth(unsigned int depth, unsigned int atmospheric, unsigned int density) +{ + return ((signed int)(depth - atmospheric)) * (BAR / 1000.0) / (density * GRAVITY); +} + +static unsigned int +deepsix_excursion_find_gasmix(deepsix_excursion_parser_t *parser, unsigned int o2, unsigned int he, unsigned int id) +{ + unsigned int i = 0; + while (i < parser->ngasmixes) { + if (o2 == parser->gasmix[i].oxygen && he == parser->gasmix[i].helium && id == parser->gasmix[i].id) + break; + i++; + } + return i; +} + dc_status_t deepsix_excursion_parser_create (dc_parser_t **out, dc_context_t *context) { @@ -75,6 +199,15 @@ deepsix_excursion_parser_create (dc_parser_t **out, dc_context_t *context) return DC_STATUS_NOMEMORY; } + // Set the default values. + parser->cached = 0; + parser->ngasmixes = 0; + for (unsigned int i = 0; i < MAX_GASMIXES; ++i) { + parser->gasmix[i].id = 0; + parser->gasmix[i].oxygen = 0; + parser->gasmix[i].helium = 0; + } + *out = (dc_parser_t *) parser; return DC_STATUS_SUCCESS; @@ -83,6 +216,17 @@ deepsix_excursion_parser_create (dc_parser_t **out, dc_context_t *context) static dc_status_t deepsix_excursion_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) { + deepsix_excursion_parser_t *parser = (deepsix_excursion_parser_t *) abstract; + + // Reset the cache. + parser->cached = 0; + parser->ngasmixes = 0; + for (unsigned int i = 0; i < MAX_GASMIXES; ++i) { + parser->gasmix[i].id = 0; + parser->gasmix[i].oxygen = 0; + parser->gasmix[i].helium = 0; + } + return DC_STATUS_SUCCESS; } @@ -92,17 +236,37 @@ deepsix_excursion_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dat const unsigned char *data = abstract->data; unsigned int size = abstract->size; - if (size < HEADERSIZE) + if (size < HEADERSIZE_MIN) return DC_STATUS_DATAFORMAT; + unsigned int version = data[3]; + const deepsix_excursion_layout_t *layout = version == 0 ? + &deepsix_excursion_layout_v0 : &deepsix_excursion_layout_v1; + + if (size < layout->headersize) + return DC_STATUS_DATAFORMAT; + + unsigned int firmware = array_uint16_be (data + layout->firmware + 4); + + const unsigned char *p = data + layout->datetime; + if (datetime) { - datetime->year = data[12] + 2000; - datetime->month = data[13]; - datetime->day = data[14]; - datetime->hour = data[15]; - datetime->minute = data[16]; - datetime->second = data[17]; - datetime->timezone = DC_TIMEZONE_NONE; + datetime->year = p[0] + 2000; + datetime->month = p[1]; + datetime->day = p[2]; + datetime->hour = p[3]; + datetime->minute = p[4]; + datetime->second = p[5]; + + if (version == 0) { + if (firmware >= FWVERSION(5, 'B')) { + datetime->timezone = (p[6] - 12) * 3600; + } else { + datetime->timezone = DC_TIMEZONE_NONE; + } + } else { + datetime->timezone = ((signed char) p[6]) * 900; + } } return DC_STATUS_SUCCESS; @@ -111,37 +275,74 @@ deepsix_excursion_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dat static dc_status_t deepsix_excursion_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) { + deepsix_excursion_parser_t *parser = (deepsix_excursion_parser_t *) abstract; const unsigned char *data = abstract->data; unsigned int size = abstract->size; - if (size < HEADERSIZE) + dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + dc_salinity_t *water = (dc_salinity_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; + + if (size < HEADERSIZE_MIN) return DC_STATUS_DATAFORMAT; - unsigned int atmospheric = array_uint32_le(data + 56); + unsigned int version = data[3]; + const deepsix_excursion_layout_t *layout = version == 0 ? + &deepsix_excursion_layout_v0 : &deepsix_excursion_layout_v1; - dc_salinity_t *water = (dc_salinity_t *) value; + if (size < layout->headersize) + return DC_STATUS_DATAFORMAT; + + if (version != 0 && !parser->cached) { + dc_status_t rc = deepsix_excursion_parser_samples_foreach_v1(abstract, NULL, NULL); + if (rc != DC_STATUS_SUCCESS) + return rc; + } + + unsigned int atmospheric = array_uint16_le(data + layout->atmospheric); + unsigned int density = DENSITY; + if (layout->salinity != UNDEFINED) { + density = 1000 + data[layout->salinity] * 10; + } if (value) { switch (type) { case DC_FIELD_DIVETIME: - *((unsigned int *) value) = array_uint32_le(data + 20); + *((unsigned int *) value) = array_uint32_le(data + layout->divetime); break; case DC_FIELD_MAXDEPTH: - *((double *) value) = (signed int)(array_uint32_le(data + 28) - atmospheric) * (BAR / 1000.0) / (DENSITY * GRAVITY); + *((double *) value) = pressure_to_depth(array_uint16_le(data + layout->maxdepth), atmospheric, density); break; + case DC_FIELD_AVGDEPTH: + if (layout->avgdepth == UNDEFINED) + return DC_STATUS_UNSUPPORTED; + *((double *) value) = pressure_to_depth(array_uint16_le(data + layout->avgdepth), atmospheric, density); + break; + case DC_FIELD_GASMIX_COUNT: + *((unsigned int *) value) = parser->ngasmixes; + break; + case DC_FIELD_GASMIX: + gasmix->oxygen = parser->gasmix[flags].oxygen / 100.0; + gasmix->helium = parser->gasmix[flags].helium / 100.0; + gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; break; case DC_FIELD_TEMPERATURE_MINIMUM: - *((double *) value) = (signed int) array_uint32_le(data + 32) / 10.0; + *((double *) value) = (signed int) array_uint16_le(data + layout->temperature_min) / 10.0; + break; + case DC_FIELD_TEMPERATURE_SURFACE: + if (layout->temperature_surf == UNDEFINED) + return DC_STATUS_UNSUPPORTED; + *((double *) value) = (signed int) array_uint16_le(data + layout->temperature_surf) / 10.0; break; case DC_FIELD_ATMOSPHERIC: *((double *) value) = atmospheric / 1000.0; break; case DC_FIELD_SALINITY: - water->type = DC_WATER_SALT; - water->density = DENSITY; + water->type = (density == 1000) ? DC_WATER_FRESH : DC_WATER_SALT; + water->density = density; break; case DC_FIELD_DIVEMODE: - switch (array_uint32_le(data + 4)) { + switch (data[layout->divemode]) { case 0: *((dc_divemode_t *) value) = DC_DIVEMODE_OC; break; @@ -155,6 +356,17 @@ deepsix_excursion_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, return DC_STATUS_DATAFORMAT; } break; + case DC_FIELD_DECOMODEL: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->conservatism = 0; + if (layout->gf != UNDEFINED) { + decomodel->params.gf.low = data[layout->gf + 0]; + decomodel->params.gf.high = data[layout->gf + 1]; + } else { + decomodel->params.gf.low = 0; + decomodel->params.gf.high = 0; + } + break; default: return DC_STATUS_UNSUPPORTED; } @@ -164,23 +376,24 @@ deepsix_excursion_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, } static dc_status_t -deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +deepsix_excursion_parser_samples_foreach_v0 (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) { const unsigned char *data = abstract->data; unsigned int size = abstract->size; + const deepsix_excursion_layout_t *layout = &deepsix_excursion_layout_v0; - if (size < HEADERSIZE) + if (size < layout->headersize) return DC_STATUS_DATAFORMAT; - int firmware4c = memcmp(data + 48, "D01-4C", 6) == 0; + int firmware4c = memcmp(data + layout->firmware, "D01-4C", 6) == 0; unsigned int maxtype = firmware4c ? TEMPERATURE : CNS; - unsigned int interval = array_uint32_le(data + 24); - unsigned int atmospheric = array_uint32_le(data + 56); + unsigned int interval = array_uint32_le(data + layout->samplerate); + unsigned int atmospheric = array_uint32_le(data + layout->atmospheric); unsigned int time = 0; - unsigned int offset = HEADERSIZE; + unsigned int offset = layout->headersize; while (offset + 1 < size) { dc_sample_value_t sample = {0}; @@ -213,7 +426,7 @@ deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb sample.time = time; if (callback) callback(DC_SAMPLE_TIME, sample, userdata); - sample.depth = (signed int)(depth - atmospheric) * (BAR / 1000.0) / (DENSITY * GRAVITY); + sample.depth = pressure_to_depth(depth, atmospheric, DENSITY); if (callback) callback(DC_SAMPLE_DEPTH, sample, userdata); } @@ -240,7 +453,7 @@ deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb unsigned int ceiling_time = array_uint16_le(data + offset + 6); } else if (type == CNS) { unsigned int cns = array_uint16_le(data + offset + 4); - sample.cns = cns; + sample.cns = cns / 100.0; if (callback) callback(DC_SAMPLE_CNS, sample, userdata); } @@ -249,3 +462,325 @@ deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb return DC_STATUS_SUCCESS; } + +static dc_status_t +deepsix_excursion_parser_samples_foreach_v1 (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +{ + deepsix_excursion_parser_t *parser = (deepsix_excursion_parser_t *) abstract; + const unsigned char *data = abstract->data; + unsigned int size = abstract->size; + const deepsix_excursion_layout_t *layout = &deepsix_excursion_layout_v1; + + if (size < layout->headersize) + return DC_STATUS_DATAFORMAT; + + unsigned int headersize = data[2]; + if (headersize < layout->headersize) + return DC_STATUS_DATAFORMAT; + + unsigned int samplerate = data[layout->samplerate]; + unsigned int atmospheric = array_uint16_le(data + layout->atmospheric); + unsigned int density = 1000 + data[layout->salinity] * 10; + + unsigned int offset = headersize; + if (offset + 1 > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + unsigned int nconfig = data[offset]; + if (nconfig > MAX_SAMPLES) { + ERROR(abstract->context, "Too many sample descriptors (%u).", nconfig); + return DC_STATUS_DATAFORMAT; + } + + offset += 1; + + if (offset + 3 * nconfig > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + deepsix_excursion_sample_info_t sample_info[MAX_SAMPLES] = {{0}}; + for (unsigned int i = 0; i < nconfig; i++) { + sample_info[i].type = data[offset + 3 * i + 0]; + sample_info[i].size = data[offset + 3 * i + 1]; + sample_info[i].divisor = data[offset + 3 * i + 2]; + + if (sample_info[i].divisor) { + switch (sample_info[i].type) { + case SAMPLE_CNS: + case SAMPLE_TEMPERATURE: + if (sample_info[i].size != 2) { + ERROR(abstract->context, "Unexpected sample size (%u).", sample_info[i].size); + return DC_STATUS_DATAFORMAT; + } + break; + case SAMPLE_DECO_NDL: + if (sample_info[i].size != 7) { + ERROR(abstract->context, "Unexpected sample size (%u).", sample_info[i].size); + return DC_STATUS_DATAFORMAT; + } + break; + default: + WARNING (abstract->context, "Unknown sample descriptor (%u %u %u).", + sample_info[i].type, sample_info[i].size, sample_info[i].divisor); + break; + } + } + } + + offset += 3 * nconfig; + + if (offset + 1 > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + unsigned int nevents = data[offset]; + if (nevents > MAX_EVENTS) { + ERROR(abstract->context, "Too many event descriptors (%u).", nevents); + return DC_STATUS_DATAFORMAT; + } + + offset += 1; + + if (offset + 2 * nevents > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + deepsix_excursion_event_info_t event_info[MAX_EVENTS] = {{0}}; + for (unsigned int i = 0; i < nevents; i++) { + event_info[i].type = data[offset + 2 * i]; + event_info[i].size = data[offset + 2 * i + 1]; + + switch (event_info[i].type) { + case EVENT_ALARMS: + if (event_info[i].size != 1) { + ERROR(abstract->context, "Unexpected event size (%u).", event_info[i].size); + return DC_STATUS_DATAFORMAT; + } + break; + case EVENT_CHANGE_GAS: + if (event_info[i].size != 3) { + ERROR(abstract->context, "Unexpected event size (%u).", event_info[i].size); + return DC_STATUS_DATAFORMAT; + } + break; + case EVENT_SAMPLES_MISSED: + if (event_info[i].size != 6) { + ERROR(abstract->context, "Unexpected event size (%u).", event_info[i].size); + return DC_STATUS_DATAFORMAT; + } + break; + default: + WARNING (abstract->context, "Unknown event descriptor (%u %u).", + event_info[i].type, event_info[i].size); + break; + } + } + + offset += 2 * nevents; + + unsigned int time = 0; + unsigned int nsamples = 0; + while (offset + 3 <= size) { + dc_sample_value_t sample = {0}; + nsamples++; + + // Time (seconds). + time += samplerate; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + + unsigned int depth = array_uint16_le (data + offset); + sample.depth = pressure_to_depth(depth, atmospheric, density); + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + offset += 2; + + // event info + unsigned int length = data[offset]; + offset += 1; + + if (offset + length > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + if (length) { + if (length < 2) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + unsigned int events = array_uint16_le (data + offset); + unsigned int event_offset = 2; + + for (unsigned int i = 0; i < nevents; i++) { + if ((events & (1 << event_info[i].type)) == 0) + continue; + + if (event_offset + event_info[i].size > length) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + unsigned int alarms = 0; + unsigned int id = 0, o2 = 0, he = 0; + unsigned int mix_idx = 0; + unsigned int count = 0, timestamp = 0; + switch (event_info[i].type) { + case EVENT_ALARMS: + alarms = data[offset + event_offset]; + for (unsigned int v = alarms, j = 0; v; v >>= 1, ++j) { + if ((v & 1) == 0) + continue; + + sample.event.type = SAMPLE_EVENT_NONE; + sample.event.time = 0; + sample.event.flags = 0; + sample.event.value = 0; + switch (j) { + case ALARM_ASCENTRATE: + sample.event.type = SAMPLE_EVENT_ASCENT; + break; + case ALARM_CEILING: + sample.event.type = SAMPLE_EVENT_CEILING; + break; + case ALARM_PO2: + sample.event.type = SAMPLE_EVENT_PO2; + break; + case ALARM_MAXDEPTH: + sample.event.type = SAMPLE_EVENT_MAXDEPTH; + break; + case ALARM_DIVETIME: + sample.event.type = SAMPLE_EVENT_DIVETIME; + break; + case ALARM_CNS: + break; + default: + WARNING (abstract->context, "Unknown event (%u).", j); + break; + } + if (sample.event.type != SAMPLE_EVENT_NONE) { + if (callback) callback (DC_SAMPLE_EVENT, sample, userdata); + } + } + break; + case EVENT_CHANGE_GAS: + id = data[offset + event_offset]; + o2 = data[offset + event_offset + 1]; + he = data[offset + event_offset + 2]; + + mix_idx = deepsix_excursion_find_gasmix(parser, o2, he, id); + if (mix_idx >= parser->ngasmixes) { + if (mix_idx >= MAX_GASMIXES) { + ERROR (abstract->context, "Maximum number of gas mixes reached."); + return DC_STATUS_NOMEMORY; + } + parser->gasmix[mix_idx].oxygen = o2; + parser->gasmix[mix_idx].helium = he; + parser->gasmix[mix_idx].id = id; + parser->ngasmixes = mix_idx + 1; + } + + sample.gasmix = mix_idx; + if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata); + break; + case EVENT_SAMPLES_MISSED: + count = array_uint16_le(data + offset + event_offset); + timestamp = array_uint32_le(data + offset + event_offset + 2); + if (timestamp < time) { + ERROR (abstract->context, "Timestamp moved backwards (%u %u).", timestamp, time); + return DC_STATUS_DATAFORMAT; + } + nsamples += count; + time = timestamp; + break; + default: + WARNING (abstract->context, "Unknown event (%u %u).", + event_info[i].type, event_info[i].size); + break; + } + event_offset += event_info[i].size; + } + + // Skip remaining sample bytes (if any). + if (event_offset < length) { + WARNING (abstract->context, "Remaining %u bytes skipped.", length - event_offset); + } + offset += length; + } + + for (unsigned int i = 0; i < nconfig; ++i) { + if (sample_info[i].divisor && (nsamples % sample_info[i].divisor) == 0) { + if (offset + sample_info[i].size > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + unsigned int value = 0; + unsigned int deco_flags = 0, deco_ndl_tts = 0; + unsigned int deco_depth = 0, deco_time = 0; + switch (sample_info[i].type) { + case SAMPLE_TEMPERATURE: + value = array_uint16_le(data + offset); + sample.temperature = value / 10.0; + if (callback) callback(DC_SAMPLE_TEMPERATURE, sample, userdata); + break; + case SAMPLE_CNS: + value = array_uint16_le(data + offset); + sample.cns = value / 10000.0; + if (callback) callback (DC_SAMPLE_CNS, sample, userdata); + break; + case SAMPLE_DECO_NDL: + deco_flags = data[offset]; + deco_ndl_tts = array_uint16_le(data + offset + 1); + deco_depth = array_uint16_le(data + offset + 3); + deco_time = array_uint16_le(data + offset + 5); + if (deco_flags & DECOSTOP) { + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.depth = pressure_to_depth(deco_depth, atmospheric, density); + sample.deco.time = deco_time; + } else if (deco_flags & SAFETYSTOP) { + sample.deco.type = DC_DECO_SAFETYSTOP; + sample.deco.depth = pressure_to_depth(deco_depth, atmospheric, density); + sample.deco.time = deco_time; + } else { + sample.deco.type = DC_DECO_NDL; + sample.deco.depth = 0; + sample.deco.time = deco_ndl_tts; + } + if (callback) callback (DC_SAMPLE_DECO, sample, userdata); + break; + default: + break; + } + offset += sample_info[i].size; + } + } + } + + parser->cached = 1; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepsix_excursion_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 < HEADERSIZE_MIN) + return DC_STATUS_DATAFORMAT; + + unsigned int version = data[3]; + + if (version == 0) { + return deepsix_excursion_parser_samples_foreach_v0(abstract, callback, userdata); + } else { + return deepsix_excursion_parser_samples_foreach_v1(abstract, callback, userdata); + } +} diff --git a/src/descriptor.c b/src/descriptor.c index c8c5eb6..8414aec 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -63,11 +63,11 @@ static int dc_filter_oceanic (dc_transport_t transport, const void *userdata, vo static int dc_filter_mclean (dc_transport_t transport, const void *userdata, void *params); static int dc_filter_atomic (dc_transport_t transport, const void *userdata, void *params); static int dc_filter_deepsix (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_deepblu (dc_transport_t transport, const void *userdata, void *params); +static int dc_filter_oceans (dc_transport_t transport, const void *userdata, void *params); // Not merged upstream yet static int dc_filter_garmin (dc_transport_t transport, const void *userdata, void *params); -static int dc_filter_deepblu (dc_transport_t transport, const void *userdata, void *params); -static int dc_filter_oceans(dc_transport_t transport, const void *userdata, void *params); static dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item); @@ -176,6 +176,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Scubapro", "Aladin A1", DC_FAMILY_UWATEC_SMART, 0x25, DC_TRANSPORT_BLE, dc_filter_uwatec}, {"Scubapro", "Mantis 2", DC_FAMILY_UWATEC_SMART, 0x26, DC_TRANSPORT_SERIAL, NULL}, {"Scubapro", "Aladin A2", DC_FAMILY_UWATEC_SMART, 0x28, DC_TRANSPORT_BLE, dc_filter_uwatec}, + {"Scubapro", "G2 TEK", DC_FAMILY_UWATEC_SMART, 0x31, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_uwatec}, {"Scubapro", "G2", DC_FAMILY_UWATEC_SMART, 0x32, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_uwatec}, {"Scubapro", "G2 Console", DC_FAMILY_UWATEC_SMART, 0x32, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_uwatec}, {"Scubapro", "G2 HUD", DC_FAMILY_UWATEC_SMART, 0x42, DC_TRANSPORT_USBHID | DC_TRANSPORT_BLE, dc_filter_uwatec}, @@ -257,7 +258,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Sherwood", "Vision", DC_FAMILY_OCEANIC_ATOM2, 0x4556, DC_TRANSPORT_SERIAL, NULL}, {"Oceanic", "VTX", DC_FAMILY_OCEANIC_ATOM2, 0x4557, DC_TRANSPORT_SERIAL, NULL}, {"Aqualung", "i300", DC_FAMILY_OCEANIC_ATOM2, 0x4559, DC_TRANSPORT_SERIAL, NULL}, - {"Aqualung", "i750TC", DC_FAMILY_OCEANIC_ATOM2, 0x455A, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_oceanic}, + {"Aqualung", "i750TC", DC_FAMILY_OCEANIC_ATOM2, 0x455A, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, {"Aqualung", "i450T", DC_FAMILY_OCEANIC_ATOM2, 0x4641, DC_TRANSPORT_SERIAL, NULL}, {"Aqualung", "i550", DC_FAMILY_OCEANIC_ATOM2, 0x4642, DC_TRANSPORT_SERIAL, NULL}, {"Aqualung", "i200", DC_FAMILY_OCEANIC_ATOM2, 0x4646, DC_TRANSPORT_SERIAL, NULL}, @@ -276,6 +277,7 @@ static const dc_descriptor_t g_descriptors[] = { {"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}, {"Aqualung", "i200Cv2", DC_FAMILY_OCEANIC_ATOM2, 0x4749, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, + {"Oceanic", "Geo Air", DC_FAMILY_OCEANIC_ATOM2, 0x474B, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, /* Mares Nemo */ {"Mares", "Nemo", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Nemo Steel", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL}, @@ -335,8 +337,9 @@ static const dc_descriptor_t g_descriptors[] = { {"Cressi", "Newton", DC_FAMILY_CRESSI_LEONARDO, 5, DC_TRANSPORT_SERIAL, NULL}, {"Cressi", "Drake", DC_FAMILY_CRESSI_LEONARDO, 6, DC_TRANSPORT_SERIAL, NULL}, /* Cressi Goa */ - {"Cressi", "Cartesio", DC_FAMILY_CRESSI_GOA, 1, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL}, - {"Cressi", "Goa", DC_FAMILY_CRESSI_GOA, 2, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL}, + {"Cressi", "Cartesio", DC_FAMILY_CRESSI_GOA, 1, DC_TRANSPORT_SERIAL, NULL}, + {"Cressi", "Goa", DC_FAMILY_CRESSI_GOA, 2, DC_TRANSPORT_SERIAL, NULL}, + {"Cressi", "Donatello", DC_FAMILY_CRESSI_GOA, 4, DC_TRANSPORT_SERIAL, NULL}, {"Cressi", "Michelangelo", DC_FAMILY_CRESSI_GOA, 5, DC_TRANSPORT_SERIAL, NULL}, {"Cressi", "Neon", DC_FAMILY_CRESSI_GOA, 9, DC_TRANSPORT_SERIAL, NULL}, /* Zeagle N2iTiON3 */ @@ -443,9 +446,14 @@ static const dc_descriptor_t g_descriptors[] = { {"Crest", "CR-4", DC_FAMILY_DEEPSIX_EXCURSION, 0, DC_TRANSPORT_BLE, dc_filter_deepsix}, {"Genesis", "Centauri", DC_FAMILY_DEEPSIX_EXCURSION, 0, DC_TRANSPORT_BLE, dc_filter_deepsix}, {"Tusa", "TC1", DC_FAMILY_DEEPSIX_EXCURSION, 0, DC_TRANSPORT_BLE, dc_filter_deepsix}, + {"Scorpena", "Alpha", DC_FAMILY_DEEPSIX_EXCURSION, 0, DC_TRANSPORT_BLE, dc_filter_deepsix}, /* Seac Screen */ {"Seac", "Screen", DC_FAMILY_SEAC_SCREEN, 0, DC_TRANSPORT_SERIAL, NULL}, {"Seac", "Action", DC_FAMILY_SEAC_SCREEN, 0, DC_TRANSPORT_SERIAL, NULL}, + /* Deepblu Cosmiq */ + {"Deepblu", "Cosmiq+", DC_FAMILY_DEEPBLU_COSMIQ, 0, DC_TRANSPORT_BLE, dc_filter_deepblu}, + /* Oceans S1 */ + {"Oceans", "S1", DC_FAMILY_OCEANS_S1, 0, DC_TRANSPORT_BLE, dc_filter_oceans}, // Not merged upstream yet /* Garmin -- model numbers as defined in FIT format; USB product id is (0x4000 | model) */ @@ -453,10 +461,6 @@ static const dc_descriptor_t g_descriptors[] = { /* for the Mk2 we are using the model of the global model - the APAC model is 3702 */ {"Garmin", "Descent Mk1", DC_FAMILY_GARMIN, 2859, DC_TRANSPORT_USBSTORAGE, dc_filter_garmin}, {"Garmin", "Descent Mk2/Mk2i", DC_FAMILY_GARMIN, 3258, DC_TRANSPORT_USBSTORAGE, dc_filter_garmin}, - /* Deepblu */ - {"Deepblu", "Cosmiq+", DC_FAMILY_DEEPBLU, 0, DC_TRANSPORT_BLE, dc_filter_deepblu}, - /* Oceans S1 */ - { "Oceans", "S1", DC_FAMILY_OCEANS_S1, 0, DC_TRANSPORT_BLE, dc_filter_oceans }, }; static int @@ -571,7 +575,7 @@ static int dc_filter_uwatec (dc_transport_t transport, const void *userdata, voi "UWATEC Galileo Sol", }; static const dc_usb_desc_t usbhid[] = { - {0x2e6c, 0x3201}, // G2 + {0x2e6c, 0x3201}, // G2, G2 TEK {0x2e6c, 0x3211}, // G2 Console {0x2e6c, 0x4201}, // G2 HUD {0xc251, 0x2006}, // Aladin Square @@ -582,6 +586,7 @@ static int dc_filter_uwatec (dc_transport_t transport, const void *userdata, voi "HUD", "A1", "A2", + "G2 TEK", }; if (transport == DC_TRANSPORT_IRDA) { @@ -718,6 +723,7 @@ static int dc_filter_oceanic (dc_transport_t transport, const void *userdata, vo 0x4742, // Sherwood Beacon 0x4743, // Aqualung i470TC 0x4749, // Aqualung i200C (newer model) + 0x474B, // Oceanic Geo Air }; if (transport == DC_TRANSPORT_BLE) { @@ -766,6 +772,7 @@ static int dc_filter_deepsix (dc_transport_t transport, const void *userdata, vo "Crest-CR4", "CENTAURI", "TC1", + "ALPHA", }; if (transport == DC_TRANSPORT_BLE) { @@ -775,20 +782,6 @@ static int dc_filter_deepsix (dc_transport_t transport, const void *userdata, vo return 1; } -// Not merged upstream yet -static int dc_filter_garmin (dc_transport_t transport, const void *userdata, void *params) -{ - static const dc_usb_desc_t usbhid[] = { - {0x091e, 0x2b2b}, // Garmin Descent Mk1 - }; - - if (transport == DC_TRANSPORT_USBSTORAGE) { - return DC_FILTER_INTERNAL (userdata, usbhid, 0, dc_match_usb); - } - - return 1; -} - static int dc_filter_deepblu (dc_transport_t transport, const void *userdata, void *params) { static const char * const bluetooth[] = { @@ -802,14 +795,28 @@ static int dc_filter_deepblu (dc_transport_t transport, const void *userdata, vo return 1; } -static int dc_filter_oceans(dc_transport_t transport, const void* userdata, void *params) +static int dc_filter_oceans (dc_transport_t transport, const void *userdata, void *params) { - static const char* const ble[] = { + static const char * const bluetooth[] = { "S1", }; if (transport == DC_TRANSPORT_BLE) { - return DC_FILTER_INTERNAL(userdata, ble, 0, dc_match_prefix); + return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_prefix); + } + + return 1; +} + +// Not merged upstream yet +static int dc_filter_garmin (dc_transport_t transport, const void *userdata, void *params) +{ + static const dc_usb_desc_t usbhid[] = { + {0x091e, 0x2b2b}, // Garmin Descent Mk1 + }; + + if (transport == DC_TRANSPORT_USBSTORAGE) { + return DC_FILTER_INTERNAL (userdata, usbhid, 0, dc_match_usb); } return 1; diff --git a/src/device.c b/src/device.c index 9c41ad4..602f875 100644 --- a/src/device.c +++ b/src/device.c @@ -62,11 +62,11 @@ #include "sporasub_sp2.h" #include "deepsix_excursion.h" #include "seac_screen.h" +#include "deepblu_cosmiq.h" +#include "oceans_s1.h" // Not merged upstream yet #include "garmin.h" -#include "deepblu.h" -#include "oceans_s1.h" #include "device-private.h" #include "context-private.h" @@ -236,6 +236,12 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr case DC_FAMILY_SEAC_SCREEN: rc = seac_screen_device_open (&device, context, iostream); break; + case DC_FAMILY_DEEPBLU_COSMIQ: + rc = deepblu_cosmiq_device_open (&device, context, iostream); + break; + case DC_FAMILY_OCEANS_S1: + rc = oceans_s1_device_open (&device, context, iostream); + break; default: return DC_STATUS_INVALIDARGS; @@ -243,12 +249,6 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr case DC_FAMILY_GARMIN: rc = garmin_device_open (&device, context, iostream, dc_descriptor_get_model (descriptor)); break; - case DC_FAMILY_DEEPBLU: - rc = deepblu_device_open (&device, context, iostream); - break; - case DC_FAMILY_OCEANS_S1: - rc = oceans_s1_device_open(&device, context, iostream); - break; } *out = device; @@ -420,6 +420,9 @@ dc_device_timesync (dc_device_t *device, const dc_datetime_t *datetime) if (device->vtable->timesync == NULL) return DC_STATUS_UNSUPPORTED; + if (datetime == NULL) + return DC_STATUS_INVALIDARGS; + return device->vtable->timesync (device, datetime); } diff --git a/src/diverite_nitekq_parser.c b/src/diverite_nitekq_parser.c index a09b0e5..5623769 100644 --- a/src/diverite_nitekq_parser.c +++ b/src/diverite_nitekq_parser.c @@ -58,6 +58,9 @@ static const dc_parser_vtable_t diverite_nitekq_parser_vtable = { sizeof(diverite_nitekq_parser_t), DC_FAMILY_DIVERITE_NITEKQ, diverite_nitekq_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ diverite_nitekq_parser_get_datetime, /* datetime */ diverite_nitekq_parser_get_field, /* fields */ diverite_nitekq_parser_samples_foreach, /* samples_foreach */ diff --git a/src/divesystem_idive.c b/src/divesystem_idive.c index 61ec469..0410cd2 100644 --- a/src/divesystem_idive.c +++ b/src/divesystem_idive.c @@ -608,11 +608,6 @@ divesystem_idive_device_timesync (dc_device_t *abstract, const dc_datetime_t *da return DC_STATUS_UNSUPPORTED; } - if (datetime == NULL) { - ERROR (abstract->context, "Invalid parameter specified."); - return DC_STATUS_INVALIDARGS; - } - // Get the UTC timestamp. dc_ticks_t timestamp = dc_datetime_mktime(datetime); if (timestamp == -1) { diff --git a/src/divesystem_idive_parser.c b/src/divesystem_idive_parser.c index 5ace8f4..3270c55 100644 --- a/src/divesystem_idive_parser.c +++ b/src/divesystem_idive_parser.c @@ -29,6 +29,7 @@ #define ISINSTANCE(parser) dc_device_isinstance((parser), &divesystem_idive_parser_vtable) #define ISIX3M(model) ((model) >= 0x21) +#define ISIX3M2(model) ((model) >= 0x60 && (model) < 0x1000) #define SZ_HEADER_IDIVE 0x32 #define SZ_SAMPLE_IDIVE 0x2A @@ -48,6 +49,15 @@ #define FREEDIVE 4 #define INVALID 0xFFFFFFFF +#define BUHLMANN 0 +#define VPM 1 +#define DUAL 2 + +#define IX3M2_BUHLMANN 0 +#define IX3M2_ZHL16B 1 +#define IX3M2_ZHL16C 2 +#define IX3M2_VPM 3 + typedef struct divesystem_idive_parser_t divesystem_idive_parser_t; typedef struct divesystem_idive_gasmix_t { @@ -74,6 +84,9 @@ struct divesystem_idive_parser_t { unsigned int ntanks; divesystem_idive_gasmix_t gasmix[NGASMIXES]; divesystem_idive_tank_t tank[NTANKS]; + unsigned int algorithm; + unsigned int gf_low; + unsigned int gf_high; }; static dc_status_t divesystem_idive_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); @@ -85,6 +98,9 @@ static const dc_parser_vtable_t divesystem_idive_parser_vtable = { sizeof(divesystem_idive_parser_t), DC_FAMILY_DIVESYSTEM_IDIVE, divesystem_idive_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ divesystem_idive_parser_get_datetime, /* datetime */ divesystem_idive_parser_get_field, /* fields */ divesystem_idive_parser_samples_foreach, /* samples_foreach */ @@ -129,6 +145,10 @@ divesystem_idive_parser_create (dc_parser_t **out, dc_context_t *context, unsign parser->tank[i].beginpressure = 0; parser->tank[i].endpressure = 0; } + parser->algorithm = INVALID; + parser->gf_low = INVALID; + parser->gf_high = INVALID; + *out = (dc_parser_t*) parser; @@ -157,6 +177,9 @@ divesystem_idive_parser_set_data (dc_parser_t *abstract, const unsigned char *da parser->tank[i].beginpressure = 0; parser->tank[i].endpressure = 0; } + parser->algorithm = INVALID; + parser->gf_low = INVALID; + parser->gf_high = INVALID; return DC_STATUS_SUCCESS; } @@ -280,6 +303,7 @@ divesystem_idive_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_tank_t *tank = (dc_tank_t *) value; dc_salinity_t *water = (dc_salinity_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; if (value) { switch (type) { @@ -343,6 +367,46 @@ divesystem_idive_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, return DC_STATUS_DATAFORMAT; } break; + case DC_FIELD_DECOMODEL: + if (parser->algorithm == INVALID) + return DC_STATUS_UNSUPPORTED; + if (ISIX3M2(parser->model)) { + switch (parser->algorithm) { + case IX3M2_BUHLMANN: + case IX3M2_ZHL16B: + case IX3M2_ZHL16C: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->conservatism = 0; + decomodel->params.gf.low = parser->gf_low; + decomodel->params.gf.high = parser->gf_high; + break; + case IX3M2_VPM: + decomodel->type = DC_DECOMODEL_VPM; + decomodel->conservatism = 0; + break; + default: + ERROR (abstract->context, "Unknown deco algorithm %02x.", parser->algorithm); + return DC_STATUS_DATAFORMAT; + } + } else { + switch (parser->algorithm) { + case BUHLMANN: + case DUAL: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->conservatism = 0; + decomodel->params.gf.low = parser->gf_low; + decomodel->params.gf.high = parser->gf_high; + break; + case VPM: + decomodel->type = DC_DECOMODEL_VPM; + decomodel->conservatism = 0; + break; + default: + ERROR (abstract->context, "Unknown deco algorithm %02x.", parser->algorithm); + return DC_STATUS_DATAFORMAT; + } + } + break; default: return DC_STATUS_UNSUPPORTED; } @@ -371,6 +435,10 @@ divesystem_idive_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callba unsigned int divemode = INVALID; unsigned int tank_previous = INVALID; unsigned int tank_idx = INVALID; + unsigned int algorithm = INVALID; + unsigned int algorithm_previous = INVALID; + unsigned int gf_low = INVALID; + unsigned int gf_high = INVALID; unsigned int firmware = 0; unsigned int apos4 = 0; @@ -433,6 +501,22 @@ divesystem_idive_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callba divemode = mode; } + // Deco model + unsigned int s_algorithm = data[offset + 14]; + unsigned int s_gf_high = data[offset + 15]; + unsigned int s_gf_low = data[offset + 16]; + if (s_algorithm != algorithm_previous) { + if (algorithm_previous != INVALID) { + WARNING (abstract->context, "Deco algorithm changed from %02x to %02x.", algorithm_previous, s_algorithm); + } + algorithm_previous = s_algorithm; + } + if (algorithm == INVALID) { + algorithm = s_algorithm; + gf_low = s_gf_low; + gf_high = s_gf_high; + } + // Setpoint if (mode == SCR || mode == CCR) { unsigned int setpoint = array_uint16_le (data + offset + 19); @@ -501,6 +585,11 @@ divesystem_idive_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callba unsigned int flags = data[offset + 47] & 0xF0; unsigned int pressure = data[offset + 49]; + if (flags & 0x20) { + // 300 bar transmitter. + pressure *= 2; + } + if (flags & 0x80) { // No active transmitter available } else if (flags & 0x40) { @@ -560,6 +649,9 @@ divesystem_idive_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callba parser->maxdepth = maxdepth; parser->divetime = time; parser->divemode = divemode; + parser->algorithm = algorithm; + parser->gf_low = gf_low; + parser->gf_high = gf_high; parser->cached = 1; return DC_STATUS_SUCCESS; diff --git a/src/garmin_parser.c b/src/garmin_parser.c index 067aa92..782a01e 100644 --- a/src/garmin_parser.c +++ b/src/garmin_parser.c @@ -357,6 +357,9 @@ static const dc_parser_vtable_t garmin_parser_vtable = { sizeof(garmin_parser_t), DC_FAMILY_GARMIN, garmin_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ garmin_parser_get_datetime, /* datetime */ garmin_parser_get_field, /* fields */ garmin_parser_samples_foreach, /* samples_foreach */ diff --git a/src/hw_frog.c b/src/hw_frog.c index 74fc170..f98523b 100644 --- a/src/hw_frog.c +++ b/src/hw_frog.c @@ -473,11 +473,6 @@ hw_frog_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) { hw_frog_device_t *device = (hw_frog_device_t *) abstract; - if (datetime == NULL) { - ERROR (abstract->context, "Invalid parameter specified."); - return DC_STATUS_INVALIDARGS; - } - // Send the command. unsigned char packet[6] = { datetime->hour, datetime->minute, datetime->second, diff --git a/src/hw_ostc.c b/src/hw_ostc.c index 7017321..6fdc6eb 100644 --- a/src/hw_ostc.c +++ b/src/hw_ostc.c @@ -348,11 +348,6 @@ hw_ostc_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) dc_status_t status = DC_STATUS_SUCCESS; hw_ostc_device_t *device = (hw_ostc_device_t *) abstract; - if (datetime == NULL) { - ERROR (abstract->context, "Invalid parameter specified."); - return DC_STATUS_INVALIDARGS; - } - // Send the command. dc_status_t rc = hw_ostc_send (device, 'b', 1); if (rc != DC_STATUS_SUCCESS) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index e8dc223..629e482 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -89,6 +89,19 @@ #define NODELAY 0 #define TIMEOUT 400 +#define HDR_COMPACT_LENGTH 0 // 3 bytes +#define HDR_COMPACT_SUMMARY 3 // 10 bytes +#define HDR_COMPACT_NUMBER 13 // 2 bytes +#define HDR_COMPACT_VERSION 15 // 1 byte + +#define HDR_FULL_LENGTH 9 // 3 bytes +#define HDR_FULL_SUMMARY 12 // 10 bytes +#define HDR_FULL_NUMBER 80 // 2 bytes +#define HDR_FULL_VERSION 8 // 1 byte + +#define HDR_FULL_POINTERS 2 // 6 bytes +#define HDR_FULL_FIRMWARE 48 // 2 bytes + typedef enum hw_ostc3_state_t { OPEN, DOWNLOAD, @@ -116,6 +129,7 @@ typedef struct hw_ostc3_logbook_t { unsigned int profile; unsigned int fingerprint; unsigned int number; + unsigned int version; } hw_ostc3_logbook_t; typedef struct hw_ostc3_firmware_t { @@ -155,16 +169,18 @@ static const dc_device_vtable_t hw_ostc3_device_vtable = { static const hw_ostc3_logbook_t hw_ostc3_logbook_compact = { RB_LOGBOOK_SIZE_COMPACT, /* size */ - 0, /* profile */ - 3, /* fingerprint */ - 13, /* number */ + HDR_COMPACT_LENGTH, /* profile */ + HDR_COMPACT_SUMMARY, /* fingerprint */ + HDR_COMPACT_NUMBER, /* number */ + HDR_COMPACT_VERSION, /* version */ }; static const hw_ostc3_logbook_t hw_ostc3_logbook_full = { RB_LOGBOOK_SIZE_FULL, /* size */ - 9, /* profile */ - 12, /* fingerprint */ - 80, /* number */ + HDR_FULL_LENGTH, /* profile */ + HDR_FULL_SUMMARY, /* fingerprint */ + HDR_FULL_NUMBER, /* number */ + HDR_FULL_VERSION, /* version */ }; @@ -279,10 +295,15 @@ hw_ostc3_transfer (hw_ostc3_device_t *device, unsigned int isize, unsigned char output[], unsigned int osize, + unsigned int *actual, unsigned int delay) { dc_device_t *abstract = (dc_device_t *) device; dc_status_t status = DC_STATUS_SUCCESS; + unsigned int length = osize; + + if (cmd == DIVE && length < RB_LOGBOOK_SIZE_FULL) + return DC_STATUS_INVALIDARGS; if (device_is_cancelled (abstract)) return DC_STATUS_CANCELLED; @@ -345,11 +366,44 @@ hw_ostc3_transfer (hw_ostc3_device_t *device, } if (output) { - // Read the output data packet. - status = hw_ostc3_read (device, progress, output, osize); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to receive the answer."); - return status; + if (cmd == DIVE) { + // Read the dive header. + status = hw_ostc3_read (device, progress, output, RB_LOGBOOK_SIZE_FULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the dive header."); + return status; + } + + // When the hwOS firmware detects the dive profile is no longer + // valid, it sends a modified dive header (with the begin/end + // pointer fields reset to zero, and the length field reduced to 8 + // bytes), along with an empty dive profile. Detect this condition + // and adjust the expected length. + if (array_isequal (output + HDR_FULL_POINTERS, 6, 0x00) && + array_uint24_le (output + HDR_FULL_LENGTH) == 8 && + length > RB_LOGBOOK_SIZE_FULL + 5) { + length = RB_LOGBOOK_SIZE_FULL + 5; + } + + // Read the dive profile. + status = hw_ostc3_read (device, progress, output + RB_LOGBOOK_SIZE_FULL, length - RB_LOGBOOK_SIZE_FULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the dive profile."); + return status; + } + + // Update and emit a progress event. + if (progress && osize > length) { + progress->current += osize - length; + device_event_emit ((dc_device_t *) device, DC_EVENT_PROGRESS, progress); + } + } else { + // Read the output data packet. + status = hw_ostc3_read (device, progress, output, length); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the answer."); + return status; + } } } @@ -373,6 +427,9 @@ hw_ostc3_transfer (hw_ostc3_device_t *device, } } + if (actual) + *actual = length; + return DC_STATUS_SUCCESS; } @@ -445,9 +502,9 @@ hw_ostc3_device_id (hw_ostc3_device_t *device, unsigned char data[], unsigned in // Send the command. unsigned char hardware[SZ_HARDWARE2] = {0}; - status = hw_ostc3_transfer (device, NULL, HARDWARE2, NULL, 0, hardware, SZ_HARDWARE2, NODELAY); + status = hw_ostc3_transfer (device, NULL, HARDWARE2, NULL, 0, hardware, SZ_HARDWARE2, NULL, NODELAY); if (status == DC_STATUS_UNSUPPORTED) { - status = hw_ostc3_transfer (device, NULL, HARDWARE, NULL, 0, hardware + 1, SZ_HARDWARE, NODELAY); + status = hw_ostc3_transfer (device, NULL, HARDWARE, NULL, 0, hardware + 1, SZ_HARDWARE, NULL, NODELAY); } if (status != DC_STATUS_SUCCESS) return status; @@ -469,7 +526,7 @@ hw_ostc3_device_init_download (hw_ostc3_device_t *device) dc_context_t *context = (abstract ? abstract->context : NULL); // Send the init command. - dc_status_t status = hw_ostc3_transfer (device, NULL, INIT, NULL, 0, NULL, 0, NODELAY); + dc_status_t status = hw_ostc3_transfer (device, NULL, INIT, NULL, 0, NULL, 0, NULL, NODELAY); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to send the command."); return status; @@ -562,7 +619,7 @@ hw_ostc3_device_init (hw_ostc3_device_t *device, hw_ostc3_state_t state) // Read the version information. unsigned char version[SZ_VERSION] = {0}; - rc = hw_ostc3_transfer (device, NULL, IDENTITY, NULL, 0, version, sizeof(version), NODELAY); + rc = hw_ostc3_transfer (device, NULL, IDENTITY, NULL, 0, version, sizeof(version), NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the version information."); return rc; @@ -592,7 +649,7 @@ hw_ostc3_device_close (dc_device_t *abstract) // Send the exit command if (device->state == DOWNLOAD || device->state == SERVICE) { - rc = hw_ostc3_transfer (device, NULL, EXIT, NULL, 0, NULL, 0, NODELAY); + rc = hw_ostc3_transfer (device, NULL, EXIT, NULL, 0, NULL, 0, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the command."); dc_status_set_error(&status, rc); @@ -636,7 +693,7 @@ hw_ostc3_device_version (dc_device_t *abstract, unsigned char data[], unsigned i return rc; // Send the command. - rc = hw_ostc3_transfer (device, NULL, IDENTITY, NULL, 0, data, size, NODELAY); + rc = hw_ostc3_transfer (device, NULL, IDENTITY, NULL, 0, data, size, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; @@ -709,11 +766,11 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi // This is slower, but also works for older firmware versions. unsigned int compact = 1; rc = hw_ostc3_transfer (device, &progress, COMPACT, - NULL, 0, header, RB_LOGBOOK_SIZE_COMPACT * RB_LOGBOOK_COUNT, NODELAY); + NULL, 0, header, RB_LOGBOOK_SIZE_COMPACT * RB_LOGBOOK_COUNT, NULL, NODELAY); if (rc == DC_STATUS_UNSUPPORTED) { compact = 0; rc = hw_ostc3_transfer (device, &progress, HEADER, - NULL, 0, header, RB_LOGBOOK_SIZE_FULL * RB_LOGBOOK_COUNT, NODELAY); + NULL, 0, header, RB_LOGBOOK_SIZE_FULL * RB_LOGBOOK_COUNT, NULL, NODELAY); } if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the header."); @@ -769,8 +826,8 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi unsigned int length = RB_LOGBOOK_SIZE_FULL + array_uint24_le (header + offset + logbook->profile) - 3; if (!compact) { // Workaround for a bug in older firmware versions. - unsigned int firmware = array_uint16_be (header + offset + 0x30); - if (firmware < 93) + unsigned int firmware = array_uint16_be (header + offset + HDR_FULL_FIRMWARE); + if (firmware < OSTC3FW(0,93)) length -= 3; } if (length < RB_LOGBOOK_SIZE_FULL) { @@ -817,15 +874,15 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi unsigned int length = RB_LOGBOOK_SIZE_FULL + array_uint24_le (header + offset + logbook->profile) - 3; if (!compact) { // Workaround for a bug in older firmware versions. - unsigned int firmware = array_uint16_be (header + offset + 0x30); - if (firmware < 93) + unsigned int firmware = array_uint16_be (header + offset + HDR_FULL_FIRMWARE); + if (firmware < OSTC3FW(0,93)) length -= 3; } // Download the dive. unsigned char number[1] = {idx}; rc = hw_ostc3_transfer (device, &progress, DIVE, - number, sizeof (number), profile, length, NODELAY); + number, sizeof (number), profile, length, &length, NODELAY); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the dive."); free (profile); @@ -834,11 +891,15 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi } // Verify the header in the logbook and profile are identical. - if (!compact && memcmp (profile, header + offset, logbook->size) != 0) { + if (memcmp (profile + HDR_FULL_VERSION, header + offset + logbook->version, 1) != 0 || + compact ? + memcmp (profile + HDR_FULL_SUMMARY, header + offset + HDR_COMPACT_SUMMARY, 10) != 0 || + memcmp (profile + HDR_FULL_NUMBER, header + offset + HDR_COMPACT_NUMBER, 2) != 0 : + memcmp (profile + HDR_FULL_SUMMARY, header + offset + HDR_FULL_SUMMARY, RB_LOGBOOK_SIZE_FULL - HDR_FULL_SUMMARY) != 0) { ERROR (abstract->context, "Unexpected profile header."); free (profile); free (header); - return rc; + return DC_STATUS_DATAFORMAT; } // Detect invalid profile data. @@ -852,8 +913,8 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi } else if (length == RB_LOGBOOK_SIZE_FULL + 2) { // A profile containing only the 2 byte end-of-profile // marker is considered a valid empty profile. - } else if (length < RB_LOGBOOK_SIZE_FULL + 5 + 2 || - array_uint24_le (profile + RB_LOGBOOK_SIZE_FULL) + delta != array_uint24_le (profile + 9)) { + } else if (length < RB_LOGBOOK_SIZE_FULL + 5 || + array_uint24_le (profile + RB_LOGBOOK_SIZE_FULL) + delta != array_uint24_le (profile + HDR_FULL_LENGTH)) { // If there is more data available, then there should be a // valid profile header containing a length matching the // length in the dive header. @@ -861,7 +922,7 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi length = RB_LOGBOOK_SIZE_FULL; } - if (callback && !callback (profile, length, profile + 12, sizeof (device->fingerprint), userdata)) + if (callback && !callback (profile, length, profile + HDR_FULL_SUMMARY, sizeof (device->fingerprint), userdata)) break; } @@ -877,11 +938,6 @@ hw_ostc3_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) { hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; - if (datetime == NULL) { - ERROR (abstract->context, "Invalid parameter specified."); - return DC_STATUS_INVALIDARGS; - } - dc_status_t rc = hw_ostc3_device_init (device, DOWNLOAD); if (rc != DC_STATUS_SUCCESS) return rc; @@ -890,7 +946,7 @@ hw_ostc3_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) unsigned char packet[6] = { datetime->hour, datetime->minute, datetime->second, datetime->month, datetime->day, datetime->year - 2000}; - rc = hw_ostc3_transfer (device, NULL, CLOCK, packet, sizeof (packet), NULL, 0, NODELAY); + rc = hw_ostc3_transfer (device, NULL, CLOCK, packet, sizeof (packet), NULL, 0, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; @@ -918,7 +974,7 @@ hw_ostc3_device_display (dc_device_t *abstract, const char *text) return rc; // Send the command. - rc = hw_ostc3_transfer (device, NULL, DISPLAY, packet, sizeof (packet), NULL, 0, NODELAY); + rc = hw_ostc3_transfer (device, NULL, DISPLAY, packet, sizeof (packet), NULL, 0, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; @@ -946,7 +1002,7 @@ hw_ostc3_device_customtext (dc_device_t *abstract, const char *text) return rc; // Send the command. - rc = hw_ostc3_transfer (device, NULL, CUSTOMTEXT, packet, sizeof (packet), NULL, 0, NODELAY); + rc = hw_ostc3_transfer (device, NULL, CUSTOMTEXT, packet, sizeof (packet), NULL, 0, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; @@ -972,7 +1028,7 @@ hw_ostc3_device_config_read (dc_device_t *abstract, unsigned int config, unsigne // Send the command. unsigned char command[1] = {config}; - rc = hw_ostc3_transfer (device, NULL, READ, command, sizeof (command), data, size, NODELAY); + rc = hw_ostc3_transfer (device, NULL, READ, command, sizeof (command), data, size, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; @@ -999,7 +1055,7 @@ hw_ostc3_device_config_write (dc_device_t *abstract, unsigned int config, const // Send the command. unsigned char command[SZ_CONFIG + 1] = {config}; memcpy(command + 1, data, size); - rc = hw_ostc3_transfer (device, NULL, WRITE, command, size + 1, NULL, 0, NODELAY); + rc = hw_ostc3_transfer (device, NULL, WRITE, command, size + 1, NULL, 0, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; @@ -1019,7 +1075,7 @@ hw_ostc3_device_config_reset (dc_device_t *abstract) return rc; // Send the command. - rc = hw_ostc3_transfer (device, NULL, RESET, NULL, 0, NULL, 0, NODELAY); + rc = hw_ostc3_transfer (device, NULL, RESET, NULL, 0, NULL, 0, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; @@ -1245,7 +1301,7 @@ hw_ostc3_firmware_erase (hw_ostc3_device_t *device, unsigned int addr, unsigned array_uint24_be_set (buffer, addr); buffer[3] = blocks; - return hw_ostc3_transfer (device, NULL, S_ERASE, buffer, sizeof (buffer), NULL, 0, delay); + return hw_ostc3_transfer (device, NULL, S_ERASE, buffer, sizeof (buffer), NULL, 0, NULL, delay); } static dc_status_t @@ -1255,7 +1311,7 @@ hw_ostc3_firmware_block_read (hw_ostc3_device_t *device, unsigned int addr, unsi array_uint24_be_set (buffer, addr); array_uint24_be_set (buffer + 3, block_size); - return hw_ostc3_transfer (device, NULL, S_BLOCK_READ, buffer, sizeof (buffer), block, block_size, NODELAY); + return hw_ostc3_transfer (device, NULL, S_BLOCK_READ, buffer, sizeof (buffer), block, block_size, NULL, NODELAY); } static dc_status_t @@ -1270,7 +1326,7 @@ hw_ostc3_firmware_block_write1 (hw_ostc3_device_t *device, unsigned int addr, co array_uint24_be_set (buffer, addr); memcpy (buffer + 3, block, block_size); - return hw_ostc3_transfer (device, NULL, S_BLOCK_WRITE, buffer, 3 + block_size, NULL, 0, TIMEOUT); + return hw_ostc3_transfer (device, NULL, S_BLOCK_WRITE, buffer, 3 + block_size, NULL, 0, NULL, TIMEOUT); } static dc_status_t @@ -1289,7 +1345,7 @@ hw_ostc3_firmware_block_write2 (hw_ostc3_device_t *device, unsigned int address, array_uint24_be_set (buffer, address); memcpy (buffer + 3, data + nbytes, SZ_FIRMWARE_BLOCK2); - status = hw_ostc3_transfer (device, NULL, S_BLOCK_WRITE2, buffer, sizeof(buffer), NULL, 0, NODELAY); + status = hw_ostc3_transfer (device, NULL, S_BLOCK_WRITE2, buffer, sizeof(buffer), NULL, 0, NULL, NODELAY); if (status != DC_STATUS_SUCCESS) { return status; } @@ -1330,7 +1386,7 @@ hw_ostc3_firmware_upgrade (dc_device_t *abstract, unsigned int checksum) buffer[4] = (buffer[4]<<1 | buffer[4]>>7); } - rc = hw_ostc3_transfer (device, NULL, S_UPGRADE, buffer, sizeof (buffer), NULL, 0, NODELAY); + rc = hw_ostc3_transfer (device, NULL, S_UPGRADE, buffer, sizeof (buffer), NULL, 0, NULL, NODELAY); if (rc != DC_STATUS_SUCCESS) { ERROR (context, "Failed to send flash firmware command"); return rc; @@ -1391,7 +1447,7 @@ hw_ostc3_device_fwupdate3 (dc_device_t *abstract, const char *filename) for (unsigned int len = 0; len < SZ_FIRMWARE; len += SZ_FIRMWARE_BLOCK) { char status[SZ_DISPLAY + 1]; // Status message on the display - snprintf (status, sizeof(status), " Uploading %2d%%", (100 * len) / SZ_FIRMWARE); + dc_platform_snprintf (status, sizeof(status), " Uploading %2d%%", (100 * len) / SZ_FIRMWARE); hw_ostc3_device_display (abstract, status); rc = hw_ostc3_firmware_block_write (device, FIRMWARE_AREA + len, firmware->data + len, SZ_FIRMWARE_BLOCK); @@ -1410,7 +1466,7 @@ hw_ostc3_device_fwupdate3 (dc_device_t *abstract, const char *filename) for (unsigned int len = 0; len < SZ_FIRMWARE; len += SZ_FIRMWARE_BLOCK) { unsigned char block[SZ_FIRMWARE_BLOCK]; char status[SZ_DISPLAY + 1]; // Status message on the display - snprintf (status, sizeof(status), " Verifying %2d%%", (100 * len) / SZ_FIRMWARE); + dc_platform_snprintf (status, sizeof(status), " Verifying %2d%%", (100 * len) / SZ_FIRMWARE); hw_ostc3_device_display (abstract, status); rc = hw_ostc3_firmware_block_read (device, FIRMWARE_AREA + len, block, sizeof (block)); @@ -1512,7 +1568,7 @@ hw_ostc3_device_fwupdate4 (dc_device_t *abstract, const char *filename) // Read the firmware version info. unsigned char fwinfo[SZ_FWINFO] = {0}; status = hw_ostc3_transfer (device, NULL, S_FWINFO, - data + offset + 4, 1, fwinfo, sizeof(fwinfo), NODELAY); + data + offset + 4, 1, fwinfo, sizeof(fwinfo), NULL, NODELAY); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the firmware info."); goto error; @@ -1525,7 +1581,7 @@ hw_ostc3_device_fwupdate4 (dc_device_t *abstract, const char *filename) !array_isequal(fwinfo, sizeof(fwinfo), 0xFF)) { status = hw_ostc3_transfer (device, &progress, S_UPLOAD, - data + offset, length, NULL, 0, usecs / 1000); + data + offset, length, NULL, 0, NULL, usecs / 1000); if (status != DC_STATUS_SUCCESS) { goto error; } diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index 7ebcd6a..ad15eec 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -81,8 +81,6 @@ #define OSTC4 0x3B -#define UNSUPPORTED 0xFFFFFFFF - #define OSTC3FW(major,minor) ( \ (((major) & 0xFF) << 8) | \ ((minor) & 0xFF)) @@ -112,15 +110,17 @@ typedef struct hw_ostc_layout_t { unsigned int salinity; unsigned int avgdepth; unsigned int duration; - unsigned int deco_info1; - unsigned int deco_info2; - unsigned int deco_model; + unsigned int gf; + unsigned int decomodel; unsigned int divemode; } hw_ostc_layout_t; typedef struct hw_ostc_gasmix_t { unsigned int oxygen; unsigned int helium; + unsigned int type; + unsigned int enabled; + unsigned int diluent; } hw_ostc_gasmix_t; typedef struct hw_ostc_parser_t { @@ -150,6 +150,9 @@ static const dc_parser_vtable_t hw_ostc_parser_vtable = { sizeof(hw_ostc_parser_t), DC_FAMILY_HW_OSTC, hw_ostc_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ hw_ostc_parser_get_datetime, /* datetime */ hw_ostc_parser_get_field, /* fields */ hw_ostc_parser_samples_foreach, /* samples_foreach */ @@ -169,9 +172,8 @@ static const hw_ostc_layout_t hw_ostc_layout_ostc = { 43, /* salinity */ 45, /* avgdepth */ 47, /* duration */ - 49, /* deco_info1 */ - 50, /* deco_info1 */ - 0, /* deco_model */ + 49, /* gf */ + UNDEFINED, /* decomodel */ 51, /* divemode */ }; @@ -188,9 +190,8 @@ static const hw_ostc_layout_t hw_ostc_layout_frog = { 43, /* salinity */ 45, /* avgdepth */ 47, /* duration */ - 49, /* deco_info1 */ - 50, /* deco_info2 */ - 0, /* deco_model */ + 49, /* gf */ + UNDEFINED, /* decomodel */ 51, /* divemode */ }; @@ -207,14 +208,13 @@ static const hw_ostc_layout_t hw_ostc_layout_ostc3 = { 70, /* salinity */ 73, /* avgdepth */ 75, /* duration */ - 77, /* deco_info1 */ - 78, /* deco_info2 */ - 79, /* deco_model */ + 77, /* gf */ + 79, /* decomodel */ 82, /* divemode */ }; static unsigned int -hw_ostc_find_gasmix (hw_ostc_parser_t *parser, unsigned int o2, unsigned int he, unsigned int type) +hw_ostc_find_gasmix (hw_ostc_parser_t *parser, unsigned int o2, unsigned int he, unsigned int dil, unsigned int type) { unsigned int offset = 0; unsigned int count = parser->ngasmixes; @@ -226,7 +226,7 @@ hw_ostc_find_gasmix (hw_ostc_parser_t *parser, unsigned int o2, unsigned int he, unsigned int i = offset; while (i < count) { - if (o2 == parser->gasmix[i].oxygen && he == parser->gasmix[i].helium) + if (o2 == parser->gasmix[i].oxygen && he == parser->gasmix[i].helium && dil == parser->gasmix[i].diluent) break; i++; } @@ -234,6 +234,18 @@ hw_ostc_find_gasmix (hw_ostc_parser_t *parser, unsigned int o2, unsigned int he, return i; } +static unsigned int +hw_ostc_is_ccr (unsigned int divemode, unsigned int version) +{ + if (version == 0x21) { + return divemode == OSTC_ZHL16_CC || divemode == OSTC_ZHL16_CC_GF || divemode == OSTC_PSCR_GF; + } else if (version == 0x23 || version == 0x24) { + return divemode == OSTC3_CC || divemode == OSTC3_PSCR; + } else { + return 0; + } +} + static dc_status_t hw_ostc_parser_cache (hw_ostc_parser_t *parser) { @@ -282,6 +294,13 @@ hw_ostc_parser_cache (hw_ostc_parser_t *parser) return DC_STATUS_DATAFORMAT; } + // Get the dive mode. + unsigned int divemode = layout->divemode < header ? + data[layout->divemode] : UNDEFINED; + + // Get the CCR mode. + unsigned int ccr = hw_ostc_is_ccr (divemode, version); + // Get all the gas mixes, the index of the inital mix, // the initial setpoint (used in the fixed setpoint CCR mode), // and the initial CNS from the header @@ -298,19 +317,25 @@ hw_ostc_parser_cache (hw_ostc_parser_t *parser) for (unsigned int i = 0; i < ngasmixes; ++i) { gasmix[i].oxygen = data[25 + 2 * i]; gasmix[i].helium = 0; + gasmix[i].type = 0; + gasmix[i].enabled = 1; + gasmix[i].diluent = 0; } } else if (version == 0x23 || version == 0x24) { ngasmixes = 5; for (unsigned int i = 0; i < ngasmixes; ++i) { gasmix[i].oxygen = data[28 + 4 * i + 0]; gasmix[i].helium = data[28 + 4 * i + 1]; + gasmix[i].type = data[28 + 4 * i + 3]; + gasmix[i].enabled = gasmix[i].type != 0; + gasmix[i].diluent = ccr; // Find the first gas marked as the initial gas. if (initial == UNDEFINED && data[28 + 4 * i + 3] == 1) { initial = i + 1; /* One based index! */ } } // The first fixed setpoint is the initial setpoint in CCR mode. - if (data[layout->divemode] == OSTC3_CC || data[layout->divemode] == OSTC3_PSCR) { + if (ccr) { initial_setpoint = data[60]; } // Initial CNS @@ -323,6 +348,13 @@ hw_ostc_parser_cache (hw_ostc_parser_t *parser) for (unsigned int i = 0; i < ngasmixes; ++i) { gasmix[i].oxygen = data[19 + 2 * i + 0]; gasmix[i].helium = data[19 + 2 * i + 1]; + gasmix[i].type = 0; + if (version == 0x21) { + gasmix[i].enabled = data[53] & (1 << i); + } else { + gasmix[i].enabled = 1; + } + gasmix[i].diluent = ccr; } } if (initial != UNDEFINED) { @@ -382,6 +414,9 @@ hw_ostc_parser_create_internal (dc_parser_t **out, dc_context_t *context, unsign for (unsigned int i = 0; i < NGASMIXES; ++i) { parser->gasmix[i].oxygen = 0; parser->gasmix[i].helium = 0; + parser->gasmix[i].type = 0; + parser->gasmix[i].enabled = 0; + parser->gasmix[i].diluent = 0; } parser->serial = serial; @@ -421,6 +456,9 @@ hw_ostc_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsig for (unsigned int i = 0; i < NGASMIXES; ++i) { parser->gasmix[i].oxygen = 0; parser->gasmix[i].helium = 0; + parser->gasmix[i].type = 0; + parser->gasmix[i].enabled = 0; + parser->gasmix[i].diluent = 0; } return DC_STATUS_SUCCESS; @@ -512,6 +550,7 @@ hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_salinity_t *water = (dc_salinity_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; dc_field_string_t *string = (dc_field_string_t *) value; unsigned int salinity = data[layout->salinity]; @@ -615,6 +654,70 @@ hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned return DC_STATUS_UNSUPPORTED; } break; + case DC_FIELD_DECOMODEL: + if (version == 0x21) { + switch (data[layout->divemode]) { + case OSTC_APNEA: + case OSTC_GAUGE: + decomodel->type = DC_DECOMODEL_NONE; + break; + case OSTC_ZHL16_OC: + case OSTC_ZHL16_CC: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->params.gf.low = 100; + decomodel->params.gf.high = 100; + break; + case OSTC_ZHL16_OC_GF: + case OSTC_ZHL16_CC_GF: + case OSTC_PSCR_GF: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->params.gf.low = data[layout->gf + 0]; + decomodel->params.gf.high = data[layout->gf + 1]; + break; + default: + return DC_STATUS_DATAFORMAT; + } + } else if (version == 0x22) { + switch (data[layout->divemode]) { + case FROG_ZHL16: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->params.gf.low = 100; + decomodel->params.gf.high = 100; + break; + case FROG_ZHL16_GF: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->params.gf.low = data[layout->gf + 0]; + decomodel->params.gf.high = data[layout->gf + 1]; + break; + case FROG_APNEA: + decomodel->type = DC_DECOMODEL_NONE; + break; + default: + return DC_STATUS_DATAFORMAT; + } + } else if (version == 0x23 || version == 0x24) { + switch (data[layout->decomodel]) { + case OSTC3_ZHL16: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->params.gf.low = 100; + decomodel->params.gf.high = 100; + break; + case OSTC3_ZHL16_GF: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->params.gf.low = data[layout->gf + 0]; + decomodel->params.gf.high = data[layout->gf + 1]; + break; + case OSTC4_VPM: + decomodel->type = DC_DECOMODEL_VPM; + break; + default: + return DC_STATUS_DATAFORMAT; + } + } else { + return DC_STATUS_UNSUPPORTED; + } + decomodel->conservatism = 0; + break; case DC_FIELD_STRING: switch(flags) { case 0: /* serial */ @@ -657,11 +760,11 @@ hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned case 4: /* Deco model */ string->desc = "Deco model"; - if (((version == 0x23 || version == 0x24) && data[layout->deco_model] == OSTC3_ZHL16) || + if (((version == 0x23 || version == 0x24) && data[layout->decomodel] == OSTC3_ZHL16) || (version == 0x22 && data[layout->divemode] == FROG_ZHL16) || (version == 0x21 && (data[layout->divemode] == OSTC_ZHL16_OC || data[layout->divemode] == OSTC_ZHL16_CC))) strncpy(buf, "ZH-L16", BUFLEN); - else if (((version == 0x23 || version == 0x24) && data[layout->deco_model] == OSTC3_ZHL16_GF) || + else if (((version == 0x23 || version == 0x24) && data[layout->decomodel] == OSTC3_ZHL16_GF) || (version == 0x22 && data[layout->divemode] == FROG_ZHL16_GF) || (version == 0x21 && (data[layout->divemode] == OSTC_ZHL16_OC_GF || data[layout->divemode] == OSTC_ZHL16_CC_GF))) strncpy(buf, "ZH-L16-GF", BUFLEN); @@ -672,14 +775,14 @@ hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned break; case 5: /* Deco model info */ string->desc = "Deco model info"; - if (((version == 0x23 || version == 0x24) && data[layout->deco_model] == OSTC3_ZHL16) || + if (((version == 0x23 || version == 0x24) && data[layout->decomodel] == OSTC3_ZHL16) || (version == 0x22 && data[layout->divemode] == FROG_ZHL16) || (version == 0x21 && (data[layout->divemode] == OSTC_ZHL16_OC || data[layout->divemode] == OSTC_ZHL16_CC))) - snprintf(buf, BUFLEN, "Saturation %u, Desaturation %u", data[layout->deco_info1], data[layout->deco_info2]); - else if (((version == 0x23 || version == 0x24) && data[layout->deco_model] == OSTC3_ZHL16_GF) || + snprintf(buf, BUFLEN, "Saturation %u, Desaturation %u", data[layout->gf], data[layout->gf+1]); + else if (((version == 0x23 || version == 0x24) && data[layout->decomodel] == OSTC3_ZHL16_GF) || (version == 0x22 && data[layout->divemode] == FROG_ZHL16_GF) || (version == 0x21 && (data[layout->divemode] == OSTC_ZHL16_OC_GF || data[layout->divemode] == OSTC_ZHL16_CC_GF))) - snprintf(buf, BUFLEN, "GF %u/%u", data[layout->deco_info1], data[layout->deco_info2]); + snprintf(buf, BUFLEN, "GF %u/%u", data[layout->gf], data[layout->gf+1]); else return DC_STATUS_DATAFORMAT; break; @@ -714,8 +817,10 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call const hw_ostc_layout_t *layout = parser->layout; // Exit if no profile data available. - if (size == header || (size == header + 2 && - data[header] == 0xFD && data[header + 1] == 0xFD)) { + const unsigned char empty[] = {0x08, 0x00, 0x00, 0xFD, 0xFD}; + if (size == header || + (size == header + 2 && memcmp(data + header, empty + 3, 2) == 0) || + (size == header + 5 && memcmp(data + header, empty, 5) == 0)) { parser->cached = PROFILE; return DC_STATUS_SUCCESS; } @@ -803,6 +908,13 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call firmware = array_uint16_be (data + layout->firmware); } + // Get the dive mode. + unsigned int divemode = layout->divemode < header ? + data[layout->divemode] : UNDEFINED; + + // Get the CCR mode. + unsigned int ccr = hw_ostc_is_ccr (divemode, version); + unsigned int time = 0; unsigned int nsamples = 0; unsigned int tank = parser->initial != UNDEFINED ? parser->initial : 0; @@ -910,7 +1022,7 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call } unsigned int o2 = data[offset]; unsigned int he = data[offset + 1]; - unsigned int idx = hw_ostc_find_gasmix (parser, o2, he, MANUAL); + unsigned int idx = hw_ostc_find_gasmix (parser, o2, he, ccr, MANUAL); if (idx >= parser->ngasmixes) { if (idx >= NGASMIXES) { ERROR (abstract->context, "Maximum number of gas mixes reached."); @@ -918,6 +1030,9 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call } parser->gasmix[idx].oxygen = o2; parser->gasmix[idx].helium = he; + parser->gasmix[idx].type = 0; + parser->gasmix[idx].enabled = 1; + parser->gasmix[idx].diluent = ccr; parser->ngasmixes = idx + 1; } @@ -934,8 +1049,8 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call return DC_STATUS_DATAFORMAT; } unsigned int idx = data[offset]; - if (idx < 1 || idx > parser->ngasmixes) { - ERROR(abstract->context, "Invalid gas mix."); + if (idx < 1 || idx > parser->nfixed) { + ERROR(abstract->context, "Invalid gas mix (%u).", idx); return DC_STATUS_DATAFORMAT; } idx--; /* Convert to a zero based index. */ @@ -968,7 +1083,7 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call unsigned int o2 = data[offset]; unsigned int he = data[offset + 1]; - unsigned int idx = hw_ostc_find_gasmix (parser, o2, he, MANUAL); + unsigned int idx = hw_ostc_find_gasmix (parser, o2, he, 0, MANUAL); if (idx >= parser->ngasmixes) { if (idx >= NGASMIXES) { ERROR (abstract->context, "Maximum number of gas mixes reached."); @@ -976,6 +1091,9 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call } parser->gasmix[idx].oxygen = o2; parser->gasmix[idx].helium = he; + parser->gasmix[idx].type = 0; + parser->gasmix[idx].enabled = 1; + parser->gasmix[idx].diluent = 0; parser->ngasmixes = idx + 1; } @@ -1098,7 +1216,7 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call unsigned int o2 = data[offset]; unsigned int he = data[offset + 1]; - unsigned int idx = hw_ostc_find_gasmix (parser, o2, he, MANUAL); + unsigned int idx = hw_ostc_find_gasmix (parser, o2, he, 0, MANUAL); if (idx >= parser->ngasmixes) { if (idx >= NGASMIXES) { ERROR (abstract->context, "Maximum number of gas mixes reached."); @@ -1106,6 +1224,9 @@ hw_ostc_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t call } parser->gasmix[idx].oxygen = o2; parser->gasmix[idx].helium = he; + parser->gasmix[idx].type = 0; + parser->gasmix[idx].enabled = 1; + parser->gasmix[idx].diluent = 0; parser->ngasmixes = idx + 1; } diff --git a/src/irda.c b/src/irda.c index 8c5d612..a5bb8e4 100644 --- a/src/irda.c +++ b/src/irda.c @@ -24,7 +24,6 @@ #endif #include // malloc, free -#include // snprintf #include #include "socket.h" @@ -314,7 +313,7 @@ dc_irda_open (dc_iostream_t **out, dc_context_t *context, unsigned int address, peer.irdaDeviceID[1] = (address >> 8) & 0xFF; peer.irdaDeviceID[2] = (address >> 16) & 0xFF; peer.irdaDeviceID[3] = (address >> 24) & 0xFF; - snprintf (peer.irdaServiceName, sizeof(peer.irdaServiceName), "LSAP-SEL%u", lsap); + dc_platform_snprintf (peer.irdaServiceName, sizeof(peer.irdaServiceName), "LSAP-SEL%u", lsap); #else struct sockaddr_irda peer; peer.sir_family = AF_IRDA; diff --git a/src/libdivecomputer.symbols b/src/libdivecomputer.symbols index 0982ba6..85d99ec 100644 --- a/src/libdivecomputer.symbols +++ b/src/libdivecomputer.symbols @@ -89,6 +89,9 @@ dc_custom_open dc_parser_new dc_parser_new2 +dc_parser_set_clock +dc_parser_set_atmospheric +dc_parser_set_density dc_parser_get_type dc_parser_set_data dc_parser_get_datetime diff --git a/src/liquivision_lynx_parser.c b/src/liquivision_lynx_parser.c index c4272d8..764d20e 100644 --- a/src/liquivision_lynx_parser.c +++ b/src/liquivision_lynx_parser.c @@ -66,6 +66,9 @@ #define TEC 2 #define REC 3 +#define ZHL16GF 0 +#define RGBM 1 + #define NORMAL 0 #define BOOKMARK 1 #define ALARM_DEPTH 2 @@ -123,6 +126,9 @@ static const dc_parser_vtable_t liquivision_lynx_parser_vtable = { sizeof(liquivision_lynx_parser_t), DC_FAMILY_LIQUIVISION_LYNX, liquivision_lynx_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ liquivision_lynx_parser_get_datetime, /* datetime */ liquivision_lynx_parser_get_field, /* fields */ liquivision_lynx_parser_samples_foreach, /* samples_foreach */ @@ -231,6 +237,7 @@ liquivision_lynx_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_tank_t *tank = (dc_tank_t *) value; dc_salinity_t *water = (dc_salinity_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; if (value) { switch (type) { @@ -288,6 +295,22 @@ liquivision_lynx_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, } } break; + case DC_FIELD_DECOMODEL: + switch (abstract->data[93]) { + case ZHL16GF: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->conservatism = 0; + decomodel->params.gf.low = 0; + decomodel->params.gf.high = 0; + break; + case RGBM: + decomodel->type = DC_DECOMODEL_RGBM; + decomodel->conservatism = 0; + break; + default: + return DC_STATUS_DATAFORMAT; + } + break; case DC_FIELD_GASMIX_COUNT: *((unsigned int *) value) = parser->ngasmixes; break; diff --git a/src/mares_darwin_parser.c b/src/mares_darwin_parser.c index ba0382f..88e831c 100644 --- a/src/mares_darwin_parser.c +++ b/src/mares_darwin_parser.c @@ -56,6 +56,9 @@ static const dc_parser_vtable_t mares_darwin_parser_vtable = { sizeof(mares_darwin_parser_t), DC_FAMILY_MARES_DARWIN, mares_darwin_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ mares_darwin_parser_get_datetime, /* datetime */ mares_darwin_parser_get_field, /* fields */ mares_darwin_parser_samples_foreach, /* samples_foreach */ diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index 87440c3..ed2e4ae 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -276,6 +276,9 @@ static const dc_parser_vtable_t mares_iconhd_parser_vtable = { sizeof(mares_iconhd_parser_t), DC_FAMILY_MARES_ICONHD, mares_iconhd_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ mares_iconhd_parser_get_datetime, /* datetime */ mares_iconhd_parser_get_field, /* fields */ mares_iconhd_parser_samples_foreach, /* samples_foreach */ @@ -1089,6 +1092,7 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t unsigned int depth = 0, temperature = 0; unsigned int gasmix = 0, alarms = 0; unsigned int decostop = 0, decodepth = 0, decotime = 0, tts = 0; + unsigned int bookmark = 0; if (parser->model == GENIUS || parser->model == HORIZON) { if (parser->logformat == 1) { if (!mares_genius_isvalid (data + offset, SDPT_SIZE, SDPT_TYPE)) { @@ -1102,6 +1106,7 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t alarms = array_uint32_le (data + offset + marker + 0x14); misc = array_uint32_le (data + offset + marker + 0x18); deco = array_uint32_le (data + offset + marker + 0x1C); + bookmark = (misc >> 2) & 0x0F; gasmix = (misc >> 6) & 0x0F; decostop = (misc >> 10) & 0x01; if (decostop) { @@ -1123,6 +1128,7 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t decotime = array_uint16_le (data + offset + marker + 0x0A); alarms = array_uint32_le (data + offset + marker + 0x0C); misc = array_uint32_le (data + offset + marker + 0x14); + bookmark = (misc >> 2) & 0x0F; gasmix = (misc >> 6) & 0x0F; decostop = (misc >> 18) & 0x01; decodepth = (misc >> 19) & 0x7F; @@ -1159,6 +1165,15 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t } } + // Bookmark + if (bookmark) { + sample.event.type = SAMPLE_EVENT_BOOKMARK; + sample.event.time = 0; + sample.event.flags = 0; + sample.event.value = bookmark; + if (callback) callback (DC_SAMPLE_EVENT, sample, userdata); + } + if (parser->model == GENIUS || parser->model == HORIZON) { // Deco stop / NDL. if (decostop) { diff --git a/src/mares_nemo_parser.c b/src/mares_nemo_parser.c index c0ab03b..2f26605 100644 --- a/src/mares_nemo_parser.c +++ b/src/mares_nemo_parser.c @@ -68,6 +68,9 @@ static const dc_parser_vtable_t mares_nemo_parser_vtable = { sizeof(mares_nemo_parser_t), DC_FAMILY_MARES_NEMO, mares_nemo_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ mares_nemo_parser_get_datetime, /* datetime */ mares_nemo_parser_get_field, /* fields */ mares_nemo_parser_samples_foreach, /* samples_foreach */ diff --git a/src/mclean_extreme.c b/src/mclean_extreme.c index b69546f..98038ff 100644 --- a/src/mclean_extreme.c +++ b/src/mclean_extreme.c @@ -471,11 +471,6 @@ mclean_extreme_device_timesync(dc_device_t *abstract, const dc_datetime_t *datet { mclean_extreme_device_t *device = (mclean_extreme_device_t *)abstract; - if (datetime == NULL) { - ERROR(abstract->context, "Invalid parameter specified."); - return DC_STATUS_INVALIDARGS; - } - // Get the UTC timestamp. dc_ticks_t ticks = dc_datetime_mktime(datetime); if (ticks == -1 || ticks < EPOCH || ticks - EPOCH > 0xFFFFFFFF) { diff --git a/src/mclean_extreme_parser.c b/src/mclean_extreme_parser.c index 5fef8aa..694bf60 100644 --- a/src/mclean_extreme_parser.c +++ b/src/mclean_extreme_parser.c @@ -67,6 +67,9 @@ static const dc_parser_vtable_t mclean_extreme_parser_vtable = { sizeof(mclean_extreme_parser_t), DC_FAMILY_MCLEAN_EXTREME, mclean_extreme_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ mclean_extreme_parser_get_datetime, /* datetime */ mclean_extreme_parser_get_field, /* fields */ mclean_extreme_parser_samples_foreach, /* samples_foreach */ diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index 658e930..a032f9c 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -35,14 +35,6 @@ #define ISINSTANCE(device) dc_device_isinstance((device), &oceanic_atom2_device_vtable.base) -#define PROPLUSX 0x4552 -#define VTX 0x4557 -#define I750TC 0x455A -#define SAGE 0x4647 -#define I770R 0x4651 -#define GEO40 0x4653 -#define BEACON 0x4742 - #define MAXPACKET 256 #define MAXRETRIES 2 #define MAXDELAY 16 @@ -62,9 +54,13 @@ #define ACK 0x5A #define NAK 0xA5 +#define REPEAT 50 + typedef struct oceanic_atom2_device_t { oceanic_common_device_t base; dc_iostream_t *iostream; + unsigned int handshake_repeat; + unsigned int handshake_counter; unsigned int sequence; unsigned int delay; unsigned int extra; @@ -365,7 +361,7 @@ static const oceanic_common_layout_t oceanic_proplusx_layout = { }; static const oceanic_common_layout_t aqualung_i770r_layout = { - 0x440000, /* memsize */ + 0x640000, /* memsize */ 0x40000, /* highmem */ 0x0000, /* cf_devinfo */ 0x0040, /* cf_pointers */ @@ -373,7 +369,7 @@ static const oceanic_common_layout_t aqualung_i770r_layout = { 0x10000, /* rb_logbook_end */ 16, /* rb_logbook_entry_size */ 0x40000, /* rb_profile_begin */ - 0x440000, /* rb_profile_end */ + 0x640000, /* rb_profile_end */ 0, /* pt_mode_global */ 1, /* pt_mode_logbook */ 0, /* pt_mode_serial */ @@ -410,100 +406,103 @@ static const oceanic_common_layout_t aqualung_i450t_layout = { }; static const oceanic_common_version_t versions[] = { - {"OCEVEO10 \0\0 8K", 0, &oceanic_veo1_layout}, - {"AERIS XR1 NX R\0\0", 0, &oceanic_veo1_layout}, + {"OCEVEO10 \0\0 8K", 0, VEO10, &oceanic_veo1_layout}, + {"AERIS XR1 NX R\0\0", 0, XR1NX, &oceanic_veo1_layout}, - {"ATOM rev\0\0 256K", 0, &oceanic_atom1_layout}, + {"ATOM rev\0\0 256K", 0, ATOM1, &oceanic_atom1_layout}, - {"MANTA R\0\0 512K", 0x3242, &oceanic_atom2a_layout}, - {"MANTA R\0\0 512K", 0, &oceanic_atom2c_layout}, - {"2M ATOM r\0\0 512K", 0x3349, &oceanic_atom2a_layout}, - {"2M ATOM r\0\0 512K", 0, &oceanic_atom2c_layout}, + {"MANTA R\0\0 512K", 0x3242, MANTA, &oceanic_atom2a_layout}, + {"MANTA R\0\0 512K", 0, MANTA, &oceanic_atom2c_layout}, + {"2M ATOM r\0\0 512K", 0x3349, ATOM2, &oceanic_atom2a_layout}, + {"2M ATOM r\0\0 512K", 0, ATOM2, &oceanic_atom2c_layout}, - {"INSIGHT2 \0\0 512K", 0, &oceanic_atom2a_layout}, - {"OCEVEO30 \0\0 512K", 0, &oceanic_atom2a_layout}, - {"ATMOSAI R\0\0 512K", 0, &oceanic_atom2a_layout}, - {"PROPLUS2 \0\0 512K", 0, &oceanic_atom2a_layout}, - {"OCEGEO20 \0\0 512K", 0, &oceanic_atom2a_layout}, - {"OCE GEO R\0\0 512K", 0, &oceanic_atom2a_layout}, - {"AQUAI200 \0\0 512K", 0, &oceanic_atom2a_layout}, - {"AQUA200C \0\0 512K", 0, &oceanic_atom2a_layout}, + {"INSIGHT2 \0\0 512K", 0, INSIGHT2, &oceanic_atom2a_layout}, + {"OCEVEO30 \0\0 512K", 0, VEO30, &oceanic_atom2a_layout}, + {"ATMOSAI R\0\0 512K", 0, ATMOSAI2, &oceanic_atom2a_layout}, + {"PROPLUS2 \0\0 512K", 0, PROPLUS21, &oceanic_atom2a_layout}, + {"OCEGEO20 \0\0 512K", 0, GEO20, &oceanic_atom2a_layout}, + {"OCE GEO R\0\0 512K", 0, GEO, &oceanic_atom2a_layout}, + {"AQUAI200 \0\0 512K", 0, I200, &oceanic_atom2a_layout}, + {"AQUA200C \0\0 512K", 0, I200C, &oceanic_atom2a_layout}, - {"ELEMENT2 \0\0 512K", 0, &oceanic_atom2b_layout}, - {"OCEVEO20 \0\0 512K", 0, &oceanic_atom2b_layout}, - {"TUSAZEN \0\0 512K", 0, &oceanic_atom2b_layout}, - {"AQUAI300 \0\0 512K", 0, &oceanic_atom2b_layout}, - {"HOLLDG03 \0\0 512K", 0, &oceanic_atom2b_layout}, - {"AQUAI100 \0\0 512K", 0, &oceanic_atom2b_layout}, - {"AQUA300C \0\0 512K", 0, &oceanic_atom2b_layout}, - {"OCEGEO40 \0\0 512K", 0, &oceanic_atom2b_layout}, - {"VEOSMART \0\0 512K", 0, &oceanic_atom2b_layout}, + {"ELEMENT2 \0\0 512K", 0, ELEMENT2, &oceanic_atom2b_layout}, + {"OCEVEO20 \0\0 512K", 0, VEO20, &oceanic_atom2b_layout}, + {"TUSAZEN \0\0 512K", 0, ZEN, &oceanic_atom2b_layout}, + {"AQUAI300 \0\0 512K", 0, I300, &oceanic_atom2b_layout}, + {"HOLLDG03 \0\0 512K", 0, DG03, &oceanic_atom2b_layout}, + {"AQUAI100 \0\0 512K", 0, I100, &oceanic_atom2b_layout}, + {"AQUA300C \0\0 512K", 0, I300C, &oceanic_atom2b_layout}, + {"OCEGEO40 \0\0 512K", 0, GEO40, &oceanic_atom2b_layout}, + {"VEOSMART \0\0 512K", 0, VEO40, &oceanic_atom2b_layout}, - {"2M EPIC r\0\0 512K", 0, &oceanic_atom2c_layout}, - {"EPIC1 R\0\0 512K", 0, &oceanic_atom2c_layout}, - {"AERIA300 \0\0 512K", 0, &oceanic_atom2c_layout}, + {"2M EPIC r\0\0 512K", 0, EPICA, &oceanic_atom2c_layout}, + {"EPIC1 R\0\0 512K", 0, EPICB, &oceanic_atom2c_layout}, + {"AERIA300 \0\0 512K", 0, A300, &oceanic_atom2c_layout}, - {"OCE VT3 R\0\0 512K", 0, &oceanic_default_layout}, - {"ELITET3 R\0\0 512K", 0, &oceanic_default_layout}, - {"ELITET31 \0\0 512K", 0, &oceanic_default_layout}, - {"DATAMASK \0\0 512K", 0, &oceanic_default_layout}, - {"COMPMASK \0\0 512K", 0, &oceanic_default_layout}, + {"OCE VT3 R\0\0 512K", 0, VT3, &oceanic_default_layout}, + {"ELITET3 R\0\0 512K", 0, T3A, &oceanic_default_layout}, + {"ELITET31 \0\0 512K", 0, T3B, &oceanic_default_layout}, + {"DATAMASK \0\0 512K", 0, DATAMASK, &oceanic_default_layout}, + {"COMPMASK \0\0 512K", 0, COMPUMASK, &oceanic_default_layout}, - {"WISDOM R\0\0 512K", 0, &sherwood_wisdom_layout}, + {"WISDOM R\0\0 512K", 0x3342, WISDOM3, &sherwood_wisdom_layout}, + {"WISDOM R\0\0 512K", 0, WISDOM2, &sherwood_wisdom_layout}, - {"PROPLUS3 \0\0 512K", 0, &oceanic_proplus3_layout}, - {"PROPLUS4 \0\0 512K", 0, &oceanic_proplus3_layout}, + {"PROPLUS3 \0\0 512K", 0, PROPLUS3, &oceanic_proplus3_layout}, + {"PROPLUS4 \0\0 512K", 0, PROPLUS4, &oceanic_proplus3_layout}, - {"TUZENAIR \0\0 512K", 0, &tusa_zenair_layout}, - {"AMPHOSSW \0\0 512K", 0, &tusa_zenair_layout}, - {"AMPHOAIR \0\0 512K", 0, &tusa_zenair_layout}, - {"VOYAGE2G \0\0 512K", 0, &tusa_zenair_layout}, - {"TUSTALIS \0\0 512K", 0, &tusa_zenair_layout}, - {"AMPHOS20 \0\0 512K", 0, &tusa_zenair_layout}, - {"AMPAIR20 \0\0 512K", 0, &tusa_zenair_layout}, + {"TUZENAIR \0\0 512K", 0, ZENAIR, &tusa_zenair_layout}, + {"AMPHOSSW \0\0 512K", 0, AMPHOS, &tusa_zenair_layout}, + {"AMPHOAIR \0\0 512K", 0, AMPHOSAIR, &tusa_zenair_layout}, + {"VOYAGE2G \0\0 512K", 0, VOYAGER2G, &tusa_zenair_layout}, + {"TUSTALIS \0\0 512K", 0, TALIS, &tusa_zenair_layout}, + {"AMPHOS20 \0\0 512K", 0, AMPHOS2, &tusa_zenair_layout}, + {"AMPAIR20 \0\0 512K", 0, AMPHOSAIR2, &tusa_zenair_layout}, - {"REACPRO2 \0\0 512K", 0, &oceanic_reactpro_layout}, + {"REACPRO2 \0\0 512K", 0, REACTPROWHITE, &oceanic_reactpro_layout}, - {"FREEWAER \0\0 512K", 0, &aeris_f10_layout}, - {"OCEANF10 \0\0 512K", 0, &aeris_f10_layout}, - {"MUNDIAL R\0\0 512K", 0, &aeris_f10_layout}, + {"FREEWAER \0\0 512K", 0, F10A, &aeris_f10_layout}, + {"OCEANF10 \0\0 512K", 0, F10B, &aeris_f10_layout}, + {"MUNDIAL R\0\0 512K", 0x3300, MUNDIAL3, &aeris_f10_layout}, + {"MUNDIAL R\0\0 512K", 0, MUNDIAL2, &aeris_f10_layout}, - {"AERISF11 \0\0 1024", 0, &aeris_f11_layout}, - {"OCEANF11 \0\0 1024", 0, &aeris_f11_layout}, + {"AERISF11 \0\0 1024", 0, F11A, &aeris_f11_layout}, + {"OCEANF11 \0\0 1024", 0, F11B, &aeris_f11_layout}, - {"OCWATCH R\0\0 1024", 0, &oceanic_oc1_layout}, - {"OC1WATCH \0\0 1024", 0, &oceanic_oc1_layout}, - {"OCSWATCH \0\0 1024", 0, &oceanic_oc1_layout}, - {"AQUAI550 \0\0 1024", 0, &oceanic_oc1_layout}, - {"AQUA550C \0\0 1024", 0, &oceanic_oc1_layout}, - {"WISDOM04 \0\0 1024", 0, &oceanic_oc1_layout}, - {"AQUA470C \0\0 1024", 0, &oceanic_oc1_layout}, - {"AQUA200C \0\0 1024", 0, &oceanic_oc1_layout}, + {"OCWATCH R\0\0 1024", 0, OC1A, &oceanic_oc1_layout}, + {"OC1WATCH \0\0 1024", 0, OC1B, &oceanic_oc1_layout}, + {"OCSWATCH \0\0 1024", 0, OCS, &oceanic_oc1_layout}, + {"AQUAI550 \0\0 1024", 0, I550, &oceanic_oc1_layout}, + {"AQUA550C \0\0 1024", 0, I550C, &oceanic_oc1_layout}, + {"WISDOM04 \0\0 1024", 0, WISDOM4, &oceanic_oc1_layout}, + {"AQUA470C \0\0 1024", 0, I470TC, &oceanic_oc1_layout}, + {"AQUA200C \0\0 1024", 0, I200CV2, &oceanic_oc1_layout}, + {"GEOAIR \0\0 1024", 0, GEOAIR, &oceanic_oc1_layout}, - {"OCEANOCI \0\0 1024", 0, &oceanic_oci_layout}, + {"OCEANOCI \0\0 1024", 0, OCI, &oceanic_oci_layout}, - {"OCEATOM3 \0\0 1024", 0, &oceanic_atom3_layout}, - {"ATOM31 \0\0 1024", 0, &oceanic_atom3_layout}, + {"OCEATOM3 \0\0 1024", 0, ATOM3, &oceanic_atom3_layout}, + {"ATOM31 \0\0 1024", 0, ATOM31, &oceanic_atom3_layout}, - {"OCEANVT4 \0\0 1024", 0, &oceanic_vt4_layout}, - {"OCEAVT41 \0\0 1024", 0, &oceanic_vt4_layout}, - {"AERISAIR \0\0 1024", 0, &oceanic_vt4_layout}, - {"SWVISION \0\0 1024", 0, &oceanic_vt4_layout}, - {"XPSUBAIR \0\0 1024", 0, &oceanic_vt4_layout}, + {"OCEANVT4 \0\0 1024", 0, VT4, &oceanic_vt4_layout}, + {"OCEAVT41 \0\0 1024", 0, VT41, &oceanic_vt4_layout}, + {"AERISAIR \0\0 1024", 0, A300AI, &oceanic_vt4_layout}, + {"SWVISION \0\0 1024", 0, VISION, &oceanic_vt4_layout}, + {"XPSUBAIR \0\0 1024", 0, XPAIR, &oceanic_vt4_layout}, - {"HOLLDG04 \0\0 2048", 0, &hollis_tx1_layout}, + {"HOLLDG04 \0\0 2048", 0, TX1, &hollis_tx1_layout}, - {"AER300CS \0\0 2048", 0, &aeris_a300cs_layout}, - {"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}, + {"AER300CS \0\0 2048", 0, A300CS, &aeris_a300cs_layout}, + {"OCEANVTX \0\0 2048", 0, VTX, &aeris_a300cs_layout}, + {"AQUAI750 \0\0 2048", 0, I750TC, &aeris_a300cs_layout}, + {"SWDRAGON \0\0 2048", 0, SAGE, &aeris_a300cs_layout}, + {"SWBEACON \0\0 2048", 0, BEACON, &aeris_a300cs_layout}, - {"AQUAI450 \0\0 2048", 0, &aqualung_i450t_layout}, + {"AQUAI450 \0\0 2048", 0, I450T, &aqualung_i450t_layout}, - {"OCEANOCX \0\0 \0\0\0\0", 0, &oceanic_proplusx_layout}, + {"OCEANOCX \0\0 \0\0\0\0", 0, PROPLUSX, &oceanic_proplusx_layout}, - {"AQUA770R \0\0 \0\0\0\0", 0, &aqualung_i770r_layout}, + {"AQUA770R \0\0 \0\0\0\0", 0, I770R, &aqualung_i770r_layout}, }; /* @@ -932,8 +931,8 @@ oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream } // Detect the memory layout. - device->base.layout = OCEANIC_COMMON_MATCH(device->base.version, versions, &device->base.firmware); - if (device->base.layout == NULL) { + const oceanic_common_version_t *version = OCEANIC_COMMON_MATCH(device->base.version, versions, &device->base.firmware); + if (version == NULL) { WARNING (context, "Unsupported device detected (%s)!", device->base.version); if (memcmp(device->base.version + 12, "256K", 4) == 0) { device->base.layout = &oceanic_atom1_layout; @@ -946,10 +945,15 @@ oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream } else { device->base.layout = &oceanic_default_layout; } + device->base.model = 0; + } else { + device->base.layout = version->layout; + device->base.model = version->model; } // Set the big page support. - if (device->base.layout == &aeris_f11_layout) { + if (device->base.layout == &aeris_f11_layout || + device->base.layout == &oceanic_proplus3_layout) { device->bigpage = 8; } else if (device->base.layout == &oceanic_proplusx_layout || device->base.layout == &aqualung_i770r_layout || @@ -957,6 +961,11 @@ oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream device->bigpage = 16; } + // Repeat the handshaking every few packets. + device->handshake_repeat = dc_iostream_get_transport (device->iostream) == DC_TRANSPORT_BLE && + device->base.model == PROPLUS4; + device->handshake_counter = 0; + *out = (dc_device_t*) device; return DC_STATUS_SUCCESS; @@ -1043,7 +1052,7 @@ oceanic_atom2_device_read (dc_device_t *abstract, unsigned int address, unsigned break; case 8: read_cmd = CMD_READ8; - crc_size = 1; + crc_size = device->base.model == PROPLUS4 ? 2 : 1; break; case 16: read_cmd = CMD_READ16; @@ -1074,6 +1083,12 @@ oceanic_atom2_device_read (dc_device_t *abstract, unsigned int address, unsigned unsigned int page = (address - highmem) / pagesize; if (page != device->cached_page || highmem != device->cached_highmem) { + if (device->handshake_repeat && ++device->handshake_counter % REPEAT == 0) { + unsigned char version[PAGESIZE] = {0}; + oceanic_atom2_device_version (abstract, version, sizeof (version)); + oceanic_atom2_ble_handshake (device); + } + // Read the package. unsigned int number = highmem ? page : page * device->bigpage; // This is always PAGESIZE, even in big page mode. unsigned char command[] = {read_cmd, diff --git a/src/oceanic_atom2_parser.c b/src/oceanic_atom2_parser.c index ea60f8f..edb652d 100644 --- a/src/oceanic_atom2_parser.c +++ b/src/oceanic_atom2_parser.c @@ -33,78 +33,6 @@ #define ISINSTANCE(parser) dc_parser_isinstance((parser), &oceanic_atom2_parser_vtable) -#define ATOM1 0x4250 -#define EPICA 0x4257 -#define VT3 0x4258 -#define T3A 0x4259 -#define ATOM2 0x4342 -#define GEO 0x4344 -#define MANTA 0x4345 -#define DATAMASK 0x4347 -#define COMPUMASK 0x4348 -#define OC1A 0x434E -#define F10A 0x434D -#define WISDOM2 0x4350 -#define INSIGHT2 0x4353 -#define ELEMENT2 0x4357 -#define VEO20 0x4359 -#define VEO30 0x435A -#define ZEN 0x4441 -#define ZENAIR 0x4442 -#define ATMOSAI2 0x4443 -#define PROPLUS21 0x4444 -#define GEO20 0x4446 -#define VT4 0x4447 -#define OC1B 0x4449 -#define VOYAGER2G 0x444B -#define ATOM3 0x444C -#define DG03 0x444D -#define OCS 0x4450 -#define OC1C 0x4451 -#define VT41 0x4452 -#define EPICB 0x4453 -#define T3B 0x4455 -#define ATOM31 0x4456 -#define A300AI 0x4457 -#define WISDOM3 0x4458 -#define A300 0x445A -#define TX1 0x4542 -#define MUNDIAL2 0x4543 -#define AMPHOS 0x4545 -#define AMPHOSAIR 0x4546 -#define PROPLUS3 0x4548 -#define F11A 0x4549 -#define OCI 0x454B -#define A300CS 0x454C -#define TALIS 0x454E -#define MUNDIAL3 0x4550 -#define PROPLUSX 0x4552 -#define F10B 0x4553 -#define F11B 0x4554 -#define XPAIR 0x4555 -#define VISION 0x4556 -#define VTX 0x4557 -#define I300 0x4559 -#define I750TC 0x455A -#define I450T 0x4641 -#define I550 0x4642 -#define I200 0x4646 -#define SAGE 0x4647 -#define I300C 0x4648 -#define I200C 0x4649 -#define I100 0x464E -#define I770R 0x4651 -#define I550C 0x4652 -#define GEO40 0x4653 -#define VEO40 0x4654 -#define WISDOM4 0x4655 -#define PROPLUS4 0x4656 -#define AMPHOS2 0x4657 -#define AMPHOSAIR2 0x4658 -#define BEACON 0x4742 -#define I470TC 0x4743 -#define I200CV2 0x4749 - #define NORMAL 0 #define GAUGE 1 #define FREEDIVE 2 @@ -143,6 +71,9 @@ static const dc_parser_vtable_t oceanic_atom2_parser_vtable = { sizeof(oceanic_atom2_parser_t), DC_FAMILY_OCEANIC_ATOM2, oceanic_atom2_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ oceanic_atom2_parser_get_datetime, /* datetime */ oceanic_atom2_parser_get_field, /* fields */ oceanic_atom2_parser_samples_foreach, /* samples_foreach */ @@ -178,7 +109,8 @@ oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, unsigned model == I300 || model == I550 || model == I200 || model == I200C || model == I300C || model == GEO40 || - model == VEO40 || model == I470TC) { + model == VEO40 || model == I470TC || + model == GEOAIR) { parser->headersize -= PAGESIZE; } else if (model == VT4 || model == VT41) { parser->headersize += PAGESIZE; @@ -285,6 +217,7 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim case WISDOM4: case I470TC: case I200CV2: + case GEOAIR: datetime->year = ((p[5] & 0xE0) >> 5) + ((p[7] & 0xE0) >> 2) + 2000; datetime->month = (p[3] & 0x0F); datetime->day = ((p[0] & 0x80) >> 3) + ((p[3] & 0xF0) >> 4); @@ -735,7 +668,8 @@ 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 == BEACON) { + parser->model == SAGE || parser->model == BEACON || + parser->model == GEOAIR) { samplesize = PAGESIZE; } @@ -912,7 +846,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I200 || parser->model == I100 || parser->model == I300C || parser->model == I200C || parser->model == GEO40 || parser->model == VEO40 || - parser->model == I470TC || parser->model == I200CV2) { + parser->model == I470TC || parser->model == I200CV2 || + parser->model == GEOAIR) { temperature = data[offset + 3]; } else if (parser->model == OCS || parser->model == TX1) { temperature = data[offset + 1]; @@ -956,7 +891,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ if (have_pressure) { if (parser->model == OC1A || parser->model == OC1B || parser->model == OC1C || parser->model == OCI || - parser->model == I450T || parser->model == I470TC) + parser->model == I450T || parser->model == I470TC || + parser->model == GEOAIR) pressure = (data[offset + 10] + (data[offset + 11] << 8)) & 0x0FFF; else if (parser->model == VT4 || parser->model == VT41|| parser->model == ATOM3 || parser->model == ATOM31 || @@ -991,7 +927,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I200 || parser->model == I100 || parser->model == I300C || parser->model == I200C || parser->model == GEO40 || parser->model == VEO40 || - parser->model == I470TC || parser->model == I200CV2) + parser->model == I470TC || parser->model == I200CV2 || + parser->model == GEOAIR) depth = (data[offset + 4] + (data[offset + 5] << 8)) & 0x0FFF; else if (parser->model == ATOM1) depth = data[offset + 3] * 16; @@ -1047,7 +984,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == I100 || parser->model == I300C || parser->model == I450T || parser->model == I200C || parser->model == GEO40 || parser->model == VEO40 || - parser->model == I470TC || parser->model == I200CV2) { + parser->model == I470TC || parser->model == I200CV2 || + parser->model == GEOAIR) { decostop = (data[offset + 7] & 0xF0) >> 4; decotime = array_uint16_le(data + offset + 6) & 0x0FFF; have_deco = 1; @@ -1072,7 +1010,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ } else if (parser->model == I450T || parser->model == OC1A || parser->model == OC1B || parser->model == OC1C || parser->model == OCI || parser->model == PROPLUSX || - parser->model == I770R || parser->model == I470TC) { + parser->model == I770R || parser->model == I470TC || + parser->model == GEOAIR) { rbt = array_uint16_le(data + offset + 8) & 0x01FF; have_rbt = 1; } else if (parser->model == VISION || parser->model == XPAIR || @@ -1089,7 +1028,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ // Bookmarks unsigned int have_bookmark = 0; if (parser->model == OC1A || parser->model == OC1B || - parser->model == OC1C || parser->model == OCI) { + parser->model == OC1C || parser->model == OCI || + parser->model == GEOAIR) { have_bookmark = data[offset + 12] & 0x80; } if (have_bookmark) { diff --git a/src/oceanic_common.c b/src/oceanic_common.c index c33655b..62f5f74 100644 --- a/src/oceanic_common.c +++ b/src/oceanic_common.c @@ -56,8 +56,9 @@ get_profile_first (const unsigned char data[], const oceanic_common_layout_t *la } unsigned int npages = (layout->memsize - layout->highmem) / pagesize; - - if (npages > 0x2000) { + if (npages > 0x4000) { + value &= 0x7FFF; + } else if (npages > 0x2000) { value &= 0x3FFF; } else if (npages > 0x1000) { value &= 0x1FFF; @@ -86,7 +87,9 @@ get_profile_last (const unsigned char data[], const oceanic_common_layout_t *lay unsigned int npages = (layout->memsize - layout->highmem) / pagesize; - if (npages > 0x2000) { + if (npages > 0x4000) { + value &= 0x7FFF; + } else if (npages > 0x2000) { value &= 0x3FFF; } else if (npages > 0x1000) { value &= 0x1FFF; @@ -129,7 +132,7 @@ oceanic_common_match_pattern (const unsigned char *string, const unsigned char * return 1; } -const oceanic_common_layout_t * +const oceanic_common_version_t * oceanic_common_match (const unsigned char *version, const oceanic_common_version_t patterns[], size_t n, unsigned int *firmware) { for (size_t i = 0; i < n; ++i) { @@ -140,7 +143,7 @@ oceanic_common_match (const unsigned char *version, const oceanic_common_version if (firmware) { *firmware = fw; } - return patterns[i].layout; + return patterns + i; } } @@ -157,6 +160,7 @@ oceanic_common_device_init (oceanic_common_device_t *device) device->firmware = 0; memset (device->version, 0, sizeof (device->version)); memset (device->fingerprint, 0, sizeof (device->fingerprint)); + device->model = 0; device->layout = NULL; device->multipage = 1; } diff --git a/src/oceanic_common.h b/src/oceanic_common.h index 915081e..045dca4 100644 --- a/src/oceanic_common.h +++ b/src/oceanic_common.h @@ -28,6 +28,103 @@ extern "C" { #endif /* __cplusplus */ +// vtpro +#define AERIS500AI 0x4151 +#define VERSAPRO 0x4155 +#define ATMOS2 0x4158 +#define PROPLUS2 0x4159 +#define ATMOSAI 0x4244 +#define VTPRO 0x4245 +#define WISDOM 0x4246 +#define ELITE 0x424F + +// veo250 +#define REACTPRO 0x4247 +#define VEO200 0x424B +#define VEO250 0x424C +#define XP5 0x4251 +#define VEO180 0x4252 +#define XR2 0x4255 +#define INSIGHT 0x425A +#define DG02 0x4352 + +// atom2 +#define ATOM1 0x4250 +#define EPICA 0x4257 +#define VT3 0x4258 +#define T3A 0x4259 +#define ATOM2 0x4342 +#define GEO 0x4344 +#define MANTA 0x4345 +#define XR1NX 0x4346 +#define DATAMASK 0x4347 +#define COMPUMASK 0x4348 +#define F10A 0x434D +#define OC1A 0x434E +#define WISDOM2 0x4350 +#define INSIGHT2 0x4353 +#define REACTPROWHITE 0x4354 +#define ELEMENT2 0x4357 +#define VEO10 0x4358 +#define VEO20 0x4359 +#define VEO30 0x435A +#define ZEN 0x4441 +#define ZENAIR 0x4442 +#define ATMOSAI2 0x4443 +#define PROPLUS21 0x4444 +#define GEO20 0x4446 +#define VT4 0x4447 +#define OC1B 0x4449 +#define VOYAGER2G 0x444B +#define ATOM3 0x444C +#define DG03 0x444D +#define OCS 0x4450 +#define OC1C 0x4451 +#define VT41 0x4452 +#define EPICB 0x4453 +#define T3B 0x4455 +#define ATOM31 0x4456 +#define A300AI 0x4457 +#define WISDOM3 0x4458 +#define A300 0x445A +#define TX1 0x4542 +#define MUNDIAL2 0x4543 +#define AMPHOS 0x4545 +#define AMPHOSAIR 0x4546 +#define PROPLUS3 0x4548 +#define F11A 0x4549 +#define OCI 0x454B +#define A300CS 0x454C +#define TALIS 0x454E +#define MUNDIAL3 0x4550 +#define PROPLUSX 0x4552 +#define F10B 0x4553 +#define F11B 0x4554 +#define XPAIR 0x4555 +#define VISION 0x4556 +#define VTX 0x4557 +#define I300 0x4559 +#define I750TC 0x455A +#define I450T 0x4641 +#define I550 0x4642 +#define I200 0x4646 +#define SAGE 0x4647 +#define I300C 0x4648 +#define I200C 0x4649 +#define I100 0x464E +#define I770R 0x4651 +#define I550C 0x4652 +#define GEO40 0x4653 +#define VEO40 0x4654 +#define WISDOM4 0x4655 +#define PROPLUS4 0x4656 +#define AMPHOS2 0x4657 +#define AMPHOSAIR2 0x4658 +#define BEACON 0x4742 +#define I470TC 0x4743 +#define I200CV2 0x4749 +#define GEOAIR 0x474B + #define PAGESIZE 0x10 #define FPMAXSIZE 0x20 @@ -64,6 +161,7 @@ typedef struct oceanic_common_device_t { unsigned int firmware; unsigned char version[PAGESIZE]; unsigned char fingerprint[FPMAXSIZE]; + unsigned int model; const oceanic_common_layout_t *layout; unsigned int multipage; } oceanic_common_device_t; @@ -77,10 +175,11 @@ typedef struct oceanic_common_device_vtable_t { typedef struct oceanic_common_version_t { unsigned char pattern[PAGESIZE + 1]; unsigned int firmware; + unsigned int model; const oceanic_common_layout_t *layout; } oceanic_common_version_t; -const oceanic_common_layout_t * +const oceanic_common_version_t * oceanic_common_match (const unsigned char *version, const oceanic_common_version_t patterns[], size_t n, unsigned int *firmware); void diff --git a/src/oceanic_veo250.c b/src/oceanic_veo250.c index db323db..58a9ceb 100644 --- a/src/oceanic_veo250.c +++ b/src/oceanic_veo250.c @@ -78,14 +78,14 @@ static const oceanic_common_layout_t oceanic_veo250_layout = { }; static const oceanic_common_version_t versions[] = { - {"GENREACT \0\0 256K", 0, &oceanic_veo250_layout}, - {"VEO 200 R\0\0 256K", 0, &oceanic_veo250_layout}, - {"VEO 250 R\0\0 256K", 0, &oceanic_veo250_layout}, - {"SEEMANN R\0\0 256K", 0, &oceanic_veo250_layout}, - {"VEO 180 R\0\0 256K", 0, &oceanic_veo250_layout}, - {"AERISXR2 \0\0 256K", 0, &oceanic_veo250_layout}, - {"INSIGHT R\0\0 256K", 0, &oceanic_veo250_layout}, - {"HO DGO2 R\0\0 256K", 0, &oceanic_veo250_layout}, + {"GENREACT \0\0 256K", 0, REACTPRO, &oceanic_veo250_layout}, + {"VEO 200 R\0\0 256K", 0, VEO200, &oceanic_veo250_layout}, + {"VEO 250 R\0\0 256K", 0, VEO250, &oceanic_veo250_layout}, + {"SEEMANN R\0\0 256K", 0, XP5, &oceanic_veo250_layout}, + {"VEO 180 R\0\0 256K", 0, VEO180, &oceanic_veo250_layout}, + {"AERISXR2 \0\0 256K", 0, XR2, &oceanic_veo250_layout}, + {"INSIGHT R\0\0 256K", 0, INSIGHT, &oceanic_veo250_layout}, + {"HO DGO2 R\0\0 256K", 0, DG02, &oceanic_veo250_layout}, }; static dc_status_t @@ -316,10 +316,14 @@ oceanic_veo250_device_open (dc_device_t **out, dc_context_t *context, dc_iostrea } // Detect the memory layout. - device->base.layout = OCEANIC_COMMON_MATCH(device->base.version, versions, &device->base.firmware); - if (device->base.layout == NULL) { + const oceanic_common_version_t *version = OCEANIC_COMMON_MATCH(device->base.version, versions, &device->base.firmware); + if (version == NULL) { WARNING (context, "Unsupported device detected!"); device->base.layout = &oceanic_veo250_layout; + device->base.model = 0; + } else { + device->base.layout = version->layout; + device->base.model = version->model; } *out = (dc_device_t*) device; diff --git a/src/oceanic_veo250_parser.c b/src/oceanic_veo250_parser.c index fc7c6ed..90d3383 100644 --- a/src/oceanic_veo250_parser.c +++ b/src/oceanic_veo250_parser.c @@ -31,12 +31,6 @@ #define ISINSTANCE(parser) dc_parser_isinstance((parser), &oceanic_veo250_parser_vtable) -#define REACTPRO 0x4247 -#define VEO200 0x424B -#define VEO250 0x424C -#define INSIGHT 0x425A -#define REACTPROWHITE 0x4354 - typedef struct oceanic_veo250_parser_t oceanic_veo250_parser_t; struct oceanic_veo250_parser_t { @@ -57,6 +51,9 @@ static const dc_parser_vtable_t oceanic_veo250_parser_vtable = { sizeof(oceanic_veo250_parser_t), DC_FAMILY_OCEANIC_VEO250, oceanic_veo250_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ oceanic_veo250_parser_get_datetime, /* datetime */ oceanic_veo250_parser_get_field, /* fields */ oceanic_veo250_parser_samples_foreach, /* samples_foreach */ diff --git a/src/oceanic_vtpro.c b/src/oceanic_vtpro.c index 74b94ec..ef3d6d1 100644 --- a/src/oceanic_vtpro.c +++ b/src/oceanic_vtpro.c @@ -40,8 +40,6 @@ #define NAK 0xA5 #define END 0x51 -#define AERIS500AI 0x4151 - typedef enum oceanic_vtpro_protocol_t { MOD, INTR, @@ -50,7 +48,6 @@ typedef enum oceanic_vtpro_protocol_t { typedef struct oceanic_vtpro_device_t { oceanic_common_device_t base; dc_iostream_t *iostream; - unsigned int model; oceanic_vtpro_protocol_t protocol; } oceanic_vtpro_device_t; @@ -120,14 +117,14 @@ static const oceanic_common_layout_t aeris_500ai_layout = { }; static const oceanic_common_version_t versions[] = { - {"VERSAPRO \0\0 256K", 0, &oceanic_vtpro_layout}, - {"ATMOSTWO \0\0 256K", 0, &oceanic_vtpro_layout}, - {"PROPLUS2 \0\0 256K", 0, &oceanic_vtpro_layout}, - {"ATMOSAIR \0\0 256K", 0, &oceanic_vtpro_layout}, - {"VTPRO r\0\0 256K", 0, &oceanic_vtpro_layout}, - {"ELITE r\0\0 256K", 0, &oceanic_vtpro_layout}, + {"VERSAPRO \0\0 256K", 0, VERSAPRO, &oceanic_vtpro_layout}, + {"ATMOSTWO \0\0 256K", 0, ATMOS2, &oceanic_vtpro_layout}, + {"PROPLUS2 \0\0 256K", 0, PROPLUS2, &oceanic_vtpro_layout}, + {"ATMOSAIR \0\0 256K", 0, ATMOSAI, &oceanic_vtpro_layout}, + {"VTPRO r\0\0 256K", 0, VTPRO, &oceanic_vtpro_layout}, + {"ELITE r\0\0 256K", 0, ELITE, &oceanic_vtpro_layout}, - {"WISDOM r\0\0 256K", 0, &oceanic_wisdom_layout}, + {"WISDOM r\0\0 256K", 0, WISDOM, &oceanic_wisdom_layout}, }; static dc_status_t @@ -381,7 +378,7 @@ oceanic_vtpro_device_logbook (dc_device_t *abstract, dc_event_progress_t *progre { oceanic_vtpro_device_t *device = (oceanic_vtpro_device_t *) abstract; - if (device->model == AERIS500AI) { + if (device->base.model == AERIS500AI) { return oceanic_aeris500ai_device_logbook (abstract, progress, logbook); } else { return oceanic_common_device_logbook (abstract, progress, logbook); @@ -412,7 +409,6 @@ oceanic_vtpro_device_open (dc_device_t **out, dc_context_t *context, dc_iostream // Set the default values. device->iostream = iostream; - device->model = model; if (model == AERIS500AI) { device->protocol = INTR; } else { @@ -492,11 +488,16 @@ oceanic_vtpro_device_open (dc_device_t **out, dc_context_t *context, dc_iostream // Detect the memory layout. if (model == AERIS500AI) { device->base.layout = &aeris_500ai_layout; + device->base.model = AERIS500AI; } else { - device->base.layout = OCEANIC_COMMON_MATCH(device->base.version, versions, &device->base.firmware); - if (device->base.layout == NULL) { + const oceanic_common_version_t * version = OCEANIC_COMMON_MATCH(device->base.version, versions, &device->base.firmware); + if (version == NULL) { WARNING (context, "Unsupported device detected!"); device->base.layout = &oceanic_vtpro_layout; + device->base.model = 0; + } else { + device->base.layout = version->layout; + device->base.model = version->model; } } diff --git a/src/oceanic_vtpro_parser.c b/src/oceanic_vtpro_parser.c index fff7389..9650c49 100644 --- a/src/oceanic_vtpro_parser.c +++ b/src/oceanic_vtpro_parser.c @@ -31,8 +31,6 @@ #define ISINSTANCE(parser) dc_parser_isinstance((parser), &oceanic_vtpro_parser_vtable) -#define AERIS500AI 0x4151 - typedef struct oceanic_vtpro_parser_t oceanic_vtpro_parser_t; struct oceanic_vtpro_parser_t { @@ -53,6 +51,9 @@ static const dc_parser_vtable_t oceanic_vtpro_parser_vtable = { sizeof(oceanic_vtpro_parser_t), DC_FAMILY_OCEANIC_VTPRO, oceanic_vtpro_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ oceanic_vtpro_parser_get_datetime, /* datetime */ oceanic_vtpro_parser_get_field, /* fields */ oceanic_vtpro_parser_samples_foreach, /* samples_foreach */ diff --git a/src/oceans_s1.c b/src/oceans_s1.c index 0a40222..e26979e 100644 --- a/src/oceans_s1.c +++ b/src/oceans_s1.c @@ -1,29 +1,64 @@ -// SPDX-License-Identifier: LGPL-2.1-or-later -// Copyright (C) 2020 Linus Torvalds +/* + * libdivecomputer + * + * Copyright (C) 2020 Linus Torvalds + * Copyright (C) 2022 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 // memcmp, memcpy #include // malloc, free -#include #include #include -#include #include "oceans_s1.h" +#include "oceans_s1_common.h" #include "context-private.h" #include "device-private.h" +#include "platform.h" +#include "checksum.h" #include "array.h" -#define S1_FINGERPRINT 32 +#define SOH 0x01 +#define EOT 0x04 +#define ACK 0x06 +#define NAK 0x15 +#define CAN 0x18 +#define CRC 0x43 + +#define SZ_PACKET 256 +#define SZ_XMODEM 512 + +#define SZ_FINGERPRINT 8 + +typedef struct oceans_s1_dive_t { + struct oceans_s1_dive_t *next; + dc_ticks_t timestamp; + unsigned int number; +} oceans_s1_dive_t; typedef struct oceans_s1_device_t { dc_device_t base; - dc_iostream_t* iostream; - unsigned char fingerprint[S1_FINGERPRINT]; + dc_iostream_t *iostream; + dc_ticks_t timestamp; } oceans_s1_device_t; static dc_status_t oceans_s1_device_set_fingerprint(dc_device_t *abstract, const unsigned char data[], unsigned int size); static dc_status_t oceans_s1_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); -static dc_status_t oceans_s1_device_close(dc_device_t *abstract); static dc_status_t oceans_s1_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime); static const dc_device_vtable_t oceans_s1_device_vtable = { @@ -35,79 +70,172 @@ static const dc_device_vtable_t oceans_s1_device_vtable = { NULL, /* dump */ oceans_s1_device_foreach, /* foreach */ oceans_s1_device_timesync, /* timesync */ - oceans_s1_device_close, /* close */ + NULL, /* close */ }; -static dc_status_t -oceans_s1_write(oceans_s1_device_t *s1, const char *msg) +/* + * Oceans S1 initial sequence (all ASCII text with newlines): + * + * Cmd Reply + * + * utc\n utc>ok 1592912375\r\n + * battery\n battery>ok 59%\r\n + * version\n version>ok 1.1 42a7e564\r\n + * utc 1592912364\n utc>ok\r\n + * units 1\n units>ok\r\n + * dllist\n dllist>xmr\r\n + * + * At this point, the dive computer switches to the XMODEM protocol and + * the sequence is no longer single packets with a full line with newline + * termination. + * + * The actual payload remains ASCII text (note the single space indentation): + * + * divelog v1,10s/sample + * dive 1,0,21,1591372057 + * continue 612,10 + * enddive 3131,496 + * dive 2,0,21,1591372925 + * enddive 1535,277 + * dive 3,0,32,1591463368 + * enddive 1711,4515 + * dive 4,0,32,1591961688 + * continue 300,45 + * continue 391,238 + * continue 420,126 + * continue 236,17 + * enddive 1087,2636 + * endlog + * + * Because the XMODEM protocol uses fixed size packets (512 bytes), the last + * packet is padded with newline characters. + * + * Then it goes back to line-mode: + * + * dlget 4 5\n dlget>xmr\r\n + * + * and the data is again transferred using the XMODEM protocol. The payload is + * also ASCII text (note the space indentation again): + * + * divelog v1,10s/sample + * dive 4,0,32,1591961688 + * 365,13,1 + * 382,13,51456 + * 367,13,16640 + * 381,13,49408 + * 375,13,24576 + * 355,13,16384 + * 346,13,16384 + * 326,14,16384 + * 355,14,16384 + * 394,14,24576 + * 397,14,16384 + * 434,14,49152 + * 479,14,49152 + * 488,14,16384 + * 556,14,57344 + * 616,14,49152 + * 655,14,49152 + * 738,14,49152 + * 800,14,57344 + * 800,14,49408 + * 834,14,16640 + * 871,14,24832 + * 860,14,16640 + * 860,14,16640 + * 815,14,24832 + * 738,14,16640 + * 707,14,16640 + * 653,14,24832 + * 647,13,16640 + * 670,13,16640 + * 653,13,24832 + * ... + * continue 236,17 + * 227,13,57600 + * 238,14,16640 + * 267,14,24832 + * 283,14,16384 + * 272,14,16384 + * 303,14,24576 + * 320,14,16384 + * 318,14,16384 + * 318,14,16384 + * 335,14,24576 + * 332,14,16384 + * 386,14,16384 + * 417,14,24576 + * 244,14,16640 + * 71,14,16640 + * enddive 1087,2636 + * endlog + * + * Where the samples seem to be + * - depth in cm + * - temperature in °C + * - events + * + * Repeat with 'dlget 3 4', 'dlget 2 3', 'dlget 1 2'. + * + * Done. + */ + +/* + * Add a dive to the dive list, sorted with newest dive first + * + * I'm not sure if the dive list is always presented sorted by the + * Oceans S1, but it arrives in the reverse order of what we want + * (we want newest first, it lists them oldest first). So we need + * to switch the order, and we might as well make sure it's sorted + * while doing that. + */ +static void +oceans_s1_list_add (oceans_s1_dive_t **head, oceans_s1_dive_t *dive) { - return dc_iostream_write(s1->iostream, msg, strlen(msg), NULL); -} + if (head == NULL) + return; -static dc_status_t -oceans_s1_read(oceans_s1_device_t *s1, char *buf, size_t bufsz) -{ - size_t nbytes; - dc_status_t status; - - status = dc_iostream_read(s1->iostream, buf, bufsz, &nbytes); - if (status != DC_STATUS_SUCCESS) - return status; - if (nbytes < bufsz) - buf[nbytes] = 0; - return status; -} - -#define BUFSZ 64 - -// Note how we don't rely on the return value of 'vsnprintf(), or on -// NUL termination because it's not portable. -static dc_status_t oceans_s1_printf(oceans_s1_device_t *s1, const char *fmt, ...) -{ - va_list ap; - char buffer[BUFSZ]; - - va_start(ap, fmt); - vsnprintf(buffer, BUFSZ, fmt, ap); - va_end(ap); - buffer[BUFSZ-1] = 0; - - return oceans_s1_write(s1, buffer); -} - -static dc_status_t oceans_s1_expect(oceans_s1_device_t *s1, const char *result) -{ - char buffer[BUFSZ]; - dc_status_t status; - - status = oceans_s1_read(s1, buffer, BUFSZ); - if (status != DC_STATUS_SUCCESS) - return status; - - if (strncmp(buffer, result, strlen(result))) { - ERROR(s1->base.context, "Expected '%s' got '%s'", result, buffer); - return DC_STATUS_IO; + oceans_s1_dive_t *current = *head, *previous = NULL; + while (current) { + if (dive->number >= current->number) + break; + previous = current; + current = current->next; } - return DC_STATUS_SUCCESS; + if (previous) { + dive->next = previous->next; + previous->next = dive; + } else { + dive->next = *head; + *head = dive; + } +} + +static void +oceans_s1_list_free (oceans_s1_dive_t *head) +{ + oceans_s1_dive_t *current = head; + while (current) { + oceans_s1_dive_t *next = current->next; + free (current); + current = next; + } } /* - * The "blob mode" is sends stuff in bigger chunks with some binary - * header and trailer. + * The main data is transferred using the XMODEM-CRC protocol. * - * It seems to be a sequence of packets with 517 bytes of payload: - * three bytes of header, 512 bytes of ASCII data, and two bytes of - * trailer (data checksum?). + * This variant of the XMODEM protocol uses a sequence of 517 byte packets, + * where each packet has a three byte header, 512 bytes of payload data and a + * two byte CRC checksum. The header is a 'SOH' byte, followed by the block + * number (starting at 1), and the inverse block number (255-block). * - * We're supposed to start the sequence with a 'C' packet, and reply - * to each 517-byte packet sequence with a '\006' packet. + * We're supposed to start the sequence with a 'CRC' byte, and reply to each + * packet with a 'ACK' byte. When there is no more data, the device will + * send us a 'EOT' packet, which we'll ack with a final 'ACK' byte. * - * When there is no more data, the S1 will send us a '\004' packet, - * which we'll ack with a final '\006' packet. - * - * The header is '\001' followed by block number (starting at 1), - * followed by (255-block) number. So we can get a sequence of + * So we get a sequence of: * * 01 01 fe <512 bytes> xx xx * 01 02 fd <512 bytes> xx xx @@ -118,534 +246,472 @@ static dc_status_t oceans_s1_expect(oceans_s1_device_t *s1, const char *result) * 01 07 f8 <512 bytes> xx xx * 04 * - * And we should reply with that '\006' packet for each of those - * entries. + * And we should reply with an 'ACK' byte for each of those entries. * * NOTE! The above is not in single BLE packets, although the * sequence blocks always start at a packet boundary. + * + * NOTE! The Oceans Android app uses GATT "Write Commands" (0x53), and not + * GATT "Write Requests" (0x12) for sending the XMODEM single byte commands, + * but this difference does not seem to matter. */ -#define BLOB_BUFSZ 256 -static dc_status_t oceans_s1_get_sequence(oceans_s1_device_t *s1, unsigned char seq, dc_buffer_t *res) +static dc_status_t +oceans_s1_xmodem_packet (oceans_s1_device_t *device, unsigned char seq, unsigned char data[], size_t size) { - unsigned char buffer[BLOB_BUFSZ]; - dc_status_t status; - size_t nbytes; + dc_status_t status = DC_STATUS_SUCCESS; + unsigned char packet[3 + SZ_XMODEM + 2] = {0}; + size_t nbytes = 0; - status = dc_iostream_read(s1->iostream, buffer, BLOB_BUFSZ, &nbytes); - if (status != DC_STATUS_SUCCESS) + if (size < SZ_XMODEM) + return DC_STATUS_INVALIDARGS; + + status = dc_iostream_read (device->iostream, packet, sizeof(packet), &nbytes); + if (status != DC_STATUS_SUCCESS) { + ERROR (device->base.context, "Failed to receive the packet."); return status; - if (!nbytes) - return DC_STATUS_IO; - - if (buffer[0] == 4) - return DC_STATUS_DONE; - - if (nbytes <= 3 || buffer[0] != 1) - return DC_STATUS_IO; - - if (buffer[1] != seq || buffer[2]+seq != 255) - return DC_STATUS_IO; - - nbytes -= 3; - dc_buffer_append(res, buffer+3, nbytes); - while (nbytes < 512) { - size_t got; - - status = dc_iostream_read(s1->iostream, buffer, BLOB_BUFSZ, &got); - if (status != DC_STATUS_SUCCESS) - return status; - - if (!got) - return DC_STATUS_IO; - - // We should check the checksum if it is that? - if (got + nbytes > 512) - got = 512-nbytes; - dc_buffer_append(res, buffer, got); - nbytes += got; } - return DC_STATUS_SUCCESS; -} -static dc_status_t oceans_s1_get_blob(oceans_s1_device_t *s1, const unsigned char **result) -{ - dc_status_t status; - dc_buffer_t *res; - unsigned char *data; - size_t size; - unsigned char seq; + if (nbytes < 1) { + ERROR (device->base.context, "Unexpected packet length (" DC_PRINTF_SIZE ").", nbytes); + return DC_STATUS_PROTOCOL; + } - res = dc_buffer_new(0); - if (!res) - return DC_STATUS_NOMEMORY; + if (packet[0] == EOT) { + return DC_STATUS_DONE; + } - // Tell the Oceans S1 to into some kind of block mode.. - // - // The Oceans Android app uses a "Write Command" rather than - // a "Write Request" for this, but it seems to not matter + if (nbytes < 3) { + ERROR (device->base.context, "Unexpected packet length (" DC_PRINTF_SIZE ").", nbytes); + return DC_STATUS_PROTOCOL; + } - status = dc_iostream_write(s1->iostream, "C", 1, NULL); - if (status != DC_STATUS_SUCCESS) - return status; - - seq = 1; - for (;;) { - status = oceans_s1_get_sequence(s1, seq, res); - if (status == DC_STATUS_DONE) - break; + if (packet[0] != SOH || packet[1] != seq || packet[1] + packet[2] != 0xFF) { + ERROR (device->base.context, "Unexpected packet header."); + return DC_STATUS_PROTOCOL; + } + while (nbytes < sizeof(packet)) { + size_t received = 0; + status = dc_iostream_read (device->iostream, packet + nbytes, sizeof(packet) - nbytes, &received); if (status != DC_STATUS_SUCCESS) { - dc_buffer_free(res); + ERROR (device->base.context, "Failed to receive the packet."); return status; } - // Ack the packet sequence, and go look for the next one - status = dc_iostream_write(s1->iostream, "\006", 1, NULL); - if (status != DC_STATUS_SUCCESS) - return status; - seq++; + nbytes += received; } - - - // Tell the Oceans S1 to exit block mode (??) - status = dc_iostream_write(s1->iostream, "\006", 1, NULL); - if (status != DC_STATUS_SUCCESS) { - dc_buffer_free(res); - return status; + unsigned short crc = array_uint16_be (packet + nbytes - 2); + unsigned short ccrc = checksum_crc16_ccitt (packet + 3, nbytes - 5, 0x0000); + if (crc != ccrc) { + ERROR (device->base.context, "Unexpected answer checksum (%04x %04x).", crc, ccrc); + return DC_STATUS_PROTOCOL; } - size = dc_buffer_get_size(res); + memcpy (data, packet + 3, SZ_XMODEM); - // NUL-terminate before getting buffer - dc_buffer_append(res, "", 1); - data = dc_buffer_get_data(res); - - /* Remove trailing whitespace */ - while (size && isspace(data[size-1])) - data[--size] = 0; - - *result = data; return DC_STATUS_SUCCESS; } -/* - * The Oceans S1 uses the normal UNIX epoch time format: seconds - * since 1-1-1970. In UTC format (so converting local time to UTC). - */ -static dc_status_t oceans_s1_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime) +static dc_status_t +oceans_s1_xmodem_recv (oceans_s1_device_t *device, dc_buffer_t *buffer) { - oceans_s1_device_t *s1 = (oceans_s1_device_t *) abstract; - dc_ticks_t timestamp; - dc_status_t status; + dc_status_t status = DC_STATUS_SUCCESS; + const unsigned char crc = CRC; + const unsigned char ack = ACK; - timestamp = dc_datetime_mktime(datetime); - if (timestamp < 0) - return DC_STATUS_INVALIDARGS; + dc_buffer_clear (buffer); - timestamp += datetime->timezone; - - status = oceans_s1_printf(s1, "utc %lld\n", (long long) timestamp); + // Request XMODEM-CRC mode. + status = dc_iostream_write (device->iostream, &crc, 1, NULL); if (status != DC_STATUS_SUCCESS) return status; - return oceans_s1_expect(s1, "utc>ok"); + unsigned char seq = 1; + while (1) { + // Receive the XMODEM data packet. + unsigned char packet[SZ_XMODEM] = {0}; + status = oceans_s1_xmodem_packet (device, seq, packet, sizeof(packet)); + if (status != DC_STATUS_SUCCESS) { + if (status == DC_STATUS_DONE) + break; + return status; + } + + dc_buffer_append (buffer, packet, sizeof(packet)); + + // Ack the data packet. + status = dc_iostream_write (device->iostream, &ack, 1, NULL); + if (status != DC_STATUS_SUCCESS) + return status; + + seq++; + } + + // Ack the EOT packet. + status = dc_iostream_write (device->iostream, &ack, 1, NULL); + if (status != DC_STATUS_SUCCESS) { + return status; + } + + // Find trailing newline(s). + size_t size = dc_buffer_get_size (buffer); + unsigned char *data = dc_buffer_get_data (buffer); + while (size > 1 && (data[size - 2] == '\r' || data[size - 2] == '\n')) + size--; + + // Remove trailing newline(s). + dc_buffer_slice (buffer, 0, size); + + return DC_STATUS_SUCCESS; } +static dc_status_t DC_ATTR_FORMAT_PRINTF(6, 7) +oceans_s1_transfer (oceans_s1_device_t *device, dc_buffer_t *buffer, char data[], size_t size, const char *cmd, const char *params, ...) +{ + dc_status_t status = DC_STATUS_SUCCESS; + char buf[SZ_PACKET + 1] = {0}; + size_t buflen = 0; + + if (device_is_cancelled (&device->base)) + return DC_STATUS_CANCELLED; + + size_t cmdlen = strlen (cmd); + if (buflen + cmdlen > sizeof(buf) - 1) { + ERROR (device->base.context, "Not enough space for the command string."); + return DC_STATUS_NOMEMORY; + } + + // Copy the command string. + memcpy (buf, cmd, cmdlen); + buflen += cmdlen; + + // Null terminate the buffer. + buf[buflen] = 0; + + if (params) { + if (buflen + 1 > sizeof(buf) - 1) { + ERROR (device->base.context, "Not enough space for the separator."); + return DC_STATUS_NOMEMORY; + } + + // Append a space. + buf[buflen++] = ' '; + + // Null terminate the buffer. + buf[buflen] = 0; + + // Append the arguments. + va_list ap; + va_start (ap, params); + int n = dc_platform_vsnprintf (buf + buflen, sizeof(buf) - buflen, params, ap); + va_end (ap); + if (n < 0) { + ERROR (device->base.context, "Not enough space for the arguments."); + return DC_STATUS_NOMEMORY; + } + + buflen += n; + } + + DEBUG(device->base.context, "cmd: %s", buf); + + if (buflen + 1 > sizeof(buf) - 1) { + ERROR (device->base.context, "Not enough space for the newline."); + return DC_STATUS_NOMEMORY; + } + + // Append a newline. + buf[buflen++] = '\n'; + + // Null terminate the buffer. + buf[buflen] = 0; + + // Send the command. + status = dc_iostream_write (device->iostream, buf, buflen, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (device->base.context, "Failed to send the command."); + return status; + } + + // Receive the response. + size_t nbytes = 0; + status = dc_iostream_read (device->iostream, buf, sizeof(buf) - 1, &nbytes); + if (status != DC_STATUS_SUCCESS) { + ERROR (device->base.context, "Failed to receive the response."); + return status; + } + + // Remove trailing newline(s). + while (nbytes && (buf[nbytes - 1] == '\r' || buf[nbytes - 1] == '\n')) + nbytes--; + + // Null terminate the buffer. + buf[nbytes] = 0; + + DEBUG (device->base.context, "rcv: %s", buf); + + // Verify the response. + if (strncmp (buf, cmd, cmdlen) != 0) { + ERROR (device->base.context, "Received unexpected packet data ('%s').", buf); + return DC_STATUS_PROTOCOL; + } + + // Check the type of response. + // If the response indicates "ok", the payload data is send inline in + // the remainder of the response packet. If the response indicates "xmr", + // the payload data is send separately using the XMODEM protocol. + if (strncmp (buf + cmdlen, ">ok", 3) == 0) { + // Ignore leading whitespace. + const char *line = buf + cmdlen + 3; + while (*line == ' ') + line++; + + // Copy the payload data. + size_t len = nbytes - (line - buf); + if (size) { + if (len + 1 > size) { + ERROR (device->base.context, "Unexpected packet length (" DC_PRINTF_SIZE ").", len); + return DC_STATUS_PROTOCOL; + } + memcpy (data, line, len + 1); + } else { + if (len != 0) { + ERROR (device->base.context, "Unexpected packet length (" DC_PRINTF_SIZE ").", len); + return DC_STATUS_PROTOCOL; + } + } + } else if (strncmp (buf + cmdlen, ">xmr", 4) == 0) { + if (nbytes > cmdlen + 4) { + WARNING (device->base.context, "Packet contains extra data ('%s').", buf + cmdlen + 4); + } + return oceans_s1_xmodem_recv (device, buffer); + } else { + ERROR (device->base.context, "Received unexpected packet data ('%s').", buf); + return DC_STATUS_PROTOCOL; + } + + return status; +} -/* - * Oceans S1 initial sequence (all ASCII text with newlines): - * - * Cmd Reply Comments - * - * "utc" "utc>ok 1592912375" // TZ=UTC date -d"@1592912375" - * "battery" "battery>ok 59%" - * "version" "version>ok 1.1 42a7e564" // Odd hex contant. Device ID? - * "utc 1592912364" "utc>ok" TZ=UTC date -d"@1592912364" - * "units 0" "units>ok" - * "name TGludXM=" "name>ok" // WTF? - * "dllist" "dllist>xmr" - * - * At this point, the sequence changed and is no longer single packets - * with a full line with newline termination. - * - * We send a single 'C' character as a GATT "Write Command" - 0x53 (so - * not "Write Request" - 0x12). - * - * The dive computer replies with GATT packets that contains: - * - * - binary three bytes: "\x01\x01\xfe" - * - * - followed by ASCII text blob (note the single space indentation): - * - * divelog v1,10s/sample - * dive 1,0,21,1591372057 - * continue 612,10 - * enddive 3131,496 - * dive 2,0,21,1591372925 - * enddive 1535,277 - * dive 3,0,32,1591463368 - * enddive 1711,4515 - * dive 4,0,32,1591961688 - * continue 300,45 - * continue 391,238 - * continue 420,126 - * continue 236,17 - * enddive 1087,2636 - * endlog - * - * Followed by a lot of newlines to pad out the packets. - * - * NOTE! The newlines are probably because the way the Nordic Semi UART - * buffering works: it will buffer the packets until they are full, or - * until a newline. - * - * Then some odd data: write a single '\x06' character and get a single - * character reply of '\x04' (!?). Repeat, get a '\x13' byte back. - * - * NOTE! Again these single-byte things are GATT "write command", not - * GATT "write request" things. They may be commands to the UART, not - * data. Some kind of flow control? Or UART buffer control? - * - * Then it seems to go back to line-mode with the usual Write Request: - * - * "dlget 4 5" "dlget>xmr" - * - * which puts us in that "blob" mode again, and we send a singler 'C' - * character again, and now get that same '\x01\x01\xfe' binary data - * followed by ASCII text blob (note the space indentation again): - * - * divelog v1,10s/sample - * dive 4,0,32,1591961688 - * 365,13,1 - * 382,13,51456 - * 367,13,16640 - * 381,13,49408 - * 375,13,24576 - * 355,13,16384 - * 346,13,16384 - * 326,14,16384 - * 355,14,16384 - * 394,14,24576 - * 397,14,16384 - * 434,14,49152 - * 479,14,49152 - * 488,14,16384 - * 556,14,57344 - * 616,14,49152 - * 655,14,49152 - * 738,14,49152 - * 800,14,57344 - * 800,14,49408 - * 834,14,16640 - * 871,14,24832 - * 860,14,16640 - * 860,14,16640 - * 815,14,24832 - * 738,14,16640 - * 707,14,16640 - * 653,14,24832 - * 647,13,16640 - * 670,13,16640 - * 653,13,24832 - * ... - * continue 236,17 - * 227,13,57600 - * 238,14,16640 - * 267,14,24832 - * 283,14,16384 - * 272,14,16384 - * 303,14,24576 - * 320,14,16384 - * 318,14,16384 - * 318,14,16384 - * 335,14,24576 - * 332,14,16384 - * 386,14,16384 - * 417,14,24576 - * 244,14,16640 - * 71,14,16640 - * enddive 1087,2636 - * endlog - * - * Where the samples seem to be - * - 'depth in cm' - * - 'temperature in °C' (??) - * - 'hex value flags' (??) - * - * Repeat with 'dlget 3 4', 'dlget 2 3', 'dlget 1 2'. - * - * Done. - */ dc_status_t oceans_s1_device_open(dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) { - char buffer[128]; dc_status_t status = DC_STATUS_SUCCESS; - oceans_s1_device_t *s1 = NULL; + oceans_s1_device_t *device = NULL; if (out == NULL) return DC_STATUS_INVALIDARGS; // Allocate memory. - s1 = (oceans_s1_device_t*)dc_device_allocate(context, &oceans_s1_device_vtable); - if (s1 == NULL) { + device = (oceans_s1_device_t *) dc_device_allocate (context, &oceans_s1_device_vtable); + if (device == NULL) { ERROR(context, "Failed to allocate memory."); return DC_STATUS_NOMEMORY; } // Set the default values. - s1->iostream = iostream; - memset(s1->fingerprint, 0, sizeof(s1->fingerprint)); + device->iostream = iostream; + device->timestamp = 0; - *out = (dc_device_t*)s1; + // Set the timeout for receiving data (4000 ms). + status = dc_iostream_set_timeout (device->iostream, 4000); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the timeout."); + goto error_free; + } - // Do minimal verification that we can talk to it - // as part of the open. - status = oceans_s1_write(s1, "utc\n"); - if (status != DC_STATUS_SUCCESS) - return status; + dc_iostream_purge (device->iostream, DC_DIRECTION_ALL); - status = oceans_s1_read(s1, buffer, sizeof(buffer)); - if (status != DC_STATUS_SUCCESS) - return status; - if (memcmp(buffer, "utc>ok", 6)) - return DC_STATUS_IO; + *out = (dc_device_t *) device; return DC_STATUS_SUCCESS; + +error_free: + dc_device_deallocate ((dc_device_t *) device); + return status; } static dc_status_t -oceans_s1_device_close(dc_device_t *abstract) +oceans_s1_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size) { - dc_status_t status = DC_STATUS_SUCCESS; - oceans_s1_device_t *s1 = (oceans_s1_device_t*)abstract; + oceans_s1_device_t *device = (oceans_s1_device_t *) abstract; - // Fill in - - return DC_STATUS_SUCCESS; -} - -static dc_status_t -oceans_s1_device_set_fingerprint(dc_device_t *abstract, const unsigned char data[], unsigned int size) -{ - oceans_s1_device_t *s1 = (oceans_s1_device_t*)abstract; - - if (size > sizeof(s1->fingerprint)) + if (size && size != SZ_FINGERPRINT) return DC_STATUS_INVALIDARGS; - memset(s1->fingerprint, 0, sizeof(s1->fingerprint)); - memcpy(s1->fingerprint, data, size); + if (size) + device->timestamp = array_uint64_be (data); + else + device->timestamp = 0; return DC_STATUS_SUCCESS; } static dc_status_t -get_dive_list(oceans_s1_device_t *s1, const unsigned char **list) +oceans_s1_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) { - dc_status_t status; - - status = oceans_s1_write(s1, "dllist\n"); - if (status != DC_STATUS_SUCCESS) - return status; - - status = oceans_s1_expect(s1, "dllist>xmr"); - if (status != DC_STATUS_SUCCESS) - return status; - - return oceans_s1_get_blob(s1, list); -} - -static dc_status_t -get_one_dive(oceans_s1_device_t *s1, int nr, const unsigned char **dive) -{ - dc_status_t status; - - status = oceans_s1_printf(s1, "dlget %d %d\n", nr, nr+1); - if (status != DC_STATUS_SUCCESS) - return status; - - status = oceans_s1_expect(s1, "dlget>xmr"); - if (status != DC_STATUS_SUCCESS) - return status; - - return oceans_s1_get_blob(s1, dive); -} - -static const unsigned char *get_string_line(const unsigned char **blob) -{ - const unsigned char *in = *blob; - const unsigned char *line; - unsigned char c; - - *blob = NULL; - if (!in) - return NULL; - - while (isspace(*in)) - in++; - - if (!*in) - return NULL; - - line = in; - while ((c = *in) != 0) { - if (c == '\r' || c == '\n') - break; - in++; - } - *blob = in; - return line; -} - -// The 'unknown' field is probably the dive mode -// 'date' is seconds since UNIX epoch (the usual "local time as GMT") -// depth and duration are in cm and seconds -// The fingerprint is just the 'dive' line padded with NUL characters -struct s1_dive { - int nr, unknown, o2; - long long date; - unsigned maxdepth, duration; - unsigned char fingerprint[S1_FINGERPRINT]; - struct s1_dive *next; -}; - -/* - * React to the "dive x,y,z,date" line. - * - * Allocate the dive. - */ -static struct s1_dive * -s1_alloc_dive(const unsigned char *line, size_t len) -{ - struct s1_dive *dive; - int nr, unknown, o2; - long long date; - - if (sscanf(line, "dive %d,%d,%d,%lld", &nr, &unknown, &o2, &date) != 4) - return NULL; - - dive = malloc(sizeof(*dive)); - if (dive) { - memset(dive, 0, sizeof(*dive)); - - dive->nr = nr; - dive->unknown = unknown; - dive->o2 = o2; - dive->date = date; - dive->next = NULL; - - if (len >= S1_FINGERPRINT) - len = S1_FINGERPRINT-1; - memcpy(dive->fingerprint, line, len); - } - return dive; -} - -/* - * React to the "enddive x,y" line - * - * Add a dive to the dive list, sorted with newest dive first - * - * I'm not sure if the dive list is always presented sorted by the - * Oceans S1, but it arrives in the reverse order of what we want - * (we want newest first, it lists them oldest first). So we need - * to switch the order, and we might as well make sure it's sorted - * while doing that. - * - * If it always comes sorted from the Oceans S1, the while () loop - * here won't ever actually loop, so there's no real cost to this - * (not that CPU time here matters). - */ -static int -s1_add_dive(struct s1_dive *dive, struct s1_dive **list, const unsigned char *line, size_t len) -{ - unsigned maxdepth, duration; - struct s1_dive *next; - - if (!dive) - return 0; - - if (sscanf(line, "enddive %u,%u", &maxdepth, &duration) != 2) - return 0; - - dive->maxdepth = maxdepth; - dive->duration = duration; - while ((next = *list) != NULL) { - if (dive->nr >= next->nr) - break; - list = &next->next; - } - dive->next = next; - *list = dive; - return 1; -} - -static dc_status_t -oceans_s1_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) -{ - int nr; - struct s1_dive *divelist, *current_dive; - const unsigned char *blob, *line; dc_status_t status = DC_STATUS_SUCCESS; - oceans_s1_device_t *s1 = (oceans_s1_device_t*)abstract; + oceans_s1_device_t *device = (oceans_s1_device_t *) abstract; dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; - device_event_emit(abstract, DC_EVENT_PROGRESS, &progress); + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - status = get_dive_list(s1, &blob); - if (status != DC_STATUS_SUCCESS) + char version[SZ_PACKET] = {0}; + status = oceans_s1_transfer (device, NULL, version, sizeof(version), "version", NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the version."); return status; + } - nr = 0; - divelist = NULL; - current_dive = NULL; - while ((line = get_string_line(&blob)) != NULL) { - int linelen = blob - line; - const unsigned char *dive; + unsigned int major = 0, minor = 0, unknown = 0; + if (sscanf (version, "%u.%u %x", &major, &minor, &unknown) != 3) { + ERROR (abstract->context, "Failed to parse the version response."); + return DC_STATUS_PROTOCOL; + } - /* We only care about 'dive' and 'enddive' lines */ - if (linelen < 8) + // Emit a device info event. + dc_event_devinfo_t devinfo; + devinfo.model = 0; + devinfo.firmware = major << 16 | minor; + devinfo.serial = 0; + device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); + + dc_buffer_t *buffer = dc_buffer_new (4096); + if (buffer == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_exit; + } + + status = oceans_s1_transfer (device, buffer, NULL, 0, "dllist", NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to download the dive list."); + goto error_free_buffer; + } + + const unsigned char *data = dc_buffer_get_data (buffer); + size_t size = dc_buffer_get_size (buffer); + + oceans_s1_dive_t *logbook = NULL, *dive = NULL; + unsigned int ndives = 0; + + char *ptr = NULL; + size_t len = 0; + int n = 0; + while ((n = oceans_s1_getline (&ptr, &len, &data, &size)) != -1) { + // Ignore empty lines. + if (n == 0) continue; - if (!memcmp(line, "dive ", 5)) { - current_dive = s1_alloc_dive(line, linelen); - continue; + // Ignore leading whitespace. + const char *line = ptr; + while (*line == ' ') + line++; + + if (strncmp (line, "divelog", 7) == 0 || + strncmp (line, "endlog", 6) == 0 || + strncmp (line, "continue", 8) == 0) { + // Nothing to do. + } else if (strncmp (line, "dive", 4) == 0) { + if (dive != NULL) { + ERROR (abstract->context, "Skipping dive without 'enddive' line."); + free (dive); + dive = NULL; + } + + unsigned int number = 0, divemode = 0, o2 = 0; + dc_ticks_t timestamp = 0; + if (sscanf (line, "dive %u,%u,%u," DC_FORMAT_INT64, &number, &divemode, &o2, ×tamp) != 4) { + ERROR (abstract->context, "Failed to parse the line '%s'.", line); + status = DC_STATUS_DATAFORMAT; + goto error_free_list; + } + + dive = (oceans_s1_dive_t *) malloc (sizeof (oceans_s1_dive_t)); + if (dive == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_free_list; + } + + dive->next = NULL; + dive->timestamp = timestamp; + dive->number = number; + } else if (strncmp (line, "enddive", 7) == 0) { + if (dive) { + if (dive->timestamp > device->timestamp) { + oceans_s1_list_add (&logbook, dive); + ndives++; + } else { + free (dive); + } + dive = NULL; + } else { + WARNING (abstract->context, "Unexpected line '%s'.", line); + } + } else { + WARNING (abstract->context, "Unexpected line '%s'.", line); + } + } + + if (dive != NULL) { + WARNING (abstract->context, "Skipping dive without 'enddive' line."); + free (dive); + dive = NULL; + } + + progress.current = 1; + progress.maximum = 1 + ndives; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + for (dive = logbook; dive; dive = dive->next) { + status = oceans_s1_transfer (device, buffer, NULL, 0, "dlget", "%u %u", dive->number, dive->number + 1); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to download the dive."); + goto error_free_list; } - if (memcmp(line, "enddive ", 8)) - continue; - - if (s1_add_dive(current_dive, &divelist, line, linelen)) - nr++; - current_dive = NULL; - } - if (!nr) - return DC_STATUS_SUCCESS; - - progress.current = 0; - progress.maximum = nr; - device_event_emit(abstract, DC_EVENT_PROGRESS, &progress); - - for (current_dive = divelist; current_dive; current_dive = current_dive->next) { - const unsigned char *blob; - - if (!memcmp(current_dive->fingerprint, s1->fingerprint, S1_FINGERPRINT)) - break; - - if (device_is_cancelled(&s1->base)) - break; - - status = get_one_dive(s1, current_dive->nr, &blob); - if (status != DC_STATUS_SUCCESS) - return status; - progress.current++; - device_event_emit(abstract, DC_EVENT_PROGRESS, &progress); + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - if (callback && !callback(blob, strlen(blob), current_dive->fingerprint, S1_FINGERPRINT, userdata)) + unsigned char fingerprint[SZ_FINGERPRINT] = {0}; + array_uint64_be_set (fingerprint, dive->timestamp); + + if (callback && !callback (dc_buffer_get_data (buffer), dc_buffer_get_size (buffer), fingerprint, sizeof(fingerprint), userdata)) break; } +error_free_list: + oceans_s1_list_free (logbook); + free (ptr); +error_free_buffer: + dc_buffer_free (buffer); +error_exit: + return status; +} + +static dc_status_t +oceans_s1_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime) +{ + dc_status_t status = DC_STATUS_SUCCESS; + oceans_s1_device_t *device = (oceans_s1_device_t *) abstract; + + // Ignore the timezone offset. + dc_datetime_t dt = *datetime; + dt.timezone = DC_TIMEZONE_NONE; + + dc_ticks_t timestamp = dc_datetime_mktime (&dt); + if (timestamp < 0) { + ERROR (abstract->context, "Invalid date/time value specified."); + return DC_STATUS_INVALIDARGS; + } + + status = oceans_s1_transfer (device, NULL, NULL, 0, "utc", DC_FORMAT_INT64, timestamp); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to set the date/time."); + return status; + } + return status; } diff --git a/src/oceans_s1.h b/src/oceans_s1.h index 483b144..825d309 100644 --- a/src/oceans_s1.h +++ b/src/oceans_s1.h @@ -1,5 +1,24 @@ -// SPDX-License-Identifier: LGPL-2.1-or-later -// Copyright (C) 2020 Linus Torvalds +/* + * libdivecomputer + * + * Copyright (C) 2020 Linus Torvalds + * Copyright (C) 2022 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 OCEANS_S1_H #define OCEANS_S1_H diff --git a/src/oceans_s1_common.c b/src/oceans_s1_common.c new file mode 100644 index 0000000..0522891 --- /dev/null +++ b/src/oceans_s1_common.c @@ -0,0 +1,68 @@ +/* + * libdivecomputer + * + * Copyright (C) 2022 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 // memcmp, memcpy +#include // malloc, free +#include + +#include "oceans_s1_common.h" + +int +oceans_s1_getline (char **line, size_t *linelen, const unsigned char **data, size_t *size) +{ + if (line == NULL || linelen == NULL || data == NULL || size == NULL) + return -1; + + if (*size == 0) + return -1; + + // Find the end of the line. + unsigned int strip = 0; + const unsigned char *p = *data, *end = p + *size; + while (p != end) { + unsigned char c = *p++; + if (c == '\r' || c == '\n') { + strip = 1; + break; + } + } + + // Get the length of the line. + size_t len = p - *data; + + // Resize the buffer (if necessary). + if (*line == NULL || len + 1 > *linelen) { + char *buf = (char *) malloc (len + 1); + if (buf == NULL) + return -1; + free (*line); + *line = buf; + *linelen = len + 1; + } + + // Copy the data. + memcpy (*line, *data, len - strip); + (*line)[len - strip] = 0; + *data += len; + *size -= len; + + return len - strip; +} diff --git a/src/oceans_s1_common.h b/src/oceans_s1_common.h new file mode 100644 index 0000000..37a530c --- /dev/null +++ b/src/oceans_s1_common.h @@ -0,0 +1,35 @@ +/* + * libdivecomputer + * + * Copyright (C) 2022 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 OCEANS_S1_COMMON_H +#define OCEANS_S1_COMMON_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +int +oceans_s1_getline (char **line, size_t *linelen, const unsigned char **data, size_t *size); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* OCEANS_S1_COMMON_H */ diff --git a/src/oceans_s1_parser.c b/src/oceans_s1_parser.c index b2e012f..8b4cfbd 100644 --- a/src/oceans_s1_parser.c +++ b/src/oceans_s1_parser.c @@ -1,26 +1,61 @@ -// SPDX-License-Identifier: LGPL-2.1-or-later -// Copyright (C) 2020 Linus Torvalds +/* + * libdivecomputer + * + * Copyright (C) 2020 Linus Torvalds + * Copyright (C) 2022 Jef Driesen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ #include #include -#include #include -#include #include "oceans_s1.h" +#include "oceans_s1_common.h" #include "context-private.h" #include "parser-private.h" -#include "field-cache.h" +#include "platform.h" #include "array.h" +#define SCUBA 0 +#define APNEA 1 + +#define EVENT_DIVE_STARTED 0x0001 +#define EVENT_DIVE_ENDED 0x0002 +#define EVENT_DIVE_RESUMED 0x0004 +#define EVENT_PING_SENT 0x0008 +#define EVENT_PING_RECEIVED 0x0010 +#define EVENT_DECO_STOP 0x0020 +#define EVENT_SAFETY_STOP 0x0040 +#define EVENT_BATTERY_LOW 0x0080 +#define EVENT_BACKLIGHT_ON 0x0100 + typedef struct oceans_s1_parser_t oceans_s1_parser_t; struct oceans_s1_parser_t { dc_parser_t base; - int divenr; - unsigned int maxdepth, duration; - long long date; - struct dc_field_cache cache; + // Cached fields. + dc_ticks_t timestamp; + unsigned int cached; + unsigned int number; + unsigned int divemode; + unsigned int oxygen; + unsigned int maxdepth; + unsigned int divetime; }; static dc_status_t oceans_s1_parser_set_data(dc_parser_t *abstract, const unsigned char *data, unsigned int size); @@ -32,6 +67,9 @@ static const dc_parser_vtable_t oceans_s1_parser_vtable = { sizeof(oceans_s1_parser_t), DC_FAMILY_OCEANS_S1, oceans_s1_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ oceans_s1_parser_get_datetime, /* datetime */ oceans_s1_parser_get_field, /* fields */ oceans_s1_parser_samples_foreach, /* samples_foreach */ @@ -39,7 +77,7 @@ static const dc_parser_vtable_t oceans_s1_parser_vtable = { }; dc_status_t -oceans_s1_parser_create(dc_parser_t **out, dc_context_t *context) +oceans_s1_parser_create (dc_parser_t **out, dc_context_t *context) { oceans_s1_parser_t *parser = NULL; @@ -47,160 +85,233 @@ oceans_s1_parser_create(dc_parser_t **out, dc_context_t *context) return DC_STATUS_INVALIDARGS; // Allocate memory. - parser = (oceans_s1_parser_t*)dc_parser_allocate(context, &oceans_s1_parser_vtable); + parser = (oceans_s1_parser_t *) dc_parser_allocate (context, &oceans_s1_parser_vtable); if (parser == NULL) { - ERROR(context, "Failed to allocate memory."); + ERROR (context, "Failed to allocate memory."); return DC_STATUS_NOMEMORY; } - *out = (dc_parser_t*)parser; + // Set the default values. + parser->cached = 0; + parser->timestamp = 0; + parser->number = 0; + parser->divemode = 0; + parser->oxygen = 0; + parser->maxdepth = 0; + parser->divetime = 0; + + *out = (dc_parser_t *) parser; return DC_STATUS_SUCCESS; } -static const unsigned char *get_string_line(const unsigned char *in, const unsigned char **next) +static dc_status_t +oceans_s1_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) { - const unsigned char *line; - unsigned char c; + oceans_s1_parser_t *parser = (oceans_s1_parser_t *) abstract; - if (!in) { - *next = NULL; - return NULL; - } + // Reset the cache. + parser->cached = 0; + parser->timestamp = 0; + parser->number = 0; + parser->divemode = 0; + parser->oxygen = 0; + parser->maxdepth = 0; + parser->divetime = 0; - while (isspace(*in)) - in++; - - if (!*in) { - *next = NULL; - return NULL; - } - - line = in; - while ((c = *in) != 0) { - if (c == '\r' || c == '\n') - break; - in++; - } - *next = in; - return line; + return DC_STATUS_SUCCESS; } static dc_status_t -oceans_s1_parse_dive(struct oceans_s1_parser_t *s1, const unsigned char *data, dc_sample_callback_t callback, void *userdata) +oceans_s1_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) { - const unsigned char *line; - unsigned int sample_interval = 10; - unsigned int sample_time = 0; + oceans_s1_parser_t *parser = (oceans_s1_parser_t *) abstract; - memset(&s1->cache, 0, sizeof(s1->cache)); + if (!parser->cached) { + dc_status_t status = oceans_s1_parser_samples_foreach (abstract, NULL, NULL); + if (status != DC_STATUS_SUCCESS) + return status; + } - while ((line = get_string_line(data, &data)) != NULL) { - dc_sample_value_t sample = {0}; - int depth = 0, temp = 0, flags = 0; + if (!dc_datetime_gmtime (datetime, parser->timestamp)) + return DC_STATUS_DATAFORMAT; - if (!strncmp(line, "divelog ", 8)) { - sscanf(line, "divelog v1,%us/sample", &sample_interval); - continue; - } - if (!strncmp(line, "dive ", 5)) { - int nr, unknown, o2; - long long date; + datetime->timezone = DC_TIMEZONE_NONE; - sscanf(line, "dive %d,%d,%d,%lld", &nr, &unknown, &o2, &date); - s1->divenr = nr; - s1->date = date; - // I think "unknown" is dive mode - if (o2) { - dc_gasmix_t mix = { 0 }; - mix.oxygen = o2 / 100.0; - DC_ASSIGN_FIELD(s1->cache, GASMIX_COUNT, 1); - DC_ASSIGN_IDX(s1->cache, GASMIX, 0, mix); + return DC_STATUS_SUCCESS; +} + +static dc_status_t +oceans_s1_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) +{ + oceans_s1_parser_t *parser = (oceans_s1_parser_t *) abstract; + + if (!parser->cached) { + dc_status_t status = oceans_s1_parser_samples_foreach (abstract, NULL, NULL); + if (status != DC_STATUS_SUCCESS) + return status; + } + + dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + + if (value) { + switch (type) { + case DC_FIELD_DIVETIME: + *((unsigned int *) value) = parser->divetime; + break; + case DC_FIELD_MAXDEPTH: + *((double *) value) = parser->maxdepth / 100.0; + break; + case DC_FIELD_GASMIX_COUNT: + *((unsigned int *) value) = parser->divemode == SCUBA; + break; + case DC_FIELD_GASMIX: + gasmix->helium = 0.0; + gasmix->oxygen = parser->oxygen / 100.0; + gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; + break; + case DC_FIELD_DIVEMODE: + switch (parser->divemode) { + case SCUBA: + *((dc_divemode_t *) value) = DC_DIVEMODE_OC; + break; + case APNEA: + *((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE; + break; + default: + return DC_STATUS_DATAFORMAT; } - continue; + break; + default: + return DC_STATUS_UNSUPPORTED; } - if (!strncmp(line, "continue ", 9)) { - int depth = 0, seconds = 0; - sscanf(line, "continue %d,%d", &depth, &seconds); + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +oceans_s1_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +{ + dc_status_t status = DC_STATUS_SUCCESS; + oceans_s1_parser_t *parser = (oceans_s1_parser_t *) abstract; + const unsigned char *data = abstract->data; + size_t size = abstract->size; + + dc_ticks_t timestamp = 0; + unsigned int number = 0, divemode = 0, oxygen = 0; + unsigned int maxdepth = 0, divetime = 0; + unsigned int interval = 10; + unsigned int time = 0; + + char *ptr = NULL; + size_t len = 0; + int n = 0; + while ((n = oceans_s1_getline (&ptr, &len, &data, &size)) != -1) { + dc_sample_value_t sample = {0}; + + // Ignore empty lines. + if (n == 0) + continue; + + // Ignore leading whitespace. + const char *line = ptr; + while (*line == ' ') + line++; + + if (strncmp (line, "divelog", 7) == 0) { + if (sscanf (line, "divelog v1,%us/sample", &interval) != 1) { + ERROR (parser->base.context, "Failed to parse the line '%s'.", line); + status = DC_STATUS_DATAFORMAT; + goto error_free; + } + if (interval == 0) { + ERROR (parser->base.context, "Invalid sample interval (%u).", interval); + status = DC_STATUS_DATAFORMAT; + goto error_free; + } + } else if (strncmp (line, "dive", 4) == 0) { + if (sscanf (line, "dive %u,%u,%u," DC_FORMAT_INT64, &number, &divemode, &oxygen, ×tamp) != 4) { + ERROR (parser->base.context, "Failed to parse the line '%s'.", line); + status = DC_STATUS_DATAFORMAT; + goto error_free; + } + } else if (strncmp (line, "continue", 8) == 0) { + unsigned int depth = 0, seconds = 0; + if (sscanf (line, "continue %u,%u", &depth, &seconds) != 2) { + ERROR (parser->base.context, "Failed to parse the line '%s'.", line); + status = DC_STATUS_DATAFORMAT; + goto error_free; + } // Create surface samples for the surface time, - // and then a depth sample at the stated depth - if (callback) { - if (seconds >= sample_interval*2) { - dc_sample_value_t sample = {0}; - sample.time = sample_time + sample_interval; - callback(DC_SAMPLE_TIME, sample, userdata); - sample.depth = 0; - callback(DC_SAMPLE_DEPTH, sample, userdata); + // and then a depth sample at the stated depth. + unsigned int nsamples = seconds / interval; + for (unsigned int i = 0; i < nsamples; ++i) { + time += interval; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); - sample.time = sample_time + seconds - sample_interval; - callback(DC_SAMPLE_TIME, sample, userdata); - sample.depth = 0; - callback(DC_SAMPLE_DEPTH, sample, userdata); - } - sample.time = sample_time + seconds; - callback(DC_SAMPLE_TIME, sample, userdata); - sample.depth = depth / 100.0; - callback(DC_SAMPLE_DEPTH, sample, userdata); + sample.depth = 0; + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); } - sample_time += seconds; - continue; - } - if (!strncmp(line, "enddive ", 8)) { - int maxdepth = 0, duration = 0; - sscanf(line, "enddive %d,%d", &maxdepth, &duration); - DC_ASSIGN_FIELD(s1->cache, MAXDEPTH, maxdepth / 100.0); - DC_ASSIGN_FIELD(s1->cache, DIVETIME, duration); - s1->maxdepth = maxdepth; - s1->duration = duration; - continue; - } - if (sscanf(line, "%d,%d,%d", &depth, &temp, &flags) != 3) - continue; - sample_time += sample_interval; - if (callback) { - dc_sample_value_t sample = {0}; - sample.time = sample_time; - callback(DC_SAMPLE_TIME, sample, userdata); + time += interval; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + sample.depth = depth / 100.0; - callback(DC_SAMPLE_DEPTH, sample, userdata); - sample.temperature = temp; - callback (DC_SAMPLE_TEMPERATURE, sample, userdata); + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + } else if (strncmp(line, "enddive", 7) == 0) { + if (sscanf(line, "enddive %u,%u", &maxdepth, &divetime) != 2) { + ERROR (parser->base.context, "Failed to parse the line '%s'.", line); + status = DC_STATUS_DATAFORMAT; + goto error_free; + } + } else if (strncmp (line, "endlog", 6) == 0) { + // Nothing to do. + } else { + unsigned int depth = 0, events = 0; + int temperature = 0; + if (sscanf (line, "%u,%d,%u", &depth, &temperature, &events) != 3) { + ERROR (parser->base.context, "Failed to parse the line '%s'.", line); + status = DC_STATUS_DATAFORMAT; + goto error_free; + } + + time += interval; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + + sample.depth = depth / 100.0; + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + + sample.temperature = temperature; + if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); + + if (events & EVENT_DECO_STOP) { + sample.deco.type = DC_DECO_DECOSTOP; + } else if (events & EVENT_SAFETY_STOP) { + sample.deco.type = DC_DECO_SAFETYSTOP; + } else { + sample.deco.type = DC_DECO_NDL; + } + sample.deco.depth = 0.0; + sample.deco.time = 0; + if (callback) callback (DC_SAMPLE_DECO, sample, userdata); } } - return DC_STATUS_SUCCESS; -} - -static dc_status_t -oceans_s1_parser_set_data(dc_parser_t *abstract, const unsigned char *data, unsigned int size) -{ - struct oceans_s1_parser_t *s1 = (struct oceans_s1_parser_t *)abstract; - - return oceans_s1_parse_dive(s1, data, NULL, NULL); -} - -static dc_status_t -oceans_s1_parser_get_datetime(dc_parser_t *abstract, dc_datetime_t *datetime) -{ - oceans_s1_parser_t *s1 = (oceans_s1_parser_t *)abstract; - - dc_datetime_gmtime(datetime, s1->date); - return DC_STATUS_SUCCESS; -} - -static dc_status_t -oceans_s1_parser_get_field(dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) -{ - oceans_s1_parser_t *s1 = (oceans_s1_parser_t *)abstract; - - return dc_field_get(&s1->cache, type, flags, value); -} - -static dc_status_t -oceans_s1_parser_samples_foreach(dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) -{ - struct oceans_s1_parser_t *s1 = (struct oceans_s1_parser_t *)abstract; - - return oceans_s1_parse_dive(s1, s1->base.data, callback, userdata); + + // Cache the data for later use. + parser->timestamp = timestamp; + parser->number = number; + parser->divemode = divemode; + parser->oxygen = oxygen; + parser->maxdepth = maxdepth; + parser->divetime = divetime; + parser->cached = 1; + +error_free: + free (ptr); + return status; } diff --git a/src/parser-private.h b/src/parser-private.h index eed9d51..af1a76e 100644 --- a/src/parser-private.h +++ b/src/parser-private.h @@ -25,6 +25,10 @@ #include #include +#define DEF_DENSITY_FRESH 1000.0 +#define DEF_DENSITY_SALT 1025.0 +#define DEF_ATMOSPHERIC ATM + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ @@ -48,6 +52,12 @@ struct dc_parser_vtable_t { dc_status_t (*set_data) (dc_parser_t *parser, const unsigned char *data, unsigned int size); + dc_status_t (*set_clock) (dc_parser_t *parser, unsigned int devtime, dc_ticks_t systime); + + dc_status_t (*set_atmospheric) (dc_parser_t *parser, double atmospheric); + + dc_status_t (*set_density) (dc_parser_t *parser, double density); + dc_status_t (*datetime) (dc_parser_t *parser, dc_datetime_t *datetime); dc_status_t (*field) (dc_parser_t *parser, dc_field_type_t type, unsigned int flags, void *value); diff --git a/src/parser.c b/src/parser.c index ff31e1b..68ed4f3 100644 --- a/src/parser.c +++ b/src/parser.c @@ -35,7 +35,6 @@ #include "uwatec_memomouse.h" #include "uwatec_smart.h" #include "oceanic_atom2.h" -#include "oceanic_atom2.h" #include "oceanic_veo250.h" #include "oceanic_vtpro.h" #include "mares_darwin.h" @@ -62,11 +61,11 @@ #include "sporasub_sp2.h" #include "deepsix_excursion.h" #include "seac_screen.h" +#include "deepblu_cosmiq.h" +#include "oceans_s1.h" // Not merged upstream yet #include "garmin.h" -#include "deepblu.h" -#include "oceans_s1.h" #include "context-private.h" #include "parser-private.h" @@ -197,6 +196,12 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa case DC_FAMILY_SEAC_SCREEN: rc = seac_screen_parser_create (&parser, context); break; + case DC_FAMILY_DEEPBLU_COSMIQ: + rc = deepblu_cosmiq_parser_create (&parser, context); + break; + case DC_FAMILY_OCEANS_S1: + rc = oceans_s1_parser_create (&parser, context); + break; default: return DC_STATUS_INVALIDARGS; @@ -204,12 +209,6 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa case DC_FAMILY_GARMIN: rc = garmin_parser_create (&parser, context); break; - case DC_FAMILY_DEEPBLU: - rc = deepblu_parser_create (&parser, context); - break; - case DC_FAMILY_OCEANS_S1: - rc = oceans_s1_parser_create(&parser, context); - break; } *out = parser; @@ -290,6 +289,45 @@ dc_parser_get_type (dc_parser_t *parser) } +dc_status_t +dc_parser_set_clock (dc_parser_t *parser, unsigned int devtime, dc_ticks_t systime) +{ + if (parser == NULL) + return DC_STATUS_UNSUPPORTED; + + if (parser->vtable->set_clock == NULL) + return DC_STATUS_UNSUPPORTED; + + return parser->vtable->set_clock (parser, devtime, systime); +} + + +dc_status_t +dc_parser_set_atmospheric (dc_parser_t *parser, double atmospheric) +{ + if (parser == NULL) + return DC_STATUS_UNSUPPORTED; + + if (parser->vtable->set_atmospheric == NULL) + return DC_STATUS_UNSUPPORTED; + + return parser->vtable->set_atmospheric (parser, atmospheric); +} + + +dc_status_t +dc_parser_set_density (dc_parser_t *parser, double density) +{ + if (parser == NULL) + return DC_STATUS_UNSUPPORTED; + + if (parser->vtable->set_density == NULL) + return DC_STATUS_UNSUPPORTED; + + return parser->vtable->set_density (parser, density); +} + + dc_status_t dc_parser_set_data (dc_parser_t *parser, const unsigned char *data, unsigned int size) { diff --git a/src/platform.c b/src/platform.c index 16a3c08..4164549 100644 --- a/src/platform.c +++ b/src/platform.c @@ -28,6 +28,8 @@ #include #endif +#include + #include "platform.h" int @@ -49,3 +51,48 @@ dc_platform_sleep (unsigned int milliseconds) return 0; } + +int +dc_platform_vsnprintf (char *str, size_t size, const char *format, va_list ap) +{ + int n = 0; + + if (size == 0) + return -1; + +#ifdef _MSC_VER + /* + * The non-standard vsnprintf implementation provided by MSVC doesn't null + * terminate the string and returns a negative value if the destination + * buffer is too small. + */ + n = _vsnprintf (str, size - 1, format, ap); + if (n == size - 1 || n < 0) + str[size - 1] = 0; +#else + /* + * The C99 vsnprintf function will always null terminate the string. If the + * destination buffer is too small, the return value is the number of + * characters that would have been written if the buffer had been large + * enough. + */ + n = vsnprintf (str, size, format, ap); + if (n >= 0 && (size_t) n >= size) + n = -1; +#endif + + return n; +} + +int +dc_platform_snprintf (char *str, size_t size, const char *format, ...) +{ + va_list ap; + int n = 0; + + va_start (ap, format); + n = dc_platform_vsnprintf (str, size, format, ap); + va_end (ap); + + return n; +} diff --git a/src/platform.h b/src/platform.h index 438aa7d..e4a5cd5 100644 --- a/src/platform.h +++ b/src/platform.h @@ -22,18 +22,27 @@ #ifndef DC_PLATFORM_H #define DC_PLATFORM_H +#include + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ +#if defined(__GNUC__) +#define DC_ATTR_FORMAT_PRINTF(a,b) __attribute__((format(printf, a, b))) +#else +#define DC_ATTR_FORMAT_PRINTF(a,b) +#endif + #ifdef _WIN32 #define DC_PRINTF_SIZE "%Iu" +#define DC_FORMAT_INT64 "%I64d" #else #define DC_PRINTF_SIZE "%zu" +#define DC_FORMAT_INT64 "%lld" #endif #ifdef _MSC_VER -#define snprintf _snprintf #define strcasecmp _stricmp #define strncasecmp _strnicmp #if _MSC_VER < 1800 @@ -47,6 +56,13 @@ extern "C" { int dc_platform_sleep(unsigned int milliseconds); +/* + * A wrapper for the vsnprintf function, which will always null terminate the + * string and returns a negative value if the destination buffer is too small. + */ +int dc_platform_snprintf (char *str, size_t size, const char *format, ...) DC_ATTR_FORMAT_PRINTF(3, 4); +int dc_platform_vsnprintf (char *str, size_t size, const char *format, va_list ap) DC_ATTR_FORMAT_PRINTF(3, 0); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/reefnet_sensus_parser.c b/src/reefnet_sensus_parser.c index 35fb952..00c3b47 100644 --- a/src/reefnet_sensus_parser.c +++ b/src/reefnet_sensus_parser.c @@ -49,6 +49,9 @@ struct reefnet_sensus_parser_t { }; static dc_status_t reefnet_sensus_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t reefnet_sensus_parser_set_clock (dc_parser_t *abstract, unsigned int devtime, dc_ticks_t systime); +static dc_status_t reefnet_sensus_parser_set_atmospheric (dc_parser_t *abstract, double atmospheric); +static dc_status_t reefnet_sensus_parser_set_density (dc_parser_t *abstract, double density); static dc_status_t reefnet_sensus_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); static dc_status_t reefnet_sensus_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); static dc_status_t reefnet_sensus_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); @@ -57,6 +60,9 @@ static const dc_parser_vtable_t reefnet_sensus_parser_vtable = { sizeof(reefnet_sensus_parser_t), DC_FAMILY_REEFNET_SENSUS, reefnet_sensus_parser_set_data, /* set_data */ + reefnet_sensus_parser_set_clock, /* set_clock */ + reefnet_sensus_parser_set_atmospheric, /* set_atmospheric */ + reefnet_sensus_parser_set_density, /* set_density */ reefnet_sensus_parser_get_datetime, /* datetime */ reefnet_sensus_parser_get_field, /* fields */ reefnet_sensus_parser_samples_foreach, /* samples_foreach */ @@ -80,8 +86,8 @@ reefnet_sensus_parser_create (dc_parser_t **out, dc_context_t *context, unsigned } // Set the default values. - parser->atmospheric = ATM; - parser->hydrostatic = 1025.0 * GRAVITY; + parser->atmospheric = DEF_ATMOSPHERIC; + parser->hydrostatic = DEF_DENSITY_SALT * GRAVITY; parser->devtime = devtime; parser->systime = systime; parser->cached = 0; @@ -123,6 +129,40 @@ reefnet_sensus_parser_set_calibration (dc_parser_t *abstract, double atmospheric } +static dc_status_t +reefnet_sensus_parser_set_clock (dc_parser_t *abstract, unsigned int devtime, dc_ticks_t systime) +{ + reefnet_sensus_parser_t *parser = (reefnet_sensus_parser_t *) abstract; + + parser->devtime = devtime; + parser->systime = systime; + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +reefnet_sensus_parser_set_atmospheric (dc_parser_t *abstract, double atmospheric) +{ + reefnet_sensus_parser_t *parser = (reefnet_sensus_parser_t *) abstract; + + parser->atmospheric = atmospheric; + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +reefnet_sensus_parser_set_density (dc_parser_t *abstract, double density) +{ + reefnet_sensus_parser_t *parser = (reefnet_sensus_parser_t *) abstract; + + parser->hydrostatic = density * GRAVITY; + + return DC_STATUS_SUCCESS; +} + + static dc_status_t reefnet_sensus_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) { diff --git a/src/reefnet_sensuspro_parser.c b/src/reefnet_sensuspro_parser.c index c303ee9..ad371d7 100644 --- a/src/reefnet_sensuspro_parser.c +++ b/src/reefnet_sensuspro_parser.c @@ -48,6 +48,9 @@ struct reefnet_sensuspro_parser_t { }; static dc_status_t reefnet_sensuspro_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t reefnet_sensuspro_parser_set_clock (dc_parser_t *abstract, unsigned int devtime, dc_ticks_t systime); +static dc_status_t reefnet_sensuspro_parser_set_atmospheric (dc_parser_t *abstract, double atmospheric); +static dc_status_t reefnet_sensuspro_parser_set_density (dc_parser_t *abstract, double density); static dc_status_t reefnet_sensuspro_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); static dc_status_t reefnet_sensuspro_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); static dc_status_t reefnet_sensuspro_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); @@ -56,6 +59,9 @@ static const dc_parser_vtable_t reefnet_sensuspro_parser_vtable = { sizeof(reefnet_sensuspro_parser_t), DC_FAMILY_REEFNET_SENSUSPRO, reefnet_sensuspro_parser_set_data, /* set_data */ + reefnet_sensuspro_parser_set_clock, /* set_clock */ + reefnet_sensuspro_parser_set_atmospheric, /* set_atmospheric */ + reefnet_sensuspro_parser_set_density, /* set_density */ reefnet_sensuspro_parser_get_datetime, /* datetime */ reefnet_sensuspro_parser_get_field, /* fields */ reefnet_sensuspro_parser_samples_foreach, /* samples_foreach */ @@ -79,8 +85,8 @@ reefnet_sensuspro_parser_create (dc_parser_t **out, dc_context_t *context, unsig } // Set the default values. - parser->atmospheric = ATM; - parser->hydrostatic = 1025.0 * GRAVITY; + parser->atmospheric = DEF_ATMOSPHERIC; + parser->hydrostatic = DEF_DENSITY_SALT * GRAVITY; parser->devtime = devtime; parser->systime = systime; parser->cached = 0; @@ -122,6 +128,40 @@ reefnet_sensuspro_parser_set_calibration (dc_parser_t *abstract, double atmosphe } +static dc_status_t +reefnet_sensuspro_parser_set_clock (dc_parser_t *abstract, unsigned int devtime, dc_ticks_t systime) +{ + reefnet_sensuspro_parser_t *parser = (reefnet_sensuspro_parser_t *) abstract; + + parser->devtime = devtime; + parser->systime = systime; + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +reefnet_sensuspro_parser_set_atmospheric (dc_parser_t *abstract, double atmospheric) +{ + reefnet_sensuspro_parser_t *parser = (reefnet_sensuspro_parser_t *) abstract; + + parser->atmospheric = atmospheric; + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +reefnet_sensuspro_parser_set_density (dc_parser_t *abstract, double density) +{ + reefnet_sensuspro_parser_t *parser = (reefnet_sensuspro_parser_t *) abstract; + + parser->hydrostatic = density * GRAVITY; + + return DC_STATUS_SUCCESS; +} + + static dc_status_t reefnet_sensuspro_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) { diff --git a/src/reefnet_sensusultra_parser.c b/src/reefnet_sensusultra_parser.c index bad5450..bc52f1f 100644 --- a/src/reefnet_sensusultra_parser.c +++ b/src/reefnet_sensusultra_parser.c @@ -48,6 +48,9 @@ struct reefnet_sensusultra_parser_t { }; static dc_status_t reefnet_sensusultra_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t reefnet_sensusultra_parser_set_clock (dc_parser_t *abstract, unsigned int devtime, dc_ticks_t systime); +static dc_status_t reefnet_sensusultra_parser_set_atmospheric (dc_parser_t *abstract, double atmospheric); +static dc_status_t reefnet_sensusultra_parser_set_density (dc_parser_t *abstract, double density); static dc_status_t reefnet_sensusultra_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); static dc_status_t reefnet_sensusultra_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); static dc_status_t reefnet_sensusultra_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); @@ -56,6 +59,9 @@ static const dc_parser_vtable_t reefnet_sensusultra_parser_vtable = { sizeof(reefnet_sensusultra_parser_t), DC_FAMILY_REEFNET_SENSUSULTRA, reefnet_sensusultra_parser_set_data, /* set_data */ + reefnet_sensusultra_parser_set_clock, /* set_clock */ + reefnet_sensusultra_parser_set_atmospheric, /* set_atmospheric */ + reefnet_sensusultra_parser_set_density, /* set_density */ reefnet_sensusultra_parser_get_datetime, /* datetime */ reefnet_sensusultra_parser_get_field, /* fields */ reefnet_sensusultra_parser_samples_foreach, /* samples_foreach */ @@ -79,8 +85,8 @@ reefnet_sensusultra_parser_create (dc_parser_t **out, dc_context_t *context, uns } // Set the default values. - parser->atmospheric = ATM; - parser->hydrostatic = 1025.0 * GRAVITY; + parser->atmospheric = DEF_ATMOSPHERIC; + parser->hydrostatic = DEF_DENSITY_SALT * GRAVITY; parser->devtime = devtime; parser->systime = systime; parser->cached = 0; @@ -122,6 +128,40 @@ reefnet_sensusultra_parser_set_calibration (dc_parser_t *abstract, double atmosp } +static dc_status_t +reefnet_sensusultra_parser_set_clock (dc_parser_t *abstract, unsigned int devtime, dc_ticks_t systime) +{ + reefnet_sensusultra_parser_t *parser = (reefnet_sensusultra_parser_t *) abstract; + + parser->devtime = devtime; + parser->systime = systime; + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +reefnet_sensusultra_parser_set_atmospheric (dc_parser_t *abstract, double atmospheric) +{ + reefnet_sensusultra_parser_t *parser = (reefnet_sensusultra_parser_t *) abstract; + + parser->atmospheric = atmospheric; + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +reefnet_sensusultra_parser_set_density (dc_parser_t *abstract, double density) +{ + reefnet_sensusultra_parser_t *parser = (reefnet_sensusultra_parser_t *) abstract; + + parser->hydrostatic = density * GRAVITY; + + return DC_STATUS_SUCCESS; +} + + static dc_status_t reefnet_sensusultra_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) { diff --git a/src/seac_screen_parser.c b/src/seac_screen_parser.c index 28988e4..8c588d2 100644 --- a/src/seac_screen_parser.c +++ b/src/seac_screen_parser.c @@ -44,6 +44,8 @@ struct seac_screen_parser_t { unsigned int cached; unsigned int ngasmixes; unsigned int oxygen[NGASMIXES]; + unsigned int gf_low; + unsigned int gf_high; }; static dc_status_t seac_screen_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); @@ -55,6 +57,9 @@ static const dc_parser_vtable_t seac_screen_parser_vtable = { sizeof(seac_screen_parser_t), DC_FAMILY_SEAC_SCREEN, seac_screen_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ seac_screen_parser_get_datetime, /* datetime */ seac_screen_parser_get_field, /* fields */ seac_screen_parser_samples_foreach, /* samples_foreach */ @@ -82,6 +87,8 @@ seac_screen_parser_create (dc_parser_t **out, dc_context_t *context) for (unsigned int i = 0; i < NGASMIXES; ++i) { parser->oxygen[i] = 0; } + parser->gf_low = 0; + parser->gf_high = 0; *out = (dc_parser_t *) parser; @@ -99,6 +106,8 @@ seac_screen_parser_set_data (dc_parser_t *abstract, const unsigned char *data, u for (unsigned int i = 0; i < NGASMIXES; ++i) { parser->oxygen[i] = 0; } + parser->gf_low = 0; + parser->gf_high = 0; return DC_STATUS_SUCCESS; } @@ -207,6 +216,7 @@ seac_screen_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsig } dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; if (value) { switch (type) { @@ -249,6 +259,12 @@ seac_screen_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsig return DC_STATUS_DATAFORMAT; } break; + case DC_FIELD_DECOMODEL: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->conservatism = 0; + decomodel->params.gf.low = parser->gf_low; + decomodel->params.gf.high = parser->gf_high; + break; default: return DC_STATUS_UNSUPPORTED; } @@ -279,6 +295,9 @@ seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t unsigned int oxygen[NGASMIXES] = {0}; unsigned int o2_previous = INVALID; + unsigned int gf_low = 0; + unsigned int gf_high = 0; + unsigned int time = 0; unsigned int offset = SZ_HEADER; while (offset + SZ_SAMPLE <= size) { @@ -298,6 +317,8 @@ seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t unsigned int decotime = array_uint16_le (data + offset + 0x10); unsigned int ndl_tts = array_uint16_le (data + offset + 0x12); unsigned int cns = array_uint16_le (data + offset + 0x16); + unsigned int gf_hi = data[offset + 0x3B]; + unsigned int gf_lo = data[offset + 0x3C]; if (id != dive_id) { ERROR (abstract->context, "Unexpected sample id (%u %u).", dive_id, id); @@ -362,6 +383,12 @@ seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t sample.cns = cns / 100.0; if (callback) callback (DC_SAMPLE_CNS, sample, userdata); + // Deco model + if (gf_low == 0 && gf_high == 0) { + gf_low = gf_lo; + gf_high = gf_hi; + } + offset += SZ_SAMPLE; } @@ -370,6 +397,8 @@ seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t parser->oxygen[i] = oxygen[i]; } parser->ngasmixes = ngasmixes; + parser->gf_low = gf_low; + parser->gf_high = gf_high; parser->cached = 1; return DC_STATUS_SUCCESS; diff --git a/src/serial_posix.c b/src/serial_posix.c index 8872982..a189e15 100644 --- a/src/serial_posix.c +++ b/src/serial_posix.c @@ -30,13 +30,13 @@ #include // fcntl #include // tcgetattr, tcsetattr, cfsetispeed, cfsetospeed, tcflush, tcsendbreak #include // ioctl +#include // select #ifdef HAVE_LINUX_SERIAL_H #include #endif #ifdef HAVE_IOKIT_SERIAL_IOSS_H #include #endif -#include #include #include #include @@ -225,7 +225,7 @@ dc_serial_iterator_next (dc_iterator_t *abstract, void *out) continue; char filename[sizeof(device->name)]; - int n = snprintf (filename, sizeof (filename), "%s/%s", DIRNAME, ep->d_name); + int n = dc_platform_snprintf (filename, sizeof (filename), "%s/%s", DIRNAME, ep->d_name); if (n < 0 || (size_t) n >= sizeof (filename)) { return DC_STATUS_NOMEMORY; } diff --git a/src/shearwater_petrel.c b/src/shearwater_petrel.c index c7893e8..47cf534 100644 --- a/src/shearwater_petrel.c +++ b/src/shearwater_petrel.c @@ -248,6 +248,7 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call case 0x0C0D: // current docs (June 2023) show this as Perdix AI case 0x0D0D: // current docs (June 2023) imply this is not a valid hardware ID case 0x7C2D: // Perdix AI + case 0x8D6C: model = PERDIXAI; break; case 0xC407: // Perdix 2 diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index 624669b..419dd35 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -79,13 +79,23 @@ #define M_OC_REC 6 #define M_FREEDIVE 7 +#define AI_OFF 0 +#define AI_HPCCR 4 +#define AI_ON 5 + +#define GF 0 +#define VPMB 1 +#define VPMB_GFS 2 +#define DCIEM 3 + #define METRIC 0 #define IMPERIAL 1 -#define NGASMIXES 10 -#define MAXSTRINGS 32 -#define NTANKS 4 +#define NGASMIXES 20 +#define NFIXED 10 +#define NTANKS 6 #define NRECORDS 8 +#define MAXSTRINGS 32 #define PREDATOR 2 #define PETREL 3 @@ -98,6 +108,7 @@ typedef struct shearwater_predator_parser_t shearwater_predator_parser_t; typedef struct shearwater_predator_gasmix_t { unsigned int oxygen; unsigned int helium; + unsigned int diluent; } shearwater_predator_gasmix_t; typedef struct shearwater_predator_tank_t { @@ -131,6 +142,7 @@ struct shearwater_predator_parser_t { shearwater_predator_gasmix_t gasmix[NGASMIXES]; shearwater_predator_tank_t tank[NTANKS]; unsigned int tankidx[NTANKS]; + unsigned int aimode; unsigned int calibrated; double calibration[3]; unsigned int serial; @@ -153,6 +165,9 @@ static const dc_parser_vtable_t shearwater_predator_parser_vtable = { sizeof(shearwater_predator_parser_t), DC_FAMILY_SHEARWATER_PREDATOR, shearwater_predator_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ shearwater_predator_parser_get_datetime, /* datetime */ shearwater_predator_parser_get_field, /* fields */ shearwater_predator_parser_samples_foreach, /* samples_foreach */ @@ -163,6 +178,9 @@ static const dc_parser_vtable_t shearwater_petrel_parser_vtable = { sizeof(shearwater_predator_parser_t), DC_FAMILY_SHEARWATER_PETREL, shearwater_predator_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ shearwater_predator_parser_get_datetime, /* datetime */ shearwater_predator_parser_get_field, /* fields */ shearwater_predator_parser_samples_foreach, /* samples_foreach */ @@ -171,11 +189,11 @@ static const dc_parser_vtable_t shearwater_petrel_parser_vtable = { static unsigned int -shearwater_predator_find_gasmix (shearwater_predator_parser_t *parser, unsigned int o2, unsigned int he) +shearwater_predator_find_gasmix (shearwater_predator_parser_t *parser, unsigned int o2, unsigned int he, unsigned int dil) { unsigned int i = 0; while (i < parser->ngasmixes) { - if (o2 == parser->gasmix[i].oxygen && he == parser->gasmix[i].helium) + if (o2 == parser->gasmix[i].oxygen && he == parser->gasmix[i].helium && dil == parser->gasmix[i].diluent) break; i++; } @@ -230,6 +248,7 @@ shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsig for (unsigned int i = 0; i < NGASMIXES; ++i) { parser->gasmix[i].oxygen = 0; parser->gasmix[i].helium = 0; + parser->gasmix[i].diluent = 0; } parser->ntanks = 0; for (unsigned int i = 0; i < NTANKS; ++i) { @@ -244,13 +263,14 @@ shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsig parser->tank[i].battery = 0; parser->tankidx[i] = i; } + parser->aimode = AI_OFF; parser->calibrated = 0; for (unsigned int i = 0; i < 3; ++i) { parser->calibration[i] = 0.0; } parser->units = METRIC; - parser->density = 1025; - parser->atmospheric = ATM / (BAR / 1000); + parser->density = DEF_DENSITY_SALT; + parser->atmospheric = DEF_ATMOSPHERIC / (BAR / 1000); DC_ASSIGN_FIELD(parser->cache, DIVEMODE, DC_DIVEMODE_OC); @@ -294,6 +314,7 @@ shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char for (unsigned int i = 0; i < NGASMIXES; ++i) { parser->gasmix[i].oxygen = 0; parser->gasmix[i].helium = 0; + parser->gasmix[i].diluent = 0; } parser->ntanks = 0; for (unsigned int i = 0; i < NTANKS; ++i) { @@ -307,13 +328,14 @@ shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char memset (parser->tank[i].name, 0, sizeof (parser->tank[i].name)); parser->tankidx[i] = i; } + parser->aimode = AI_OFF; parser->calibrated = 0; for (unsigned int i = 0; i < 3; ++i) { parser->calibration[i] = 0.0; } parser->units = METRIC; - parser->density = 1025; - parser->atmospheric = ATM / (BAR / 1000); + parser->density = DEF_DENSITY_SALT; + parser->atmospheric = DEF_ATMOSPHERIC / (BAR / 1000); DC_ASSIGN_FIELD(parser->cache, DIVEMODE, DC_DIVEMODE_OC); @@ -495,10 +517,18 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) unsigned int divemode = M_OC_TEC; // Get the gas mixes. - unsigned int ngasmixes = 0; + unsigned int ngasmixes = NFIXED; shearwater_predator_gasmix_t gasmix[NGASMIXES] = {0}; shearwater_predator_tank_t tank[NTANKS] = {0}; - unsigned int o2_previous = 0, he_previous = 0; + unsigned int o2_previous = UNDEFINED, he_previous = UNDEFINED, dil_previous = UNDEFINED; + unsigned int aimode = AI_OFF; + if (!pnf) { + for (unsigned int i = 0; i < NFIXED; ++i) { + gasmix[i].oxygen = data[20 + i]; + gasmix[i].helium = data[30 + i]; + gasmix[i].diluent = i >= 5; + } + } unsigned int offset = headersize; unsigned int length = size - footersize; @@ -515,18 +545,20 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) if (type == LOG_RECORD_DIVE_SAMPLE) { // Status flags. unsigned int status = data[offset + 11 + pnf]; - if ((status & OC) == 0) { + unsigned int ccr = (status & OC) == 0; + if (ccr) { divemode = status & SC ? M_SC : M_CC; } // Gaschange. unsigned int o2 = data[offset + 7 + pnf]; unsigned int he = data[offset + 8 + pnf]; - if (o2 != o2_previous || he != he_previous) { + if ((o2 != o2_previous || he != he_previous || ccr != dil_previous) && + (o2 != 0 || he != 0)) { // Find the gasmix in the list. unsigned int idx = 0; while (idx < ngasmixes) { - if (o2 == gasmix[idx].oxygen && he == gasmix[idx].helium) + if (o2 == gasmix[idx].oxygen && he == gasmix[idx].helium && ccr == gasmix[idx].diluent) break; idx++; } @@ -539,11 +571,13 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) } gasmix[idx].oxygen = o2; gasmix[idx].helium = he; + gasmix[idx].diluent = ccr; ngasmixes = idx + 1; } o2_previous = o2; he_previous = he; + dil_previous = ccr; } // Tank pressure @@ -559,17 +593,18 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) // level (0=normal, 1=critical, 2=warning), and the lower 12 // bits the tank pressure in units of 2 psi. unsigned int pressure = array_uint16_be (data + offset + pnf + idx[i]); + unsigned int id = (aimode == AI_HPCCR ? 4 : 0) + i; if (pressure < 0xFFF0) { unsigned int battery = 1u << (pressure >> 12); pressure &= 0x0FFF; - if (!tank[i].active) { - tank[i].active = 1; - tank[i].beginpressure = pressure; - tank[i].endpressure = pressure; - tank[i].battery = 0; + if (!tank[id].active) { + tank[id].active = 1; + tank[id].beginpressure = pressure; + tank[id].endpressure = pressure; + tank[id].battery = 0; } - tank[i].endpressure = pressure; - tank[i].battery |= battery; + tank[id].endpressure = pressure; + tank[id].battery |= battery; } } } @@ -578,14 +613,33 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) if (logversion >= 13) { for (unsigned int i = 0; i < 2; ++i) { unsigned int pressure = array_uint16_be (data + offset + pnf + i * 2); + unsigned int id = 2 + i; if (pressure < 0xFFF0) { pressure &= 0x0FFF; - if (!tank[i + 2].active) { - tank[i + 2].active = 1; - tank[i + 2].beginpressure = pressure; - tank[i + 2].endpressure = pressure; + if (!tank[id].active) { + tank[id].active = 1; + tank[id].beginpressure = pressure; + tank[id].endpressure = pressure; } - tank[i + 2].endpressure = pressure; + tank[id].endpressure = pressure; + } + } + } + // Tank pressure (HP CCR) + if (logversion >= 14) { + for (unsigned int i = 0; i < 2; ++i) { + unsigned int pressure = array_uint16_be (data + offset + pnf + 4 + i * 2); + unsigned int id = 4 + i; + if (pressure) { + if (!tank[id].active) { + tank[id].active = 1; + tank[id].enabled = 1; + tank[id].beginpressure = pressure; + tank[id].endpressure = pressure; + tank[id].name[0] = i == 0 ? 'D': 'O'; + tank[id].name[1] = 0; + } + tank[id].endpressure = pressure; } } } @@ -596,24 +650,41 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) // Opening record parser->opening[type - LOG_RECORD_OPENING_0] = offset; - if (type == LOG_RECORD_OPENING_4) { + if (type == LOG_RECORD_OPENING_0) { + for (unsigned int i = 0; i < NFIXED; ++i) { + gasmix[i].oxygen = data[offset + 20 + i]; + gasmix[i].diluent = i >= 5; + } + for (unsigned int i = 0; i < 2; ++i) { + gasmix[i].helium = data[offset + 30 + i]; + } + } else if (type == LOG_RECORD_OPENING_1) { + for (unsigned int i = 2; i < NFIXED; ++i) { + gasmix[i].helium = data[offset + 1 + i - 2]; + } + } else if (type == LOG_RECORD_OPENING_4) { // Log version logversion = data[offset + 16]; // Air integration mode if (logversion >= 7) { - unsigned int airmode = data[offset + 28]; + aimode = data[offset + 28]; if (logversion < 13) { - if (airmode == 1 || airmode == 2) { - tank[airmode - 1].enabled = 1; - } else if (airmode == 3) { + if (aimode == 1 || aimode == 2) { + tank[aimode - 1].enabled = 1; + } else if (aimode == 3) { tank[0].enabled = 1; tank[1].enabled = 1; } } - if (airmode == 4) { - tank[0].enabled = 1; - tank[1].enabled = 1; + if (logversion < 14) { + if (aimode == AI_HPCCR) { + for (unsigned int i = 0; i < 2; ++i) { + tank[4 + i].enabled = 1; + tank[4 + i].name[0] = i == 0 ? 'D': 'O'; + tank[4 + i].name[1] = 0; + } + } } } } else if (type == LOG_RECORD_OPENING_5) { @@ -733,9 +804,14 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) parser->logversion = logversion; parser->headersize = headersize; parser->footersize = footersize; - parser->ngasmixes = ngasmixes; - for (unsigned int i = 0; i < ngasmixes; ++i) { - parser->gasmix[i] = gasmix[i]; + parser->ngasmixes = 0; + if (divemode != M_FREEDIVE) { + for (unsigned int i = 0; i < ngasmixes; ++i) { + if (gasmix[i].oxygen == 0 && gasmix[i].helium == 0) + continue; + parser->gasmix[parser->ngasmixes] = gasmix[i]; + parser->ngasmixes++; + } } parser->ntanks = 0; for (unsigned int i = 0; i < NTANKS; ++i) { @@ -747,6 +823,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) parser->tankidx[i] = UNDEFINED; } } + parser->aimode = aimode; parser->units = data[parser->opening[0] + 8]; parser->atmospheric = array_uint16_be (data + parser->opening[1] + (parser->pnf ? 16 : 47)); parser->density = array_uint16_be (data + parser->opening[3] + (parser->pnf ? 3 : 83)); @@ -801,9 +878,13 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ if (rc != DC_STATUS_SUCCESS) return rc; + unsigned int decomodel_idx = parser->pnf ? parser->opening[2] + 18 : 67; + unsigned int gf_idx = parser->pnf ? parser->opening[0] + 4 : 4; + dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_tank_t *tank = (dc_tank_t *) value; dc_salinity_t *water = (dc_salinity_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; dc_field_string_t *string = (dc_field_string_t *) value; if (value) { @@ -853,6 +934,27 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ break; case DC_FIELD_DIVEMODE: return DC_FIELD_VALUE(parser->cache, value, DIVEMODE); + case DC_FIELD_DECOMODEL: + switch (data[decomodel_idx]) { + case GF: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->conservatism = 0; + decomodel->params.gf.low = data[gf_idx + 0]; + decomodel->params.gf.high = data[gf_idx + 1]; + break; + case VPMB: + case VPMB_GFS: + decomodel->type = DC_DECOMODEL_VPM; + decomodel->conservatism = data[decomodel_idx + 1]; + break; + case DCIEM: + decomodel->type = DC_DECOMODEL_DCIEM; + decomodel->conservatism = 0; + break; + default: + return DC_STATUS_DATAFORMAT; + } + break; case DC_FIELD_STRING: return dc_field_get_string(&parser->cache, flags, string); default: @@ -878,7 +980,7 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal return rc; // Previous gas mix. - unsigned int o2_previous = 0, he_previous = 0; + unsigned int o2_previous = UNDEFINED, he_previous = UNDEFINED, dil_previous = UNDEFINED; // Sample interval. unsigned int time = 0; @@ -942,8 +1044,9 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal // Status flags. unsigned int status = data[offset + pnf + 11]; + unsigned int ccr = (status & OC) == 0; - if ((status & OC) == 0) { + if (ccr) { // PPO2 if ((status & PPO2_EXTERNAL) == 0) { if (!parser->calibrated) { @@ -984,8 +1087,9 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal // Gaschange. unsigned int o2 = data[offset + pnf + 7]; unsigned int he = data[offset + pnf + 8]; - if (o2 != o2_previous || he != he_previous) { - unsigned int idx = shearwater_predator_find_gasmix (parser, o2, he); + if ((o2 != o2_previous || he != he_previous || ccr != dil_previous) && + (o2 != 0 || he != 0)) { + unsigned int idx = shearwater_predator_find_gasmix (parser, o2, he, ccr); if (idx >= parser->ngasmixes) { ERROR (abstract->context, "Invalid gas mix."); return DC_STATUS_DATAFORMAT; @@ -995,6 +1099,7 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal if (callback) callback (DC_SAMPLE_GASMIX, sample, userdata); o2_previous = o2; he_previous = he; + dil_previous = ccr; } // Deco stop / NDL. @@ -1027,9 +1132,10 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal // level (0=normal, 1=critical, 2=warning), and the lower 12 // bits the tank pressure in units of 2 psi. unsigned int pressure = array_uint16_be (data + offset + pnf + idx[i]); + unsigned int id = (parser->aimode == AI_HPCCR ? 4 : 0) + i; if (pressure < 0xFFF0) { pressure &= 0x0FFF; - sample.pressure.tank = parser->tankidx[i]; + sample.pressure.tank = parser->tankidx[id]; sample.pressure.value = pressure * 2 * PSI / BAR; if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata); } @@ -1052,9 +1158,22 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal if (parser->logversion >= 13) { for (unsigned int i = 0; i < 2; ++i) { unsigned int pressure = array_uint16_be (data + offset + pnf + i * 2); + unsigned int id = 2 + i; if (pressure < 0xFFF0) { pressure &= 0x0FFF; - sample.pressure.tank = parser->tankidx[i + 2]; + sample.pressure.tank = parser->tankidx[id]; + sample.pressure.value = pressure * 2 * PSI / BAR; + if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata); + } + } + } + // Tank pressure (HP CCR) + if (parser->logversion >= 14) { + for (unsigned int i = 0; i < 2; ++i) { + unsigned int pressure = array_uint16_be (data + offset + pnf + 4 + i * 2); + unsigned int id = 4 + i; + if (pressure) { + sample.pressure.tank = parser->tankidx[id]; sample.pressure.value = pressure * 2 * PSI / BAR; if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata); } diff --git a/src/sporasub_sp2.c b/src/sporasub_sp2.c index 8112305..8be2e8a 100644 --- a/src/sporasub_sp2.c +++ b/src/sporasub_sp2.c @@ -461,7 +461,7 @@ sporasub_sp2_device_timesync (dc_device_t *abstract, const dc_datetime_t *dateti dc_status_t status = DC_STATUS_SUCCESS; sporasub_sp2_device_t *device = (sporasub_sp2_device_t *) abstract; - if (datetime == NULL || datetime->year < 2000) { + if (datetime->year < 2000) { ERROR (abstract->context, "Invalid parameter specified."); return DC_STATUS_INVALIDARGS; } diff --git a/src/sporasub_sp2_parser.c b/src/sporasub_sp2_parser.c index 42cde0c..5ac44b8 100644 --- a/src/sporasub_sp2_parser.c +++ b/src/sporasub_sp2_parser.c @@ -46,6 +46,9 @@ 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 */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ sporasub_sp2_parser_get_datetime, /* datetime */ sporasub_sp2_parser_get_field, /* fields */ sporasub_sp2_parser_samples_foreach, /* samples_foreach */ diff --git a/src/suunto_d9_parser.c b/src/suunto_d9_parser.c index 061dd8e..9eafeb1 100644 --- a/src/suunto_d9_parser.c +++ b/src/suunto_d9_parser.c @@ -103,6 +103,9 @@ static const dc_parser_vtable_t suunto_d9_parser_vtable = { sizeof(suunto_d9_parser_t), DC_FAMILY_SUUNTO_D9, suunto_d9_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ suunto_d9_parser_get_datetime, /* datetime */ suunto_d9_parser_get_field, /* fields */ suunto_d9_parser_samples_foreach, /* samples_foreach */ @@ -365,6 +368,7 @@ suunto_d9_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigne return rc; dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; dc_field_string_t *string = (dc_field_string_t *) value; char buf[BUFLEN]; @@ -415,6 +419,20 @@ suunto_d9_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigne return DC_STATUS_DATAFORMAT; } break; + case DC_FIELD_DECOMODEL: + decomodel->type = DC_DECOMODEL_RGBM; + if (parser->model == D4i ||parser->model == D6i || + parser->model == D9tx || parser->model == ZOOPNOVO_A || + parser->model == ZOOPNOVO_B || parser->model == VYPERNOVO || + parser->model == D4F) + decomodel->conservatism = data[0x21] - 2; + else if (parser->model == HELO2) + decomodel->conservatism = data[0x23] - 2; + else if (parser->model == DX) + decomodel->conservatism = data[0x25] - 2; + else + decomodel->conservatism = data[0x1E]; + break; case DC_FIELD_STRING: switch (flags) { case 0: /* serial */ diff --git a/src/suunto_eon_parser.c b/src/suunto_eon_parser.c index 2106c69..8e80aca 100644 --- a/src/suunto_eon_parser.c +++ b/src/suunto_eon_parser.c @@ -52,6 +52,9 @@ static const dc_parser_vtable_t suunto_eon_parser_vtable = { sizeof(suunto_eon_parser_t), DC_FAMILY_SUUNTO_EON, suunto_eon_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ suunto_eon_parser_get_datetime, /* datetime */ suunto_eon_parser_get_field, /* fields */ suunto_eon_parser_samples_foreach, /* samples_foreach */ diff --git a/src/suunto_eonsteel.c b/src/suunto_eonsteel.c index 2d8122c..6ffc7bc 100644 --- a/src/suunto_eonsteel.c +++ b/src/suunto_eonsteel.c @@ -897,7 +897,7 @@ suunto_eonsteel_device_foreach(dc_device_t *abstract, dc_dive_callback_t callbac break; } - len = snprintf(pathname, sizeof(pathname), "%s/%s", dive_directory, de->name); + len = dc_platform_snprintf(pathname, sizeof(pathname), "%s/%s", dive_directory, de->name); if (len < 0 || (unsigned int) len >= sizeof(pathname)) { dc_status_set_error(&status, DC_STATUS_PROTOCOL); break; diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index 752250e..2947221 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -816,11 +816,11 @@ static void sample_setpoint_type(const struct type_desc *desc, struct sample_dat } if (!strcasecmp(type, "Low")) - sample.ppo2 = info->eon->cache.lowsetpoint; + sample.setpoint = info->eon->cache.lowsetpoint; else if (!strcasecmp(type, "High")) - sample.ppo2 = info->eon->cache.highsetpoint; + sample.setpoint = info->eon->cache.highsetpoint; else if (!strcasecmp(type, "Custom")) - sample.ppo2 = info->eon->cache.customsetpoint; + sample.setpoint = info->eon->cache.customsetpoint; else { DEBUG(info->eon->base.context, "sample_setpoint_type(%u) unknown type '%s'", value, type); free(type); @@ -1514,6 +1514,9 @@ static const dc_parser_vtable_t suunto_eonsteel_parser_vtable = { sizeof(suunto_eonsteel_parser_t), DC_FAMILY_SUUNTO_EONSTEEL, suunto_eonsteel_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ suunto_eonsteel_parser_get_datetime, /* datetime */ suunto_eonsteel_parser_get_field, /* fields */ suunto_eonsteel_parser_samples_foreach, /* samples_foreach */ diff --git a/src/suunto_solution_parser.c b/src/suunto_solution_parser.c index 34aab7d..59648d5 100644 --- a/src/suunto_solution_parser.c +++ b/src/suunto_solution_parser.c @@ -47,6 +47,9 @@ static const dc_parser_vtable_t suunto_solution_parser_vtable = { sizeof(suunto_solution_parser_t), DC_FAMILY_SUUNTO_SOLUTION, suunto_solution_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ NULL, /* datetime */ suunto_solution_parser_get_field, /* fields */ suunto_solution_parser_samples_foreach, /* samples_foreach */ diff --git a/src/suunto_vyper_parser.c b/src/suunto_vyper_parser.c index bf4134a..20a52df 100644 --- a/src/suunto_vyper_parser.c +++ b/src/suunto_vyper_parser.c @@ -56,6 +56,9 @@ static const dc_parser_vtable_t suunto_vyper_parser_vtable = { sizeof(suunto_vyper_parser_t), DC_FAMILY_SUUNTO_VYPER, suunto_vyper_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ suunto_vyper_parser_get_datetime, /* datetime */ suunto_vyper_parser_get_field, /* fields */ suunto_vyper_parser_samples_foreach, /* samples_foreach */ @@ -243,6 +246,7 @@ suunto_vyper_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi dc_gasmix_t *gas = (dc_gasmix_t *) value; dc_tank_t *tank = (dc_tank_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; dc_field_string_t *string = (dc_field_string_t *) value; char buf[BUFLEN]; @@ -304,6 +308,10 @@ suunto_vyper_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi *((dc_divemode_t *) value) = DC_DIVEMODE_OC; } break; + case DC_FIELD_DECOMODEL: + decomodel->type = DC_DECOMODEL_RGBM; + decomodel->conservatism = (data[4] & 0x0F) / 3; + break; case DC_FIELD_STRING: switch(flags) { case 0: /* serial */ diff --git a/src/tecdiving_divecomputereu_parser.c b/src/tecdiving_divecomputereu_parser.c index a3006ea..d060996 100644 --- a/src/tecdiving_divecomputereu_parser.c +++ b/src/tecdiving_divecomputereu_parser.c @@ -45,6 +45,9 @@ static const dc_parser_vtable_t tecdiving_divecomputereu_parser_vtable = { sizeof(tecdiving_divecomputereu_parser_t), DC_FAMILY_TECDIVING_DIVECOMPUTEREU, tecdiving_divecomputereu_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ tecdiving_divecomputereu_parser_get_datetime, /* datetime */ tecdiving_divecomputereu_parser_get_field, /* fields */ tecdiving_divecomputereu_parser_samples_foreach, /* samples_foreach */ diff --git a/src/usbhid.c b/src/usbhid.c index b3805ca..acd20ef 100644 --- a/src/usbhid.c +++ b/src/usbhid.c @@ -86,6 +86,7 @@ struct dc_usbhid_device_t { int interface; unsigned char endpoint_in; unsigned char endpoint_out; + unsigned short packetsize; #elif defined(USE_HIDAPI) char *path; #endif @@ -125,6 +126,7 @@ typedef struct dc_usbhid_t { int interface; unsigned char endpoint_in; unsigned char endpoint_out; + unsigned short packetsize; unsigned int timeout; #elif defined(USE_HIDAPI) hid_device *handle; @@ -507,6 +509,7 @@ dc_usbhid_iterator_next (dc_iterator_t *abstract, void *out) device->interface = interface->bInterfaceNumber; device->endpoint_in = ep_in->bEndpointAddress; device->endpoint_out = ep_out->bEndpointAddress; + device->packetsize = ep_in->wMaxPacketSize; *(dc_usbhid_device_t **) out = device; @@ -614,6 +617,7 @@ dc_usbhid_open (dc_iostream_t **out, dc_context_t *context, dc_usbhid_device_t * usbhid->interface = device->interface; usbhid->endpoint_in = device->endpoint_in; usbhid->endpoint_out = device->endpoint_out; + usbhid->packetsize = device->packetsize; usbhid->timeout = 0; #elif defined(USE_HIDAPI) @@ -704,6 +708,10 @@ dc_usbhid_read (dc_iostream_t *abstract, void *data, size_t size, size_t *actual int nbytes = 0; #if defined(USE_LIBUSB) + if (size > usbhid->packetsize) { + size = usbhid->packetsize; + } + int rc = libusb_interrupt_transfer (usbhid->handle, usbhid->endpoint_in, data, size, &nbytes, usbhid->timeout); if (rc != LIBUSB_SUCCESS || nbytes < 0) { ERROR (abstract->context, "Usb read interrupt transfer failed (%s).", diff --git a/src/uwatec_memomouse_parser.c b/src/uwatec_memomouse_parser.c index 386e984..3c818fa 100644 --- a/src/uwatec_memomouse_parser.c +++ b/src/uwatec_memomouse_parser.c @@ -39,6 +39,7 @@ struct uwatec_memomouse_parser_t { }; static dc_status_t uwatec_memomouse_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t uwatec_memomouse_parser_set_clock (dc_parser_t *abstract, unsigned int devtime, dc_ticks_t systime); static dc_status_t uwatec_memomouse_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); static dc_status_t uwatec_memomouse_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); static dc_status_t uwatec_memomouse_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); @@ -47,6 +48,9 @@ static const dc_parser_vtable_t uwatec_memomouse_parser_vtable = { sizeof(uwatec_memomouse_parser_t), DC_FAMILY_UWATEC_MEMOMOUSE, uwatec_memomouse_parser_set_data, /* set_data */ + uwatec_memomouse_parser_set_clock, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ uwatec_memomouse_parser_get_datetime, /* datetime */ uwatec_memomouse_parser_get_field, /* fields */ uwatec_memomouse_parser_samples_foreach, /* samples_foreach */ @@ -86,6 +90,18 @@ uwatec_memomouse_parser_set_data (dc_parser_t *abstract, const unsigned char *da } +static dc_status_t +uwatec_memomouse_parser_set_clock (dc_parser_t *abstract, unsigned int devtime, dc_ticks_t systime) +{ + uwatec_memomouse_parser_t *parser = (uwatec_memomouse_parser_t *) abstract; + + parser->devtime = devtime; + parser->systime = systime; + + return DC_STATUS_SUCCESS; +} + + static dc_status_t uwatec_memomouse_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) { diff --git a/src/uwatec_smart.c b/src/uwatec_smart.c index fd8a906..63915c8 100644 --- a/src/uwatec_smart.c +++ b/src/uwatec_smart.c @@ -37,6 +37,8 @@ #define PACKETSIZE_USBHID_TX 32 #define CMD_MODEL 0x10 +#define CMD_HARDWARE 0x11 +#define CMD_SOFTWARE 0x13 #define CMD_SERIAL 0x14 #define CMD_DEVTIME 0x1A #define CMD_HANDSHAKE1 0x1B @@ -274,9 +276,13 @@ uwatec_smart_usbhid_send (uwatec_smart_device_t *device, unsigned char cmd, cons { dc_status_t rc = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; - unsigned char buf[PACKETSIZE_USBHID_TX + 1]; + dc_transport_t transport = dc_iostream_get_transport(device->iostream); + unsigned char buf[DATASIZE + 3]; - if (size + 3 > sizeof(buf)) { + size_t packetsize = transport == DC_TRANSPORT_USBHID ? + PACKETSIZE_USBHID_TX + 1 : sizeof(buf); + + if (size > DATASIZE || size + 3 > packetsize) { ERROR (abstract->context, "Command too large (" DC_PRINTF_SIZE ").", size); return DC_STATUS_INVALIDARGS; } @@ -294,7 +300,7 @@ uwatec_smart_usbhid_send (uwatec_smart_device_t *device, unsigned char cmd, cons if (dc_iostream_get_transport(device->iostream) == DC_TRANSPORT_BLE) { rc = dc_iostream_write(device->iostream, buf + 1, size + 2, NULL); } else { - rc = dc_iostream_write(device->iostream, buf, sizeof(buf), NULL); + rc = dc_iostream_write(device->iostream, buf, packetsize, NULL); } if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the command."); @@ -309,26 +315,16 @@ uwatec_smart_usbhid_receive (uwatec_smart_device_t *device, dc_event_progress_t { dc_status_t rc = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; - unsigned char buf[MAX_PACKETSIZE]; - dc_transport_t transport; - unsigned int pktsize; + dc_transport_t transport = dc_iostream_get_transport(device->iostream); + unsigned char buf[DATASIZE + 1]; - /* - * USB HID uses 64-byte packets, BLE uses variable sized ones. - * - * BLE used to be limited to 64 bytes too, but firmware 2.0 - * seems to have increased that to at least 101 bytes (one odd - * byte header and 100 bytes of data). - */ - transport = dc_iostream_get_transport(device->iostream); - pktsize = sizeof(buf); - if (transport == DC_TRANSPORT_USBHID) - pktsize = PACKETSIZE_USBHID_RX; + size_t packetsize = transport == DC_TRANSPORT_USBHID ? + PACKETSIZE_USBHID_RX : sizeof(buf); size_t nbytes = 0; while (nbytes < size) { size_t transferred = 0; - rc = dc_iostream_read (device->iostream, buf, pktsize, &transferred); + rc = dc_iostream_read (device->iostream, buf, packetsize, &transferred); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to receive the packet."); return rc; @@ -377,7 +373,7 @@ uwatec_smart_usbhid_receive (uwatec_smart_device_t *device, dc_event_progress_t * * It may be just an oddly implemented sequence number. Whatever. */ - unsigned int len = transferred-1; + unsigned int len = transferred - 1; if (transport == DC_TRANSPORT_USBHID) { if (len > buf[0]) len = buf[0]; @@ -574,6 +570,18 @@ uwatec_smart_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) if (rc != DC_STATUS_SUCCESS) return rc; + // Read the hardware version. + unsigned char hardware[1] = {0}; + rc = uwatec_smart_transfer (device, CMD_HARDWARE, NULL, 0, hardware, sizeof (hardware)); + if (rc != DC_STATUS_SUCCESS) + return rc; + + // Read the software version. + unsigned char software[1] = {0}; + rc = uwatec_smart_transfer (device, CMD_SOFTWARE, NULL, 0, software, sizeof (software)); + if (rc != DC_STATUS_SUCCESS) + return rc; + // Read the serial number. unsigned char serial[4] = {0}; rc = uwatec_smart_transfer (device, CMD_SERIAL, NULL, 0, serial, sizeof (serial)); @@ -591,7 +599,7 @@ uwatec_smart_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) device->devtime = array_uint32_le (devtime); // Update and emit a progress event. - progress.current += 9; + progress.current += 11; device_event_emit (&device->base, DC_EVENT_PROGRESS, &progress); // Emit a clock event. @@ -603,7 +611,7 @@ uwatec_smart_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) // Emit a device info event. dc_event_devinfo_t devinfo; devinfo.model = model[0]; - devinfo.firmware = 0; + devinfo.firmware = bcd2dec (software[0]); devinfo.serial = array_uint32_le (serial); device_event_emit (&device->base, DC_EVENT_DEVINFO, &devinfo); @@ -627,7 +635,7 @@ uwatec_smart_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) unsigned int length = array_uint32_le (answer); // Update and emit a progress event. - progress.maximum = 4 + 9 + (length ? length + 4 : 0); + progress.maximum = 4 + 11 + (length ? length + 4 : 0); progress.current += 4; device_event_emit (&device->base, DC_EVENT_PROGRESS, &progress); diff --git a/src/uwatec_smart_parser.c b/src/uwatec_smart_parser.c index 74e6317..39a927c 100644 --- a/src/uwatec_smart_parser.c +++ b/src/uwatec_smart_parser.c @@ -49,6 +49,7 @@ #define ALADINA1 0x25 #define MANTIS2 0x26 #define ALADINA2 0x28 +#define G2TEK 0x31 #define G2 0x32 #define G2HUD 0x42 @@ -167,6 +168,9 @@ static const dc_parser_vtable_t uwatec_smart_parser_vtable = { sizeof(uwatec_smart_parser_t), DC_FAMILY_UWATEC_SMART, uwatec_smart_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ uwatec_smart_parser_get_datetime, /* datetime */ uwatec_smart_parser_get_field, /* fields */ uwatec_smart_parser_samples_foreach, /* samples_foreach */ @@ -529,7 +533,8 @@ uwatec_smart_parser_cache (uwatec_smart_parser_t *parser) parser->model == CHROMIS || parser->model == MANTIS2 || parser->model == G2 || parser->model == ALADINSPORTMATRIX || parser->model == ALADINSQUARE || parser->model == G2HUD || - parser->model == ALADINA1 || parser->model == ALADINA2 ) { + parser->model == ALADINA1 || parser->model == ALADINA2 || + parser->model == G2TEK) { unsigned int offset = header->tankpressure + 2 * i; endpressure = array_uint16_le(data + offset); beginpressure = array_uint16_le(data + offset + 2 * header->ngases); @@ -619,6 +624,7 @@ uwatec_smart_parser_create (dc_parser_t **out, dc_context_t *context, unsigned i break; case G2: case G2HUD: + case G2TEK: case ALADINSPORTMATRIX: case ALADINA1: case ALADINA2: @@ -965,7 +971,8 @@ uwatec_smart_parse (uwatec_smart_parser_t *parser, dc_sample_callback_t callback parser->model == CHROMIS || parser->model == MANTIS2 || parser->model == G2 || parser->model == ALADINSPORTMATRIX || parser->model == ALADINSQUARE || parser->model == G2HUD || - parser->model == ALADINA1 || parser->model == ALADINA2 ) { + parser->model == ALADINA1 || parser->model == ALADINA2 || + parser->model == G2TEK) { // Uwatec Galileo id = uwatec_galileo_identify (data[offset]); } else {