diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3fe3f374..d7f23872 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,13 +25,13 @@ jobs: sudo apt-get update sudo apt-get install libbluetooth-dev libusb-1.0-0-dev - run: autoreconf --install --force - - run: ./configure --prefix=/usr + - run: ./configure - run: make - run: make distcheck - name: Package artifacts run: | make install DESTDIR=$PWD/artifacts - tar -czf ${{ github.job }}-${{ matrix.compiler }}.tar.gz -C artifacts usr + tar -czf ${{ github.job }}-${{ matrix.compiler }}.tar.gz -C artifacts usr/local - uses: actions/upload-artifact@v4 with: name: ${{ github.job }}-${{ matrix.compiler }} @@ -56,13 +56,13 @@ jobs: - name: Install dependencies run: brew install autoconf automake libtool hidapi libusb - run: autoreconf --install --force - - run: ./configure --prefix=/usr + - run: ./configure - run: make - run: make distcheck - name: Package artifacts run: | make install DESTDIR=$PWD/artifacts - tar -czf ${{ github.job }}-${{ matrix.compiler }}.tar.gz -C artifacts usr + tar -czf ${{ github.job }}-${{ matrix.compiler }}.tar.gz -C artifacts usr/local - uses: actions/upload-artifact@v4 with: name: ${{ github.job }}-${{ matrix.compiler }} @@ -82,7 +82,9 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install dependencies - run: sudo apt-get install gcc-mingw-w64 binutils-mingw-w64 mingw-w64-tools + run: | + sudo apt-get update + sudo apt-get install gcc-mingw-w64 binutils-mingw-w64 mingw-w64-tools - name: Install libusb env: LIBUSB_VERSION: 1.0.26 @@ -91,7 +93,7 @@ jobs: tar xzf v${LIBUSB_VERSION}.tar.gz pushd libusb-${LIBUSB_VERSION} autoreconf --install --force - ./configure --host=${{ matrix.arch }}-w64-mingw32 --prefix=/usr + ./configure --host=${{ matrix.arch }}-w64-mingw32 make make install DESTDIR=$PWD/../artifacts popd @@ -103,14 +105,14 @@ jobs: tar xzf hidapi-${HIDAPI_VERSION}.tar.gz pushd hidapi-hidapi-${HIDAPI_VERSION} autoreconf --install --force - ./configure --host=${{ matrix.arch }}-w64-mingw32 --prefix=/usr LDFLAGS='-static-libgcc' + ./configure --host=${{ matrix.arch }}-w64-mingw32 LDFLAGS='-static-libgcc' make make install DESTDIR=$PWD/../artifacts popd - run: autoreconf --install --force - - run: ./configure --host=${{ matrix.arch }}-w64-mingw32 --prefix=/usr + - run: ./configure --host=${{ matrix.arch }}-w64-mingw32 env: - PKG_CONFIG_LIBDIR: ${{ github.workspace }}/artifacts/usr/lib/pkgconfig + PKG_CONFIG_LIBDIR: ${{ github.workspace }}/artifacts/usr/local/lib/pkgconfig PKG_CONFIG_SYSROOT_DIR: ${{ github.workspace }}/artifacts PKG_CONFIG_ALLOW_SYSTEM_CFLAGS: 1 PKG_CONFIG_ALLOW_SYSTEM_LIBS: 1 @@ -119,7 +121,7 @@ jobs: - name: Package artifacts run: | make install DESTDIR=$PWD/artifacts - tar -czf ${{ github.job }}-${{ matrix.arch }}.tar.gz -C artifacts usr + tar -czf ${{ github.job }}-${{ matrix.arch }}.tar.gz -C artifacts usr/local - uses: actions/upload-artifact@v4 with: name: ${{ github.job }}-${{ matrix.arch }} @@ -140,18 +142,18 @@ jobs: # CONFIGURATION: Release # # steps: -# - uses: actions/checkout@v3 +# - uses: actions/checkout@v4 # - uses: msys2/setup-msys2@v2 # with: # install: autoconf automake libtool pkg-config make gcc # - run: | # autoreconf --install --force -# ./configure --prefix=/usr +# ./configure # make -C src revision.h # shell: msys2 {0} -# - uses: microsoft/setup-msbuild@v1 +# - uses: microsoft/setup-msbuild@v2 # - run: msbuild -m -p:Platform=${{ matrix.platform }} -p:Configuration=${{ env.CONFIGURATION }} contrib/msvc/libdivecomputer.vcxproj -# - uses: actions/upload-artifact@v3 +# - uses: actions/upload-artifact@v4 # with: # name: ${{ github.job }}-${{ matrix.platform }} # path: contrib/msvc/${{ matrix.platform }}/${{ env.CONFIGURATION }}/bin @@ -166,7 +168,7 @@ jobs: - uses: actions/checkout@v4 - run: | autoreconf --install --force - ./configure --prefix=/usr + ./configure make -C src revision.h - run: $ANDROID_NDK/ndk-build -C contrib/android NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=Android.mk - uses: actions/upload-artifact@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc4773b5..f8f2b342 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,6 +20,7 @@ jobs: - name: Build distribution tarball id: build run: | + sudo apt-get update sudo apt-get install libbluetooth-dev libusb-1.0-0-dev autoreconf --install --force ./configure diff --git a/configure.ac b/configure.ac index d418a756..57e2d678 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ # Versioning. m4_define([dc_version_major],[0]) -m4_define([dc_version_minor],[9]) +m4_define([dc_version_minor],[10]) m4_define([dc_version_micro],[0]) m4_define([dc_version_suffix],[devel-Subsurface-NG]) m4_define([dc_version],dc_version_major.dc_version_minor.dc_version_micro[]m4_ifset([dc_version_suffix],-[dc_version_suffix])) diff --git a/contrib/android/Android.mk b/contrib/android/Android.mk index 08dd3e9e..2f74444b 100644 --- a/contrib/android/Android.mk +++ b/contrib/android/Android.mk @@ -85,6 +85,7 @@ LOCAL_SRC_FILES := \ src/reefnet_sensusultra_parser.c \ src/ringbuffer.c \ src/seac_screen.c \ + src/seac_screen_common.c \ src/seac_screen_parser.c \ src/serial_posix.c \ src/shearwater_common.c \ diff --git a/contrib/msvc/libdivecomputer.vcxproj b/contrib/msvc/libdivecomputer.vcxproj index 81d6df3a..db583a37 100644 --- a/contrib/msvc/libdivecomputer.vcxproj +++ b/contrib/msvc/libdivecomputer.vcxproj @@ -253,6 +253,7 @@ + @@ -370,6 +371,7 @@ + diff --git a/examples/dctool_download.c b/examples/dctool_download.c index 29742e0c..10964080 100644 --- a/examples/dctool_download.c +++ b/examples/dctool_download.c @@ -53,6 +53,7 @@ typedef struct dive_data_t { dc_buffer_t **fingerprint; unsigned int number; dctool_output_t *output; + unsigned int limit; } dive_data_t; static int @@ -96,6 +97,11 @@ dive_cb (const unsigned char *data, unsigned int size, const unsigned char *fing cleanup: dc_parser_destroy (parser); + + if (divedata->limit > 0 && divedata->number >= divedata->limit) { + return 0; + } + return 1; } @@ -146,7 +152,7 @@ event_cb (dc_device_t *device, dc_event_type_t event, const void *data, void *us } static dc_status_t -download (dc_context_t *context, dc_descriptor_t *descriptor, dc_transport_t transport, const char *devname, const char *cachedir, dc_buffer_t *fingerprint, dctool_output_t *output) +download (dc_context_t *context, dc_descriptor_t *descriptor, dc_transport_t transport, const char *devname, const char *cachedir, dc_buffer_t *fingerprint, dctool_output_t *output, unsigned int limit) { dc_status_t rc = DC_STATUS_SUCCESS; dc_iostream_t *iostream = NULL; @@ -214,6 +220,7 @@ download (dc_context_t *context, dc_descriptor_t *descriptor, dc_transport_t tra divedata.fingerprint = &ofingerprint; divedata.number = 0; divedata.output = output; + divedata.limit = limit; // Download the dives. message ("Downloading the dives.\n"); @@ -260,10 +267,11 @@ dctool_download_run (int argc, char *argv[], dc_context_t *context, dc_descripto const char *filename = NULL; const char *cachedir = NULL; const char *format = "xml"; + unsigned int limit = 0; // Parse the command-line options. int opt = 0; - const char *optstring = "ht:o:p:c:f:u:"; + const char *optstring = "ht:o:p:c:f:u:l:"; #ifdef HAVE_GETOPT_LONG struct option options[] = { {"help", no_argument, 0, 'h'}, @@ -273,6 +281,7 @@ dctool_download_run (int argc, char *argv[], dc_context_t *context, dc_descripto {"cache", required_argument, 0, 'c'}, {"format", required_argument, 0, 'f'}, {"units", required_argument, 0, 'u'}, + {"limit", required_argument, 0, 'l'}, {0, 0, 0, 0 } }; while ((opt = getopt_long (argc, argv, optstring, options, NULL)) != -1) { @@ -304,6 +313,9 @@ dctool_download_run (int argc, char *argv[], dc_context_t *context, dc_descripto if (strcmp (optarg, "imperial") == 0) units = DCTOOL_UNITS_IMPERIAL; break; + case 'l': + limit = strtoul (optarg, NULL, 0); + break; default: return EXIT_FAILURE; } @@ -345,7 +357,7 @@ dctool_download_run (int argc, char *argv[], dc_context_t *context, dc_descripto } // Download the dives. - status = download (context, descriptor, transport, argv[0], cachedir, fingerprint, output); + status = download (context, descriptor, transport, argv[0], cachedir, fingerprint, output, limit); if (status != DC_STATUS_SUCCESS) { message ("ERROR: %s\n", dctool_errmsg (status)); exitcode = EXIT_FAILURE; @@ -375,6 +387,7 @@ const dctool_command_t dctool_download = { " -c, --cache Cache directory\n" " -f, --format Output format\n" " -u, --units Set units (metric or imperial)\n" + " -l, --limit Maximum number of dives to download\n" #else " -h Show help message\n" " -t Transport type\n" @@ -383,6 +396,7 @@ const dctool_command_t dctool_download = { " -c Cache directory\n" " -f Output format\n" " -u Set units (metric or imperial)\n" + " -l Maximum number of dives to download\n" #endif "\n" "Supported output formats:\n" diff --git a/src/Makefile.am b/src/Makefile.am index fb95fdc7..87cd8ab0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -77,6 +77,7 @@ libdivecomputer_la_SOURCES = \ liquivision_lynx.h liquivision_lynx.c liquivision_lynx_parser.c \ sporasub_sp2.h sporasub_sp2.c sporasub_sp2_parser.c \ deepsix_excursion.h deepsix_excursion.c deepsix_excursion_parser.c \ + seac_screen_common.h seac_screen_common.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 \ diff --git a/src/array.c b/src/array.c index 6d97557c..b447b1d0 100644 --- a/src/array.c +++ b/src/array.c @@ -302,6 +302,15 @@ array_uint16_le (const unsigned char data[]) ((unsigned int) data[1] << 8); } +float +array_float_le (const unsigned char data[]) +{ + float result = 0; + unsigned int value = array_uint32_le (data); + memcpy (&result, &value, sizeof(value)); + return result; +} + void array_uint64_be_set (unsigned char data[], const unsigned long long input) { diff --git a/src/array.h b/src/array.h index 81268779..d97abfe5 100644 --- a/src/array.h +++ b/src/array.h @@ -97,6 +97,9 @@ array_uint16_be (const unsigned char data[]); unsigned short array_uint16_le (const unsigned char data[]); +float +array_float_le (const unsigned char data[]); + void array_uint64_be_set (unsigned char data[], const unsigned long long input); diff --git a/src/deepblu_cosmiq_parser.c b/src/deepblu_cosmiq_parser.c index 95b5ae78..635a446a 100644 --- a/src/deepblu_cosmiq_parser.c +++ b/src/deepblu_cosmiq_parser.c @@ -191,6 +191,12 @@ deepblu_cosmiq_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback unsigned int time = 0; unsigned int offset = SZ_HEADER; while (offset + SZ_SAMPLE <= size) { + // Ignore empty samples. + if (array_isequal (data + offset, SZ_SAMPLE, 0xFF)) { + offset += SZ_SAMPLE; + continue; + } + dc_sample_value_t sample = {0}; unsigned int temperature = array_uint16_le(data + offset + 0); unsigned int depth = array_uint16_le(data + offset + 2); diff --git a/src/descriptor.c b/src/descriptor.c index e3a8bc8b..44c6c565 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -323,9 +323,11 @@ static const dc_descriptor_t g_descriptors[] = { {"Mares", "Puck Air 2", DC_FAMILY_MARES_ICONHD , 0x2D, DC_TRANSPORT_BLE, dc_filter_mares}, {"Mares", "Sirius", DC_FAMILY_MARES_ICONHD , 0x2F, DC_TRANSPORT_BLE, dc_filter_mares}, {"Mares", "Quad Ci", DC_FAMILY_MARES_ICONHD , 0x31, DC_TRANSPORT_BLE, dc_filter_mares}, + {"Mares", "Quad 2", DC_FAMILY_MARES_ICONHD , 0x32, DC_TRANSPORT_BLE, dc_filter_mares}, {"Mares", "Puck 4", DC_FAMILY_MARES_ICONHD , 0x35, DC_TRANSPORT_BLE, dc_filter_mares}, {"Mares", "Puck Lite", DC_FAMILY_MARES_ICONHD , 0x35, DC_TRANSPORT_BLE, dc_filter_mares}, {"Mares", "Puck Pro EZ", DC_FAMILY_MARES_ICONHD , 0x35, DC_TRANSPORT_BLE, dc_filter_mares}, + {"Mares", "Puck Pro Ultra", DC_FAMILY_MARES_ICONHD , 0x35, DC_TRANSPORT_BLE, dc_filter_mares}, /* Heinrichs Weikamp */ {"Heinrichs Weikamp", "OSTC", DC_FAMILY_HW_OSTC, 0, DC_TRANSPORT_SERIAL, NULL}, {"Heinrichs Weikamp", "OSTC Mk2", DC_FAMILY_HW_OSTC, 1, DC_TRANSPORT_SERIAL, NULL}, @@ -792,9 +794,11 @@ dc_filter_mares (const dc_descriptor_t *descriptor, dc_transport_t transport, co "Mares Genius", "Sirius", "Quad Ci", + "Quad2", "Puck4", "Puck Lite", "Puck", + "Puck Pro U", }; if (transport == DC_TRANSPORT_BLE) { diff --git a/src/halcyon_symbios.c b/src/halcyon_symbios.c index 1ecdb992..3eabf806 100644 --- a/src/halcyon_symbios.c +++ b/src/halcyon_symbios.c @@ -210,7 +210,7 @@ halcyon_symbios_recv (halcyon_symbios_device_t *device, unsigned char cmd, unsig } static dc_status_t -halcyon_symbios_transfer (halcyon_symbios_device_t *device, unsigned char cmd, const unsigned char data[], unsigned int size, unsigned char answer[], unsigned int asize, unsigned int *errorcode) +halcyon_symbios_transfer (halcyon_symbios_device_t *device, unsigned char cmd, const unsigned char data[], unsigned int size, unsigned char answer[], unsigned int asize, unsigned int *actual, unsigned int *errorcode) { dc_status_t status = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; @@ -231,11 +231,16 @@ halcyon_symbios_transfer (halcyon_symbios_device_t *device, unsigned char cmd, c goto error_exit; } - // Verify the length of the packet. - if (length != asize) { - ERROR (abstract->context, "Unexpected packet length (%u).", length); - status = DC_STATUS_PROTOCOL; - goto error_exit; + if (actual == NULL) { + // Verify the length of the packet. + if (length != asize) { + ERROR (abstract->context, "Unexpected packet length (%u).", length); + status = DC_STATUS_PROTOCOL; + goto error_exit; + } + } else { + // Return the actual length. + *actual = length; } error_exit: @@ -257,7 +262,7 @@ halcyon_symbios_download (halcyon_symbios_device_t *device, dc_event_progress_t // Request the data. unsigned char response[4] = {0}; - status = halcyon_symbios_transfer (device, request, data, size, response, sizeof(response), &errcode); + status = halcyon_symbios_transfer (device, request, data, size, response, sizeof(response), NULL, &errcode); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to request the data."); goto error_exit; @@ -438,19 +443,27 @@ halcyon_symbios_device_foreach (dc_device_t *abstract, dc_dive_callback_t callba device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); // Read the device status. - unsigned char info[20] = {0}; - status = halcyon_symbios_transfer (device, CMD_GET_STATUS, NULL, 0, info, sizeof(info), NULL); + unsigned char info[36] = {0}; + unsigned int info_size = 0; + status = halcyon_symbios_transfer (device, CMD_GET_STATUS, NULL, 0, info, sizeof(info), &info_size, NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the device status."); goto error_exit; } - HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Version", info, sizeof(info)); + // Verify the length of the packet. + if (info_size < 20) { + ERROR (abstract->context, "Unexpected packet length (%u).", info_size); + status = DC_STATUS_PROTOCOL; + goto error_exit; + } + + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "Version", info, info_size); // Emit a vendor event. dc_event_vendor_t vendor; vendor.data = info; - vendor.size = sizeof(info); + vendor.size = info_size; device_event_emit (abstract, DC_EVENT_VENDOR, &vendor); // Emit a device info event. @@ -568,7 +581,7 @@ halcyon_symbios_device_timesync (dc_device_t *abstract, const dc_datetime_t *dat datetime->minute, datetime->second, }; - status = halcyon_symbios_transfer (device, CMD_SET_TIME, request, sizeof(request), NULL, 0, NULL); + status = halcyon_symbios_transfer (device, CMD_SET_TIME, request, sizeof(request), NULL, 0, NULL, NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to set the time."); goto error_exit; diff --git a/src/halcyon_symbios_parser.c b/src/halcyon_symbios_parser.c index ae5f4202..485e4701 100644 --- a/src/halcyon_symbios_parser.c +++ b/src/halcyon_symbios_parser.c @@ -55,6 +55,10 @@ (type) == ID_HEADER || \ (type) == ID_FOOTER) +#define LOGVERSION(major,minor) ( \ + (((major) & 0xFF) << 8) | \ + ((minor) & 0xFF)) + #define UNDEFINED 0xFFFFFFFF #define EPOCH 1609459200 /* 2021-01-01 00:00:00 */ @@ -87,7 +91,9 @@ typedef struct halcyon_symbios_parser_t { dc_parser_t base; // Cached fields. unsigned int cached; + unsigned int logversion; unsigned int datetime; + int timezone; unsigned int divetime; unsigned int maxdepth; unsigned int divemode; @@ -135,7 +141,9 @@ halcyon_symbios_parser_create (dc_parser_t **out, dc_context_t *context, const u // Set the default values. parser->cached = 0; + parser->logversion = 0; parser->datetime = UNDEFINED; + parser->timezone = 0; parser->divetime = 0; parser->maxdepth = 0; parser->divemode = UNDEFINED; @@ -182,8 +190,23 @@ halcyon_symbios_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datet dc_ticks_t ticks = (dc_ticks_t) parser->datetime + EPOCH; - if (!dc_datetime_localtime (datetime, ticks)) - return DC_STATUS_DATAFORMAT; + if (parser->logversion >= LOGVERSION(1,9)) { + // For firmware versions with timezone support, the UTC offset of the + // device is used. + int timezone = parser->timezone * 3600; + + ticks += timezone; + + if (!dc_datetime_gmtime (datetime, ticks)) + return DC_STATUS_DATAFORMAT; + + datetime->timezone = timezone; + } else { + // For firmware versions without timezone support, the current timezone + // of the host system is used. + if (!dc_datetime_localtime (datetime, ticks)) + return DC_STATUS_DATAFORMAT; + } return DC_STATUS_SUCCESS; } @@ -311,7 +334,9 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac 6, /* ID_GF_INFO */ }; + unsigned int logversion = 0; unsigned int time_start = UNDEFINED, time_end = UNDEFINED; + int timezone = 0; unsigned int maxdepth = 0; unsigned int divemode = UNDEFINED; unsigned int atmospheric = UNDEFINED; @@ -367,6 +392,7 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac } if (type == ID_LOG_VERSION) { + logversion = array_uint16_be (data + offset + 2); unsigned int version_major = data[offset + 2]; unsigned int version_minor = data[offset + 3]; DEBUG (abstract->context, "Version: %u.%u", @@ -384,6 +410,7 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac unsigned int DC_ATTR_UNUSED detection = data[offset + 11]; unsigned int DC_ATTR_UNUSED noflytime = data[offset + 12]; divemode = data[offset + 13]; + timezone = (signed char) data[offset + 14]; atmospheric = array_uint16_le(data + offset + 16); unsigned int DC_ATTR_UNUSED number = array_uint16_le(data + offset + 18); unsigned int DC_ATTR_UNUSED battery = array_uint16_le(data + offset + 20); @@ -687,7 +714,9 @@ halcyon_symbios_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callbac } parser->cached = 1; + parser->logversion = logversion; parser->datetime = time_start; + parser->timezone = timezone; if (time_start != UNDEFINED && time_end != UNDEFINED) { parser->divetime = time_end - time_start; } else { diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index 160e5d1b..86f1cd32 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -86,7 +86,7 @@ #define OSTC4FW(major,minor,micro,beta) ( \ (((major) & 0x1F) << 11) | \ - (((minor) & 0x1F) >> 6) | \ + (((minor) & 0x1F) << 6) | \ (((micro) & 0x1F) << 1) | \ ((beta) & 0x01)) @@ -944,8 +944,18 @@ hw_ostc_parser_internal_foreach (hw_ostc_parser_t *parser, dc_sample_callback_t unsigned int firmware = 0; if (parser->model == OSTC4) { firmware = array_uint16_le (data + layout->firmware); + DEBUG (abstract->context, "Device: firmware=%u (%u.%u.%u.%u)", + firmware, + (firmware >> 11) & 0x1F, + (firmware >> 6) & 0x1F, + (firmware >> 1) & 0x1F, + (firmware ) & 0x01); } else { firmware = array_uint16_be (data + layout->firmware); + DEBUG (abstract->context, "Device: firmware=%u (%u.%u)", + firmware, + (firmware >> 8) & 0xFF, + (firmware ) & 0xFF); } // Get the dive mode. @@ -1174,6 +1184,7 @@ hw_ostc_parser_internal_foreach (hw_ostc_parser_t *parser, dc_sample_callback_t if (callback) { unsigned int value = array_uint16_le(data + offset); + dc_sample_type_t eventType = DC_SAMPLE_EVENT; dc_sample_value_t sample = { .event.type = SAMPLE_EVENT_STRING, .event.flags = SAMPLE_FLAGS_SEVERITY_INFO, @@ -1183,12 +1194,13 @@ hw_ostc_parser_internal_foreach (hw_ostc_parser_t *parser, dc_sample_callback_t if (value & OSTC4_COMPASS_HEADING_CLEARED_FLAG) { snprintf(buf, BUFLEN, "Cleared compass heading"); } else { - sample.event.value = heading; if (value & OSTC4_COMPASS_HEADING_SET_FLAG) { - sample.event.type = SAMPLE_EVENT_HEADING; + eventType = DC_SAMPLE_BEARING; + sample.bearing = heading; snprintf(buf, BUFLEN, "Set compass heading [degrees]%s", sample.event.value ? "" : ": 0"); } else { + sample.event.value = heading; snprintf(buf, BUFLEN, "Logged compass heading [degrees]%s", sample.event.value ? "" : ": 0"); } @@ -1196,13 +1208,24 @@ hw_ostc_parser_internal_foreach (hw_ostc_parser_t *parser, dc_sample_callback_t sample.event.name = buf; - callback(DC_SAMPLE_EVENT, &sample, userdata); + callback(eventType, &sample, userdata); } offset += 2; length -= 2; } + // GNSS position + if (events & 0x0400) { + if (length < 8) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + offset += 8; + length -= 8; + } + // Scrubber state update if (events & 0x0800) { if (length < 2) { @@ -1251,6 +1274,7 @@ hw_ostc_parser_internal_foreach (hw_ostc_parser_t *parser, dc_sample_callback_t offset += 2; length -= 2; } + } // Extended sample info. diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index 11b8a0dc..ea3fa563 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -51,6 +51,7 @@ #define PUCKAIR2 0x2D #define SIRIUS 0x2F #define QUADCI 0x31 +#define QUAD2 0x32 #define PUCK4 0x35 #define ISSMART(model) ( \ @@ -64,12 +65,14 @@ (model) == PUCKAIR2 || \ (model) == SIRIUS || \ (model) == QUADCI || \ + (model) == QUAD2 || \ (model) == PUCK4) #define ISSIRIUS(model) ( \ (model) == PUCKAIR2 || \ (model) == SIRIUS || \ (model) == QUADCI || \ + (model) == QUAD2 || \ (model) == PUCK4) #define MAXRETRIES 4 @@ -195,9 +198,11 @@ mares_iconhd_get_model (mares_iconhd_device_t *device) {"Puck Air 2", PUCKAIR2}, {"Sirius", SIRIUS}, {"Quad Ci", QUADCI}, + {"Quad2", QUAD2}, {"Puck4", PUCK4}, {"Puck Lite", PUCK4}, {"Puck", PUCK4}, + {"Puck Pro U", PUCK4}, }; // Check the product name in the version packet against the list @@ -667,6 +672,7 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_ case PUCKAIR2: case SIRIUS: case QUADCI: + case QUAD2: case PUCK4: device->layout = &mares_genius_layout; device->packetsize = 4096; diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index b7545601..5be9c969 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -51,6 +51,7 @@ #define PUCKAIR2 0x2D #define SIRIUS 0x2F #define QUADCI 0x31 +#define QUAD2 0x32 #define PUCK4 0x35 #define ISSMART(model) ( \ @@ -64,6 +65,7 @@ (model) == PUCKAIR2 || \ (model) == SIRIUS || \ (model) == QUADCI || \ + (model) == QUAD2 || \ (model) == PUCK4) #define NGASMIXES_ICONHD 3 diff --git a/src/pelagic_i330r.c b/src/pelagic_i330r.c index 17aad3bf..8cbbed95 100644 --- a/src/pelagic_i330r.c +++ b/src/pelagic_i330r.c @@ -58,11 +58,14 @@ #define RSP_READY 1 #define RSP_DONE 2 +#define RSP_ACCESSCODE_INVALID 13 #define MAXPACKET 255 #define MAXPASSCODE 6 +#define MAXRETRIES 3 + typedef struct pelagic_i330r_device_t { oceanic_common_device_t base; dc_iostream_t *iostream; @@ -276,6 +279,8 @@ pelagic_i330r_transfer (pelagic_i330r_device_t *device, unsigned char cmd, unsig if (errorcode != response) { ERROR (abstract->context, "Unexpected response code (%u)", errorcode); + if (errorcode == RSP_ACCESSCODE_INVALID) + return DC_STATUS_NOACCESS; return DC_STATUS_PROTOCOL; } @@ -388,14 +393,29 @@ pelagic_i330r_init (pelagic_i330r_device_t *device) return status; } - if (array_isequal (device->accesscode, sizeof(device->accesscode), 0)) { - // Request to display the PIN code. + unsigned int nretries = 0; + while (1) { + // Try to request access. status = pelagic_i330r_init_accesscode (device); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to display the PIN code."); + if (status != DC_STATUS_SUCCESS && status != DC_STATUS_NOACCESS) { + ERROR (abstract->context, "Failed to request access."); return status; } + if (array_isequal (device->accesscode, sizeof(device->accesscode), 0)) { + WARNING (abstract->context, "Access denied, no access code."); + } else { + if (status == DC_STATUS_SUCCESS) + break; + WARNING (abstract->context, "Access denied, invalid access code."); + } + + // Abort if the maximum number of retries is reached. + if (nretries++ >= MAXRETRIES) { + ERROR (abstract->context, "Maximum number of retries reached."); + return DC_STATUS_NOACCESS; + } + // Get the bluetooth PIN code. char pincode[6 + 1] = {0}; status = dc_iostream_ioctl (device->iostream, DC_IOCTL_BLE_GET_PINCODE, pincode, sizeof(pincode)); @@ -422,13 +442,6 @@ pelagic_i330r_init (pelagic_i330r_device_t *device) } } - // Request access. - status = pelagic_i330r_init_accesscode (device); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to request access."); - return status; - } - // Send the wakeup command. status = pelagic_i330r_init_handshake (device, 1); if (status != DC_STATUS_SUCCESS) { diff --git a/src/platform.h b/src/platform.h index f98f0d7a..5f7ca962 100644 --- a/src/platform.h +++ b/src/platform.h @@ -22,6 +22,7 @@ #ifndef DC_PLATFORM_H #define DC_PLATFORM_H +#include #include #ifdef __cplusplus diff --git a/src/seac_screen.c b/src/seac_screen.c index e8f91c67..06ac942a 100644 --- a/src/seac_screen.c +++ b/src/seac_screen.c @@ -23,6 +23,7 @@ #include // malloc, free #include "seac_screen.h" +#include "seac_screen_common.h" #include "context-private.h" #include "device-private.h" #include "ringbuffer.h" @@ -66,11 +67,8 @@ #define SZ_ADDRESS 4 #define SZ_READ 2048 -#define SZ_HEADER 128 -#define SZ_SAMPLE 64 - -#define FP_OFFSET 0x0A -#define FP_SIZE 7 +#define FP_OFFSET 0 +#define FP_SIZE 4 #define RB_PROFILE_DISTANCE(a,b,l) ringbuffer_distance (a, b, DC_RINGBUFFER_FULL, l->rb_profile_begin, l->rb_profile_end) #define RB_PROFILE_INCR(a,b,l) ringbuffer_increment (a, b, l->rb_profile_begin, l->rb_profile_end) @@ -95,15 +93,10 @@ typedef struct seac_screen_device_t { dc_iostream_t *iostream; const seac_screen_commands_t *cmds; const seac_screen_layout_t *layout; + unsigned int fingerprint; unsigned char info[SZ_HWINFO + SZ_SWINFO]; - unsigned char fingerprint[FP_SIZE]; } seac_screen_device_t; -typedef struct seac_screen_logbook_t { - unsigned int address; - unsigned char header[SZ_HEADER]; -} seac_screen_logbook_t; - static dc_status_t seac_screen_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); static dc_status_t seac_screen_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size); static dc_status_t seac_screen_device_dump (dc_device_t *abstract, dc_buffer_t *buffer); @@ -329,8 +322,8 @@ seac_screen_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t device->iostream = iostream; device->cmds = NULL; device->layout = NULL; + device->fingerprint = 0; memset (device->info, 0, sizeof (device->info)); - memset (device->fingerprint, 0, sizeof (device->fingerprint)); // Set the serial communication protocol (115200 8N1). status = dc_iostream_configure (device->iostream, 115200, 8, DC_PARITY_NONE, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE); @@ -395,13 +388,14 @@ seac_screen_device_set_fingerprint (dc_device_t *abstract, const unsigned char d { seac_screen_device_t *device = (seac_screen_device_t *) abstract; - if (size && size != sizeof (device->fingerprint)) + if (size && size != FP_SIZE) return DC_STATUS_INVALIDARGS; - if (size) - memcpy (device->fingerprint, data, sizeof (device->fingerprint)); - else - memset (device->fingerprint, 0, sizeof (device->fingerprint)); + if (size) { + device->fingerprint = array_uint32_le (data); + } else { + device->fingerprint = 0; + } return DC_STATUS_SUCCESS; } @@ -533,21 +527,35 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, // Calculate the number of dives. unsigned int ndives = last - first + 1; + // Check the fingerprint. + if (device->fingerprint >= last) { + ndives = 0; + } else if (device->fingerprint >= first) { + first = device->fingerprint + 1; + ndives = last - first + 1; + } + // Update and emit a progress event. progress.current += SZ_RANGE; - progress.maximum += SZ_RANGE + ndives * (SZ_ADDRESS + SZ_HEADER); + progress.maximum += SZ_RANGE + ndives * SZ_ADDRESS; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - // Allocate memory for the logbook data. - seac_screen_logbook_t *logbook = (seac_screen_logbook_t *) malloc (ndives * sizeof (seac_screen_logbook_t)); - if (logbook == NULL) { + // Exit if no dives to download. + if (ndives == 0) { + goto error_exit; + } + + // Allocate memory for the dive addresses. + unsigned int *address = (unsigned int *) malloc (ndives * sizeof (unsigned int)); + if (address == NULL) { status = DC_STATUS_NOMEMORY; goto error_exit; } - // Read the header of each dive in reverse order (most recent first). + // Read the address of each dive in reverse order (most recent first). unsigned int eop = 0; unsigned int previous = 0; + unsigned int begin = 0; unsigned int count = 0; unsigned int skip = 0; unsigned int rb_profile_size = 0; @@ -569,55 +577,71 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, goto error_free_logbook; } - // Get the dive address. - logbook[i].address = array_uint32_be (rsp_address); - if (logbook[i].address < layout->rb_profile_begin || logbook[i].address >= layout->rb_profile_end) { - ERROR (abstract->context, "Invalid ringbuffer pointer (0x%08x).", logbook[i].address); - status = DC_STATUS_DATAFORMAT; - goto error_free_logbook; - } - - // Read the dive header. - status = seac_screen_device_read (abstract, logbook[i].address, logbook[i].header, SZ_HEADER); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the dive header."); - goto error_free_logbook; - } - // Update and emit a progress event. - progress.current += SZ_ADDRESS + SZ_HEADER; + progress.current += SZ_ADDRESS; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - // Check the header checksums. - if (checksum_crc16_ccitt (logbook[i].header, SZ_HEADER / 2, 0xFFFF, 0x0000) != 0 || - checksum_crc16_ccitt (logbook[i].header + SZ_HEADER / 2, SZ_HEADER / 2, 0xFFFF, 0x0000) != 0) { - ERROR (abstract->context, "Unexpected header checksum."); + // Get the dive address. + address[i] = array_uint32_be (rsp_address); + if (address[i] < layout->rb_profile_begin || address[i] >= layout->rb_profile_end) { + ERROR (abstract->context, "Invalid ringbuffer pointer (0x%08x).", address[i]); status = DC_STATUS_DATAFORMAT; goto error_free_logbook; } - // Check the fingerprint. - if (memcmp (logbook[i].header + FP_OFFSET, device->fingerprint, sizeof (device->fingerprint)) == 0) { - skip = 1; - break; - } - - // Get the number of samples. - unsigned int nsamples = array_uint32_le (logbook[i].header + 0x44); - unsigned int nbytes = SZ_HEADER + nsamples * SZ_SAMPLE; - // Get the end-of-profile pointer. if (eop == 0) { - eop = previous = RB_PROFILE_INCR (logbook[i].address, nbytes, layout); + // Read the dive header. + unsigned char header[SZ_HEADER] = {0}; + status = seac_screen_device_read (abstract, address[i], header, sizeof(header)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the dive header."); + goto error_free_logbook; + } + + // Update and emit a progress event. + progress.current += sizeof(header); + progress.maximum += sizeof(header); + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Check the header records. + unsigned int isvalid = 1; + for (unsigned int j = 0; j < 2; ++j) { + unsigned int type = j == 0 ? HEADER1 : HEADER2; + if (!seac_screen_record_isvalid (abstract->context, + header + j * SZ_HEADER / 2, SZ_HEADER / 2, + type, number)) { + WARNING (abstract->context, "Invalid header record %u.", j); + isvalid = 0; + } + } + + // For dives with an invalid header, the number of samples in the + // header is not guaranteed to be valid. Discard the entire dive + // instead and take its start address as the end of the profile. + if (!isvalid) { + WARNING (abstract->context, "Unable to locate the end of the profile."); + eop = previous = address[i]; + begin = 1; + skip++; + continue; + } + + // Get the number of samples. + unsigned int nsamples = array_uint32_le (header + 0x44); + unsigned int nbytes = SZ_HEADER + nsamples * SZ_SAMPLE; + + // Calculate the end of the profile. + eop = previous = RB_PROFILE_INCR (address[i], nbytes, layout); } // Calculate the length. - unsigned int length = RB_PROFILE_DISTANCE (logbook[i].address, previous, layout); + unsigned int length = RB_PROFILE_DISTANCE (address[i], previous, layout); // Check for the end of the ringbuffer. if (length > remaining) { WARNING (abstract->context, "Reached the end of the ringbuffer."); - skip = 1; + skip++; break; } @@ -626,12 +650,12 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, // Move to the start of the current dive. remaining -= length; - previous = logbook[i].address; + previous = address[i]; count++; } // Update and emit a progress event. - progress.maximum -= (ndives - count - skip) * (SZ_ADDRESS + SZ_HEADER) + + progress.maximum -= (ndives - count - skip) * SZ_ADDRESS + ((layout->rb_profile_end - layout->rb_profile_begin) - rb_profile_size); device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); @@ -658,12 +682,15 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, previous = eop; unsigned int offset = rb_profile_size; for (unsigned int i = 0; i < count; ++i) { + unsigned int idx = begin + i; + unsigned int number = last - idx; + // Calculate the length. - unsigned int length = RB_PROFILE_DISTANCE (logbook[i].address, previous, layout); + unsigned int length = RB_PROFILE_DISTANCE (address[idx], previous, layout); // Move to the start of the current dive. offset -= length; - previous = logbook[i].address; + previous = address[idx]; // Read the dive. status = dc_rbstream_read (rbstream, &progress, profile + offset, length); @@ -672,26 +699,46 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, goto error_free_rbstream; } - // Check the dive header. - if (memcmp (profile + offset, logbook[i].header, SZ_HEADER) != 0) { - ERROR (abstract->context, "Unexpected dive header."); + // Check the minimum header length. + if (length < SZ_HEADER) { + ERROR (abstract->context, "Unexpected dive length (%u).", length); status = DC_STATUS_DATAFORMAT; goto error_free_rbstream; } + // Check the header records. + unsigned int isvalid = 1; + for (unsigned int j = 0; j < 2; ++j) { + unsigned int type = j == 0 ? HEADER1 : HEADER2; + if (!seac_screen_record_isvalid (abstract->context, + profile + offset + j * SZ_HEADER / 2, SZ_HEADER / 2, + type, number)) { + WARNING (abstract->context, "Invalid header record %u.", j); + isvalid = 0; + } + } + // Get the number of samples. // The actual size of the dive, based on the number of samples, can // sometimes be smaller than the maximum length. In that case, the // remainder of the data is padded with 0xFF bytes. - unsigned int nsamples = array_uint32_le (logbook[i].header + 0x44); - unsigned int nbytes = SZ_HEADER + nsamples * SZ_SAMPLE; + unsigned int nsamples = 0; + unsigned int nbytes = 0; + if (isvalid) { + nsamples = array_uint32_le (profile + offset + 0x44); + nbytes = SZ_HEADER + nsamples * SZ_SAMPLE; + } else { + WARNING (abstract->context, "Unable to locate the padding bytes."); + nbytes = length; + } + if (nbytes > length) { ERROR (abstract->context, "Unexpected dive length (%u %u).", nbytes, length); status = DC_STATUS_DATAFORMAT; goto error_free_rbstream; } - if (callback && !callback (profile + offset, nbytes, profile + offset + FP_OFFSET, sizeof(device->fingerprint), userdata)) { + if (callback && !callback (profile + offset, nbytes, profile + offset + FP_OFFSET, FP_SIZE, userdata)) { break; } } @@ -701,7 +748,7 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, error_free_profile: free (profile); error_free_logbook: - free (logbook); + free (address); error_exit: return status; } diff --git a/src/seac_screen_common.c b/src/seac_screen_common.c new file mode 100644 index 00000000..f5890a26 --- /dev/null +++ b/src/seac_screen_common.c @@ -0,0 +1,58 @@ +/* + * libdivecomputer + * + * Copyright (C) 2025 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 "seac_screen_common.h" +#include "context-private.h" +#include "checksum.h" +#include "array.h" + +int +seac_screen_record_isvalid (dc_context_t *context, const unsigned char data[], unsigned int size, unsigned int type, unsigned int id) +{ + // Check the record size. + if (size != SZ_RECORD) { + ERROR (context, "Unexpected record size (%u).", size); + return 0; + } + + // Check the record checksum. + unsigned short csum = checksum_crc16_ccitt (data, size, 0xFFFF, 0x0000); + if (csum != 0) { + ERROR (context, "Unexpected record checksum (%04x).", csum); + return 0; + } + + // Check the record type. + unsigned int rtype = data[size - 3]; + if (rtype != type) { + ERROR (context, "Unexpected record type (%02x %02x).", rtype, type); + return 0; + } + + // Check the record id. + unsigned int rid = array_uint32_le (data); + if (rid != id) { + ERROR (context, "Unexpected record id (%u %u).", rid, id); + return 0; + } + + return 1; +} diff --git a/src/seac_screen_common.h b/src/seac_screen_common.h new file mode 100644 index 00000000..054e56b3 --- /dev/null +++ b/src/seac_screen_common.h @@ -0,0 +1,45 @@ +/* + * libdivecomputer + * + * Copyright (C) 2025 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 SEAC_SCREEN_COMMON_H +#define SEAC_SCREEN_COMMON_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define HEADER1 0xCF +#define HEADER2 0xC0 +#define SAMPLE 0xAA + +#define SZ_RECORD 64 +#define SZ_HEADER (SZ_RECORD * 2) +#define SZ_SAMPLE SZ_RECORD + +int +seac_screen_record_isvalid (dc_context_t *context, const unsigned char data[], unsigned int size, unsigned int type, unsigned int id); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* SEAC_SCREEN_COMMON_H */ diff --git a/src/seac_screen_parser.c b/src/seac_screen_parser.c index b19c8ea9..42cf0864 100644 --- a/src/seac_screen_parser.c +++ b/src/seac_screen_parser.c @@ -22,16 +22,13 @@ #include #include "seac_screen.h" +#include "seac_screen_common.h" #include "context-private.h" #include "parser-private.h" -#include "checksum.h" #include "array.h" #define ISINSTANCE(parser) dc_device_isinstance((parser), &seac_screen_parser_vtable) -#define SZ_HEADER 128 -#define SZ_SAMPLE 64 - #define NGASMIXES 2 #define INVALID 0xFFFFFFFF @@ -152,6 +149,20 @@ seac_screen_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) if (abstract->size < SZ_HEADER) return DC_STATUS_DATAFORMAT; + // Get the dive ID. + unsigned int dive_id = array_uint32_le (data + 0x00); + + // Check the header records. + for (unsigned int i = 0; i < 2; ++i) { + unsigned int type = i == 0 ? HEADER1 : HEADER2; + if (!seac_screen_record_isvalid (abstract->context, + data + i * SZ_HEADER / 2, SZ_HEADER / 2, + type, dive_id)) { + ERROR (abstract->context, "Invalid header record %u.", i); + return DC_STATUS_DATAFORMAT; + } + } + // The date/time is stored as UTC time with a timezone offset. To convert to // local time, the UTC time is first converted to unix time (seconds since // the epoch), then adjusted for the timezone offset, and finally converted @@ -271,14 +282,20 @@ seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t if (abstract->size < SZ_HEADER) return DC_STATUS_DATAFORMAT; - if (checksum_crc16_ccitt (data, SZ_HEADER / 2, 0xFFFF, 0x0000) != 0 || - checksum_crc16_ccitt (data + SZ_HEADER / 2, SZ_HEADER / 2, 0xFFFF, 0x0000) != 0) { - ERROR (abstract->context, "Unexpected header checksum."); - return DC_STATUS_DATAFORMAT; - } - + // Get the dive ID. unsigned int dive_id = array_uint32_le (data + 0x00); + // Check the header records. + for (unsigned int i = 0; i < 2; ++i) { + unsigned int type = i == 0 ? HEADER1 : HEADER2; + if (!seac_screen_record_isvalid (abstract->context, + data + i * SZ_HEADER / 2, SZ_HEADER / 2, + type, dive_id)) { + ERROR (abstract->context, "Invalid header record %u.", i); + return DC_STATUS_DATAFORMAT; + } + } + unsigned int ngasmixes = 0; unsigned int oxygen[NGASMIXES] = {0}; unsigned int o2_previous = INVALID; @@ -291,12 +308,12 @@ seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t while (offset + SZ_SAMPLE <= size) { dc_sample_value_t sample = {0}; - if (checksum_crc16_ccitt (data + offset, SZ_SAMPLE, 0xFFFF, 0x0000) != 0) { - ERROR (abstract->context, "Unexpected sample checksum."); + // Check the sample record. + if (!seac_screen_record_isvalid (abstract->context, data + offset, SZ_SAMPLE, SAMPLE, dive_id)) { + ERROR (abstract->context, "Invalid sample record."); return DC_STATUS_DATAFORMAT; } - unsigned int id = array_uint32_le (data + offset + 0x00); unsigned int timestamp = array_uint32_le (data + offset + 0x04); unsigned int depth = array_uint16_le (data + offset + 0x08); unsigned int temperature = array_uint16_le (data + offset + 0x0A); @@ -309,11 +326,6 @@ seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t 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); - return DC_STATUS_DATAFORMAT; - } - // Time (seconds). if (timestamp < time) { ERROR (abstract->context, "Timestamp moved backwards (%u %u).", timestamp, time); diff --git a/src/shearwater_common.c b/src/shearwater_common.c index 0bb2bf99..0a863f19 100644 --- a/src/shearwater_common.c +++ b/src/shearwater_common.c @@ -746,6 +746,7 @@ dc_status_t shearwater_common_get_model(shearwater_common_device_t *device, unsi case 0x0C0D: case 0x7C2D: case 0x8D6C: + case 0x425B: *model = PERDIXAI; break; case 0x704C: @@ -763,6 +764,7 @@ dc_status_t shearwater_common_get_model(shearwater_common_device_t *device, unsi *model = PEREGRINE; break; case 0x1712: + case 0x813A: *model = PEREGRINE_TX; break; case 0xC0E0: diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index c9ad0004..9b86b6c8 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -39,6 +39,7 @@ #define LOG_RECORD_DIVE_SAMPLE 0x01 #define LOG_RECORD_FREEDIVE_SAMPLE 0x02 +#define LOG_RECORD_AVELO_SAMPLE 0x03 #define LOG_RECORD_OPENING_0 0x10 #define LOG_RECORD_OPENING_1 0x11 #define LOG_RECORD_OPENING_2 0x12 @@ -80,6 +81,7 @@ #define M_CC2 5 #define M_OC_REC 6 #define M_FREEDIVE 7 +#define M_AVELO 12 #define SM_3_GAS_NX 0 #define SM_AIR 1 @@ -540,10 +542,15 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) // Get the record type. unsigned int type = pnf ? data[offset] : LOG_RECORD_DIVE_SAMPLE; - if (type == LOG_RECORD_DIVE_SAMPLE) { + if (type == LOG_RECORD_DIVE_SAMPLE || + type == LOG_RECORD_AVELO_SAMPLE) { // Status flags. - unsigned int status = data[offset + 11 + pnf]; - unsigned int ccr = (status & OC) == 0; + unsigned int status = 0; + unsigned int ccr = 0; + if (type != LOG_RECORD_AVELO_SAMPLE) { + status = data[offset + 11 + pnf]; + ccr = (status & OC) == 0; + } if (ccr) { divemode = status & SC ? M_SC : M_CC; } @@ -583,7 +590,8 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) // Tank pressure if (logversion >= 7) { const unsigned int idx[2] = {27, 19}; - for (unsigned int i = 0; i < 2; ++i) { + const unsigned int count = type == LOG_RECORD_AVELO_SAMPLE ? 1 : 2; + for (unsigned int i = 0; i < count; ++i) { // Values above 0xFFF0 are special codes: // 0xFFFF AI is off // 0xFFFE No comms for 90 seconds+ @@ -1038,7 +1046,30 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ *((double *) value) = parser->atmospheric / 1000.0; break; case DC_FIELD_DIVEMODE: - return DC_FIELD_VALUE(parser->cache, value, DIVEMODE); + switch (parser->divemode) { + case M_CC: + case M_CC2: + *((dc_divemode_t *) value) = DC_DIVEMODE_CCR; + break; + case M_SC: + *((dc_divemode_t *) value) = DC_DIVEMODE_SCR; + break; + case M_OC_TEC: + case M_OC_REC: + case M_AVELO: + *((dc_divemode_t *) value) = DC_DIVEMODE_OC; + break; + case M_GAUGE: + case M_PPO2: + *((dc_divemode_t *) value) = DC_DIVEMODE_GAUGE; + break; + case M_FREEDIVE: + *((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE; + break; + default: + return DC_STATUS_DATAFORMAT; + } + break; case DC_FIELD_DECOMODEL: switch (data[decomodel_idx]) { case GF: @@ -1109,11 +1140,8 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal // Get the record type. unsigned int type = pnf ? data[offset] : LOG_RECORD_DIVE_SAMPLE; - // stop parsing if we see the end block - if (type == LOG_RECORD_FINAL && data[offset + 1] == 0xFD) - break; - - if (type == LOG_RECORD_DIVE_SAMPLE) { + if (type == LOG_RECORD_DIVE_SAMPLE || + type == LOG_RECORD_AVELO_SAMPLE) { // Time (seconds). time += interval; sample.time = time; @@ -1143,8 +1171,12 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal if (callback) callback (DC_SAMPLE_TEMPERATURE, &sample, userdata); // Status flags. - unsigned int status = data[offset + pnf + 11]; - unsigned int ccr = (status & OC) == 0; + unsigned int status = 0; + unsigned int ccr = 0; + if (type != LOG_RECORD_AVELO_SAMPLE) { + status = data[offset + 11 + pnf]; + ccr = (status & OC) == 0; + } if (ccr) { // PPO2 @@ -1261,7 +1293,8 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal // detect tank pressure if (parser->logversion >= 7) { const unsigned int idx[2] = {27, 19}; - for (unsigned int i = 0; i < 2; ++i) { + const unsigned int count = type == LOG_RECORD_AVELO_SAMPLE ? 1 : 2; + for (unsigned int i = 0; i < count; ++i) { // Tank pressure // Values above 0xFFF0 are special codes: // 0xFFFF AI is off