diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e41d43a..00ff527 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -122,3 +122,52 @@ jobs: with: name: ${{ github.job }}-${{ matrix.arch }} path: ${{ github.job }}-${{ matrix.arch }}.tar.gz + + msvc: + + name: Visual Studio + + runs-on: windows-latest + + strategy: + fail-fast: false + matrix: + platform: [x86, x64] + + env: + CONFIGURATION: Release + + steps: + - uses: actions/checkout@v3 + - uses: msys2/setup-msys2@v2 + with: + install: autoconf automake libtool pkg-config make gcc + - run: | + autoreconf --install --force + ./configure --prefix=/usr + make -C src revision.h + shell: msys2 {0} + - uses: microsoft/setup-msbuild@v1 + - run: msbuild -m -p:Platform=${{ matrix.platform }} -p:Configuration=${{ env.CONFIGURATION }} contrib/msvc/libdivecomputer.vcxproj + - uses: actions/upload-artifact@v3 + with: + name: ${{ github.job }}-${{ matrix.platform }} + path: contrib/msvc/${{ matrix.platform }}/${{ env.CONFIGURATION }}/bin + + android: + + name: Android + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - run: | + autoreconf --install --force + ./configure --prefix=/usr + 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@v3 + with: + name: ${{ github.job }} + path: contrib/android/libs diff --git a/Makefile.am b/Makefile.am index 9ae4414..3798611 100644 --- a/Makefile.am +++ b/Makefile.am @@ -16,5 +16,8 @@ pkgconfig_DATA = libdivecomputer.pc EXTRA_DIST = \ libdivecomputer.pc.in \ - msvc/libdivecomputer.vcxproj \ - msvc/libdivecomputer.vcxproj.filters + contrib/README \ + contrib/android/Android.mk \ + contrib/msvc/libdivecomputer.vcxproj \ + contrib/msvc/libdivecomputer.vcxproj.filters \ + contrib/udev/libdivecomputer.rules diff --git a/NEWS b/NEWS index 3fabd4d..df264ae 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,38 @@ -Version 0.7.0 (2020-05-07) +Version 0.8.0 (2023-05-11) +========================== + +The v0.8.0 release is mainly a bugfix release, and brings in support for a +number of new devices. This release is fully backwards compatible with the +previous one. + +New features: + + * Add support for new backends: + - excursion: Deep Six Excursion, Crest CR-4, Genesis Centauri, Tusa TC1, Scorpena Alpha + - screen: Seac Screen and Action + - cosmiq: Deepblu Cosmiq+ + - s1: Oceans S1 + - freedom: Divesoft Freedom and Liberty + * Add support for some new devices: + - Aqualung: i200C + - Cressi: Donatello, Michelangelo, Neon + - Mares: Puck Pro + + - Oceanic: Geo Air + - Ratio: iX3M 2 + - Scubapro: G2 TEK + - Shearwater: Petrel 3, Perdix 2 + - Sherwood: Amphos Air 2.0 + * Add support for parsing the decompression model + * Add a public api to configure the depth calibration + * Add a public api to configure the clock synchronization + * Add a basic Android build system + +Removed/changed features: + + * Migrate to Visual Studio 2013 (or newer) + * Move the Visual Studio project to the contrib directory + +Version 0.7.0 (2021-05-07) ========================== The main highlight of the v0.7.0 release is the introduction of the new diff --git a/configure.ac b/configure.ac index a58d3e1..f580dbc 100644 --- a/configure.ac +++ b/configure.ac @@ -253,7 +253,6 @@ AC_CONFIG_FILES([ include/libdivecomputer/Makefile include/libdivecomputer/version.h src/Makefile - src/libdivecomputer.rc doc/Makefile doc/doxygen.cfg doc/man/Makefile diff --git a/contrib/README b/contrib/README new file mode 100644 index 0000000..cb0b77c --- /dev/null +++ b/contrib/README @@ -0,0 +1,56 @@ +Alternative build systems +========================= + +The autotools based build system is the official build system for the +libdivecomputer project. But for convenience, a few alternative build systems +are available as well. Unfortunately, these builds systems require a few extra +steps to generate some header files. + +If you have access to a UNIX build system (for example a Linux virtual machine, +MinGW, Cygwin or the Windows Subsystem for Linux), you can use the autotools +build system to generate those files: + + $ autoreconf --install --force + $ ./configure + $ make -C src revision.h + +Alternative, you can generate those files manually. First, create the version.h +file from the version.h.in template: + + $ cp include/libdivecomputer/version.h.in include/libdivecomputer/version.h + +and replace all the @DC_VERSION@ placeholders with the values defined in the +configure.ac file. + +Next, generate the revision.h file: + + $ echo "#define DC_VERSION_REVISION \"$(git rev-parse --verify HEAD)\"" > src/revision.h + +The alternative build systems are ready to use now. + +Visual Studio +------------- + +The Visual Studio project file can be opened in the IDE, or build directly from +the command-line: + + msbuild -m -p:Platform=x86|x64 -p:Configuration=Debug|Release contrib/msvc/libdivecomputer.vcxproj + +Android NDK +----------- + + $ANDROID_NDK/ndk-build -C contrib/android NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=Android.mk + +Linux udev rules +================ + +For dive computers using USB or USB HID communication, regular users typically +don't have the necessary permissions to access the device nodes. This can be +fixed with some udev rules. + +Install the udev rules, and reload them: + + $ sudo cp contrib/udev/libdivecomputer.rules /etc/udev/rules.d/ + $ sudo udevadm control --reload + +Note: the provided udev rules assume the user is in the plugdev group. diff --git a/contrib/android/Android.mk b/contrib/android/Android.mk new file mode 100644 index 0000000..fcceb81 --- /dev/null +++ b/contrib/android/Android.mk @@ -0,0 +1,143 @@ +LOCAL_PATH := $(call my-dir)/../.. + +include $(CLEAR_VARS) +LOCAL_MODULE := libdivecomputer +LOCAL_CFLAGS := -DENABLE_LOGGING -DHAVE_VERSION_SUFFIX -DHAVE_PTHREAD_H -DHAVE_STRERROR_R -DHAVE_CLOCK_GETTIME -DHAVE_LOCALTIME_R -DHAVE_GMTIME_R -DHAVE_TIMEGM -DHAVE_STRUCT_TM_TM_GMTOFF +LOCAL_C_INCLUDES := $(LOCAL_PATH)/include +LOCAL_SRC_FILES := \ + src/aes.c \ + src/array.c \ + src/atomics_cobalt.c \ + src/atomics_cobalt_parser.c \ + src/bluetooth.c \ + src/buffer.c \ + src/checksum.c \ + src/citizen_aqualand.c \ + src/citizen_aqualand_parser.c \ + src/cochran_commander.c \ + src/cochran_commander_parser.c \ + src/common.c \ + src/context.c \ + src/cressi_edy.c \ + src/cressi_edy_parser.c \ + src/cressi_goa.c \ + src/cressi_goa_parser.c \ + src/cressi_leonardo.c \ + src/cressi_leonardo_parser.c \ + src/custom.c \ + src/datetime.c \ + src/deepblu_cosmiq.c \ + src/deepblu_cosmiq_parser.c \ + src/deepsix_excursion.c \ + src/deepsix_excursion_parser.c \ + src/descriptor.c \ + src/device.c \ + src/diverite_nitekq.c \ + src/diverite_nitekq_parser.c \ + src/divesoft_freedom.c \ + src/divesoft_freedom_parser.c \ + src/divesystem_idive.c \ + src/divesystem_idive_parser.c \ + src/hdlc.c \ + src/hw_frog.c \ + src/hw_ostc3.c \ + src/hw_ostc.c \ + src/hw_ostc_parser.c \ + src/ihex.c \ + src/iostream.c \ + src/irda.c \ + src/iterator.c \ + src/liquivision_lynx.c \ + src/liquivision_lynx_parser.c \ + src/mares_common.c \ + src/mares_darwin.c \ + src/mares_darwin_parser.c \ + src/mares_iconhd.c \ + src/mares_iconhd_parser.c \ + src/mares_nemo.c \ + src/mares_nemo_parser.c \ + src/mares_puck.c \ + src/mclean_extreme.c \ + src/mclean_extreme_parser.c \ + src/oceanic_atom2.c \ + src/oceanic_atom2_parser.c \ + src/oceanic_common.c \ + src/oceanic_veo250.c \ + src/oceanic_veo250_parser.c \ + src/oceanic_vtpro.c \ + src/oceanic_vtpro_parser.c \ + src/oceans_s1.c \ + src/oceans_s1_common.c \ + src/oceans_s1_parser.c \ + src/packet.c \ + src/parser.c \ + src/platform.c \ + src/rbstream.c \ + src/reefnet_sensus.c \ + src/reefnet_sensus_parser.c \ + src/reefnet_sensuspro.c \ + src/reefnet_sensuspro_parser.c \ + src/reefnet_sensusultra.c \ + src/reefnet_sensusultra_parser.c \ + src/ringbuffer.c \ + src/seac_screen.c \ + src/seac_screen_parser.c \ + src/serial_posix.c \ + src/shearwater_common.c \ + src/shearwater_petrel.c \ + src/shearwater_predator.c \ + src/shearwater_predator_parser.c \ + src/socket.c \ + src/sporasub_sp2.c \ + src/sporasub_sp2_parser.c \ + src/suunto_common2.c \ + src/suunto_common.c \ + src/suunto_d9.c \ + src/suunto_d9_parser.c \ + src/suunto_eon.c \ + src/suunto_eon_parser.c \ + src/suunto_eonsteel.c \ + src/suunto_eonsteel_parser.c \ + src/suunto_solution.c \ + src/suunto_solution_parser.c \ + src/suunto_vyper2.c \ + src/suunto_vyper.c \ + src/suunto_vyper_parser.c \ + src/tecdiving_divecomputereu.c \ + src/tecdiving_divecomputereu_parser.c \ + src/timer.c \ + src/usb.c \ + src/usbhid.c \ + src/uwatec_aladin.c \ + src/uwatec_memomouse.c \ + src/uwatec_memomouse_parser.c \ + src/uwatec_smart.c \ + src/uwatec_smart_parser.c \ + src/version.c \ + src/zeagle_n2ition3.c +include $(BUILD_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := dctool +LOCAL_SHARED_LIBRARIES := libdivecomputer +LOCAL_CFLAGS := -DHAVE_UNISTD_H -DHAVE_GETOPT_H -DHAVE_GETOPT_LONG -DHAVE_DECL_OPTRESET=1 +LOCAL_C_INCLUDES := $(LOCAL_PATH)/include +LOCAL_SRC_FILES := \ + examples/common.c \ + examples/dctool.c \ + examples/dctool_download.c \ + examples/dctool_dump.c \ + examples/dctool_fwupdate.c \ + examples/dctool_help.c \ + examples/dctool_list.c \ + examples/dctool_parse.c \ + examples/dctool_read.c \ + examples/dctool_scan.c \ + examples/dctool_timesync.c \ + examples/dctool_version.c \ + examples/dctool_write.c \ + examples/output.c \ + examples/output_raw.c \ + examples/output_xml.c \ + examples/utils.c +include $(BUILD_EXECUTABLE) diff --git a/contrib/msvc/libdivecomputer.vcxproj b/contrib/msvc/libdivecomputer.vcxproj new file mode 100644 index 0000000..7f1335c --- /dev/null +++ b/contrib/msvc/libdivecomputer.vcxproj @@ -0,0 +1,407 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {CEA7215A-D6B5-4840-8086-3C854F371997} + libdivecomputer + Win32Proj + + + + DynamicLibrary + true + $(DefaultPlatformToolset) + Unicode + + + DynamicLibrary + true + $(DefaultPlatformToolset) + Unicode + + + DynamicLibrary + false + $(DefaultPlatformToolset) + Unicode + true + + + DynamicLibrary + false + $(DefaultPlatformToolset) + Unicode + true + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(PlatformTarget)\$(Configuration)\bin\ + $(PlatformTarget)\$(Configuration)\obj\ + + + true + $(SolutionDir)$(PlatformTarget)\$(Configuration)\bin\ + $(PlatformTarget)\$(Configuration)\obj\ + + + false + $(SolutionDir)$(PlatformTarget)\$(Configuration)\bin\ + $(PlatformTarget)\$(Configuration)\obj\ + + + false + $(SolutionDir)$(PlatformTarget)\$(Configuration)\bin\ + $(PlatformTarget)\$(Configuration)\obj\ + + + + Disabled + ..\..\include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBDIVECOMPUTER_EXPORTS;ENABLE_LOGGING;HAVE_VERSION_SUFFIX;HAVE_AF_IRDA_H;HAVE_WS2BTH_H;%(PreprocessorDefinitions) + + Level3 + true + + + ws2_32.lib;%(AdditionalDependencies) + $(OutDir)libdivecomputer.def + true + Windows + + + ..\..\include;%(AdditionalIncludeDirectories) + + + + + Disabled + ..\..\include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBDIVECOMPUTER_EXPORTS;ENABLE_LOGGING;HAVE_VERSION_SUFFIX;HAVE_AF_IRDA_H;HAVE_WS2BTH_H;%(PreprocessorDefinitions) + + + Level3 + true + + + ws2_32.lib;%(AdditionalDependencies) + $(OutDir)libdivecomputer.def + true + Windows + + + ..\..\include;%(AdditionalIncludeDirectories) + + + + + MaxSpeed + true + ..\..\include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBDIVECOMPUTER_EXPORTS;ENABLE_LOGGING;HAVE_VERSION_SUFFIX;HAVE_AF_IRDA_H;HAVE_WS2BTH_H;%(PreprocessorDefinitions) + true + + Level3 + true + + + ws2_32.lib;%(AdditionalDependencies) + $(OutDir)libdivecomputer.def + true + Windows + true + true + + + ..\..\include;%(AdditionalIncludeDirectories) + + + + + MaxSpeed + true + ..\..\include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBDIVECOMPUTER_EXPORTS;ENABLE_LOGGING;HAVE_VERSION_SUFFIX;HAVE_AF_IRDA_H;HAVE_WS2BTH_H;%(PreprocessorDefinitions) + true + + + Level3 + true + + + ws2_32.lib;%(AdditionalDependencies) + $(OutDir)libdivecomputer.def + true + Windows + true + true + + + ..\..\include;%(AdditionalIncludeDirectories) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + echo EXPORTS > "$(OutDir)libdivecomputer.def" && type "%(FullPath)" >> "$(OutDir)libdivecomputer.def" + echo EXPORTS > "$(OutDir)libdivecomputer.def" && type "%(FullPath)" >> "$(OutDir)libdivecomputer.def" + $(OutDir)libdivecomputer.def;%(Outputs) + $(OutDir)libdivecomputer.def;%(Outputs) + echo EXPORTS > "$(OutDir)libdivecomputer.def" && type "%(FullPath)" >> "$(OutDir)libdivecomputer.def" + echo EXPORTS > "$(OutDir)libdivecomputer.def" && type "%(FullPath)" >> "$(OutDir)libdivecomputer.def" + $(OutDir)libdivecomputer.def;%(Outputs) + $(OutDir)libdivecomputer.def;%(Outputs) + + + + + + diff --git a/msvc/libdivecomputer.vcxproj.filters b/contrib/msvc/libdivecomputer.vcxproj.filters similarity index 100% rename from msvc/libdivecomputer.vcxproj.filters rename to contrib/msvc/libdivecomputer.vcxproj.filters diff --git a/doc/man/dc_datetime_gmtime.3 b/doc/man/dc_datetime_gmtime.3 index 1e4bcf5..9297446 100644 --- a/doc/man/dc_datetime_gmtime.3 +++ b/doc/man/dc_datetime_gmtime.3 @@ -82,7 +82,7 @@ is .Sh SEE ALSO .Xr dc_datetime_localtime 3 , .Xr dc_datetime_mktime 3 , -.Xr dc_datetime_new 3 +.Xr dc_datetime_now 3 .Sh AUTHORS The .Lb libdivecomputer diff --git a/doc/man/dc_datetime_localtime.3 b/doc/man/dc_datetime_localtime.3 index ec6681f..8b9c2f6 100644 --- a/doc/man/dc_datetime_localtime.3 +++ b/doc/man/dc_datetime_localtime.3 @@ -82,7 +82,7 @@ is .Sh SEE ALSO .Xr dc_datetime_gmtime 3 , .Xr dc_datetime_mktime 3 , -.Xr dc_datetime_new 3 +.Xr dc_datetime_now 3 .Sh AUTHORS The .Lb libdivecomputer diff --git a/doc/man/dc_datetime_mktime.3 b/doc/man/dc_datetime_mktime.3 index a3dbccb..4ba38b3 100644 --- a/doc/man/dc_datetime_mktime.3 +++ b/doc/man/dc_datetime_mktime.3 @@ -47,7 +47,7 @@ may not sanely be converted. .Sh SEE ALSO .Xr dc_datetime_gmtime 3 , .Xr dc_datetime_localtime 3 , -.Xr dc_datetime_new 3 +.Xr dc_datetime_now 3 .Sh AUTHORS The .Lb libdivecomputer diff --git a/examples/common.c b/examples/common.c index 47d457f..f30337b 100644 --- a/examples/common.c +++ b/examples/common.c @@ -98,6 +98,7 @@ static const backend_table_t g_backends[] = { {"screen", DC_FAMILY_SEAC_SCREEN, 0}, {"cosmiq", DC_FAMILY_DEEPBLU_COSMIQ, 0}, {"s1", DC_FAMILY_OCEANS_S1, 0}, + {"freedom", DC_FAMILY_DIVESOFT_FREEDOM, 19}, // Not merged upstream yet {"descentmk1", DC_FAMILY_GARMIN, 0}, diff --git a/examples/dctool.c b/examples/dctool.c index 9d3f74b..62a15fc 100644 --- a/examples/dctool.c +++ b/examples/dctool.c @@ -50,7 +50,7 @@ #define RESET 1 #endif -#if defined(__GLIBC__) || defined(__MINGW32__) || defined(BSD) +#if defined(__GLIBC__) || defined(__MINGW32__) || defined(BSD) || defined(__ANDROID__) #define NOPERMUTATION "+" #else #define NOPERMUTATION "" diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h index 6619bd0..0d5ab17 100644 --- a/include/libdivecomputer/common.h +++ b/include/libdivecomputer/common.h @@ -122,6 +122,8 @@ typedef enum dc_family_t { DC_FAMILY_DEEPBLU_COSMIQ = (21 << 16), /* Oceans S1 */ DC_FAMILY_OCEANS_S1 = (22 << 16), + /* Divesoft Freedom */ + DC_FAMILY_DIVESOFT_FREEDOM = (23 << 16), // Not merged upstream yet /* Garmin */ diff --git a/msvc/libdivecomputer.vcxproj b/msvc/libdivecomputer.vcxproj deleted file mode 100644 index b3b370f..0000000 --- a/msvc/libdivecomputer.vcxproj +++ /dev/null @@ -1,388 +0,0 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {CEA7215A-D6B5-4840-8086-3C854F371997} - libdivecomputer - Win32Proj - - - - DynamicLibrary - true - $(DefaultPlatformToolset) - Unicode - - - DynamicLibrary - true - $(DefaultPlatformToolset) - Unicode - - - DynamicLibrary - false - $(DefaultPlatformToolset) - Unicode - true - - - DynamicLibrary - false - $(DefaultPlatformToolset) - Unicode - true - - - - - - - - - - - - - - - - - - - true - $(SolutionDir)$(PlatformTarget)\$(Configuration)\bin\ - $(PlatformTarget)\$(Configuration)\obj\ - - - true - $(SolutionDir)$(PlatformTarget)\$(Configuration)\bin\ - $(PlatformTarget)\$(Configuration)\obj\ - - - false - $(SolutionDir)$(PlatformTarget)\$(Configuration)\bin\ - $(PlatformTarget)\$(Configuration)\obj\ - - - false - $(SolutionDir)$(PlatformTarget)\$(Configuration)\bin\ - $(PlatformTarget)\$(Configuration)\obj\ - - - - Disabled - ..\include;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBDIVECOMPUTER_EXPORTS;ENABLE_LOGGING;HAVE_AF_IRDA_H;HAVE_WS2BTH_H;%(PreprocessorDefinitions) - - Level3 - true - - - ws2_32.lib;%(AdditionalDependencies) - $(OutDir)libdivecomputer.def - true - Windows - - - - - Disabled - ..\include;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBDIVECOMPUTER_EXPORTS;ENABLE_LOGGING;HAVE_AF_IRDA_H;HAVE_WS2BTH_H;%(PreprocessorDefinitions) - - - Level3 - true - - - ws2_32.lib;%(AdditionalDependencies) - $(OutDir)libdivecomputer.def - true - Windows - - - - - MaxSpeed - true - ..\include;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBDIVECOMPUTER_EXPORTS;ENABLE_LOGGING;HAVE_AF_IRDA_H;HAVE_WS2BTH_H;%(PreprocessorDefinitions) - true - - Level3 - true - - - ws2_32.lib;%(AdditionalDependencies) - $(OutDir)libdivecomputer.def - true - Windows - true - true - - - - - MaxSpeed - true - ..\include;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBDIVECOMPUTER_EXPORTS;ENABLE_LOGGING;HAVE_AF_IRDA_H;HAVE_WS2BTH_H;%(PreprocessorDefinitions) - true - - - Level3 - true - - - ws2_32.lib;%(AdditionalDependencies) - $(OutDir)libdivecomputer.def - true - Windows - true - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - echo EXPORTS > "$(OutDir)libdivecomputer.def" && type "%(FullPath)" >> "$(OutDir)libdivecomputer.def" - echo EXPORTS > "$(OutDir)libdivecomputer.def" && type "%(FullPath)" >> "$(OutDir)libdivecomputer.def" - $(OutDir)libdivecomputer.def;%(Outputs) - $(OutDir)libdivecomputer.def;%(Outputs) - echo EXPORTS > "$(OutDir)libdivecomputer.def" && type "%(FullPath)" >> "$(OutDir)libdivecomputer.def" - echo EXPORTS > "$(OutDir)libdivecomputer.def" && type "%(FullPath)" >> "$(OutDir)libdivecomputer.def" - $(OutDir)libdivecomputer.def;%(Outputs) - $(OutDir)libdivecomputer.def;%(Outputs) - - - - - - diff --git a/src/Makefile.am b/src/Makefile.am index c269fd2..fec3d31 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -80,6 +80,9 @@ libdivecomputer_la_SOURCES = \ 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 \ + divesoft_freedom.h divesoft_freedom.c divesoft_freedom_parser.c \ + hdlc.h hdlc.c \ + packet.h packet.c \ socket.h socket.c \ irda.c \ usb.c \ @@ -109,7 +112,7 @@ libdivecomputer.exp: libdivecomputer.symbols $(AM_V_GEN) sed -e '/^$$/d' $< > $@ .rc.lo: - $(AM_V_GEN) $(LIBTOOL) --silent --tag=CC --mode=compile $(RC) $(DEFS) $(DEFAULT_INCLUDES) $< -o $@ + $(AM_V_GEN) $(LIBTOOL) --silent --tag=CC --mode=compile $(RC) $(DEFS) $(DEFAULT_INCLUDES) $(AM_CPPFLAGS) $< -o $@ libdivecomputer.lo: revision.h diff --git a/src/array.c b/src/array.c index 1216d41..29f5faf 100644 --- a/src/array.c +++ b/src/array.c @@ -384,3 +384,23 @@ dec2bcd (unsigned char value) unsigned char lo = value % 10; return (hi << 4) | lo; } + +/* + * When turning a two's-complement number with a certain number + * of bits into one with more bits, the sign bit must be repeated + * in all the extra bits. + */ +unsigned int +signextend (unsigned int value, unsigned int nbits) +{ + if (nbits <= 0 || nbits > 32) + return 0; + + unsigned int signbit = 1U << (nbits - 1); + unsigned int mask = signbit - 1; + + if ((value & signbit) == signbit) + return value | ~mask; + else + return value & mask; +} diff --git a/src/array.h b/src/array.h index ac4c3b1..a6ef21e 100644 --- a/src/array.h +++ b/src/array.h @@ -123,6 +123,9 @@ bcd2dec (unsigned char value); unsigned char dec2bcd (unsigned char value); +unsigned int +signextend (unsigned int value, unsigned int nbits); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/checksum.c b/src/checksum.c index 9d06630..eeda912 100644 --- a/src/checksum.c +++ b/src/checksum.c @@ -68,8 +68,13 @@ checksum_xor_uint8 (const unsigned char data[], unsigned int size, unsigned char } +/* + * Polynomial: 0x1021 + * RefIn: False + * RefOut: False + */ unsigned short -checksum_crc16_ccitt (const unsigned char data[], unsigned int size, unsigned short init) +checksum_crc16_ccitt (const unsigned char data[], unsigned int size, unsigned short init, unsigned short xorout) { static const unsigned short crc_ccitt_table[] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, @@ -110,11 +115,170 @@ checksum_crc16_ccitt (const unsigned char data[], unsigned int size, unsigned sh for (unsigned int i = 0; i < size; ++i) crc = (crc << 8) ^ crc_ccitt_table[(crc >> 8) ^ data[i]]; - return crc; + return crc ^ xorout; } + +/* + * Polynomial: 0x1021 + * RefIn: True + * RefOut: True + */ +unsigned short +checksum_crc16r_ccitt (const unsigned char data[], unsigned int size, unsigned short init, unsigned short xorout) +{ + static const unsigned short crc_ccitt_table[] = { + 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, + 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, + 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, + 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, + 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, + 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, + 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, + 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, + 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, + 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, + 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, + 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, + 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, + 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, + 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, + 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, + 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, + 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, + 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, + 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, + 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, + 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, + 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, + 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, + 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, + 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, + 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, + 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, + 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, + 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, + 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, + 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 + }; + + unsigned short crc = init; + for (unsigned int i = 0; i < size; ++i) + crc = (crc >> 8) ^ crc_ccitt_table[(crc ^ data[i]) & 0xff]; + + return crc ^ xorout; +} + +/* + * Polynomial: 0x8005 + * RefIn: False + * RefOut: False + */ +unsigned short +checksum_crc16_ansi (const unsigned char data[], unsigned int size, unsigned short init, unsigned short xorout) +{ + static const unsigned short crc_ccitt_table[] = { + 0x0000, 0x8005, 0x800f, 0x000a, 0x801b, 0x001e, 0x0014, 0x8011, + 0x8033, 0x0036, 0x003c, 0x8039, 0x0028, 0x802d, 0x8027, 0x0022, + 0x8063, 0x0066, 0x006c, 0x8069, 0x0078, 0x807d, 0x8077, 0x0072, + 0x0050, 0x8055, 0x805f, 0x005a, 0x804b, 0x004e, 0x0044, 0x8041, + 0x80c3, 0x00c6, 0x00cc, 0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2, + 0x00f0, 0x80f5, 0x80ff, 0x00fa, 0x80eb, 0x00ee, 0x00e4, 0x80e1, + 0x00a0, 0x80a5, 0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4, 0x80b1, + 0x8093, 0x0096, 0x009c, 0x8099, 0x0088, 0x808d, 0x8087, 0x0082, + 0x8183, 0x0186, 0x018c, 0x8189, 0x0198, 0x819d, 0x8197, 0x0192, + 0x01b0, 0x81b5, 0x81bf, 0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1, + 0x01e0, 0x81e5, 0x81ef, 0x01ea, 0x81fb, 0x01fe, 0x01f4, 0x81f1, + 0x81d3, 0x01d6, 0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7, 0x01c2, + 0x0140, 0x8145, 0x814f, 0x014a, 0x815b, 0x015e, 0x0154, 0x8151, + 0x8173, 0x0176, 0x017c, 0x8179, 0x0168, 0x816d, 0x8167, 0x0162, + 0x8123, 0x0126, 0x012c, 0x8129, 0x0138, 0x813d, 0x8137, 0x0132, + 0x0110, 0x8115, 0x811f, 0x011a, 0x810b, 0x010e, 0x0104, 0x8101, + 0x8303, 0x0306, 0x030c, 0x8309, 0x0318, 0x831d, 0x8317, 0x0312, + 0x0330, 0x8335, 0x833f, 0x033a, 0x832b, 0x032e, 0x0324, 0x8321, + 0x0360, 0x8365, 0x836f, 0x036a, 0x837b, 0x037e, 0x0374, 0x8371, + 0x8353, 0x0356, 0x035c, 0x8359, 0x0348, 0x834d, 0x8347, 0x0342, + 0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db, 0x03de, 0x03d4, 0x83d1, + 0x83f3, 0x03f6, 0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7, 0x03e2, + 0x83a3, 0x03a6, 0x03ac, 0x83a9, 0x03b8, 0x83bd, 0x83b7, 0x03b2, + 0x0390, 0x8395, 0x839f, 0x039a, 0x838b, 0x038e, 0x0384, 0x8381, + 0x0280, 0x8285, 0x828f, 0x028a, 0x829b, 0x029e, 0x0294, 0x8291, + 0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8, 0x82ad, 0x82a7, 0x02a2, + 0x82e3, 0x02e6, 0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7, 0x02f2, + 0x02d0, 0x82d5, 0x82df, 0x02da, 0x82cb, 0x02ce, 0x02c4, 0x82c1, + 0x8243, 0x0246, 0x024c, 0x8249, 0x0258, 0x825d, 0x8257, 0x0252, + 0x0270, 0x8275, 0x827f, 0x027a, 0x826b, 0x026e, 0x0264, 0x8261, + 0x0220, 0x8225, 0x822f, 0x022a, 0x823b, 0x023e, 0x0234, 0x8231, + 0x8213, 0x0216, 0x021c, 0x8219, 0x0208, 0x820d, 0x8207, 0x0202 + }; + + unsigned short crc = init; + for (unsigned int i = 0; i < size; ++i) + crc = (crc << 8) ^ crc_ccitt_table[(crc >> 8) ^ data[i]]; + + return crc ^ xorout; +} + +/* + * Polynomial: 0x8005 + * RefIn: True + * RefOut: True + */ +unsigned short +checksum_crc16r_ansi (const unsigned char data[], unsigned int size, unsigned short init, unsigned short xorout) +{ + static const unsigned short crc_ccitt_table[] = { + 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, + 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, + 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, + 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, + 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, + 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, + 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, + 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, + 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240, + 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, + 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41, + 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, + 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, + 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, + 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640, + 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, + 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240, + 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, + 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, + 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, + 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, + 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, + 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, + 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, + 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, + 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440, + 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, + 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841, + 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, + 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41, + 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, + 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040 + }; + + unsigned short crc = init; + for (unsigned int i = 0; i < size; ++i) + crc = (crc >> 8) ^ crc_ccitt_table[(crc ^ data[i]) & 0xff]; + + return crc ^ xorout; +} + + +/* + * Polynomial: 0x04C11DB7 + * Init: 0xffffffff + * XorOut: 0xffffffff + * RefIn: True + * RefOut: True + */ unsigned int -checksum_crc32 (const unsigned char data[], unsigned int size) +checksum_crc32r (const unsigned char data[], unsigned int size) { static const unsigned int crc_table[] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, @@ -158,8 +322,16 @@ checksum_crc32 (const unsigned char data[], unsigned int size) return crc ^ 0xffffffff; } + +/* + * Polynomial: 0x04C11DB7 + * Init: 0xffffffff + * XorOut: 0xffffffff + * RefIn: False + * RefOut: False + */ unsigned int -checksum_crc32b (const unsigned char data[], unsigned int size) +checksum_crc32 (const unsigned char data[], unsigned int size) { static const unsigned int crc_table[] = { 0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9, 0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005, diff --git a/src/checksum.h b/src/checksum.h index c8a9001..f9e54bb 100644 --- a/src/checksum.h +++ b/src/checksum.h @@ -39,14 +39,23 @@ unsigned char checksum_xor_uint8 (const unsigned char data[], unsigned int size, unsigned char init); unsigned short -checksum_crc16_ccitt (const unsigned char data[], unsigned int size, unsigned short init); +checksum_crc16_ccitt (const unsigned char data[], unsigned int size, unsigned short init, unsigned short xorout); + +unsigned short +checksum_crc16r_ccitt (const unsigned char data[], unsigned int size, unsigned short init, unsigned short xorout); + +unsigned short +checksum_crc16_ansi (const unsigned char data[], unsigned int size, unsigned short init, unsigned short xorout); + +unsigned short +checksum_crc16r_ansi (const unsigned char data[], unsigned int size, unsigned short init, unsigned short xorout); + +unsigned int +checksum_crc32r (const unsigned char data[], unsigned int size); unsigned int checksum_crc32 (const unsigned char data[], unsigned int size); -unsigned int -checksum_crc32b (const unsigned char data[], unsigned int size); - #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/cressi_goa.c b/src/cressi_goa.c index e414743..9df10e8 100644 --- a/src/cressi_goa.c +++ b/src/cressi_goa.c @@ -93,7 +93,7 @@ cressi_goa_device_send (cressi_goa_device_t *device, unsigned char cmd, const un if (size) { memcpy (packet + 5, data, size); } - crc = checksum_crc16_ccitt (packet + 3, size + 2, 0x000); + crc = checksum_crc16_ccitt (packet + 3, size + 2, 0x000, 0x0000); packet[5 + size + 0] = (crc ) & 0xFF; // Low packet[5 + size + 1] = (crc >> 8) & 0xFF; // High packet[5 + size + 2] = TRAILER; @@ -155,7 +155,7 @@ cressi_goa_device_receive (cressi_goa_device_t *device, unsigned char data[], un // Verify the checksum of the packet. unsigned short crc = array_uint16_le (packet + length + 5); - unsigned short ccrc = checksum_crc16_ccitt (packet + 3, length + 2, 0x0000); + unsigned short ccrc = checksum_crc16_ccitt (packet + 3, length + 2, 0x0000, 0x0000); if (crc != ccrc) { ERROR (abstract->context, "Unexpected answer checksum."); return DC_STATUS_PROTOCOL; @@ -203,7 +203,7 @@ cressi_goa_device_download (cressi_goa_device_t *device, dc_buffer_t *buffer, dc // Verify the checksum of the packet. unsigned short crc = array_uint16_le (packet + sizeof(packet) - 2); - unsigned short ccrc = checksum_crc16_ccitt (packet + 3, sizeof(packet) - 5, 0x0000); + unsigned short ccrc = checksum_crc16_ccitt (packet + 3, sizeof(packet) - 5, 0x0000, 0x0000); if (crc != ccrc) { ERROR (abstract->context, "Unexpected answer checksum."); return DC_STATUS_PROTOCOL; diff --git a/src/cressi_leonardo.c b/src/cressi_leonardo.c index f331537..dd941da 100644 --- a/src/cressi_leonardo.c +++ b/src/cressi_leonardo.c @@ -84,7 +84,7 @@ cressi_leonardo_make_ascii (const unsigned char raw[], unsigned int rsize, unsig array_convert_bin2hex (raw, rsize, ascii + 1, 2 * rsize); // Checksum - unsigned short crc = checksum_crc16_ccitt (ascii + 1, 2 * rsize, 0xffff); + unsigned short crc = checksum_crc16_ccitt (ascii + 1, 2 * rsize, 0xffff, 0x0000); unsigned char checksum[] = { (crc >> 8) & 0xFF, // High (crc ) & 0xFF}; // Low @@ -129,7 +129,7 @@ cressi_leonardo_packet (cressi_leonardo_device_t *device, const unsigned char co // Verify the checksum of the packet. unsigned short crc = array_uint16_be (checksum); - unsigned short ccrc = checksum_crc16_ccitt (answer + 1, asize - 6, 0xffff); + unsigned short ccrc = checksum_crc16_ccitt (answer + 1, asize - 6, 0xffff, 0x0000); if (crc != ccrc) { ERROR (abstract->context, "Unexpected answer checksum."); return DC_STATUS_PROTOCOL; @@ -372,7 +372,7 @@ cressi_leonardo_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) // Verify the checksum. unsigned int csum1 = array_uint16_be (checksum); - unsigned int csum2 = checksum_crc16_ccitt (data, SZ_MEMORY, 0xffff); + unsigned int csum2 = checksum_crc16_ccitt (data, SZ_MEMORY, 0xffff, 0x0000); if (csum1 != csum2) { ERROR (abstract->context, "Unexpected answer bytes."); return DC_STATUS_PROTOCOL; diff --git a/src/descriptor.c b/src/descriptor.c index 8414aec..7601e5e 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -65,6 +65,7 @@ static int dc_filter_atomic (dc_transport_t transport, const void *userdata, voi 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); +static int dc_filter_divesoft (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); @@ -454,6 +455,9 @@ static const dc_descriptor_t g_descriptors[] = { {"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}, + /* Divesoft Freedom */ + {"Divesoft", "Freedom", DC_FAMILY_DIVESOFT_FREEDOM, 19, DC_TRANSPORT_BLE, dc_filter_divesoft}, + {"Divesoft", "Liberty", DC_FAMILY_DIVESOFT_FREEDOM, 10, DC_TRANSPORT_BLE, dc_filter_divesoft}, // Not merged upstream yet /* Garmin -- model numbers as defined in FIT format; USB product id is (0x4000 | model) */ @@ -808,6 +812,20 @@ static int dc_filter_oceans (dc_transport_t transport, const void *userdata, voi return 1; } +static int dc_filter_divesoft (dc_transport_t transport, const void *userdata, void *params) +{ + static const char * const bluetooth[] = { + "Freedom", + "Liberty", + }; + + if (transport == DC_TRANSPORT_BLE) { + 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) { diff --git a/src/device.c b/src/device.c index 602f875..6443159 100644 --- a/src/device.c +++ b/src/device.c @@ -64,6 +64,7 @@ #include "seac_screen.h" #include "deepblu_cosmiq.h" #include "oceans_s1.h" +#include "divesoft_freedom.h" // Not merged upstream yet #include "garmin.h" @@ -242,6 +243,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr case DC_FAMILY_OCEANS_S1: rc = oceans_s1_device_open (&device, context, iostream); break; + case DC_FAMILY_DIVESOFT_FREEDOM: + rc = divesoft_freedom_device_open (&device, context, iostream); + break; default: return DC_STATUS_INVALIDARGS; diff --git a/src/divesoft_freedom.c b/src/divesoft_freedom.c new file mode 100644 index 0000000..ddf9162 --- /dev/null +++ b/src/divesoft_freedom.c @@ -0,0 +1,595 @@ +/* + * libdivecomputer + * + * Copyright (C) 2023 Jan MatouÅ¡ek, 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 "divesoft_freedom.h" +#include "context-private.h" +#include "device-private.h" +#include "platform.h" +#include "checksum.h" +#include "array.h" +#include "hdlc.h" + +#define MAXDATA 256 + +#define HEADER_SIGNATURE_V1 0x45766944 // "DivE" +#define HEADER_SIGNATURE_V2 0x45566944 // "DiVE" + +#define HEADER_SIZE_V1 32 +#define HEADER_SIZE_V2 64 + +#define RECORD_SIZE 16 +#define FINGERPRINT_SIZE 20 + +#define INVALID 0xFFFFFFFF +#define COMPRESSION 1 +#define DIRECTION 1 +#define NRECORDS 100 + +#define DEVICE_CCR_CU 1 // Liberty HW rev. 1.X +#define DEVICE_FREEDOM 2 // Freedom HW rev. 2.X +#define DEVICE_FREEDOM3 5 // Freedom HW rev. 3.X +#define DEVICE_CCR_CU15 10 // Liberty HW rev. 2.X, Bluetooth enabled +#define DEVICE_FREEDOM4 19 // Freedom HW rev. 4.X, Bluetooth enabled + +typedef enum message_t { + MSG_ECHO = 0, + MSG_RESULT = 1, + MSG_CONNECT = 2, + MSG_CONNECTED = 3, + MSG_VERSION = 4, + MSG_VERSION_RSP = 5, + MSG_DIVE_DATA = 64, + MSG_DIVE_DATA_RSP = 65, + MSG_DIVE_LIST = 66, + MSG_DIVE_LIST_V1 = 67, + MSG_DIVE_LIST_V2 = 71, +} message_t; + +typedef struct divesoft_freedom_device_t { + dc_device_t base; + dc_iostream_t *iostream; + unsigned char fingerprint[FINGERPRINT_SIZE]; + unsigned int seqnum; +} divesoft_freedom_device_t; + +static dc_status_t divesoft_freedom_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); +static dc_status_t divesoft_freedom_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +static dc_status_t divesoft_freedom_device_close (dc_device_t *device); + +static const dc_device_vtable_t divesoft_freedom_device_vtable = { + sizeof(divesoft_freedom_device_t), + DC_FAMILY_DIVESOFT_FREEDOM, + divesoft_freedom_device_set_fingerprint, /* set_fingerprint */ + NULL, /* read */ + NULL, /* write */ + NULL, /* dump */ + divesoft_freedom_device_foreach, /* foreach */ + NULL, /* timesync */ + divesoft_freedom_device_close, /* close */ +}; + +static dc_status_t +divesoft_freedom_send (divesoft_freedom_device_t *device, message_t message, const unsigned char data[], size_t size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + + size_t nbytes = 0, count = 0; + while (1) { + size_t len = size - nbytes; + if (len > MAXDATA) + len = MAXDATA; + + unsigned int islast = nbytes + len == size; + + unsigned char packet[6 + MAXDATA + 2] = {0}; + packet[0] = ((count & 0x0F) << 4) | (device->seqnum & 0x0F); + packet[1] = 0x80 | (islast << 6); + array_uint16_le_set (packet + 2, message); + array_uint16_le_set (packet + 4, len); + if (len) { + memcpy (packet + 6, data + nbytes, len); + } + unsigned short crc = checksum_crc16r_ccitt (packet, len + 6, 0xFFFF, 0xFFFF); + array_uint16_le_set (packet + 6 + len, crc); + + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "cmd", packet, 6 + len + 2); + + status = dc_iostream_write (device->iostream, packet, 6 + len + 2, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the packet."); + return status; + } + + nbytes += len; + count++; + + if (islast) + break; + } + + return status; +} + +static dc_status_t +divesoft_freedom_recv (divesoft_freedom_device_t *device, dc_event_progress_t *progress, message_t *message, dc_buffer_t *buffer) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + unsigned int msg = INVALID; + + unsigned int count = 0; + while (1) { + size_t len = 0; + unsigned char packet[6 + MAXDATA + 2] = {0}; + status = dc_iostream_read (device->iostream, packet, sizeof(packet), &len); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the packet."); + return status; + } + + HEXDUMP (abstract->context, DC_LOGLEVEL_DEBUG, "rcv", packet, len); + + if (len < 8) { + ERROR (abstract->context, "Unexpected packet length (" DC_PRINTF_SIZE ").", len); + return DC_STATUS_PROTOCOL; + } + + unsigned int seqnum = packet[0]; + unsigned int flags = packet[1]; + unsigned int type = array_uint16_le (packet + 2); + unsigned int length = array_uint16_le (packet + 4); + + unsigned int expected = ((count & 0x0F) << 4) | (device->seqnum & 0x0F); + if (seqnum != expected) { + ERROR (abstract->context, "Unexpected packet sequence number (%u %u).", seqnum, expected); + return DC_STATUS_PROTOCOL; + } + + if ((flags & ~0x40) != 0) { + ERROR (abstract->context, "Unexpected packet flags (%u).", flags); + return DC_STATUS_PROTOCOL; + } + + if (length != len - 8) { + ERROR (abstract->context, "Unexpected packet length (%u " DC_PRINTF_SIZE ").", length, len - 8); + return DC_STATUS_PROTOCOL; + } + + if (msg == INVALID) { + msg = type; + } else if (msg != type) { + ERROR (abstract->context, "Unexpected packet type (%u).", msg); + return DC_STATUS_PROTOCOL; + } + + unsigned short crc = array_uint16_le (packet + len - 2); + unsigned short ccrc = checksum_crc16r_ccitt (packet, len - 2, 0xFFFF, 0xFFFF); + if (crc != ccrc) { + ERROR (abstract->context, "Unexpected packet checksum (%04x %04x).", crc, ccrc); + return DC_STATUS_PROTOCOL; + } + + // Update and emit a progress event. + if (progress) { + progress->current += len - 8; + // Limit the progress to the maximum size. This could happen if the + // dive computer sends more data than requested for some reason. + if (progress->current > progress->maximum) { + WARNING (abstract->context, "Progress exceeds the maximum size."); + progress->current = progress->maximum; + } + device_event_emit (abstract, DC_EVENT_PROGRESS, progress); + } + + if (!dc_buffer_append (buffer, packet + 6, len - 8)) { + ERROR (abstract->context, "Insufficient buffer space available."); + return DC_STATUS_NOMEMORY; + } + + count++; + + if (flags & 0x40) + break; + } + + if (message) + *message = msg; + + return status; +} + +static dc_status_t +divesoft_freedom_transfer (divesoft_freedom_device_t *device, dc_event_progress_t *progress, message_t cmd, const unsigned char data[], size_t size, message_t *msg, dc_buffer_t *buffer) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + + if (device_is_cancelled (abstract)) + return DC_STATUS_CANCELLED; + + device->seqnum++; + + status = divesoft_freedom_send (device, cmd, data, size); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the command."); + return status; + } + + status = divesoft_freedom_recv (device, progress, msg, buffer); + if(status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive response."); + return status; + } + + return status; +} + +static dc_status_t +divesoft_freedom_download (divesoft_freedom_device_t *device, message_t cmd, const unsigned char cdata[], size_t csize, unsigned char rdata[], size_t rsize) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + + dc_buffer_t *buffer = dc_buffer_new (rsize); + if (buffer == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_exit; + } + + message_t msg = MSG_ECHO; + status = divesoft_freedom_transfer (device, NULL, cmd, cdata, csize, &msg, buffer); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to transfer the packet."); + goto error_free; + } + + if (msg != cmd + 1) { + ERROR (abstract->context, "Unexpected response message (%u).", msg); + status = DC_STATUS_PROTOCOL; + goto error_free; + } + + size_t length = dc_buffer_get_size (buffer); + if (length != rsize) { + ERROR (abstract->context, "Unexpected response length (" DC_PRINTF_SIZE " " DC_PRINTF_SIZE ").", length, rsize); + status = DC_STATUS_PROTOCOL; + goto error_free; + } + + if (rsize) { + memcpy (rdata, dc_buffer_get_data (buffer), rsize); + } + +error_free: + dc_buffer_free (buffer); +error_exit: + return status; +} + +dc_status_t +divesoft_freedom_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream) +{ + dc_status_t status = DC_STATUS_SUCCESS; + divesoft_freedom_device_t *device = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + device = (divesoft_freedom_device_t *) dc_device_allocate (context, &divesoft_freedom_device_vtable); + if (device == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Set the default values. + device->iostream = NULL; + memset(device->fingerprint, 0, sizeof(device->fingerprint)); + device->seqnum = 0; + + // Setup the HDLC communication. + status = dc_hdlc_open (&device->iostream, context, iostream, 244, 244); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to create the HDLC stream."); + goto error_free; + } + + // Set the serial communication protocol (115200 8N1). + status = dc_iostream_configure (device->iostream, 115200, 8, DC_PARITY_NONE, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to set the terminal attributes."); + goto error_free_hdlc; + } + + // 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_hdlc; + } + + // Initiate the connection with the dive computer. + const char client[] = "libdivecomputer"; + unsigned char cmd_connect[2 + sizeof(client) - 1] = {0}; + array_uint16_le_set (cmd_connect, COMPRESSION); + memcpy (cmd_connect + 2, client, sizeof(client) - 1); + unsigned char rsp_connect[36] = {0}; + status = divesoft_freedom_download (device, MSG_CONNECT, cmd_connect, sizeof(cmd_connect), rsp_connect, sizeof(rsp_connect)); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to connect to the device."); + goto error_free_hdlc; + } + + DEBUG (context, "Connection: compression=%u, protocol=%u.%u, serial=%.16s", + array_uint16_le (rsp_connect), + rsp_connect[2], rsp_connect[3], + rsp_connect + 4); + + *out = (dc_device_t *) device; + + return DC_STATUS_SUCCESS; + +error_free_hdlc: + dc_iostream_close (device->iostream); +error_free: + dc_device_deallocate ((dc_device_t *) device); + return status; +} + +static dc_status_t +divesoft_freedom_device_close (dc_device_t *abstract) +{ + divesoft_freedom_device_t *device = (divesoft_freedom_device_t *) abstract; + + return dc_iostream_close (device->iostream); +} + +static dc_status_t +divesoft_freedom_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size) +{ + divesoft_freedom_device_t *device = (divesoft_freedom_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 +divesoft_freedom_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) +{ + dc_status_t status = DC_STATUS_SUCCESS; + divesoft_freedom_device_t *device = (divesoft_freedom_device_t *) abstract; + + // Enable progress notifications. + dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Read the device information. + unsigned char rsp_version[26] = {0}; + status = divesoft_freedom_download (device, MSG_VERSION, NULL, 0, rsp_version, sizeof(rsp_version)); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the device information."); + goto error_exit; + } + + DEBUG (abstract->context, "Device: model=%u, hw=%u.%u, sw=%u.%u.%u.%u serial=%.16s", + rsp_version[0], + rsp_version[1], rsp_version[2], + rsp_version[3], rsp_version[4], rsp_version[5], + array_uint32_le (rsp_version + 6), + rsp_version + 10); + + // Emit a device info event. + dc_event_devinfo_t devinfo; + devinfo.model = rsp_version[0]; + devinfo.firmware = array_uint24_be (rsp_version + 3); + devinfo.serial = array_convert_str2num (rsp_version + 10 + 5, 11); + device_event_emit(abstract, DC_EVENT_DEVINFO, &devinfo); + + // Allocate memory for the dive list. + dc_buffer_t *divelist = dc_buffer_new (0); + if (divelist == NULL) { + status = DC_STATUS_NOMEMORY; + goto error_exit; + } + + // Allocate memory for the download buffer. + dc_buffer_t *buffer = dc_buffer_new (NRECORDS * (4 + FINGERPRINT_SIZE + HEADER_SIZE_V2)); + if (buffer == NULL) { + status = DC_STATUS_NOMEMORY; + goto error_free_divelist; + } + + // Record version and size. + unsigned int version = 0; + unsigned int headersize = 0; + unsigned int recordsize = 0; + + // Download the dive list. + unsigned int ndives = 0; + unsigned int total = 0; + unsigned int maxsize = 0; + unsigned int current = INVALID; + while (1) { + // Clear the buffer. + dc_buffer_clear (buffer); + + // Prepare the command. + unsigned char cmd_list[6] = {0}; + array_uint32_le_set (cmd_list, current); + cmd_list[4] = DIRECTION; + cmd_list[5] = NRECORDS; + + // Download the dive list records. + message_t msg_list = MSG_ECHO; + status = divesoft_freedom_transfer (device, &progress, MSG_DIVE_LIST, cmd_list, sizeof(cmd_list), &msg_list, buffer); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to download the dive list."); + goto error_free_buffer; + } + + // Check the response message type. + if (msg_list != MSG_DIVE_LIST_V1 && msg_list != MSG_DIVE_LIST_V2) { + ERROR (abstract->context, "Unexpected response message (%u).", msg_list); + status = DC_STATUS_PROTOCOL; + goto error_free_buffer; + } + + // Store/check the version. + if (version == 0) { + version = msg_list; + headersize = version == MSG_DIVE_LIST_V1 ? + HEADER_SIZE_V1 : HEADER_SIZE_V2; + recordsize = 4 + FINGERPRINT_SIZE + headersize; + } else if (version != msg_list) { + ERROR (abstract->context, "Unexpected response message (%u).", msg_list); + status = DC_STATUS_PROTOCOL; + goto error_free_buffer; + } + + const unsigned char *data = dc_buffer_get_data (buffer); + size_t size = dc_buffer_get_size (buffer); + + // Process the records. + size_t offset = 0, count = 0; + while (offset + recordsize <= size) { + // Get the record data. + unsigned int handle = array_uint32_le (data + offset); + const unsigned char *fingerprint = data + offset + 4; + const unsigned char *header = data + offset + 4 + FINGERPRINT_SIZE; + + // Check the fingerprint data. + if (memcmp (device->fingerprint, fingerprint, sizeof(device->fingerprint)) == 0) { + break; + } + + // Get the length of the dive. + unsigned int nrecords = version == MSG_DIVE_LIST_V1 ? + array_uint32_le (header + 16) & 0x3FFFF : + array_uint32_le (header + 20); + unsigned int length = headersize + nrecords * RECORD_SIZE; + + // Calculate the total and maximum size. + if (length > maxsize) + maxsize = length; + total += length; + + // Set the handle for the next request. + current = handle; + + offset += recordsize; + count++; + ndives++; + } + + // Append the records to the dive list buffer. + if (!dc_buffer_append (divelist, data, count * recordsize)) { + ERROR (abstract->context, "Insufficient buffer space available."); + status = DC_STATUS_NOMEMORY; + goto error_free_buffer; + } + + // Stop downloading if there are no more records. + if (count < NRECORDS) + break; + } + + // Update and emit a progress event. + progress.maximum = progress.current + total; + device_event_emit(abstract, DC_EVENT_PROGRESS, &progress); + + // Reserve memory for the largest dive. + dc_buffer_reserve (buffer, maxsize); + + const unsigned char *data = dc_buffer_get_data (divelist); + size_t size = dc_buffer_get_size (divelist); + + size_t offset = 0; + while (offset + recordsize <= size) { + // Get the record data. + unsigned int handle = array_uint32_le (data + offset); + const unsigned char *fingerprint = data + offset + 4; + const unsigned char *header = data + offset + 4 + FINGERPRINT_SIZE; + + // Get the length of the dive. + unsigned int nrecords = version == MSG_DIVE_LIST_V1 ? + array_uint32_le (header + 16) & 0x3FFFF : + array_uint32_le (header + 20); + unsigned int length = headersize + nrecords * RECORD_SIZE; + + // Clear the buffer. + dc_buffer_clear (buffer); + + // Prepare the command. + unsigned char cmd_dive[12] = {0}; + array_uint32_le_set (cmd_dive + 0, handle); + array_uint32_le_set (cmd_dive + 4, 0); + array_uint32_le_set (cmd_dive + 8, length); + + // Download the dive. + message_t msg_dive = MSG_ECHO; + status = divesoft_freedom_transfer (device, &progress, MSG_DIVE_DATA, cmd_dive, sizeof(cmd_dive), &msg_dive, buffer); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to download the dive."); + goto error_free_buffer; + } + + // Check the response message type. + if (msg_dive != MSG_DIVE_DATA_RSP) { + ERROR (abstract->context, "Unexpected response message (%u).", msg_dive); + status = DC_STATUS_PROTOCOL; + goto error_free_buffer; + } + + // Verify both dive headers are identical. + if (dc_buffer_get_size (buffer) < headersize || + memcmp (header, dc_buffer_get_data (buffer), headersize) != 0) { + ERROR (abstract->context, "Unexpected profile header."); + status = DC_STATUS_PROTOCOL; + goto error_free_buffer; + } + + if (callback && !callback (dc_buffer_get_data(buffer), dc_buffer_get_size(buffer), fingerprint, sizeof (device->fingerprint), userdata)) { + break; + } + + offset += recordsize; + } + +error_free_buffer: + dc_buffer_free (buffer); +error_free_divelist: + dc_buffer_free (divelist); +error_exit: + return status; +} diff --git a/src/divesoft_freedom.h b/src/divesoft_freedom.h new file mode 100644 index 0000000..db04edb --- /dev/null +++ b/src/divesoft_freedom.h @@ -0,0 +1,43 @@ +/* + * libdivecomputer + * + * Copyright (C) 2023 Jan MatouÅ¡ek, 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 DIVESOFT_FREEDOM_H +#define DIVESOFT_FREEDOM_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +dc_status_t +divesoft_freedom_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); + +dc_status_t +divesoft_freedom_parser_create (dc_parser_t **parser, dc_context_t *context); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* DIVESOFT_FREEDOM_H */ diff --git a/src/divesoft_freedom_parser.c b/src/divesoft_freedom_parser.c new file mode 100644 index 0000000..b595e63 --- /dev/null +++ b/src/divesoft_freedom_parser.c @@ -0,0 +1,1070 @@ +/* + * libdivecomputer + * + * Copyright (C) 2023 Jan MatouÅ¡ek, 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 "divesoft_freedom.h" +#include "context-private.h" +#include "parser-private.h" +#include "checksum.h" +#include "array.h" + +#define UNDEFINED 0xFFFFFFFF + +#define EPOCH 946684800 // 2000-01-01 00:00:00 UTC + +#define OC 0 +#define OXYGEN 1 +#define DILUENT 2 + +#define NSENSORS 4 +#define NGASMIXES 12 +#define NTANKS 12 + +#define HEADER_SIGNATURE_V1 0x45766944 // "DivE" +#define HEADER_SIGNATURE_V2 0x45566944 // "DiVE" + +#define HEADER_SIZE_V1 32 +#define HEADER_SIZE_V2 64 + +#define RECORD_SIZE 16 + +#define SEAWATER 1028 +#define FRESHWATER 1000 + +typedef enum logrecord_t { + LREC_POINT = 0, + LREC_MANIPULATION = 1, + LREC_AUTO = 2, + LREC_DIVER_ERROR = 3, + LREC_INTERNAL_ERROR = 4, + LREC_ACTIVITY = 5, + LREC_CONFIGURATION = 6, + LREC_MEASURE = 7, + LREC_STATE = 8, + LREC_INFO = 9, +} logrecord_t; + +typedef enum point_id_t { + POINT_1 = 0, + POINT_2 = 1, + POINT_1_OLD = 0x3FF, +} point_id_t; + +typedef enum configuration_id_t { + CFG_ID_TEST_CCR_FULL = 0, + CFG_ID_TEST_CCR_PARTIAL = 1, + CFG_ID_OXYGEN_CALIBRATION = 2, + CFG_ID_SERIAL = 3, + CFG_ID_DECO = 4, + CFG_ID_VERSION = 5, + CFG_ID_ASCENT = 6, + CFG_ID_AI = 7, + CFG_ID_CCR = 8, + CFG_ID_DILUENTS = 9, +} configuration_id_t; + +typedef enum measure_id_t { + MEASURE_ID_OXYGEN = 0, + MEASURE_ID_BATTERY = 1, + MEASURE_ID_HELIUM = 2, + MEASURE_ID_OXYGEN_MV = 3, + MEASURE_ID_GPS = 4, + MEASURE_ID_PRESSURE = 5, + MEASURE_ID_AI_SAC = 6, + MEASURE_ID_AI_PRESSURE = 7, + MEASURE_ID_BRIGHTNESS = 8, + MEASURE_ID_AI_STAT = 9, +} measure_id_t; + +typedef enum state_id_t { + STATE_ID_DECO_N2LOW = 0, + STATE_ID_DECO_N2HIGH = 1, + STATE_ID_DECO_HELOW = 2, + STATE_ID_DECO_HEHIGH = 3, + STATE_ID_PLAN_STEPS = 4, +} state_id_t; + +typedef enum event_t { + EVENT_DUMMY = 0, + EVENT_SETPOINT_MANUAL = 1, + EVENT_SETPOINT_AUTO = 2, + EVENT_OC = 3, + EVENT_CCR = 4, + EVENT_MIX_CHANGED = 5, + EVENT_START = 6, + EVENT_TOO_FAST = 7, + EVENT_ABOVE_CEILING = 8, + EVENT_TOXIC = 9, + EVENT_HYPOX = 10, + EVENT_CRITICAL = 11, + EVENT_SENSOR_DISABLED = 12, + EVENT_SENSOR_ENABLED = 13, + EVENT_O2_BACKUP = 14, + EVENT_PEER_DOWN = 15, + EVENT_HS_DOWN = 16, + EVENT_INCONSISTENT = 17, + EVENT_KEYDOWN = 18, + EVENT_SCR = 19, + EVENT_ABOVE_STOP = 20, + EVENT_SAFETY_MISS = 21, + EVENT_FATAL = 22, + EVENT_DILUENT = 23, + EVENT_CHANGE_MODE = 24, + EVENT_SOLENOID = 25, + EVENT_BOOKMARK = 26, + EVENT_GF_SWITCH = 27, + EVENT_PEER_UP = 28, + EVENT_HS_UP = 29, + EVENT_CNS = 30, + EVENT_BATTERY_LOW = 31, + EVENT_PPO2_LOST = 32, + EVENT_SENSOR_VALUE_BAD = 33, + EVENT_SAFETY_STOP_END = 34, + EVENT_DECO_STOP_END = 35, + EVENT_DEEP_STOP_END = 36, + EVENT_NODECO_END = 37, + EVENT_DEPTH_REACHED = 38, + EVENT_TIME_ELAPSED = 39, + EVENT_STACK_USAGE = 40, + EVENT_GAS_SWITCH_INFO = 41, + EVENT_PRESSURE_SENS_WARN = 42, + EVENT_PRESSURE_SENS_FAIL = 43, + EVENT_CHECK_O2_SENSORS = 44, + EVENT_SWITCH_TO_COMP_SCR = 45, + EVENT_GAS_LOST = 46, + EVENT_AIRBREAK = 47, + EVENT_AIRBREAK_END = 48, + EVENT_AIRBREAK_MISSED = 49, + EVENT_BORMT_EXPIRATION = 50, + EVENT_BORMT_EXPIRED = 51, + EVENT_SENSOR_EXCLUDED = 52, + EVENT_PREBR_SKIPPED = 53, + EVENT_BOCCR_BORMT_EXPIRED = 54, + EVENT_WAYPOINT = 55, + EVENT_TURNAROUND = 56, + EVENT_SOLENOID_FAILURE = 57, + EVENT_SM_CYL_PRESS_DIFF = 58, + EVENT_BAILOUT_MOD_EXCEEDED = 59, +} event_t; + +typedef enum divemode_t { + STMODE_UNKNOWN = 0, + STMODE_OC = 1, + STMODE_CCR = 2, + STMODE_MCCR = 3, + STMODE_FREE = 4, + STMODE_GAUGE = 5, + STMODE_ASCR = 6, + STMODE_PSCR = 7, + STMODE_BOCCR = 8, +} divemode_t; + +typedef enum setpoint_change_t { + SP_MANUAL = 0, + SP_AUTO_START = 1, + SP_AUTO_HYPOX = 2, + SP_AUTO_TIMEOUT = 3, + SP_AUTO_ASCENT = 4, + SP_AUTO_STALL = 5, + SP_AUTO_SPLOW = 6, + SP_AUTO_DEPTH_DESC = 7, + SP_AUTO_DEPTH_ASC = 8, +} setpoint_change_t; + +typedef enum sensor_state_t { + SENSTAT_NORMAL = 0, + SENSTAT_OVERRANGE = 1, + SENSTAT_DISABLED = 2, + SENSTAT_EXCLUDED = 3, + SENSTAT_UNCALIBRATED = 4, + SENSTAT_ERROR = 5, + SENSTAT_OFFLINE = 6, + SENSTAT_INHIBITED = 7, + SENSTAT_NOT_EXIST = 8, +} sensor_state_t; + +typedef enum battery_state_t { + BATSTATE_NO_BATTERY = 0, + BATSTATE_UNKNOWN = 1, + BATSTATE_DISCHARGING = 2, + BATSTATE_CHARGING = 3, + BATSTATE_FULL = 4, +} battery_state_t; + +typedef struct divesoft_freedom_gasmix_t { + unsigned int oxygen; + unsigned int helium; + unsigned int type; + unsigned int id; +} divesoft_freedom_gasmix_t; + +typedef struct divesoft_freedom_tank_t { + unsigned int volume; + unsigned int workpressure; + unsigned int beginpressure; + unsigned int endpressure; + unsigned int transmitter; + unsigned int active; +} divesoft_freedom_tank_t; + +typedef struct divesoft_freedom_parser_t { + dc_parser_t base; + // Cached fields. + unsigned int cached; + unsigned int version; + unsigned int headersize; + unsigned int divetime; + unsigned int divemode; + int temperature_min; + unsigned int maxdepth; + unsigned int atmospheric; + unsigned int avgdepth; + unsigned int ngasmixes; + divesoft_freedom_gasmix_t gasmix[NGASMIXES]; + unsigned int diluent; + unsigned int ntanks; + divesoft_freedom_tank_t tank[NTANKS]; + unsigned int vpm; + unsigned int gf_lo; + unsigned int gf_hi; + unsigned int seawater; + unsigned int calibration[NSENSORS]; + unsigned int calibrated; +} divesoft_freedom_parser_t; + +static dc_status_t divesoft_freedom_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); +static dc_status_t divesoft_freedom_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); +static dc_status_t divesoft_freedom_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); +static dc_status_t divesoft_freedom_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); + +static const dc_parser_vtable_t divesoft_freedom_parser_vtable = { + sizeof(divesoft_freedom_parser_t), + DC_FAMILY_DIVESOFT_FREEDOM, + divesoft_freedom_parser_set_data, /* set_data */ + NULL, /* set_clock */ + NULL, /* set_atmospheric */ + NULL, /* set_density */ + divesoft_freedom_parser_get_datetime, /* datetime */ + divesoft_freedom_parser_get_field, /* fields */ + divesoft_freedom_parser_samples_foreach, /* samples_foreach */ + NULL /* destroy */ +}; + +static unsigned int +divesoft_freedom_find_gasmix (divesoft_freedom_gasmix_t gasmix[], unsigned int count, unsigned int oxygen, unsigned int helium, unsigned int type) +{ + unsigned int i = 0; + while (i < count) { + if (oxygen == gasmix[i].oxygen && + helium == gasmix[i].helium && + type == gasmix[i].type) + break; + i++; + } + + return i; +} + +static unsigned int +divesoft_freedom_find_tank (divesoft_freedom_tank_t tank[], unsigned int count, unsigned int transmitter) +{ + unsigned int i = 0; + while (i < count) { + if (transmitter == tank[i].transmitter) + break; + i++; + } + + return i; +} + +static unsigned int +divesoft_freedom_is_ccr (unsigned int divemode) +{ + return divemode == STMODE_CCR || divemode == STMODE_MCCR || + divemode == STMODE_ASCR || divemode == STMODE_PSCR || + divemode == STMODE_BOCCR; +} + +static dc_status_t +divesoft_freedom_cache (divesoft_freedom_parser_t *parser) +{ + dc_parser_t *abstract = (dc_parser_t *) parser; + const unsigned char *data = abstract->data; + unsigned int size = abstract->size; + + if (parser->cached) { + return DC_STATUS_SUCCESS; + } + + unsigned int headersize = 4; + if (size < headersize) { + ERROR (abstract->context, "Unexpected header size (%u).", size); + return DC_STATUS_DATAFORMAT; + } + + unsigned int version = array_uint32_le (data); + if (version == HEADER_SIGNATURE_V1) { + headersize = HEADER_SIZE_V1; + } else if (version == HEADER_SIGNATURE_V2) { + headersize = HEADER_SIZE_V2; + } else { + ERROR (abstract->context, "Unexpected header version (%08x).", version); + return DC_STATUS_DATAFORMAT; + } + + if (size < headersize) { + ERROR (abstract->context, "Unexpected header size (%u).", size); + return DC_STATUS_DATAFORMAT; + } + + unsigned short crc = array_uint16_le (data + 4); + unsigned short ccrc = checksum_crc16r_ansi (data + 6, headersize - 6, 0xFFFF, 0x0000); + if (crc != ccrc) { + ERROR (abstract->context, "Invalid header checksum (%04x %04x).", crc, ccrc); + return DC_STATUS_DATAFORMAT; + } + + // Parse the dive header. + unsigned int divetime = 0; + unsigned int divemode = 0; + unsigned int temperature_min = 0; + unsigned int maxdepth = 0; + unsigned int atmospheric = 0; + unsigned int avgdepth = 0; + unsigned int diluent_o2 = 0, diluent_he = 0; + if (version == HEADER_SIGNATURE_V1) { + unsigned int misc1 = array_uint32_le (data + 12); + unsigned int misc2 = array_uint32_le (data + 16); + divetime = misc1 & 0x1FFFF; + divemode = (misc1 & 0x38000000) >> 27; + temperature_min = (signed int) signextend ((misc2 & 0xFFC0000) >> 18, 10); + maxdepth = array_uint16_le (data + 20); + atmospheric = array_uint16_le (data + 24); + avgdepth = 0; + diluent_o2 = data[26]; + diluent_he = data[27]; + } else { + divetime = array_uint32_le (data + 12); + divemode = data[18]; + temperature_min = (signed short) array_uint16_le (data + 24); + maxdepth = array_uint16_le (data + 28); + atmospheric = array_uint16_le (data + 32); + avgdepth = array_uint16_le (data + 38); + diluent_o2 = 0; + diluent_he = 0; + + DEBUG (abstract->context, "Device: serial=%.4s-%.8s", + data + 52, data + 56); + } + + divesoft_freedom_gasmix_t gasmix_ai[NGASMIXES] = {0}, + gasmix_diluent[NGASMIXES] = {0}, + gasmix_event[NGASMIXES] = {0}; + unsigned int ngasmix_ai = 0, + ngasmix_diluent = 0, + ngasmix_event = 0; + divesoft_freedom_tank_t tank[NTANKS] = {0}; + unsigned int ntanks = 0; + + unsigned int vpm = 0, gf_lo = 0, gf_hi = 0; + unsigned int seawater = 0; + unsigned int calibration[NSENSORS] = {0}; + unsigned int calibrated = 0; + + unsigned int gasmixid_previous = UNDEFINED; + + + // Parse the dive profile. + unsigned int offset = headersize; + while (offset + RECORD_SIZE <= size) { + if (array_isequal(data + offset, RECORD_SIZE, 0xFF)) { + WARNING (abstract->context, "Skipping empty sample."); + offset += RECORD_SIZE; + continue; + } + + unsigned int flags = array_uint32_le (data + offset); + unsigned int type = (flags & 0x0000000F) >> 0; + unsigned int id = (flags & 0x7FE00000) >> 21; + + if (type == LREC_CONFIGURATION) { + // Configuration record. + if (id == CFG_ID_DECO) { + unsigned int misc = array_uint16_le (data + offset + 4); + gf_lo = data[offset + 8]; + gf_hi = data[offset + 9]; + seawater = misc & 0x02; + vpm = misc & 0x20; + } else if (id == CFG_ID_VERSION) { + DEBUG (abstract->context, "Device: model=%u, hw=%u.%u, sw=%u.%u.%u.%u flags=%u", + data[offset + 4], + data[offset + 5], data[offset + 6], + data[offset + 7], data[offset + 8], data[offset + 9], + array_uint32_le (data + offset + 12), + array_uint16_le (data + offset + 10)); + } else if (id == CFG_ID_SERIAL) { + DEBUG (abstract->context, "Device: serial=%.4s-%.8s", + data + offset + 4, data + offset + 8); + } else if (id == CFG_ID_DILUENTS) { + for (unsigned int i = 0; i < 4; ++i) { + unsigned int o2 = data[offset + 4 + i * 3 + 0]; + unsigned int he = data[offset + 4 + i * 3 + 1]; + unsigned int state = data[offset + 4 + i * 3 + 2]; + if (state & 0x01) { + if (ngasmix_diluent >= NGASMIXES) { + ERROR (abstract->context, "Maximum number of gas mixes reached."); + return DC_STATUS_NOMEMORY; + } + gasmix_diluent[ngasmix_diluent].oxygen = o2; + gasmix_diluent[ngasmix_diluent].helium = he; + gasmix_diluent[ngasmix_diluent].type = DILUENT; + gasmix_diluent[ngasmix_diluent].id = (state & 0xFE) >> 1; + ngasmix_diluent++; + } + } + } else if (id == CFG_ID_OXYGEN_CALIBRATION) { + for (unsigned int i = 0; i < NSENSORS; ++i) { + calibration[i] = array_uint16_le (data + offset + 4 + i * 2); + } + calibrated = 1; + } else if (id == CFG_ID_AI) { + unsigned int o2 = data[offset + 4]; + unsigned int he = data[offset + 5]; + unsigned int volume = array_uint16_le (data + offset + 6); + unsigned int workpressure = array_uint16_le (data + offset + 8); + unsigned int transmitter = data[offset + 10]; + unsigned int gasmixid = data[offset + 11]; + + // Workaround for a bug in some pre-release firmware versions, + // where the ID of the CCR gas mixes (oxygen and diluent) is + // not stored correctly. + if (gasmixid < 10 && gasmixid <= gasmixid_previous && gasmixid_previous != UNDEFINED) { + WARNING (abstract->context, "Fixed the CCR gas mix id (%u -> %u) for tank %u.", + gasmixid, gasmixid + 10, ntanks); + gasmixid += 10; + } + gasmixid_previous = gasmixid; + + // Add the gas mix. + if (ngasmix_ai >= NGASMIXES) { + ERROR (abstract->context, "Maximum number of gas mixes reached."); + return DC_STATUS_NOMEMORY; + } + gasmix_ai[ngasmix_ai].oxygen = o2; + gasmix_ai[ngasmix_ai].helium = he; + if (gasmixid == 10) { + gasmix_ai[ngasmix_ai].type = OXYGEN; + } else if (gasmixid == 11) { + gasmix_ai[ngasmix_ai].type = DILUENT; + } else { + gasmix_ai[ngasmix_ai].type = OC; + } + gasmix_ai[ngasmix_ai].id = gasmixid; + ngasmix_ai++; + + // Add the tank. + if (ntanks >= NTANKS) { + ERROR (abstract->context, "Maximum number of tanks reached."); + return DC_STATUS_NOMEMORY; + } + tank[ntanks].volume = volume; + tank[ntanks].workpressure = workpressure; + tank[ntanks].transmitter = transmitter; + ntanks++; + } + } else if ((type >= LREC_MANIPULATION && type <= LREC_ACTIVITY) || type == LREC_INFO) { + // Event record. + unsigned int event = array_uint16_le (data + offset + 4); + + if (event == EVENT_MIX_CHANGED || event == EVENT_DILUENT || event == EVENT_CHANGE_MODE) { + unsigned int o2 = data[offset + 6]; + unsigned int he = data[offset + 7]; + unsigned int mixtype = OC; + if (event == EVENT_DILUENT) { + mixtype = DILUENT; + } else if (event == EVENT_CHANGE_MODE) { + unsigned int mode = data[offset + 8]; + if (divesoft_freedom_is_ccr (mode)) { + mixtype = DILUENT; + } + } + + unsigned int idx = divesoft_freedom_find_gasmix (gasmix_event, ngasmix_event, o2, he, mixtype); + if (idx >= ngasmix_event) { + if (ngasmix_event >= NGASMIXES) { + ERROR (abstract->context, "Maximum number of gas mixes reached."); + return DC_STATUS_NOMEMORY; + } + gasmix_event[ngasmix_event].oxygen = o2; + gasmix_event[ngasmix_event].helium = he; + gasmix_event[ngasmix_event].type = mixtype; + gasmix_event[ngasmix_event].id = UNDEFINED; + ngasmix_event++; + } + } + } else if (type == LREC_MEASURE) { + // Measurement record. + if (id == MEASURE_ID_AI_PRESSURE) { + for (unsigned int i = 0; i < NTANKS; ++i) { + unsigned int pressure = data[offset + 4 + i]; + if (pressure == 0 || pressure == 0xFF) + continue; + + unsigned int idx = divesoft_freedom_find_tank (tank, ntanks, i); + if (idx >= ntanks) { + ERROR (abstract->context, "Tank %u not found.", idx); + return DC_STATUS_DATAFORMAT; + } + + if (!tank[idx].active) { + tank[idx].active = 1; + tank[idx].beginpressure = pressure; + tank[idx].endpressure = pressure; + } + tank[idx].endpressure = pressure; + } + } + } + + offset += RECORD_SIZE; + } + + unsigned int ngasmixes = 0; + divesoft_freedom_gasmix_t gasmix[NGASMIXES] = {0}; + unsigned int diluent = UNDEFINED; + + // Add the gas mixes from the AI integration records. + for (unsigned int i = 0; i < ngasmix_ai; ++i) { + gasmix[ngasmixes] = gasmix_ai[i]; + ngasmixes++; + } + + // Add the gas mixes from the diluent records. + for (unsigned int i = 0; i < ngasmix_diluent; ++i) { + unsigned int idx = divesoft_freedom_find_gasmix (gasmix, ngasmixes, + gasmix_diluent[i].oxygen, gasmix_diluent[i].helium, gasmix_diluent[i].type); + if (idx >= ngasmixes) { + if (ngasmixes >= NGASMIXES) { + ERROR (abstract->context, "Maximum number of gas mixes reached."); + return DC_STATUS_NOMEMORY; + } + gasmix[ngasmixes] = gasmix_diluent[i]; + ngasmixes++; + } + } + + // Add the initial diluent. + if (divesoft_freedom_is_ccr (divemode) && + (diluent_o2 != 0 || diluent_he != 0)) { + unsigned int idx = divesoft_freedom_find_gasmix (gasmix, ngasmixes, + diluent_o2, diluent_he, DILUENT); + if (idx >= ngasmixes) { + if (ngasmixes >= NGASMIXES) { + ERROR (abstract->context, "Maximum number of gas mixes reached."); + return DC_STATUS_NOMEMORY; + } + gasmix[ngasmixes].oxygen = diluent_o2; + gasmix[ngasmixes].helium = diluent_he; + gasmix[ngasmixes].type = DILUENT; + gasmix[ngasmixes].id = UNDEFINED; + ngasmixes++; + } + + // Index of the initial diluent. + diluent = idx; + } + + // Add the gas mixes from the gas change events. + for (unsigned int i = 0; i < ngasmix_event; ++i) { + unsigned int idx = divesoft_freedom_find_gasmix (gasmix, ngasmixes, + gasmix_event[i].oxygen, gasmix_event[i].helium, gasmix_event[i].type); + if (idx >= ngasmixes) { + if (ngasmixes >= NGASMIXES) { + ERROR (abstract->context, "Maximum number of gas mixes reached."); + return DC_STATUS_NOMEMORY; + } + gasmix[ngasmixes] = gasmix_event[i]; + ngasmixes++; + } + } + + // Cache the data for later use. + parser->cached = 1; + parser->version = version; + parser->headersize = headersize; + parser->divetime = divetime; + parser->divemode = divemode; + parser->temperature_min = temperature_min; + parser->maxdepth = maxdepth; + parser->atmospheric = atmospheric; + parser->avgdepth = avgdepth; + parser->ngasmixes = ngasmixes; + for (unsigned int i = 0; i < ngasmixes; ++i) { + parser->gasmix[i] = gasmix[i]; + } + parser->diluent = diluent; + parser->ntanks = ntanks; + for (unsigned int i = 0; i < ntanks; ++i) { + parser->tank[i] = tank[i]; + } + parser->vpm = vpm; + parser->gf_lo = gf_lo; + parser->gf_hi = gf_hi; + parser->seawater = seawater; + for (unsigned int i = 0; i < NSENSORS; ++i) { + parser->calibration[i] = calibration[i]; + } + parser->calibrated = calibrated; + + return DC_STATUS_SUCCESS; +} + +dc_status_t +divesoft_freedom_parser_create (dc_parser_t **out, dc_context_t *context) +{ + divesoft_freedom_parser_t *parser = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + parser = (divesoft_freedom_parser_t *) dc_parser_allocate (context, &divesoft_freedom_parser_vtable); + if (parser == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Set the default values. + parser->cached = 0; + parser->version = 0; + parser->headersize = 0; + parser->divetime = 0; + parser->divemode = 0; + parser->temperature_min = 0; + parser->maxdepth = 0; + parser->atmospheric = 0; + parser->avgdepth = 0; + parser->ngasmixes = 0; + 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].id = 0; + } + parser->diluent = UNDEFINED; + parser->ntanks = 0; + for (unsigned int i = 0; i < NTANKS; ++i) { + parser->tank[i].volume = 0; + parser->tank[i].workpressure = 0; + parser->tank[i].beginpressure = 0; + parser->tank[i].endpressure = 0; + parser->tank[i].transmitter = 0; + parser->tank[i].active = 0; + } + parser->vpm = 0; + parser->gf_lo = 0; + parser->gf_hi = 0; + parser->seawater = 0; + for (unsigned int i = 0; i < NSENSORS; ++i) { + parser->calibration[i] = 0; + } + parser->calibrated = 0; + + *out = (dc_parser_t *) parser; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +divesoft_freedom_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) +{ + divesoft_freedom_parser_t *parser = (divesoft_freedom_parser_t *) abstract; + + // Reset the cache. + parser->cached = 0; + parser->version = 0; + parser->headersize = 0; + parser->divetime = 0; + parser->divemode = 0; + parser->temperature_min = 0; + parser->maxdepth = 0; + parser->atmospheric = 0; + parser->avgdepth = 0; + parser->ngasmixes = 0; + 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].id = 0; + } + parser->diluent = UNDEFINED; + parser->ntanks = 0; + for (unsigned int i = 0; i < NTANKS; ++i) { + parser->tank[i].volume = 0; + parser->tank[i].workpressure = 0; + parser->tank[i].beginpressure = 0; + parser->tank[i].endpressure = 0; + parser->tank[i].transmitter = 0; + parser->tank[i].active = 0; + } + parser->vpm = 0; + parser->gf_lo = 0; + parser->gf_hi = 0; + parser->seawater = 0; + for (unsigned int i = 0; i < NSENSORS; ++i) { + parser->calibration[i] = 0; + } + parser->calibrated = 0; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +divesoft_freedom_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) +{ + dc_status_t status = DC_STATUS_SUCCESS; + divesoft_freedom_parser_t *parser = (divesoft_freedom_parser_t *) abstract; + const unsigned char *data = abstract->data; + + // Cache the header data. + status = divesoft_freedom_cache (parser); + if (status != DC_STATUS_SUCCESS) + return status; + + unsigned int timestamp = array_uint32_le (data + 8); + dc_ticks_t ticks = (dc_ticks_t) timestamp + EPOCH; + + if (!dc_datetime_gmtime (datetime, ticks)) + return DC_STATUS_DATAFORMAT; + + if (parser->version == HEADER_SIGNATURE_V2) { + datetime->timezone = ((signed short) array_uint16_le (data + 40)) * 60; + } else { + datetime->timezone = DC_TIMEZONE_NONE; + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +divesoft_freedom_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) +{ + dc_status_t status = DC_STATUS_SUCCESS; + divesoft_freedom_parser_t *parser = (divesoft_freedom_parser_t *) abstract; + + // Cache the header data. + status = divesoft_freedom_cache (parser); + if (status != DC_STATUS_SUCCESS) + return status; + + dc_salinity_t *water = (dc_salinity_t *) value; + dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + dc_tank_t *tank = (dc_tank_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_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_AVGDEPTH: + if (parser->version != HEADER_SIGNATURE_V2) + return DC_STATUS_UNSUPPORTED; + *((double *) value) = parser->avgdepth / 100.0; + break; + case DC_FIELD_TEMPERATURE_MINIMUM: + *((double *) value) = parser->temperature_min / 10.0; + break; + case DC_FIELD_ATMOSPHERIC: + *((double *) value) = parser->atmospheric * 10.0 / BAR; + break; + case DC_FIELD_SALINITY: + water->type = parser->seawater ? DC_WATER_SALT : DC_WATER_FRESH; + water->density = parser->seawater ? SEAWATER : FRESHWATER; + break; + case DC_FIELD_DIVEMODE: + switch (parser->divemode) { + case STMODE_OC: + *((dc_divemode_t *) value) = DC_DIVEMODE_OC; + break; + case STMODE_CCR: + case STMODE_MCCR: + case STMODE_BOCCR: + *((dc_divemode_t *) value) = DC_DIVEMODE_CCR; + break; + case STMODE_FREE: + *((dc_divemode_t *) value) = DC_DIVEMODE_FREEDIVE; + break; + case STMODE_GAUGE: + *((dc_divemode_t *) value) = DC_DIVEMODE_GAUGE; + break; + case STMODE_ASCR: + case STMODE_PSCR: + *((dc_divemode_t *) value) = DC_DIVEMODE_SCR; + break; + case STMODE_UNKNOWN: + return DC_STATUS_UNSUPPORTED; + default: + return DC_STATUS_DATAFORMAT; + } + break; + case DC_FIELD_GASMIX_COUNT: + *((unsigned int *) value) = parser->ngasmixes; + break; + case DC_FIELD_GASMIX: + gasmix->helium = parser->gasmix[flags].helium / 100.0; + gasmix->oxygen = parser->gasmix[flags].oxygen / 100.0; + gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; + break; + case DC_FIELD_TANK_COUNT: + *((unsigned int *) value) = parser->ntanks; + break; + case DC_FIELD_TANK: + if (parser->tank[flags].volume > 990 || + parser->tank[flags].workpressure > 400) { + tank->type = DC_TANKVOLUME_NONE; + tank->volume = 0.0; + tank->workpressure = 0.0; + } else { + tank->type = DC_TANKVOLUME_METRIC; + tank->volume = parser->tank[flags].volume / 10.0; + tank->workpressure = parser->tank[flags].workpressure; + } + tank->beginpressure = parser->tank[flags].beginpressure * 2.0; + tank->endpressure = parser->tank[flags].endpressure * 2.0; + tank->gasmix = flags; + break; + case DC_FIELD_DECOMODEL: + if (parser->vpm) { + decomodel->type = DC_DECOMODEL_VPM; + decomodel->conservatism = 0; + } else { + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->conservatism = 0; + decomodel->params.gf.low = parser->gf_lo; + decomodel->params.gf.high = parser->gf_hi; + } + break; + default: + return DC_STATUS_UNSUPPORTED; + } + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +divesoft_freedom_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +{ + dc_status_t status = DC_STATUS_SUCCESS; + divesoft_freedom_parser_t *parser = (divesoft_freedom_parser_t *) abstract; + const unsigned char *data = abstract->data; + unsigned int size = abstract->size; + + // Cache the header data. + status = divesoft_freedom_cache (parser); + if (status != DC_STATUS_SUCCESS) + return status; + + unsigned int time = UNDEFINED; + unsigned int initial = 0; + unsigned int offset = parser->headersize; + while (offset + RECORD_SIZE <= size) { + dc_sample_value_t sample = {0}; + + if (array_isequal(data + offset, RECORD_SIZE, 0xFF)) { + WARNING (abstract->context, "Skipping empty sample."); + offset += RECORD_SIZE; + continue; + } + + unsigned int flags = array_uint32_le (data + offset); + unsigned int type = (flags & 0x0000000F) >> 0; + unsigned int timestamp = (flags & 0x001FFFF0) >> 4; + unsigned int id = (flags & 0x7FE00000) >> 21; + + if (timestamp != time) { + if (timestamp < time && time != UNDEFINED) { + // The timestamp are supposed to be monotonically increasing, + // but occasionally there are small jumps back in time with just + // 1 or 2 seconds. To get back in sync, those samples are + // skipped. Larger jumps are treated as errors. + if (time - timestamp > 5) { + ERROR (abstract->context, "Timestamp moved backwards (%u %u).", timestamp, time); + return DC_STATUS_DATAFORMAT; + } + WARNING (abstract->context, "Timestamp moved backwards (%u %u).", timestamp, time); + offset += RECORD_SIZE; + continue; + } + time = timestamp; + sample.time = time; + if (callback) callback(DC_SAMPLE_TIME, sample, userdata); + } + + // Initial diluent. + if (!initial) { + if (parser->diluent != UNDEFINED) { + sample.gasmix = parser->diluent; + if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata); + } + initial = 1; + } + + if (type == LREC_POINT) { + // General log record. + unsigned int depth = array_uint16_le (data + offset + 4); + unsigned int ppo2 = array_uint16_le (data + offset + 6); + + sample.depth = depth / 100.0; + if (callback) callback(DC_SAMPLE_DEPTH, sample, userdata); + + if (ppo2) { + sample.ppo2 = ppo2 * 10.0 / BAR; + if (callback) callback(DC_SAMPLE_PPO2, sample, userdata); + } + + if (id == POINT_2) { + unsigned int orientation = array_uint32_le (data + offset + 8); + unsigned int heading = orientation & 0x1FF; + sample.bearing = heading; + if (callback) callback (DC_SAMPLE_BEARING, sample, userdata); + } else if (id == POINT_1 || id == POINT_1_OLD) { + unsigned int misc = array_uint32_le (data + offset + 8); + unsigned int ceiling = array_uint16_le (data + offset + 12); + unsigned int setpoint = data[offset + 15]; + unsigned int ndl = (misc & 0x000003FF); + unsigned int tts = (misc & 0x000FFC00) >> 10; + unsigned int temp = (misc & 0x3FF00000) >> 20; + + // Temperature + sample.temperature = (signed int) signextend (temp, 10) / 10.0; + if (callback) callback(DC_SAMPLE_TEMPERATURE, sample, userdata); + + // Deco / NDL + if (ceiling) { + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.time = 0; + sample.deco.depth = ceiling / 100.0; + } else { + sample.deco.type = DC_DECO_NDL; + sample.deco.time = ndl * 60; + sample.deco.depth = 0.0; + } + if (callback) callback(DC_SAMPLE_DECO, sample, userdata); + + // Setpoint + if (setpoint) { + sample.setpoint = setpoint / 100.0; + if (callback) callback(DC_SAMPLE_SETPOINT, sample, userdata); + } + } + } else if ((type >= LREC_MANIPULATION && type <= LREC_ACTIVITY) || type == LREC_INFO) { + // Event record. + unsigned int event = array_uint16_le (data + offset + 4); + + if (event == EVENT_BOOKMARK) { + sample.event.type = SAMPLE_EVENT_BOOKMARK; + sample.event.time = 0; + sample.event.flags = 0; + sample.event.value = 0; + if (callback) callback(DC_SAMPLE_EVENT, sample, userdata); + } else if (event == EVENT_MIX_CHANGED || event == EVENT_DILUENT || event == EVENT_CHANGE_MODE) { + unsigned int o2 = data[offset + 6]; + unsigned int he = data[offset + 7]; + unsigned int mixtype = OC; + if (event == EVENT_DILUENT) { + mixtype = DILUENT; + } else if (event == EVENT_CHANGE_MODE) { + unsigned int mode = data[offset + 8]; + if (divesoft_freedom_is_ccr (mode)) { + mixtype = DILUENT; + } + } + + unsigned int idx = divesoft_freedom_find_gasmix (parser->gasmix, parser->ngasmixes, o2, he, mixtype); + if (idx >= parser->ngasmixes) { + ERROR (abstract->context, "Gas mix (%u/%u) not found.", o2, he); + return DC_STATUS_DATAFORMAT; + } + sample.gasmix = idx; + if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata); + } else if (event == EVENT_CNS) { + sample.cns = array_uint16_le (data + offset + 6) / 100.0; + if (callback) callback(DC_SAMPLE_CNS, sample, userdata); + } else if (event == EVENT_SETPOINT_MANUAL || event == EVENT_SETPOINT_AUTO) { + sample.setpoint = data[6] / 100.0; + if (callback) callback(DC_SAMPLE_SETPOINT, sample, userdata); + } + } else if (type == LREC_MEASURE) { + // Measurement record. + if (id == MEASURE_ID_AI_PRESSURE) { + for (unsigned int i = 0; i < NTANKS; ++i) { + unsigned int pressure = data[offset + 4 + i]; + if (pressure == 0 || pressure == 0xFF) + continue; + + unsigned int idx = divesoft_freedom_find_tank (parser->tank, parser->ntanks, i); + if (idx >= parser->ntanks) { + ERROR (abstract->context, "Tank %u not found.", idx); + return DC_STATUS_DATAFORMAT; + } + + sample.pressure.tank = idx; + sample.pressure.value = pressure * 2.0; + if (callback) callback(DC_SAMPLE_PRESSURE, sample, userdata); + } + } else if (id == MEASURE_ID_OXYGEN) { + for (unsigned int i = 0; i < NSENSORS; ++i) { + unsigned int ppo2 = array_uint16_le (data + offset + 4 + i * 2); + if (ppo2 == 0 || ppo2 == 0xFFFF) + continue; + sample.ppo2 = ppo2 * 10.0 / BAR; + if (callback) callback(DC_SAMPLE_PPO2, sample, userdata); + } + } else if (id == MEASURE_ID_OXYGEN_MV) { + for (unsigned int i = 0; i < NSENSORS; ++i) { + unsigned int value = array_uint16_le (data + offset + 4 + i * 2); + unsigned int state = data[offset + 12 + i]; + if (!parser->calibrated || state == SENSTAT_UNCALIBRATED || + state == SENSTAT_NOT_EXIST) + continue; + sample.ppo2 = value / 100.0 * parser->calibration[i] / BAR; + if (callback) callback(DC_SAMPLE_PPO2, sample, userdata); + } + } + } else if (type == LREC_STATE) { + // Tissue saturation record. + } + + offset += RECORD_SIZE; + } + + return DC_STATUS_SUCCESS; +} diff --git a/src/divesystem_idive.c b/src/divesystem_idive.c index 0410cd2..9841a6f 100644 --- a/src/divesystem_idive.c +++ b/src/divesystem_idive.c @@ -29,6 +29,7 @@ #include "platform.h" #include "checksum.h" #include "array.h" +#include "packet.h" #define ISINSTANCE(device) dc_device_isinstance((device), &divesystem_idive_device_vtable) @@ -102,6 +103,7 @@ typedef struct divesystem_idive_device_t { static dc_status_t divesystem_idive_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); static dc_status_t divesystem_idive_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); static dc_status_t divesystem_idive_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime); +static dc_status_t divesystem_idive_device_close (dc_device_t *abstract); static const dc_device_vtable_t divesystem_idive_device_vtable = { sizeof(divesystem_idive_device_t), @@ -112,7 +114,7 @@ static const dc_device_vtable_t divesystem_idive_device_vtable = { NULL, /* dump */ divesystem_idive_device_foreach, /* foreach */ divesystem_idive_device_timesync, /* timesync */ - NULL /* close */ + divesystem_idive_device_close /* close */ }; static const divesystem_idive_commands_t idive = { @@ -152,6 +154,7 @@ divesystem_idive_device_open (dc_device_t **out, dc_context_t *context, dc_iostr { dc_status_t status = DC_STATUS_SUCCESS; divesystem_idive_device_t *device = NULL; + dc_transport_t transport = dc_iostream_get_transport (iostream); if (out == NULL) return DC_STATUS_INVALIDARGS; @@ -164,22 +167,32 @@ divesystem_idive_device_open (dc_device_t **out, dc_context_t *context, dc_iostr } // Set the default values. - device->iostream = iostream; memset (device->fingerprint, 0, sizeof (device->fingerprint)); device->model = model; + // Create the packet stream. + if (transport == DC_TRANSPORT_BLE) { + status = dc_packet_open (&device->iostream, context, iostream, 244, 244); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to create the packet stream."); + goto error_free; + } + } else { + device->iostream = iostream; + } + // Set the serial communication protocol (115200 8N1). status = dc_iostream_configure (device->iostream, 115200, 8, DC_PARITY_NONE, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to set the terminal attributes."); - goto error_free; + goto error_free_iostream; } // 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; + goto error_free_iostream; } // Make sure everything is in a sane state. @@ -190,11 +203,27 @@ divesystem_idive_device_open (dc_device_t **out, dc_context_t *context, dc_iostr return DC_STATUS_SUCCESS; +error_free_iostream: + if (transport == DC_TRANSPORT_BLE) { + dc_iostream_close (device->iostream); + } error_free: dc_device_deallocate ((dc_device_t *) device); return status; } +static dc_status_t +divesystem_idive_device_close (dc_device_t *abstract) +{ + divesystem_idive_device_t *device = (divesystem_idive_device_t *) abstract; + + // Close the packet stream. + if (dc_iostream_get_transport (device->iostream) == DC_TRANSPORT_BLE) { + return dc_iostream_close (device->iostream); + } + + return DC_STATUS_SUCCESS; +} static dc_status_t divesystem_idive_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size) @@ -231,7 +260,7 @@ divesystem_idive_send (divesystem_idive_device_t *device, const unsigned char co packet[0] = START; packet[1] = csize; memcpy(packet + 2, command, csize); - crc = checksum_crc16_ccitt (packet, csize + 2, 0xffff); + crc = checksum_crc16_ccitt (packet, csize + 2, 0xffff, 0x0000); packet[csize + 2] = (crc >> 8) & 0xFF; packet[csize + 3] = (crc ) & 0xFF; @@ -292,7 +321,7 @@ divesystem_idive_receive (divesystem_idive_device_t *device, unsigned char answe // Verify the checksum. unsigned short crc = array_uint16_be (packet + len + 2); - unsigned short ccrc = checksum_crc16_ccitt (packet, len + 2, 0xffff); + unsigned short ccrc = checksum_crc16_ccitt (packet, len + 2, 0xffff, 0x0000); if (crc != ccrc) { ERROR (abstract->context, "Unexpected packet checksum."); return DC_STATUS_PROTOCOL; diff --git a/src/hdlc.c b/src/hdlc.c new file mode 100644 index 0000000..40e1ae7 --- /dev/null +++ b/src/hdlc.c @@ -0,0 +1,406 @@ +/* + * libdivecomputer + * + * Copyright (C) 2023 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 // malloc, free + +#include "hdlc.h" + +#include "iostream-private.h" +#include "common-private.h" +#include "context-private.h" + +#define END 0x7E +#define ESC 0x7D +#define ESC_BIT 0x20 + +static dc_status_t dc_hdlc_set_timeout (dc_iostream_t *abstract, int timeout); +static dc_status_t dc_hdlc_set_break (dc_iostream_t *abstract, unsigned int value); +static dc_status_t dc_hdlc_set_dtr (dc_iostream_t *abstract, unsigned int value); +static dc_status_t dc_hdlc_set_rts (dc_iostream_t *abstract, unsigned int value); +static dc_status_t dc_hdlc_get_lines (dc_iostream_t *abstract, unsigned int *value); +static dc_status_t dc_hdlc_configure (dc_iostream_t *abstract, unsigned int baudrate, unsigned int databits, dc_parity_t parity, dc_stopbits_t stopbits, dc_flowcontrol_t flowcontrol); +static dc_status_t dc_hdlc_poll (dc_iostream_t *abstract, int timeout); +static dc_status_t dc_hdlc_read (dc_iostream_t *abstract, void *data, size_t size, size_t *actual); +static dc_status_t dc_hdlc_write (dc_iostream_t *abstract, const void *data, size_t size, size_t *actual); +static dc_status_t dc_hdlc_ioctl (dc_iostream_t *abstract, unsigned int request, void *data, size_t size); +static dc_status_t dc_hdlc_flush (dc_iostream_t *abstract); +static dc_status_t dc_hdlc_purge (dc_iostream_t *abstract, dc_direction_t direction); +static dc_status_t dc_hdlc_sleep (dc_iostream_t *abstract, unsigned int milliseconds); +static dc_status_t dc_hdlc_close (dc_iostream_t *abstract); + +typedef struct dc_hdlc_t { + /* Base class. */ + dc_iostream_t base; + /* Internal state. */ + dc_context_t *context; + dc_iostream_t *iostream; + unsigned char *rbuf; + unsigned char *wbuf; + size_t rbuf_size; + size_t rbuf_offset; + size_t rbuf_available; + size_t wbuf_size; + size_t wbuf_offset; +} dc_hdlc_t; + +static const dc_iostream_vtable_t dc_hdlc_vtable = { + sizeof(dc_hdlc_t), + dc_hdlc_set_timeout, /* set_timeout */ + dc_hdlc_set_break, /* set_break */ + dc_hdlc_set_dtr, /* set_dtr */ + dc_hdlc_set_rts, /* set_rts */ + dc_hdlc_get_lines, /* get_lines */ + NULL, /* get_available */ + dc_hdlc_configure, /* configure */ + dc_hdlc_poll, /* poll */ + dc_hdlc_read, /* read */ + dc_hdlc_write, /* write */ + dc_hdlc_ioctl, /* ioctl */ + dc_hdlc_flush, /* flush */ + dc_hdlc_purge, /* purge */ + dc_hdlc_sleep, /* sleep */ + dc_hdlc_close, /* close */ +}; + +dc_status_t +dc_hdlc_open (dc_iostream_t **out, dc_context_t *context, dc_iostream_t *base, size_t isize, size_t osize) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_hdlc_t *hdlc = NULL; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + if (base == NULL || isize == 0 || osize == 0) + return DC_STATUS_INVALIDARGS; + + dc_transport_t transport = dc_iostream_get_transport (base); + + // Allocate memory. + hdlc = (dc_hdlc_t *) dc_iostream_allocate (NULL, &dc_hdlc_vtable, transport); + if (hdlc == NULL) { + ERROR (context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_exit; + } + + // Allocate the read buffer. + hdlc->rbuf = malloc (isize); + if (hdlc->rbuf == NULL) { + ERROR (context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_free; + } + + // Allocate the write buffer. + hdlc->wbuf = malloc (osize); + if (hdlc->wbuf == NULL) { + ERROR (context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_free_rbuf; + } + + hdlc->context = context; + hdlc->iostream = base; + hdlc->rbuf_size = isize; + hdlc->rbuf_offset = 0; + hdlc->rbuf_available = 0; + hdlc->wbuf_size = osize; + hdlc->wbuf_offset = 0; + + *out = (dc_iostream_t *) hdlc; + + return DC_STATUS_SUCCESS; + +error_free_rbuf: + free (hdlc->rbuf); +error_free: + dc_iostream_deallocate ((dc_iostream_t *) hdlc); +error_exit: + return status; +} + +static dc_status_t +dc_hdlc_set_timeout (dc_iostream_t *abstract, int timeout) +{ + dc_hdlc_t *hdlc = (dc_hdlc_t *) abstract; + + return dc_iostream_set_timeout (hdlc->iostream, timeout); +} + +static dc_status_t +dc_hdlc_set_break (dc_iostream_t *abstract, unsigned int value) +{ + dc_hdlc_t *hdlc = (dc_hdlc_t *) abstract; + + return dc_iostream_set_break (hdlc->iostream, value); +} + +static dc_status_t +dc_hdlc_set_dtr (dc_iostream_t *abstract, unsigned int value) +{ + dc_hdlc_t *hdlc = (dc_hdlc_t *) abstract; + + return dc_iostream_set_dtr (hdlc->iostream, value); +} + +static dc_status_t +dc_hdlc_set_rts (dc_iostream_t *abstract, unsigned int value) +{ + dc_hdlc_t *hdlc = (dc_hdlc_t *) abstract; + + return dc_iostream_set_rts (hdlc->iostream, value); +} + +static dc_status_t +dc_hdlc_get_lines (dc_iostream_t *abstract, unsigned int *value) +{ + dc_hdlc_t *hdlc = (dc_hdlc_t *) abstract; + + return dc_iostream_get_lines (hdlc->iostream, value); +} + +static dc_status_t +dc_hdlc_configure (dc_iostream_t *abstract, unsigned int baudrate, unsigned int databits, dc_parity_t parity, dc_stopbits_t stopbits, dc_flowcontrol_t flowcontrol) +{ + dc_hdlc_t *hdlc = (dc_hdlc_t *) abstract; + + return dc_iostream_configure (hdlc->iostream, baudrate, databits, parity, stopbits, flowcontrol); +} + +static dc_status_t +dc_hdlc_poll (dc_iostream_t *abstract, int timeout) +{ + dc_hdlc_t *hdlc = (dc_hdlc_t *) abstract; + + if (hdlc->rbuf_available) { + return DC_STATUS_SUCCESS; + } + + return dc_iostream_poll (hdlc->iostream, timeout); +} + +static dc_status_t +dc_hdlc_read (dc_iostream_t *abstract, void *data, size_t size, size_t *actual) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_hdlc_t *hdlc = (dc_hdlc_t *) abstract; + size_t nbytes = 0; + + unsigned int initialized = 0; + unsigned int escaped = 0; + + while (1) { + if (hdlc->rbuf_available == 0) { + // Read a packet into the cache. + size_t len = 0; + status = dc_iostream_read (hdlc->iostream, hdlc->rbuf, hdlc->rbuf_size, &len); + if (status != DC_STATUS_SUCCESS) { + goto out; + } + + hdlc->rbuf_available = len; + hdlc->rbuf_offset = 0; + } + + while (hdlc->rbuf_available) { + unsigned char c = hdlc->rbuf[hdlc->rbuf_offset]; + hdlc->rbuf_offset++; + hdlc->rbuf_available--; + + if (c == END) { + if (escaped) { + ERROR (hdlc->context, "HDLC frame escaped the special character %02x.", c); + status = DC_STATUS_IO; + goto out; + } + + if (initialized) { + goto out; + } + + initialized = 1; + continue; + } + + if (!initialized) { + continue; + } + + if (c == ESC) { + if (escaped) { + ERROR (hdlc->context, "HDLC frame escaped the special character %02x.", c); + status = DC_STATUS_IO; + goto out; + } + escaped = 1; + continue; + } + + if (escaped) { + c ^= ESC_BIT; + escaped = 0; + } + + if (nbytes < size) + ((unsigned char *)data)[nbytes] = c; + nbytes++; + } + } + +out: + if (nbytes > size) { + ERROR (hdlc->context, "HDLC frame is too large (" DC_PRINTF_SIZE " " DC_PRINTF_SIZE ").", nbytes, size); + dc_status_set_error (&status, DC_STATUS_IO); + nbytes = size; + } + + if (actual) + *actual = nbytes; + + return status; +} + +static dc_status_t +dc_hdlc_write (dc_iostream_t *abstract, const void *data, size_t size, size_t *actual) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_hdlc_t *hdlc = (dc_hdlc_t *) abstract; + size_t nbytes = 0; + + // Clear the buffer. + hdlc->wbuf_offset = 0; + + // Start of the packet. + hdlc->wbuf[hdlc->wbuf_offset++] = END; + + // Flush the buffer if necessary. + if (hdlc->wbuf_offset >= hdlc->wbuf_size) { + status = dc_iostream_write (hdlc->iostream, hdlc->wbuf, hdlc->wbuf_offset, NULL); + if (status != DC_STATUS_SUCCESS) { + goto out; + } + + hdlc->wbuf_offset = 0; + } + + while (nbytes < size) { + unsigned char c = ((const unsigned char *) data)[nbytes]; + + if (c == END || c == ESC) { + // Append the escape character. + hdlc->wbuf[hdlc->wbuf_offset++] = ESC; + + // Flush the buffer if necessary. + if (hdlc->wbuf_offset >= hdlc->wbuf_size) { + status = dc_iostream_write (hdlc->iostream, hdlc->wbuf, hdlc->wbuf_offset, NULL); + if (status != DC_STATUS_SUCCESS) { + goto out; + } + + hdlc->wbuf_offset = 0; + } + + // Escape the character. + c ^= ESC_BIT; + } + + // Append the character. + hdlc->wbuf[hdlc->wbuf_offset++] = c; + + // Flush the buffer if necessary. + if (hdlc->wbuf_offset >= hdlc->wbuf_size) { + status = dc_iostream_write (hdlc->iostream, hdlc->wbuf, hdlc->wbuf_offset, NULL); + if (status != DC_STATUS_SUCCESS) { + goto out; + } + + hdlc->wbuf_offset = 0; + } + + nbytes++; + } + + // End of the packet. + hdlc->wbuf[hdlc->wbuf_offset++] = END; + + // Flush the buffer. + status = dc_iostream_write (hdlc->iostream, hdlc->wbuf, hdlc->wbuf_offset, NULL); + if (status != DC_STATUS_SUCCESS) { + goto out; + } + + hdlc->wbuf_offset = 0; + +out: + if (actual) + *actual = nbytes; + + return status; +} + +static dc_status_t +dc_hdlc_ioctl (dc_iostream_t *abstract, unsigned int request, void *data, size_t size) +{ + dc_hdlc_t *hdlc = (dc_hdlc_t *) abstract; + + return dc_iostream_ioctl (hdlc->iostream, request, data, size); +} + +static dc_status_t +dc_hdlc_flush (dc_iostream_t *abstract) +{ + dc_hdlc_t *hdlc = (dc_hdlc_t *) abstract; + + return dc_iostream_flush (hdlc->iostream); +} + +static dc_status_t +dc_hdlc_purge (dc_iostream_t *abstract, dc_direction_t direction) +{ + dc_hdlc_t *hdlc = (dc_hdlc_t *) abstract; + + if (direction & DC_DIRECTION_INPUT) { + hdlc->rbuf_available = 0; + hdlc->rbuf_offset = 0; + } + + return dc_iostream_purge (hdlc->iostream, direction); +} + +static dc_status_t +dc_hdlc_sleep (dc_iostream_t *abstract, unsigned int milliseconds) +{ + dc_hdlc_t *hdlc = (dc_hdlc_t *) abstract; + + return dc_iostream_sleep (hdlc->iostream, milliseconds); +} + +static dc_status_t +dc_hdlc_close (dc_iostream_t *abstract) +{ + dc_hdlc_t *hdlc = (dc_hdlc_t *) abstract; + + free (hdlc->wbuf); + free (hdlc->rbuf); + + return DC_STATUS_SUCCESS; +} diff --git a/src/hdlc.h b/src/hdlc.h new file mode 100644 index 0000000..aedd9b5 --- /dev/null +++ b/src/hdlc.h @@ -0,0 +1,50 @@ +/* + * libdivecomputer + * + * Copyright (C) 2023 Jef Driesen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#ifndef DC_HDLC_H +#define DC_HDLC_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * Create a HDLC I/O stream layered on top of another base I/O stream. + * + * @param[out] iostream A location to store the HDLC I/O stream. + * @param[in] context A valid context. + * @param[in] base A valid I/O stream. + * @param[in] isize The input packet size in bytes. + * @param[in] osize The output packet size in bytes. + * @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code + * on failure. + */ +dc_status_t +dc_hdlc_open (dc_iostream_t **iostream, dc_context_t *context, dc_iostream_t *base, size_t isize, size_t osize); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* DC_HDLC_H */ diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index cfa6989..8f17906 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -30,6 +30,7 @@ #include "array.h" #include "aes.h" #include "platform.h" +#include "packet.h" #define ISINSTANCE(device) dc_device_isinstance((device), &hw_ostc3_device_vtable) @@ -119,9 +120,6 @@ typedef struct hw_ostc3_device_t { unsigned int firmware; unsigned char fingerprint[5]; hw_ostc3_state_t state; - unsigned char cache[244]; - unsigned int available; - unsigned int offset; } hw_ostc3_device_t; typedef struct hw_ostc3_logbook_t { @@ -207,41 +205,20 @@ static dc_status_t hw_ostc3_read (hw_ostc3_device_t *device, dc_event_progress_t *progress, unsigned char data[], size_t size) { dc_status_t rc = DC_STATUS_SUCCESS; - dc_transport_t transport = dc_iostream_get_transport(device->iostream); size_t nbytes = 0; while (nbytes < size) { - if (transport == DC_TRANSPORT_BLE) { - if (device->available == 0) { - // Read a packet into the cache. - size_t len = 0; - rc = dc_iostream_read (device->iostream, device->cache, sizeof(device->cache), &len); - if (rc != DC_STATUS_SUCCESS) - return rc; - - device->available = len; - device->offset = 0; - } - } - // Set the minimum packet size. - size_t length = (transport == DC_TRANSPORT_BLE) ? device->available : 1024; + size_t length = 1024; // Limit the packet size to the total size. if (nbytes + length > size) length = size - nbytes; - if (transport == DC_TRANSPORT_BLE) { - // Copy the data from the cached packet. - memcpy (data + nbytes, device->cache + device->offset, length); - device->available -= length; - device->offset += length; - } else { - // Read the packet. - rc = dc_iostream_read (device->iostream, data + nbytes, length, NULL); - if (rc != DC_STATUS_SUCCESS) - return rc; - } + // Read the packet. + rc = dc_iostream_read (device->iostream, data + nbytes, length, NULL); + if (rc != DC_STATUS_SUCCESS) + return rc; // Update and emit a progress event. if (progress) { @@ -259,12 +236,11 @@ static dc_status_t hw_ostc3_write (hw_ostc3_device_t *device, dc_event_progress_t *progress, const unsigned char data[], size_t size) { dc_status_t rc = DC_STATUS_SUCCESS; - dc_transport_t transport = dc_iostream_get_transport(device->iostream); size_t nbytes = 0; while (nbytes < size) { // Set the maximum packet size. - size_t length = (transport == DC_TRANSPORT_BLE) ? sizeof(device->cache) : 64; + size_t length = 1024; // Limit the packet size to the total size. if (nbytes + length > size) @@ -313,7 +289,7 @@ hw_ostc3_transfer (hw_ostc3_device_t *device, // Send the command. unsigned char command[1] = {cmd}; - status = hw_ostc3_write (device, NULL, command, sizeof (command)); + status = dc_iostream_write (device->iostream, command, sizeof (command), NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the command."); return status; @@ -321,7 +297,7 @@ hw_ostc3_transfer (hw_ostc3_device_t *device, // Read the echo. unsigned char echo[1] = {0}; - status = hw_ostc3_read (device, NULL, echo, sizeof (echo)); + status = dc_iostream_read (device->iostream, echo, sizeof (echo), NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to receive the echo."); return status; @@ -407,14 +383,14 @@ hw_ostc3_transfer (hw_ostc3_device_t *device, } } - if (delay && device->available == 0) { + if (delay) { dc_iostream_poll (device->iostream, delay); } if (cmd != EXIT) { // Read the ready byte. unsigned char answer[1] = {0}; - status = hw_ostc3_read (device, NULL, answer, sizeof (answer)); + status = dc_iostream_read (device->iostream, answer, sizeof (answer), NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to receive the ready byte."); return status; @@ -439,6 +415,7 @@ hw_ostc3_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *i { dc_status_t status = DC_STATUS_SUCCESS; hw_ostc3_device_t *device = NULL; + dc_transport_t transport = dc_iostream_get_transport (iostream); if (out == NULL) return DC_STATUS_INVALIDARGS; @@ -451,29 +428,36 @@ hw_ostc3_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *i } // Set the default values. - device->iostream = iostream; device->hardware = INVALID; device->feature = 0; device->model = 0; device->serial = 0; device->firmware = 0; memset (device->fingerprint, 0, sizeof (device->fingerprint)); - memset (device->cache, 0, sizeof (device->cache)); - device->available = 0; - device->offset = 0; + + // Create the packet stream. + if (transport == DC_TRANSPORT_BLE) { + status = dc_packet_open (&device->iostream, context, iostream, 244, 20); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to create the packet stream."); + goto error_free; + } + } else { + device->iostream = iostream; + } // Set the serial communication protocol (115200 8N1). status = dc_iostream_configure (device->iostream, 115200, 8, DC_PARITY_NONE, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to set the terminal attributes."); - goto error_free; + goto error_free_iostream; } // 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; + goto error_free_iostream; } // Make sure everything is in a sane state. @@ -486,6 +470,10 @@ hw_ostc3_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *i return DC_STATUS_SUCCESS; +error_free_iostream: + if (transport == DC_TRANSPORT_BLE) { + dc_iostream_close (device->iostream); + } error_free: dc_device_deallocate ((dc_device_t *) device); return status; @@ -548,14 +536,14 @@ hw_ostc3_device_init_service (hw_ostc3_device_t *device) unsigned char answer[5] = {0}; // Send the command and service key. - status = hw_ostc3_write (device, NULL, command, sizeof (command)); + status = dc_iostream_write (device->iostream, command, sizeof (command), NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the command."); return status; } // Read the response. - status = hw_ostc3_read (device, NULL, answer, sizeof (answer)); + status = dc_iostream_read (device->iostream, answer, sizeof (answer), NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to receive the answer."); return status; @@ -656,6 +644,15 @@ hw_ostc3_device_close (dc_device_t *abstract) } } + // Close the packet stream. + if (dc_iostream_get_transport (device->iostream) == DC_TRANSPORT_BLE) { + rc = dc_iostream_close (device->iostream); + if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to close the packet stream."); + dc_status_set_error(&status, rc); + } + } + return status; } diff --git a/src/libdivecomputer.rc b/src/libdivecomputer.rc new file mode 100644 index 0000000..1441085 --- /dev/null +++ b/src/libdivecomputer.rc @@ -0,0 +1,68 @@ +/* + * libdivecomputer + * + * Copyright (C) 2010 Jef Driesen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include + +#ifdef HAVE_VERSION_SUFFIX +#include "revision.h" +#endif + +VS_VERSION_INFO VERSIONINFO +FILEVERSION DC_VERSION_MAJOR,DC_VERSION_MINOR,DC_VERSION_MICRO,0 +PRODUCTVERSION DC_VERSION_MAJOR,DC_VERSION_MINOR,DC_VERSION_MICRO,0 +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef HAVE_VERSION_SUFFIX +FILEFLAGS VS_FF_PRERELEASE +#else +FILEFLAGS 0 +#endif +FILEOS VOS__WINDOWS32 +FILETYPE VFT_DLL +FILESUBTYPE 0 +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "The libdivecomputer developers" + VALUE "FileDescription", "A library for communication with various dive computers." + VALUE "FileVersion", DC_VERSION + VALUE "InternalName", "libdivecomputer" + VALUE "LegalCopyright", "Copyright © 2010 Jef Driesen" + VALUE "OriginalFilename", "libdivecomputer.dll" + VALUE "ProductName", "libdivecomputer" + VALUE "ProductVersion", DC_VERSION +#ifdef HAVE_VERSION_SUFFIX + VALUE "Comments", DC_VERSION_REVISION +#endif + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 1033, 1200 + END +END diff --git a/src/libdivecomputer.rc.in b/src/libdivecomputer.rc.in deleted file mode 100644 index f98d2ed..0000000 --- a/src/libdivecomputer.rc.in +++ /dev/null @@ -1,45 +0,0 @@ -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include - -#ifdef HAVE_VERSION_SUFFIX -#include "revision.h" -#endif - -VS_VERSION_INFO VERSIONINFO -FILEVERSION @DC_VERSION_MAJOR@,@DC_VERSION_MINOR@,@DC_VERSION_MICRO@,0 -PRODUCTVERSION @DC_VERSION_MAJOR@,@DC_VERSION_MINOR@,@DC_VERSION_MICRO@,0 -FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -#ifdef HAVE_VERSION_SUFFIX -FILEFLAGS VS_FF_PRERELEASE -#else -FILEFLAGS 0 -#endif -FILEOS VOS__WINDOWS32 -FILETYPE VFT_DLL -FILESUBTYPE 0 -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904B0" - BEGIN - VALUE "CompanyName", "The libdivecomputer developers" - VALUE "FileDescription", "A library for communication with various dive computers." - VALUE "FileVersion", "@VERSION@" - VALUE "InternalName", "libdivecomputer" - VALUE "LegalCopyright", "Copyright © 2010 Jef Driesen" - VALUE "OriginalFilename", "libdivecomputer.dll" - VALUE "ProductName", "libdivecomputer" - VALUE "ProductVersion", "@VERSION@" -#ifdef HAVE_VERSION_SUFFIX - VALUE "Comments", DC_VERSION_REVISION -#endif - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 1033, 1200 - END -END diff --git a/src/liquivision_lynx.c b/src/liquivision_lynx.c index a0d8e0a..b755955 100644 --- a/src/liquivision_lynx.c +++ b/src/liquivision_lynx.c @@ -158,7 +158,7 @@ liquivision_lynx_recv (liquivision_lynx_device_t *device, unsigned char data[], // Verify the checksum. unsigned short crc = array_uint16_be (packet + 1 + size); - unsigned short ccrc = checksum_crc16_ccitt (packet + 1, size, 0xffff); + unsigned short ccrc = checksum_crc16_ccitt (packet + 1, size, 0xffff, 0x0000); if (crc != ccrc) { ERROR (abstract->context, "Unexpected answer checksum (%04x %04x).", crc, ccrc); return DC_STATUS_PROTOCOL; @@ -528,7 +528,7 @@ liquivision_lynx_device_foreach (dc_device_t *abstract, dc_dive_callback_t callb memcpy (header + 0, device->info + 2, 4); memcpy (header + 4, logbook + offset + 4, headersize - 4); unsigned int crc = array_uint32_le (logbook + offset + 0); - unsigned int ccrc = checksum_crc32b (header, headersize - unused); + unsigned int ccrc = checksum_crc32 (header, headersize - unused); if (crc != ccrc) { WARNING (abstract->context, "Invalid dive checksum (%08x %08x)", crc, ccrc); status = DC_STATUS_DATAFORMAT; diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index 3378063..eb4f337 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -29,6 +29,7 @@ #include "array.h" #include "rbstream.h" #include "platform.h" +#include "packet.h" #define ISINSTANCE(device) dc_device_isinstance((device), &mares_iconhd_device_vtable) @@ -93,9 +94,6 @@ typedef struct mares_iconhd_device_t { unsigned char version[140]; unsigned int model; unsigned int packetsize; - unsigned char cache[20]; - unsigned int available; - unsigned int offset; unsigned int splitcommand; } mares_iconhd_device_t; @@ -103,6 +101,7 @@ static dc_status_t mares_iconhd_device_set_fingerprint (dc_device_t *abstract, c static dc_status_t mares_iconhd_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size); static dc_status_t mares_iconhd_device_dump (dc_device_t *abstract, dc_buffer_t *buffer); static dc_status_t mares_iconhd_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); +static dc_status_t mares_iconhd_device_close (dc_device_t *abstract); static const dc_device_vtable_t mares_iconhd_device_vtable = { sizeof(mares_iconhd_device_t), @@ -113,7 +112,7 @@ static const dc_device_vtable_t mares_iconhd_device_vtable = { mares_iconhd_device_dump, /* dump */ mares_iconhd_device_foreach, /* foreach */ NULL, /* timesync */ - NULL /* close */ + mares_iconhd_device_close /* close */ }; static const mares_iconhd_layout_t mares_iconhd_layout = { @@ -178,78 +177,6 @@ mares_iconhd_get_model (mares_iconhd_device_t *device) return model; } -static dc_status_t -mares_iconhd_read (mares_iconhd_device_t *device, unsigned char data[], size_t size) -{ - dc_status_t rc = DC_STATUS_SUCCESS; - dc_transport_t transport = dc_iostream_get_transport(device->iostream); - - size_t nbytes = 0; - while (nbytes < size) { - if (transport == DC_TRANSPORT_BLE) { - if (device->available == 0) { - // Read a packet into the cache. - size_t len = 0; - rc = dc_iostream_read (device->iostream, device->cache, sizeof(device->cache), &len); - if (rc != DC_STATUS_SUCCESS) - return rc; - - device->available = len; - device->offset = 0; - } - } - - // Set the minimum packet size. - size_t length = (transport == DC_TRANSPORT_BLE) ? device->available : size - nbytes; - - // Limit the packet size to the total size. - if (nbytes + length > size) - length = size - nbytes; - - if (transport == DC_TRANSPORT_BLE) { - // Copy the data from the cached packet. - memcpy (data + nbytes, device->cache + device->offset, length); - device->available -= length; - device->offset += length; - } else { - // Read the packet. - rc = dc_iostream_read (device->iostream, data + nbytes, length, &length); - if (rc != DC_STATUS_SUCCESS) - return rc; - } - - nbytes += length; - } - - return rc; -} - -static dc_status_t -mares_iconhd_write (mares_iconhd_device_t *device, const unsigned char data[], size_t size) -{ - dc_status_t rc = DC_STATUS_SUCCESS; - dc_transport_t transport = dc_iostream_get_transport(device->iostream); - - size_t nbytes = 0; - while (nbytes < size) { - // Set the maximum packet size. - size_t length = (transport == DC_TRANSPORT_BLE) ? sizeof(device->cache) : size - nbytes; - - // Limit the packet size to the total size. - if (nbytes + length > size) - length = size - nbytes; - - // Write the packet. - rc = dc_iostream_write (device->iostream, data + nbytes, length, &length); - if (rc != DC_STATUS_SUCCESS) - return rc; - - nbytes += length; - } - - return rc; -} - static dc_status_t mares_iconhd_packet (mares_iconhd_device_t *device, const unsigned char command[], unsigned int csize, @@ -267,7 +194,7 @@ mares_iconhd_packet (mares_iconhd_device_t *device, split_csize = device->splitcommand ? 2 : csize; // Send the command header to the dive computer. - status = mares_iconhd_write (device, command, split_csize); + status = dc_iostream_write (device->iostream, command, split_csize, NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the command."); return status; @@ -275,7 +202,7 @@ mares_iconhd_packet (mares_iconhd_device_t *device, // Receive the header byte. unsigned char header[1] = {0}; - status = mares_iconhd_read (device, header, sizeof (header)); + status = dc_iostream_read (device->iostream, header, sizeof (header), NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to receive the answer."); return status; @@ -289,7 +216,7 @@ mares_iconhd_packet (mares_iconhd_device_t *device, // Send any remaining command payload to the dive computer. if (csize > split_csize) { - status = mares_iconhd_write (device, command + split_csize, csize - split_csize); + status = dc_iostream_write (device->iostream, command + split_csize, csize - split_csize, NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the command."); return status; @@ -297,7 +224,7 @@ mares_iconhd_packet (mares_iconhd_device_t *device, } // Read the packet. - status = mares_iconhd_read (device, answer, asize); + status = dc_iostream_read (device->iostream, answer, asize, NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to receive the answer."); return status; @@ -305,7 +232,7 @@ mares_iconhd_packet (mares_iconhd_device_t *device, // Receive the trailer byte. unsigned char trailer[1] = {0}; - status = mares_iconhd_read (device, trailer, sizeof (trailer)); + status = dc_iostream_read (device->iostream, trailer, sizeof (trailer), NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to receive the answer."); return status; @@ -338,8 +265,6 @@ mares_iconhd_transfer (mares_iconhd_device_t *device, const unsigned char comman // Discard any garbage bytes. dc_iostream_sleep (device->iostream, 100); dc_iostream_purge (device->iostream, DC_DIRECTION_INPUT); - device->available = 0; - device->offset = 0; } return DC_STATUS_SUCCESS; @@ -463,6 +388,7 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_ { dc_status_t status = DC_STATUS_SUCCESS; mares_iconhd_device_t *device = NULL; + dc_transport_t transport = dc_iostream_get_transport (iostream); if (out == NULL) return DC_STATUS_INVALIDARGS; @@ -475,16 +401,23 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_ } // Set the default values. - device->iostream = iostream; device->layout = NULL; memset (device->fingerprint, 0, sizeof (device->fingerprint)); device->fingerprint_size = sizeof (device->fingerprint); memset (device->version, 0, sizeof (device->version)); device->model = 0; device->packetsize = 0; - memset (device->cache, 0, sizeof (device->cache)); - device->available = 0; - device->offset = 0; + + // Create the packet stream. + if (transport == DC_TRANSPORT_BLE) { + status = dc_packet_open (&device->iostream, context, iostream, 20, 20); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to create the packet stream."); + goto error_free; + } + } else { + device->iostream = iostream; + } /* * At least the Mares Matrix needs the command to be split into @@ -499,28 +432,28 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_ status = dc_iostream_configure (device->iostream, 115200, 8, DC_PARITY_EVEN, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to set the terminal attributes."); - goto error_free; + goto error_free_iostream; } // Set the timeout for receiving data (3000 ms). status = dc_iostream_set_timeout (device->iostream, 3000); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to set the timeout."); - goto error_free; + goto error_free_iostream; } // Clear the DTR line. status = dc_iostream_set_dtr (device->iostream, 0); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to clear the DTR line."); - goto error_free; + goto error_free_iostream; } // Clear the RTS line. status = dc_iostream_set_rts (device->iostream, 0); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to clear the RTS line."); - goto error_free; + goto error_free_iostream; } // Make sure everything is in a sane state. @@ -531,7 +464,7 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_ status = mares_iconhd_transfer (device, command, sizeof (command), device->version, sizeof (device->version)); if (status != DC_STATUS_SUCCESS) { - goto error_free; + goto error_free_iostream; } // Autodetect the model using the version packet. @@ -619,12 +552,31 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_ return DC_STATUS_SUCCESS; + +error_free_iostream: + if (transport == DC_TRANSPORT_BLE) { + dc_iostream_close (device->iostream); + } error_free: dc_device_deallocate ((dc_device_t *) device); return status; } +static dc_status_t +mares_iconhd_device_close (dc_device_t *abstract) +{ + mares_iconhd_device_t *device = (mares_iconhd_device_t *) abstract; + + // Close the packet stream. + if (dc_iostream_get_transport (device->iostream) == DC_TRANSPORT_BLE) { + return dc_iostream_close (device->iostream); + } + + return DC_STATUS_SUCCESS; +} + + static dc_status_t mares_iconhd_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size) { diff --git a/src/mares_iconhd_parser.c b/src/mares_iconhd_parser.c index ed2e4ae..50cf6f7 100644 --- a/src/mares_iconhd_parser.c +++ b/src/mares_iconhd_parser.c @@ -299,7 +299,7 @@ mares_genius_isvalid (const unsigned char data[], size_t size, unsigned int type } unsigned short crc = array_uint16_le(data + size - 6); - unsigned short ccrc = checksum_crc16_ccitt(data + 4, size - 10, 0x0000); + unsigned short ccrc = checksum_crc16_ccitt(data + 4, size - 10, 0x0000, 0x0000); if (crc != ccrc) { return 0; } diff --git a/src/mclean_extreme.c b/src/mclean_extreme.c index 98038ff..2ef1a87 100644 --- a/src/mclean_extreme.c +++ b/src/mclean_extreme.c @@ -26,6 +26,7 @@ #include "context-private.h" #include "device-private.h" #include "array.h" +#include "packet.h" #define ISINSTANCE(device) dc_device_isinstance((device), &mclean_extreme_device_vtable) @@ -391,6 +392,7 @@ mclean_extreme_device_open(dc_device_t **out, dc_context_t *context, dc_iostream { dc_status_t status = DC_STATUS_SUCCESS; mclean_extreme_device_t *device = NULL; + dc_transport_t transport = dc_iostream_get_transport (iostream); if (out == NULL) return DC_STATUS_INVALIDARGS; @@ -403,21 +405,31 @@ mclean_extreme_device_open(dc_device_t **out, dc_context_t *context, dc_iostream } // Set the default values. - device->iostream = iostream; memset(device->fingerprint, 0, sizeof(device->fingerprint)); + // Create the packet stream. + if (transport == DC_TRANSPORT_BLE) { + status = dc_packet_open (&device->iostream, context, iostream, 244, 244); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to create the packet stream."); + goto error_free; + } + } else { + device->iostream = iostream; + } + // Set the serial communication protocol (115200 8N1). status = dc_iostream_configure(device->iostream, 115200, 8, DC_PARITY_NONE, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE); if (status != DC_STATUS_SUCCESS) { ERROR(context, "Failed to set the terminal attributes."); - goto error_free; + goto error_free_iostream; } // 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; + goto error_free_iostream; } // Make sure everything is in a sane state. @@ -428,6 +440,10 @@ mclean_extreme_device_open(dc_device_t **out, dc_context_t *context, dc_iostream return DC_STATUS_SUCCESS; +error_free_iostream: + if (transport == DC_TRANSPORT_BLE) { + dc_iostream_close (device->iostream); + } error_free: dc_device_deallocate((dc_device_t *)device); return status; @@ -438,14 +454,24 @@ mclean_extreme_device_close(dc_device_t *abstract) { dc_status_t status = DC_STATUS_SUCCESS; mclean_extreme_device_t *device = (mclean_extreme_device_t *)abstract; + dc_status_t rc = DC_STATUS_SUCCESS; - status = mclean_extreme_send(device, CMD_CLOSE, NULL, 0); - if (status != DC_STATUS_SUCCESS) { + rc = mclean_extreme_send(device, CMD_CLOSE, NULL, 0); + if (rc != DC_STATUS_SUCCESS) { ERROR(abstract->context, "Failed to send the exit command."); - return status; + dc_status_set_error(&status, rc); } - return DC_STATUS_SUCCESS; + // Close the packet stream. + if (dc_iostream_get_transport (device->iostream) == DC_TRANSPORT_BLE) { + rc = dc_iostream_close (device->iostream); + if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to close the packet stream."); + dc_status_set_error(&status, rc); + } + } + + return status; } static dc_status_t diff --git a/src/oceans_s1.c b/src/oceans_s1.c index e26979e..125d090 100644 --- a/src/oceans_s1.c +++ b/src/oceans_s1.c @@ -302,7 +302,7 @@ oceans_s1_xmodem_packet (oceans_s1_device_t *device, unsigned char seq, unsigned } unsigned short crc = array_uint16_be (packet + nbytes - 2); - unsigned short ccrc = checksum_crc16_ccitt (packet + 3, nbytes - 5, 0x0000); + unsigned short ccrc = checksum_crc16_ccitt (packet + 3, nbytes - 5, 0x0000, 0x0000); if (crc != ccrc) { ERROR (device->base.context, "Unexpected answer checksum (%04x %04x).", crc, ccrc); return DC_STATUS_PROTOCOL; diff --git a/src/packet.c b/src/packet.c new file mode 100644 index 0000000..cc8304f --- /dev/null +++ b/src/packet.c @@ -0,0 +1,321 @@ +/* + * libdivecomputer + * + * Copyright (C) 2023 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 // malloc, free +#include + +#include "packet.h" + +#include "iostream-private.h" +#include "common-private.h" +#include "context-private.h" + +static dc_status_t dc_packet_set_timeout (dc_iostream_t *abstract, int timeout); +static dc_status_t dc_packet_set_break (dc_iostream_t *abstract, unsigned int value); +static dc_status_t dc_packet_set_dtr (dc_iostream_t *abstract, unsigned int value); +static dc_status_t dc_packet_set_rts (dc_iostream_t *abstract, unsigned int value); +static dc_status_t dc_packet_get_lines (dc_iostream_t *abstract, unsigned int *value); +static dc_status_t dc_packet_get_available (dc_iostream_t *abstract, size_t *value); +static dc_status_t dc_packet_configure (dc_iostream_t *abstract, unsigned int baudrate, unsigned int databits, dc_parity_t parity, dc_stopbits_t stopbits, dc_flowcontrol_t flowcontrol); +static dc_status_t dc_packet_poll (dc_iostream_t *abstract, int timeout); +static dc_status_t dc_packet_read (dc_iostream_t *abstract, void *data, size_t size, size_t *actual); +static dc_status_t dc_packet_write (dc_iostream_t *abstract, const void *data, size_t size, size_t *actual); +static dc_status_t dc_packet_ioctl (dc_iostream_t *abstract, unsigned int request, void *data, size_t size); +static dc_status_t dc_packet_flush (dc_iostream_t *abstract); +static dc_status_t dc_packet_purge (dc_iostream_t *abstract, dc_direction_t direction); +static dc_status_t dc_packet_sleep (dc_iostream_t *abstract, unsigned int milliseconds); +static dc_status_t dc_packet_close (dc_iostream_t *abstract); + +typedef struct dc_packet_t { + /* Base class. */ + dc_iostream_t base; + /* Internal state. */ + dc_iostream_t *iostream; + unsigned char *cache; + size_t available; + size_t offset; + size_t isize; + size_t osize; +} dc_packet_t; + +static const dc_iostream_vtable_t dc_packet_vtable = { + sizeof(dc_packet_t), + dc_packet_set_timeout, /* set_timeout */ + dc_packet_set_break, /* set_break */ + dc_packet_set_dtr, /* set_dtr */ + dc_packet_set_rts, /* set_rts */ + dc_packet_get_lines, /* get_lines */ + dc_packet_get_available, /* get_available */ + dc_packet_configure, /* configure */ + dc_packet_poll, /* poll */ + dc_packet_read, /* read */ + dc_packet_write, /* write */ + dc_packet_ioctl, /* ioctl */ + dc_packet_flush, /* flush */ + dc_packet_purge, /* purge */ + dc_packet_sleep, /* sleep */ + dc_packet_close, /* close */ +}; + +dc_status_t +dc_packet_open (dc_iostream_t **out, dc_context_t *context, dc_iostream_t *base, size_t isize, size_t osize) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_packet_t *packet = NULL; + unsigned char *buffer = NULL; + + if (out == NULL || base == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + packet = (dc_packet_t *) dc_iostream_allocate (NULL, &dc_packet_vtable, dc_iostream_get_transport(base)); + if (packet == NULL) { + ERROR (context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_exit; + } + + // Allocate the read buffer. + if (isize) { + buffer = (unsigned char *) malloc (isize); + if (buffer == NULL) { + ERROR (context, "Failed to allocate memory."); + status = DC_STATUS_NOMEMORY; + goto error_free; + } + } + + packet->iostream = base; + packet->cache = buffer; + packet->available = 0; + packet->offset = 0; + packet->isize = isize; + packet->osize = osize; + + *out = (dc_iostream_t *) packet; + + return DC_STATUS_SUCCESS; + +error_free: + dc_iostream_deallocate ((dc_iostream_t *) packet); +error_exit: + return status; +} + +static dc_status_t +dc_packet_set_timeout (dc_iostream_t *abstract, int timeout) +{ + dc_packet_t *packet = (dc_packet_t *) abstract; + + return dc_iostream_set_timeout (packet->iostream, timeout); +} + +static dc_status_t +dc_packet_set_break (dc_iostream_t *abstract, unsigned int value) +{ + dc_packet_t *packet = (dc_packet_t *) abstract; + + return dc_iostream_set_break (packet->iostream, value); +} + +static dc_status_t +dc_packet_set_dtr (dc_iostream_t *abstract, unsigned int value) +{ + dc_packet_t *packet = (dc_packet_t *) abstract; + + return dc_iostream_set_dtr (packet->iostream, value); +} + +static dc_status_t +dc_packet_set_rts (dc_iostream_t *abstract, unsigned int value) +{ + dc_packet_t *packet = (dc_packet_t *) abstract; + + return dc_iostream_set_rts (packet->iostream, value); +} + +static dc_status_t +dc_packet_get_lines (dc_iostream_t *abstract, unsigned int *value) +{ + dc_packet_t *packet = (dc_packet_t *) abstract; + + return dc_iostream_get_lines (packet->iostream, value); +} + +static dc_status_t +dc_packet_get_available (dc_iostream_t *abstract, size_t *value) +{ + dc_packet_t *packet = (dc_packet_t *) abstract; + + if (packet->isize && packet->available) { + if (value) + *value = packet->available; + return DC_STATUS_SUCCESS; + } + + return dc_iostream_get_available (packet->iostream, value); +} + +static dc_status_t +dc_packet_configure (dc_iostream_t *abstract, unsigned int baudrate, unsigned int databits, dc_parity_t parity, dc_stopbits_t stopbits, dc_flowcontrol_t flowcontrol) +{ + dc_packet_t *packet = (dc_packet_t *) abstract; + + return dc_iostream_configure (packet->iostream, baudrate, databits, parity, stopbits, flowcontrol); +} + +static dc_status_t +dc_packet_poll (dc_iostream_t *abstract, int timeout) +{ + dc_packet_t *packet = (dc_packet_t *) abstract; + + if (packet->isize && packet->available) + return DC_STATUS_SUCCESS; + + return dc_iostream_poll (packet->iostream, timeout); +} + +static dc_status_t +dc_packet_read (dc_iostream_t *abstract, void *data, size_t size, size_t *actual) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_packet_t *packet = (dc_packet_t *) abstract; + size_t nbytes = 0; + + while (nbytes < size) { + // Get the remaining size. + size_t length = size - nbytes; + + if (packet->isize) { + if (packet->available == 0) { + // Read a packet into the cache. + size_t len = 0; + status = dc_iostream_read (packet->iostream, packet->cache, packet->isize, &len); + if (status != DC_STATUS_SUCCESS) + break; + + packet->available = len; + packet->offset = 0; + } + + // Limit to the maximum packet size. + if (length > packet->available) + length = packet->available; + + // Copy the data from the cached packet. + memcpy ((unsigned char *) data + nbytes, packet->cache + packet->offset, length); + packet->available -= length; + packet->offset += length; + } else { + // Read the packet. + status = dc_iostream_read (packet->iostream, (unsigned char *) data + nbytes, length, &length); + if (status != DC_STATUS_SUCCESS) + break; + } + + // Update the total number of bytes. + nbytes += length; + } + + if (actual) + *actual = nbytes; + + return status; +} + +static dc_status_t +dc_packet_write (dc_iostream_t *abstract, const void *data, size_t size, size_t *actual) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_packet_t *packet = (dc_packet_t *) abstract; + size_t nbytes = 0; + + while (nbytes < size) { + // Get the remaining size. + size_t length = size - nbytes; + + // Limit to the maximum packet size. + if (packet->osize) { + if (length > packet->osize) + length = packet->osize; + } + + // Write the packet. + status = dc_iostream_write (packet->iostream, (const unsigned char *) data + nbytes, length, &length); + if (status != DC_STATUS_SUCCESS) + break; + + // Update the total number of bytes. + nbytes += length; + } + + if (actual) + *actual = nbytes; + + return status; +} + +static dc_status_t +dc_packet_ioctl (dc_iostream_t *abstract, unsigned int request, void *data, size_t size) +{ + dc_packet_t *packet = (dc_packet_t *) abstract; + + return dc_iostream_ioctl (packet->iostream, request, data, size); +} + +static dc_status_t +dc_packet_flush (dc_iostream_t *abstract) +{ + dc_packet_t *packet = (dc_packet_t *) abstract; + + return dc_iostream_flush (packet->iostream); +} + +static dc_status_t +dc_packet_purge (dc_iostream_t *abstract, dc_direction_t direction) +{ + dc_packet_t *packet = (dc_packet_t *) abstract; + + if (direction & DC_DIRECTION_INPUT) { + packet->available = 0; + packet->offset = 0; + } + + return dc_iostream_purge (packet->iostream, direction); +} + +static dc_status_t +dc_packet_sleep (dc_iostream_t *abstract, unsigned int milliseconds) +{ + dc_packet_t *packet = (dc_packet_t *) abstract; + + return dc_iostream_sleep (packet->iostream, milliseconds); +} + +static dc_status_t +dc_packet_close (dc_iostream_t *abstract) +{ + dc_packet_t *packet = (dc_packet_t *) abstract; + + free (packet->cache); + + return DC_STATUS_SUCCESS; +} diff --git a/src/packet.h b/src/packet.h new file mode 100644 index 0000000..b420f08 --- /dev/null +++ b/src/packet.h @@ -0,0 +1,54 @@ +/* + * libdivecomputer + * + * Copyright (C) 2023 Jef Driesen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#ifndef DC_PACKET_H +#define DC_PACKET_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * Create a packet I/O stream layered on top of another base I/O stream. + * + * This layered I/O allows reading and writing a byte stream from the + * underlying packet oriented transport. It changes the packet oriented + * base transport into a stream oriented transport. + * + * @param[out] iostream A location to store the packet I/O stream. + * @param[in] context A valid context. + * @param[in] base A valid I/O stream. + * @param[in] isize The input packet size in bytes. + * @param[in] osize The output packet size in bytes. + * @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code + * on failure. + */ +dc_status_t +dc_packet_open (dc_iostream_t **iostream, dc_context_t *context, dc_iostream_t *base, size_t isize, size_t osize); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* DC_PACKET_H */ diff --git a/src/parser.c b/src/parser.c index 68ed4f3..b253070 100644 --- a/src/parser.c +++ b/src/parser.c @@ -63,6 +63,7 @@ #include "seac_screen.h" #include "deepblu_cosmiq.h" #include "oceans_s1.h" +#include "divesoft_freedom.h" // Not merged upstream yet #include "garmin.h" @@ -202,6 +203,9 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa case DC_FAMILY_OCEANS_S1: rc = oceans_s1_parser_create (&parser, context); break; + case DC_FAMILY_DIVESOFT_FREEDOM: + rc = divesoft_freedom_parser_create (&parser, context); + break; default: return DC_STATUS_INVALIDARGS; diff --git a/src/reefnet_sensuspro.c b/src/reefnet_sensuspro.c index 835fbcb..27c2fdb 100644 --- a/src/reefnet_sensuspro.c +++ b/src/reefnet_sensuspro.c @@ -177,7 +177,7 @@ reefnet_sensuspro_handshake (reefnet_sensuspro_device_t *device) // Verify the checksum of the handshake packet. unsigned short crc = array_uint16_le (handshake + SZ_HANDSHAKE); - unsigned short ccrc = checksum_crc16_ccitt (handshake, SZ_HANDSHAKE, 0xffff); + unsigned short ccrc = checksum_crc16_ccitt (handshake, SZ_HANDSHAKE, 0xffff, 0x0000); if (crc != ccrc) { ERROR (abstract->context, "Unexpected answer checksum."); return DC_STATUS_PROTOCOL; @@ -280,7 +280,7 @@ reefnet_sensuspro_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) } unsigned short crc = array_uint16_le (answer + SZ_MEMORY); - unsigned short ccrc = checksum_crc16_ccitt (answer, SZ_MEMORY, 0xffff); + unsigned short ccrc = checksum_crc16_ccitt (answer, SZ_MEMORY, 0xffff, 0x0000); if (crc != ccrc) { ERROR (abstract->context, "Unexpected answer checksum."); return DC_STATUS_PROTOCOL; diff --git a/src/reefnet_sensusultra.c b/src/reefnet_sensusultra.c index 6b059d6..bfe1c6a 100644 --- a/src/reefnet_sensusultra.c +++ b/src/reefnet_sensusultra.c @@ -224,7 +224,7 @@ reefnet_sensusultra_packet (reefnet_sensusultra_device_t *device, unsigned char // Verify the checksum of the packet. unsigned short crc = array_uint16_le (data + size - 2); - unsigned short ccrc = checksum_crc16_ccitt (data + header, size - header - 2, 0xffff); + unsigned short ccrc = checksum_crc16_ccitt (data + header, size - header - 2, 0xffff, 0x0000); if (crc != ccrc) { ERROR (abstract->context, "Unexpected answer checksum."); return DC_STATUS_PROTOCOL; @@ -477,7 +477,7 @@ reefnet_sensusultra_device_write_user (dc_device_t *abstract, const unsigned cha } // Send the checksum to the device. - unsigned short crc = checksum_crc16_ccitt (data, SZ_USER, 0xffff); + unsigned short crc = checksum_crc16_ccitt (data, SZ_USER, 0xffff, 0x0000); rc = reefnet_sensusultra_send_ushort (device, crc); if (rc != DC_STATUS_SUCCESS) return rc; diff --git a/src/seac_screen.c b/src/seac_screen.c index e9b5366..9b3796f 100644 --- a/src/seac_screen.c +++ b/src/seac_screen.c @@ -115,7 +115,7 @@ seac_screen_send (seac_screen_device_t *device, unsigned short cmd, const unsign if (size) { memcpy (packet + 5, data, size); } - crc = checksum_crc16_ccitt (packet, size + 5, 0xFFFF); + crc = checksum_crc16_ccitt (packet, size + 5, 0xFFFF, 0x0000); packet[size + 5] = (crc >> 8) & 0xFF; packet[size + 6] = (crc ) & 0xFF; @@ -165,7 +165,7 @@ seac_screen_receive (seac_screen_device_t *device, unsigned short cmd, unsigned // Verify the checksum. unsigned short crc = array_uint16_be (packet + 1 + length - 2); - unsigned short ccrc = checksum_crc16_ccitt (packet, 1 + length - 2, 0xFFFF); + unsigned short ccrc = checksum_crc16_ccitt (packet, 1 + length - 2, 0xFFFF, 0x0000); if (crc != ccrc) { ERROR (abstract->context, "Unexpected packet checksum (%04x %04x).", crc, ccrc); return DC_STATUS_PROTOCOL; @@ -486,8 +486,8 @@ seac_screen_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); // Check the header checksums. - if (checksum_crc16_ccitt (logbook[i].header, SZ_HEADER / 2, 0xFFFF) != 0 || - checksum_crc16_ccitt (logbook[i].header + SZ_HEADER / 2, SZ_HEADER / 2, 0xFFFF) != 0) { + 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."); status = DC_STATUS_DATAFORMAT; goto error_free_logbook; diff --git a/src/seac_screen_parser.c b/src/seac_screen_parser.c index 8c588d2..c4a855c 100644 --- a/src/seac_screen_parser.c +++ b/src/seac_screen_parser.c @@ -283,8 +283,8 @@ 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) != 0 || - checksum_crc16_ccitt (data + SZ_HEADER / 2, SZ_HEADER / 2, 0xFFFF) != 0) { + 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; } @@ -303,7 +303,7 @@ 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) != 0) { + if (checksum_crc16_ccitt (data + offset, SZ_SAMPLE, 0xFFFF, 0x0000) != 0) { ERROR (abstract->context, "Unexpected sample checksum."); return DC_STATUS_DATAFORMAT; } diff --git a/src/suunto_eonsteel.c b/src/suunto_eonsteel.c index 6ffc7bc..a18d27c 100644 --- a/src/suunto_eonsteel.c +++ b/src/suunto_eonsteel.c @@ -29,6 +29,7 @@ #include "array.h" #include "platform.h" #include "checksum.h" +#include "hdlc.h" #define EONSTEEL 0 #define EONCORE 1 @@ -80,14 +81,10 @@ struct directory_entry { #define MAXDATA_SIZE 2048 #define CRC_SIZE 4 -// HDLC special characters -#define END 0x7E -#define ESC 0x7D -#define ESC_BIT 0x20 - static dc_status_t suunto_eonsteel_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); static dc_status_t suunto_eonsteel_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); static dc_status_t suunto_eonsteel_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime); +static dc_status_t suunto_eonsteel_device_close (dc_device_t *abstract); static const dc_device_vtable_t suunto_eonsteel_device_vtable = { sizeof(suunto_eonsteel_device_t), @@ -98,7 +95,7 @@ static const dc_device_vtable_t suunto_eonsteel_device_vtable = { NULL, /* dump */ suunto_eonsteel_device_foreach, /* foreach */ suunto_eonsteel_device_timesync, /* timesync */ - NULL /* close */ + suunto_eonsteel_device_close /* close */ }; static const char dive_directory[] = "0:/dives"; @@ -127,156 +124,6 @@ static struct directory_entry *alloc_dirent(int type, int len, const char *name) return res; } -static void put_le16(unsigned short val, unsigned char *p) -{ - p[0] = val; - p[1] = val >> 8; -} - -static void put_le32(unsigned int val, unsigned char *p) -{ - p[0] = val; - p[1] = val >> 8; - p[2] = val >> 16; - p[3] = val >> 24; -} - -static dc_status_t -suunto_eonsteel_hdlc_write (suunto_eonsteel_device_t *device, const unsigned char data[], size_t size, size_t *actual) -{ - dc_status_t status = DC_STATUS_SUCCESS; - unsigned char buffer[20]; - size_t nbytes = 0; - - // Start of the packet. - buffer[nbytes++] = END; - - for (size_t i = 0; i < size; ++i) { - unsigned char c = data[i]; - - if (c == END || c == ESC) { - // Append the escape character. - buffer[nbytes++] = ESC; - - // Flush the buffer if necessary. - if (nbytes >= sizeof(buffer)) { - status = dc_iostream_write(device->iostream, buffer, nbytes, NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR(device->base.context, "Failed to send the packet."); - return status; - } - - nbytes = 0; - } - - // Escape the character. - c ^= ESC_BIT; - } - - // Append the character. - buffer[nbytes++] = c; - - // Flush the buffer if necessary. - if (nbytes >= sizeof(buffer)) { - status = dc_iostream_write(device->iostream, buffer, nbytes, NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR(device->base.context, "Failed to send the packet."); - return status; - } - - nbytes = 0; - } - } - - // End of the packet. - buffer[nbytes++] = END; - - // Flush the buffer. - status = dc_iostream_write(device->iostream, buffer, nbytes, NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR(device->base.context, "Failed to send the packet."); - return status; - } - - if (actual) - *actual = size; - - return status; - -} - -static dc_status_t -suunto_eonsteel_hdlc_read (suunto_eonsteel_device_t *device, unsigned char data[], size_t size, size_t *actual) -{ - dc_status_t status = DC_STATUS_SUCCESS; - unsigned char buffer[20]; - unsigned int initialized = 0; - unsigned int escaped = 0; - size_t nbytes = 0; - - while (1) { - // Read a single data packet. - 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 packet."); - return status; - } - - for (size_t i = 0; i < transferred; ++i) { - unsigned char c = buffer[i]; - - if (c == END) { - if (escaped) { - ERROR (device->base.context, "HDLC frame escaped the special character %02x.", c); - return DC_STATUS_PROTOCOL; - } - - if (initialized) { - goto done; - } - - initialized = 1; - continue; - } - - if (!initialized) { - continue; - } - - if (c == ESC) { - if (escaped) { - ERROR (device->base.context, "HDLC frame escaped the special character %02x.", c); - return DC_STATUS_PROTOCOL; - } - escaped = 1; - continue; - } - - if (escaped) { - c ^= ESC_BIT; - escaped = 0; - } - - if (nbytes < size) - data[nbytes] = c; - nbytes++; - - } - } - -done: - if (nbytes > size) { - ERROR(device->base.context, "Insufficient buffer space available."); - return DC_STATUS_PROTOCOL; - } - - if (actual) - *actual = nbytes; - - return status; -} - /* * Get a single 64-byte packet from the dive computer. This handles packet * logging and any obvious packet-level errors, and returns the payload of @@ -338,7 +185,7 @@ suunto_eonsteel_receive_ble(suunto_eonsteel_device_t *device, unsigned char data unsigned char buffer[HEADER_SIZE + MAXDATA_SIZE + CRC_SIZE]; size_t transferred = 0; - rc = suunto_eonsteel_hdlc_read(device, buffer, sizeof(buffer), &transferred); + rc = dc_iostream_read(device->iostream, buffer, sizeof(buffer), &transferred); if (rc != DC_STATUS_SUCCESS) { ERROR(device->base.context, "Failed to receive the packet."); return rc; @@ -352,7 +199,7 @@ suunto_eonsteel_receive_ble(suunto_eonsteel_device_t *device, unsigned char data unsigned int nbytes = transferred - CRC_SIZE; unsigned int crc = array_uint32_le(buffer + nbytes); - unsigned int ccrc = checksum_crc32(buffer, nbytes); + unsigned int ccrc = checksum_crc32r(buffer, nbytes); if (crc != ccrc) { ERROR(device->base.context, "Invalid checksum (expected %08x, received %08x).", ccrc, crc); return DC_STATUS_PROTOCOL; @@ -394,16 +241,16 @@ suunto_eonsteel_send(suunto_eonsteel_device_t *device, buf[1] = size + HEADER_SIZE; // 2-byte LE command word - put_le16(cmd, buf + 2); + array_uint16_le_set(buf + 2, cmd); // 4-byte LE magic value (starts at 1) - put_le32(device->magic, buf + 4); + array_uint32_le_set(buf + 4, device->magic); // 2-byte LE sequence number; - put_le16(device->seq, buf + 8); + array_uint16_le_set(buf + 8, device->seq); // 4-byte LE length - put_le32(size, buf + 10); + array_uint32_le_set(buf + 10, size); // .. followed by actual data if (size) { @@ -411,11 +258,11 @@ suunto_eonsteel_send(suunto_eonsteel_device_t *device, } // 4 byte LE checksum - unsigned int crc = checksum_crc32(buf + 2, size + HEADER_SIZE); - put_le32(crc, buf + 14 + size); + unsigned int crc = checksum_crc32r(buf + 2, size + HEADER_SIZE); + array_uint32_le_set(buf + 14 + size, crc); if (dc_iostream_get_transport(device->iostream) == DC_TRANSPORT_BLE) { - rc = suunto_eonsteel_hdlc_write(device, buf + 2, size + HEADER_SIZE + CRC_SIZE, NULL); + rc = dc_iostream_write(device->iostream, buf + 2, size + HEADER_SIZE + CRC_SIZE, NULL); } else { rc = dc_iostream_write(device->iostream, buf, sizeof(buf) - CRC_SIZE, NULL); } @@ -591,8 +438,8 @@ read_file(suunto_eonsteel_device_t *eon, const char *filename, dc_buffer_t *buf) ask = size; if (ask > 1024) ask = 1024; - put_le32(1234, cmdbuf+0); // Not file offset, after all - put_le32(ask, cmdbuf+4); // Size of read + array_uint32_le_set(cmdbuf + 0, 1234); // Not file offset, after all + array_uint32_le_set(cmdbuf + 4, ask); // Size of read rc = suunto_eonsteel_transfer(eon, CMD_FILE_READ, cmdbuf, 8, result, sizeof(result), &n); if (rc != DC_STATUS_SUCCESS) { @@ -705,7 +552,7 @@ get_file_list(suunto_eonsteel_device_t *eon, struct directory_entry **res) unsigned int n = 0; unsigned int cmdlen; - put_le32(0, cmd); + array_uint32_le_set(cmd, 0); memcpy(cmd + 4, dive_directory, sizeof(dive_directory)); cmdlen = 4 + sizeof(dive_directory); rc = suunto_eonsteel_transfer(eon, CMD_DIR_OPEN, @@ -771,6 +618,7 @@ suunto_eonsteel_device_open(dc_device_t **out, dc_context_t *context, dc_iostrea { dc_status_t status = DC_STATUS_SUCCESS; suunto_eonsteel_device_t *eon = NULL; + dc_transport_t transport = dc_iostream_get_transport (iostream); if (out == NULL) return DC_STATUS_INVALIDARGS; @@ -780,17 +628,26 @@ suunto_eonsteel_device_open(dc_device_t **out, dc_context_t *context, dc_iostrea return DC_STATUS_NOMEMORY; // Set up the magic handshake fields - eon->iostream = iostream; eon->model = model; eon->magic = INIT_MAGIC; eon->seq = INIT_SEQ; memset (eon->version, 0, sizeof (eon->version)); memset (eon->fingerprint, 0, sizeof (eon->fingerprint)); + if (transport == DC_TRANSPORT_BLE) { + status = dc_hdlc_open (&eon->iostream, context, iostream, 20, 20); + if (status != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to create the HDLC stream."); + goto error_free; + } + } else { + eon->iostream = iostream; + } + status = dc_iostream_set_timeout(eon->iostream, 5000); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to set the timeout."); - goto error_free; + goto error_free_iostream; } const unsigned char init[] = {0x02, 0x00, 0x2a, 0x00}; @@ -798,18 +655,34 @@ suunto_eonsteel_device_open(dc_device_t **out, dc_context_t *context, dc_iostrea init, sizeof(init), eon->version, sizeof(eon->version), NULL); if (status != DC_STATUS_SUCCESS) { ERROR(context, "unable to initialize device"); - goto error_free; + goto error_free_iostream; } *out = (dc_device_t *) eon; return DC_STATUS_SUCCESS; +error_free_iostream: + if (transport == DC_TRANSPORT_BLE) { + dc_iostream_close (eon->iostream); + } error_free: - free(eon); + dc_device_deallocate ((dc_device_t *) eon); return status; } +static dc_status_t +suunto_eonsteel_device_close (dc_device_t *abstract) +{ + suunto_eonsteel_device_t *device = (suunto_eonsteel_device_t *) abstract; + + if (dc_iostream_get_transport (device->iostream) == DC_TRANSPORT_BLE) { + return dc_iostream_close (device->iostream); + } + + return DC_STATUS_SUCCESS; +} + static dc_status_t suunto_eonsteel_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size) { @@ -890,7 +763,7 @@ suunto_eonsteel_device_foreach(dc_device_t *abstract, dc_dive_callback_t callbac break; } - put_le32(time, buf); + array_uint32_le_set(buf, time); if (memcmp (buf, eon->fingerprint, sizeof (eon->fingerprint)) == 0) { skip = 1; diff --git a/src/uwatec_smart_parser.c b/src/uwatec_smart_parser.c index 39a927c..bd849a8 100644 --- a/src/uwatec_smart_parser.c +++ b/src/uwatec_smart_parser.c @@ -893,25 +893,6 @@ uwatec_galileo_identify (unsigned char value) } -static unsigned int -uwatec_smart_fixsignbit (unsigned int x, unsigned int n) -{ - if (n <= 0 || n > 32) - return 0; - - unsigned int signbit = (1 << (n - 1)); - unsigned int mask = (signbit - 1); - - // When turning a two's-complement number with a certain number - // of bits into one with more bits, the sign bit must be repeated - // in all the extra bits. - if ((x & signbit) == signbit) - return x | ~mask; - else - return x & mask; -} - - static dc_status_t uwatec_smart_parse (uwatec_smart_parser_t *parser, dc_sample_callback_t callback, void *userdata) { @@ -1018,7 +999,7 @@ uwatec_smart_parse (uwatec_smart_parser_t *parser, dc_sample_callback_t callback } // Fix the sign bit. - signed int svalue = uwatec_smart_fixsignbit (value, nbits); + signed int svalue = signextend (value, nbits); // Parse the value. unsigned int idx = 0;